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 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
454 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
456 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
458 /* almost no selection action on modified button-2 or button-3 events */
460 if (item_type != RegionItem && event->button.button != 2) {
466 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
467 bool press = (event->type == GDK_BUTTON_PRESS);
470 _mouse_changed_selection = false;
476 if (eff_mouse_mode != MouseRange) {
477 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
479 /* don't change the selection unless the
480 clicked track is not currently selected. if
481 so, "collapse" the selection to just this
484 if (!selection->selected (clicked_axisview)) {
485 set_selected_track_as_side_effect (Selection::Set);
489 if (eff_mouse_mode != MouseRange) {
490 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
495 case RegionViewNameHighlight:
497 case LeftFrameHandle:
498 case RightFrameHandle:
499 case FadeInHandleItem:
500 case FadeInTrimHandleItem:
502 case FadeOutHandleItem:
503 case FadeOutTrimHandleItem:
505 case StartCrossFadeItem:
506 case EndCrossFadeItem:
507 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
508 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
509 } else if (event->type == GDK_BUTTON_PRESS) {
510 set_selected_track_as_side_effect (op);
514 case ControlPointItem:
515 set_selected_track_as_side_effect (op);
516 if (eff_mouse_mode != MouseRange) {
517 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
522 /* for context click, select track */
523 if (event->button.button == 3) {
524 selection->clear_tracks ();
525 set_selected_track_as_side_effect (op);
527 /* We won't get a release.*/
528 begin_reversible_selection_op (_("Button 3 Menu Select"));
529 commit_reversible_selection_op ();
533 case AutomationTrackItem:
534 set_selected_track_as_side_effect (op);
541 if ((!press) && _mouse_changed_selection) {
542 begin_reversible_selection_op (_("Button Selection"));
543 commit_reversible_selection_op ();
544 _mouse_changed_selection = false;
549 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
551 /* single mouse clicks on any of these item types operate
552 independent of mouse mode, mostly because they are
553 not on the main track canvas or because we want
557 NoteBase* note = NULL;
560 case PlayheadCursorItem:
561 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
565 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
566 hide_marker (item, event);
568 _drags->set (new MarkerDrag (this, item), event);
572 case TempoMarkerItem:
575 new TempoMarkerDrag (
578 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
585 case MeterMarkerItem:
588 new MeterMarkerDrag (
591 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
599 _drags->set (new VideoTimeLineDrag (this, item), event);
606 case TimecodeRulerItem:
607 case SamplesRulerItem:
608 case MinsecRulerItem:
610 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
611 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
617 case RangeMarkerBarItem:
618 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
619 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
620 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
621 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
623 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
628 case CdMarkerBarItem:
629 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
630 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
632 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
637 case TransportMarkerBarItem:
638 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
639 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
641 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
650 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
651 /* special case: allow trim of range selections in joined object mode;
652 in theory eff should equal MouseRange in this case, but it doesn't
653 because entering the range selection canvas item results in entered_regionview
654 being set to 0, so update_join_object_range_location acts as if we aren't
657 if (item_type == StartSelectionTrimItem) {
658 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
659 } else if (item_type == EndSelectionTrimItem) {
660 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
664 Editing::MouseMode eff = effective_mouse_mode ();
666 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
667 if (get_smart_mode()) {
669 case FadeInHandleItem:
670 case FadeInTrimHandleItem:
671 case FadeOutHandleItem:
672 case FadeOutTrimHandleItem:
683 case StartSelectionTrimItem:
684 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
687 case EndSelectionTrimItem:
688 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
692 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
693 start_selection_grab (item, event);
695 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
696 /* grab selection for moving */
697 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
699 /* this was debated, but decided the more common action was to
700 make a new selection */
701 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
706 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
707 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
709 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
714 case RegionViewNameHighlight:
715 if (!clicked_regionview->region()->locked()) {
716 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
722 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
723 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
725 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
734 case FadeInHandleItem:
735 case FadeOutHandleItem:
736 case LeftFrameHandle:
737 case RightFrameHandle:
738 case FeatureLineItem:
739 case RegionViewNameHighlight:
742 case AutomationTrackItem:
743 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
754 /* Existing note: allow trimming/motion */
755 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
756 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
757 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
759 _drags->set (new NoteDrag (this, item), event);
764 case ControlPointItem:
765 _drags->set (new ControlPointDrag (this, item), event);
770 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
771 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
776 case AutomationTrackItem:
777 /* rubberband drag to select automation points */
778 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
783 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
784 /* rubberband drag to select automation points */
785 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
796 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
797 event->type == GDK_BUTTON_PRESS) {
799 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
801 } else if (event->type == GDK_BUTTON_PRESS) {
804 case FadeInHandleItem:
806 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
810 case FadeOutHandleItem:
812 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
816 case StartCrossFadeItem:
817 case EndCrossFadeItem:
818 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
819 // if (!clicked_regionview->region()->locked()) {
820 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
825 case FeatureLineItem:
827 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
828 remove_transient(item);
832 _drags->set (new FeatureLineDrag (this, item), event);
838 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
839 /* click on an automation region view; do nothing here and let the ARV's signal handler
845 /* click on a normal region view */
846 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
847 add_region_copy_drag (item, event, clicked_regionview);
848 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
849 add_region_brush_drag (item, event, clicked_regionview);
851 add_region_drag (item, event, clicked_regionview);
855 _drags->start_grab (event);
859 case RegionViewNameHighlight:
860 case LeftFrameHandle:
861 case RightFrameHandle:
862 if (!clicked_regionview->region()->locked()) {
863 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
868 case FadeInTrimHandleItem:
869 case FadeOutTrimHandleItem:
870 if (!clicked_regionview->region()->locked()) {
871 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
878 /* rename happens on edit clicks */
879 if (clicked_regionview->get_name_highlight()) {
880 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
886 case ControlPointItem:
887 _drags->set (new ControlPointDrag (this, item), event);
891 case AutomationLineItem:
892 _drags->set (new LineDrag (this, item), event);
897 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
900 case AutomationTrackItem:
902 TimeAxisView* parent = clicked_axisview->get_parent ();
903 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
905 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
907 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
909 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
910 if (pl->n_regions() == 0) {
911 /* Parent has no regions; create one so that we have somewhere to put automation */
912 _drags->set (new RegionCreateDrag (this, item, parent), event);
914 /* See if there's a region before the click that we can extend, and extend it if so */
915 framepos_t const t = canvas_event_sample (event);
916 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
918 _drags->set (new RegionCreateDrag (this, item, parent), event);
920 prev->set_length (t - prev->position ());
924 /* rubberband drag to select automation points */
925 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
949 _drags->set (new LineDrag (this, item), event);
952 case ControlPointItem:
953 _drags->set (new ControlPointDrag (this, item), event);
959 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
960 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
961 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
962 event, _cursors->up_down);
964 double const y = event->button.y;
965 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
967 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
969 /* smart "join" mode: drag automation */
970 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
978 case AutomationLineItem:
979 _drags->set (new LineDrag (this, item), event);
983 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
984 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
985 /* Note is big and pointer is near the end, trim */
986 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
989 _drags->set (new NoteDrag (this, item), event);
996 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
997 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1008 if (item_type == NoteItem) {
1009 /* resize-drag notes */
1010 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1011 if (note->big_enough_to_trim()) {
1012 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1016 } else if (clicked_regionview) {
1018 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1024 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1025 scrub_reversals = 0;
1026 scrub_reverse_distance = 0;
1027 last_scrub_x = event->button.x;
1028 scrubbing_direction = 0;
1040 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1042 Editing::MouseMode const eff = effective_mouse_mode ();
1045 switch (item_type) {
1047 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1048 add_region_copy_drag (item, event, clicked_regionview);
1050 add_region_drag (item, event, clicked_regionview);
1052 _drags->start_grab (event);
1055 case ControlPointItem:
1056 _drags->set (new ControlPointDrag (this, item), event);
1064 switch (item_type) {
1065 case RegionViewNameHighlight:
1066 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1070 case LeftFrameHandle:
1071 case RightFrameHandle:
1072 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1076 case RegionViewName:
1077 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1091 /* relax till release */
1103 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1105 if (event->type == GDK_2BUTTON_PRESS) {
1106 _drags->mark_double_click ();
1107 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1111 if (event->type != GDK_BUTTON_PRESS) {
1115 _track_canvas->grab_focus();
1117 if (_session && _session->actively_recording()) {
1121 button_selection (item, event, item_type);
1123 if (!_drags->active () &&
1124 (Keyboard::is_delete_event (&event->button) ||
1125 Keyboard::is_context_menu_event (&event->button) ||
1126 Keyboard::is_edit_event (&event->button))) {
1128 /* handled by button release */
1132 //not rolling, range mode click + join_play_range : locate the PH here
1133 if ( !_drags->active () && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && ARDOUR_UI::config()->get_follow_edits() ) {
1134 framepos_t where = canvas_event_sample (event);
1136 _session->request_locate (where, false);
1139 switch (event->button.button) {
1141 return button_press_handler_1 (item, event, item_type);
1145 return button_press_handler_2 (item, event, item_type);
1152 return button_press_dispatch (&event->button);
1161 Editor::button_press_dispatch (GdkEventButton* ev)
1163 /* this function is intended only for buttons 4 and above.
1166 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1167 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1171 Editor::button_release_dispatch (GdkEventButton* ev)
1173 /* this function is intended only for buttons 4 and above.
1176 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1177 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1181 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1183 framepos_t where = canvas_event_sample (event);
1184 AutomationTimeAxisView* atv = 0;
1186 _press_cursor_ctx.reset();
1188 /* no action if we're recording */
1190 if (_session && _session->actively_recording()) {
1194 bool were_dragging = false;
1196 if (!Keyboard::is_context_menu_event (&event->button)) {
1198 /* see if we're finishing a drag */
1200 if (_drags->active ()) {
1201 bool const r = _drags->end_grab (event);
1203 /* grab dragged, so do nothing else */
1207 were_dragging = true;
1210 update_region_layering_order_editor ();
1213 /* edit events get handled here */
1215 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1216 switch (item_type) {
1218 show_region_properties ();
1221 case TempoMarkerItem: {
1223 TempoMarker* tempo_marker;
1225 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
1226 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1227 abort(); /*NOTREACHED*/
1230 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1231 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1232 abort(); /*NOTREACHED*/
1235 edit_tempo_marker (*tempo_marker);
1239 case MeterMarkerItem: {
1241 MeterMarker* meter_marker;
1243 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
1244 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1245 abort(); /*NOTREACHED*/
1248 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1249 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1250 abort(); /*NOTREACHED*/
1252 edit_meter_marker (*meter_marker);
1256 case RegionViewName:
1257 if (clicked_regionview->name_active()) {
1258 return mouse_rename_region (item, event);
1262 case ControlPointItem:
1263 edit_control_point (item);
1272 /* context menu events get handled here */
1273 if (Keyboard::is_context_menu_event (&event->button)) {
1275 context_click_event = *event;
1277 if (!_drags->active ()) {
1279 /* no matter which button pops up the context menu, tell the menu
1280 widget to use button 1 to drive menu selection.
1283 switch (item_type) {
1285 case FadeInHandleItem:
1286 case FadeInTrimHandleItem:
1287 case StartCrossFadeItem:
1288 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1292 case FadeOutHandleItem:
1293 case FadeOutTrimHandleItem:
1294 case EndCrossFadeItem:
1295 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1298 case LeftFrameHandle:
1299 case RightFrameHandle:
1303 popup_track_context_menu (1, event->button.time, item_type, false);
1307 case RegionViewNameHighlight:
1308 case RegionViewName:
1309 popup_track_context_menu (1, event->button.time, item_type, false);
1313 popup_track_context_menu (1, event->button.time, item_type, true);
1316 case AutomationTrackItem:
1317 popup_track_context_menu (1, event->button.time, item_type, false);
1321 case RangeMarkerBarItem:
1322 case TransportMarkerBarItem:
1323 case CdMarkerBarItem:
1327 case TimecodeRulerItem:
1328 case SamplesRulerItem:
1329 case MinsecRulerItem:
1331 popup_ruler_menu (where, item_type);
1335 marker_context_menu (&event->button, item);
1338 case TempoMarkerItem:
1339 tempo_or_meter_marker_context_menu (&event->button, item);
1342 case MeterMarkerItem:
1343 tempo_or_meter_marker_context_menu (&event->button, item);
1346 case CrossfadeViewItem:
1347 popup_track_context_menu (1, event->button.time, item_type, false);
1350 case ControlPointItem:
1351 popup_control_point_context_menu (item, event);
1355 if (internal_editing()) {
1356 popup_note_context_menu (item, event);
1368 /* delete events get handled here */
1370 Editing::MouseMode const eff = effective_mouse_mode ();
1372 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1374 switch (item_type) {
1375 case TempoMarkerItem:
1376 remove_tempo_marker (item);
1379 case MeterMarkerItem:
1380 remove_meter_marker (item);
1384 remove_marker (*item, event);
1388 if (eff == MouseObject) {
1389 remove_clicked_region ();
1393 case ControlPointItem:
1394 remove_control_point (item);
1398 remove_midi_note (item, event);
1407 switch (event->button.button) {
1410 switch (item_type) {
1411 /* see comments in button_press_handler */
1412 case PlayheadCursorItem:
1415 case AutomationLineItem:
1416 case StartSelectionTrimItem:
1417 case EndSelectionTrimItem:
1421 if (!_dragging_playhead) {
1422 snap_to_with_modifier (where, event, RoundNearest, true);
1423 mouse_add_new_marker (where);
1427 case CdMarkerBarItem:
1428 if (!_dragging_playhead) {
1429 // if we get here then a dragged range wasn't done
1430 snap_to_with_modifier (where, event, RoundNearest, true);
1431 mouse_add_new_marker (where, true);
1436 if (!_dragging_playhead) {
1437 snap_to_with_modifier (where, event);
1438 mouse_add_new_tempo_event (where);
1443 if (!_dragging_playhead) {
1444 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1449 case TimecodeRulerItem:
1450 case SamplesRulerItem:
1451 case MinsecRulerItem:
1462 switch (item_type) {
1465 /* check that we didn't drag before releasing, since
1466 its really annoying to create new control
1467 points when doing this.
1469 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1470 if (!were_dragging && arv) {
1471 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1472 arv->add_gain_point_event (item, event, with_guard_points);
1478 case AutomationTrackItem: {
1479 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1480 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1482 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1493 if (scrubbing_direction == 0) {
1494 /* no drag, just a click */
1495 switch (item_type) {
1497 play_selected_region ();
1503 /* make sure we stop */
1504 _session->request_transport_speed (0.0);
1513 /* do any (de)selection operations that should occur on button release */
1514 button_selection (item, event, item_type);
1524 switch (item_type) {
1526 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1528 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1531 // Button2 click is unused
1546 // x_style_paste (where, 1.0);
1567 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1574 /* by the time we reach here, entered_regionview and entered trackview
1575 * will have already been set as appropriate. Things are done this
1576 * way because this method isn't passed a pointer to a variable type of
1577 * thing that is entered (which may or may not be canvas item).
1578 * (e.g. the actual entered regionview)
1581 choose_canvas_cursor_on_entry (item_type);
1583 switch (item_type) {
1584 case ControlPointItem:
1585 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1586 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1589 fraction = 1.0 - (cp->get_y() / cp->line().height());
1591 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1592 _verbose_cursor->show ();
1597 if (mouse_mode == MouseDraw) {
1598 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1600 line->set_outline_color (ARDOUR_UI::config()->color ("entered gain line"));
1605 case AutomationLineItem:
1606 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1607 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1609 line->set_outline_color (ARDOUR_UI::config()->color ("entered automation line"));
1614 case AutomationTrackItem:
1615 AutomationTimeAxisView* atv;
1616 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1617 clear_entered_track = false;
1618 set_entered_track (atv);
1623 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1626 entered_marker = marker;
1627 marker->set_color_rgba (ARDOUR_UI::config()->color ("entered marker"));
1629 case MeterMarkerItem:
1630 case TempoMarkerItem:
1633 case FadeInHandleItem:
1634 case FadeInTrimHandleItem:
1635 if (mouse_mode == MouseObject) {
1636 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1638 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1639 rect->set_fill_color (rv->get_fill_color());
1644 case FadeOutHandleItem:
1645 case FadeOutTrimHandleItem:
1646 if (mouse_mode == MouseObject) {
1647 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1649 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1650 rect->set_fill_color (rv->get_fill_color ());
1655 case FeatureLineItem:
1657 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1658 line->set_outline_color (0xFF0000FF);
1669 /* third pass to handle entered track status in a comprehensible way.
1672 switch (item_type) {
1674 case AutomationLineItem:
1675 case ControlPointItem:
1676 /* these do not affect the current entered track state */
1677 clear_entered_track = false;
1680 case AutomationTrackItem:
1681 /* handled above already */
1693 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1701 if (!_enter_stack.empty()) {
1702 _enter_stack.pop_back();
1705 switch (item_type) {
1706 case ControlPointItem:
1707 _verbose_cursor->hide ();
1711 case AutomationLineItem:
1712 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1714 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1716 line->set_outline_color (al->get_line_color());
1722 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1726 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1727 location_flags_changed (loc);
1730 case MeterMarkerItem:
1731 case TempoMarkerItem:
1734 case FadeInTrimHandleItem:
1735 case FadeOutTrimHandleItem:
1736 case FadeInHandleItem:
1737 case FadeOutHandleItem:
1739 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1741 rect->set_fill_color (ARDOUR_UI::config()->color ("inactive fade handle"));
1746 case AutomationTrackItem:
1749 case FeatureLineItem:
1751 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1752 line->set_outline_color (ARDOUR_UI::config()->color ("zero line"));
1764 Editor::scrub (framepos_t frame, double current_x)
1768 if (scrubbing_direction == 0) {
1770 _session->request_locate (frame, false);
1771 _session->request_transport_speed (0.1);
1772 scrubbing_direction = 1;
1776 if (last_scrub_x > current_x) {
1778 /* pointer moved to the left */
1780 if (scrubbing_direction > 0) {
1782 /* we reversed direction to go backwards */
1785 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1789 /* still moving to the left (backwards) */
1791 scrub_reversals = 0;
1792 scrub_reverse_distance = 0;
1794 delta = 0.01 * (last_scrub_x - current_x);
1795 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1799 /* pointer moved to the right */
1801 if (scrubbing_direction < 0) {
1802 /* we reversed direction to go forward */
1805 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1808 /* still moving to the right */
1810 scrub_reversals = 0;
1811 scrub_reverse_distance = 0;
1813 delta = 0.01 * (current_x - last_scrub_x);
1814 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1818 /* if there have been more than 2 opposite motion moves detected, or one that moves
1819 back more than 10 pixels, reverse direction
1822 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1824 if (scrubbing_direction > 0) {
1825 /* was forwards, go backwards */
1826 _session->request_transport_speed (-0.1);
1827 scrubbing_direction = -1;
1829 /* was backwards, go forwards */
1830 _session->request_transport_speed (0.1);
1831 scrubbing_direction = 1;
1834 scrub_reverse_distance = 0;
1835 scrub_reversals = 0;
1839 last_scrub_x = current_x;
1843 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1845 _last_motion_y = event->motion.y;
1847 if (event->motion.is_hint) {
1850 /* We call this so that MOTION_NOTIFY events continue to be
1851 delivered to the canvas. We need to do this because we set
1852 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1853 the density of the events, at the expense of a round-trip
1854 to the server. Given that this will mostly occur on cases
1855 where DISPLAY = :0.0, and given the cost of what the motion
1856 event might do, its a good tradeoff.
1859 _track_canvas->get_pointer (x, y);
1862 if (current_stepping_trackview) {
1863 /* don't keep the persistent stepped trackview if the mouse moves */
1864 current_stepping_trackview = 0;
1865 step_timeout.disconnect ();
1868 if (_session && _session->actively_recording()) {
1869 /* Sorry. no dragging stuff around while we record */
1873 update_join_object_range_location (event->motion.y);
1875 if (_drags->active ()) {
1876 return _drags->motion_handler (event, from_autoscroll);
1883 Editor::can_remove_control_point (ArdourCanvas::Item* item)
1885 ControlPoint* control_point;
1887 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1888 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1889 abort(); /*NOTREACHED*/
1892 AutomationLine& line = control_point->line ();
1893 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
1894 /* we shouldn't remove the first or last gain point in region gain lines */
1895 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
1904 Editor::remove_control_point (ArdourCanvas::Item* item)
1906 if (!can_remove_control_point (item)) {
1910 ControlPoint* control_point;
1912 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1913 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1914 abort(); /*NOTREACHED*/
1917 control_point->line().remove_point (*control_point);
1921 Editor::edit_control_point (ArdourCanvas::Item* item)
1923 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
1926 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1927 abort(); /*NOTREACHED*/
1930 ControlPointDialog d (p);
1933 if (d.run () != RESPONSE_ACCEPT) {
1937 p->line().modify_point_y (*p, d.get_y_fraction ());
1941 Editor::edit_notes (MidiRegionView* mrv)
1943 MidiRegionView::Selection const & s = mrv->selection();
1949 EditNoteDialog* d = new EditNoteDialog (mrv, s);
1953 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
1957 Editor::note_edit_done (int r, EditNoteDialog* d)
1964 Editor::visible_order_range (int* low, int* high) const
1966 *low = TimeAxisView::max_order ();
1969 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
1971 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
1973 if (!rtv->hidden()) {
1975 if (*high < rtv->order()) {
1976 *high = rtv->order ();
1979 if (*low > rtv->order()) {
1980 *low = rtv->order ();
1987 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
1989 /* Either add to or set the set the region selection, unless
1990 this is an alignment click (control used)
1993 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
1994 TimeAxisView* tv = &rv.get_time_axis_view();
1995 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
1997 if (rtv && rtv->is_track()) {
1998 speed = rtv->track()->speed();
2001 framepos_t where = get_preferred_edit_position();
2005 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2007 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2009 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2011 align_region (rv.region(), End, (framepos_t) (where * speed));
2015 align_region (rv.region(), Start, (framepos_t) (where * speed));
2022 Editor::collect_new_region_view (RegionView* rv)
2024 latest_regionviews.push_back (rv);
2028 Editor::collect_and_select_new_region_view (RegionView* rv)
2031 latest_regionviews.push_back (rv);
2035 Editor::cancel_selection ()
2037 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2038 (*i)->hide_selection ();
2041 selection->clear ();
2042 clicked_selection = 0;
2046 Editor::cancel_time_selection ()
2048 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2049 (*i)->hide_selection ();
2051 selection->time.clear ();
2052 clicked_selection = 0;
2056 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2058 RegionView* rv = clicked_regionview;
2060 /* Choose action dependant on which button was pressed */
2061 switch (event->button.button) {
2063 begin_reversible_command (_("start point trim"));
2065 if (selection->selected (rv)) {
2066 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2067 i != selection->regions.by_layer().end(); ++i)
2069 if (!(*i)->region()->locked()) {
2070 (*i)->region()->clear_changes ();
2071 (*i)->region()->trim_front (new_bound);
2072 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2077 if (!rv->region()->locked()) {
2078 rv->region()->clear_changes ();
2079 rv->region()->trim_front (new_bound);
2080 _session->add_command(new StatefulDiffCommand (rv->region()));
2084 commit_reversible_command();
2088 begin_reversible_command (_("End point trim"));
2090 if (selection->selected (rv)) {
2092 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2094 if (!(*i)->region()->locked()) {
2095 (*i)->region()->clear_changes();
2096 (*i)->region()->trim_end (new_bound);
2097 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2103 if (!rv->region()->locked()) {
2104 rv->region()->clear_changes ();
2105 rv->region()->trim_end (new_bound);
2106 _session->add_command (new StatefulDiffCommand (rv->region()));
2110 commit_reversible_command();
2119 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2124 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2125 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2126 abort(); /*NOTREACHED*/
2129 Location* location = find_location_from_marker (marker, is_start);
2130 location->set_hidden (true, this);
2134 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2136 using namespace Gtkmm2ext;
2138 ArdourPrompter prompter (false);
2140 prompter.set_prompt (_("Name for region:"));
2141 prompter.set_initial_text (clicked_regionview->region()->name());
2142 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2143 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2144 prompter.show_all ();
2145 switch (prompter.run ()) {
2146 case Gtk::RESPONSE_ACCEPT:
2148 prompter.get_result(str);
2150 clicked_regionview->region()->set_name (str);
2159 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2161 /* no brushing without a useful snap setting */
2163 switch (_snap_mode) {
2165 return; /* can't work because it allows region to be placed anywhere */
2170 switch (_snap_type) {
2178 /* don't brush a copy over the original */
2180 if (pos == rv->region()->position()) {
2184 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2186 if (rtv == 0 || !rtv->is_track()) {
2190 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2191 double speed = rtv->track()->speed();
2193 playlist->clear_changes ();
2194 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2195 playlist->add_region (new_region, (framepos_t) (pos * speed));
2196 _session->add_command (new StatefulDiffCommand (playlist));
2198 // playlist is frozen, so we have to update manually XXX this is disgusting
2200 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2204 Editor::track_height_step_timeout ()
2206 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2207 current_stepping_trackview = 0;
2214 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2216 assert (region_view);
2218 if (!region_view->region()->playlist()) {
2222 switch (Config->get_edit_mode()) {
2224 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2227 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2230 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2237 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2239 assert (region_view);
2241 if (!region_view->region()->playlist()) {
2245 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2249 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2251 assert (region_view);
2253 if (!region_view->region()->playlist()) {
2257 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2261 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2263 begin_reversible_command (Operations::drag_region_brush);
2266 /** Start a grab where a time range is selected, track(s) are selected, and the
2267 * user clicks and drags a region with a modifier in order to create a new region containing
2268 * the section of the clicked region that lies within the time range.
2271 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2273 if (clicked_regionview == 0) {
2277 /* lets try to create new Region for the selection */
2279 vector<boost::shared_ptr<Region> > new_regions;
2280 create_region_from_selection (new_regions);
2282 if (new_regions.empty()) {
2286 /* XXX fix me one day to use all new regions */
2288 boost::shared_ptr<Region> region (new_regions.front());
2290 /* add it to the current stream/playlist.
2292 tricky: the streamview for the track will add a new regionview. we will
2293 catch the signal it sends when it creates the regionview to
2294 set the regionview we want to then drag.
2297 latest_regionviews.clear();
2298 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2300 /* A selection grab currently creates two undo/redo operations, one for
2301 creating the new region and another for moving it.
2304 begin_reversible_command (Operations::selection_grab);
2306 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2308 playlist->clear_changes ();
2309 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2310 _session->add_command(new StatefulDiffCommand (playlist));
2314 if (latest_regionviews.empty()) {
2315 /* something went wrong */
2319 /* we need to deselect all other regionviews, and select this one
2320 i'm ignoring undo stuff, because the region creation will take care of it
2323 selection->set (latest_regionviews);
2325 commit_reversible_command ();
2327 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2333 if (_drags->active ()) {
2336 selection->clear ();
2342 /** Update _join_object_range_state which indicate whether we are over the top
2343 * or bottom half of a route view, used by the `join object/range' tool
2344 * mode. Coordinates in canvas space.
2347 Editor::update_join_object_range_location (double y)
2349 if (!get_smart_mode()) {
2350 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2354 JoinObjectRangeState const old = _join_object_range_state;
2356 if (mouse_mode == MouseObject) {
2357 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2358 } else if (mouse_mode == MouseRange) {
2359 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2362 if (entered_regionview) {
2364 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2365 double const c = item_space.y / entered_regionview->height();
2367 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2369 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2370 if (_join_object_range_state != old && ctx) {
2371 ctx->cursor_ctx->change(which_track_cursor());
2374 } else if (entered_track) {
2376 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2378 if (entered_route_view) {
2383 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2385 double track_height = entered_route_view->view()->child_height();
2386 if (ARDOUR_UI::config()->get_show_name_highlight()) {
2387 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2389 double const c = cy / track_height;
2393 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2395 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2399 /* Other kinds of tracks use object mode */
2400 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2403 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2404 if (_join_object_range_state != old && ctx) {
2405 ctx->cursor_ctx->change(which_track_cursor());
2411 Editor::effective_mouse_mode () const
2413 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2415 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2423 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2425 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2428 e->region_view().delete_note (e->note ());
2431 /** Obtain the pointer position in canvas coordinates */
2433 Editor::get_pointer_position (double& x, double& y) const
2436 _track_canvas->get_pointer (px, py);
2437 _track_canvas->window_to_canvas (px, py, x, y);