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/playlist.h"
70 #include "ardour/audioplaylist.h"
71 #include "ardour/audioregion.h"
72 #include "ardour/midi_region.h"
73 #include "ardour/dB.h"
74 #include "ardour/utils.h"
75 #include "ardour/region_factory.h"
76 #include "ardour/source_factory.h"
77 #include "ardour/session.h"
78 #include "ardour/operations.h"
85 using namespace ARDOUR;
88 using namespace Editing;
89 using Gtkmm2ext::Keyboard;
92 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
94 /* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only
95 pays attentions to subwindows. this means that menu windows are ignored, and
96 if the pointer is in a menu, the return window from the call will be the
97 the regular subwindow *under* the menu.
99 this matters quite a lot if the pointer is moving around in a menu that overlaps
100 the track canvas because we will believe that we are within the track canvas
101 when we are not. therefore, we track enter/leave events for the track canvas
102 and allow that to override the result of gdk_window_get_pointer().
105 if (!within_track_canvas) {
111 Gdk::ModifierType mask;
112 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
113 Glib::RefPtr<const Gdk::Window> pointer_window;
115 if (!canvas_window) {
119 pointer_window = canvas_window->get_pointer (x, y, mask);
121 if (pointer_window == track_canvas->get_bin_window()) {
124 in_track_canvas = true;
127 in_track_canvas = false;
132 event.type = GDK_BUTTON_RELEASE;
136 where = event_frame (&event, 0, 0);
141 Editor::event_frame (GdkEvent const * event, double* pcx, double* pcy) const
155 switch (event->type) {
156 case GDK_BUTTON_RELEASE:
157 case GDK_BUTTON_PRESS:
158 case GDK_2BUTTON_PRESS:
159 case GDK_3BUTTON_PRESS:
160 *pcx = event->button.x;
161 *pcy = event->button.y;
162 _trackview_group->w2i(*pcx, *pcy);
164 case GDK_MOTION_NOTIFY:
165 *pcx = event->motion.x;
166 *pcy = event->motion.y;
167 _trackview_group->w2i(*pcx, *pcy);
169 case GDK_ENTER_NOTIFY:
170 case GDK_LEAVE_NOTIFY:
171 track_canvas->w2c(event->crossing.x, event->crossing.y, *pcx, *pcy);
174 case GDK_KEY_RELEASE:
175 // track_canvas->w2c(event->key.x, event->key.y, *pcx, *pcy);
178 warning << string_compose (_("Editor::event_frame() used on unhandled event type %1"), event->type) << endmsg;
182 /* note that pixel_to_frame() never returns less than zero, so even if the pixel
183 position is negative (as can be the case with motion events in particular),
184 the frame location is always positive.
187 return pixel_to_frame (*pcx);
191 Editor::which_grabber_cursor ()
193 Gdk::Cursor* c = _cursors->grabber;
195 if (_internal_editing) {
196 switch (mouse_mode) {
198 c = _cursors->midi_pencil;
202 c = _cursors->grabber_note;
206 c = _cursors->midi_resize;
215 switch (_edit_point) {
217 c = _cursors->grabber_edit_point;
220 boost::shared_ptr<Movable> m = _movable.lock();
221 if (m && m->locked()) {
222 c = _cursors->speaker;
232 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
234 boost::shared_ptr<Trimmable> st = _trimmable.lock();
236 if (!st || st == t) {
238 set_canvas_cursor ();
243 Editor::set_current_movable (boost::shared_ptr<Movable> m)
245 boost::shared_ptr<Movable> sm = _movable.lock();
247 if (!sm || sm != m) {
249 set_canvas_cursor ();
254 Editor::set_canvas_cursor ()
256 if (_internal_editing) {
258 switch (mouse_mode) {
260 current_canvas_cursor = _cursors->midi_pencil;
264 current_canvas_cursor = which_grabber_cursor();
268 current_canvas_cursor = _cursors->midi_resize;
277 switch (mouse_mode) {
279 current_canvas_cursor = _cursors->selector;
283 current_canvas_cursor = which_grabber_cursor();
287 /* shouldn't be possible, but just cover it anyway ... */
288 current_canvas_cursor = _cursors->midi_pencil;
292 current_canvas_cursor = _cursors->cross_hair;
296 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
297 current_canvas_cursor = _cursors->zoom_out;
299 current_canvas_cursor = _cursors->zoom_in;
304 current_canvas_cursor = _cursors->time_fx; // just use playhead
308 current_canvas_cursor = _cursors->speaker;
313 switch (_join_object_range_state) {
314 case JOIN_OBJECT_RANGE_NONE:
316 case JOIN_OBJECT_RANGE_OBJECT:
317 current_canvas_cursor = which_grabber_cursor ();
319 case JOIN_OBJECT_RANGE_RANGE:
320 current_canvas_cursor = _cursors->selector;
324 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
325 if (smart_mode_action->get_active()) {
327 get_pointer_position (x, y);
328 ArdourCanvas::Item* i = track_canvas->get_item_at (x, y);
329 if (i && i->property_parent() && (*i->property_parent()).get_data (X_("timeselection"))) {
330 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y + vertical_adjustment.get_value() - canvas_timebars_vsize);
331 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
332 current_canvas_cursor = _cursors->up_down;
337 set_canvas_cursor (current_canvas_cursor, true);
341 Editor::set_mouse_mode (MouseMode m, bool force)
343 if (_drags->active ()) {
347 if (!force && m == mouse_mode) {
351 Glib::RefPtr<Action> act;
355 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
359 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
363 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
367 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-gain"));
371 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-zoom"));
375 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
379 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
385 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
388 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
389 tact->set_active (false);
390 tact->set_active (true);
392 MouseModeChanged (); /* EMIT SIGNAL */
396 Editor::mouse_mode_toggled (MouseMode m)
398 Glib::RefPtr<Action> act;
399 Glib::RefPtr<ToggleAction> tact;
403 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
407 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
411 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
415 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-gain"));
419 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-zoom"));
423 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
427 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
433 tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
436 if (!tact->get_active()) {
437 /* this was just the notification that the old mode has been
438 * left. we'll get called again with the new mode active in a
446 act = ActionManager::get_action (X_("MouseMode"), X_("toggle-internal-edit"));
447 tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
448 tact->set_active (true);
458 if (!internal_editing()) {
459 if (mouse_mode != MouseRange && mouse_mode != MouseGain && _join_object_range_state == JOIN_OBJECT_RANGE_NONE) {
461 /* in all modes except range, gain and joined object/range, hide the range selection,
462 show the object (region) selection.
465 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
466 (*i)->hide_selection ();
472 in range or object/range mode, show the range selection.
475 for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
476 (*i)->show_selection (selection->time);
481 set_canvas_cursor ();
483 MouseModeChanged (); /* EMIT SIGNAL */
487 Editor::step_mouse_mode (bool next)
489 switch (current_mouse_mode()) {
492 if (Profile->get_sae()) {
493 set_mouse_mode (MouseZoom);
495 set_mouse_mode (MouseRange);
498 set_mouse_mode (MouseTimeFX);
503 if (next) set_mouse_mode (MouseDraw);
504 else set_mouse_mode (MouseObject);
508 if (next) set_mouse_mode (MouseZoom);
509 else set_mouse_mode (MouseRange);
514 if (Profile->get_sae()) {
515 set_mouse_mode (MouseTimeFX);
517 set_mouse_mode (MouseGain);
520 if (Profile->get_sae()) {
521 set_mouse_mode (MouseObject);
523 set_mouse_mode (MouseDraw);
529 if (next) set_mouse_mode (MouseTimeFX);
530 else set_mouse_mode (MouseZoom);
535 set_mouse_mode (MouseAudition);
537 if (Profile->get_sae()) {
538 set_mouse_mode (MouseZoom);
540 set_mouse_mode (MouseGain);
546 if (next) set_mouse_mode (MouseObject);
547 else set_mouse_mode (MouseTimeFX);
553 Editor::toggle_internal_editing_from_double_click (GdkEvent* event)
555 if (_drags->active()) {
556 _drags->end_grab (event);
559 ActionManager::do_action ("MouseMode", "toggle-internal-edit");
561 /* prevent reversion of edit cursor on button release */
563 pre_press_cursor = 0;
569 Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemType item_type)
571 /* in object/audition/timefx/gain-automation mode,
572 any button press sets the selection if the object
573 can be selected. this is a bit of hack, because
574 we want to avoid this if the mouse operation is a
577 note: not dbl-click or triple-click
579 Also note that there is no region selection in internal edit mode, otherwise
580 for operations operating on the selection (e.g. cut) it is not obvious whether
581 to cut notes or regions.
584 if (((mouse_mode != MouseObject) &&
585 (_join_object_range_state != JOIN_OBJECT_RANGE_OBJECT) &&
586 (mouse_mode != MouseAudition || item_type != RegionItem) &&
587 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
588 (mouse_mode != MouseGain) &&
589 (mouse_mode != MouseRange) &&
590 (mouse_mode != MouseDraw)) ||
591 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3) ||
592 internal_editing()) {
597 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
599 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
601 /* almost no selection action on modified button-2 or button-3 events */
603 if (item_type != RegionItem && event->button.button != 2) {
609 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
610 bool press = (event->type == GDK_BUTTON_PRESS);
614 if (!doing_range_stuff()) {
615 set_selected_regionview_from_click (press, op);
619 if (doing_range_stuff()) {
620 /* don't change the selection unless the
621 clicked track is not currently selected. if
622 so, "collapse" the selection to just this
625 if (!selection->selected (clicked_axisview)) {
626 set_selected_track_as_side_effect (Selection::Set);
632 case RegionViewNameHighlight:
634 case LeftFrameHandle:
635 case RightFrameHandle:
636 if (doing_object_stuff() || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
637 set_selected_regionview_from_click (press, op);
638 } else if (event->type == GDK_BUTTON_PRESS) {
639 set_selected_track_as_side_effect (op);
644 case StartCrossFadeItem:
645 case EndCrossFadeItem:
648 case FadeInHandleItem:
650 case FadeOutHandleItem:
652 if (doing_object_stuff() || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
653 set_selected_regionview_from_click (press, op);
654 } else if (event->type == GDK_BUTTON_PRESS) {
655 set_selected_track_as_side_effect (op);
659 case ControlPointItem:
660 set_selected_track_as_side_effect (op);
661 if (doing_object_stuff() || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
662 set_selected_control_point_from_click (press, op);
667 /* for context click, select track */
668 if (event->button.button == 3) {
669 selection->clear_tracks ();
670 set_selected_track_as_side_effect (op);
674 case AutomationTrackItem:
675 set_selected_track_as_side_effect (op);
684 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
686 /* single mouse clicks on any of these item types operate
687 independent of mouse mode, mostly because they are
688 not on the main track canvas or because we want
693 case PlayheadCursorItem:
694 _drags->set (new CursorDrag (this, item, true), event);
698 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
699 hide_marker (item, event);
701 _drags->set (new MarkerDrag (this, item), event);
705 case TempoMarkerItem:
707 TempoMarker* m = reinterpret_cast<TempoMarker*> (item->get_data ("marker"));
709 if (m->tempo().movable ()) {
711 new TempoMarkerDrag (
714 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
724 case MeterMarkerItem:
726 MeterMarker* m = reinterpret_cast<MeterMarker*> (item->get_data ("marker"));
728 if (m->meter().movable ()) {
730 new MeterMarkerDrag (
733 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
746 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
747 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
753 case RangeMarkerBarItem:
754 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
755 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
757 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
762 case CdMarkerBarItem:
763 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
764 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
766 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
771 case TransportMarkerBarItem:
772 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
773 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
775 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
784 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
785 /* special case: allow trim of range selections in joined object mode;
786 in theory eff should equal MouseRange in this case, but it doesn't
787 because entering the range selection canvas item results in entered_regionview
788 being set to 0, so update_join_object_range_location acts as if we aren't
791 if (item_type == StartSelectionTrimItem) {
792 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
793 } else if (item_type == EndSelectionTrimItem) {
794 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
798 Editing::MouseMode eff = effective_mouse_mode ();
800 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
801 if (item_type == FadeInHandleItem || item_type == FadeOutHandleItem) {
808 case StartSelectionTrimItem:
809 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
812 case EndSelectionTrimItem:
813 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
817 if (Keyboard::modifier_state_contains
818 (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier))) {
819 // contains and not equals because I can't use alt as a modifier alone.
820 start_selection_grab (item, event);
821 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
822 /* grab selection for moving */
823 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
825 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
826 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
828 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
829 if (smart_mode_action->get_active() && atv) {
830 /* smart "join" mode: drag automation */
831 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
833 /* this was debated, but decided the more common action was to
834 make a new selection */
835 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
842 if (internal_editing()) {
843 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
844 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
848 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
853 case RegionViewNameHighlight:
854 if (!clicked_regionview->region()->locked()) {
855 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
856 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
861 case LeftFrameHandle:
862 case RightFrameHandle:
863 if (!internal_editing() && doing_object_stuff() && !clicked_regionview->region()->locked()) {
864 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
865 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
871 if (!internal_editing()) {
872 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
881 if (internal_editing()) {
882 /* trim notes if we're in internal edit mode and near the ends of the note */
883 ArdourCanvas::CanvasNote* cn = dynamic_cast<ArdourCanvas::CanvasNote*> (item);
884 if (cn && cn->big_enough_to_trim() && cn->mouse_near_ends()) {
885 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
887 _drags->set (new NoteDrag (this, item), event);
901 if (internal_editing()) {
902 ArdourCanvas::CanvasNoteEvent* cn = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
903 if (cn->mouse_near_ends()) {
904 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
906 _drags->set (new NoteDrag (this, item), event);
916 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
917 event->type == GDK_BUTTON_PRESS) {
919 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
921 } else if (event->type == GDK_BUTTON_PRESS) {
924 case FadeInHandleItem:
926 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
927 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, _cursors->fade_in);
931 case FadeOutHandleItem:
933 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
934 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, _cursors->fade_out);
938 case StartCrossFadeItem:
939 _drags->set (new CrossfadeEdgeDrag (this, reinterpret_cast<AudioRegionView*>(item->get_data("regionview")), item, true), event, 0);
942 case EndCrossFadeItem:
943 _drags->set (new CrossfadeEdgeDrag (this, reinterpret_cast<AudioRegionView*>(item->get_data("regionview")), item, false), event, 0);
946 case FeatureLineItem:
948 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
949 remove_transient(item);
953 _drags->set (new FeatureLineDrag (this, item), event);
959 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
960 /* click on an automation region view; do nothing here and let the ARV's signal handler
966 if (internal_editing ()) {
967 if (event->type == GDK_2BUTTON_PRESS && event->button.button == 1) {
968 Glib::RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("toggle-internal-edit"));
974 /* click on a normal region view */
975 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
976 add_region_copy_drag (item, event, clicked_regionview);
977 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
978 add_region_brush_drag (item, event, clicked_regionview);
980 add_region_drag (item, event, clicked_regionview);
984 if (!internal_editing() && (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE && !selection->regions.empty())) {
985 _drags->add (new SelectionDrag (this, clicked_axisview->get_selection_rect (clicked_selection)->rect, SelectionDrag::SelectionMove));
988 _drags->start_grab (event);
991 case RegionViewNameHighlight:
992 case LeftFrameHandle:
993 case RightFrameHandle:
994 if (!clicked_regionview->region()->locked()) {
995 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
996 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
1001 case RegionViewName:
1003 /* rename happens on edit clicks */
1004 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
1005 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, s.by_layer()), event);
1010 case ControlPointItem:
1011 _drags->set (new ControlPointDrag (this, item), event);
1015 case AutomationLineItem:
1016 _drags->set (new LineDrag (this, item), event);
1021 if (internal_editing()) {
1022 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1023 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1027 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1031 case AutomationTrackItem:
1033 TimeAxisView* parent = clicked_axisview->get_parent ();
1034 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1036 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1037 /* create a MIDI region so that we have somewhere to put automation */
1038 _drags->set (new RegionCreateDrag (this, item, parent), event);
1040 /* rubberband drag to select automation points */
1041 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1048 if (smart_mode_action->get_active()) {
1049 /* we're in "smart" joined mode, and we've clicked on a Selection */
1050 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
1051 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
1053 /* if we're over an automation track, start a drag of its data */
1054 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1056 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1059 /* if we're over a track and a region, and in the `object' part of a region,
1060 put a selection around the region and drag both
1062 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
1063 if (rtv && _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
1064 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (rtv->route ());
1066 boost::shared_ptr<Playlist> pl = t->playlist ();
1069 boost::shared_ptr<Region> r = pl->top_region_at (event_frame (event));
1071 RegionView* rv = rtv->view()->find_view (r);
1072 clicked_selection = select_range (rv->region()->position(),
1073 rv->region()->last_frame()+1);
1074 _drags->add (new SelectionDrag (this, item, SelectionDrag::SelectionMove));
1075 list<RegionView*> rvs;
1077 _drags->add (new RegionMoveDrag (this, item, rv, rvs, false, false));
1078 _drags->start_grab (event);
1089 case ImageFrameHandleStartItem:
1090 imageframe_start_handle_op(item, event) ;
1093 case ImageFrameHandleEndItem:
1094 imageframe_end_handle_op(item, event) ;
1097 case MarkerViewHandleStartItem:
1098 markerview_item_start_handle_op(item, event) ;
1101 case MarkerViewHandleEndItem:
1102 markerview_item_end_handle_op(item, event) ;
1105 case MarkerViewItem:
1106 start_markerview_grab(item, event) ;
1108 case ImageFrameItem:
1109 start_imageframe_grab(item, event) ;
1125 switch (item_type) {
1127 _drags->set (new LineDrag (this, item), event);
1130 case ControlPointItem:
1131 _drags->set (new ControlPointDrag (this, item), event);
1137 AudioRegionView* arv = dynamic_cast<AudioRegionView *> (clicked_regionview);
1139 _drags->set (new AutomationRangeDrag (this, arv, selection->time), event, _cursors->up_down);
1140 _drags->start_grab (event);
1146 case AutomationLineItem:
1147 _drags->set (new LineDrag (this, item), event);
1157 if (event->type == GDK_BUTTON_PRESS) {
1158 _drags->set (new MouseZoomDrag (this, item), event);
1165 if (internal_editing() && item_type == NoteItem) {
1166 /* drag notes if we're in internal edit mode */
1167 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
1169 } else if ((!internal_editing() || dynamic_cast<AudioRegionView*> (clicked_regionview)) && clicked_regionview) {
1170 /* do time-FX if we're not in internal edit mode, or we are but we clicked on an audio region */
1171 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1177 _drags->set (new ScrubDrag (this, item), event);
1178 scrub_reversals = 0;
1179 scrub_reverse_distance = 0;
1180 last_scrub_x = event->button.x;
1181 scrubbing_direction = 0;
1182 set_canvas_cursor (_cursors->transparent);
1194 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1196 Editing::MouseMode const eff = effective_mouse_mode ();
1199 switch (item_type) {
1201 if (internal_editing ()) {
1202 /* no region drags in internal edit mode */
1206 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1207 add_region_copy_drag (item, event, clicked_regionview);
1209 add_region_drag (item, event, clicked_regionview);
1211 _drags->start_grab (event);
1214 case ControlPointItem:
1215 _drags->set (new ControlPointDrag (this, item), event);
1223 switch (item_type) {
1224 case RegionViewNameHighlight:
1225 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1229 case LeftFrameHandle:
1230 case RightFrameHandle:
1231 if (!internal_editing ()) {
1232 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1237 case RegionViewName:
1238 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1252 /* relax till release */
1258 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1259 temporal_zoom_to_frame (false, event_frame (event));
1261 temporal_zoom_to_frame (true, event_frame(event));
1274 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1276 if (event->type != GDK_BUTTON_PRESS) {
1280 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
1282 if (canvas_window) {
1283 Glib::RefPtr<const Gdk::Window> pointer_window;
1286 Gdk::ModifierType mask;
1288 pointer_window = canvas_window->get_pointer (x, y, mask);
1290 if (pointer_window == track_canvas->get_bin_window()) {
1291 track_canvas->window_to_world (x, y, wx, wy);
1295 pre_press_cursor = current_canvas_cursor;
1297 track_canvas->grab_focus();
1299 if (_session && _session->actively_recording()) {
1305 if (internal_editing()) {
1306 bool leave_internal_edit_mode = false;
1308 switch (item_type) {
1313 if (!dynamic_cast<MidiRegionView*> (clicked_regionview) && !dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
1314 leave_internal_edit_mode = true;
1318 case PlayheadCursorItem:
1320 case TempoMarkerItem:
1321 case MeterMarkerItem:
1325 case RangeMarkerBarItem:
1326 case CdMarkerBarItem:
1327 case TransportMarkerBarItem:
1328 /* button press on these events never does anything to
1329 change the editing mode.
1334 leave_internal_edit_mode = true;
1341 if (leave_internal_edit_mode) {
1342 ActionManager::do_action ("MouseMode", "toggle-internal-edit");
1346 button_selection (item, event, item_type);
1348 if (!_drags->active () &&
1349 (Keyboard::is_delete_event (&event->button) ||
1350 Keyboard::is_context_menu_event (&event->button) ||
1351 Keyboard::is_edit_event (&event->button))) {
1353 /* handled by button release */
1357 switch (event->button.button) {
1359 return button_press_handler_1 (item, event, item_type);
1363 return button_press_handler_2 (item, event, item_type);
1370 return button_press_dispatch (&event->button);
1379 Editor::button_press_dispatch (GdkEventButton* ev)
1381 /* this function is intended only for buttons 4 and above.
1384 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1385 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1389 Editor::button_release_dispatch (GdkEventButton* ev)
1391 /* this function is intended only for buttons 4 and above.
1394 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1395 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1399 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1401 framepos_t where = event_frame (event, 0, 0);
1402 AutomationTimeAxisView* atv = 0;
1404 if (pre_press_cursor) {
1405 set_canvas_cursor (pre_press_cursor);
1406 pre_press_cursor = 0;
1409 /* no action if we're recording */
1411 if (_session && _session->actively_recording()) {
1415 /* see if we're finishing a drag */
1417 bool were_dragging = false;
1418 if (_drags->active ()) {
1419 bool const r = _drags->end_grab (event);
1421 /* grab dragged, so do nothing else */
1425 were_dragging = true;
1428 update_region_layering_order_editor ();
1430 /* edit events get handled here */
1432 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1433 switch (item_type) {
1435 show_region_properties ();
1438 case TempoMarkerItem:
1439 edit_tempo_marker (item);
1442 case MeterMarkerItem:
1443 edit_meter_marker (item);
1446 case RegionViewName:
1447 if (clicked_regionview->name_active()) {
1448 return mouse_rename_region (item, event);
1452 case ControlPointItem:
1453 edit_control_point (item);
1466 /* context menu events get handled here */
1468 if (Keyboard::is_context_menu_event (&event->button)) {
1470 context_click_event = *event;
1472 if (!_drags->active ()) {
1474 /* no matter which button pops up the context menu, tell the menu
1475 widget to use button 1 to drive menu selection.
1478 switch (item_type) {
1480 case FadeInHandleItem:
1482 case FadeOutHandleItem:
1483 popup_fade_context_menu (1, event->button.time, item, item_type);
1487 popup_track_context_menu (1, event->button.time, item_type, false);
1491 case RegionViewNameHighlight:
1492 case LeftFrameHandle:
1493 case RightFrameHandle:
1494 case RegionViewName:
1495 popup_track_context_menu (1, event->button.time, item_type, false);
1499 popup_track_context_menu (1, event->button.time, item_type, true);
1502 case AutomationTrackItem:
1503 popup_track_context_menu (1, event->button.time, item_type, false);
1507 case RangeMarkerBarItem:
1508 case TransportMarkerBarItem:
1509 case CdMarkerBarItem:
1512 popup_ruler_menu (where, item_type);
1516 marker_context_menu (&event->button, item);
1519 case TempoMarkerItem:
1520 tempo_or_meter_marker_context_menu (&event->button, item);
1523 case MeterMarkerItem:
1524 tempo_or_meter_marker_context_menu (&event->button, item);
1527 case CrossfadeViewItem:
1528 popup_track_context_menu (1, event->button.time, item_type, false);
1531 case ControlPointItem:
1532 popup_control_point_context_menu (item, event);
1536 case ImageFrameItem:
1537 popup_imageframe_edit_menu(1, event->button.time, item, true) ;
1539 case ImageFrameTimeAxisItem:
1540 popup_imageframe_edit_menu(1, event->button.time, item, false) ;
1542 case MarkerViewItem:
1543 popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ;
1545 case MarkerTimeAxisItem:
1546 popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
1558 /* delete events get handled here */
1560 Editing::MouseMode const eff = effective_mouse_mode ();
1562 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1564 switch (item_type) {
1565 case TempoMarkerItem:
1566 remove_tempo_marker (item);
1569 case MeterMarkerItem:
1570 remove_meter_marker (item);
1574 remove_marker (*item, event);
1578 if (eff == MouseObject) {
1579 remove_clicked_region ();
1583 case ControlPointItem:
1584 remove_control_point (item);
1588 remove_midi_note (item, event);
1597 switch (event->button.button) {
1600 switch (item_type) {
1601 /* see comments in button_press_handler */
1602 case PlayheadCursorItem:
1605 case AutomationLineItem:
1606 case StartSelectionTrimItem:
1607 case EndSelectionTrimItem:
1611 if (!_dragging_playhead) {
1612 snap_to_with_modifier (where, event, 0, true);
1613 mouse_add_new_marker (where);
1617 case CdMarkerBarItem:
1618 if (!_dragging_playhead) {
1619 // if we get here then a dragged range wasn't done
1620 snap_to_with_modifier (where, event, 0, true);
1621 mouse_add_new_marker (where, true);
1626 if (!_dragging_playhead) {
1627 snap_to_with_modifier (where, event);
1628 mouse_add_new_tempo_event (where);
1633 if (!_dragging_playhead) {
1634 mouse_add_new_meter_event (pixel_to_frame (event->button.x));
1645 switch (item_type) {
1646 case AutomationTrackItem:
1647 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1649 atv->add_automation_event (event, where, event->button.y);
1659 switch (item_type) {
1662 /* check that we didn't drag before releasing, since
1663 its really annoying to create new control
1664 points when doing this.
1666 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1667 if (!were_dragging && arv) {
1668 arv->add_gain_point_event (item, event);
1674 case AutomationTrackItem:
1675 dynamic_cast<AutomationTimeAxisView*>(clicked_axisview)->
1676 add_automation_event (event, where, event->button.y);
1685 set_canvas_cursor (current_canvas_cursor);
1686 if (scrubbing_direction == 0) {
1687 /* no drag, just a click */
1688 switch (item_type) {
1690 play_selected_region ();
1696 /* make sure we stop */
1697 _session->request_transport_speed (0.0);
1706 /* do any (de)selection operations that should occur on button release */
1707 button_selection (item, event, item_type);
1716 switch (item_type) {
1718 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1720 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1723 // Button2 click is unused
1738 // x_style_paste (where, 1.0);
1759 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1766 switch (item_type) {
1767 case ControlPointItem:
1768 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1769 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1770 cp->set_visible (true);
1774 at_y = cp->get_y ();
1775 cp->i2w (at_x, at_y);
1779 fraction = 1.0 - (cp->get_y() / cp->line().height());
1781 if (is_drawable() && !_drags->active ()) {
1782 set_canvas_cursor (_cursors->fader);
1785 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction), at_x, at_y);
1786 _verbose_cursor->show ();
1791 if (mouse_mode == MouseGain) {
1792 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1794 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
1795 if (is_drawable()) {
1796 set_canvas_cursor (_cursors->fader);
1801 case AutomationLineItem:
1802 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1803 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1805 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
1807 if (is_drawable()) {
1808 set_canvas_cursor (_cursors->fader);
1813 case RegionViewNameHighlight:
1814 if (is_drawable() && doing_object_stuff() && entered_regionview) {
1815 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1816 _over_region_trim_target = true;
1820 case LeftFrameHandle:
1821 case RightFrameHandle:
1822 if (is_drawable() && doing_object_stuff() && !internal_editing() && entered_regionview) {
1823 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1827 case StartSelectionTrimItem:
1829 case ImageFrameHandleStartItem:
1830 case MarkerViewHandleStartItem:
1832 if (is_drawable()) {
1833 set_canvas_cursor (_cursors->left_side_trim);
1836 case EndSelectionTrimItem:
1838 case ImageFrameHandleEndItem:
1839 case MarkerViewHandleEndItem:
1841 if (is_drawable()) {
1842 set_canvas_cursor (_cursors->right_side_trim);
1846 case PlayheadCursorItem:
1847 if (is_drawable()) {
1848 switch (_edit_point) {
1850 set_canvas_cursor (_cursors->grabber_edit_point);
1853 set_canvas_cursor (_cursors->grabber);
1859 case RegionViewName:
1861 /* when the name is not an active item, the entire name highlight is for trimming */
1863 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1864 if (mouse_mode == MouseObject && is_drawable()) {
1865 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1866 _over_region_trim_target = true;
1872 case AutomationTrackItem:
1873 if (is_drawable()) {
1874 Gdk::Cursor *cursor;
1875 switch (mouse_mode) {
1877 cursor = _cursors->selector;
1880 cursor = _cursors->zoom_in;
1883 cursor = _cursors->cross_hair;
1887 set_canvas_cursor (cursor);
1889 AutomationTimeAxisView* atv;
1890 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1891 clear_entered_track = false;
1892 set_entered_track (atv);
1898 case RangeMarkerBarItem:
1899 case TransportMarkerBarItem:
1900 case CdMarkerBarItem:
1903 if (is_drawable()) {
1904 set_canvas_cursor (_cursors->timebar);
1909 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1912 entered_marker = marker;
1913 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1915 case MeterMarkerItem:
1916 case TempoMarkerItem:
1917 if (is_drawable()) {
1918 set_canvas_cursor (_cursors->timebar);
1922 case FadeInHandleItem:
1923 if (mouse_mode == MouseObject && !internal_editing()) {
1924 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1926 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1928 set_canvas_cursor (_cursors->fade_in);
1932 case FadeOutHandleItem:
1933 if (mouse_mode == MouseObject && !internal_editing()) {
1934 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1936 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1938 set_canvas_cursor (_cursors->fade_out);
1941 case FeatureLineItem:
1943 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1944 line->property_fill_color_rgba() = 0xFF0000FF;
1948 if (smart_mode_action->get_active()) {
1949 set_canvas_cursor ();
1957 /* second pass to handle entered track status in a comprehensible way.
1960 switch (item_type) {
1962 case AutomationLineItem:
1963 case ControlPointItem:
1964 /* these do not affect the current entered track state */
1965 clear_entered_track = false;
1968 case AutomationTrackItem:
1969 /* handled above already */
1973 set_entered_track (0);
1981 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1991 switch (item_type) {
1992 case ControlPointItem:
1993 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1994 if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
1995 if (cp->line().npoints() > 1 && !cp->get_selected()) {
1996 cp->set_visible (false);
2000 if (is_drawable()) {
2001 set_canvas_cursor (current_canvas_cursor);
2004 _verbose_cursor->hide ();
2007 case RegionViewNameHighlight:
2008 case LeftFrameHandle:
2009 case RightFrameHandle:
2010 case StartSelectionTrimItem:
2011 case EndSelectionTrimItem:
2012 case PlayheadCursorItem:
2015 case ImageFrameHandleStartItem:
2016 case ImageFrameHandleEndItem:
2017 case MarkerViewHandleStartItem:
2018 case MarkerViewHandleEndItem:
2021 _over_region_trim_target = false;
2023 if (is_drawable()) {
2024 set_canvas_cursor (current_canvas_cursor);
2029 case AutomationLineItem:
2030 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
2032 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
2034 line->property_fill_color_rgba() = al->get_line_color();
2036 if (is_drawable()) {
2037 set_canvas_cursor (current_canvas_cursor);
2041 case RegionViewName:
2042 /* see enter_handler() for notes */
2043 _over_region_trim_target = false;
2045 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
2046 if (is_drawable() && mouse_mode == MouseObject) {
2047 set_canvas_cursor (current_canvas_cursor);
2052 case RangeMarkerBarItem:
2053 case TransportMarkerBarItem:
2054 case CdMarkerBarItem:
2058 if (is_drawable()) {
2059 set_canvas_cursor (current_canvas_cursor);
2064 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2068 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
2069 location_flags_changed (loc, this);
2072 case MeterMarkerItem:
2073 case TempoMarkerItem:
2075 if (is_drawable()) {
2076 set_canvas_cursor (current_canvas_cursor);
2081 case FadeInHandleItem:
2082 case FadeOutHandleItem:
2083 rv = static_cast<RegionView*>(item->get_data ("regionview"));
2085 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
2087 rect->property_fill_color_rgba() = rv->get_fill_color();
2088 rect->property_outline_pixels() = 0;
2091 set_canvas_cursor (current_canvas_cursor);
2094 case AutomationTrackItem:
2095 if (is_drawable()) {
2096 set_canvas_cursor (current_canvas_cursor);
2097 clear_entered_track = true;
2098 Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track));
2101 case FeatureLineItem:
2103 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
2104 line->property_fill_color_rgba() = (guint) ARDOUR_UI::config()->canvasvar_ZeroLine.get();;
2116 Editor::left_automation_track ()
2118 if (clear_entered_track) {
2119 set_entered_track (0);
2120 clear_entered_track = false;
2126 Editor::scrub (framepos_t frame, double current_x)
2130 if (scrubbing_direction == 0) {
2132 _session->request_locate (frame, false);
2133 _session->request_transport_speed (0.1);
2134 scrubbing_direction = 1;
2138 if (last_scrub_x > current_x) {
2140 /* pointer moved to the left */
2142 if (scrubbing_direction > 0) {
2144 /* we reversed direction to go backwards */
2147 scrub_reverse_distance += (int) (last_scrub_x - current_x);
2151 /* still moving to the left (backwards) */
2153 scrub_reversals = 0;
2154 scrub_reverse_distance = 0;
2156 delta = 0.01 * (last_scrub_x - current_x);
2157 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
2161 /* pointer moved to the right */
2163 if (scrubbing_direction < 0) {
2164 /* we reversed direction to go forward */
2167 scrub_reverse_distance += (int) (current_x - last_scrub_x);
2170 /* still moving to the right */
2172 scrub_reversals = 0;
2173 scrub_reverse_distance = 0;
2175 delta = 0.01 * (current_x - last_scrub_x);
2176 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
2180 /* if there have been more than 2 opposite motion moves detected, or one that moves
2181 back more than 10 pixels, reverse direction
2184 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
2186 if (scrubbing_direction > 0) {
2187 /* was forwards, go backwards */
2188 _session->request_transport_speed (-0.1);
2189 scrubbing_direction = -1;
2191 /* was backwards, go forwards */
2192 _session->request_transport_speed (0.1);
2193 scrubbing_direction = 1;
2196 scrub_reverse_distance = 0;
2197 scrub_reversals = 0;
2201 last_scrub_x = current_x;
2205 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2207 _last_motion_y = event->motion.y;
2209 if (event->motion.is_hint) {
2212 /* We call this so that MOTION_NOTIFY events continue to be
2213 delivered to the canvas. We need to do this because we set
2214 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2215 the density of the events, at the expense of a round-trip
2216 to the server. Given that this will mostly occur on cases
2217 where DISPLAY = :0.0, and given the cost of what the motion
2218 event might do, its a good tradeoff.
2221 track_canvas->get_pointer (x, y);
2224 if (current_stepping_trackview) {
2225 /* don't keep the persistent stepped trackview if the mouse moves */
2226 current_stepping_trackview = 0;
2227 step_timeout.disconnect ();
2230 if (_session && _session->actively_recording()) {
2231 /* Sorry. no dragging stuff around while we record */
2235 JoinObjectRangeState const old = _join_object_range_state;
2236 update_join_object_range_location (event->motion.x, event->motion.y);
2237 if (_join_object_range_state != old) {
2238 set_canvas_cursor ();
2241 if (_over_region_trim_target) {
2242 set_canvas_cursor_for_region_view (event->motion.x, entered_regionview);
2245 bool handled = false;
2246 if (_drags->active ()) {
2247 handled = _drags->motion_handler (event, from_autoscroll);
2254 track_canvas_motion (event);
2259 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2261 ControlPoint* control_point;
2263 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2264 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2268 AutomationLine& line = control_point->line ();
2269 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2270 /* we shouldn't remove the first or last gain point in region gain lines */
2271 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2280 Editor::remove_control_point (ArdourCanvas::Item* item)
2282 if (!can_remove_control_point (item)) {
2286 ControlPoint* control_point;
2288 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2289 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2293 control_point->line().remove_point (*control_point);
2297 Editor::edit_control_point (ArdourCanvas::Item* item)
2299 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2302 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2306 ControlPointDialog d (p);
2307 d.set_position (Gtk::WIN_POS_MOUSE);
2310 if (d.run () != RESPONSE_ACCEPT) {
2314 p->line().modify_point_y (*p, d.get_y_fraction ());
2318 Editor::edit_note (ArdourCanvas::Item* item)
2320 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2323 EditNoteDialog d (&e->region_view(), e);
2324 d.set_position (Gtk::WIN_POS_MOUSE);
2332 Editor::visible_order_range (int* low, int* high) const
2334 *low = TimeAxisView::max_order ();
2337 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2339 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2341 if (!rtv->hidden()) {
2343 if (*high < rtv->order()) {
2344 *high = rtv->order ();
2347 if (*low > rtv->order()) {
2348 *low = rtv->order ();
2355 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2357 /* Either add to or set the set the region selection, unless
2358 this is an alignment click (control used)
2361 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2362 TimeAxisView* tv = &rv.get_time_axis_view();
2363 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2365 if (rtv && rtv->is_track()) {
2366 speed = rtv->track()->speed();
2369 framepos_t where = get_preferred_edit_position();
2373 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2375 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2377 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2379 align_region (rv.region(), End, (framepos_t) (where * speed));
2383 align_region (rv.region(), Start, (framepos_t) (where * speed));
2390 Editor::collect_new_region_view (RegionView* rv)
2392 latest_regionviews.push_back (rv);
2396 Editor::collect_and_select_new_region_view (RegionView* rv)
2399 latest_regionviews.push_back (rv);
2403 Editor::cancel_selection ()
2405 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2406 (*i)->hide_selection ();
2409 selection->clear ();
2410 clicked_selection = 0;
2415 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2417 RegionView* rv = clicked_regionview;
2419 /* Choose action dependant on which button was pressed */
2420 switch (event->button.button) {
2422 begin_reversible_command (_("start point trim"));
2424 if (selection->selected (rv)) {
2425 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2426 i != selection->regions.by_layer().end(); ++i)
2429 cerr << "region view contains null region" << endl;
2432 if (!(*i)->region()->locked()) {
2433 (*i)->region()->clear_changes ();
2434 (*i)->region()->trim_front (new_bound);
2435 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2440 if (!rv->region()->locked()) {
2441 rv->region()->clear_changes ();
2442 rv->region()->trim_front (new_bound);
2443 _session->add_command(new StatefulDiffCommand (rv->region()));
2447 commit_reversible_command();
2451 begin_reversible_command (_("End point trim"));
2453 if (selection->selected (rv)) {
2455 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2457 if (!(*i)->region()->locked()) {
2458 (*i)->region()->clear_changes();
2459 (*i)->region()->trim_end (new_bound);
2460 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2466 if (!rv->region()->locked()) {
2467 rv->region()->clear_changes ();
2468 rv->region()->trim_end (new_bound);
2469 _session->add_command (new StatefulDiffCommand (rv->region()));
2473 commit_reversible_command();
2482 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2487 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2488 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2492 Location* location = find_location_from_marker (marker, is_start);
2493 location->set_hidden (true, this);
2498 Editor::reposition_zoom_rect (framepos_t start, framepos_t end)
2500 double x1 = frame_to_pixel (start);
2501 double x2 = frame_to_pixel (end);
2502 double y2 = full_canvas_height - 1.0;
2504 zoom_rect->property_x1() = x1;
2505 zoom_rect->property_y1() = 1.0;
2506 zoom_rect->property_x2() = x2;
2507 zoom_rect->property_y2() = y2;
2512 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2514 using namespace Gtkmm2ext;
2516 ArdourPrompter prompter (false);
2518 prompter.set_prompt (_("Name for region:"));
2519 prompter.set_initial_text (clicked_regionview->region()->name());
2520 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2521 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2522 prompter.show_all ();
2523 switch (prompter.run ()) {
2524 case Gtk::RESPONSE_ACCEPT:
2526 prompter.get_result(str);
2528 clicked_regionview->region()->set_name (str);
2537 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2539 /* no brushing without a useful snap setting */
2541 switch (_snap_mode) {
2543 return; /* can't work because it allows region to be placed anywhere */
2548 switch (_snap_type) {
2556 /* don't brush a copy over the original */
2558 if (pos == rv->region()->position()) {
2562 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2564 if (rtv == 0 || !rtv->is_track()) {
2568 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2569 double speed = rtv->track()->speed();
2571 playlist->clear_changes ();
2572 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2573 playlist->add_region (new_region, (framepos_t) (pos * speed));
2574 _session->add_command (new StatefulDiffCommand (playlist));
2576 // playlist is frozen, so we have to update manually XXX this is disgusting
2578 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2582 Editor::track_height_step_timeout ()
2584 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2585 current_stepping_trackview = 0;
2592 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2594 assert (region_view);
2596 if (!region_view->region()->playlist()) {
2600 _region_motion_group->raise_to_top ();
2602 if (Config->get_edit_mode() == Splice) {
2603 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2605 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2606 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false));
2609 /* sync the canvas to what we think is its current state */
2610 update_canvas_now();
2614 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2616 assert (region_view);
2618 if (!region_view->region()->playlist()) {
2622 _region_motion_group->raise_to_top ();
2624 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2625 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true));
2629 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2631 assert (region_view);
2633 if (!region_view->region()->playlist()) {
2637 if (Config->get_edit_mode() == Splice) {
2641 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2642 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), true, false));
2644 begin_reversible_command (Operations::drag_region_brush);
2647 /** Start a grab where a time range is selected, track(s) are selected, and the
2648 * user clicks and drags a region with a modifier in order to create a new region containing
2649 * the section of the clicked region that lies within the time range.
2652 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2654 if (clicked_regionview == 0) {
2658 /* lets try to create new Region for the selection */
2660 vector<boost::shared_ptr<Region> > new_regions;
2661 create_region_from_selection (new_regions);
2663 if (new_regions.empty()) {
2667 /* XXX fix me one day to use all new regions */
2669 boost::shared_ptr<Region> region (new_regions.front());
2671 /* add it to the current stream/playlist.
2673 tricky: the streamview for the track will add a new regionview. we will
2674 catch the signal it sends when it creates the regionview to
2675 set the regionview we want to then drag.
2678 latest_regionviews.clear();
2679 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2681 /* A selection grab currently creates two undo/redo operations, one for
2682 creating the new region and another for moving it.
2685 begin_reversible_command (Operations::selection_grab);
2687 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2689 playlist->clear_changes ();
2690 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2691 _session->add_command(new StatefulDiffCommand (playlist));
2693 commit_reversible_command ();
2697 if (latest_regionviews.empty()) {
2698 /* something went wrong */
2702 /* we need to deselect all other regionviews, and select this one
2703 i'm ignoring undo stuff, because the region creation will take care of it
2705 selection->set (latest_regionviews);
2707 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2713 if (_drags->active ()) {
2716 selection->clear ();
2721 Editor::set_internal_edit (bool yn)
2723 if (_internal_editing == yn) {
2727 _internal_editing = yn;
2730 pre_internal_mouse_mode = mouse_mode;
2731 pre_internal_snap_type = _snap_type;
2732 pre_internal_snap_mode = _snap_mode;
2734 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2735 (*i)->enter_internal_edit_mode ();
2738 set_snap_to (internal_snap_type);
2739 set_snap_mode (internal_snap_mode);
2743 internal_snap_mode = _snap_mode;
2744 internal_snap_type = _snap_type;
2746 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2747 (*i)->leave_internal_edit_mode ();
2750 if (mouse_mode == MouseDraw && pre_internal_mouse_mode != MouseDraw) {
2751 /* we were drawing .. flip back to something sensible */
2752 set_mouse_mode (pre_internal_mouse_mode);
2755 set_snap_to (pre_internal_snap_type);
2756 set_snap_mode (pre_internal_snap_mode);
2759 set_canvas_cursor ();
2762 /** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
2763 * used by the `join object/range' tool mode.
2766 Editor::update_join_object_range_location (double /*x*/, double y)
2768 /* XXX: actually, this decides based on whether the mouse is in the top
2769 or bottom half of a the waveform part RouteTimeAxisView;
2771 Note that entered_{track,regionview} is not always setup (e.g. if
2772 the mouse is over a TimeSelection), and to get a Region
2773 that we're over requires searching the playlist.
2776 if (!smart_mode_action->get_active() || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
2777 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2781 if (mouse_mode == MouseObject) {
2782 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2783 } else if (mouse_mode == MouseRange) {
2784 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2787 /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
2788 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y + vertical_adjustment.get_value() - canvas_timebars_vsize);
2792 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2797 rtv->canvas_display()->w2i (cx, cy);
2799 double const c = cy / (rtv->view()->child_height() - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE);
2801 double const f = modf (c, &d);
2803 _join_object_range_state = f < 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2809 Editor::effective_mouse_mode () const
2811 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2813 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2821 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2823 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2826 e->region_view().delete_note (e->note ());
2830 Editor::set_canvas_cursor_for_region_view (double x, RegionView* rv)
2834 ArdourCanvas::Group* g = rv->get_canvas_group ();
2835 ArdourCanvas::Group* p = g->get_parent_group ();
2837 /* Compute x in region view parent coordinates */
2841 double x1, x2, y1, y2;
2842 g->get_bounds (x1, y1, x2, y2);
2844 /* Halfway across the region */
2845 double const h = (x1 + x2) / 2;
2847 Trimmable::CanTrim ct = rv->region()->can_trim ();
2849 if (ct & Trimmable::FrontTrimEarlier) {
2850 set_canvas_cursor (_cursors->left_side_trim);
2852 set_canvas_cursor (_cursors->left_side_trim_right_only);
2855 if (ct & Trimmable::EndTrimLater) {
2856 set_canvas_cursor (_cursors->right_side_trim);
2858 set_canvas_cursor (_cursors->right_side_trim_left_only);
2863 /** Obtain the pointer position in world coordinates */
2865 Editor::get_pointer_position (double& x, double& y) const
2868 track_canvas->get_pointer (px, py);
2869 track_canvas->window_to_world (px, py, x, y);