change charset for it_IT
[ardour2.git] / gtk2_ardour / editor_mouse.cc
blob2fed8e14f6d8ac54cece075ad110e76b97bdad38
2 /*
3 Copyright (C) 2000-2001 Paul Davis
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include <cassert>
22 #include <cstdlib>
23 #include <stdint.h>
24 #include <cmath>
25 #include <set>
26 #include <string>
27 #include <algorithm>
28 #include <sys/time.h>
30 #include <pbd/error.h>
31 #include <gtkmm2ext/utils.h>
32 #include <pbd/memento_command.h>
34 #include "ardour_ui.h"
35 #include "editor.h"
36 #include "time_axis_view.h"
37 #include "audio_time_axis.h"
38 #include "audio_region_view.h"
39 #include "marker.h"
40 #include "streamview.h"
41 #include "region_gain_line.h"
42 #include "automation_time_axis.h"
43 #include "prompter.h"
44 #include "utils.h"
45 #include "selection.h"
46 #include "keyboard.h"
47 #include "editing.h"
48 #include "rgb_macros.h"
50 #include <ardour/types.h>
51 #include <ardour/profile.h>
52 #include <ardour/route.h>
53 #include <ardour/audio_track.h>
54 #include <ardour/audio_diskstream.h>
55 #include <ardour/playlist.h>
56 #include <ardour/audioplaylist.h>
57 #include <ardour/audioregion.h>
58 #include <ardour/dB.h>
59 #include <ardour/utils.h>
60 #include <ardour/region_factory.h>
62 #include <bitset>
64 #include "i18n.h"
66 using namespace std;
67 using namespace ARDOUR;
68 using namespace PBD;
69 using namespace sigc;
70 using namespace Gtk;
71 using namespace Editing;
73 bool
74 Editor::mouse_frame (nframes64_t& where, bool& in_track_canvas) const
76 int x, y;
77 double wx, wy;
78 Gdk::ModifierType mask;
79 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
80 Glib::RefPtr<const Gdk::Window> pointer_window;
82 if (!canvas_window) {
83 return false;
86 pointer_window = canvas_window->get_pointer (x, y, mask);
88 if (pointer_window == track_canvas->get_bin_window()) {
89 wx = x;
90 wy = y;
91 in_track_canvas = true;
93 } else {
94 in_track_canvas = false;
95 return false;
98 GdkEvent event;
99 event.type = GDK_BUTTON_RELEASE;
100 event.button.x = wx;
101 event.button.y = wy;
103 where = event_frame (&event, 0, 0);
104 return true;
107 nframes64_t
108 Editor::event_frame (GdkEvent* event, double* pcx, double* pcy) const
110 double cx, cy;
112 if (pcx == 0) {
113 pcx = &cx;
115 if (pcy == 0) {
116 pcy = &cy;
119 *pcx = 0;
120 *pcy = 0;
122 switch (event->type) {
123 case GDK_BUTTON_RELEASE:
124 case GDK_BUTTON_PRESS:
125 case GDK_2BUTTON_PRESS:
126 case GDK_3BUTTON_PRESS:
128 *pcx = event->button.x;
129 *pcy = event->button.y;
130 _trackview_group->w2i(*pcx, *pcy);
131 break;
132 case GDK_MOTION_NOTIFY:
134 *pcx = event->motion.x;
135 *pcy = event->motion.y;
136 _trackview_group->w2i(*pcx, *pcy);
137 break;
138 case GDK_ENTER_NOTIFY:
139 case GDK_LEAVE_NOTIFY:
140 track_canvas->w2c(event->crossing.x, event->crossing.y, *pcx, *pcy);
141 break;
142 case GDK_KEY_PRESS:
143 case GDK_KEY_RELEASE:
144 // track_canvas->w2c(event->key.x, event->key.y, *pcx, *pcy);
145 break;
146 default:
147 warning << string_compose (_("Editor::event_frame() used on unhandled event type %1"), event->type) << endmsg;
148 break;
151 /* note that pixel_to_frame() never returns less than zero, so even if the pixel
152 position is negative (as can be the case with motion events in particular),
153 the frame location is always positive.
156 return pixel_to_frame (*pcx);
159 void
160 Editor::mouse_mode_toggled (MouseMode m)
162 if (ignore_mouse_mode_toggle) {
163 return;
166 switch (m) {
167 case MouseRange:
168 if (mouse_select_button.get_active()) {
169 set_mouse_mode (m);
171 break;
173 case MouseObject:
174 if (mouse_move_button.get_active()) {
175 set_mouse_mode (m);
177 break;
179 case MouseGain:
180 if (mouse_gain_button.get_active()) {
181 set_mouse_mode (m);
183 break;
185 case MouseZoom:
186 if (mouse_zoom_button.get_active()) {
187 set_mouse_mode (m);
189 break;
191 case MouseTimeFX:
192 if (mouse_timefx_button.get_active()) {
193 set_mouse_mode (m);
195 break;
197 case MouseAudition:
198 if (mouse_audition_button.get_active()) {
199 set_mouse_mode (m);
201 break;
203 default:
204 break;
208 Gdk::Cursor*
209 Editor::which_grabber_cursor ()
211 switch (_edit_point) {
212 case EditAtMouse:
213 return grabber_edit_point_cursor;
214 break;
215 default:
216 break;
218 return grabber_cursor;
221 void
222 Editor::set_canvas_cursor ()
224 switch (mouse_mode) {
225 case MouseRange:
226 current_canvas_cursor = selector_cursor;
227 break;
229 case MouseObject:
230 current_canvas_cursor = which_grabber_cursor();
231 break;
233 case MouseGain:
234 current_canvas_cursor = cross_hair_cursor;
235 break;
237 case MouseZoom:
238 current_canvas_cursor = zoom_cursor;
239 break;
241 case MouseTimeFX:
242 current_canvas_cursor = time_fx_cursor; // just use playhead
243 break;
245 case MouseAudition:
246 current_canvas_cursor = speaker_cursor;
247 break;
250 if (is_drawable()) {
251 track_canvas->get_window()->set_cursor(*current_canvas_cursor);
255 void
256 Editor::set_mouse_mode (MouseMode m, bool force)
258 if (drag_info.item) {
259 return;
262 if (!force && m == mouse_mode) {
263 return;
266 mouse_mode = m;
268 instant_save ();
270 if (mouse_mode != MouseRange) {
272 /* in all modes except range, hide the range selection,
273 show the object (region) selection.
276 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
277 (*i)->set_should_show_selection (true);
279 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
280 (*i)->hide_selection ();
283 } else {
286 in range mode,show the range selection.
289 for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
290 if ((*i)->get_selected()) {
291 (*i)->show_selection (selection->time);
296 /* XXX the hack of unsetting all other buttons should go
297 away once GTK2 allows us to use regular radio buttons drawn like
298 normal buttons, rather than my silly GroupedButton hack.
301 ignore_mouse_mode_toggle = true;
303 switch (mouse_mode) {
304 case MouseRange:
305 mouse_select_button.set_active (true);
306 break;
308 case MouseObject:
309 mouse_move_button.set_active (true);
310 break;
312 case MouseGain:
313 mouse_gain_button.set_active (true);
314 break;
316 case MouseZoom:
317 mouse_zoom_button.set_active (true);
318 break;
320 case MouseTimeFX:
321 mouse_timefx_button.set_active (true);
322 break;
324 case MouseAudition:
325 mouse_audition_button.set_active (true);
326 break;
329 ignore_mouse_mode_toggle = false;
331 set_canvas_cursor ();
334 void
335 Editor::step_mouse_mode (bool next)
337 switch (current_mouse_mode()) {
338 case MouseObject:
339 if (next) {
340 if (Profile->get_sae()) {
341 set_mouse_mode (MouseZoom);
342 } else {
343 set_mouse_mode (MouseRange);
345 } else {
346 set_mouse_mode (MouseTimeFX);
348 break;
350 case MouseRange:
351 if (next) set_mouse_mode (MouseZoom);
352 else set_mouse_mode (MouseObject);
353 break;
355 case MouseZoom:
356 if (next) {
357 if (Profile->get_sae()) {
358 set_mouse_mode (MouseTimeFX);
359 } else {
360 set_mouse_mode (MouseGain);
362 } else {
363 if (Profile->get_sae()) {
364 set_mouse_mode (MouseObject);
365 } else {
366 set_mouse_mode (MouseRange);
369 break;
371 case MouseGain:
372 if (next) set_mouse_mode (MouseTimeFX);
373 else set_mouse_mode (MouseZoom);
374 break;
376 case MouseTimeFX:
377 if (next) {
378 set_mouse_mode (MouseAudition);
379 } else {
380 if (Profile->get_sae()) {
381 set_mouse_mode (MouseZoom);
382 } else {
383 set_mouse_mode (MouseGain);
386 break;
388 case MouseAudition:
389 if (next) set_mouse_mode (MouseObject);
390 else set_mouse_mode (MouseTimeFX);
391 break;
395 void
396 Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
398 /* in object/audition/timefx/gain-automation mode,
399 any button press sets the selection if the object
400 can be selected. this is a bit of hack, because
401 we want to avoid this if the mouse operation is a
402 region alignment.
404 note: not dbl-click or triple-click
407 if (((mouse_mode != MouseObject) &&
408 (mouse_mode != MouseAudition || item_type != RegionItem) &&
409 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
410 (mouse_mode != MouseGain) &&
411 (mouse_mode != MouseRange)) ||
413 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
415 return;
418 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
420 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
422 /* almost no selection action on modified button-2 or button-3 events */
424 if (item_type != RegionItem && event->button.button != 2) {
425 return;
430 Selection::Operation op = Keyboard::selection_type (event->button.state);
431 bool press = (event->type == GDK_BUTTON_PRESS);
433 // begin_reversible_command (_("select on click"));
435 switch (item_type) {
436 case RegionItem:
437 if (mouse_mode != MouseRange) {
438 set_selected_regionview_from_click (press, op, true);
439 } else if (event->type == GDK_BUTTON_PRESS) {
440 set_selected_track_as_side_effect ();
442 break;
444 case RegionViewNameHighlight:
445 case RegionViewName:
446 if (mouse_mode != MouseRange) {
447 set_selected_regionview_from_click (press, op, true);
448 } else if (event->type == GDK_BUTTON_PRESS) {
449 set_selected_track_as_side_effect ();
451 break;
453 case FadeInHandleItem:
454 case FadeInItem:
455 case FadeOutHandleItem:
456 case FadeOutItem:
457 if (mouse_mode != MouseRange) {
458 set_selected_regionview_from_click (press, op, true);
459 } else if (event->type == GDK_BUTTON_PRESS) {
460 set_selected_track_as_side_effect ();
462 break;
464 case GainAutomationControlPointItem:
465 case PanAutomationControlPointItem:
466 case RedirectAutomationControlPointItem:
467 set_selected_track_as_side_effect ();
468 if (mouse_mode != MouseRange) {
469 set_selected_control_point_from_click (op, false);
471 break;
473 case StreamItem:
474 /* for context click or range selection, select track */
475 if (event->button.button == 3) {
476 set_selected_track_as_side_effect ();
477 } else if (event->type == GDK_BUTTON_PRESS && mouse_mode == MouseRange) {
478 set_selected_track_as_side_effect ();
480 break;
482 case AutomationTrackItem:
483 set_selected_track_as_side_effect (true);
484 break;
486 default:
487 break;
491 const static double ZERO_GAIN_FRACTION = gain_to_slider_position(dB_to_coefficient(0.0));
493 bool
494 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
496 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
498 if (canvas_window) {
499 Glib::RefPtr<const Gdk::Window> pointer_window;
500 int x, y;
501 double wx, wy;
502 Gdk::ModifierType mask;
504 pointer_window = canvas_window->get_pointer (x, y, mask);
506 if (pointer_window == track_canvas->get_bin_window()) {
507 track_canvas->window_to_world (x, y, wx, wy);
508 allow_vertical_scroll = true;
509 } else {
510 allow_vertical_scroll = false;
514 track_canvas->grab_focus();
516 if (session && session->actively_recording()) {
517 return true;
520 button_selection (item, event, item_type);
522 //ctrl-drag or right-click-drag on a "range" ruler should start a range drag
523 if (event->type == GDK_BUTTON_PRESS) {
524 if (event->button.button == 3 || ( (event->button.button == 1) && (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier) ))) {
525 if (item_type == TransportMarkerBarItem) {
526 start_range_markerbar_op (item, event, CreateTransportMarker);
527 return true;
529 if (item_type == RangeMarkerBarItem) {
530 start_range_markerbar_op (item, event, CreateRangeMarker);
531 return true;
533 if (item_type == CdMarkerBarItem) {
534 start_range_markerbar_op (item, event, CreateCDMarker);
535 return true;
540 if (drag_info.item == 0 &&
541 (Keyboard::is_delete_event (&event->button) ||
542 Keyboard::is_context_menu_event (&event->button) ||
543 Keyboard::is_edit_event (&event->button))) {
545 /* handled by button release */
546 return true;
549 switch (event->button.button) {
550 case 1:
552 if (event->type == GDK_BUTTON_PRESS) {
554 if (drag_info.item) {
555 drag_info.item->ungrab (event->button.time);
558 /* single mouse clicks on any of these item types operate
559 independent of mouse mode, mostly because they are
560 not on the main track canvas or because we want
561 them to be modeless.
564 switch (item_type) {
565 case PlayheadCursorItem:
566 start_cursor_grab (item, event);
567 return true;
569 case MarkerItem:
570 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
571 hide_marker (item, event);
572 } else {
573 start_marker_grab (item, event);
575 return true;
577 case TempoMarkerItem:
578 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
579 start_tempo_marker_copy_grab (item, event);
580 } else {
581 start_tempo_marker_grab (item, event);
583 return true;
585 case MeterMarkerItem:
586 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
587 start_meter_marker_copy_grab (item, event);
588 } else {
589 start_meter_marker_grab (item, event);
591 return true;
593 case MarkerBarItem:
594 case TempoBarItem:
595 case MeterBarItem:
596 case TransportMarkerBarItem:
597 case RangeMarkerBarItem:
598 case CdMarkerBarItem:
599 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
600 start_cursor_grab_no_stop(&playhead_cursor->canvas_item, event);
602 return true;
603 break;
605 default:
606 break;
610 switch (mouse_mode) {
611 case MouseRange:
612 switch (item_type) {
613 case StartSelectionTrimItem:
614 start_selection_op (item, event, SelectionStartTrim);
615 break;
617 case EndSelectionTrimItem:
618 start_selection_op (item, event, SelectionEndTrim);
619 break;
621 case SelectionItem:
622 if (Keyboard::modifier_state_contains
623 (event->button.state, Keyboard::ModifierMask(Keyboard::SecondaryModifier))) {
624 // contains and not equals because I can't use alt as a modifier alone.
625 start_selection_grab (item, event);
626 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
627 /* grab selection for moving */
628 start_selection_op (item, event, SelectionMove);
629 } else {
630 /* this was debated, but decided the more common action was to
631 make a new selection */
632 start_selection_op (item, event, CreateSelection);
634 break;
636 default:
637 start_selection_op (item, event, CreateSelection);
639 return true;
640 break;
642 case MouseObject:
643 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
644 event->type == GDK_BUTTON_PRESS) {
646 start_rubberband_select (item, event);
648 } else if (event->type == GDK_BUTTON_PRESS) {
650 switch (item_type) {
651 case FadeInHandleItem:
652 start_fade_in_grab (item, event);
653 return true;
655 case FadeOutHandleItem:
656 start_fade_out_grab (item, event);
657 return true;
659 case RegionItem:
660 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
661 start_region_copy_grab (item, event);
662 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
663 start_region_brush_grab (item, event);
664 } else {
665 start_region_grab (item, event);
667 break;
669 case RegionViewNameHighlight:
670 start_trim (item, event);
671 return true;
672 break;
674 case RegionViewName:
675 /* rename happens on edit clicks */
676 start_trim (clicked_regionview->get_name_highlight(), event);
677 return true;
678 break;
680 case GainAutomationControlPointItem:
681 case PanAutomationControlPointItem:
682 case RedirectAutomationControlPointItem:
683 start_control_point_grab (item, event);
684 return true;
685 break;
687 case GainAutomationLineItem:
688 case PanAutomationLineItem:
689 case RedirectAutomationLineItem:
690 start_line_grab_from_line (item, event);
691 return true;
692 break;
694 case StreamItem:
695 case AutomationTrackItem:
696 start_rubberband_select (item, event);
697 break;
699 /* <CMT Additions> */
700 case ImageFrameHandleStartItem:
701 imageframe_start_handle_op(item, event) ;
702 return(true) ;
703 break ;
704 case ImageFrameHandleEndItem:
705 imageframe_end_handle_op(item, event) ;
706 return(true) ;
707 break ;
708 case MarkerViewHandleStartItem:
709 markerview_item_start_handle_op(item, event) ;
710 return(true) ;
711 break ;
712 case MarkerViewHandleEndItem:
713 markerview_item_end_handle_op(item, event) ;
714 return(true) ;
715 break ;
716 /* </CMT Additions> */
718 /* <CMT Additions> */
719 case MarkerViewItem:
720 start_markerview_grab(item, event) ;
721 break ;
722 case ImageFrameItem:
723 start_imageframe_grab(item, event) ;
724 break ;
725 /* </CMT Additions> */
727 case MarkerBarItem:
729 break;
731 default:
732 break;
735 return true;
736 break;
738 case MouseGain:
739 switch (item_type) {
740 case RegionItem:
741 /* start a grab so that if we finish after moving
742 we can tell what happened.
744 drag_info.item = item;
745 drag_info.motion_callback = &Editor::region_gain_motion_callback;
746 drag_info.finished_callback = 0;
747 start_grab (event, current_canvas_cursor);
748 break;
750 case GainControlPointItem:
751 start_control_point_grab (item, event);
752 return true;
754 case GainLineItem:
755 start_line_grab_from_line (item, event);
756 return true;
758 case GainAutomationControlPointItem:
759 case PanAutomationControlPointItem:
760 case RedirectAutomationControlPointItem:
761 start_control_point_grab (item, event);
762 return true;
763 break;
765 default:
766 break;
768 return true;
769 break;
771 switch (item_type) {
772 case GainAutomationControlPointItem:
773 case PanAutomationControlPointItem:
774 case RedirectAutomationControlPointItem:
775 start_control_point_grab (item, event);
776 break;
778 case GainAutomationLineItem:
779 case PanAutomationLineItem:
780 case RedirectAutomationLineItem:
781 start_line_grab_from_line (item, event);
782 break;
784 case RegionItem:
785 // XXX need automation mode to identify which
786 // line to use
787 // start_line_grab_from_regionview (item, event);
788 break;
790 default:
791 break;
793 return true;
794 break;
796 case MouseZoom:
797 if (event->type == GDK_BUTTON_PRESS) {
798 start_mouse_zoom (item, event);
801 return true;
802 break;
804 case MouseTimeFX:
805 if (item_type == RegionItem) {
806 start_time_fx (item, event);
808 break;
810 case MouseAudition:
811 _scrubbing = true;
812 scrub_reversals = 0;
813 scrub_reverse_distance = 0;
814 last_scrub_x = event->button.x;
815 scrubbing_direction = 0;
816 track_canvas->get_window()->set_cursor (*transparent_cursor);
817 /* rest handled in motion & release */
818 break;
820 default:
821 break;
823 break;
825 case 2:
826 switch (mouse_mode) {
827 case MouseObject:
828 if (event->type == GDK_BUTTON_PRESS) {
829 switch (item_type) {
830 case RegionItem:
831 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
832 start_region_copy_grab (item, event);
833 } else {
834 start_region_grab (item, event);
836 return true;
837 break;
839 case GainAutomationControlPointItem:
840 case PanAutomationControlPointItem:
841 case RedirectAutomationControlPointItem:
842 start_control_point_grab (item, event);
843 return true;
844 break;
846 default:
847 break;
852 switch (item_type) {
853 case RegionViewNameHighlight:
854 start_trim (item, event);
855 return true;
856 break;
858 case RegionViewName:
859 start_trim (clicked_regionview->get_name_highlight(), event);
860 return true;
861 break;
863 default:
864 break;
867 break;
869 case MouseRange:
870 if (event->type == GDK_BUTTON_PRESS) {
871 /* relax till release */
873 return true;
874 break;
877 case MouseZoom:
878 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
879 temporal_zoom_session();
880 } else {
881 temporal_zoom_to_frame (true, event_frame(event));
883 return true;
884 break;
886 default:
887 break;
890 break;
892 case 3:
893 break;
895 default:
896 break;
900 return false;
903 bool
904 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
906 nframes64_t where = event_frame (event, 0, 0);
907 AutomationTimeAxisView* atv = 0;
909 /* no action if we're recording */
911 if (session && session->actively_recording()) {
912 return true;
915 /* first, see if we're finishing a drag ... */
917 if (drag_info.item) {
918 if (end_grab (item, event)) {
919 /* grab dragged, so do nothing else */
920 return true;
924 button_selection (item, event, item_type);
926 /* edit events get handled here */
928 if (drag_info.item == 0 && Keyboard::is_edit_event (&event->button)) {
929 switch (item_type) {
930 case RegionItem:
931 edit_region ();
932 break;
934 case TempoMarkerItem:
935 edit_tempo_marker (item);
936 break;
938 case MeterMarkerItem:
939 edit_meter_marker (item);
940 break;
942 case RegionViewName:
943 if (clicked_regionview->name_active()) {
944 return mouse_rename_region (item, event);
946 break;
948 default:
949 break;
951 return true;
954 /* context menu events get handled here */
956 if (Keyboard::is_context_menu_event (&event->button)) {
958 if (drag_info.item == 0) {
960 /* no matter which button pops up the context menu, tell the menu
961 widget to use button 1 to drive menu selection.
964 switch (item_type) {
965 case FadeInItem:
966 case FadeInHandleItem:
967 case FadeOutItem:
968 case FadeOutHandleItem:
969 popup_fade_context_menu (1, event->button.time, item, item_type);
970 break;
972 case StreamItem:
973 popup_track_context_menu (1, event->button.time, item_type, false, where);
974 break;
976 case RegionItem:
977 case RegionViewNameHighlight:
978 case RegionViewName:
979 popup_track_context_menu (1, event->button.time, item_type, false, where);
980 break;
982 case SelectionItem:
983 popup_track_context_menu (1, event->button.time, item_type, true, where);
984 break;
986 case AutomationTrackItem:
987 popup_track_context_menu (1, event->button.time, item_type, false, where);
988 break;
990 case MarkerBarItem:
991 case RangeMarkerBarItem:
992 case TransportMarkerBarItem:
993 case CdMarkerBarItem:
994 case TempoBarItem:
995 case MeterBarItem:
996 popup_ruler_menu (where, item_type);
997 break;
999 case MarkerItem:
1000 marker_context_menu (&event->button, item);
1001 break;
1003 case TempoMarkerItem:
1004 tm_marker_context_menu (&event->button, item);
1005 break;
1007 case MeterMarkerItem:
1008 tm_marker_context_menu (&event->button, item);
1009 break;
1011 case CrossfadeViewItem:
1012 popup_track_context_menu (1, event->button.time, item_type, false, where);
1013 break;
1015 /* <CMT Additions> */
1016 case ImageFrameItem:
1017 popup_imageframe_edit_menu(1, event->button.time, item, true) ;
1018 break ;
1019 case ImageFrameTimeAxisItem:
1020 popup_imageframe_edit_menu(1, event->button.time, item, false) ;
1021 break ;
1022 case MarkerViewItem:
1023 popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ;
1024 break ;
1025 case MarkerTimeAxisItem:
1026 popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
1027 break ;
1028 /* <CMT Additions> */
1031 default:
1032 break;
1035 return true;
1039 /* delete events get handled here */
1041 if (drag_info.item == 0 && Keyboard::is_delete_event (&event->button)) {
1043 switch (item_type) {
1044 case TempoMarkerItem:
1045 remove_tempo_marker (item);
1046 break;
1048 case MeterMarkerItem:
1049 remove_meter_marker (item);
1050 break;
1052 case MarkerItem:
1053 remove_marker (*item, event);
1054 break;
1056 case RegionItem:
1057 if (mouse_mode == MouseObject) {
1058 remove_clicked_region ();
1060 break;
1062 case GainControlPointItem:
1063 if (mouse_mode == MouseGain) {
1064 remove_gain_control_point (item, event);
1066 break;
1068 case GainAutomationControlPointItem:
1069 case PanAutomationControlPointItem:
1070 case RedirectAutomationControlPointItem:
1071 remove_control_point (item, event);
1072 break;
1074 default:
1075 break;
1077 return true;
1080 switch (event->button.button) {
1081 case 1:
1083 switch (item_type) {
1084 /* see comments in button_press_handler */
1085 case PlayheadCursorItem:
1086 case MarkerItem:
1087 case GainLineItem:
1088 case GainAutomationLineItem:
1089 case PanAutomationLineItem:
1090 case RedirectAutomationLineItem:
1091 case StartSelectionTrimItem:
1092 case EndSelectionTrimItem:
1093 return true;
1095 case MarkerBarItem:
1096 if (!_dragging_playhead) {
1097 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
1098 snap_to (where, 0, true);
1100 mouse_add_new_marker (where);
1102 return true;
1104 case CdMarkerBarItem:
1105 if (!_dragging_playhead) {
1106 // if we get here then a dragged range wasn't done
1107 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
1108 snap_to (where, 0, true);
1110 mouse_add_new_marker (where, true);
1112 return true;
1114 case TempoBarItem:
1115 if (!_dragging_playhead) {
1116 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
1117 snap_to (where);
1119 mouse_add_new_tempo_event (where);
1121 return true;
1123 case MeterBarItem:
1124 if (!_dragging_playhead) {
1125 mouse_add_new_meter_event (pixel_to_frame (event->button.x));
1127 return true;
1128 break;
1130 default:
1131 break;
1134 switch (mouse_mode) {
1135 case MouseObject:
1136 switch (item_type) {
1137 case AutomationTrackItem:
1138 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_trackview);
1139 if (atv) {
1140 atv->add_automation_event (item, event, where, event->button.y);
1142 return true;
1144 break;
1146 default:
1147 break;
1149 break;
1151 case MouseGain:
1152 // Gain only makes sense for audio regions
1154 if (!dynamic_cast<AudioRegionView*>(clicked_regionview)) {
1155 break;
1158 switch (item_type) {
1159 case RegionItem:
1160 /* check that we didn't drag before releasing, since
1161 its really annoying to create new control
1162 points when doing this.
1164 if (drag_info.first_move) {
1165 dynamic_cast<AudioRegionView*>(clicked_regionview)->add_gain_point_event (item, event);
1167 return true;
1168 break;
1170 case AutomationTrackItem:
1171 dynamic_cast<AutomationTimeAxisView*>(clicked_trackview)->
1172 add_automation_event (item, event, where, event->button.y);
1173 return true;
1174 break;
1175 default:
1176 break;
1178 break;
1180 case MouseAudition:
1181 _scrubbing = false;
1182 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1183 if (scrubbing_direction == 0) {
1184 /* no drag, just a click */
1185 switch (item_type) {
1186 case RegionItem:
1187 play_selected_region ();
1188 break;
1189 default:
1190 break;
1192 } else {
1193 /* make sure we stop */
1194 session->request_transport_speed (0.0);
1196 break;
1198 default:
1199 break;
1203 return true;
1204 break;
1207 case 2:
1208 switch (mouse_mode) {
1210 case MouseObject:
1211 switch (item_type) {
1212 case RegionItem:
1213 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1214 raise_region ();
1215 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1216 lower_region ();
1217 } else {
1218 // Button2 click is unused
1220 return true;
1222 break;
1224 default:
1225 break;
1227 break;
1229 case MouseRange:
1231 // x_style_paste (where, 1.0);
1232 return true;
1233 break;
1235 default:
1236 break;
1239 break;
1241 case 3:
1242 break;
1244 default:
1245 break;
1247 return false;
1250 bool
1251 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1253 ControlPoint* cp;
1254 Marker * marker;
1255 double fraction;
1257 if (last_item_entered != item) {
1258 last_item_entered = item;
1259 last_item_entered_n = 0;
1262 switch (item_type) {
1263 case GainControlPointItem:
1264 if (mouse_mode == MouseGain) {
1265 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1266 cp->set_visible (true);
1268 double at_x, at_y;
1269 at_x = cp->get_x();
1270 at_y = cp->get_y ();
1271 cp->item->i2w (at_x, at_y);
1272 at_x += 10.0;
1273 at_y += 10.0;
1275 fraction = 1.0 - (cp->get_y() / cp->line.height());
1277 if (is_drawable() && !_scrubbing) {
1278 track_canvas->get_window()->set_cursor (*fader_cursor);
1281 last_item_entered_n++;
1282 set_verbose_canvas_cursor (cp->line.get_verbose_cursor_string (fraction), at_x, at_y);
1283 if (last_item_entered_n < 10) {
1284 show_verbose_canvas_cursor ();
1287 break;
1289 case GainAutomationControlPointItem:
1290 case PanAutomationControlPointItem:
1291 case RedirectAutomationControlPointItem:
1292 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1293 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1294 cp->set_visible (true);
1296 double at_x, at_y;
1297 at_x = cp->get_x();
1298 at_y = cp->get_y ();
1299 cp->item->i2w (at_x, at_y);
1300 at_x += 10.0;
1301 at_y += 10.0;
1303 fraction = 1.0 - (cp->get_y() / cp->line.height());
1305 set_verbose_canvas_cursor (cp->line.get_verbose_cursor_string (fraction), at_x, at_y);
1306 show_verbose_canvas_cursor ();
1308 if (is_drawable()) {
1309 track_canvas->get_window()->set_cursor (*fader_cursor);
1312 break;
1314 case GainLineItem:
1315 if (mouse_mode == MouseGain) {
1316 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1317 if (line)
1318 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
1319 if (is_drawable()) {
1320 track_canvas->get_window()->set_cursor (*fader_cursor);
1323 break;
1325 case GainAutomationLineItem:
1326 case RedirectAutomationLineItem:
1327 case PanAutomationLineItem:
1328 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1330 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1331 if (line)
1332 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
1334 if (is_drawable()) {
1335 track_canvas->get_window()->set_cursor (*fader_cursor);
1338 break;
1340 case RegionViewNameHighlight:
1341 if (is_drawable() && mouse_mode == MouseObject) {
1342 track_canvas->get_window()->set_cursor (*trimmer_cursor);
1344 break;
1346 case StartSelectionTrimItem:
1347 case EndSelectionTrimItem:
1348 /* <CMT Additions> */
1349 case ImageFrameHandleStartItem:
1350 case ImageFrameHandleEndItem:
1351 case MarkerViewHandleStartItem:
1352 case MarkerViewHandleEndItem:
1353 /* </CMT Additions> */
1355 if (is_drawable()) {
1356 track_canvas->get_window()->set_cursor (*trimmer_cursor);
1358 break;
1360 case PlayheadCursorItem:
1361 if (is_drawable()) {
1362 switch (_edit_point) {
1363 case EditAtMouse:
1364 track_canvas->get_window()->set_cursor (*grabber_edit_point_cursor);
1365 break;
1366 default:
1367 track_canvas->get_window()->set_cursor (*grabber_cursor);
1368 break;
1371 break;
1373 case RegionViewName:
1375 /* when the name is not an active item, the entire name highlight is for trimming */
1377 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1378 if (mouse_mode == MouseObject && is_drawable()) {
1379 track_canvas->get_window()->set_cursor (*trimmer_cursor);
1382 break;
1385 case AutomationTrackItem:
1386 if (is_drawable()) {
1387 Gdk::Cursor *cursor;
1388 switch (mouse_mode) {
1389 case MouseRange:
1390 cursor = selector_cursor;
1391 break;
1392 case MouseZoom:
1393 cursor = zoom_cursor;
1394 break;
1395 default:
1396 cursor = cross_hair_cursor;
1397 break;
1400 track_canvas->get_window()->set_cursor (*cursor);
1402 AutomationTimeAxisView* atv;
1403 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1404 clear_entered_track = false;
1405 set_entered_track (atv);
1408 break;
1410 case MarkerBarItem:
1411 case RangeMarkerBarItem:
1412 case TransportMarkerBarItem:
1413 case CdMarkerBarItem:
1414 case MeterBarItem:
1415 case TempoBarItem:
1416 if (is_drawable()) {
1417 track_canvas->get_window()->set_cursor (*timebar_cursor);
1419 break;
1421 case MarkerItem:
1422 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1423 break;
1425 entered_marker = marker;
1426 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1427 // fall through
1428 case MeterMarkerItem:
1429 case TempoMarkerItem:
1430 if (is_drawable()) {
1431 track_canvas->get_window()->set_cursor (*timebar_cursor);
1433 break;
1434 case FadeInHandleItem:
1435 case FadeOutHandleItem:
1436 if (mouse_mode == MouseObject) {
1437 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1438 if (rect) {
1439 rect->property_fill_color_rgba() = 0;
1440 rect->property_outline_pixels() = 1;
1443 break;
1445 default:
1446 break;
1449 /* second pass to handle entered track status in a comprehensible way.
1452 switch (item_type) {
1453 case GainLineItem:
1454 case GainAutomationLineItem:
1455 case RedirectAutomationLineItem:
1456 case PanAutomationLineItem:
1457 case GainControlPointItem:
1458 case GainAutomationControlPointItem:
1459 case PanAutomationControlPointItem:
1460 case RedirectAutomationControlPointItem:
1461 /* these do not affect the current entered track state */
1462 clear_entered_track = false;
1463 break;
1465 case AutomationTrackItem:
1466 /* handled above already */
1467 break;
1469 default:
1470 set_entered_track (0);
1471 break;
1474 return false;
1477 bool
1478 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1480 AutomationLine* al;
1481 ControlPoint* cp;
1482 Marker *marker;
1483 Location *loc;
1484 RegionView* rv;
1485 bool is_start;
1487 switch (item_type) {
1488 case GainControlPointItem:
1489 case GainAutomationControlPointItem:
1490 case PanAutomationControlPointItem:
1491 case RedirectAutomationControlPointItem:
1492 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1493 if (cp->line.npoints() > 1) {
1494 if (!cp->selected) {
1495 cp->set_visible (false);
1499 if (is_drawable()) {
1500 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1503 hide_verbose_canvas_cursor ();
1504 break;
1506 case RegionViewNameHighlight:
1507 case StartSelectionTrimItem:
1508 case EndSelectionTrimItem:
1509 case PlayheadCursorItem:
1510 /* <CMT Additions> */
1511 case ImageFrameHandleStartItem:
1512 case ImageFrameHandleEndItem:
1513 case MarkerViewHandleStartItem:
1514 case MarkerViewHandleEndItem:
1515 /* </CMT Additions> */
1516 if (is_drawable()) {
1517 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1519 break;
1521 case GainLineItem:
1522 case GainAutomationLineItem:
1523 case RedirectAutomationLineItem:
1524 case PanAutomationLineItem:
1525 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1527 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1528 if (line)
1529 line->property_fill_color_rgba() = al->get_line_color();
1531 if (is_drawable()) {
1532 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1534 break;
1536 case RegionViewName:
1537 /* see enter_handler() for notes */
1538 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1539 if (is_drawable() && mouse_mode == MouseObject) {
1540 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1543 break;
1545 case RangeMarkerBarItem:
1546 case TransportMarkerBarItem:
1547 case CdMarkerBarItem:
1548 case MeterBarItem:
1549 case TempoBarItem:
1550 case MarkerBarItem:
1551 if (is_drawable()) {
1552 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1554 break;
1556 case MarkerItem:
1557 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1558 break;
1560 entered_marker = 0;
1561 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1562 location_flags_changed (loc, this);
1564 // fall through
1565 case MeterMarkerItem:
1566 case TempoMarkerItem:
1568 if (is_drawable()) {
1569 track_canvas->get_window()->set_cursor (*timebar_cursor);
1572 break;
1574 case FadeInHandleItem:
1575 case FadeOutHandleItem:
1576 rv = static_cast<RegionView*>(item->get_data ("regionview"));
1578 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1579 if (rect) {
1580 rect->property_fill_color_rgba() = rv->get_fill_color();
1581 rect->property_outline_pixels() = 0;
1584 break;
1586 case AutomationTrackItem:
1587 if (is_drawable()) {
1588 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1589 clear_entered_track = true;
1590 Glib::signal_idle().connect (mem_fun(*this, &Editor::left_automation_track));
1592 break;
1594 default:
1595 break;
1598 return false;
1601 gint
1602 Editor::left_automation_track ()
1604 if (clear_entered_track) {
1605 set_entered_track (0);
1606 clear_entered_track = false;
1608 return false;
1611 void
1612 Editor::scrub ()
1614 double delta;
1616 if (scrubbing_direction == 0) {
1617 /* first move */
1618 session->request_locate (drag_info.current_pointer_frame, false);
1619 session->request_transport_speed (0.1);
1620 scrubbing_direction = 1;
1622 } else {
1624 if (last_scrub_x > drag_info.current_pointer_x) {
1626 /* pointer moved to the left */
1628 if (scrubbing_direction > 0) {
1630 /* we reversed direction to go backwards */
1632 scrub_reversals++;
1633 scrub_reverse_distance += (int) (last_scrub_x - drag_info.current_pointer_x);
1635 } else {
1637 /* still moving to the left (backwards) */
1639 scrub_reversals = 0;
1640 scrub_reverse_distance = 0;
1642 delta = 0.01 * (last_scrub_x - drag_info.current_pointer_x);
1643 session->request_transport_speed (session->transport_speed() - delta);
1646 } else {
1647 /* pointer moved to the right */
1649 if (scrubbing_direction < 0) {
1650 /* we reversed direction to go forward */
1652 scrub_reversals++;
1653 scrub_reverse_distance += (int) (drag_info.current_pointer_x - last_scrub_x);
1655 } else {
1656 /* still moving to the right */
1658 scrub_reversals = 0;
1659 scrub_reverse_distance = 0;
1661 delta = 0.01 * (drag_info.current_pointer_x - last_scrub_x);
1662 session->request_transport_speed (session->transport_speed() + delta);
1666 /* if there have been more than 2 opposite motion moves detected, or one that moves
1667 back more than 10 pixels, reverse direction
1670 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1672 if (scrubbing_direction > 0) {
1673 /* was forwards, go backwards */
1674 session->request_transport_speed (-0.1);
1675 scrubbing_direction = -1;
1676 } else {
1677 /* was backwards, go forwards */
1678 session->request_transport_speed (0.1);
1679 scrubbing_direction = 1;
1682 scrub_reverse_distance = 0;
1683 scrub_reversals = 0;
1687 last_scrub_x = drag_info.current_pointer_x;
1690 bool
1691 Editor::motion_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type, bool from_autoscroll)
1693 if (event->motion.is_hint) {
1694 gint x, y;
1696 /* We call this so that MOTION_NOTIFY events continue to be
1697 delivered to the canvas. We need to do this because we set
1698 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1699 the density of the events, at the expense of a round-trip
1700 to the server. Given that this will mostly occur on cases
1701 where DISPLAY = :0.0, and given the cost of what the motion
1702 event might do, its a good tradeoff.
1705 track_canvas->get_pointer (x, y);
1708 if (current_stepping_trackview) {
1709 /* don't keep the persistent stepped trackview if the mouse moves */
1710 current_stepping_trackview = 0;
1711 step_timeout.disconnect ();
1714 if (session && session->actively_recording()) {
1715 /* Sorry. no dragging stuff around while we record */
1716 return true;
1719 drag_info.item_type = item_type;
1720 drag_info.last_pointer_x = drag_info.current_pointer_x;
1721 drag_info.last_pointer_y = drag_info.current_pointer_y;
1722 drag_info.current_pointer_frame = event_frame (event, &drag_info.current_pointer_x,
1723 &drag_info.current_pointer_y);
1726 switch (mouse_mode) {
1727 case MouseAudition:
1728 if (_scrubbing) {
1729 scrub ();
1731 break;
1733 default:
1734 break;
1737 if (!from_autoscroll && drag_info.item) {
1738 /* item != 0 is the best test i can think of for dragging.
1740 if (!drag_info.move_threshold_passed) {
1742 bool x_threshold_passed = (::llabs ((nframes64_t) (drag_info.current_pointer_x - drag_info.grab_x)) > 4LL);
1743 bool y_threshold_passed = (::llabs ((nframes64_t) (drag_info.current_pointer_y - drag_info.grab_y)) > 4LL);
1745 drag_info.move_threshold_passed = (x_threshold_passed || y_threshold_passed);
1747 // and change the initial grab loc/frame if this drag info wants us to
1749 if (drag_info.want_move_threshold && drag_info.move_threshold_passed) {
1750 drag_info.grab_frame = drag_info.current_pointer_frame;
1751 drag_info.grab_x = drag_info.current_pointer_x;
1752 drag_info.grab_y = drag_info.current_pointer_y;
1753 drag_info.last_pointer_frame = drag_info.grab_frame;
1754 drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
1759 switch (item_type) {
1760 case PlayheadCursorItem:
1761 case MarkerItem:
1762 case MarkerBarItem:
1763 case TempoBarItem:
1764 case MeterBarItem:
1765 case RangeMarkerBarItem:
1766 case TransportMarkerBarItem:
1767 case CdMarkerBarItem:
1768 case GainControlPointItem:
1769 case RedirectAutomationControlPointItem:
1770 case GainAutomationControlPointItem:
1771 case PanAutomationControlPointItem:
1772 case TempoMarkerItem:
1773 case MeterMarkerItem:
1774 case RegionViewNameHighlight:
1775 case StartSelectionTrimItem:
1776 case EndSelectionTrimItem:
1777 case SelectionItem:
1778 case GainLineItem:
1779 case RedirectAutomationLineItem:
1780 case GainAutomationLineItem:
1781 case PanAutomationLineItem:
1782 case FadeInHandleItem:
1783 case FadeOutHandleItem:
1784 /* <CMT Additions> */
1785 case ImageFrameHandleStartItem:
1786 case ImageFrameHandleEndItem:
1787 case MarkerViewHandleStartItem:
1788 case MarkerViewHandleEndItem:
1789 /* </CMT Additions> */
1790 if (drag_info.item && (event->motion.state & Gdk::BUTTON1_MASK ||
1791 (event->motion.state & Gdk::BUTTON3_MASK) ||
1792 (event->motion.state & Gdk::BUTTON2_MASK))) {
1793 if (!from_autoscroll) {
1794 maybe_autoscroll_horizontally (&event->motion);
1796 if (drag_info.motion_callback) {
1797 (this->*(drag_info.motion_callback)) (item, event);
1799 goto handled;
1801 goto not_handled;
1802 break;
1803 default:
1804 break;
1807 switch (mouse_mode) {
1808 case MouseGain:
1809 if (item_type == RegionItem) {
1810 if (drag_info.item && drag_info.motion_callback) {
1811 (this->*(drag_info.motion_callback)) (item, event);
1813 goto handled;
1815 break;
1817 case MouseObject:
1818 case MouseRange:
1819 case MouseZoom:
1820 case MouseTimeFX:
1821 if (drag_info.item && (event->motion.state & GDK_BUTTON1_MASK ||
1822 (event->motion.state & GDK_BUTTON2_MASK))) {
1823 if (!from_autoscroll) {
1824 maybe_autoscroll (&event->motion);
1826 if (drag_info.motion_callback) {
1827 (this->*(drag_info.motion_callback)) (item, event);
1829 goto handled;
1831 goto not_handled;
1832 break;
1834 default:
1835 break;
1838 handled:
1839 track_canvas_motion (event);
1840 // drag_info.last_pointer_frame = drag_info.current_pointer_frame;
1841 return true;
1843 not_handled:
1844 return false;
1847 void
1848 Editor::break_drag ()
1850 stop_canvas_autoscroll ();
1851 hide_verbose_canvas_cursor ();
1853 if (drag_info.item) {
1854 drag_info.item->ungrab (0);
1856 /* put it back where it came from */
1858 double cxw, cyw;
1859 cxw = 0;
1860 cyw = 0;
1861 drag_info.item->i2w (cxw, cyw);
1862 drag_info.item->move (drag_info.original_x - cxw, drag_info.original_y - cyw);
1865 finalize_drag ();
1868 void
1869 Editor::finalize_drag ()
1871 drag_info.item = 0;
1872 drag_info.copy = false;
1873 drag_info.motion_callback = 0;
1874 drag_info.finished_callback = 0;
1875 drag_info.dest_trackview = 0;
1876 drag_info.source_trackview = 0;
1877 drag_info.last_frame_position = 0;
1878 drag_info.grab_frame = 0;
1879 drag_info.last_pointer_frame = 0;
1880 drag_info.current_pointer_frame = 0;
1881 drag_info.brushing = false;
1882 range_marker_drag_rect->hide();
1883 drag_info.clear_copied_locations ();
1886 void
1887 Editor::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
1889 if (drag_info.item == 0) {
1890 fatal << _("programming error: start_grab called without drag item") << endmsg;
1891 /*NOTREACHED*/
1892 return;
1895 if (cursor == 0) {
1896 cursor = which_grabber_cursor ();
1899 // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
1901 if (Keyboard::is_button2_event (&event->button)) {
1902 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
1903 drag_info.y_constrained = true;
1904 drag_info.x_constrained = false;
1905 } else {
1906 drag_info.y_constrained = false;
1907 drag_info.x_constrained = true;
1909 } else {
1910 drag_info.x_constrained = false;
1911 drag_info.y_constrained = false;
1914 drag_info.grab_frame = event_frame (event, &drag_info.grab_x, &drag_info.grab_y);
1915 drag_info.last_pointer_frame = drag_info.grab_frame;
1916 drag_info.current_pointer_frame = drag_info.grab_frame;
1917 drag_info.current_pointer_x = drag_info.grab_x;
1918 drag_info.current_pointer_y = drag_info.grab_y;
1919 drag_info.last_pointer_x = drag_info.current_pointer_x;
1920 drag_info.last_pointer_y = drag_info.current_pointer_y;
1921 drag_info.cumulative_x_drag = 0;
1922 drag_info.cumulative_y_drag = 0;
1923 drag_info.first_move = true;
1924 drag_info.move_threshold_passed = false;
1925 drag_info.want_move_threshold = false;
1926 drag_info.pointer_frame_offset = 0;
1927 drag_info.brushing = false;
1928 drag_info.clear_copied_locations ();
1930 drag_info.original_x = 0;
1931 drag_info.original_y = 0;
1932 drag_info.item->i2w (drag_info.original_x, drag_info.original_y);
1934 drag_info.item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
1935 *cursor,
1936 event->button.time);
1938 if (session && session->transport_rolling()) {
1939 drag_info.was_rolling = true;
1940 } else {
1941 drag_info.was_rolling = false;
1944 switch (snap_type) {
1945 case SnapToRegionStart:
1946 case SnapToRegionEnd:
1947 case SnapToRegionSync:
1948 case SnapToRegionBoundary:
1949 build_region_boundary_cache ();
1950 break;
1951 default:
1952 break;
1956 void
1957 Editor::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t time)
1959 drag_info.item->ungrab (0);
1960 drag_info.item = new_item;
1962 if (cursor == 0) {
1963 cursor = which_grabber_cursor ();
1966 drag_info.item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK, *cursor, time);
1969 bool
1970 Editor::end_grab (ArdourCanvas::Item* item, GdkEvent* event)
1972 bool did_drag = false;
1974 stop_canvas_autoscroll ();
1976 if (drag_info.item == 0) {
1977 return false;
1980 drag_info.item->ungrab (event ? event->button.time : 0);
1982 if (drag_info.finished_callback && event) {
1983 drag_info.last_pointer_x = drag_info.current_pointer_x;
1984 drag_info.last_pointer_y = drag_info.current_pointer_y;
1985 (this->*(drag_info.finished_callback)) (item, event);
1988 did_drag = !drag_info.first_move;
1990 hide_verbose_canvas_cursor();
1992 finalize_drag ();
1994 return did_drag;
1997 void
1998 Editor::region_gain_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2000 if (drag_info.first_move && drag_info.move_threshold_passed) {
2001 drag_info.first_move = false;
2005 void
2006 Editor::start_fade_in_grab (ArdourCanvas::Item* item, GdkEvent* event)
2008 drag_info.item = item;
2009 drag_info.motion_callback = &Editor::fade_in_drag_motion_callback;
2010 drag_info.finished_callback = &Editor::fade_in_drag_finished_callback;
2012 start_grab (event);
2014 if ((drag_info.data = (item->get_data ("regionview"))) == 0) {
2015 fatal << _("programming error: fade in canvas item has no regionview data pointer!") << endmsg;
2016 /*NOTREACHED*/
2019 AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data);
2021 drag_info.pointer_frame_offset = drag_info.grab_frame - ((nframes64_t) arv->audio_region()->fade_in().back()->when + arv->region()->position());
2024 void
2025 Editor::fade_in_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2027 AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data);
2028 nframes64_t pos;
2029 nframes64_t fade_length;
2031 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2032 pos = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2034 else {
2035 pos = 0;
2038 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2039 snap_to (pos);
2042 if (pos < (arv->region()->position() + 64)) {
2043 fade_length = 64; // this should be a minimum defined somewhere
2044 } else if (pos > arv->region()->last_frame()) {
2045 fade_length = arv->region()->length();
2046 } else {
2047 fade_length = pos - arv->region()->position();
2049 /* mapover the region selection */
2051 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
2053 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
2055 if (!tmp) {
2056 continue;
2059 tmp->reset_fade_in_shape_width (fade_length);
2062 show_verbose_duration_cursor (arv->region()->position(), arv->region()->position() + fade_length, 10);
2064 drag_info.first_move = false;
2067 void
2068 Editor::fade_in_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
2070 AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data);
2071 nframes64_t pos;
2072 nframes64_t fade_length;
2074 if (drag_info.first_move) return;
2076 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2077 pos = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2078 } else {
2079 pos = 0;
2082 if (pos < (arv->region()->position() + 64)) {
2083 fade_length = 64; // this should be a minimum defined somewhere
2084 } else if (pos > arv->region()->last_frame()) {
2085 fade_length = arv->region()->length();
2086 } else {
2087 fade_length = pos - arv->region()->position();
2090 begin_reversible_command (_("change fade in length"));
2092 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
2094 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
2096 if (!tmp) {
2097 continue;
2100 AutomationList& alist = tmp->audio_region()->fade_in();
2101 XMLNode &before = alist.get_state();
2103 tmp->audio_region()->set_fade_in_length (fade_length);
2104 tmp->audio_region()->set_fade_in_active (true);
2106 XMLNode &after = alist.get_state();
2107 session->add_command(new MementoCommand<AutomationList>(alist, &before, &after));
2110 commit_reversible_command ();
2113 void
2114 Editor::start_fade_out_grab (ArdourCanvas::Item* item, GdkEvent* event)
2116 drag_info.item = item;
2117 drag_info.motion_callback = &Editor::fade_out_drag_motion_callback;
2118 drag_info.finished_callback = &Editor::fade_out_drag_finished_callback;
2120 start_grab (event);
2122 if ((drag_info.data = (item->get_data ("regionview"))) == 0) {
2123 fatal << _("programming error: fade out canvas item has no regionview data pointer!") << endmsg;
2124 /*NOTREACHED*/
2127 AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data);
2129 drag_info.pointer_frame_offset = drag_info.grab_frame - (arv->region()->length() - (nframes64_t) arv->audio_region()->fade_out().back()->when + arv->region()->position());
2132 void
2133 Editor::fade_out_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2135 AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data);
2136 nframes64_t pos;
2137 nframes64_t fade_length;
2139 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2140 pos = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2141 } else {
2142 pos = 0;
2145 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2146 snap_to (pos);
2149 if (pos > (arv->region()->last_frame() - 64)) {
2150 fade_length = 64; // this should really be a minimum fade defined somewhere
2152 else if (pos < arv->region()->position()) {
2153 fade_length = arv->region()->length();
2155 else {
2156 fade_length = arv->region()->last_frame() - pos;
2159 /* mapover the region selection */
2161 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
2163 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
2165 if (!tmp) {
2166 continue;
2169 tmp->reset_fade_out_shape_width (fade_length);
2172 show_verbose_duration_cursor (arv->region()->last_frame() - fade_length, arv->region()->last_frame(), 10);
2174 drag_info.first_move = false;
2177 void
2178 Editor::fade_out_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
2180 if (drag_info.first_move) return;
2182 AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data);
2183 nframes64_t pos;
2184 nframes64_t fade_length;
2186 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2187 pos = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2189 else {
2190 pos = 0;
2193 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2194 snap_to (pos);
2197 if (pos > (arv->region()->last_frame() - 64)) {
2198 fade_length = 64; // this should really be a minimum fade defined somewhere
2200 else if (pos < arv->region()->position()) {
2201 fade_length = arv->region()->length();
2203 else {
2204 fade_length = arv->region()->last_frame() - pos;
2207 begin_reversible_command (_("change fade out length"));
2209 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
2211 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
2213 if (!tmp) {
2214 continue;
2217 AutomationList& alist = tmp->audio_region()->fade_out();
2218 XMLNode &before = alist.get_state();
2220 tmp->audio_region()->set_fade_out_length (fade_length);
2221 tmp->audio_region()->set_fade_out_active (true);
2223 XMLNode &after = alist.get_state();
2224 session->add_command(new MementoCommand<AutomationList>(alist, &before, &after));
2227 commit_reversible_command ();
2230 void
2231 Editor::start_cursor_grab (ArdourCanvas::Item* item, GdkEvent* event)
2233 drag_info.item = item;
2234 drag_info.motion_callback = &Editor::cursor_drag_motion_callback;
2235 drag_info.finished_callback = &Editor::cursor_drag_finished_callback;
2237 start_grab (event);
2239 if ((drag_info.data = (item->get_data ("cursor"))) == 0) {
2240 fatal << _("programming error: cursor canvas item has no cursor data pointer!") << endmsg;
2241 /*NOTREACHED*/
2244 Cursor* cursor = (Cursor *) drag_info.data;
2246 if (cursor == playhead_cursor) {
2247 _dragging_playhead = true;
2249 if (session && drag_info.was_rolling) {
2250 session->request_stop ();
2253 if (session && session->is_auditioning()) {
2254 session->cancel_audition ();
2258 drag_info.pointer_frame_offset = drag_info.grab_frame - cursor->current_frame;
2260 show_verbose_time_cursor (cursor->current_frame, 10);
2263 void
2264 Editor::start_cursor_grab_no_stop (ArdourCanvas::Item* item, GdkEvent* event)
2266 drag_info.item = item;
2267 drag_info.motion_callback = &Editor::cursor_drag_motion_callback;
2268 drag_info.finished_callback = &Editor::cursor_drag_finished_ensure_locate_callback;
2270 start_grab (event);
2272 if ((drag_info.data = (item->get_data ("cursor"))) == 0) {
2273 fatal << _("programming error: cursor canvas item has no cursor data pointer!") << endmsg;
2274 /*NOTREACHED*/
2277 Cursor* cursor = (Cursor *) drag_info.data;
2278 nframes64_t where = event_frame (event, 0, 0);
2280 snap_to(where);
2281 playhead_cursor->set_position (where);
2283 if (cursor == playhead_cursor) {
2284 _dragging_playhead = true;
2286 if (session && session->is_auditioning()) {
2287 session->cancel_audition ();
2291 drag_info.pointer_frame_offset = drag_info.grab_frame - cursor->current_frame;
2293 show_verbose_time_cursor (cursor->current_frame, 10);
2296 void
2297 Editor::cursor_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2299 Cursor* cursor = (Cursor *) drag_info.data;
2300 nframes64_t adjusted_frame;
2302 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2303 adjusted_frame = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2305 else {
2306 adjusted_frame = 0;
2309 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2310 if (cursor == playhead_cursor) {
2311 snap_to (adjusted_frame);
2315 if (adjusted_frame == drag_info.last_pointer_frame) return;
2317 cursor->set_position (adjusted_frame);
2319 show_verbose_time_cursor (cursor->current_frame, 10);
2321 #ifdef GTKOSX
2322 track_canvas->update_now ();
2323 #endif
2324 UpdateAllTransportClocks (cursor->current_frame);
2326 drag_info.last_pointer_frame = adjusted_frame;
2327 drag_info.first_move = false;
2330 void
2331 Editor::cursor_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
2333 _dragging_playhead = false;
2335 if (drag_info.first_move) {
2336 return;
2339 cursor_drag_motion_callback (item, event);
2341 if (item == &playhead_cursor->canvas_item) {
2342 if (session) {
2343 session->request_locate (playhead_cursor->current_frame, drag_info.was_rolling);
2348 void
2349 Editor::cursor_drag_finished_ensure_locate_callback (ArdourCanvas::Item* item, GdkEvent* event)
2351 _dragging_playhead = false;
2353 cursor_drag_motion_callback (item, event);
2355 if (item == &playhead_cursor->canvas_item) {
2356 if (session) {
2357 session->request_locate (playhead_cursor->current_frame, drag_info.was_rolling);
2362 void
2363 Editor::update_marker_drag_item (Location *location)
2365 double x1 = frame_to_pixel (location->start());
2366 double x2 = frame_to_pixel (location->end());
2368 if (location->is_mark()) {
2369 marker_drag_line_points.front().set_x(x1);
2370 marker_drag_line_points.back().set_x(x1);
2371 marker_drag_line->property_points() = marker_drag_line_points;
2372 } else {
2373 range_marker_drag_rect->property_x1() = x1;
2374 range_marker_drag_rect->property_x2() = x2;
2378 void
2379 Editor::start_marker_grab (ArdourCanvas::Item* item, GdkEvent* event)
2381 Marker* marker;
2383 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2384 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2385 /*NOTREACHED*/
2388 bool is_start;
2390 Location *location = find_location_from_marker (marker, is_start);
2392 drag_info.item = item;
2393 drag_info.data = marker;
2394 drag_info.motion_callback = &Editor::marker_drag_motion_callback;
2395 drag_info.finished_callback = &Editor::marker_drag_finished_callback;
2397 start_grab (event);
2399 _dragging_edit_point = true;
2401 drag_info.pointer_frame_offset = drag_info.grab_frame - (is_start ? location->start() : location->end());
2403 update_marker_drag_item (location);
2405 if (location->is_mark()) {
2406 // marker_drag_line->show();
2407 // marker_drag_line->raise_to_top();
2408 } else {
2409 range_marker_drag_rect->show();
2410 //range_marker_drag_rect->raise_to_top();
2413 if (is_start) {
2414 show_verbose_time_cursor (location->start(), 10);
2415 } else {
2416 show_verbose_time_cursor (location->end(), 10);
2419 Selection::Operation op = Keyboard::selection_type (event->button.state);
2421 switch (op) {
2422 case Selection::Toggle:
2423 selection->toggle (marker);
2424 break;
2425 case Selection::Set:
2426 if (!selection->selected (marker)) {
2427 selection->set (marker);
2429 break;
2430 case Selection::Extend:
2432 Locations::LocationList ll;
2433 list<Marker*> to_add;
2434 nframes64_t s, e;
2435 selection->markers.range (s, e);
2436 s = min (marker->position(), s);
2437 e = max (marker->position(), e);
2438 s = min (s, e);
2439 e = max (s, e);
2440 if (e < max_frames) {
2441 ++e;
2443 session->locations()->find_all_between (s, e, ll, Location::Flags (0));
2444 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
2445 LocationMarkers* lm = find_location_markers (*i);
2446 if (lm) {
2447 if (lm->start) {
2448 to_add.push_back (lm->start);
2450 if (lm->end) {
2451 to_add.push_back (lm->end);
2455 if (!to_add.empty()) {
2456 selection->add (to_add);
2458 break;
2460 case Selection::Add:
2461 selection->add (marker);
2462 break;
2465 /* set up copies for us to manipulate during the drag */
2467 drag_info.clear_copied_locations ();
2469 for (MarkerSelection::iterator i = selection->markers.begin(); i != selection->markers.end(); ++i) {
2470 Location *l = find_location_from_marker (*i, is_start);
2471 drag_info.copied_locations.push_back (new Location (*l));
2475 void
2476 Editor::marker_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2478 nframes64_t f_delta = 0;
2479 nframes64_t newframe;
2480 bool is_start;
2481 bool move_both = false;
2482 Marker* dragged_marker = (Marker*) drag_info.data;
2483 Marker* marker;
2484 Location *real_location;
2485 Location *copy_location;
2487 if (drag_info.pointer_frame_offset <= drag_info.current_pointer_frame) {
2488 newframe = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2489 } else {
2490 newframe = 0;
2493 nframes64_t next = newframe;
2495 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2496 snap_to (newframe, 0, true);
2499 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) {
2500 return;
2503 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
2504 move_both = true;
2507 MarkerSelection::iterator i;
2508 list<Location*>::iterator x;
2510 /* find the marker we're dragging, and compute the delta */
2512 for (i = selection->markers.begin(), x = drag_info.copied_locations.begin();
2513 x != drag_info.copied_locations.end() && i != selection->markers.end();
2514 ++i, ++x) {
2516 copy_location = *x;
2517 marker = *i;
2519 if (marker == dragged_marker) {
2521 if ((real_location = find_location_from_marker (marker, is_start)) == 0) {
2522 /* que pasa ?? */
2523 return;
2526 if (real_location->is_mark()) {
2527 f_delta = newframe - copy_location->start();
2528 } else {
2531 switch (marker->type()) {
2532 case Marker::Start:
2533 case Marker::LoopStart:
2534 case Marker::PunchIn:
2535 f_delta = newframe - copy_location->start();
2536 break;
2538 case Marker::End:
2539 case Marker::LoopEnd:
2540 case Marker::PunchOut:
2541 f_delta = newframe - copy_location->end();
2542 break;
2543 default:
2544 /* what kind of marker is this ? */
2545 return;
2548 break;
2552 if (i == selection->markers.end()) {
2553 /* hmm, impossible - we didn't find the dragged marker */
2554 return;
2557 /* now move them all */
2559 for (i = selection->markers.begin(), x = drag_info.copied_locations.begin();
2560 x != drag_info.copied_locations.end() && i != selection->markers.end();
2561 ++i, ++x) {
2563 copy_location = *x;
2564 marker = *i;
2566 /* call this to find out if its the start or end */
2568 if ((real_location = find_location_from_marker (marker, is_start)) == 0) {
2569 continue;
2572 if (real_location->locked()) {
2573 continue;
2576 if (copy_location->is_mark()) {
2578 /* just move it */
2580 copy_location->set_start (copy_location->start() + f_delta);
2582 } else {
2584 nframes64_t new_start = copy_location->start() + f_delta;
2585 nframes64_t new_end = copy_location->end() + f_delta;
2587 if (is_start) { // start-of-range marker
2589 if (move_both) {
2590 copy_location->set_start (new_start);
2591 copy_location->set_end (new_end);
2592 } else if (new_start < copy_location->end()) {
2593 copy_location->set_start (new_start);
2594 } else {
2595 snap_to (next, 1, true);
2596 copy_location->set_end (next);
2597 copy_location->set_start (newframe);
2600 } else { // end marker
2602 if (move_both) {
2603 copy_location->set_end (new_end);
2604 copy_location->set_start (new_start);
2605 } else if (new_end > copy_location->start()) {
2606 copy_location->set_end (new_end);
2607 } else if (newframe > 0) {
2608 snap_to (next, -1, true);
2609 copy_location->set_start (next);
2610 copy_location->set_end (newframe);
2614 update_marker_drag_item (copy_location);
2616 LocationMarkers* lm = find_location_markers (real_location);
2618 if (lm) {
2619 lm->set_position (copy_location->start(), copy_location->end());
2623 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
2624 drag_info.first_move = false;
2626 if (drag_info.copied_locations.empty()) {
2627 abort();
2630 edit_point_clock.set (drag_info.copied_locations.front()->start());
2631 show_verbose_time_cursor (newframe, 10);
2633 #ifdef GTKOSX
2634 track_canvas->update_now ();
2635 #endif
2638 void
2639 Editor::marker_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
2641 if (drag_info.first_move) {
2643 /* just a click, do nothing but finish
2644 off the selection process
2647 Selection::Operation op = Keyboard::selection_type (event->button.state);
2648 Marker* marker = (Marker *) drag_info.data;
2650 switch (op) {
2651 case Selection::Set:
2652 if (selection->selected (marker) && selection->markers.size() > 1) {
2653 selection->set (marker);
2655 break;
2657 case Selection::Toggle:
2658 case Selection::Extend:
2659 case Selection::Add:
2660 break;
2663 return;
2666 _dragging_edit_point = false;
2669 begin_reversible_command ( _("move marker") );
2670 XMLNode &before = session->locations()->get_state();
2672 MarkerSelection::iterator i;
2673 list<Location*>::iterator x;
2674 bool is_start;
2676 for (i = selection->markers.begin(), x = drag_info.copied_locations.begin();
2677 x != drag_info.copied_locations.end() && i != selection->markers.end();
2678 ++i, ++x) {
2680 Location * location = find_location_from_marker ((*i), is_start);
2682 if (location) {
2684 if (location->locked()) {
2685 return;
2688 if (location->is_mark()) {
2689 location->set_start ((*x)->start());
2690 } else {
2691 location->set ((*x)->start(), (*x)->end());
2696 XMLNode &after = session->locations()->get_state();
2697 session->add_command(new MementoCommand<Locations>(*(session->locations()), &before, &after));
2698 commit_reversible_command ();
2700 marker_drag_line->hide();
2701 range_marker_drag_rect->hide();
2704 void
2705 Editor::start_meter_marker_grab (ArdourCanvas::Item* item, GdkEvent* event)
2707 Marker* marker;
2708 MeterMarker* meter_marker;
2710 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
2711 fatal << _("programming error: meter marker canvas item has no marker object pointer!") << endmsg;
2712 /*NOTREACHED*/
2715 meter_marker = dynamic_cast<MeterMarker*> (marker);
2717 MetricSection& section (meter_marker->meter());
2719 if (!section.movable()) {
2720 return;
2723 drag_info.item = item;
2724 drag_info.copy = false;
2725 drag_info.data = marker;
2726 drag_info.motion_callback = &Editor::meter_marker_drag_motion_callback;
2727 drag_info.finished_callback = &Editor::meter_marker_drag_finished_callback;
2729 start_grab (event);
2731 drag_info.pointer_frame_offset = drag_info.grab_frame - meter_marker->meter().frame();
2733 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
2736 void
2737 Editor::start_meter_marker_copy_grab (ArdourCanvas::Item* item, GdkEvent* event)
2739 Marker* marker;
2740 MeterMarker* meter_marker;
2742 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
2743 fatal << _("programming error: meter marker canvas item has no marker object pointer!") << endmsg;
2744 /*NOTREACHED*/
2747 meter_marker = dynamic_cast<MeterMarker*> (marker);
2749 // create a dummy marker for visual representation of moving the copy.
2750 // The actual copying is not done before we reach the finish callback.
2751 char name[64];
2752 snprintf (name, sizeof(name), "%g/%g", meter_marker->meter().beats_per_bar(), meter_marker->meter().note_divisor ());
2753 MeterMarker* new_marker = new MeterMarker(*this, *meter_group, ARDOUR_UI::config()->canvasvar_MeterMarker.get(), name,
2754 *new MeterSection(meter_marker->meter()));
2756 drag_info.item = &new_marker->the_item();
2757 drag_info.copy = true;
2758 drag_info.data = new_marker;
2759 drag_info.motion_callback = &Editor::meter_marker_drag_motion_callback;
2760 drag_info.finished_callback = &Editor::meter_marker_drag_finished_callback;
2762 start_grab (event);
2764 drag_info.pointer_frame_offset = drag_info.grab_frame - meter_marker->meter().frame();
2766 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
2769 void
2770 Editor::meter_marker_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2772 MeterMarker* marker = (MeterMarker *) drag_info.data;
2773 nframes64_t adjusted_frame;
2775 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2776 adjusted_frame = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2778 else {
2779 adjusted_frame = 0;
2782 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2783 snap_to (adjusted_frame);
2786 if (adjusted_frame == drag_info.last_pointer_frame) return;
2788 marker->set_position (adjusted_frame);
2791 drag_info.last_pointer_frame = adjusted_frame;
2792 drag_info.first_move = false;
2794 show_verbose_time_cursor (adjusted_frame, 10);
2797 void
2798 Editor::meter_marker_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
2800 if (drag_info.first_move) return;
2802 meter_marker_drag_motion_callback (drag_info.item, event);
2804 MeterMarker* marker = (MeterMarker *) drag_info.data;
2805 BBT_Time when;
2807 TempoMap& map (session->tempo_map());
2808 map.bbt_time (drag_info.last_pointer_frame, when);
2810 if (drag_info.copy == true) {
2811 begin_reversible_command (_("copy meter mark"));
2812 XMLNode &before = map.get_state();
2813 map.add_meter (marker->meter(), when);
2814 XMLNode &after = map.get_state();
2815 session->add_command(new MementoCommand<TempoMap>(map, &before, &after));
2816 commit_reversible_command ();
2818 // delete the dummy marker we used for visual representation of copying.
2819 // a new visual marker will show up automatically.
2820 delete marker;
2821 } else {
2822 begin_reversible_command (_("move meter mark"));
2823 XMLNode &before = map.get_state();
2824 map.move_meter (marker->meter(), when);
2825 XMLNode &after = map.get_state();
2826 session->add_command(new MementoCommand<TempoMap>(map, &before, &after));
2827 commit_reversible_command ();
2831 void
2832 Editor::start_tempo_marker_grab (ArdourCanvas::Item* item, GdkEvent* event)
2834 Marker* marker;
2835 TempoMarker* tempo_marker;
2837 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
2838 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
2839 /*NOTREACHED*/
2842 if ((tempo_marker = dynamic_cast<TempoMarker *> (marker)) == 0) {
2843 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
2844 /*NOTREACHED*/
2847 MetricSection& section (tempo_marker->tempo());
2849 if (!section.movable()) {
2850 return;
2853 drag_info.item = item;
2854 drag_info.copy = false;
2855 drag_info.data = marker;
2856 drag_info.motion_callback = &Editor::tempo_marker_drag_motion_callback;
2857 drag_info.finished_callback = &Editor::tempo_marker_drag_finished_callback;
2859 start_grab (event);
2861 drag_info.pointer_frame_offset = drag_info.grab_frame - tempo_marker->tempo().frame();
2862 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
2865 void
2866 Editor::start_tempo_marker_copy_grab (ArdourCanvas::Item* item, GdkEvent* event)
2868 Marker* marker;
2869 TempoMarker* tempo_marker;
2871 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
2872 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
2873 /*NOTREACHED*/
2876 if ((tempo_marker = dynamic_cast<TempoMarker *> (marker)) == 0) {
2877 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
2878 /*NOTREACHED*/
2881 // create a dummy marker for visual representation of moving the copy.
2882 // The actual copying is not done before we reach the finish callback.
2883 char name[64];
2884 snprintf (name, sizeof (name), "%.2f", tempo_marker->tempo().beats_per_minute());
2885 TempoMarker* new_marker = new TempoMarker(*this, *tempo_group, ARDOUR_UI::config()->canvasvar_TempoMarker.get(), name,
2886 *new TempoSection(tempo_marker->tempo()));
2888 drag_info.item = &new_marker->the_item();
2889 drag_info.copy = true;
2890 drag_info.data = new_marker;
2891 drag_info.motion_callback = &Editor::tempo_marker_drag_motion_callback;
2892 drag_info.finished_callback = &Editor::tempo_marker_drag_finished_callback;
2894 start_grab (event);
2896 drag_info.pointer_frame_offset = drag_info.grab_frame - tempo_marker->tempo().frame();
2898 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
2901 void
2902 Editor::tempo_marker_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2904 TempoMarker* marker = (TempoMarker *) drag_info.data;
2905 nframes64_t adjusted_frame;
2907 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2908 adjusted_frame = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2910 else {
2911 adjusted_frame = 0;
2914 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2915 snap_to (adjusted_frame);
2918 if (adjusted_frame == drag_info.last_pointer_frame) return;
2920 /* OK, we've moved far enough to make it worth actually move the thing. */
2922 marker->set_position (adjusted_frame);
2924 show_verbose_time_cursor (adjusted_frame, 10);
2926 drag_info.last_pointer_frame = adjusted_frame;
2927 drag_info.first_move = false;
2930 void
2931 Editor::tempo_marker_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
2933 if (drag_info.first_move) return;
2935 tempo_marker_drag_motion_callback (drag_info.item, event);
2937 TempoMarker* marker = (TempoMarker *) drag_info.data;
2938 BBT_Time when;
2940 TempoMap& map (session->tempo_map());
2941 map.bbt_time (drag_info.last_pointer_frame, when);
2943 if (drag_info.copy == true) {
2944 begin_reversible_command (_("copy tempo mark"));
2945 XMLNode &before = map.get_state();
2946 map.add_tempo (marker->tempo(), when);
2947 XMLNode &after = map.get_state();
2948 session->add_command (new MementoCommand<TempoMap>(map, &before, &after));
2949 commit_reversible_command ();
2951 // delete the dummy marker we used for visual representation of copying.
2952 // a new visual marker will show up automatically.
2953 delete marker;
2954 } else {
2955 begin_reversible_command (_("move tempo mark"));
2956 XMLNode &before = map.get_state();
2957 map.move_tempo (marker->tempo(), when);
2958 XMLNode &after = map.get_state();
2959 session->add_command (new MementoCommand<TempoMap>(map, &before, &after));
2960 commit_reversible_command ();
2964 void
2965 Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* event)
2967 ControlPoint* control_point;
2969 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2970 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2971 /*NOTREACHED*/
2974 // We shouldn't remove the first or last gain point
2975 if (control_point->line.is_last_point(*control_point) ||
2976 control_point->line.is_first_point(*control_point)) {
2977 return;
2980 control_point->line.remove_point (*control_point);
2983 void
2984 Editor::remove_control_point (ArdourCanvas::Item*item, GdkEvent* event)
2986 ControlPoint* control_point;
2988 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2989 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2990 /*NOTREACHED*/
2993 control_point->line.remove_point (*control_point);
2996 void
2997 Editor::start_control_point_grab (ArdourCanvas::Item* item, GdkEvent* event)
2999 ControlPoint* control_point;
3001 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
3002 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
3003 /*NOTREACHED*/
3006 drag_info.item = item;
3007 drag_info.data = control_point;
3008 drag_info.motion_callback = &Editor::control_point_drag_motion_callback;
3009 drag_info.finished_callback = &Editor::control_point_drag_finished_callback;
3011 start_grab (event, fader_cursor);
3013 // start the grab at the center of the control point so
3014 // the point doesn't 'jump' to the mouse after the first drag
3015 drag_info.grab_x = control_point->get_x();
3016 drag_info.grab_y = control_point->get_y();
3017 control_point->line.parent_group().i2w(drag_info.grab_x, drag_info.grab_y);
3018 track_canvas->w2c(drag_info.grab_x, drag_info.grab_y, drag_info.grab_x, drag_info.grab_y);
3020 drag_info.grab_frame = pixel_to_frame(drag_info.grab_x);
3022 control_point->line.start_drag (control_point, drag_info.grab_frame, 0);
3024 float fraction = 1.0 - (control_point->get_y() / control_point->line.height());
3025 set_verbose_canvas_cursor (control_point->line.get_verbose_cursor_string (fraction),
3026 drag_info.current_pointer_x + 10, drag_info.current_pointer_y + 10);
3028 show_verbose_canvas_cursor ();
3031 void
3032 Editor::control_point_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
3034 ControlPoint* cp = reinterpret_cast<ControlPoint *> (drag_info.data);
3036 double dx = drag_info.current_pointer_x - drag_info.last_pointer_x;
3037 double dy = drag_info.current_pointer_y - drag_info.last_pointer_y;
3039 if (event->button.state & Keyboard::SecondaryModifier) {
3040 dx *= 0.1;
3041 dy *= 0.1;
3044 double cx = drag_info.grab_x + drag_info.cumulative_x_drag + dx;
3045 double cy = drag_info.grab_y + drag_info.cumulative_y_drag + dy;
3047 // calculate zero crossing point. back off by .01 to stay on the
3048 // positive side of zero
3049 double _unused = 0;
3050 double zero_gain_y = (1.0 - ZERO_GAIN_FRACTION) * cp->line.height() - .01;
3051 cp->line.parent_group().i2w(_unused, zero_gain_y);
3053 // make sure we hit zero when passing through
3054 if ((cy < zero_gain_y and (cy - dy) > zero_gain_y)
3055 or (cy > zero_gain_y and (cy - dy) < zero_gain_y)) {
3056 cy = zero_gain_y;
3059 if (drag_info.x_constrained) {
3060 cx = drag_info.grab_x;
3062 if (drag_info.y_constrained) {
3063 cy = drag_info.grab_y;
3066 drag_info.cumulative_x_drag = cx - drag_info.grab_x;
3067 drag_info.cumulative_y_drag = cy - drag_info.grab_y;
3069 cp->line.parent_group().w2i (cx, cy);
3071 cx = max (0.0, cx);
3072 cy = max (0.0, cy);
3073 cy = min ((double) cp->line.height(), cy);
3075 //translate cx to frames
3076 nframes64_t cx_frames = unit_to_frame (cx);
3078 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier()) && !drag_info.x_constrained) {
3079 snap_to (cx_frames);
3082 float fraction = 1.0 - (cy / cp->line.height());
3084 bool push;
3086 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
3087 push = true;
3088 } else {
3089 push = false;
3092 cp->line.point_drag (*cp, cx_frames , fraction, push);
3094 set_verbose_canvas_cursor_text (cp->line.get_verbose_cursor_string (fraction));
3096 drag_info.first_move = false;
3099 void
3100 Editor::control_point_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
3102 ControlPoint* cp = reinterpret_cast<ControlPoint *> (drag_info.data);
3104 if (drag_info.first_move) {
3106 /* just a click */
3108 if ((event->type == GDK_BUTTON_RELEASE) && (event->button.button == 1) && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3109 reset_point_selection ();
3112 } else {
3113 control_point_drag_motion_callback (item, event);
3115 cp->line.end_drag (cp);
3118 void
3119 Editor::start_line_grab_from_regionview (ArdourCanvas::Item* item, GdkEvent* event)
3121 switch (mouse_mode) {
3122 case MouseGain:
3123 assert(dynamic_cast<AudioRegionView*>(clicked_regionview));
3124 start_line_grab (dynamic_cast<AudioRegionView*>(clicked_regionview)->get_gain_line(), event);
3125 break;
3126 default:
3127 break;
3131 void
3132 Editor::start_line_grab_from_line (ArdourCanvas::Item* item, GdkEvent* event)
3134 AutomationLine* al;
3136 if ((al = reinterpret_cast<AutomationLine*> (item->get_data ("line"))) == 0) {
3137 fatal << _("programming error: line canvas item has no line pointer!") << endmsg;
3138 /*NOTREACHED*/
3141 start_line_grab (al, event);
3144 void
3145 Editor::start_line_grab (AutomationLine* line, GdkEvent* event)
3147 double cx;
3148 double cy;
3149 nframes64_t frame_within_region;
3151 /* need to get x coordinate in terms of parent (TimeAxisItemView)
3152 origin, and ditto for y.
3155 cx = event->button.x;
3156 cy = event->button.y;
3158 line->parent_group().w2i (cx, cy);
3160 frame_within_region = (nframes64_t) floor (cx * frames_per_unit);
3162 if (!line->control_points_adjacent (frame_within_region, current_line_drag_info.before,
3163 current_line_drag_info.after)) {
3164 /* no adjacent points */
3165 return;
3168 drag_info.item = &line->grab_item();
3169 drag_info.data = line;
3170 drag_info.motion_callback = &Editor::line_drag_motion_callback;
3171 drag_info.finished_callback = &Editor::line_drag_finished_callback;
3173 start_grab (event, fader_cursor);
3175 /* store grab start in parent frame */
3177 drag_info.grab_x = cx;
3178 drag_info.grab_y = cy;
3180 double fraction = 1.0 - (cy / line->height());
3182 line->start_drag (0, drag_info.grab_frame, fraction);
3184 set_verbose_canvas_cursor (line->get_verbose_cursor_string (fraction),
3185 drag_info.current_pointer_x + 10, drag_info.current_pointer_y + 10);
3186 show_verbose_canvas_cursor ();
3189 void
3190 Editor::line_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
3192 AutomationLine* line = reinterpret_cast<AutomationLine *> (drag_info.data);
3194 double dy = drag_info.current_pointer_y - drag_info.last_pointer_y;
3196 if (event->button.state & Keyboard::SecondaryModifier) {
3197 dy *= 0.1;
3200 double cy = drag_info.grab_y + drag_info.cumulative_y_drag + dy;
3202 drag_info.cumulative_y_drag = cy - drag_info.grab_y;
3204 cy = max (0.0, cy);
3205 cy = min ((double) line->height(), cy);
3207 double fraction = 1.0 - (cy / line->height());
3209 bool push;
3211 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
3212 push = false;
3213 } else {
3214 push = true;
3217 line->line_drag (current_line_drag_info.before, current_line_drag_info.after, fraction, push);
3219 set_verbose_canvas_cursor_text (line->get_verbose_cursor_string (fraction));
3222 void
3223 Editor::line_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
3225 AutomationLine* line = reinterpret_cast<AutomationLine *> (drag_info.data);
3226 line_drag_motion_callback (item, event);
3227 line->end_drag (0);
3230 void
3231 Editor::start_region_grab (ArdourCanvas::Item* item, GdkEvent* event)
3233 if (selection->regions.empty() || clicked_regionview == 0) {
3234 return;
3237 drag_info.copy = false;
3238 drag_info.item = item;
3239 drag_info.data = clicked_regionview;
3241 if (Config->get_edit_mode() == Splice) {
3242 drag_info.motion_callback = &Editor::region_drag_splice_motion_callback;
3243 drag_info.finished_callback = &Editor::region_drag_splice_finished_callback;
3244 } else {
3245 drag_info.motion_callback = &Editor::region_drag_motion_callback;
3246 drag_info.finished_callback = &Editor::region_drag_finished_callback;
3249 start_grab (event);
3251 double speed = 1.0;
3252 TimeAxisView* tvp = clicked_trackview;
3253 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
3255 if (tv && tv->is_audio_track()) {
3256 speed = tv->get_diskstream()->speed();
3259 drag_info.last_frame_position = (nframes64_t) (clicked_regionview->region()->position() / speed);
3260 drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
3261 drag_info.source_trackview = &clicked_regionview->get_time_axis_view();
3262 drag_info.dest_trackview = drag_info.source_trackview;
3263 // we want a move threshold
3264 drag_info.want_move_threshold = true;
3266 show_verbose_time_cursor (drag_info.last_frame_position, 10);
3268 begin_reversible_command (_("move region(s)"));
3270 _region_motion_group->raise_to_top ();
3272 /* sync the canvas to what we think is its current state */
3273 track_canvas->update_now();
3276 void
3277 Editor::start_region_copy_grab (ArdourCanvas::Item* item, GdkEvent* event)
3279 if (selection->regions.empty() || clicked_regionview == 0) {
3280 return;
3283 drag_info.copy = true;
3284 drag_info.item = item;
3285 drag_info.data = clicked_regionview;
3287 start_grab(event);
3289 TimeAxisView* tv = &clicked_regionview->get_time_axis_view();
3290 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*>(tv);
3291 double speed = 1.0;
3293 if (atv && atv->is_audio_track()) {
3294 speed = atv->get_diskstream()->speed();
3297 drag_info.source_trackview = &clicked_regionview->get_time_axis_view();
3298 drag_info.dest_trackview = drag_info.source_trackview;
3299 drag_info.last_frame_position = (nframes64_t) (clicked_regionview->region()->position() / speed);
3300 drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
3301 // we want a move threshold
3302 drag_info.want_move_threshold = true;
3303 drag_info.motion_callback = &Editor::region_drag_motion_callback;
3304 drag_info.finished_callback = &Editor::region_drag_finished_callback;
3305 show_verbose_time_cursor (drag_info.last_frame_position, 10);
3306 _region_motion_group->raise_to_top ();
3309 void
3310 Editor::start_region_brush_grab (ArdourCanvas::Item* item, GdkEvent* event)
3312 if (selection->regions.empty() || clicked_regionview == 0 || Config->get_edit_mode() == Splice) {
3313 return;
3316 drag_info.copy = false;
3317 drag_info.item = item;
3318 drag_info.data = clicked_regionview;
3319 drag_info.motion_callback = &Editor::region_drag_motion_callback;
3320 drag_info.finished_callback = &Editor::region_drag_finished_callback;
3322 start_grab (event);
3324 double speed = 1.0;
3325 TimeAxisView* tvp = clicked_trackview;
3326 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
3328 if (tv && tv->is_audio_track()) {
3329 speed = tv->get_diskstream()->speed();
3332 drag_info.last_frame_position = (nframes64_t) (clicked_regionview->region()->position() / speed);
3333 drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
3334 drag_info.source_trackview = &clicked_regionview->get_time_axis_view();
3335 drag_info.dest_trackview = drag_info.source_trackview;
3336 // we want a move threshold
3337 drag_info.want_move_threshold = true;
3338 drag_info.brushing = true;
3340 begin_reversible_command (_("Drag region brush"));
3343 void
3344 Editor::possibly_copy_regions_during_grab (GdkEvent* event)
3346 if (drag_info.copy && drag_info.move_threshold_passed && drag_info.want_move_threshold) {
3348 drag_info.want_move_threshold = false; // don't copy again
3350 /* duplicate the regionview(s) and region(s) */
3352 vector<RegionView*> new_regionviews;
3354 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
3355 RegionView* nrv;
3356 AudioRegionView* arv;
3358 if ((arv = dynamic_cast<AudioRegionView*>(*i)) == 0) {
3359 /* XXX handle MIDI here */
3360 continue;
3363 const boost::shared_ptr<const Region> original = arv->region();
3364 boost::shared_ptr<Region> region_copy = RegionFactory::create (original);
3365 boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (region_copy);
3367 nrv = new AudioRegionView (*arv, ar);
3368 nrv->get_canvas_group()->show ();
3370 new_regionviews.push_back (nrv);
3373 if (new_regionviews.empty()) {
3374 return;
3377 /* reset selection to new regionviews. This will not set selection visual status for
3378 these regionviews since they don't belong to a track, so do that by hand too.
3381 selection->set (new_regionviews);
3383 for (vector<RegionView*>::iterator i = new_regionviews.begin(); i != new_regionviews.end(); ++i) {
3384 (*i)->set_selected (true);
3387 /* reset drag_info data to reflect the fact that we are dragging the copies */
3389 drag_info.data = new_regionviews.front();
3391 swap_grab (new_regionviews.front()->get_canvas_group (), 0, event->motion.time);
3393 sync the canvas to what we think is its current state
3394 without it, the canvas seems to
3395 "forget" to update properly after the upcoming reparent()
3396 ..only if the mouse is in rapid motion at the time of the grab.
3397 something to do with regionview creation raking so long?
3399 track_canvas->update_now();
3403 bool
3404 Editor::check_region_drag_possible (AudioTimeAxisView** tv)
3406 /* Which trackview is this ? */
3408 TimeAxisView* tvp = trackview_by_y_position (drag_info.current_pointer_y);
3409 (*tv) = dynamic_cast<AudioTimeAxisView*>(tvp);
3411 /* The region motion is only processed if the pointer is over
3412 an audio track.
3415 if (!(*tv) || !(*tv)->is_audio_track()) {
3416 /* To make sure we hide the verbose canvas cursor when the mouse is
3417 not held over and audiotrack.
3419 hide_verbose_canvas_cursor ();
3420 return false;
3423 return true;
3426 struct RegionSelectionByPosition {
3427 bool operator() (RegionView*a, RegionView* b) {
3428 return a->region()->position () < b->region()->position();
3432 void
3433 Editor::region_drag_splice_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
3435 AudioTimeAxisView* tv;
3437 if (!check_region_drag_possible (&tv)) {
3438 return;
3441 if (!drag_info.move_threshold_passed) {
3442 return;
3445 int dir;
3447 if (drag_info.current_pointer_x - drag_info.grab_x > 0) {
3448 dir = 1;
3449 } else {
3450 dir = -1;
3453 RegionSelection copy (selection->regions);
3455 RegionSelectionByPosition cmp;
3456 copy.sort (cmp);
3458 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
3460 AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*> (&(*i)->get_time_axis_view());
3462 if (!atv) {
3463 continue;
3466 boost::shared_ptr<Playlist> playlist;
3468 if ((playlist = atv->playlist()) == 0) {
3469 continue;
3472 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
3473 continue;
3476 if (dir > 0) {
3477 if (drag_info.current_pointer_frame < (*i)->region()->last_frame() + 1) {
3478 continue;
3480 } else {
3481 if (drag_info.current_pointer_frame > (*i)->region()->first_frame()) {
3482 continue;
3487 playlist->shuffle ((*i)->region(), dir);
3489 drag_info.grab_x = drag_info.current_pointer_x;
3493 void
3494 Editor::region_drag_splice_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
3498 void
3499 Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
3501 double x_delta;
3502 double y_delta = 0;
3503 nframes64_t pending_region_position = 0;
3504 int32_t pointer_y_span = 0, canvas_pointer_y_span = 0, original_pointer_order;
3505 int32_t visible_y_high = 0, visible_y_low = 512; //high meaning higher numbered.. not the height on the screen
3506 bool clamp_y_axis = false;
3507 vector<int32_t> height_list(512) ;
3508 vector<int32_t>::iterator j;
3509 AudioTimeAxisView* tv;
3511 possibly_copy_regions_during_grab (event);
3513 if (!check_region_drag_possible (&tv)) {
3514 return;
3517 original_pointer_order = drag_info.dest_trackview->order;
3519 /************************************************************
3520 Y-Delta Computation
3521 ************************************************************/
3523 if (drag_info.brushing) {
3524 clamp_y_axis = true;
3525 pointer_y_span = 0;
3526 goto y_axis_done;
3529 if ((pointer_y_span = (drag_info.dest_trackview->order - tv->order)) != 0) {
3531 int32_t children = 0, numtracks = 0;
3532 // XXX hard coding track limit, oh my, so very very bad
3533 bitset <1024> tracks (0x00);
3534 /* get a bitmask representing the visible tracks */
3536 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
3537 TimeAxisView *tracklist_timeview;
3538 tracklist_timeview = (*i);
3539 AudioTimeAxisView* atv2 = dynamic_cast<AudioTimeAxisView*>(tracklist_timeview);
3540 list<TimeAxisView*> children_list;
3542 /* zeroes are audio tracks. ones are other types. */
3544 if (!atv2->hidden()) {
3546 if (visible_y_high < atv2->order) {
3547 visible_y_high = atv2->order;
3549 if (visible_y_low > atv2->order) {
3550 visible_y_low = atv2->order;
3553 if (!atv2->is_audio_track()) {
3554 tracks = tracks |= (0x01 << atv2->order);
3557 height_list[atv2->order] = (*i)->current_height();
3558 children = 1;
3559 if ((children_list = atv2->get_child_list()).size() > 0) {
3560 for (list<TimeAxisView*>::iterator j = children_list.begin(); j != children_list.end(); ++j) {
3561 tracks = tracks |= (0x01 << (atv2->order + children));
3562 height_list[atv2->order + children] = (*j)->current_height();
3563 numtracks++;
3564 children++;
3567 numtracks++;
3570 /* find the actual span according to the canvas */
3572 canvas_pointer_y_span = pointer_y_span;
3573 if (drag_info.dest_trackview->order >= tv->order) {
3574 int32_t y;
3575 for (y = tv->order; y < drag_info.dest_trackview->order; y++) {
3576 if (height_list[y] == 0 ) {
3577 canvas_pointer_y_span--;
3580 } else {
3581 int32_t y;
3582 for (y = drag_info.dest_trackview->order;y <= tv->order; y++) {
3583 if ( height_list[y] == 0 ) {
3584 canvas_pointer_y_span++;
3589 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
3590 RegionView* rv2 = (*i);
3591 double ix1, ix2, iy1, iy2;
3592 int32_t n = 0;
3594 if (rv2->region()->locked()) {
3595 continue;
3598 rv2->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
3599 rv2->get_canvas_group()->i2w (ix1, iy1);
3600 iy1 += vertical_adjustment.get_value() - canvas_timebars_vsize;
3602 TimeAxisView* tvp2 = trackview_by_y_position (iy1);
3603 RouteTimeAxisView* atv2 = dynamic_cast<RouteTimeAxisView*>(tvp2);
3605 if (atv2->order != original_pointer_order) {
3606 /* this isn't the pointer track */
3608 if (canvas_pointer_y_span > 0) {
3610 /* moving up the canvas */
3611 if ((atv2->order - canvas_pointer_y_span) >= visible_y_low) {
3613 int32_t visible_tracks = 0;
3614 while (visible_tracks < canvas_pointer_y_span ) {
3615 visible_tracks++;
3617 while (height_list[atv2->order - (visible_tracks - n)] == 0) {
3618 /* we're passing through a hidden track */
3619 n--;
3623 if (tracks[atv2->order - (canvas_pointer_y_span - n)] != 0x00) {
3624 clamp_y_axis = true;
3627 } else {
3628 clamp_y_axis = true;
3631 } else if (canvas_pointer_y_span < 0) {
3633 /*moving down the canvas*/
3635 if ((atv2->order - (canvas_pointer_y_span - n)) <= visible_y_high) { // we will overflow
3638 int32_t visible_tracks = 0;
3640 while (visible_tracks > canvas_pointer_y_span ) {
3641 visible_tracks--;
3643 while (height_list[atv2->order - (visible_tracks - n)] == 0) {
3644 n++;
3647 if ( tracks[atv2->order - ( canvas_pointer_y_span - n)] != 0x00) {
3648 clamp_y_axis = true;
3651 } else {
3653 clamp_y_axis = true;
3657 } else {
3659 /* this is the pointer's track */
3660 if ((atv2->order - pointer_y_span) > visible_y_high) { // we will overflow
3661 clamp_y_axis = true;
3662 } else if ((atv2->order - pointer_y_span) < visible_y_low) { // we will underflow
3663 clamp_y_axis = true;
3666 if (clamp_y_axis) {
3667 break;
3671 } else if (drag_info.dest_trackview == tv) {
3672 clamp_y_axis = true;
3675 y_axis_done:
3676 if (!clamp_y_axis) {
3677 drag_info.dest_trackview = tv;
3680 /************************************************************
3681 X DELTA COMPUTATION
3682 ************************************************************/
3684 /* compute the amount of pointer motion in frames, and where
3685 the region would be if we moved it by that much.
3687 if ( drag_info.move_threshold_passed ) {
3689 if (drag_info.current_pointer_frame >= drag_info.pointer_frame_offset) {
3691 nframes64_t sync_frame;
3692 nframes64_t sync_offset;
3693 int32_t sync_dir;
3695 pending_region_position = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
3697 sync_offset = clicked_regionview->region()->sync_offset (sync_dir);
3699 /* we don't handle a sync point that lies before zero.
3701 if (sync_dir >= 0 || (sync_dir < 0 && pending_region_position >= sync_offset)) {
3702 sync_frame = pending_region_position + (sync_dir*sync_offset);
3704 /* we snap if the snap modifier is not enabled.
3707 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
3708 snap_to (sync_frame);
3711 pending_region_position = clicked_regionview->region()->adjust_to_sync (sync_frame);
3713 } else {
3714 pending_region_position = drag_info.last_frame_position;
3717 } else {
3718 pending_region_position = 0;
3721 if (pending_region_position > max_frames - clicked_regionview->region()->length()) {
3722 pending_region_position = drag_info.last_frame_position;
3725 // printf ("3: pending_region_position= %lu %lu\n", pending_region_position, drag_info.last_frame_position );
3727 bool x_move_allowed;
3729 if (Config->get_edit_mode() == Lock) {
3730 if (drag_info.copy) {
3731 x_move_allowed = !drag_info.x_constrained;
3732 } else {
3733 /* in locked edit mode, reverse the usual meaning of x_constrained */
3734 x_move_allowed = drag_info.x_constrained;
3736 } else {
3737 x_move_allowed = !drag_info.x_constrained;
3740 if (( pending_region_position != drag_info.last_frame_position) && x_move_allowed ) {
3742 /* now compute the canvas unit distance we need to move the regionview
3743 to make it appear at the new location.
3746 if (pending_region_position > drag_info.last_frame_position) {
3747 x_delta = ((double) (pending_region_position - drag_info.last_frame_position) / frames_per_unit);
3748 } else {
3749 x_delta = -((double) (drag_info.last_frame_position - pending_region_position) / frames_per_unit);
3750 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
3752 RegionView* rv2 = (*i);
3754 // If any regionview is at zero, we need to know so we can stop further leftward motion.
3756 double ix1, ix2, iy1, iy2;
3757 rv2->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
3758 rv2->get_canvas_group()->i2w (ix1, iy1);
3760 if (-x_delta > ix1 + horizontal_adjustment.get_value()) {
3761 x_delta = 0;
3762 pending_region_position = drag_info.last_frame_position;
3763 break;
3769 drag_info.last_frame_position = pending_region_position;
3771 } else {
3772 x_delta = 0;
3775 } else {
3776 /* threshold not passed */
3778 x_delta = 0;
3781 /*************************************************************
3782 PREPARE TO MOVE
3783 ************************************************************/
3785 if (x_delta == 0 && (pointer_y_span == 0)) {
3786 /* haven't reached next snap point, and we're not switching
3787 trackviews. nothing to do.
3789 return;
3792 /*************************************************************
3793 MOTION
3794 ************************************************************/
3795 bool do_move = true;
3796 if (drag_info.first_move) {
3797 if (!drag_info.move_threshold_passed) {
3798 do_move = false;
3802 if (do_move) {
3804 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
3805 const list<RegionView*>& layered_regions = selection->regions.by_layer();
3807 for (list<RegionView*>::const_iterator i = layered_regions.begin(); i != layered_regions.end(); ++i) {
3809 RegionView* rv = (*i);
3810 double ix1, ix2, iy1, iy2;
3811 int32_t temp_pointer_y_span = pointer_y_span;
3813 if (rv->region()->locked()) {
3814 continue;
3817 /* get item BBox, which will be relative to parent. so we have
3818 to query on a child, then convert to world coordinates using
3819 the parent.
3822 rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
3823 rv->get_canvas_group()->i2w (ix1, iy1);
3825 /* for evaluation of the track position of iy1, we have to adjust
3826 to allow for the vertical scrolling adjustment and the height of the timebars.
3829 iy1 += get_trackview_group_vertical_offset ();
3830 if (drag_info.first_move) {
3832 // hide any dependent views
3834 rv->get_time_axis_view().hide_dependent_views (*rv);
3837 reparent to a non scrolling group so that we can keep the
3838 region selection above all time axis views.
3839 reparenting means we have to move the rv as the two
3840 parent groups have different coordinates.
3843 rv->get_canvas_group()->property_y() = iy1 - 1;
3844 rv->get_canvas_group()->reparent(*_region_motion_group);
3846 rv->fake_set_opaque (true);
3849 TimeAxisView* tvp2 = trackview_by_y_position (iy1);
3850 AudioTimeAxisView* canvas_atv = dynamic_cast<AudioTimeAxisView*>(tvp2);
3851 AudioTimeAxisView* temp_atv;
3853 if ((pointer_y_span != 0) && !clamp_y_axis) {
3854 y_delta = 0;
3855 int32_t x = 0;
3856 for (j = height_list.begin(); j!= height_list.end(); j++) {
3857 if (x == canvas_atv->order) {
3858 /* we found the track the region is on */
3859 if (x != original_pointer_order) {
3860 /*this isn't from the same track we're dragging from */
3861 temp_pointer_y_span = canvas_pointer_y_span;
3863 while (temp_pointer_y_span > 0) {
3864 /* we're moving up canvas-wise,
3865 so we need to find the next track height
3867 if (j != height_list.begin()) {
3868 j--;
3870 if (x != original_pointer_order) {
3871 /* we're not from the dragged track, so ignore hidden tracks. */
3872 if ((*j) == 0) {
3873 temp_pointer_y_span++;
3876 y_delta -= (*j);
3877 temp_pointer_y_span--;
3880 while (temp_pointer_y_span < 0) {
3881 y_delta += (*j);
3882 if (x != original_pointer_order) {
3883 if ((*j) == 0) {
3884 temp_pointer_y_span--;
3888 if (j != height_list.end()) {
3889 j++;
3891 temp_pointer_y_span++;
3893 /* find out where we'll be when we move and set height accordingly */
3895 tvp2 = trackview_by_y_position (iy1 + y_delta);
3896 temp_atv = dynamic_cast<AudioTimeAxisView*>(tvp2);
3897 rv->set_height (temp_atv->current_height());
3899 /* if you un-comment the following, the region colours will follow the track colours whilst dragging,
3900 personally, i think this can confuse things, but never mind.
3903 //const GdkColor& col (temp_atv->view->get_region_color());
3904 //rv->set_color (const_cast<GdkColor&>(col));
3905 break;
3907 x++;
3911 if (drag_info.brushing) {
3912 mouse_brush_insert_region (rv, pending_region_position);
3913 } else {
3914 rv->move (x_delta, y_delta);
3917 } /* foreach region */
3919 } /* if do_move */
3921 if (drag_info.first_move && drag_info.move_threshold_passed) {
3922 cursor_group->raise_to_top();
3923 drag_info.first_move = false;
3926 if (x_delta != 0 && !drag_info.brushing) {
3927 show_verbose_time_cursor (drag_info.last_frame_position, 10);
3931 void
3932 Editor::region_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
3934 bool nocommit = true;
3935 vector<RegionView*> copies;
3936 RouteTimeAxisView* source_tv;
3937 boost::shared_ptr<Diskstream> ds;
3938 boost::shared_ptr<Playlist> from_playlist;
3939 vector<RegionView*> new_selection;
3940 typedef set<boost::shared_ptr<Playlist> > PlaylistSet;
3941 PlaylistSet modified_playlists;
3942 PlaylistSet frozen_playlists;
3943 list <sigc::connection> modified_playlist_connections;
3944 pair<PlaylistSet::iterator,bool> insert_result, frozen_insert_result;
3945 nframes64_t drag_delta;
3946 bool changed_tracks, changed_position;
3948 /* first_move is set to false if the regionview has been moved in the
3949 motion handler.
3952 if (drag_info.first_move) {
3953 /* just a click */
3954 goto out;
3957 nocommit = false;
3959 if (Config->get_edit_mode() == Splice && !pre_drag_region_selection.empty()) {
3960 selection->set (pre_drag_region_selection);
3961 pre_drag_region_selection.clear ();
3964 if (drag_info.brushing) {
3965 /* all changes were made during motion event handlers */
3967 if (drag_info.copy) {
3968 for (list<RegionView*>::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
3969 copies.push_back (*i);
3973 goto out;
3976 char* op_string;
3978 /* reverse this here so that we have the correct logic to finalize
3979 the drag.
3982 if (Config->get_edit_mode() == Lock && !drag_info.copy) {
3983 drag_info.x_constrained = !drag_info.x_constrained;
3986 if (drag_info.copy) {
3987 if (drag_info.x_constrained) {
3988 op_string = _("fixed time region copy");
3989 } else {
3990 op_string = _("region copy");
3992 } else {
3993 if (drag_info.x_constrained) {
3994 op_string = _("fixed time region drag");
3995 } else {
3996 op_string = _("region drag");
4000 begin_reversible_command (op_string);
4001 changed_position = (drag_info.last_frame_position != (nframes64_t) (clicked_regionview->region()->position()));
4002 changed_tracks = (trackview_by_y_position (drag_info.current_pointer_y) != &clicked_regionview->get_time_axis_view());
4004 drag_delta = clicked_regionview->region()->position() - drag_info.last_frame_position;
4006 track_canvas->update_now ();
4008 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ) {
4010 RegionView* rv = (*i);
4011 double ix1, ix2, iy1, iy2;
4012 rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
4013 rv->get_canvas_group()->i2w (ix1, iy1);
4014 iy1 += vertical_adjustment.get_value() - canvas_timebars_vsize;
4016 TimeAxisView* dest_tv = trackview_by_y_position (iy1);
4017 AudioTimeAxisView* dest_atv = dynamic_cast<AudioTimeAxisView*>(dest_tv);
4018 nframes64_t where;
4020 if (rv->region()->locked()) {
4021 ++i;
4022 continue;
4025 if (changed_position && !drag_info.x_constrained) {
4026 where = rv->region()->position() - drag_delta;
4027 } else {
4028 where = rv->region()->position();
4031 boost::shared_ptr<Region> new_region;
4033 if (drag_info.copy) {
4034 /* we already made a copy */
4035 new_region = rv->region();
4037 /* undo the previous hide_dependent_views so that xfades don't
4038 disappear on copying regions
4041 //rv->get_time_axis_view().reveal_dependent_views (*rv);
4043 } else if (changed_tracks) {
4044 new_region = RegionFactory::create (rv->region());
4047 if (changed_tracks || drag_info.copy) {
4049 boost::shared_ptr<Playlist> to_playlist = dest_atv->playlist();
4051 latest_regionviews.clear ();
4053 sigc::connection c = dest_atv->view()->RegionViewAdded.connect (mem_fun(*this, &Editor::collect_new_region_view));
4055 insert_result = modified_playlists.insert (to_playlist);
4056 if (insert_result.second) {
4057 session->add_command (new MementoCommand<Playlist>(*to_playlist, &to_playlist->get_state(), 0));
4060 to_playlist->add_region (new_region, where);
4062 c.disconnect ();
4064 if (!latest_regionviews.empty()) {
4065 // XXX why just the first one ? we only expect one
4066 //dest_atv->reveal_dependent_views (*latest_regionviews.front());
4067 new_selection.push_back (latest_regionviews.front());
4070 } else {
4072 motion on the same track. plonk the previously reparented region
4073 back to its original canvas group (its streamview).
4074 No need to do anything for copies as they are fake regions which will be deleted.
4077 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (dest_atv);
4078 rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
4079 rv->get_canvas_group()->property_y() = 0;
4081 /* just change the model */
4083 boost::shared_ptr<Playlist> playlist = dest_atv->playlist();
4085 insert_result = modified_playlists.insert (playlist);
4086 if (insert_result.second) {
4087 session->add_command (new MementoCommand<Playlist>(*playlist, &playlist->get_state(), 0));
4089 /* freeze to avoid lots of relayering in the case of a multi-region drag */
4090 frozen_insert_result = frozen_playlists.insert(playlist);
4091 if (frozen_insert_result.second) {
4092 playlist->freeze();
4095 rv->region()->set_position (where, (void*) this);
4098 if (changed_tracks && !drag_info.copy) {
4100 /* get the playlist where this drag started. we can't use rv->region()->playlist()
4101 because we may have copied the region and it has not been attached to a playlist.
4104 assert ((source_tv = dynamic_cast<RouteTimeAxisView*> (&rv->get_time_axis_view())));
4105 assert ((ds = source_tv->get_diskstream()));
4106 assert ((from_playlist = ds->playlist()));
4108 /* moved to a different audio track, without copying */
4110 /* the region that used to be in the old playlist is not
4111 moved to the new one - we use a copy of it. as a result,
4112 any existing editor for the region should no longer be
4113 visible.
4116 rv->hide_region_editor();
4117 rv->fake_set_opaque (false);
4119 /* remove the region from the old playlist */
4121 insert_result = modified_playlists.insert (from_playlist);
4122 if (insert_result.second) {
4123 session->add_command (new MementoCommand<Playlist>(*from_playlist, &from_playlist->get_state(), 0));
4126 from_playlist->remove_region ((rv->region()));
4128 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
4129 was selected in all of them, then removing it from a playlist will have removed all
4130 trace of it from the selection (i.e. there were N regions selected, we removed 1,
4131 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
4132 corresponding regionview, and the selection is now empty).
4134 this could have invalidated any and all iterators into the region selection.
4136 the heuristic we use here is: if the region selection is empty, break out of the loop
4137 here. if the region selection is not empty, then restart the loop because we know that
4138 we must have removed at least the region(view) we've just been working on as well as any
4139 that we processed on previous iterations.
4141 EXCEPT .... if we are doing a copy drag, then the selection hasn't been modified and
4142 we can just iterate.
4145 if (selection->regions.empty()) {
4146 break;
4147 } else {
4148 i = selection->regions.by_layer().begin();
4151 } else {
4152 ++i;
4155 if (drag_info.copy) {
4156 copies.push_back (rv);
4160 if (new_selection.empty()) {
4161 if (drag_info.copy) {
4162 /* the region(view)s that are selected and being dragged around
4163 are copies and do not belong to any track. remove them
4164 from the selection right here.
4166 selection->clear_regions();
4168 } else {
4169 /* this will clear any existing selection that would have been
4170 cleared in the other clause above
4172 selection->set (new_selection);
4175 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
4176 (*p)->thaw();
4179 out:
4180 if (!nocommit) {
4181 for (set<boost::shared_ptr<Playlist> >::iterator p = modified_playlists.begin(); p != modified_playlists.end(); ++p) {
4182 session->add_command (new MementoCommand<Playlist>(*(*p), 0, &(*p)->get_state()));
4184 commit_reversible_command ();
4187 for (vector<RegionView*>::iterator x = copies.begin(); x != copies.end(); ++x) {
4188 delete *x;
4193 void
4194 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
4196 /* Either add to or set the set the region selection, unless
4197 this is an alignment click (control used)
4200 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
4201 TimeAxisView* tv = &rv.get_time_axis_view();
4202 AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*>(tv);
4203 double speed = 1.0;
4204 if (atv && atv->is_audio_track()) {
4205 speed = atv->get_diskstream()->speed();
4208 nframes64_t where = get_preferred_edit_position();
4210 if (where >= 0) {
4212 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
4214 align_region (rv.region(), SyncPoint, (nframes64_t) (where * speed));
4216 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
4218 align_region (rv.region(), End, (nframes64_t) (where * speed));
4220 } else {
4222 align_region (rv.region(), Start, (nframes64_t) (where * speed));
4228 void
4229 Editor::show_verbose_time_cursor (nframes64_t frame, double offset, double xpos, double ypos)
4231 char buf[128];
4232 SMPTE::Time smpte;
4233 BBT_Time bbt;
4234 int hours, mins;
4235 nframes64_t frame_rate;
4236 float secs;
4238 if (session == 0) {
4239 return;
4242 AudioClock::Mode m;
4244 if (Profile->get_sae() || Profile->get_small_screen()) {
4245 m = ARDOUR_UI::instance()->primary_clock.mode();
4246 } else {
4247 m = ARDOUR_UI::instance()->secondary_clock.mode();
4250 switch (m) {
4251 case AudioClock::BBT:
4252 session->bbt_time (frame, bbt);
4253 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, bbt.bars, bbt.beats, bbt.ticks);
4254 break;
4256 case AudioClock::SMPTE:
4257 session->smpte_time (frame, smpte);
4258 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
4259 break;
4261 case AudioClock::MinSec:
4262 /* XXX this is copied from show_verbose_duration_cursor() */
4263 frame_rate = session->frame_rate();
4264 hours = frame / (frame_rate * 3600);
4265 frame = frame % (frame_rate * 3600);
4266 mins = frame / (frame_rate * 60);
4267 frame = frame % (frame_rate * 60);
4268 secs = (float) frame / (float) frame_rate;
4269 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%.4f", hours, mins, secs);
4270 break;
4272 default:
4273 snprintf (buf, sizeof(buf), "%" PRIi64, frame);
4274 break;
4277 if (xpos >= 0 && ypos >=0) {
4278 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
4280 else {
4281 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);
4283 show_verbose_canvas_cursor ();
4286 void
4287 Editor::show_verbose_duration_cursor (nframes64_t start, nframes64_t end, double offset, double xpos, double ypos)
4289 char buf[128];
4290 SMPTE::Time smpte;
4291 BBT_Time sbbt;
4292 BBT_Time ebbt;
4293 int hours, mins;
4294 nframes64_t distance, frame_rate;
4295 float secs;
4296 Meter meter_at_start(session->tempo_map().meter_at(start));
4298 if (session == 0) {
4299 return;
4302 AudioClock::Mode m;
4304 if (Profile->get_sae() || Profile->get_small_screen()) {
4305 m = ARDOUR_UI::instance()->primary_clock.mode ();
4306 } else {
4307 m = ARDOUR_UI::instance()->secondary_clock.mode ();
4310 switch (m) {
4311 case AudioClock::BBT:
4312 session->bbt_time (start, sbbt);
4313 session->bbt_time (end, ebbt);
4315 /* subtract */
4316 /* XXX this computation won't work well if the
4317 user makes a selection that spans any meter changes.
4320 ebbt.bars -= sbbt.bars;
4321 if (ebbt.beats >= sbbt.beats) {
4322 ebbt.beats -= sbbt.beats;
4323 } else {
4324 ebbt.bars--;
4325 ebbt.beats = int(meter_at_start.beats_per_bar()) + ebbt.beats - sbbt.beats;
4327 if (ebbt.ticks >= sbbt.ticks) {
4328 ebbt.ticks -= sbbt.ticks;
4329 } else {
4330 ebbt.beats--;
4331 ebbt.ticks = int(Meter::ticks_per_beat) + ebbt.ticks - sbbt.ticks;
4334 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, ebbt.bars, ebbt.beats, ebbt.ticks);
4335 break;
4337 case AudioClock::SMPTE:
4338 session->smpte_duration (end - start, smpte);
4339 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
4340 break;
4342 case AudioClock::MinSec:
4343 /* XXX this stuff should be elsewhere.. */
4344 distance = end - start;
4345 frame_rate = session->frame_rate();
4346 hours = distance / (frame_rate * 3600);
4347 distance = distance % (frame_rate * 3600);
4348 mins = distance / (frame_rate * 60);
4349 distance = distance % (frame_rate * 60);
4350 secs = (float) distance / (float) frame_rate;
4351 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%.4f", hours, mins, secs);
4352 break;
4354 default:
4355 snprintf (buf, sizeof(buf), "%" PRIi64, end - start);
4356 break;
4359 if (xpos >= 0 && ypos >=0) {
4360 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
4362 else {
4363 set_verbose_canvas_cursor (buf, drag_info.current_pointer_x + offset, drag_info.current_pointer_y + offset);
4366 show_verbose_canvas_cursor ();
4369 void
4370 Editor::collect_new_region_view (RegionView* rv)
4372 latest_regionviews.push_back (rv);
4375 void
4376 Editor::start_selection_grab (ArdourCanvas::Item* item, GdkEvent* event)
4378 if (clicked_regionview == 0) {
4379 return;
4382 /* lets try to create new Region for the selection */
4384 vector<boost::shared_ptr<AudioRegion> > new_regions;
4385 create_region_from_selection (new_regions);
4387 if (new_regions.empty()) {
4388 return;
4391 /* XXX fix me one day to use all new regions */
4393 boost::shared_ptr<Region> region (new_regions.front());
4395 /* add it to the current stream/playlist.
4397 tricky: the streamview for the track will add a new regionview. we will
4398 catch the signal it sends when it creates the regionview to
4399 set the regionview we want to then drag.
4402 latest_regionviews.clear();
4403 sigc::connection c = clicked_audio_trackview->view()->RegionViewAdded.connect (mem_fun(*this, &Editor::collect_new_region_view));
4405 /* A selection grab currently creates two undo/redo operations, one for
4406 creating the new region and another for moving it.
4409 begin_reversible_command (_("selection grab"));
4411 boost::shared_ptr<Playlist> playlist = clicked_trackview->playlist();
4413 XMLNode *before = &(playlist->get_state());
4414 clicked_trackview->playlist()->add_region (region, selection->time[clicked_selection].start);
4415 XMLNode *after = &(playlist->get_state());
4416 session->add_command(new MementoCommand<Playlist>(*playlist, before, after));
4418 commit_reversible_command ();
4420 c.disconnect ();
4422 if (latest_regionviews.empty()) {
4423 /* something went wrong */
4424 return;
4427 /* we need to deselect all other regionviews, and select this one
4428 i'm ignoring undo stuff, because the region creation will take care of it
4430 selection->set (latest_regionviews);
4432 drag_info.item = latest_regionviews.front()->get_canvas_group();
4433 drag_info.data = latest_regionviews.front();
4434 drag_info.motion_callback = &Editor::region_drag_motion_callback;
4435 drag_info.finished_callback = &Editor::region_drag_finished_callback;
4437 start_grab (event);
4439 drag_info.source_trackview = clicked_trackview;
4440 drag_info.dest_trackview = drag_info.source_trackview;
4441 drag_info.last_frame_position = latest_regionviews.front()->region()->position();
4442 drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
4444 show_verbose_time_cursor (drag_info.last_frame_position, 10);
4447 void
4448 Editor::cancel_selection ()
4450 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
4451 (*i)->hide_selection ();
4453 selection->clear ();
4454 clicked_selection = 0;
4457 void
4458 Editor::start_selection_op (ArdourCanvas::Item* item, GdkEvent* event, SelectionOp op)
4460 nframes64_t start = 0;
4461 nframes64_t end = 0;
4463 if (session == 0) {
4464 return;
4467 drag_info.item = item;
4468 drag_info.motion_callback = &Editor::drag_selection;
4469 drag_info.finished_callback = &Editor::end_selection_op;
4471 selection_op = op;
4473 switch (op) {
4474 case CreateSelection:
4475 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
4476 drag_info.copy = true;
4477 } else {
4478 drag_info.copy = false;
4480 start_grab (event, selector_cursor);
4481 break;
4483 case SelectionStartTrim:
4484 if (clicked_trackview) {
4485 clicked_trackview->order_selection_trims (item, true);
4487 start_grab (event, trimmer_cursor);
4488 start = selection->time[clicked_selection].start;
4489 drag_info.pointer_frame_offset = drag_info.grab_frame - start;
4490 break;
4492 case SelectionEndTrim:
4493 if (clicked_trackview) {
4494 clicked_trackview->order_selection_trims (item, false);
4496 start_grab (event, trimmer_cursor);
4497 end = selection->time[clicked_selection].end;
4498 drag_info.pointer_frame_offset = drag_info.grab_frame - end;
4499 break;
4501 case SelectionMove:
4502 start = selection->time[clicked_selection].start;
4503 start_grab (event);
4504 drag_info.pointer_frame_offset = drag_info.grab_frame - start;
4505 break;
4508 if (selection_op == SelectionMove) {
4509 show_verbose_time_cursor(start, 10);
4510 } else {
4511 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
4515 void
4516 Editor::drag_selection (ArdourCanvas::Item* item, GdkEvent* event)
4518 nframes64_t start = 0;
4519 nframes64_t end = 0;
4520 nframes64_t length;
4521 nframes64_t pending_position;
4523 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
4524 pending_position = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
4525 } else {
4526 pending_position = 0;
4529 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
4530 snap_to (pending_position);
4533 /* only alter selection if the current frame is
4534 different from the last frame position (adjusted)
4537 if (pending_position == drag_info.last_pointer_frame) return;
4539 switch (selection_op) {
4540 case CreateSelection:
4542 if (drag_info.first_move) {
4543 snap_to (drag_info.grab_frame);
4546 if (pending_position < drag_info.grab_frame) {
4547 start = pending_position;
4548 end = drag_info.grab_frame;
4549 } else {
4550 end = pending_position;
4551 start = drag_info.grab_frame;
4554 /* first drag: Either add to the selection
4555 or create a new selection->
4558 if (drag_info.first_move) {
4560 begin_reversible_command (_("range selection"));
4562 if (drag_info.copy) {
4563 /* adding to the selection */
4564 clicked_selection = selection->add (start, end);
4565 drag_info.copy = false;
4566 } else {
4567 /* new selection-> */
4568 clicked_selection = selection->set (clicked_trackview, start, end);
4571 break;
4573 case SelectionStartTrim:
4575 if (drag_info.first_move) {
4576 begin_reversible_command (_("trim selection start"));
4579 start = selection->time[clicked_selection].start;
4580 end = selection->time[clicked_selection].end;
4582 if (pending_position > end) {
4583 start = end;
4584 } else {
4585 start = pending_position;
4587 break;
4589 case SelectionEndTrim:
4591 if (drag_info.first_move) {
4592 begin_reversible_command (_("trim selection end"));
4595 start = selection->time[clicked_selection].start;
4596 end = selection->time[clicked_selection].end;
4598 if (pending_position < start) {
4599 end = start;
4600 } else {
4601 end = pending_position;
4604 break;
4606 case SelectionMove:
4608 if (drag_info.first_move) {
4609 begin_reversible_command (_("move selection"));
4612 start = selection->time[clicked_selection].start;
4613 end = selection->time[clicked_selection].end;
4615 length = end - start;
4617 start = pending_position;
4618 snap_to (start);
4620 end = start + length;
4622 break;
4625 if (event->button.x >= horizontal_adjustment.get_value() + canvas_width) {
4626 start_canvas_autoscroll (1, 0);
4629 if (start != end) {
4630 selection->replace (clicked_selection, start, end);
4633 drag_info.last_pointer_frame = pending_position;
4634 drag_info.first_move = false;
4636 if (selection_op == SelectionMove) {
4637 show_verbose_time_cursor(start, 10);
4638 } else {
4639 show_verbose_time_cursor(pending_position, 10);
4643 void
4644 Editor::end_selection_op (ArdourCanvas::Item* item, GdkEvent* event)
4646 if (!drag_info.first_move) {
4647 drag_selection (item, event);
4648 /* XXX this is not object-oriented programming at all. ick */
4649 if (selection->time.consolidate()) {
4650 selection->TimeChanged ();
4652 commit_reversible_command ();
4653 } else {
4654 /* just a click, no pointer movement.*/
4656 if (Keyboard::no_modifier_keys_pressed (&event->button)) {
4658 selection->clear_time();
4663 /* XXX what happens if its a music selection? */
4664 session->set_audio_range (selection->time);
4665 stop_canvas_autoscroll ();
4668 void
4669 Editor::start_trim (ArdourCanvas::Item* item, GdkEvent* event)
4671 double speed = 1.0;
4672 TimeAxisView* tvp = clicked_trackview;
4673 AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp);
4675 if (tv && tv->is_audio_track()) {
4676 speed = tv->get_diskstream()->speed();
4679 nframes64_t region_start = (nframes64_t) (clicked_regionview->region()->position() / speed);
4680 nframes64_t region_end = (nframes64_t) (clicked_regionview->region()->last_frame() / speed);
4681 nframes64_t region_length = (nframes64_t) (clicked_regionview->region()->length() / speed);
4683 //drag_info.item = clicked_regionview->get_name_highlight();
4684 drag_info.item = item;
4685 drag_info.motion_callback = &Editor::trim_motion_callback;
4686 drag_info.finished_callback = &Editor::trim_finished_callback;
4688 start_grab (event, trimmer_cursor);
4690 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
4691 trim_op = ContentsTrim;
4692 } else {
4693 /* These will get overridden for a point trim.*/
4694 if (drag_info.current_pointer_frame < (region_start + region_length/2)) {
4695 /* closer to start */
4696 trim_op = StartTrim;
4697 } else if (drag_info.current_pointer_frame > (region_end - region_length/2)) {
4698 /* closer to end */
4699 trim_op = EndTrim;
4703 switch (trim_op) {
4704 case StartTrim:
4705 show_verbose_time_cursor(region_start, 10);
4706 break;
4707 case EndTrim:
4708 show_verbose_time_cursor(region_end, 10);
4709 break;
4710 case ContentsTrim:
4711 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
4712 break;
4716 void
4717 Editor::trim_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
4719 RegionView* rv = clicked_regionview;
4720 nframes64_t frame_delta = 0;
4721 bool left_direction;
4722 bool obey_snap = !Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier());
4724 /* snap modifier works differently here..
4725 its' current state has to be passed to the
4726 various trim functions in order to work properly
4729 double speed = 1.0;
4730 TimeAxisView* tvp = clicked_trackview;
4731 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
4732 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
4734 if (tv && tv->is_audio_track()) {
4735 speed = tv->get_diskstream()->speed();
4738 if (drag_info.last_pointer_frame > drag_info.current_pointer_frame) {
4739 left_direction = true;
4740 } else {
4741 left_direction = false;
4744 if (obey_snap) {
4745 snap_to (drag_info.current_pointer_frame);
4748 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) {
4749 return;
4752 if (drag_info.first_move) {
4754 string trim_type;
4756 switch (trim_op) {
4757 case StartTrim:
4758 trim_type = "Region start trim";
4759 break;
4760 case EndTrim:
4761 trim_type = "Region end trim";
4762 break;
4763 case ContentsTrim:
4764 trim_type = "Region content trim";
4765 break;
4768 begin_reversible_command (trim_type);
4770 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
4771 (*i)->fake_set_opaque(false);
4772 (*i)->region()->freeze ();
4774 AudioRegionView* const arv = dynamic_cast<AudioRegionView*>(*i);
4775 if (arv)
4776 arv->temporarily_hide_envelope ();
4778 boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
4779 insert_result = motion_frozen_playlists.insert (pl);
4780 if (insert_result.second) {
4781 session->add_command(new MementoCommand<Playlist>(*pl, &pl->get_state(), 0));
4786 if (left_direction) {
4787 frame_delta = (drag_info.last_pointer_frame - drag_info.current_pointer_frame);
4788 } else {
4789 frame_delta = (drag_info.current_pointer_frame - drag_info.last_pointer_frame);
4792 switch (trim_op) {
4793 case StartTrim:
4794 if ((left_direction == false) && (drag_info.current_pointer_frame <= rv->region()->first_frame()/speed)) {
4795 break;
4796 } else {
4797 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
4798 single_start_trim (**i, frame_delta, left_direction, obey_snap);
4800 break;
4803 case EndTrim:
4804 if ((left_direction == true) && (drag_info.current_pointer_frame > (nframes64_t) (rv->region()->last_frame()/speed))) {
4805 break;
4806 } else {
4807 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
4808 single_end_trim (**i, frame_delta, left_direction, obey_snap);
4810 break;
4813 case ContentsTrim:
4815 bool swap_direction = false;
4817 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
4818 swap_direction = true;
4821 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
4822 i != selection->regions.by_layer().end(); ++i)
4824 single_contents_trim (**i, frame_delta, left_direction, swap_direction, obey_snap);
4827 break;
4830 switch (trim_op) {
4831 case StartTrim:
4832 show_verbose_time_cursor((nframes64_t) (rv->region()->position()/speed), 10);
4833 break;
4834 case EndTrim:
4835 show_verbose_time_cursor((nframes64_t) (rv->region()->last_frame()/speed), 10);
4836 break;
4837 case ContentsTrim:
4838 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
4839 break;
4842 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
4843 drag_info.first_move = false;
4846 void
4847 Editor::single_contents_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool swap_direction, bool obey_snap)
4849 boost::shared_ptr<Region> region (rv.region());
4851 if (region->locked()) {
4852 return;
4855 nframes64_t new_bound;
4857 double speed = 1.0;
4858 TimeAxisView* tvp = clicked_trackview;
4859 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
4861 if (tv && tv->is_audio_track()) {
4862 speed = tv->get_diskstream()->speed();
4865 if (left_direction) {
4866 if (swap_direction) {
4867 new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
4868 } else {
4869 new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
4871 } else {
4872 if (swap_direction) {
4873 new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
4874 } else {
4875 new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
4879 if (obey_snap) {
4880 snap_to (new_bound);
4882 region->trim_start ((nframes64_t) (new_bound * speed), this);
4883 rv.region_changed (StartChanged);
4886 void
4887 Editor::single_start_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool obey_snap)
4889 boost::shared_ptr<Region> region (rv.region());
4891 if (region->locked()) {
4892 return;
4895 nframes64_t new_bound;
4897 double speed = 1.0;
4898 TimeAxisView* tvp = clicked_trackview;
4899 AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp);
4901 if (tv && tv->is_audio_track()) {
4902 speed = tv->get_diskstream()->speed();
4905 if (left_direction) {
4906 new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
4907 } else {
4908 new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
4911 if (obey_snap) {
4912 snap_to (new_bound, (left_direction ? 0 : 1));
4915 region->trim_front ((nframes64_t) (new_bound * speed), this);
4917 rv.region_changed (Change (LengthChanged|PositionChanged|StartChanged));
4920 void
4921 Editor::single_end_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool obey_snap)
4923 boost::shared_ptr<Region> region (rv.region());
4925 if (region->locked()) {
4926 return;
4929 nframes64_t new_bound;
4931 double speed = 1.0;
4932 TimeAxisView* tvp = clicked_trackview;
4933 AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp);
4935 if (tv && tv->is_audio_track()) {
4936 speed = tv->get_diskstream()->speed();
4939 if (left_direction) {
4940 new_bound = (nframes64_t) ((region->last_frame() + 1)/speed) - frame_delta;
4941 } else {
4942 new_bound = (nframes64_t) ((region->last_frame() + 1)/speed) + frame_delta;
4945 if (obey_snap) {
4946 snap_to (new_bound);
4948 region->trim_end ((nframes64_t) (new_bound * speed), this);
4949 rv.region_changed (LengthChanged);
4952 void
4953 Editor::trim_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
4955 if (!drag_info.first_move) {
4956 trim_motion_callback (item, event);
4958 if (!selection->selected (clicked_regionview)) {
4959 thaw_region_after_trim (*clicked_regionview);
4960 } else {
4962 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
4963 i != selection->regions.by_layer().end(); ++i)
4965 thaw_region_after_trim (**i);
4966 (*i)->fake_set_opaque (true);
4970 for (set<boost::shared_ptr<Playlist> >::iterator p = motion_frozen_playlists.begin(); p != motion_frozen_playlists.end(); ++p) {
4971 //(*p)->thaw ();
4972 session->add_command (new MementoCommand<Playlist>(*(*p).get(), 0, &(*p)->get_state()));
4975 motion_frozen_playlists.clear ();
4977 commit_reversible_command();
4978 } else {
4979 /* no mouse movement */
4980 point_trim (event);
4984 void
4985 Editor::point_trim (GdkEvent* event)
4987 RegionView* rv = clicked_regionview;
4988 nframes64_t new_bound = drag_info.current_pointer_frame;
4990 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
4991 snap_to (new_bound);
4994 /* Choose action dependant on which button was pressed */
4995 switch (event->button.button) {
4996 case 1:
4997 trim_op = StartTrim;
4998 begin_reversible_command (_("Start point trim"));
5000 if (selection->selected (rv)) {
5002 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
5003 i != selection->regions.by_layer().end(); ++i)
5005 if (!(*i)->region()->locked()) {
5006 boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
5007 XMLNode &before = pl->get_state();
5008 (*i)->region()->trim_front (new_bound, this);
5009 XMLNode &after = pl->get_state();
5010 session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
5014 } else {
5016 if (!rv->region()->locked()) {
5017 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
5018 XMLNode &before = pl->get_state();
5019 rv->region()->trim_front (new_bound, this);
5020 XMLNode &after = pl->get_state();
5021 session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
5025 commit_reversible_command();
5027 break;
5028 case 2:
5029 trim_op = EndTrim;
5030 begin_reversible_command (_("End point trim"));
5032 if (selection->selected (rv)) {
5034 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
5036 if (!(*i)->region()->locked()) {
5037 boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
5038 XMLNode &before = pl->get_state();
5039 (*i)->region()->trim_end (new_bound, this);
5040 XMLNode &after = pl->get_state();
5041 session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
5045 } else {
5047 if (!rv->region()->locked()) {
5048 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
5049 XMLNode &before = pl->get_state();
5050 rv->region()->trim_end (new_bound, this);
5051 XMLNode &after = pl->get_state();
5052 session->add_command (new MementoCommand<Playlist>(*pl.get(), &before, &after));
5056 commit_reversible_command();
5058 break;
5059 default:
5060 break;
5064 void
5065 Editor::thaw_region_after_trim (RegionView& rv)
5067 boost::shared_ptr<Region> region (rv.region());
5069 if (region->locked()) {
5070 return;
5073 region->thaw (_("trimmed region"));
5074 XMLNode &after = region->playlist()->get_state();
5075 session->add_command (new MementoCommand<Playlist>(*(region->playlist()), 0, &after));
5077 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(&rv);
5078 if (arv)
5079 arv->unhide_envelope ();
5082 void
5083 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* event)
5085 Marker* marker;
5086 bool is_start;
5088 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
5089 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
5090 /*NOTREACHED*/
5093 Location* location = find_location_from_marker (marker, is_start);
5094 location->set_hidden (true, this);
5098 void
5099 Editor::start_range_markerbar_op (ArdourCanvas::Item* item, GdkEvent* event, RangeMarkerOp op)
5101 if (session == 0) {
5102 return;
5105 drag_info.item = item;
5106 drag_info.motion_callback = &Editor::drag_range_markerbar_op;
5107 drag_info.finished_callback = &Editor::end_range_markerbar_op;
5109 range_marker_op = op;
5111 if (!temp_location) {
5112 temp_location = new Location;
5115 switch (op) {
5116 case CreateRangeMarker:
5117 case CreateTransportMarker:
5118 case CreateCDMarker:
5120 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
5121 drag_info.copy = true;
5122 } else {
5123 drag_info.copy = false;
5125 start_grab (event, selector_cursor);
5126 break;
5129 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
5133 void
5134 Editor::drag_range_markerbar_op (ArdourCanvas::Item* item, GdkEvent* event)
5136 nframes64_t start = 0;
5137 nframes64_t end = 0;
5138 ArdourCanvas::SimpleRect *crect;
5140 switch (range_marker_op) {
5141 case CreateRangeMarker:
5142 crect = range_bar_drag_rect;
5143 break;
5144 case CreateTransportMarker:
5145 crect = transport_bar_drag_rect;
5146 break;
5147 case CreateCDMarker:
5148 crect = cd_marker_bar_drag_rect;
5149 break;
5150 default:
5151 cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl;
5152 return;
5153 break;
5156 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
5157 snap_to (drag_info.current_pointer_frame);
5160 /* only alter selection if the current frame is
5161 different from the last frame position.
5164 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) return;
5166 switch (range_marker_op) {
5167 case CreateRangeMarker:
5168 case CreateTransportMarker:
5169 case CreateCDMarker:
5170 if (drag_info.first_move) {
5171 snap_to (drag_info.grab_frame);
5174 if (drag_info.current_pointer_frame < drag_info.grab_frame) {
5175 start = drag_info.current_pointer_frame;
5176 end = drag_info.grab_frame;
5177 } else {
5178 end = drag_info.current_pointer_frame;
5179 start = drag_info.grab_frame;
5182 /* first drag: Either add to the selection
5183 or create a new selection.
5186 if (drag_info.first_move) {
5188 temp_location->set (start, end);
5190 crect->show ();
5192 update_marker_drag_item (temp_location);
5193 range_marker_drag_rect->show();
5194 //range_marker_drag_rect->raise_to_top();
5197 break;
5200 if (event->button.x >= horizontal_adjustment.get_value() + canvas_width) {
5201 start_canvas_autoscroll (1, 0);
5204 if (start != end) {
5205 temp_location->set (start, end);
5207 double x1 = frame_to_pixel (start);
5208 double x2 = frame_to_pixel (end);
5209 crect->property_x1() = x1;
5210 crect->property_x2() = x2;
5212 update_marker_drag_item (temp_location);
5215 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
5216 drag_info.first_move = false;
5218 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
5222 void
5223 Editor::end_range_markerbar_op (ArdourCanvas::Item* item, GdkEvent* event)
5225 Location * newloc = 0;
5226 string rangename;
5227 int flags;
5229 if (!drag_info.first_move) {
5230 drag_range_markerbar_op (item, event);
5232 switch (range_marker_op) {
5233 case CreateRangeMarker:
5234 case CreateCDMarker:
5236 begin_reversible_command (_("new range marker"));
5237 XMLNode &before = session->locations()->get_state();
5238 session->locations()->next_available_name(rangename,"unnamed");
5239 if (range_marker_op == CreateCDMarker) {
5240 flags = Location::IsRangeMarker|Location::IsCDMarker;
5241 cd_marker_bar_drag_rect->hide();
5243 else {
5244 flags = Location::IsRangeMarker;
5245 range_bar_drag_rect->hide();
5247 newloc = new Location(temp_location->start(), temp_location->end(), rangename, (Location::Flags) flags);
5248 session->locations()->add (newloc, true);
5249 XMLNode &after = session->locations()->get_state();
5250 session->add_command(new MementoCommand<Locations>(*(session->locations()), &before, &after));
5251 commit_reversible_command ();
5253 range_marker_drag_rect->hide();
5254 break;
5257 case CreateTransportMarker:
5258 // popup menu to pick loop or punch
5259 new_transport_marker_context_menu (&event->button, item);
5261 break;
5263 } else {
5264 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5266 if (Keyboard::no_modifier_keys_pressed (&event->button) && range_marker_op != CreateCDMarker) {
5268 nframes64_t start;
5269 nframes64_t end;
5271 start = session->locations()->first_mark_before (drag_info.grab_frame);
5272 end = session->locations()->first_mark_after (drag_info.grab_frame);
5274 if (end == max_frames) {
5275 end = session->current_end_frame ();
5278 if (start == 0) {
5279 start = session->current_start_frame ();
5282 switch (mouse_mode) {
5283 case MouseObject:
5284 /* find the two markers on either side and then make the selection from it */
5285 select_all_within (start, end, 0.0f, FLT_MAX, track_views, Selection::Set);
5286 break;
5288 case MouseRange:
5289 /* find the two markers on either side of the click and make the range out of it */
5290 selection->set (0, start, end);
5291 break;
5293 default:
5294 break;
5299 stop_canvas_autoscroll ();
5304 void
5305 Editor::start_mouse_zoom (ArdourCanvas::Item* item, GdkEvent* event)
5307 drag_info.item = item;
5308 drag_info.motion_callback = &Editor::drag_mouse_zoom;
5309 drag_info.finished_callback = &Editor::end_mouse_zoom;
5311 start_grab (event, zoom_cursor);
5313 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5316 void
5317 Editor::drag_mouse_zoom (ArdourCanvas::Item* item, GdkEvent* event)
5319 nframes64_t start;
5320 nframes64_t end;
5322 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
5323 snap_to (drag_info.current_pointer_frame);
5325 if (drag_info.first_move) {
5326 snap_to (drag_info.grab_frame);
5330 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) return;
5332 /* base start and end on initial click position */
5333 if (drag_info.current_pointer_frame < drag_info.grab_frame) {
5334 start = drag_info.current_pointer_frame;
5335 end = drag_info.grab_frame;
5336 } else {
5337 end = drag_info.current_pointer_frame;
5338 start = drag_info.grab_frame;
5341 if (start != end) {
5343 if (drag_info.first_move) {
5344 zoom_rect->show();
5345 zoom_rect->raise_to_top();
5348 reposition_zoom_rect(start, end);
5350 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
5351 drag_info.first_move = false;
5353 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5357 void
5358 Editor::end_mouse_zoom (ArdourCanvas::Item* item, GdkEvent* event)
5360 if (!drag_info.first_move) {
5361 drag_mouse_zoom (item, event);
5363 if (drag_info.grab_frame < drag_info.last_pointer_frame) {
5364 temporal_zoom_by_frame (drag_info.grab_frame, drag_info.last_pointer_frame, "mouse zoom");
5365 } else {
5366 temporal_zoom_by_frame (drag_info.last_pointer_frame, drag_info.grab_frame, "mouse zoom");
5368 } else {
5369 temporal_zoom_to_frame (false, drag_info.grab_frame);
5371 temporal_zoom_step (false);
5372 center_screen (drag_info.grab_frame);
5376 zoom_rect->hide();
5379 void
5380 Editor::reposition_zoom_rect (nframes64_t start, nframes64_t end)
5382 double x1 = frame_to_pixel (start);
5383 double x2 = frame_to_pixel (end);
5384 double y2 = full_canvas_height - 1.0;
5386 zoom_rect->property_x1() = x1;
5387 zoom_rect->property_y1() = 1.0;
5388 zoom_rect->property_x2() = x2;
5389 zoom_rect->property_y2() = y2;
5392 void
5393 Editor::start_rubberband_select (ArdourCanvas::Item* item, GdkEvent* event)
5395 drag_info.item = item;
5396 drag_info.motion_callback = &Editor::drag_rubberband_select;
5397 drag_info.finished_callback = &Editor::end_rubberband_select;
5399 start_grab (event, cross_hair_cursor);
5401 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5404 void
5405 Editor::drag_rubberband_select (ArdourCanvas::Item* item, GdkEvent* event)
5407 nframes64_t start;
5408 nframes64_t end;
5409 double y1;
5410 double y2;
5412 /* use a bigger drag threshold than the default */
5414 if (abs ((int) (drag_info.current_pointer_frame - drag_info.grab_frame)) < 8) {
5415 return;
5418 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier()) && Config->get_rubberbanding_snaps_to_grid()) {
5419 if (drag_info.first_move) {
5420 snap_to (drag_info.grab_frame);
5422 snap_to (drag_info.current_pointer_frame);
5425 /* base start and end on initial click position */
5427 if (drag_info.current_pointer_frame < drag_info.grab_frame) {
5428 start = drag_info.current_pointer_frame;
5429 end = drag_info.grab_frame;
5430 } else {
5431 end = drag_info.current_pointer_frame;
5432 start = drag_info.grab_frame;
5435 if (drag_info.current_pointer_y < drag_info.grab_y) {
5436 y1 = drag_info.current_pointer_y;
5437 y2 = drag_info.grab_y;
5438 } else {
5439 y2 = drag_info.current_pointer_y;
5440 y1 = drag_info.grab_y;
5444 if (start != end || y1 != y2) {
5446 double x1 = frame_to_pixel (start);
5447 double x2 = frame_to_pixel (end);
5449 rubberband_rect->property_x1() = x1;
5450 rubberband_rect->property_y1() = y1;
5451 rubberband_rect->property_x2() = x2;
5452 rubberband_rect->property_y2() = y2;
5454 rubberband_rect->show();
5455 rubberband_rect->raise_to_top();
5457 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
5458 drag_info.first_move = false;
5460 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5464 void
5465 Editor::end_rubberband_select (ArdourCanvas::Item* item, GdkEvent* event)
5467 if (!drag_info.first_move) {
5469 drag_rubberband_select (item, event);
5471 double y1,y2;
5472 if (drag_info.current_pointer_y < drag_info.grab_y) {
5473 y1 = drag_info.current_pointer_y;
5474 y2 = drag_info.grab_y;
5475 } else {
5476 y2 = drag_info.current_pointer_y;
5477 y1 = drag_info.grab_y;
5481 Selection::Operation op = Keyboard::selection_type (event->button.state);
5482 bool commit;
5484 begin_reversible_command (_("rubberband selection"));
5486 if (drag_info.grab_frame < drag_info.last_pointer_frame) {
5487 commit = select_all_within (drag_info.grab_frame, drag_info.last_pointer_frame, y1, y2, track_views, op);
5488 } else {
5489 commit = select_all_within (drag_info.last_pointer_frame, drag_info.grab_frame, y1, y2, track_views, op);
5492 if (commit) {
5493 commit_reversible_command ();
5496 } else {
5497 if (!getenv("ARDOUR_SAE")) {
5498 selection->clear_tracks();
5500 selection->clear_regions();
5501 selection->clear_points ();
5502 selection->clear_lines ();
5505 rubberband_rect->hide();
5509 gint
5510 Editor::mouse_rename_region (ArdourCanvas::Item* item, GdkEvent* event)
5512 using namespace Gtkmm2ext;
5514 ArdourPrompter prompter (false);
5516 prompter.set_prompt (_("Name for region:"));
5517 prompter.set_initial_text (clicked_regionview->region()->name());
5518 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
5519 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
5520 prompter.show_all ();
5521 switch (prompter.run ()) {
5522 case Gtk::RESPONSE_ACCEPT:
5523 string str;
5524 prompter.get_result(str);
5525 if (str.length()) {
5526 clicked_regionview->region()->set_name (str);
5528 break;
5530 return true;
5533 void
5534 Editor::start_time_fx (ArdourCanvas::Item* item, GdkEvent* event)
5536 drag_info.item = item;
5537 drag_info.motion_callback = &Editor::time_fx_motion;
5538 drag_info.finished_callback = &Editor::end_time_fx;
5540 start_grab (event);
5542 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5545 void
5546 Editor::time_fx_motion (ArdourCanvas::Item *item, GdkEvent* event)
5548 RegionView* rv = clicked_regionview;
5550 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
5551 snap_to (drag_info.current_pointer_frame);
5554 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) {
5555 return;
5558 if (drag_info.current_pointer_frame > rv->region()->position()) {
5559 rv->get_time_axis_view().show_timestretch (rv->region()->position(), drag_info.current_pointer_frame);
5562 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
5563 drag_info.first_move = false;
5565 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5568 void
5569 Editor::end_time_fx (ArdourCanvas::Item* item, GdkEvent* event)
5571 clicked_regionview->get_time_axis_view().hide_timestretch ();
5573 if (drag_info.first_move) {
5574 return;
5577 if (drag_info.last_pointer_frame < clicked_regionview->region()->position()) {
5578 /* backwards drag of the left edge - not usable */
5579 return;
5582 nframes64_t newlen = drag_info.last_pointer_frame - clicked_regionview->region()->position();
5583 #ifdef USE_RUBBERBAND
5584 float percentage = (float) ((double) newlen / (double) clicked_regionview->region()->length());
5585 #else
5586 float percentage = (float) ((double) newlen - (double) clicked_regionview->region()->length()) / ((double) newlen) * 100.0f;
5587 #endif
5589 begin_reversible_command (_("timestretch"));
5591 // XXX how do timeFX on multiple regions ?
5593 RegionSelection rs;
5594 rs.add (clicked_regionview);
5596 if (time_stretch (rs, percentage) == 0) {
5597 session->commit_reversible_command ();
5601 void
5602 Editor::mouse_brush_insert_region (RegionView* rv, nframes64_t pos)
5604 /* no brushing without a useful snap setting */
5606 // FIXME
5607 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
5608 assert(arv);
5610 switch (snap_mode) {
5611 case SnapMagnetic:
5612 return; /* can't work because it allows region to be placed anywhere */
5613 default:
5614 break; /* OK */
5617 switch (snap_type) {
5618 case SnapToMark:
5619 return;
5621 default:
5622 break;
5625 /* don't brush a copy over the original */
5627 if (pos == rv->region()->position()) {
5628 return;
5631 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*>(&arv->get_time_axis_view());
5633 if (atv == 0 || !atv->is_audio_track()) {
5634 return;
5637 boost::shared_ptr<Playlist> playlist = atv->playlist();
5638 double speed = atv->get_diskstream()->speed();
5640 XMLNode &before = playlist->get_state();
5641 playlist->add_region (boost::dynamic_pointer_cast<AudioRegion> (RegionFactory::create (arv->audio_region())), (nframes64_t) (pos * speed));
5642 XMLNode &after = playlist->get_state();
5643 session->add_command(new MementoCommand<Playlist>(*playlist.get(), &before, &after));
5645 // playlist is frozen, so we have to update manually
5647 playlist->Modified(); /* EMIT SIGNAL */
5650 gint
5651 Editor::track_height_step_timeout ()
5653 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
5654 current_stepping_trackview = 0;
5655 return false;
5657 return true;