2 Copyright (C) 2000-2001 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #include "pbd/error.h"
29 #include "pbd/enumwriter.h"
30 #include "pbd/memento_command.h"
31 #include "pbd/basename.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "gtkmm2ext/bindings.h"
35 #include "gtkmm2ext/utils.h"
36 #include "gtkmm2ext/tearoff.h"
38 #include "ardour_ui.h"
40 #include "canvas-note.h"
42 #include "time_axis_view.h"
43 #include "audio_time_axis.h"
44 #include "audio_region_view.h"
45 #include "midi_region_view.h"
47 #include "streamview.h"
48 #include "region_gain_line.h"
49 #include "automation_time_axis.h"
50 #include "control_point.h"
53 #include "selection.h"
56 #include "rgb_macros.h"
57 #include "control_point_dialog.h"
58 #include "editor_drag.h"
59 #include "automation_region_view.h"
60 #include "edit_note_dialog.h"
61 #include "mouse_cursors.h"
62 #include "editor_cursors.h"
63 #include "verbose_cursor.h"
65 #include "ardour/types.h"
66 #include "ardour/profile.h"
67 #include "ardour/route.h"
68 #include "ardour/audio_track.h"
69 #include "ardour/audio_diskstream.h"
70 #include "ardour/midi_diskstream.h"
71 #include "ardour/playlist.h"
72 #include "ardour/audioplaylist.h"
73 #include "ardour/audioregion.h"
74 #include "ardour/midi_region.h"
75 #include "ardour/dB.h"
76 #include "ardour/utils.h"
77 #include "ardour/region_factory.h"
78 #include "ardour/source_factory.h"
79 #include "ardour/session.h"
80 #include "ardour/operations.h"
87 using namespace ARDOUR
;
90 using namespace Editing
;
91 using Gtkmm2ext::Keyboard
;
94 Editor::mouse_frame (framepos_t
& where
, bool& in_track_canvas
) const
98 Gdk::ModifierType mask
;
99 Glib::RefPtr
<Gdk::Window
> canvas_window
= const_cast<Editor
*>(this)->track_canvas
->get_window();
100 Glib::RefPtr
<const Gdk::Window
> pointer_window
;
102 if (!canvas_window
) {
106 pointer_window
= canvas_window
->get_pointer (x
, y
, mask
);
108 if (pointer_window
== track_canvas
->get_bin_window()) {
111 in_track_canvas
= true;
114 in_track_canvas
= false;
119 event
.type
= GDK_BUTTON_RELEASE
;
123 where
= event_frame (&event
, 0, 0);
128 Editor::event_frame (GdkEvent
const * event
, double* pcx
, double* pcy
) const
142 switch (event
->type
) {
143 case GDK_BUTTON_RELEASE
:
144 case GDK_BUTTON_PRESS
:
145 case GDK_2BUTTON_PRESS
:
146 case GDK_3BUTTON_PRESS
:
147 *pcx
= event
->button
.x
;
148 *pcy
= event
->button
.y
;
149 _trackview_group
->w2i(*pcx
, *pcy
);
151 case GDK_MOTION_NOTIFY
:
152 *pcx
= event
->motion
.x
;
153 *pcy
= event
->motion
.y
;
154 _trackview_group
->w2i(*pcx
, *pcy
);
156 case GDK_ENTER_NOTIFY
:
157 case GDK_LEAVE_NOTIFY
:
158 track_canvas
->w2c(event
->crossing
.x
, event
->crossing
.y
, *pcx
, *pcy
);
161 case GDK_KEY_RELEASE
:
162 // track_canvas->w2c(event->key.x, event->key.y, *pcx, *pcy);
165 warning
<< string_compose (_("Editor::event_frame() used on unhandled event type %1"), event
->type
) << endmsg
;
169 /* note that pixel_to_frame() never returns less than zero, so even if the pixel
170 position is negative (as can be the case with motion events in particular),
171 the frame location is always positive.
174 return pixel_to_frame (*pcx
);
178 Editor::which_grabber_cursor ()
180 Gdk::Cursor
* c
= _cursors
->grabber
;
182 if (_internal_editing
) {
183 switch (mouse_mode
) {
185 c
= _cursors
->midi_pencil
;
189 c
= _cursors
->grabber_note
;
193 c
= _cursors
->midi_resize
;
202 switch (_edit_point
) {
204 c
= _cursors
->grabber_edit_point
;
207 boost::shared_ptr
<Movable
> m
= _movable
.lock();
208 if (m
&& m
->locked()) {
209 c
= _cursors
->speaker
;
219 Editor::set_current_trimmable (boost::shared_ptr
<Trimmable
> t
)
221 boost::shared_ptr
<Trimmable
> st
= _trimmable
.lock();
223 if (!st
|| st
== t
) {
225 set_canvas_cursor ();
230 Editor::set_current_movable (boost::shared_ptr
<Movable
> m
)
232 boost::shared_ptr
<Movable
> sm
= _movable
.lock();
234 if (!sm
|| sm
!= m
) {
236 set_canvas_cursor ();
241 Editor::set_canvas_cursor ()
243 if (_internal_editing
) {
245 switch (mouse_mode
) {
247 current_canvas_cursor
= _cursors
->midi_pencil
;
251 current_canvas_cursor
= which_grabber_cursor();
255 current_canvas_cursor
= _cursors
->midi_resize
;
264 switch (mouse_mode
) {
266 current_canvas_cursor
= _cursors
->selector
;
270 current_canvas_cursor
= which_grabber_cursor();
274 current_canvas_cursor
= _cursors
->cross_hair
;
278 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L
)) {
279 current_canvas_cursor
= _cursors
->zoom_out
;
281 current_canvas_cursor
= _cursors
->zoom_in
;
286 current_canvas_cursor
= _cursors
->time_fx
; // just use playhead
290 current_canvas_cursor
= _cursors
->speaker
;
295 switch (_join_object_range_state
) {
296 case JOIN_OBJECT_RANGE_NONE
:
298 case JOIN_OBJECT_RANGE_OBJECT
:
299 current_canvas_cursor
= which_grabber_cursor ();
301 case JOIN_OBJECT_RANGE_RANGE
:
302 current_canvas_cursor
= _cursors
->selector
;
306 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
307 if (join_object_range_button
.get_active() && last_item_entered
) {
308 if (last_item_entered
->property_parent() && (*last_item_entered
->property_parent()).get_data (X_("timeselection"))) {
309 pair
<TimeAxisView
*, int> tvp
= trackview_by_y_position (_last_motion_y
+ vertical_adjustment
.get_value() - canvas_timebars_vsize
);
310 if (dynamic_cast<AutomationTimeAxisView
*> (tvp
.first
)) {
311 current_canvas_cursor
= _cursors
->up_down
;
316 set_canvas_cursor (current_canvas_cursor
, true);
320 Editor::set_mouse_mode (MouseMode m
, bool force
)
322 if (_drags
->active ()) {
326 if (!force
&& m
== mouse_mode
) {
330 Glib::RefPtr
<Action
> act
;
334 act
= ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
338 act
= ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
342 act
= ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-gain"));
346 act
= ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-zoom"));
350 act
= ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
354 act
= ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
360 Glib::RefPtr
<ToggleAction
> tact
= Glib::RefPtr
<ToggleAction
>::cast_dynamic (act
);
363 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
364 tact
->set_active (false);
365 tact
->set_active (true);
367 MouseModeChanged (); /* EMIT SIGNAL */
371 Editor::mouse_mode_toggled (MouseMode m
)
377 if (!internal_editing()) {
378 if (mouse_mode
!= MouseRange
&& _join_object_range_state
== JOIN_OBJECT_RANGE_NONE
) {
380 /* in all modes except range and joined object/range, hide the range selection,
381 show the object (region) selection.
384 for (TrackViewList::iterator i
= track_views
.begin(); i
!= track_views
.end(); ++i
) {
385 (*i
)->hide_selection ();
391 in range or object/range mode, show the range selection.
394 for (TrackSelection::iterator i
= selection
->tracks
.begin(); i
!= selection
->tracks
.end(); ++i
) {
395 (*i
)->show_selection (selection
->time
);
400 set_canvas_cursor ();
402 MouseModeChanged (); /* EMIT SIGNAL */
406 Editor::step_mouse_mode (bool next
)
408 switch (current_mouse_mode()) {
411 if (Profile
->get_sae()) {
412 set_mouse_mode (MouseZoom
);
414 set_mouse_mode (MouseRange
);
417 set_mouse_mode (MouseTimeFX
);
422 if (next
) set_mouse_mode (MouseZoom
);
423 else set_mouse_mode (MouseObject
);
428 if (Profile
->get_sae()) {
429 set_mouse_mode (MouseTimeFX
);
431 set_mouse_mode (MouseGain
);
434 if (Profile
->get_sae()) {
435 set_mouse_mode (MouseObject
);
437 set_mouse_mode (MouseRange
);
443 if (next
) set_mouse_mode (MouseTimeFX
);
444 else set_mouse_mode (MouseZoom
);
449 set_mouse_mode (MouseAudition
);
451 if (Profile
->get_sae()) {
452 set_mouse_mode (MouseZoom
);
454 set_mouse_mode (MouseGain
);
460 if (next
) set_mouse_mode (MouseObject
);
461 else set_mouse_mode (MouseTimeFX
);
467 Editor::button_selection (ArdourCanvas::Item
* /*item*/, GdkEvent
* event
, ItemType item_type
)
469 /* in object/audition/timefx/gain-automation mode,
470 any button press sets the selection if the object
471 can be selected. this is a bit of hack, because
472 we want to avoid this if the mouse operation is a
475 note: not dbl-click or triple-click
477 Also note that there is no region selection in internal edit mode, otherwise
478 for operations operating on the selection (e.g. cut) it is not obvious whether
479 to cut notes or regions.
482 if (((mouse_mode
!= MouseObject
) &&
483 (_join_object_range_state
!= JOIN_OBJECT_RANGE_OBJECT
) &&
484 (mouse_mode
!= MouseAudition
|| item_type
!= RegionItem
) &&
485 (mouse_mode
!= MouseTimeFX
|| item_type
!= RegionItem
) &&
486 (mouse_mode
!= MouseGain
) &&
487 (mouse_mode
!= MouseRange
)) ||
488 ((event
->type
!= GDK_BUTTON_PRESS
&& event
->type
!= GDK_BUTTON_RELEASE
) || event
->button
.button
> 3) ||
489 internal_editing()) {
494 if (event
->type
== GDK_BUTTON_PRESS
|| event
->type
== GDK_BUTTON_RELEASE
) {
496 if ((event
->button
.state
& Keyboard::RelevantModifierKeyMask
) && event
->button
.button
!= 1) {
498 /* almost no selection action on modified button-2 or button-3 events */
500 if (item_type
!= RegionItem
&& event
->button
.button
!= 2) {
506 Selection::Operation op
= ArdourKeyboard::selection_type (event
->button
.state
);
507 bool press
= (event
->type
== GDK_BUTTON_PRESS
);
511 if (mouse_mode
!= MouseRange
|| _join_object_range_state
== JOIN_OBJECT_RANGE_OBJECT
) {
512 set_selected_regionview_from_click (press
, op
, true);
513 } else if (event
->type
== GDK_BUTTON_PRESS
) {
514 selection
->clear_tracks ();
515 set_selected_track_as_side_effect (op
, true);
517 if (_join_object_range_state
== JOIN_OBJECT_RANGE_OBJECT
&& !selection
->regions
.empty()) {
518 clicked_selection
= select_range_around_region (selection
->regions
.front());
522 case RegionViewNameHighlight
:
524 case LeftFrameHandle
:
525 case RightFrameHandle
:
526 if (mouse_mode
!= MouseRange
|| _join_object_range_state
== JOIN_OBJECT_RANGE_OBJECT
) {
527 set_selected_regionview_from_click (press
, op
, true);
528 } else if (event
->type
== GDK_BUTTON_PRESS
) {
529 set_selected_track_as_side_effect (op
);
534 case FadeInHandleItem
:
536 case FadeOutHandleItem
:
538 if (mouse_mode
!= MouseRange
|| _join_object_range_state
== JOIN_OBJECT_RANGE_OBJECT
) {
539 set_selected_regionview_from_click (press
, op
, true);
540 } else if (event
->type
== GDK_BUTTON_PRESS
) {
541 set_selected_track_as_side_effect (op
);
545 case ControlPointItem
:
546 set_selected_track_as_side_effect (op
, true);
547 if (mouse_mode
!= MouseRange
|| _join_object_range_state
== JOIN_OBJECT_RANGE_OBJECT
) {
548 set_selected_control_point_from_click (op
, false);
553 /* for context click, select track */
554 if (event
->button
.button
== 3) {
555 selection
->clear_tracks ();
556 set_selected_track_as_side_effect (op
, true);
560 case AutomationTrackItem
:
561 set_selected_track_as_side_effect (op
, true);
570 Editor::button_press_handler_1 (ArdourCanvas::Item
* item
, GdkEvent
* event
, ItemType item_type
)
572 /* single mouse clicks on any of these item types operate
573 independent of mouse mode, mostly because they are
574 not on the main track canvas or because we want
579 case PlayheadCursorItem
:
580 _drags
->set (new CursorDrag (this, item
, true), event
);
584 if (Keyboard::modifier_state_equals (event
->button
.state
, Keyboard::ModifierMask(Keyboard::PrimaryModifier
|Keyboard::TertiaryModifier
))) {
585 hide_marker (item
, event
);
587 _drags
->set (new MarkerDrag (this, item
), event
);
591 case TempoMarkerItem
:
593 new TempoMarkerDrag (
596 Keyboard::modifier_state_contains (event
->button
.state
, Keyboard::CopyModifier
)
602 case MeterMarkerItem
:
604 new MeterMarkerDrag (
607 Keyboard::modifier_state_contains (event
->button
.state
, Keyboard::CopyModifier
)
616 if (!Keyboard::modifier_state_equals (event
->button
.state
, Keyboard::PrimaryModifier
)) {
617 _drags
->set (new CursorDrag (this, &playhead_cursor
->canvas_item
, false), event
);
623 case RangeMarkerBarItem
:
624 if (!Keyboard::modifier_state_equals (event
->button
.state
, Keyboard::PrimaryModifier
)) {
625 _drags
->set (new CursorDrag (this, &playhead_cursor
->canvas_item
, false), event
);
627 _drags
->set (new RangeMarkerBarDrag (this, item
, RangeMarkerBarDrag::CreateRangeMarker
), event
);
632 case CdMarkerBarItem
:
633 if (!Keyboard::modifier_state_equals (event
->button
.state
, Keyboard::PrimaryModifier
)) {
634 _drags
->set (new CursorDrag (this, &playhead_cursor
->canvas_item
, false), event
);
636 _drags
->set (new RangeMarkerBarDrag (this, item
, RangeMarkerBarDrag::CreateCDMarker
), event
);
641 case TransportMarkerBarItem
:
642 if (!Keyboard::modifier_state_equals (event
->button
.state
, Keyboard::PrimaryModifier
)) {
643 _drags
->set (new CursorDrag (this, &playhead_cursor
->canvas_item
, false), event
);
645 _drags
->set (new RangeMarkerBarDrag (this, item
, RangeMarkerBarDrag::CreateTransportMarker
), event
);
654 if (_join_object_range_state
== JOIN_OBJECT_RANGE_OBJECT
) {
655 /* special case: allow trim of range selections in joined object mode;
656 in theory eff should equal MouseRange in this case, but it doesn't
657 because entering the range selection canvas item results in entered_regionview
658 being set to 0, so update_join_object_range_location acts as if we aren't
661 if (item_type
== StartSelectionTrimItem
) {
662 _drags
->set (new SelectionDrag (this, item
, SelectionDrag::SelectionStartTrim
), event
);
663 } else if (item_type
== EndSelectionTrimItem
) {
664 _drags
->set (new SelectionDrag (this, item
, SelectionDrag::SelectionEndTrim
), event
);
668 Editing::MouseMode eff
= effective_mouse_mode ();
670 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
671 if (item_type
== FadeInHandleItem
|| item_type
== FadeOutHandleItem
) {
678 case StartSelectionTrimItem
:
679 _drags
->set (new SelectionDrag (this, item
, SelectionDrag::SelectionStartTrim
), event
);
682 case EndSelectionTrimItem
:
683 _drags
->set (new SelectionDrag (this, item
, SelectionDrag::SelectionEndTrim
), event
);
687 if (Keyboard::modifier_state_contains
688 (event
->button
.state
, Keyboard::ModifierMask(Keyboard::PrimaryModifier
))) {
689 // contains and not equals because I can't use alt as a modifier alone.
690 start_selection_grab (item
, event
);
691 } else if (Keyboard::modifier_state_equals (event
->button
.state
, Keyboard::SecondaryModifier
)) {
692 /* grab selection for moving */
693 _drags
->set (new SelectionDrag (this, item
, SelectionDrag::SelectionMove
), event
);
695 double const y
= event
->button
.y
+ vertical_adjustment
.get_value() - canvas_timebars_vsize
;
696 pair
<TimeAxisView
*, int> tvp
= trackview_by_y_position (y
);
698 AutomationTimeAxisView
* atv
= dynamic_cast<AutomationTimeAxisView
*> (tvp
.first
);
699 if (join_object_range_button
.get_active() && atv
) {
700 /* smart "join" mode: drag automation */
701 _drags
->set (new AutomationRangeDrag (this, atv
->base_item(), selection
->time
), event
, _cursors
->up_down
);
703 /* this was debated, but decided the more common action was to
704 make a new selection */
705 _drags
->set (new SelectionDrag (this, item
, SelectionDrag::CreateSelection
), event
);
712 if (internal_editing()) {
713 /* trim notes if we're in internal edit mode and near the ends of the note */
714 ArdourCanvas::CanvasNote
* cn
= dynamic_cast<ArdourCanvas::CanvasNote
*> (item
);
715 if (cn
&& cn
->big_enough_to_trim() && cn
->mouse_near_ends()) {
716 _drags
->set (new NoteResizeDrag (this, item
), event
, current_canvas_cursor
);
718 _drags
->set (new NoteDrag (this, item
), event
);
724 if (internal_editing()) {
725 if (dynamic_cast<MidiTimeAxisView
*> (clicked_axisview
)) {
726 _drags
->set (new RegionCreateDrag (this, item
, clicked_axisview
), event
);
730 _drags
->set (new SelectionDrag (this, item
, SelectionDrag::CreateSelection
), event
);
735 case RegionViewNameHighlight
:
736 if (!clicked_regionview
->region()->locked()) {
737 RegionSelection s
= get_equivalent_regions (selection
->regions
, Properties::edit
.property_id
);
738 _drags
->set (new TrimDrag (this, item
, clicked_regionview
, s
.by_layer()), event
);
743 case LeftFrameHandle
:
744 case RightFrameHandle
:
745 if (!internal_editing() && !clicked_regionview
->region()->locked()) {
746 RegionSelection s
= get_equivalent_regions (selection
->regions
, Properties::edit
.property_id
);
747 _drags
->set (new TrimDrag (this, item
, clicked_regionview
, s
.by_layer()), event
);
753 if (!internal_editing()) {
754 _drags
->set (new SelectionDrag (this, item
, SelectionDrag::CreateSelection
), event
);
763 if (internal_editing()) {
764 ArdourCanvas::CanvasNoteEvent
* cn
= dynamic_cast<ArdourCanvas::CanvasNoteEvent
*> (item
);
765 if (cn
->mouse_near_ends()) {
766 _drags
->set (new NoteResizeDrag (this, item
), event
, current_canvas_cursor
);
768 _drags
->set (new NoteDrag (this, item
), event
);
778 if (Keyboard::modifier_state_contains (event
->button
.state
, Keyboard::ModifierMask(Keyboard::PrimaryModifier
|Keyboard::SecondaryModifier
)) &&
779 event
->type
== GDK_BUTTON_PRESS
) {
781 _drags
->set (new RubberbandSelectDrag (this, item
), event
);
783 } else if (event
->type
== GDK_BUTTON_PRESS
) {
786 case FadeInHandleItem
:
788 RegionSelection s
= get_equivalent_regions (selection
->regions
, Properties::edit
.property_id
);
789 _drags
->set (new FadeInDrag (this, item
, reinterpret_cast<RegionView
*> (item
->get_data("regionview")), s
), event
, _cursors
->fade_in
);
793 case FadeOutHandleItem
:
795 RegionSelection s
= get_equivalent_regions (selection
->regions
, Properties::edit
.property_id
);
796 _drags
->set (new FadeOutDrag (this, item
, reinterpret_cast<RegionView
*> (item
->get_data("regionview")), s
), event
, _cursors
->fade_out
);
800 case FeatureLineItem
:
802 if (Keyboard::modifier_state_contains (event
->button
.state
, Keyboard::TertiaryModifier
)) {
803 remove_transient(item
);
807 _drags
->set (new FeatureLineDrag (this, item
), event
);
813 if (dynamic_cast<AutomationRegionView
*> (clicked_regionview
)) {
814 /* click on an automation region view; do nothing here and let the ARV's signal handler
820 if (internal_editing ()) {
821 /* no region drags in internal edit mode */
825 /* click on a normal region view */
826 if (Keyboard::modifier_state_contains (event
->button
.state
, Keyboard::CopyModifier
)) {
827 add_region_copy_drag (item
, event
, clicked_regionview
);
828 } else if (Keyboard::the_keyboard().key_is_down (GDK_b
)) {
829 add_region_brush_drag (item
, event
, clicked_regionview
);
831 add_region_drag (item
, event
, clicked_regionview
);
834 if (_join_object_range_state
== JOIN_OBJECT_RANGE_OBJECT
&& !selection
->regions
.empty()) {
835 _drags
->add (new SelectionDrag (this, clicked_axisview
->get_selection_rect (clicked_selection
)->rect
, SelectionDrag::SelectionMove
));
838 _drags
->start_grab (event
);
841 case RegionViewNameHighlight
:
842 case LeftFrameHandle
:
843 case RightFrameHandle
:
844 if (!clicked_regionview
->region()->locked()) {
845 RegionSelection s
= get_equivalent_regions (selection
->regions
, Properties::edit
.property_id
);
846 _drags
->set (new TrimDrag (this, item
, clicked_regionview
, s
.by_layer()), event
);
853 /* rename happens on edit clicks */
854 RegionSelection s
= get_equivalent_regions (selection
->regions
, Properties::edit
.property_id
);
855 _drags
->set (new TrimDrag (this, clicked_regionview
->get_name_highlight(), clicked_regionview
, s
.by_layer()), event
);
860 case ControlPointItem
:
861 _drags
->set (new ControlPointDrag (this, item
), event
);
865 case AutomationLineItem
:
866 _drags
->set (new LineDrag (this, item
), event
);
871 if (internal_editing()) {
872 if (dynamic_cast<MidiTimeAxisView
*> (clicked_axisview
)) {
873 _drags
->set (new RegionCreateDrag (this, item
, clicked_axisview
), event
);
877 _drags
->set (new RubberbandSelectDrag (this, item
), event
);
881 case AutomationTrackItem
:
882 /* rubberband drag to select automation points */
883 _drags
->set (new RubberbandSelectDrag (this, item
), event
);
888 if (join_object_range_button
.get_active()) {
889 /* we're in "smart" joined mode, and we've clicked on a Selection */
890 double const y
= event
->button
.y
+ vertical_adjustment
.get_value() - canvas_timebars_vsize
;
891 pair
<TimeAxisView
*, int> tvp
= trackview_by_y_position (y
);
893 /* if we're over an automation track, start a drag of its data */
894 AutomationTimeAxisView
* atv
= dynamic_cast<AutomationTimeAxisView
*> (tvp
.first
);
896 _drags
->set (new AutomationRangeDrag (this, atv
->base_item(), selection
->time
), event
, _cursors
->up_down
);
899 /* if we're over a track and a region, and in the `object' part of a region,
900 put a selection around the region and drag both
902 RouteTimeAxisView
* rtv
= dynamic_cast<RouteTimeAxisView
*> (tvp
.first
);
903 if (rtv
&& _join_object_range_state
== JOIN_OBJECT_RANGE_OBJECT
) {
904 boost::shared_ptr
<Track
> t
= boost::dynamic_pointer_cast
<Track
> (rtv
->route ());
906 boost::shared_ptr
<Playlist
> pl
= t
->playlist ();
909 boost::shared_ptr
<Region
> r
= pl
->top_region_at (event_frame (event
));
911 RegionView
* rv
= rtv
->view()->find_view (r
);
912 clicked_selection
= select_range_around_region (rv
);
913 _drags
->add (new SelectionDrag (this, item
, SelectionDrag::SelectionMove
));
914 list
<RegionView
*> rvs
;
916 _drags
->add (new RegionMoveDrag (this, item
, rv
, rvs
, false, false));
917 _drags
->start_grab (event
);
928 case ImageFrameHandleStartItem
:
929 imageframe_start_handle_op(item
, event
) ;
932 case ImageFrameHandleEndItem
:
933 imageframe_end_handle_op(item
, event
) ;
936 case MarkerViewHandleStartItem
:
937 markerview_item_start_handle_op(item
, event
) ;
940 case MarkerViewHandleEndItem
:
941 markerview_item_end_handle_op(item
, event
) ;
945 start_markerview_grab(item
, event
) ;
948 start_imageframe_grab(item
, event
) ;
966 /* start a grab so that if we finish after moving
967 we can tell what happened.
969 _drags
->set (new RegionGainDrag (this, item
), event
, current_canvas_cursor
);
973 _drags
->set (new LineDrag (this, item
), event
);
976 case ControlPointItem
:
977 _drags
->set (new ControlPointDrag (this, item
), event
);
988 case ControlPointItem
:
989 _drags
->set (new ControlPointDrag (this, item
), event
);
992 case AutomationLineItem
:
993 _drags
->set (new LineDrag (this, item
), event
);
997 // XXX need automation mode to identify which
999 // start_line_grab_from_regionview (item, event);
1009 if (event
->type
== GDK_BUTTON_PRESS
) {
1010 _drags
->set (new MouseZoomDrag (this, item
), event
);
1017 if (internal_editing() && item_type
== NoteItem
) {
1018 /* drag notes if we're in internal edit mode */
1019 _drags
->set (new NoteResizeDrag (this, item
), event
, current_canvas_cursor
);
1021 } else if ((!internal_editing() || dynamic_cast<AudioRegionView
*> (clicked_regionview
)) && clicked_regionview
) {
1022 /* do time-FX if we're not in internal edit mode, or we are but we clicked on an audio region */
1023 _drags
->set (new TimeFXDrag (this, item
, clicked_regionview
, selection
->regions
.by_layer()), event
);
1029 _drags
->set (new ScrubDrag (this, item
), event
);
1030 scrub_reversals
= 0;
1031 scrub_reverse_distance
= 0;
1032 last_scrub_x
= event
->button
.x
;
1033 scrubbing_direction
= 0;
1034 set_canvas_cursor (_cursors
->transparent
);
1046 Editor::button_press_handler_2 (ArdourCanvas::Item
* item
, GdkEvent
* event
, ItemType item_type
)
1048 Editing::MouseMode
const eff
= effective_mouse_mode ();
1051 switch (item_type
) {
1053 if (internal_editing ()) {
1054 /* no region drags in internal edit mode */
1058 if (Keyboard::modifier_state_contains (event
->button
.state
, Keyboard::CopyModifier
)) {
1059 add_region_copy_drag (item
, event
, clicked_regionview
);
1061 add_region_drag (item
, event
, clicked_regionview
);
1063 _drags
->start_grab (event
);
1066 case ControlPointItem
:
1067 _drags
->set (new ControlPointDrag (this, item
), event
);
1075 switch (item_type
) {
1076 case RegionViewNameHighlight
:
1077 _drags
->set (new TrimDrag (this, item
, clicked_regionview
, selection
->regions
.by_layer()), event
);
1081 case LeftFrameHandle
:
1082 case RightFrameHandle
:
1083 if (!internal_editing ()) {
1084 _drags
->set (new TrimDrag (this, item
, clicked_regionview
, selection
->regions
.by_layer()), event
);
1089 case RegionViewName
:
1090 _drags
->set (new TrimDrag (this, clicked_regionview
->get_name_highlight(), clicked_regionview
, selection
->regions
.by_layer()), event
);
1101 /* relax till release */
1107 if (Keyboard::modifier_state_equals (event
->button
.state
, Keyboard::PrimaryModifier
)) {
1108 temporal_zoom_to_frame (false, event_frame (event
));
1110 temporal_zoom_to_frame (true, event_frame(event
));
1123 Editor::button_press_handler (ArdourCanvas::Item
* item
, GdkEvent
* event
, ItemType item_type
)
1125 if (event
->type
!= GDK_BUTTON_PRESS
) {
1129 Glib::RefPtr
<Gdk::Window
> canvas_window
= const_cast<Editor
*>(this)->track_canvas
->get_window();
1131 if (canvas_window
) {
1132 Glib::RefPtr
<const Gdk::Window
> pointer_window
;
1135 Gdk::ModifierType mask
;
1137 pointer_window
= canvas_window
->get_pointer (x
, y
, mask
);
1139 if (pointer_window
== track_canvas
->get_bin_window()) {
1140 track_canvas
->window_to_world (x
, y
, wx
, wy
);
1144 pre_press_cursor
= current_canvas_cursor
;
1146 track_canvas
->grab_focus();
1148 if (_session
&& _session
->actively_recording()) {
1152 button_selection (item
, event
, item_type
);
1154 if (!_drags
->active () &&
1155 (Keyboard::is_delete_event (&event
->button
) ||
1156 Keyboard::is_context_menu_event (&event
->button
) ||
1157 Keyboard::is_edit_event (&event
->button
))) {
1159 /* handled by button release */
1163 switch (event
->button
.button
) {
1165 return button_press_handler_1 (item
, event
, item_type
);
1169 return button_press_handler_2 (item
, event
, item_type
);
1176 return button_press_dispatch (&event
->button
);
1185 Editor::button_press_dispatch (GdkEventButton
* ev
)
1187 /* this function is intended only for buttons 4 and above.
1190 Gtkmm2ext::MouseButton
b (ev
->state
, ev
->button
);
1191 return button_bindings
->activate (b
, Gtkmm2ext::Bindings::Press
);
1195 Editor::button_release_dispatch (GdkEventButton
* ev
)
1197 /* this function is intended only for buttons 4 and above.
1200 Gtkmm2ext::MouseButton
b (ev
->state
, ev
->button
);
1201 return button_bindings
->activate (b
, Gtkmm2ext::Bindings::Release
);
1205 Editor::button_release_handler (ArdourCanvas::Item
* item
, GdkEvent
* event
, ItemType item_type
)
1207 framepos_t where
= event_frame (event
, 0, 0);
1208 AutomationTimeAxisView
* atv
= 0;
1210 if (pre_press_cursor
) {
1211 set_canvas_cursor (pre_press_cursor
);
1212 pre_press_cursor
= 0;
1215 /* no action if we're recording */
1217 if (_session
&& _session
->actively_recording()) {
1221 /* see if we're finishing a drag */
1223 bool were_dragging
= false;
1224 if (_drags
->active ()) {
1225 bool const r
= _drags
->end_grab (event
);
1227 /* grab dragged, so do nothing else */
1231 were_dragging
= true;
1234 update_region_layering_order_editor ();
1236 /* edit events get handled here */
1238 if (!_drags
->active () && Keyboard::is_edit_event (&event
->button
)) {
1239 switch (item_type
) {
1241 show_region_properties ();
1244 case TempoMarkerItem
:
1245 edit_tempo_marker (item
);
1248 case MeterMarkerItem
:
1249 edit_meter_marker (item
);
1252 case RegionViewName
:
1253 if (clicked_regionview
->name_active()) {
1254 return mouse_rename_region (item
, event
);
1258 case ControlPointItem
:
1259 edit_control_point (item
);
1272 /* context menu events get handled here */
1274 if (Keyboard::is_context_menu_event (&event
->button
)) {
1276 if (!_drags
->active ()) {
1278 /* no matter which button pops up the context menu, tell the menu
1279 widget to use button 1 to drive menu selection.
1282 switch (item_type
) {
1284 case FadeInHandleItem
:
1286 case FadeOutHandleItem
:
1287 popup_fade_context_menu (1, event
->button
.time
, item
, item_type
);
1291 popup_track_context_menu (1, event
->button
.time
, item_type
, false);
1295 case RegionViewNameHighlight
:
1296 case LeftFrameHandle
:
1297 case RightFrameHandle
:
1298 case RegionViewName
:
1299 popup_track_context_menu (1, event
->button
.time
, item_type
, false);
1303 popup_track_context_menu (1, event
->button
.time
, item_type
, true);
1306 case AutomationTrackItem
:
1307 popup_track_context_menu (1, event
->button
.time
, item_type
, false);
1311 case RangeMarkerBarItem
:
1312 case TransportMarkerBarItem
:
1313 case CdMarkerBarItem
:
1316 popup_ruler_menu (where
, item_type
);
1320 marker_context_menu (&event
->button
, item
);
1323 case TempoMarkerItem
:
1324 tempo_or_meter_marker_context_menu (&event
->button
, item
);
1327 case MeterMarkerItem
:
1328 tempo_or_meter_marker_context_menu (&event
->button
, item
);
1331 case CrossfadeViewItem
:
1332 popup_track_context_menu (1, event
->button
.time
, item_type
, false);
1336 case ImageFrameItem
:
1337 popup_imageframe_edit_menu(1, event
->button
.time
, item
, true) ;
1339 case ImageFrameTimeAxisItem
:
1340 popup_imageframe_edit_menu(1, event
->button
.time
, item
, false) ;
1342 case MarkerViewItem
:
1343 popup_marker_time_axis_edit_menu(1, event
->button
.time
, item
, true) ;
1345 case MarkerTimeAxisItem
:
1346 popup_marker_time_axis_edit_menu(1, event
->button
.time
, item
, false) ;
1358 /* delete events get handled here */
1360 Editing::MouseMode
const eff
= effective_mouse_mode ();
1362 if (!_drags
->active () && Keyboard::is_delete_event (&event
->button
)) {
1364 switch (item_type
) {
1365 case TempoMarkerItem
:
1366 remove_tempo_marker (item
);
1369 case MeterMarkerItem
:
1370 remove_meter_marker (item
);
1374 remove_marker (*item
, event
);
1378 if (eff
== MouseObject
) {
1379 remove_clicked_region ();
1383 case ControlPointItem
:
1384 if (eff
== MouseGain
) {
1385 remove_gain_control_point (item
, event
);
1387 remove_control_point (item
, event
);
1392 remove_midi_note (item
, event
);
1401 switch (event
->button
.button
) {
1404 switch (item_type
) {
1405 /* see comments in button_press_handler */
1406 case PlayheadCursorItem
:
1409 case AutomationLineItem
:
1410 case StartSelectionTrimItem
:
1411 case EndSelectionTrimItem
:
1415 if (!_dragging_playhead
) {
1416 snap_to_with_modifier (where
, event
, 0, true);
1417 mouse_add_new_marker (where
);
1421 case CdMarkerBarItem
:
1422 if (!_dragging_playhead
) {
1423 // if we get here then a dragged range wasn't done
1424 snap_to_with_modifier (where
, event
, 0, true);
1425 mouse_add_new_marker (where
, true);
1430 if (!_dragging_playhead
) {
1431 snap_to_with_modifier (where
, event
);
1432 mouse_add_new_tempo_event (where
);
1437 if (!_dragging_playhead
) {
1438 mouse_add_new_meter_event (pixel_to_frame (event
->button
.x
));
1449 switch (item_type
) {
1450 case AutomationTrackItem
:
1451 atv
= dynamic_cast<AutomationTimeAxisView
*>(clicked_axisview
);
1453 atv
->add_automation_event (item
, event
, where
, event
->button
.y
);
1464 switch (item_type
) {
1467 /* check that we didn't drag before releasing, since
1468 its really annoying to create new control
1469 points when doing this.
1471 AudioRegionView
* arv
= dynamic_cast<AudioRegionView
*> (clicked_regionview
);
1472 if (were_dragging
&& arv
) {
1473 arv
->add_gain_point_event (item
, event
);
1479 case AutomationTrackItem
:
1480 dynamic_cast<AutomationTimeAxisView
*>(clicked_axisview
)->
1481 add_automation_event (item
, event
, where
, event
->button
.y
);
1490 set_canvas_cursor (current_canvas_cursor
);
1491 if (scrubbing_direction
== 0) {
1492 /* no drag, just a click */
1493 switch (item_type
) {
1495 play_selected_region ();
1501 /* make sure we stop */
1502 _session
->request_transport_speed (0.0);
1511 /* do any (de)selection operations that should occur on button release */
1512 button_selection (item
, event
, item_type
);
1521 switch (item_type
) {
1523 if (Keyboard::modifier_state_equals (event
->button
.state
, Keyboard::TertiaryModifier
)) {
1525 } else if (Keyboard::modifier_state_equals (event
->button
.state
, Keyboard::ModifierMask (Keyboard::TertiaryModifier
|Keyboard::SecondaryModifier
))) {
1528 // Button2 click is unused
1541 // x_style_paste (where, 1.0);
1562 Editor::enter_handler (ArdourCanvas::Item
* item
, GdkEvent
* event
, ItemType item_type
)
1569 last_item_entered
= item
;
1571 switch (item_type
) {
1572 case ControlPointItem
:
1573 if (mouse_mode
== MouseGain
|| mouse_mode
== MouseObject
) {
1574 cp
= static_cast<ControlPoint
*>(item
->get_data ("control_point"));
1575 cp
->set_visible (true);
1579 at_y
= cp
->get_y ();
1580 cp
->i2w (at_x
, at_y
);
1584 fraction
= 1.0 - (cp
->get_y() / cp
->line().height());
1586 if (is_drawable() && !_drags
->active ()) {
1587 set_canvas_cursor (_cursors
->fader
);
1590 _verbose_cursor
->set (cp
->line().get_verbose_cursor_string (fraction
), at_x
, at_y
);
1591 _verbose_cursor
->show ();
1596 if (mouse_mode
== MouseGain
) {
1597 ArdourCanvas::Line
*line
= dynamic_cast<ArdourCanvas::Line
*> (item
);
1599 line
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine
.get();
1600 if (is_drawable()) {
1601 set_canvas_cursor (_cursors
->fader
);
1606 case AutomationLineItem
:
1607 if (mouse_mode
== MouseGain
|| mouse_mode
== MouseObject
) {
1609 ArdourCanvas::Line
*line
= dynamic_cast<ArdourCanvas::Line
*> (item
);
1611 line
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine
.get();
1613 if (is_drawable()) {
1614 set_canvas_cursor (_cursors
->fader
);
1619 case RegionViewNameHighlight
:
1620 if (is_drawable() && mouse_mode
== MouseObject
&& entered_regionview
) {
1621 set_canvas_cursor_for_region_view (event
->crossing
.x
, entered_regionview
);
1622 _over_region_trim_target
= true;
1626 case LeftFrameHandle
:
1627 case RightFrameHandle
:
1628 if (is_drawable() && mouse_mode
== MouseObject
&& !internal_editing() && entered_regionview
) {
1629 set_canvas_cursor_for_region_view (event
->crossing
.x
, entered_regionview
);
1633 case StartSelectionTrimItem
:
1634 case EndSelectionTrimItem
:
1637 case ImageFrameHandleStartItem
:
1638 case ImageFrameHandleEndItem
:
1639 case MarkerViewHandleStartItem
:
1640 case MarkerViewHandleEndItem
:
1643 if (is_drawable()) {
1644 set_canvas_cursor (_cursors
->trimmer
);
1648 case PlayheadCursorItem
:
1649 if (is_drawable()) {
1650 switch (_edit_point
) {
1652 set_canvas_cursor (_cursors
->grabber_edit_point
);
1655 set_canvas_cursor (_cursors
->grabber
);
1661 case RegionViewName
:
1663 /* when the name is not an active item, the entire name highlight is for trimming */
1665 if (!reinterpret_cast<RegionView
*> (item
->get_data ("regionview"))->name_active()) {
1666 if (mouse_mode
== MouseObject
&& is_drawable()) {
1667 set_canvas_cursor_for_region_view (event
->crossing
.x
, entered_regionview
);
1668 _over_region_trim_target
= true;
1674 case AutomationTrackItem
:
1675 if (is_drawable()) {
1676 Gdk::Cursor
*cursor
;
1677 switch (mouse_mode
) {
1679 cursor
= _cursors
->selector
;
1682 cursor
= _cursors
->zoom_in
;
1685 cursor
= _cursors
->cross_hair
;
1689 set_canvas_cursor (cursor
);
1691 AutomationTimeAxisView
* atv
;
1692 if ((atv
= static_cast<AutomationTimeAxisView
*>(item
->get_data ("trackview"))) != 0) {
1693 clear_entered_track
= false;
1694 set_entered_track (atv
);
1700 case RangeMarkerBarItem
:
1701 case TransportMarkerBarItem
:
1702 case CdMarkerBarItem
:
1705 if (is_drawable()) {
1706 set_canvas_cursor (_cursors
->timebar
);
1711 if ((marker
= static_cast<Marker
*> (item
->get_data ("marker"))) == 0) {
1714 entered_marker
= marker
;
1715 marker
->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker
.get());
1717 case MeterMarkerItem
:
1718 case TempoMarkerItem
:
1719 if (is_drawable()) {
1720 set_canvas_cursor (_cursors
->timebar
);
1724 case FadeInHandleItem
:
1725 if (mouse_mode
== MouseObject
&& !internal_editing()) {
1726 ArdourCanvas::SimpleRect
*rect
= dynamic_cast<ArdourCanvas::SimpleRect
*> (item
);
1728 rect
->property_fill_color_rgba() = 0xBBBBBBAA;
1730 set_canvas_cursor (_cursors
->fade_in
);
1734 case FadeOutHandleItem
:
1735 if (mouse_mode
== MouseObject
&& !internal_editing()) {
1736 ArdourCanvas::SimpleRect
*rect
= dynamic_cast<ArdourCanvas::SimpleRect
*> (item
);
1738 rect
->property_fill_color_rgba() = 0xBBBBBBAA;
1740 set_canvas_cursor (_cursors
->fade_out
);
1743 case FeatureLineItem
:
1745 ArdourCanvas::Line
*line
= dynamic_cast<ArdourCanvas::Line
*> (item
);
1746 line
->property_fill_color_rgba() = 0xFF0000FF;
1750 if (join_object_range_button
.get_active()) {
1751 set_canvas_cursor ();
1759 /* second pass to handle entered track status in a comprehensible way.
1762 switch (item_type
) {
1764 case AutomationLineItem
:
1765 case ControlPointItem
:
1766 /* these do not affect the current entered track state */
1767 clear_entered_track
= false;
1770 case AutomationTrackItem
:
1771 /* handled above already */
1775 set_entered_track (0);
1783 Editor::leave_handler (ArdourCanvas::Item
* item
, GdkEvent
* event
, ItemType item_type
)
1793 switch (item_type
) {
1794 case ControlPointItem
:
1795 cp
= reinterpret_cast<ControlPoint
*>(item
->get_data ("control_point"));
1796 if (cp
->line().the_list()->interpolation() != AutomationList::Discrete
) {
1797 if (cp
->line().npoints() > 1 && !cp
->get_selected()) {
1798 cp
->set_visible (false);
1802 if (is_drawable()) {
1803 set_canvas_cursor (current_canvas_cursor
);
1806 _verbose_cursor
->hide ();
1809 case RegionViewNameHighlight
:
1810 case LeftFrameHandle
:
1811 case RightFrameHandle
:
1812 case StartSelectionTrimItem
:
1813 case EndSelectionTrimItem
:
1814 case PlayheadCursorItem
:
1817 case ImageFrameHandleStartItem
:
1818 case ImageFrameHandleEndItem
:
1819 case MarkerViewHandleStartItem
:
1820 case MarkerViewHandleEndItem
:
1823 _over_region_trim_target
= false;
1825 if (is_drawable()) {
1826 set_canvas_cursor (current_canvas_cursor
);
1831 case AutomationLineItem
:
1832 al
= reinterpret_cast<AutomationLine
*> (item
->get_data ("line"));
1834 ArdourCanvas::Line
*line
= dynamic_cast<ArdourCanvas::Line
*> (item
);
1836 line
->property_fill_color_rgba() = al
->get_line_color();
1838 if (is_drawable()) {
1839 set_canvas_cursor (current_canvas_cursor
);
1843 case RegionViewName
:
1844 /* see enter_handler() for notes */
1845 _over_region_trim_target
= false;
1847 if (!reinterpret_cast<RegionView
*> (item
->get_data ("regionview"))->name_active()) {
1848 if (is_drawable() && mouse_mode
== MouseObject
) {
1849 set_canvas_cursor (current_canvas_cursor
);
1854 case RangeMarkerBarItem
:
1855 case TransportMarkerBarItem
:
1856 case CdMarkerBarItem
:
1860 if (is_drawable()) {
1861 set_canvas_cursor (current_canvas_cursor
);
1866 if ((marker
= static_cast<Marker
*> (item
->get_data ("marker"))) == 0) {
1870 if ((loc
= find_location_from_marker (marker
, is_start
)) != 0) {
1871 location_flags_changed (loc
, this);
1874 case MeterMarkerItem
:
1875 case TempoMarkerItem
:
1877 if (is_drawable()) {
1878 set_canvas_cursor (_cursors
->timebar
);
1883 case FadeInHandleItem
:
1884 case FadeOutHandleItem
:
1885 rv
= static_cast<RegionView
*>(item
->get_data ("regionview"));
1887 ArdourCanvas::SimpleRect
*rect
= dynamic_cast<ArdourCanvas::SimpleRect
*> (item
);
1889 rect
->property_fill_color_rgba() = rv
->get_fill_color();
1890 rect
->property_outline_pixels() = 0;
1893 set_canvas_cursor (current_canvas_cursor
);
1896 case AutomationTrackItem
:
1897 if (is_drawable()) {
1898 set_canvas_cursor (current_canvas_cursor
);
1899 clear_entered_track
= true;
1900 Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track
));
1903 case FeatureLineItem
:
1905 ArdourCanvas::Line
*line
= dynamic_cast<ArdourCanvas::Line
*> (item
);
1906 line
->property_fill_color_rgba() = (guint
) ARDOUR_UI::config()->canvasvar_ZeroLine
.get();;
1918 Editor::left_automation_track ()
1920 if (clear_entered_track
) {
1921 set_entered_track (0);
1922 clear_entered_track
= false;
1928 Editor::scrub (framepos_t frame
, double current_x
)
1932 if (scrubbing_direction
== 0) {
1934 _session
->request_locate (frame
, false);
1935 _session
->request_transport_speed (0.1);
1936 scrubbing_direction
= 1;
1940 if (last_scrub_x
> current_x
) {
1942 /* pointer moved to the left */
1944 if (scrubbing_direction
> 0) {
1946 /* we reversed direction to go backwards */
1949 scrub_reverse_distance
+= (int) (last_scrub_x
- current_x
);
1953 /* still moving to the left (backwards) */
1955 scrub_reversals
= 0;
1956 scrub_reverse_distance
= 0;
1958 delta
= 0.01 * (last_scrub_x
- current_x
);
1959 _session
->request_transport_speed_nonzero (_session
->transport_speed() - delta
);
1963 /* pointer moved to the right */
1965 if (scrubbing_direction
< 0) {
1966 /* we reversed direction to go forward */
1969 scrub_reverse_distance
+= (int) (current_x
- last_scrub_x
);
1972 /* still moving to the right */
1974 scrub_reversals
= 0;
1975 scrub_reverse_distance
= 0;
1977 delta
= 0.01 * (current_x
- last_scrub_x
);
1978 _session
->request_transport_speed_nonzero (_session
->transport_speed() + delta
);
1982 /* if there have been more than 2 opposite motion moves detected, or one that moves
1983 back more than 10 pixels, reverse direction
1986 if (scrub_reversals
>= 2 || scrub_reverse_distance
> 10) {
1988 if (scrubbing_direction
> 0) {
1989 /* was forwards, go backwards */
1990 _session
->request_transport_speed (-0.1);
1991 scrubbing_direction
= -1;
1993 /* was backwards, go forwards */
1994 _session
->request_transport_speed (0.1);
1995 scrubbing_direction
= 1;
1998 scrub_reverse_distance
= 0;
1999 scrub_reversals
= 0;
2003 last_scrub_x
= current_x
;
2007 Editor::motion_handler (ArdourCanvas::Item
* /*item*/, GdkEvent
* event
, bool from_autoscroll
)
2009 _last_motion_y
= event
->motion
.y
;
2011 if (event
->motion
.is_hint
) {
2014 /* We call this so that MOTION_NOTIFY events continue to be
2015 delivered to the canvas. We need to do this because we set
2016 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2017 the density of the events, at the expense of a round-trip
2018 to the server. Given that this will mostly occur on cases
2019 where DISPLAY = :0.0, and given the cost of what the motion
2020 event might do, its a good tradeoff.
2023 track_canvas
->get_pointer (x
, y
);
2026 if (current_stepping_trackview
) {
2027 /* don't keep the persistent stepped trackview if the mouse moves */
2028 current_stepping_trackview
= 0;
2029 step_timeout
.disconnect ();
2032 if (_session
&& _session
->actively_recording()) {
2033 /* Sorry. no dragging stuff around while we record */
2037 JoinObjectRangeState
const old
= _join_object_range_state
;
2038 update_join_object_range_location (event
->motion
.x
, event
->motion
.y
);
2039 if (_join_object_range_state
!= old
) {
2040 set_canvas_cursor ();
2043 if (_over_region_trim_target
) {
2044 set_canvas_cursor_for_region_view (event
->motion
.x
, entered_regionview
);
2047 bool handled
= false;
2048 if (_drags
->active ()) {
2049 handled
= _drags
->motion_handler (event
, from_autoscroll
);
2056 track_canvas_motion (event
);
2061 Editor::remove_gain_control_point (ArdourCanvas::Item
*item
, GdkEvent
* /*event*/)
2063 ControlPoint
* control_point
;
2065 if ((control_point
= reinterpret_cast<ControlPoint
*> (item
->get_data ("control_point"))) == 0) {
2066 fatal
<< _("programming error: control point canvas item has no control point object pointer!") << endmsg
;
2070 // We shouldn't remove the first or last gain point
2071 if (control_point
->line().is_last_point(*control_point
) ||
2072 control_point
->line().is_first_point(*control_point
)) {
2076 control_point
->line().remove_point (*control_point
);
2080 Editor::remove_control_point (ArdourCanvas::Item
* item
, GdkEvent
* /*event*/)
2082 ControlPoint
* control_point
;
2084 if ((control_point
= reinterpret_cast<ControlPoint
*> (item
->get_data ("control_point"))) == 0) {
2085 fatal
<< _("programming error: control point canvas item has no control point object pointer!") << endmsg
;
2089 control_point
->line().remove_point (*control_point
);
2093 Editor::edit_control_point (ArdourCanvas::Item
* item
)
2095 ControlPoint
* p
= reinterpret_cast<ControlPoint
*> (item
->get_data ("control_point"));
2098 fatal
<< _("programming error: control point canvas item has no control point object pointer!") << endmsg
;
2102 ControlPointDialog
d (p
);
2103 d
.set_position (Gtk::WIN_POS_MOUSE
);
2106 if (d
.run () != RESPONSE_ACCEPT
) {
2110 p
->line().modify_point_y (*p
, d
.get_y_fraction ());
2114 Editor::edit_note (ArdourCanvas::Item
* item
)
2116 ArdourCanvas::CanvasNoteEvent
* e
= dynamic_cast<ArdourCanvas::CanvasNoteEvent
*> (item
);
2119 EditNoteDialog
d (&e
->region_view(), e
);
2120 d
.set_position (Gtk::WIN_POS_MOUSE
);
2128 Editor::visible_order_range (int* low
, int* high
) const
2130 *low
= TimeAxisView::max_order ();
2133 for (TrackViewList::const_iterator i
= track_views
.begin(); i
!= track_views
.end(); ++i
) {
2135 RouteTimeAxisView
* rtv
= dynamic_cast<RouteTimeAxisView
*> (*i
);
2137 if (!rtv
->hidden()) {
2139 if (*high
< rtv
->order()) {
2140 *high
= rtv
->order ();
2143 if (*low
> rtv
->order()) {
2144 *low
= rtv
->order ();
2151 Editor::region_view_item_click (AudioRegionView
& rv
, GdkEventButton
* event
)
2153 /* Either add to or set the set the region selection, unless
2154 this is an alignment click (control used)
2157 if (Keyboard::modifier_state_contains (event
->state
, Keyboard::PrimaryModifier
)) {
2158 TimeAxisView
* tv
= &rv
.get_time_axis_view();
2159 RouteTimeAxisView
* rtv
= dynamic_cast<RouteTimeAxisView
*>(tv
);
2161 if (rtv
&& rtv
->is_track()) {
2162 speed
= rtv
->track()->speed();
2165 framepos_t where
= get_preferred_edit_position();
2169 if (Keyboard::modifier_state_equals (event
->state
, Keyboard::ModifierMask (Keyboard::PrimaryModifier
|Keyboard::SecondaryModifier
))) {
2171 align_region (rv
.region(), SyncPoint
, (framepos_t
) (where
* speed
));
2173 } else if (Keyboard::modifier_state_equals (event
->state
, Keyboard::ModifierMask (Keyboard::PrimaryModifier
|Keyboard::TertiaryModifier
))) {
2175 align_region (rv
.region(), End
, (framepos_t
) (where
* speed
));
2179 align_region (rv
.region(), Start
, (framepos_t
) (where
* speed
));
2186 Editor::collect_new_region_view (RegionView
* rv
)
2188 latest_regionviews
.push_back (rv
);
2192 Editor::collect_and_select_new_region_view (RegionView
* rv
)
2195 latest_regionviews
.push_back (rv
);
2199 Editor::cancel_selection ()
2201 for (TrackViewList::iterator i
= track_views
.begin(); i
!= track_views
.end(); ++i
) {
2202 (*i
)->hide_selection ();
2205 selection
->clear ();
2206 clicked_selection
= 0;
2211 Editor::point_trim (GdkEvent
* event
, framepos_t new_bound
)
2213 RegionView
* rv
= clicked_regionview
;
2215 /* Choose action dependant on which button was pressed */
2216 switch (event
->button
.button
) {
2218 begin_reversible_command (_("start point trim"));
2220 if (selection
->selected (rv
)) {
2221 for (list
<RegionView
*>::const_iterator i
= selection
->regions
.by_layer().begin();
2222 i
!= selection
->regions
.by_layer().end(); ++i
)
2225 cerr
<< "region view contains null region" << endl
;
2228 if (!(*i
)->region()->locked()) {
2229 (*i
)->region()->clear_changes ();
2230 (*i
)->region()->trim_front (new_bound
);
2231 _session
->add_command(new StatefulDiffCommand ((*i
)->region()));
2236 if (!rv
->region()->locked()) {
2237 rv
->region()->clear_changes ();
2238 rv
->region()->trim_front (new_bound
);
2239 _session
->add_command(new StatefulDiffCommand (rv
->region()));
2243 commit_reversible_command();
2247 begin_reversible_command (_("End point trim"));
2249 if (selection
->selected (rv
)) {
2251 for (list
<RegionView
*>::const_iterator i
= selection
->regions
.by_layer().begin(); i
!= selection
->regions
.by_layer().end(); ++i
)
2253 if (!(*i
)->region()->locked()) {
2254 (*i
)->region()->clear_changes();
2255 (*i
)->region()->trim_end (new_bound
);
2256 _session
->add_command(new StatefulDiffCommand ((*i
)->region()));
2262 if (!rv
->region()->locked()) {
2263 rv
->region()->clear_changes ();
2264 rv
->region()->trim_end (new_bound
);
2265 _session
->add_command (new StatefulDiffCommand (rv
->region()));
2269 commit_reversible_command();
2278 Editor::hide_marker (ArdourCanvas::Item
* item
, GdkEvent
* /*event*/)
2283 if ((marker
= static_cast<Marker
*> (item
->get_data ("marker"))) == 0) {
2284 fatal
<< _("programming error: marker canvas item has no marker object pointer!") << endmsg
;
2288 Location
* location
= find_location_from_marker (marker
, is_start
);
2289 location
->set_hidden (true, this);
2294 Editor::reposition_zoom_rect (framepos_t start
, framepos_t end
)
2296 double x1
= frame_to_pixel (start
);
2297 double x2
= frame_to_pixel (end
);
2298 double y2
= full_canvas_height
- 1.0;
2300 zoom_rect
->property_x1() = x1
;
2301 zoom_rect
->property_y1() = 1.0;
2302 zoom_rect
->property_x2() = x2
;
2303 zoom_rect
->property_y2() = y2
;
2308 Editor::mouse_rename_region (ArdourCanvas::Item
* /*item*/, GdkEvent
* /*event*/)
2310 using namespace Gtkmm2ext
;
2312 ArdourPrompter
prompter (false);
2314 prompter
.set_prompt (_("Name for region:"));
2315 prompter
.set_initial_text (clicked_regionview
->region()->name());
2316 prompter
.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT
);
2317 prompter
.set_response_sensitive (Gtk::RESPONSE_ACCEPT
, false);
2318 prompter
.show_all ();
2319 switch (prompter
.run ()) {
2320 case Gtk::RESPONSE_ACCEPT
:
2322 prompter
.get_result(str
);
2324 clicked_regionview
->region()->set_name (str
);
2333 Editor::mouse_brush_insert_region (RegionView
* rv
, framepos_t pos
)
2335 /* no brushing without a useful snap setting */
2337 switch (_snap_mode
) {
2339 return; /* can't work because it allows region to be placed anywhere */
2344 switch (_snap_type
) {
2352 /* don't brush a copy over the original */
2354 if (pos
== rv
->region()->position()) {
2358 RouteTimeAxisView
* rtv
= dynamic_cast<RouteTimeAxisView
*>(&rv
->get_time_axis_view());
2360 if (rtv
== 0 || !rtv
->is_track()) {
2364 boost::shared_ptr
<Playlist
> playlist
= rtv
->playlist();
2365 double speed
= rtv
->track()->speed();
2367 playlist
->clear_changes ();
2368 boost::shared_ptr
<Region
> new_region (RegionFactory::create (rv
->region(), true));
2369 playlist
->add_region (new_region
, (framepos_t
) (pos
* speed
));
2370 _session
->add_command (new StatefulDiffCommand (playlist
));
2372 // playlist is frozen, so we have to update manually XXX this is disgusting
2374 playlist
->RegionAdded (new_region
); /* EMIT SIGNAL */
2378 Editor::track_height_step_timeout ()
2380 if (get_microseconds() - last_track_height_step_timestamp
< 250000) {
2381 current_stepping_trackview
= 0;
2388 Editor::add_region_drag (ArdourCanvas::Item
* item
, GdkEvent
* event
, RegionView
* region_view
)
2390 assert (region_view
);
2392 if (!region_view
->region()->playlist()) {
2396 _region_motion_group
->raise_to_top ();
2398 if (Config
->get_edit_mode() == Splice
) {
2399 _drags
->add (new RegionSpliceDrag (this, item
, region_view
, selection
->regions
.by_layer()));
2401 RegionSelection s
= get_equivalent_regions (selection
->regions
, ARDOUR::Properties::edit
.property_id
);
2402 _drags
->add (new RegionMoveDrag (this, item
, region_view
, s
.by_layer(), false, false));
2405 /* sync the canvas to what we think is its current state */
2406 update_canvas_now();
2410 Editor::add_region_copy_drag (ArdourCanvas::Item
* item
, GdkEvent
* event
, RegionView
* region_view
)
2412 assert (region_view
);
2414 if (!region_view
->region()->playlist()) {
2418 _region_motion_group
->raise_to_top ();
2420 RegionSelection s
= get_equivalent_regions (selection
->regions
, ARDOUR::Properties::edit
.property_id
);
2421 _drags
->add (new RegionMoveDrag (this, item
, region_view
, s
.by_layer(), false, true));
2425 Editor::add_region_brush_drag (ArdourCanvas::Item
* item
, GdkEvent
* event
, RegionView
* region_view
)
2427 assert (region_view
);
2429 if (!region_view
->region()->playlist()) {
2433 if (Config
->get_edit_mode() == Splice
) {
2437 RegionSelection s
= get_equivalent_regions (selection
->regions
, ARDOUR::Properties::edit
.property_id
);
2438 _drags
->add (new RegionMoveDrag (this, item
, region_view
, s
.by_layer(), true, false));
2440 begin_reversible_command (Operations::drag_region_brush
);
2443 /** Start a grab where a time range is selected, track(s) are selected, and the
2444 * user clicks and drags a region with a modifier in order to create a new region containing
2445 * the section of the clicked region that lies within the time range.
2448 Editor::start_selection_grab (ArdourCanvas::Item
* /*item*/, GdkEvent
* event
)
2450 if (clicked_regionview
== 0) {
2454 /* lets try to create new Region for the selection */
2456 vector
<boost::shared_ptr
<Region
> > new_regions
;
2457 create_region_from_selection (new_regions
);
2459 if (new_regions
.empty()) {
2463 /* XXX fix me one day to use all new regions */
2465 boost::shared_ptr
<Region
> region (new_regions
.front());
2467 /* add it to the current stream/playlist.
2469 tricky: the streamview for the track will add a new regionview. we will
2470 catch the signal it sends when it creates the regionview to
2471 set the regionview we want to then drag.
2474 latest_regionviews
.clear();
2475 sigc::connection c
= clicked_routeview
->view()->RegionViewAdded
.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view
));
2477 /* A selection grab currently creates two undo/redo operations, one for
2478 creating the new region and another for moving it.
2481 begin_reversible_command (Operations::selection_grab
);
2483 boost::shared_ptr
<Playlist
> playlist
= clicked_axisview
->playlist();
2485 playlist
->clear_changes ();
2486 clicked_routeview
->playlist()->add_region (region
, selection
->time
[clicked_selection
].start
);
2487 _session
->add_command(new StatefulDiffCommand (playlist
));
2489 commit_reversible_command ();
2493 if (latest_regionviews
.empty()) {
2494 /* something went wrong */
2498 /* we need to deselect all other regionviews, and select this one
2499 i'm ignoring undo stuff, because the region creation will take care of it
2501 selection
->set (latest_regionviews
);
2503 _drags
->set (new RegionMoveDrag (this, latest_regionviews
.front()->get_canvas_group(), latest_regionviews
.front(), latest_regionviews
, false, false), event
);
2509 if (_drags
->active ()) {
2512 selection
->clear ();
2517 Editor::set_internal_edit (bool yn
)
2519 _internal_editing
= yn
;
2522 mouse_select_button
.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
2523 mouse_select_button
.get_image ()->show ();
2524 ARDOUR_UI::instance()->set_tip (mouse_select_button
, _("Draw/Edit MIDI Notes"));
2525 mouse_mode_toggled (mouse_mode
);
2527 pre_internal_mouse_mode
= mouse_mode
;
2529 for (TrackViewList::iterator i
= track_views
.begin(); i
!= track_views
.end(); ++i
) {
2530 (*i
)->enter_internal_edit_mode ();
2535 mouse_select_button
.set_image (*(manage (new Image (::get_icon("tool_range")))));
2536 mouse_select_button
.get_image ()->show ();
2537 ARDOUR_UI::instance()->set_tip (mouse_select_button
, _("Select/Move Ranges"));
2538 mouse_mode_toggled (mouse_mode
); // sets cursor
2540 for (TrackViewList::iterator i
= track_views
.begin(); i
!= track_views
.end(); ++i
) {
2541 (*i
)->leave_internal_edit_mode ();
2544 if (mouse_mode
== MouseRange
&& pre_internal_mouse_mode
!= MouseRange
) {
2545 /* we were drawing .. flip back to something sensible */
2546 set_mouse_mode (pre_internal_mouse_mode
);
2551 /** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
2552 * used by the `join object/range' tool mode.
2555 Editor::update_join_object_range_location (double x
, double y
)
2557 /* XXX: actually, this decides based on whether the mouse is in the top or bottom half of a RouteTimeAxisView;
2558 entered_{track,regionview} is not always setup (e.g. if the mouse is over a TimeSelection), and to get a Region
2559 that we're over requires searching the playlist.
2562 if (join_object_range_button
.get_active() == false || (mouse_mode
!= MouseRange
&& mouse_mode
!= MouseObject
)) {
2563 _join_object_range_state
= JOIN_OBJECT_RANGE_NONE
;
2567 if (mouse_mode
== MouseObject
) {
2568 _join_object_range_state
= JOIN_OBJECT_RANGE_OBJECT
;
2569 } else if (mouse_mode
== MouseRange
) {
2570 _join_object_range_state
= JOIN_OBJECT_RANGE_RANGE
;
2573 /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
2574 pair
<TimeAxisView
*, int> tvp
= trackview_by_y_position (y
+ vertical_adjustment
.get_value() - canvas_timebars_vsize
);
2578 RouteTimeAxisView
* rtv
= dynamic_cast<RouteTimeAxisView
*> (tvp
.first
);
2583 rtv
->canvas_display()->w2i (cx
, cy
);
2585 double const c
= cy
/ rtv
->view()->child_height();
2587 double const f
= modf (c
, &d
);
2589 _join_object_range_state
= f
< 0.5 ? JOIN_OBJECT_RANGE_RANGE
: JOIN_OBJECT_RANGE_OBJECT
;
2595 Editor::effective_mouse_mode () const
2597 if (_join_object_range_state
== JOIN_OBJECT_RANGE_OBJECT
) {
2599 } else if (_join_object_range_state
== JOIN_OBJECT_RANGE_RANGE
) {
2607 Editor::remove_midi_note (ArdourCanvas::Item
* item
, GdkEvent
*)
2609 ArdourCanvas::CanvasNoteEvent
* e
= dynamic_cast<ArdourCanvas::CanvasNoteEvent
*> (item
);
2612 e
->region_view().delete_note (e
->note ());
2616 Editor::set_canvas_cursor_for_region_view (double x
, RegionView
* rv
)
2620 ArdourCanvas::Group
* g
= rv
->get_canvas_group ();
2621 ArdourCanvas::Group
* p
= g
->get_parent_group ();
2623 /* Compute x in region view parent coordinates */
2627 double x1
, x2
, y1
, y2
;
2628 g
->get_bounds (x1
, y1
, x2
, y2
);
2630 /* Halfway across the region */
2631 double const h
= (x1
+ x2
) / 2;
2633 Trimmable::CanTrim ct
= rv
->region()->can_trim ();
2635 if (ct
& Trimmable::FrontTrimEarlier
) {
2636 set_canvas_cursor (_cursors
->left_side_trim
);
2638 set_canvas_cursor (_cursors
->left_side_trim_right_only
);
2641 if (ct
& Trimmable::EndTrimLater
) {
2642 set_canvas_cursor (_cursors
->right_side_trim
);
2644 set_canvas_cursor (_cursors
->right_side_trim_left_only
);
2649 /** Obtain the pointer position in world coordinates */
2651 Editor::get_pointer_position (double& x
, double& y
) const
2654 track_canvas
->get_pointer (px
, py
);
2655 track_canvas
->window_to_world (px
, py
, x
, y
);