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.
29 #include "pbd/error.h"
30 #include "pbd/enumwriter.h"
31 #include "pbd/memento_command.h"
32 #include "pbd/basename.h"
33 #include "pbd/stateful_diff_command.h"
35 #include "gtkmm2ext/bindings.h"
36 #include "gtkmm2ext/utils.h"
37 #include "gtkmm2ext/tearoff.h"
39 #include "canvas/canvas.h"
41 #include "ardour/audioregion.h"
42 #include "ardour/operations.h"
43 #include "ardour/playlist.h"
44 #include "ardour/profile.h"
45 #include "ardour/region_factory.h"
46 #include "ardour/route.h"
47 #include "ardour/session.h"
48 #include "ardour/types.h"
50 #include "ardour_ui.h"
53 #include "time_axis_view.h"
54 #include "audio_time_axis.h"
55 #include "audio_region_view.h"
56 #include "midi_region_view.h"
58 #include "streamview.h"
59 #include "region_gain_line.h"
60 #include "automation_time_axis.h"
61 #include "control_point.h"
63 #include "selection.h"
66 #include "rgb_macros.h"
67 #include "control_point_dialog.h"
68 #include "editor_drag.h"
69 #include "automation_region_view.h"
70 #include "edit_note_dialog.h"
71 #include "mouse_cursors.h"
72 #include "editor_cursors.h"
73 #include "verbose_cursor.h"
79 using namespace ARDOUR;
82 using namespace Editing;
83 using Gtkmm2ext::Keyboard;
86 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
88 /* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only
89 pays attentions to subwindows. this means that menu windows are ignored, and
90 if the pointer is in a menu, the return window from the call will be the
91 the regular subwindow *under* the menu.
93 this matters quite a lot if the pointer is moving around in a menu that overlaps
94 the track canvas because we will believe that we are within the track canvas
95 when we are not. therefore, we track enter/leave events for the track canvas
96 and allow that to override the result of gdk_window_get_pointer().
99 if (!within_track_canvas) {
104 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->_track_canvas->get_window();
106 if (!canvas_window) {
110 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
112 if (!pointer_window) {
116 if (pointer_window != canvas_window) {
117 in_track_canvas = false;
121 in_track_canvas = true;
124 event.type = GDK_BUTTON_RELEASE;
128 where = window_event_sample (&event, 0, 0);
134 Editor::window_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
136 ArdourCanvas::Duple d;
138 if (!gdk_event_get_coords (event, &d.x, &d.y)) {
142 /* event coordinates are in window units, so convert to canvas
145 d = _track_canvas->window_to_canvas (d);
155 return pixel_to_sample (d.x);
159 Editor::canvas_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
164 /* event coordinates are already in canvas units */
166 if (!gdk_event_get_coords (event, &x, &y)) {
167 cerr << "!NO c COORDS for event type " << event->type << endl;
179 /* note that pixel_to_sample_from_event() never returns less than zero, so even if the pixel
180 position is negative (as can be the case with motion events in particular),
181 the frame location is always positive.
184 return pixel_to_sample_from_event (x);
188 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
190 boost::shared_ptr<Trimmable> st = _trimmable.lock();
192 if (!st || st == t) {
198 Editor::set_current_movable (boost::shared_ptr<Movable> m)
200 boost::shared_ptr<Movable> sm = _movable.lock();
202 if (!sm || sm != m) {
208 Editor::mouse_mode_object_range_toggled()
210 MouseMode m = mouse_mode;
212 Glib::RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
214 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
217 set_mouse_mode(m, true); //call this so the button styles can get updated
220 static Glib::RefPtr<Action>
221 get_mouse_mode_action(MouseMode m)
225 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
227 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
229 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
231 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
233 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
235 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-content"));
237 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
239 return Glib::RefPtr<Action>();
243 Editor::set_mouse_mode (MouseMode m, bool force)
245 if (_drags->active ()) {
249 if (!force && m == mouse_mode) {
253 if (ARDOUR::Profile->get_mixbus()) {
254 if ( m == MouseCut) m = MouseObject;
257 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
258 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
260 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
261 tact->set_active (false);
262 tact->set_active (true);
264 //NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting
268 Editor::mouse_mode_toggled (MouseMode m)
270 if (ARDOUR::Profile->get_mixbus()) {
271 if ( m == MouseCut) m = MouseObject;
274 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
275 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
277 if (!tact->get_active()) {
278 /* this was just the notification that the old mode has been
279 * left. we'll get called again with the new mode active in a
285 if (_session && mouse_mode == MouseAudition) {
286 /* stop transport and reset default speed to avoid oddness with
288 _session->request_transport_speed (0.0, true);
291 const bool was_internal = internal_editing();
295 /* Switch snap type/mode if we're moving to/from an internal tool. Note
296 this must toggle the actions and not call set_snap_*() directly,
297 otherwise things get out of sync and the combo box stops working. */
298 if (!was_internal && internal_editing()) {
299 snap_type_action(internal_snap_type)->set_active(true);
300 snap_mode_action(internal_snap_mode)->set_active(true);
301 } else if (was_internal && !internal_editing()) {
302 snap_type_action(pre_internal_snap_type)->set_active(true);
303 snap_mode_action(pre_internal_snap_mode)->set_active(true);
308 /* this should generate a new enter event which will
309 trigger the appropiate cursor.
313 _track_canvas->re_enter ();
316 set_gain_envelope_visibility ();
318 update_time_selection_display ();
320 update_all_enter_cursors ();
322 MouseModeChanged (); /* EMIT SIGNAL */
326 Editor::internal_editing() const
328 return mouse_mode == Editing::MouseContent || mouse_mode == Editing::MouseDraw;
332 Editor::update_time_selection_display ()
334 switch (mouse_mode) {
336 selection->clear_objects ();
337 selection->ClearMidiNoteSelection (); /* EMIT SIGNAL */
340 selection->clear_time ();
341 selection->clear_tracks ();
342 selection->ClearMidiNoteSelection (); /* EMIT SIGNAL */
345 /* Clear regions, but not time or tracks, since that
346 would destroy the range selection rectangle, which we need to stick
347 around for AutomationRangeDrag. */
348 selection->clear_regions ();
349 selection->clear_playlists ();
352 /* This handles internal edit.
353 Clear everything except points and notes.
355 selection->clear_regions();
356 selection->clear_lines();
357 selection->clear_playlists ();
359 selection->clear_time ();
360 selection->clear_tracks ();
364 /* We probably want to keep region selection */
365 selection->clear_points ();
366 selection->clear_lines();
367 selection->clear_playlists ();
369 selection->clear_time ();
370 selection->clear_tracks ();
374 /*Don't lose lines or points if no action in this mode */
375 selection->clear_regions ();
376 selection->clear_playlists ();
377 selection->clear_time ();
378 selection->clear_tracks ();
382 /*Clear everything */
383 selection->clear_objects();
384 selection->clear_time ();
385 selection->clear_tracks ();
391 Editor::step_mouse_mode (bool next)
393 const int n_mouse_modes = (int)MouseContent + 1;
394 int current = (int)current_mouse_mode();
396 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
398 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
403 Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemType item_type)
405 /* in object/audition/timefx/gain-automation mode,
406 any button press sets the selection if the object
407 can be selected. this is a bit of hack, because
408 we want to avoid this if the mouse operation is a
411 note: not dbl-click or triple-click
413 Also note that there is no region selection in internal edit mode, otherwise
414 for operations operating on the selection (e.g. cut) it is not obvious whether
415 to cut notes or regions.
418 MouseMode eff_mouse_mode = effective_mouse_mode ();
420 if (eff_mouse_mode == MouseCut) {
421 /* never change selection in cut mode */
425 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
426 /* context clicks are always about object properties, even if
427 we're in range mode within smart mode.
429 eff_mouse_mode = MouseObject;
432 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
433 if (get_smart_mode()) {
435 case FadeInHandleItem:
436 case FadeInTrimHandleItem:
437 case FadeOutHandleItem:
438 case FadeOutTrimHandleItem:
439 eff_mouse_mode = MouseObject;
446 if (((mouse_mode != MouseObject) &&
447 (mouse_mode != MouseAudition || item_type != RegionItem) &&
448 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
449 (mouse_mode != MouseDraw) &&
450 (mouse_mode != MouseContent || item_type == RegionItem)) ||
451 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
455 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
457 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
459 /* almost no selection action on modified button-2 or button-3 events */
461 if (item_type != RegionItem && event->button.button != 2) {
467 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
468 bool press = (event->type == GDK_BUTTON_PRESS);
471 _mouse_changed_selection = false;
477 if (eff_mouse_mode != MouseRange) {
478 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
480 /* don't change the selection unless the
481 clicked track is not currently selected. if
482 so, "collapse" the selection to just this
485 if (!selection->selected (clicked_axisview)) {
486 set_selected_track_as_side_effect (Selection::Set);
490 if (eff_mouse_mode != MouseRange) {
491 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
496 case RegionViewNameHighlight:
498 case LeftFrameHandle:
499 case RightFrameHandle:
500 case FadeInHandleItem:
501 case FadeInTrimHandleItem:
503 case FadeOutHandleItem:
504 case FadeOutTrimHandleItem:
506 case StartCrossFadeItem:
507 case EndCrossFadeItem:
508 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
509 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
510 } else if (event->type == GDK_BUTTON_PRESS) {
511 set_selected_track_as_side_effect (op);
515 case ControlPointItem:
516 /* for object/track exclusivity, we don't call set_selected_track_as_side_effect (op); */
518 if (eff_mouse_mode != MouseRange) {
519 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
524 /* for context click, select track */
525 if (event->button.button == 3) {
526 selection->clear_tracks ();
527 set_selected_track_as_side_effect (op);
529 /* We won't get a release.*/
530 begin_reversible_selection_op (X_("Button 3 Menu Select"));
531 commit_reversible_selection_op ();
535 case AutomationTrackItem:
536 set_selected_track_as_side_effect (op);
543 if ((!press) && _mouse_changed_selection) {
544 begin_reversible_selection_op (X_("Button Selection"));
545 commit_reversible_selection_op ();
546 _mouse_changed_selection = false;
551 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
553 /* single mouse clicks on any of these item types operate
554 independent of mouse mode, mostly because they are
555 not on the main track canvas or because we want
559 NoteBase* note = NULL;
562 case PlayheadCursorItem:
563 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
567 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
568 hide_marker (item, event);
570 _drags->set (new MarkerDrag (this, item), event);
574 case TempoMarkerItem:
577 new TempoMarkerDrag (
580 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
587 case MeterMarkerItem:
590 new MeterMarkerDrag (
593 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
601 _drags->set (new VideoTimeLineDrag (this, item), event);
608 case TimecodeRulerItem:
609 case SamplesRulerItem:
610 case MinsecRulerItem:
612 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
613 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
619 case RangeMarkerBarItem:
620 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
621 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
622 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
623 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
625 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
630 case CdMarkerBarItem:
631 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
632 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
634 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
639 case TransportMarkerBarItem:
640 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
641 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
643 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
652 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
653 /* special case: allow trim of range selections in joined object mode;
654 in theory eff should equal MouseRange in this case, but it doesn't
655 because entering the range selection canvas item results in entered_regionview
656 being set to 0, so update_join_object_range_location acts as if we aren't
659 if (item_type == StartSelectionTrimItem) {
660 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
661 } else if (item_type == EndSelectionTrimItem) {
662 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
666 Editing::MouseMode eff = effective_mouse_mode ();
668 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
669 if (get_smart_mode()) {
671 case FadeInHandleItem:
672 case FadeInTrimHandleItem:
673 case FadeOutHandleItem:
674 case FadeOutTrimHandleItem:
685 case StartSelectionTrimItem:
686 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
689 case EndSelectionTrimItem:
690 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
694 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
695 start_selection_grab (item, event);
697 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
698 /* grab selection for moving */
699 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
701 /* this was debated, but decided the more common action was to
702 make a new selection */
703 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
708 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
709 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
711 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
716 case RegionViewNameHighlight:
717 if (!clicked_regionview->region()->locked()) {
718 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
724 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
725 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
727 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
736 case FadeInHandleItem:
737 case FadeOutHandleItem:
738 case LeftFrameHandle:
739 case RightFrameHandle:
740 case FeatureLineItem:
741 case RegionViewNameHighlight:
744 case AutomationTrackItem:
745 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
756 /* Existing note: allow trimming/motion */
757 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
758 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
759 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
761 _drags->set (new NoteDrag (this, item), event);
767 _drags->set (new LineDrag (this, item), event);
771 case ControlPointItem:
772 _drags->set (new ControlPointDrag (this, item), event);
776 case AutomationLineItem:
777 _drags->set (new LineDrag (this, item), event);
782 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
785 case AutomationTrackItem:
786 /* rubberband drag to select automation points */
787 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
792 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
793 /* rubberband drag to select automation points */
794 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
805 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
806 event->type == GDK_BUTTON_PRESS) {
808 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
810 } else if (event->type == GDK_BUTTON_PRESS) {
813 case FadeInHandleItem:
815 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
819 case FadeOutHandleItem:
821 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
825 case StartCrossFadeItem:
826 case EndCrossFadeItem:
827 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
828 // if (!clicked_regionview->region()->locked()) {
829 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
834 case FeatureLineItem:
836 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
837 remove_transient(item);
841 _drags->set (new FeatureLineDrag (this, item), event);
847 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
848 /* click on an automation region view; do nothing here and let the ARV's signal handler
854 /* click on a normal region view */
855 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
856 add_region_copy_drag (item, event, clicked_regionview);
857 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
858 add_region_brush_drag (item, event, clicked_regionview);
860 add_region_drag (item, event, clicked_regionview);
864 _drags->start_grab (event);
868 case RegionViewNameHighlight:
869 case LeftFrameHandle:
870 case RightFrameHandle:
871 if (!clicked_regionview->region()->locked()) {
872 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
877 case FadeInTrimHandleItem:
878 case FadeOutTrimHandleItem:
879 if (!clicked_regionview->region()->locked()) {
880 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
887 /* rename happens on edit clicks */
888 if (clicked_regionview->get_name_highlight()) {
889 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
895 case ControlPointItem:
896 _drags->set (new ControlPointDrag (this, item), event);
900 case AutomationLineItem:
901 _drags->set (new LineDrag (this, item), event);
906 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
909 case AutomationTrackItem:
911 TimeAxisView* parent = clicked_axisview->get_parent ();
912 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
914 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
916 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
918 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
919 if (pl->n_regions() == 0) {
920 /* Parent has no regions; create one so that we have somewhere to put automation */
921 _drags->set (new RegionCreateDrag (this, item, parent), event);
923 /* See if there's a region before the click that we can extend, and extend it if so */
924 framepos_t const t = canvas_event_sample (event);
925 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
927 _drags->set (new RegionCreateDrag (this, item, parent), event);
929 prev->set_length (t - prev->position ());
933 /* rubberband drag to select automation points */
934 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
958 _drags->set (new LineDrag (this, item), event);
961 case ControlPointItem:
962 _drags->set (new ControlPointDrag (this, item), event);
968 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
969 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
970 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
971 event, _cursors->up_down);
973 double const y = event->button.y;
974 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
976 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
978 /* smart "join" mode: drag automation */
979 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
987 case AutomationLineItem:
988 _drags->set (new LineDrag (this, item), event);
992 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
993 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
994 /* Note is big and pointer is near the end, trim */
995 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
998 _drags->set (new NoteDrag (this, item), event);
1005 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1006 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1017 if (item_type == NoteItem) {
1018 /* resize-drag notes */
1019 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1020 if (note->big_enough_to_trim()) {
1021 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1025 } else if (clicked_regionview) {
1027 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1033 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1034 scrub_reversals = 0;
1035 scrub_reverse_distance = 0;
1036 last_scrub_x = event->button.x;
1037 scrubbing_direction = 0;
1049 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1051 Editing::MouseMode const eff = effective_mouse_mode ();
1054 switch (item_type) {
1056 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1057 add_region_copy_drag (item, event, clicked_regionview);
1059 add_region_drag (item, event, clicked_regionview);
1061 _drags->start_grab (event);
1064 case ControlPointItem:
1065 _drags->set (new ControlPointDrag (this, item), event);
1073 switch (item_type) {
1074 case RegionViewNameHighlight:
1075 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1079 case LeftFrameHandle:
1080 case RightFrameHandle:
1081 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1085 case RegionViewName:
1086 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1100 /* relax till release */
1112 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1114 if (event->type == GDK_2BUTTON_PRESS) {
1115 _drags->mark_double_click ();
1116 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1120 if (event->type != GDK_BUTTON_PRESS) {
1124 _track_canvas->grab_focus();
1126 if (_session && _session->actively_recording()) {
1130 button_selection (item, event, item_type);
1132 if (!_drags->active () &&
1133 (Keyboard::is_delete_event (&event->button) ||
1134 Keyboard::is_context_menu_event (&event->button) ||
1135 Keyboard::is_edit_event (&event->button))) {
1137 /* handled by button release */
1141 //not rolling, range mode click + join_play_range : locate the PH here
1142 if ( !_drags->active () && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && ARDOUR_UI::config()->get_follow_edits() ) {
1143 framepos_t where = canvas_event_sample (event);
1145 _session->request_locate (where, false);
1148 switch (event->button.button) {
1150 return button_press_handler_1 (item, event, item_type);
1154 return button_press_handler_2 (item, event, item_type);
1161 return button_press_dispatch (&event->button);
1170 Editor::button_press_dispatch (GdkEventButton* ev)
1172 /* this function is intended only for buttons 4 and above.
1175 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1176 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1180 Editor::button_release_dispatch (GdkEventButton* ev)
1182 /* this function is intended only for buttons 4 and above.
1185 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1186 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1190 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1192 framepos_t where = canvas_event_sample (event);
1193 AutomationTimeAxisView* atv = 0;
1195 _press_cursor_ctx.reset();
1197 /* no action if we're recording */
1199 if (_session && _session->actively_recording()) {
1203 bool were_dragging = false;
1205 if (!Keyboard::is_context_menu_event (&event->button)) {
1207 /* see if we're finishing a drag */
1209 if (_drags->active ()) {
1210 bool const r = _drags->end_grab (event);
1212 /* grab dragged, so do nothing else */
1216 were_dragging = true;
1219 update_region_layering_order_editor ();
1222 /* edit events get handled here */
1224 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1225 switch (item_type) {
1227 show_region_properties ();
1230 case TempoMarkerItem: {
1231 ArdourMarker* marker;
1232 TempoMarker* tempo_marker;
1234 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1235 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1236 abort(); /*NOTREACHED*/
1239 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1240 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1241 abort(); /*NOTREACHED*/
1244 edit_tempo_marker (*tempo_marker);
1248 case MeterMarkerItem: {
1249 ArdourMarker* marker;
1250 MeterMarker* meter_marker;
1252 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1253 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1254 abort(); /*NOTREACHED*/
1257 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1258 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1259 abort(); /*NOTREACHED*/
1261 edit_meter_marker (*meter_marker);
1265 case RegionViewName:
1266 if (clicked_regionview->name_active()) {
1267 return mouse_rename_region (item, event);
1271 case ControlPointItem:
1272 edit_control_point (item);
1281 /* context menu events get handled here */
1282 if (Keyboard::is_context_menu_event (&event->button)) {
1284 context_click_event = *event;
1286 if (!_drags->active ()) {
1288 /* no matter which button pops up the context menu, tell the menu
1289 widget to use button 1 to drive menu selection.
1292 switch (item_type) {
1294 case FadeInHandleItem:
1295 case FadeInTrimHandleItem:
1296 case StartCrossFadeItem:
1297 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1301 case FadeOutHandleItem:
1302 case FadeOutTrimHandleItem:
1303 case EndCrossFadeItem:
1304 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1307 case LeftFrameHandle:
1308 case RightFrameHandle:
1312 popup_track_context_menu (1, event->button.time, item_type, false);
1316 case RegionViewNameHighlight:
1317 case RegionViewName:
1318 popup_track_context_menu (1, event->button.time, item_type, false);
1322 popup_track_context_menu (1, event->button.time, item_type, true);
1325 case AutomationTrackItem:
1326 popup_track_context_menu (1, event->button.time, item_type, false);
1330 case RangeMarkerBarItem:
1331 case TransportMarkerBarItem:
1332 case CdMarkerBarItem:
1336 case TimecodeRulerItem:
1337 case SamplesRulerItem:
1338 case MinsecRulerItem:
1340 popup_ruler_menu (where, item_type);
1344 marker_context_menu (&event->button, item);
1347 case TempoMarkerItem:
1348 tempo_or_meter_marker_context_menu (&event->button, item);
1351 case MeterMarkerItem:
1352 tempo_or_meter_marker_context_menu (&event->button, item);
1355 case CrossfadeViewItem:
1356 popup_track_context_menu (1, event->button.time, item_type, false);
1359 case ControlPointItem:
1360 popup_control_point_context_menu (item, event);
1364 if (internal_editing()) {
1365 popup_note_context_menu (item, event);
1377 /* delete events get handled here */
1379 Editing::MouseMode const eff = effective_mouse_mode ();
1381 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1383 switch (item_type) {
1384 case TempoMarkerItem:
1385 remove_tempo_marker (item);
1388 case MeterMarkerItem:
1389 remove_meter_marker (item);
1393 remove_marker (*item, event);
1397 if (eff == MouseObject) {
1398 remove_clicked_region ();
1402 case ControlPointItem:
1403 remove_control_point (item);
1407 remove_midi_note (item, event);
1416 switch (event->button.button) {
1419 switch (item_type) {
1420 /* see comments in button_press_handler */
1421 case PlayheadCursorItem:
1424 case AutomationLineItem:
1425 case StartSelectionTrimItem:
1426 case EndSelectionTrimItem:
1430 if (!_dragging_playhead) {
1431 snap_to_with_modifier (where, event, RoundNearest, true);
1432 mouse_add_new_marker (where);
1436 case CdMarkerBarItem:
1437 if (!_dragging_playhead) {
1438 // if we get here then a dragged range wasn't done
1439 snap_to_with_modifier (where, event, RoundNearest, true);
1440 mouse_add_new_marker (where, true);
1445 if (!_dragging_playhead) {
1446 snap_to_with_modifier (where, event);
1447 mouse_add_new_tempo_event (where);
1452 if (!_dragging_playhead) {
1453 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1458 case TimecodeRulerItem:
1459 case SamplesRulerItem:
1460 case MinsecRulerItem:
1471 switch (item_type) {
1474 /* check that we didn't drag before releasing, since
1475 its really annoying to create new control
1476 points when doing this.
1478 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1479 if (!were_dragging && arv) {
1480 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1481 arv->add_gain_point_event (item, event, with_guard_points);
1487 case AutomationTrackItem: {
1488 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1489 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1491 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1502 if (scrubbing_direction == 0) {
1503 /* no drag, just a click */
1504 switch (item_type) {
1506 play_selected_region ();
1511 } else if (_session) {
1512 /* make sure we stop */
1513 _session->request_transport_speed (0.0);
1522 /* do any (de)selection operations that should occur on button release */
1523 button_selection (item, event, item_type);
1533 switch (item_type) {
1535 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1537 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1540 // Button2 click is unused
1555 // x_style_paste (where, 1.0);
1576 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1579 ArdourMarker * marker;
1583 /* by the time we reach here, entered_regionview and entered trackview
1584 * will have already been set as appropriate. Things are done this
1585 * way because this method isn't passed a pointer to a variable type of
1586 * thing that is entered (which may or may not be canvas item).
1587 * (e.g. the actual entered regionview)
1590 choose_canvas_cursor_on_entry (item_type);
1592 switch (item_type) {
1593 case ControlPointItem:
1594 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1595 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1598 fraction = 1.0 - (cp->get_y() / cp->line().height());
1600 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1601 _verbose_cursor->show ();
1606 if (mouse_mode == MouseDraw) {
1607 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1609 line->set_outline_color (ARDOUR_UI::config()->color ("entered gain line"));
1614 case AutomationLineItem:
1615 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1616 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1618 line->set_outline_color (ARDOUR_UI::config()->color ("entered automation line"));
1623 case AutomationTrackItem:
1624 AutomationTimeAxisView* atv;
1625 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1626 clear_entered_track = false;
1627 set_entered_track (atv);
1632 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1635 entered_marker = marker;
1636 marker->set_color_rgba (ARDOUR_UI::config()->color ("entered marker"));
1638 case MeterMarkerItem:
1639 case TempoMarkerItem:
1642 case FadeInHandleItem:
1643 case FadeInTrimHandleItem:
1644 if (mouse_mode == MouseObject) {
1645 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1647 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1648 rect->set_fill_color (rv->get_fill_color());
1653 case FadeOutHandleItem:
1654 case FadeOutTrimHandleItem:
1655 if (mouse_mode == MouseObject) {
1656 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1658 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1659 rect->set_fill_color (rv->get_fill_color ());
1664 case FeatureLineItem:
1666 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1667 line->set_outline_color (0xFF0000FF);
1676 if (entered_regionview) {
1677 entered_regionview->entered();
1686 /* third pass to handle entered track status in a comprehensible way.
1689 switch (item_type) {
1691 case AutomationLineItem:
1692 case ControlPointItem:
1693 /* these do not affect the current entered track state */
1694 clear_entered_track = false;
1697 case AutomationTrackItem:
1698 /* handled above already */
1710 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1713 ArdourMarker *marker;
1718 if (!_enter_stack.empty()) {
1719 _enter_stack.pop_back();
1722 switch (item_type) {
1723 case ControlPointItem:
1724 _verbose_cursor->hide ();
1728 case AutomationLineItem:
1729 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1731 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1733 line->set_outline_color (al->get_line_color());
1739 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1743 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1744 location_flags_changed (loc);
1747 case MeterMarkerItem:
1748 case TempoMarkerItem:
1751 case FadeInTrimHandleItem:
1752 case FadeOutTrimHandleItem:
1753 case FadeInHandleItem:
1754 case FadeOutHandleItem:
1756 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1758 rect->set_fill_color (ARDOUR_UI::config()->color ("inactive fade handle"));
1763 case AutomationTrackItem:
1766 case FeatureLineItem:
1768 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1769 line->set_outline_color (ARDOUR_UI::config()->color ("zero line"));
1781 Editor::scrub (framepos_t frame, double current_x)
1785 if (scrubbing_direction == 0) {
1787 _session->request_locate (frame, false);
1788 _session->request_transport_speed (0.1);
1789 scrubbing_direction = 1;
1793 if (last_scrub_x > current_x) {
1795 /* pointer moved to the left */
1797 if (scrubbing_direction > 0) {
1799 /* we reversed direction to go backwards */
1802 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1806 /* still moving to the left (backwards) */
1808 scrub_reversals = 0;
1809 scrub_reverse_distance = 0;
1811 delta = 0.01 * (last_scrub_x - current_x);
1812 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1816 /* pointer moved to the right */
1818 if (scrubbing_direction < 0) {
1819 /* we reversed direction to go forward */
1822 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1825 /* still moving to the right */
1827 scrub_reversals = 0;
1828 scrub_reverse_distance = 0;
1830 delta = 0.01 * (current_x - last_scrub_x);
1831 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1835 /* if there have been more than 2 opposite motion moves detected, or one that moves
1836 back more than 10 pixels, reverse direction
1839 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1841 if (scrubbing_direction > 0) {
1842 /* was forwards, go backwards */
1843 _session->request_transport_speed (-0.1);
1844 scrubbing_direction = -1;
1846 /* was backwards, go forwards */
1847 _session->request_transport_speed (0.1);
1848 scrubbing_direction = 1;
1851 scrub_reverse_distance = 0;
1852 scrub_reversals = 0;
1856 last_scrub_x = current_x;
1860 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1862 _last_motion_y = event->motion.y;
1864 if (event->motion.is_hint) {
1867 /* We call this so that MOTION_NOTIFY events continue to be
1868 delivered to the canvas. We need to do this because we set
1869 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1870 the density of the events, at the expense of a round-trip
1871 to the server. Given that this will mostly occur on cases
1872 where DISPLAY = :0.0, and given the cost of what the motion
1873 event might do, its a good tradeoff.
1876 _track_canvas->get_pointer (x, y);
1879 if (current_stepping_trackview) {
1880 /* don't keep the persistent stepped trackview if the mouse moves */
1881 current_stepping_trackview = 0;
1882 step_timeout.disconnect ();
1885 if (_session && _session->actively_recording()) {
1886 /* Sorry. no dragging stuff around while we record */
1890 update_join_object_range_location (event->motion.y);
1892 if (_drags->active ()) {
1893 return _drags->motion_handler (event, from_autoscroll);
1900 Editor::can_remove_control_point (ArdourCanvas::Item* item)
1902 ControlPoint* control_point;
1904 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1905 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1906 abort(); /*NOTREACHED*/
1909 AutomationLine& line = control_point->line ();
1910 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
1911 /* we shouldn't remove the first or last gain point in region gain lines */
1912 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
1921 Editor::remove_control_point (ArdourCanvas::Item* item)
1923 if (!can_remove_control_point (item)) {
1927 ControlPoint* control_point;
1929 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1930 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1931 abort(); /*NOTREACHED*/
1934 control_point->line().remove_point (*control_point);
1938 Editor::edit_control_point (ArdourCanvas::Item* item)
1940 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
1943 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1944 abort(); /*NOTREACHED*/
1947 ControlPointDialog d (p);
1949 if (d.run () != RESPONSE_ACCEPT) {
1953 p->line().modify_point_y (*p, d.get_y_fraction ());
1957 Editor::edit_notes (MidiRegionView* mrv)
1959 MidiRegionView::Selection const & s = mrv->selection();
1965 EditNoteDialog* d = new EditNoteDialog (mrv, s);
1968 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
1972 Editor::note_edit_done (int r, EditNoteDialog* d)
1979 Editor::visible_order_range (int* low, int* high) const
1981 *low = TimeAxisView::max_order ();
1984 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
1986 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
1988 if (!rtv->hidden()) {
1990 if (*high < rtv->order()) {
1991 *high = rtv->order ();
1994 if (*low > rtv->order()) {
1995 *low = rtv->order ();
2002 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2004 /* Either add to or set the set the region selection, unless
2005 this is an alignment click (control used)
2008 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2009 TimeAxisView* tv = &rv.get_time_axis_view();
2010 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2012 if (rtv && rtv->is_track()) {
2013 speed = rtv->track()->speed();
2016 framepos_t where = get_preferred_edit_position();
2020 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2022 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2024 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2026 align_region (rv.region(), End, (framepos_t) (where * speed));
2030 align_region (rv.region(), Start, (framepos_t) (where * speed));
2037 Editor::collect_new_region_view (RegionView* rv)
2039 latest_regionviews.push_back (rv);
2043 Editor::collect_and_select_new_region_view (RegionView* rv)
2046 latest_regionviews.push_back (rv);
2050 Editor::cancel_selection ()
2052 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2053 (*i)->hide_selection ();
2056 selection->clear ();
2057 clicked_selection = 0;
2061 Editor::cancel_time_selection ()
2063 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2064 (*i)->hide_selection ();
2066 selection->time.clear ();
2067 clicked_selection = 0;
2071 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2073 RegionView* rv = clicked_regionview;
2075 /* Choose action dependant on which button was pressed */
2076 switch (event->button.button) {
2078 begin_reversible_command (_("start point trim"));
2080 if (selection->selected (rv)) {
2081 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2082 i != selection->regions.by_layer().end(); ++i)
2084 if (!(*i)->region()->locked()) {
2085 (*i)->region()->clear_changes ();
2086 (*i)->region()->trim_front (new_bound);
2087 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2092 if (!rv->region()->locked()) {
2093 rv->region()->clear_changes ();
2094 rv->region()->trim_front (new_bound);
2095 _session->add_command(new StatefulDiffCommand (rv->region()));
2099 commit_reversible_command();
2103 begin_reversible_command (_("End point trim"));
2105 if (selection->selected (rv)) {
2107 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2109 if (!(*i)->region()->locked()) {
2110 (*i)->region()->clear_changes();
2111 (*i)->region()->trim_end (new_bound);
2112 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2118 if (!rv->region()->locked()) {
2119 rv->region()->clear_changes ();
2120 rv->region()->trim_end (new_bound);
2121 _session->add_command (new StatefulDiffCommand (rv->region()));
2125 commit_reversible_command();
2134 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2136 ArdourMarker* marker;
2139 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2140 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2141 abort(); /*NOTREACHED*/
2144 Location* location = find_location_from_marker (marker, is_start);
2145 location->set_hidden (true, this);
2149 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2151 using namespace Gtkmm2ext;
2153 ArdourPrompter prompter (false);
2155 prompter.set_prompt (_("Name for region:"));
2156 prompter.set_initial_text (clicked_regionview->region()->name());
2157 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2158 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2159 prompter.show_all ();
2160 switch (prompter.run ()) {
2161 case Gtk::RESPONSE_ACCEPT:
2163 prompter.get_result(str);
2165 clicked_regionview->region()->set_name (str);
2174 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2176 /* no brushing without a useful snap setting */
2178 switch (_snap_mode) {
2180 return; /* can't work because it allows region to be placed anywhere */
2185 switch (_snap_type) {
2193 /* don't brush a copy over the original */
2195 if (pos == rv->region()->position()) {
2199 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2201 if (rtv == 0 || !rtv->is_track()) {
2205 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2206 double speed = rtv->track()->speed();
2208 playlist->clear_changes ();
2209 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2210 playlist->add_region (new_region, (framepos_t) (pos * speed));
2211 _session->add_command (new StatefulDiffCommand (playlist));
2213 // playlist is frozen, so we have to update manually XXX this is disgusting
2215 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2219 Editor::track_height_step_timeout ()
2221 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2222 current_stepping_trackview = 0;
2229 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2231 assert (region_view);
2233 if (!region_view->region()->playlist()) {
2237 switch (Config->get_edit_mode()) {
2239 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2242 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2245 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2252 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2254 assert (region_view);
2256 if (!region_view->region()->playlist()) {
2260 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2264 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2266 assert (region_view);
2268 if (!region_view->region()->playlist()) {
2272 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2276 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2279 /** Start a grab where a time range is selected, track(s) are selected, and the
2280 * user clicks and drags a region with a modifier in order to create a new region containing
2281 * the section of the clicked region that lies within the time range.
2284 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2286 if (clicked_regionview == 0) {
2290 /* lets try to create new Region for the selection */
2292 vector<boost::shared_ptr<Region> > new_regions;
2293 create_region_from_selection (new_regions);
2295 if (new_regions.empty()) {
2299 /* XXX fix me one day to use all new regions */
2301 boost::shared_ptr<Region> region (new_regions.front());
2303 /* add it to the current stream/playlist.
2305 tricky: the streamview for the track will add a new regionview. we will
2306 catch the signal it sends when it creates the regionview to
2307 set the regionview we want to then drag.
2310 latest_regionviews.clear();
2311 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2313 /* A selection grab currently creates two undo/redo operations, one for
2314 creating the new region and another for moving it.
2316 begin_reversible_command (Operations::selection_grab);
2318 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2320 playlist->clear_changes ();
2321 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2322 _session->add_command(new StatefulDiffCommand (playlist));
2326 if (latest_regionviews.empty()) {
2327 /* something went wrong */
2328 abort_reversible_command ();
2332 /* we need to deselect all other regionviews, and select this one
2333 i'm ignoring undo stuff, because the region creation will take care of it
2336 selection->set (latest_regionviews);
2338 commit_reversible_command ();
2340 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2346 if (_drags->active ()) {
2349 selection->clear ();
2355 /** Update _join_object_range_state which indicate whether we are over the top
2356 * or bottom half of a route view, used by the `join object/range' tool
2357 * mode. Coordinates in canvas space.
2360 Editor::update_join_object_range_location (double y)
2362 if (!get_smart_mode()) {
2363 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2367 JoinObjectRangeState const old = _join_object_range_state;
2369 if (mouse_mode == MouseObject) {
2370 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2371 } else if (mouse_mode == MouseRange) {
2372 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2375 if (entered_regionview) {
2377 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2378 double const c = item_space.y / entered_regionview->height();
2380 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2382 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2383 if (_join_object_range_state != old && ctx) {
2384 ctx->cursor_ctx->change(which_track_cursor());
2387 } else if (entered_track) {
2389 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2391 if (entered_route_view) {
2396 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2398 double track_height = entered_route_view->view()->child_height();
2399 if (ARDOUR_UI::config()->get_show_name_highlight()) {
2400 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2402 double const c = cy / track_height;
2406 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2408 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2412 /* Other kinds of tracks use object mode */
2413 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2416 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2417 if (_join_object_range_state != old && ctx) {
2418 ctx->cursor_ctx->change(which_track_cursor());
2424 Editor::effective_mouse_mode () const
2426 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2428 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2436 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2438 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2441 e->region_view().delete_note (e->note ());
2444 /** Obtain the pointer position in canvas coordinates */
2446 Editor::get_pointer_position (double& x, double& y) const
2449 _track_canvas->get_pointer (px, py);
2450 _track_canvas->window_to_canvas (px, py, x, y);