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"
52 #include "time_axis_view.h"
53 #include "audio_time_axis.h"
54 #include "audio_region_view.h"
55 #include "midi_region_view.h"
57 #include "streamview.h"
58 #include "region_gain_line.h"
59 #include "automation_time_axis.h"
60 #include "control_point.h"
62 #include "selection.h"
65 #include "rgb_macros.h"
66 #include "control_point_dialog.h"
67 #include "editor_drag.h"
68 #include "automation_region_view.h"
69 #include "edit_note_dialog.h"
70 #include "mouse_cursors.h"
71 #include "editor_cursors.h"
72 #include "verbose_cursor.h"
78 using namespace ARDOUR;
81 using namespace Editing;
82 using Gtkmm2ext::Keyboard;
85 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
87 /* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only
88 pays attentions to subwindows. this means that menu windows are ignored, and
89 if the pointer is in a menu, the return window from the call will be the
90 the regular subwindow *under* the menu.
92 this matters quite a lot if the pointer is moving around in a menu that overlaps
93 the track canvas because we will believe that we are within the track canvas
94 when we are not. therefore, we track enter/leave events for the track canvas
95 and allow that to override the result of gdk_window_get_pointer().
98 if (!within_track_canvas) {
103 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->_track_canvas->get_window();
105 if (!canvas_window) {
109 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
111 if (!pointer_window) {
115 if (pointer_window != canvas_window) {
116 in_track_canvas = false;
120 in_track_canvas = true;
123 event.type = GDK_BUTTON_RELEASE;
127 where = window_event_sample (&event, 0, 0);
133 Editor::window_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
135 ArdourCanvas::Duple d;
137 if (!gdk_event_get_coords (event, &d.x, &d.y)) {
141 /* event coordinates are in window units, so convert to canvas
144 d = _track_canvas->window_to_canvas (d);
154 return pixel_to_sample (d.x);
158 Editor::canvas_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
163 /* event coordinates are already in canvas units */
165 if (!gdk_event_get_coords (event, &x, &y)) {
166 cerr << "!NO c COORDS for event type " << event->type << endl;
178 /* note that pixel_to_sample_from_event() never returns less than zero, so even if the pixel
179 position is negative (as can be the case with motion events in particular),
180 the frame location is always positive.
183 return pixel_to_sample_from_event (x);
187 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
189 boost::shared_ptr<Trimmable> st = _trimmable.lock();
191 if (!st || st == t) {
197 Editor::set_current_movable (boost::shared_ptr<Movable> m)
199 boost::shared_ptr<Movable> sm = _movable.lock();
201 if (!sm || sm != m) {
207 Editor::mouse_mode_object_range_toggled()
209 MouseMode m = mouse_mode;
211 Glib::RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
213 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
216 set_mouse_mode(m, true); //call this so the button styles can get updated
219 static Glib::RefPtr<Action>
220 get_mouse_mode_action(MouseMode m)
224 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
226 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
228 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
230 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
232 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
234 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-content"));
236 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
238 return Glib::RefPtr<Action>();
242 Editor::set_mouse_mode (MouseMode m, bool force)
244 if (_drags->active ()) {
248 if (!force && m == mouse_mode) {
252 if (ARDOUR::Profile->get_mixbus()) {
253 if ( m == MouseCut) m = MouseObject;
256 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
257 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
259 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
260 tact->set_active (false);
261 tact->set_active (true);
263 //NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting
267 Editor::mouse_mode_toggled (MouseMode m)
269 if (ARDOUR::Profile->get_mixbus()) {
270 if ( m == MouseCut) m = MouseObject;
273 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
274 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
276 if (!tact->get_active()) {
277 /* this was just the notification that the old mode has been
278 * left. we'll get called again with the new mode active in a
284 if (_session && mouse_mode == MouseAudition) {
285 /* stop transport and reset default speed to avoid oddness with
287 _session->request_transport_speed (0.0, true);
290 const bool was_internal = internal_editing();
294 /* Switch snap type/mode if we're moving to/from an internal tool. Note
295 this must toggle the actions and not call set_snap_*() directly,
296 otherwise things get out of sync and the combo box stops working. */
297 if (!was_internal && internal_editing()) {
298 snap_type_action(internal_snap_type)->set_active(true);
299 snap_mode_action(internal_snap_mode)->set_active(true);
300 } else if (was_internal && !internal_editing()) {
301 snap_type_action(pre_internal_snap_type)->set_active(true);
302 snap_mode_action(pre_internal_snap_mode)->set_active(true);
307 /* this should generate a new enter event which will
308 trigger the appropiate cursor.
312 _track_canvas->re_enter ();
315 set_gain_envelope_visibility ();
317 update_time_selection_display ();
319 update_all_enter_cursors ();
321 MouseModeChanged (); /* EMIT SIGNAL */
325 Editor::internal_editing() const
327 return mouse_mode == Editing::MouseContent || mouse_mode == Editing::MouseDraw;
331 Editor::update_time_selection_display ()
333 switch (mouse_mode) {
335 selection->clear_objects ();
336 selection->ClearMidiNoteSelection (); /* EMIT SIGNAL */
339 selection->clear_time ();
340 selection->clear_tracks ();
341 selection->ClearMidiNoteSelection (); /* EMIT SIGNAL */
344 /* Clear regions, but not time or tracks, since that
345 would destroy the range selection rectangle, which we need to stick
346 around for AutomationRangeDrag. */
347 selection->clear_regions ();
348 selection->clear_playlists ();
351 /* This handles internal edit.
352 Clear everything except points and notes.
354 selection->clear_regions();
355 selection->clear_lines();
356 selection->clear_playlists ();
358 selection->clear_time ();
359 selection->clear_tracks ();
363 /* We probably want to keep region selection */
364 selection->clear_points ();
365 selection->clear_lines();
366 selection->clear_playlists ();
368 selection->clear_time ();
369 selection->clear_tracks ();
373 /*Don't lose lines or points if no action in this mode */
374 selection->clear_regions ();
375 selection->clear_playlists ();
376 selection->clear_time ();
377 selection->clear_tracks ();
381 /*Clear everything */
382 selection->clear_objects();
383 selection->clear_time ();
384 selection->clear_tracks ();
390 Editor::step_mouse_mode (bool next)
392 const int n_mouse_modes = (int)MouseContent + 1;
393 int current = (int)current_mouse_mode();
395 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
397 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
402 Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemType item_type)
404 /* in object/audition/timefx/gain-automation mode,
405 any button press sets the selection if the object
406 can be selected. this is a bit of hack, because
407 we want to avoid this if the mouse operation is a
410 note: not dbl-click or triple-click
412 Also note that there is no region selection in internal edit mode, otherwise
413 for operations operating on the selection (e.g. cut) it is not obvious whether
414 to cut notes or regions.
417 MouseMode eff_mouse_mode = effective_mouse_mode ();
419 if (eff_mouse_mode == MouseCut) {
420 /* never change selection in cut mode */
424 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
425 /* context clicks are always about object properties, even if
426 we're in range mode within smart mode.
428 eff_mouse_mode = MouseObject;
431 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
432 if (get_smart_mode()) {
434 case FadeInHandleItem:
435 case FadeInTrimHandleItem:
436 case FadeOutHandleItem:
437 case FadeOutTrimHandleItem:
438 eff_mouse_mode = MouseObject;
445 if (((mouse_mode != MouseObject) &&
446 (mouse_mode != MouseAudition || item_type != RegionItem) &&
447 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
448 (mouse_mode != MouseDraw) &&
449 (mouse_mode != MouseContent || item_type == RegionItem)) ||
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 /* for object/track exclusivity, we don't call set_selected_track_as_side_effect (op); */
517 if (eff_mouse_mode != MouseRange) {
518 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
523 /* for context click, select track */
524 if (event->button.button == 3) {
525 selection->clear_tracks ();
526 set_selected_track_as_side_effect (op);
528 /* We won't get a release.*/
529 begin_reversible_selection_op (X_("Button 3 Menu Select"));
530 commit_reversible_selection_op ();
534 case AutomationTrackItem:
535 set_selected_track_as_side_effect (op);
542 if ((!press) && _mouse_changed_selection) {
543 begin_reversible_selection_op (X_("Button Selection"));
544 commit_reversible_selection_op ();
545 _mouse_changed_selection = false;
550 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
552 /* single mouse clicks on any of these item types operate
553 independent of mouse mode, mostly because they are
554 not on the main track canvas or because we want
558 NoteBase* note = NULL;
561 case PlayheadCursorItem:
562 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
566 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
567 hide_marker (item, event);
569 _drags->set (new MarkerDrag (this, item), event);
573 case TempoMarkerItem:
576 new TempoMarkerDrag (
579 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
586 case MeterMarkerItem:
589 new MeterMarkerDrag (
592 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
600 _drags->set (new VideoTimeLineDrag (this, item), event);
607 case TimecodeRulerItem:
608 case SamplesRulerItem:
609 case MinsecRulerItem:
611 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
612 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
618 case RangeMarkerBarItem:
619 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
620 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
621 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
622 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
624 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
629 case CdMarkerBarItem:
630 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
631 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
633 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
638 case TransportMarkerBarItem:
639 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
640 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
642 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
651 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
652 /* special case: allow trim of range selections in joined object mode;
653 in theory eff should equal MouseRange in this case, but it doesn't
654 because entering the range selection canvas item results in entered_regionview
655 being set to 0, so update_join_object_range_location acts as if we aren't
658 if (item_type == StartSelectionTrimItem) {
659 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
660 } else if (item_type == EndSelectionTrimItem) {
661 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
665 Editing::MouseMode eff = effective_mouse_mode ();
667 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
668 if (get_smart_mode()) {
670 case FadeInHandleItem:
671 case FadeInTrimHandleItem:
672 case FadeOutHandleItem:
673 case FadeOutTrimHandleItem:
684 case StartSelectionTrimItem:
685 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
688 case EndSelectionTrimItem:
689 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
693 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
694 start_selection_grab (item, event);
696 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
697 /* grab selection for moving */
698 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
700 /* this was debated, but decided the more common action was to
701 make a new selection */
702 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
707 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
708 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
710 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
715 case RegionViewNameHighlight:
716 if (!clicked_regionview->region()->locked()) {
717 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
723 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
724 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
726 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
735 case FadeInHandleItem:
736 case FadeOutHandleItem:
737 case LeftFrameHandle:
738 case RightFrameHandle:
739 case FeatureLineItem:
740 case RegionViewNameHighlight:
743 case AutomationTrackItem:
744 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
755 /* Existing note: allow trimming/motion */
756 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
757 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
758 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
760 _drags->set (new NoteDrag (this, item), event);
766 _drags->set (new LineDrag (this, item), event);
770 case ControlPointItem:
771 _drags->set (new ControlPointDrag (this, item), event);
775 case AutomationLineItem:
776 _drags->set (new LineDrag (this, item), event);
781 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
784 case AutomationTrackItem:
785 /* rubberband drag to select automation points */
786 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
791 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
792 /* rubberband drag to select automation points */
793 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
804 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
805 event->type == GDK_BUTTON_PRESS) {
807 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
809 } else if (event->type == GDK_BUTTON_PRESS) {
812 case FadeInHandleItem:
814 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
818 case FadeOutHandleItem:
820 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
824 case StartCrossFadeItem:
825 case EndCrossFadeItem:
826 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
827 // if (!clicked_regionview->region()->locked()) {
828 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
833 case FeatureLineItem:
835 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
836 remove_transient(item);
840 _drags->set (new FeatureLineDrag (this, item), event);
846 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
847 /* click on an automation region view; do nothing here and let the ARV's signal handler
853 /* click on a normal region view */
854 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
855 add_region_copy_drag (item, event, clicked_regionview);
856 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
857 add_region_brush_drag (item, event, clicked_regionview);
859 add_region_drag (item, event, clicked_regionview);
863 _drags->start_grab (event);
867 case RegionViewNameHighlight:
868 case LeftFrameHandle:
869 case RightFrameHandle:
870 if (!clicked_regionview->region()->locked()) {
871 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
876 case FadeInTrimHandleItem:
877 case FadeOutTrimHandleItem:
878 if (!clicked_regionview->region()->locked()) {
879 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
886 /* rename happens on edit clicks */
887 if (clicked_regionview->get_name_highlight()) {
888 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
894 case ControlPointItem:
895 _drags->set (new ControlPointDrag (this, item), event);
899 case AutomationLineItem:
900 _drags->set (new LineDrag (this, item), event);
905 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
908 case AutomationTrackItem:
910 TimeAxisView* parent = clicked_axisview->get_parent ();
911 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
913 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
915 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
917 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
918 if (pl->n_regions() == 0) {
919 /* Parent has no regions; create one so that we have somewhere to put automation */
920 _drags->set (new RegionCreateDrag (this, item, parent), event);
922 /* See if there's a region before the click that we can extend, and extend it if so */
923 framepos_t const t = canvas_event_sample (event);
924 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
926 _drags->set (new RegionCreateDrag (this, item, parent), event);
928 prev->set_length (t - prev->position ());
932 /* rubberband drag to select automation points */
933 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
957 _drags->set (new LineDrag (this, item), event);
960 case ControlPointItem:
961 _drags->set (new ControlPointDrag (this, item), event);
967 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
968 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
969 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
970 event, _cursors->up_down);
972 double const y = event->button.y;
973 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
975 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
977 /* smart "join" mode: drag automation */
978 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
986 case AutomationLineItem:
987 _drags->set (new LineDrag (this, item), event);
991 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
992 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
993 /* Note is big and pointer is near the end, trim */
994 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
997 _drags->set (new NoteDrag (this, item), event);
1004 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1005 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1016 if (item_type == NoteItem) {
1017 /* resize-drag notes */
1018 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1019 if (note->big_enough_to_trim()) {
1020 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1024 } else if (clicked_regionview) {
1026 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1032 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1033 scrub_reversals = 0;
1034 scrub_reverse_distance = 0;
1035 last_scrub_x = event->button.x;
1036 scrubbing_direction = 0;
1048 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1050 Editing::MouseMode const eff = effective_mouse_mode ();
1053 switch (item_type) {
1055 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1056 add_region_copy_drag (item, event, clicked_regionview);
1058 add_region_drag (item, event, clicked_regionview);
1060 _drags->start_grab (event);
1063 case ControlPointItem:
1064 _drags->set (new ControlPointDrag (this, item), event);
1072 switch (item_type) {
1073 case RegionViewNameHighlight:
1074 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1078 case LeftFrameHandle:
1079 case RightFrameHandle:
1080 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1084 case RegionViewName:
1085 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1099 /* relax till release */
1111 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1113 if (event->type == GDK_2BUTTON_PRESS) {
1114 _drags->mark_double_click ();
1115 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1119 if (event->type != GDK_BUTTON_PRESS) {
1123 _track_canvas->grab_focus();
1125 if (_session && _session->actively_recording()) {
1129 button_selection (item, event, item_type);
1131 if (!_drags->active () &&
1132 (Keyboard::is_delete_event (&event->button) ||
1133 Keyboard::is_context_menu_event (&event->button) ||
1134 Keyboard::is_edit_event (&event->button))) {
1136 /* handled by button release */
1140 //not rolling, range mode click + join_play_range : locate the PH here
1141 if ( !_drags->active () && _session && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && UIConfiguration::instance().get_follow_edits() && !_session->config.get_external_sync() ) {
1142 framepos_t where = canvas_event_sample (event);
1144 _session->request_locate (where, false);
1147 switch (event->button.button) {
1149 return button_press_handler_1 (item, event, item_type);
1153 return button_press_handler_2 (item, event, item_type);
1160 return button_press_dispatch (&event->button);
1169 Editor::button_press_dispatch (GdkEventButton* ev)
1171 /* this function is intended only for buttons 4 and above.
1174 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1175 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1179 Editor::button_release_dispatch (GdkEventButton* ev)
1181 /* this function is intended only for buttons 4 and above.
1184 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1185 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1189 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1191 framepos_t where = canvas_event_sample (event);
1192 AutomationTimeAxisView* atv = 0;
1194 _press_cursor_ctx.reset();
1196 /* no action if we're recording */
1198 if (_session && _session->actively_recording()) {
1202 bool were_dragging = false;
1204 if (!Keyboard::is_context_menu_event (&event->button)) {
1206 /* see if we're finishing a drag */
1208 if (_drags->active ()) {
1209 bool const r = _drags->end_grab (event);
1211 /* grab dragged, so do nothing else */
1215 were_dragging = true;
1218 update_region_layering_order_editor ();
1221 /* edit events get handled here */
1223 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1224 switch (item_type) {
1226 show_region_properties ();
1229 case TempoMarkerItem: {
1230 ArdourMarker* marker;
1231 TempoMarker* tempo_marker;
1233 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1234 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1235 abort(); /*NOTREACHED*/
1238 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1239 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1240 abort(); /*NOTREACHED*/
1243 edit_tempo_marker (*tempo_marker);
1247 case MeterMarkerItem: {
1248 ArdourMarker* marker;
1249 MeterMarker* meter_marker;
1251 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1252 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1253 abort(); /*NOTREACHED*/
1256 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1257 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1258 abort(); /*NOTREACHED*/
1260 edit_meter_marker (*meter_marker);
1264 case RegionViewName:
1265 if (clicked_regionview->name_active()) {
1266 return mouse_rename_region (item, event);
1270 case ControlPointItem:
1271 edit_control_point (item);
1280 /* context menu events get handled here */
1281 if (Keyboard::is_context_menu_event (&event->button)) {
1283 context_click_event = *event;
1285 if (!_drags->active ()) {
1287 /* no matter which button pops up the context menu, tell the menu
1288 widget to use button 1 to drive menu selection.
1291 switch (item_type) {
1293 case FadeInHandleItem:
1294 case FadeInTrimHandleItem:
1295 case StartCrossFadeItem:
1296 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1300 case FadeOutHandleItem:
1301 case FadeOutTrimHandleItem:
1302 case EndCrossFadeItem:
1303 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1306 case LeftFrameHandle:
1307 case RightFrameHandle:
1311 popup_track_context_menu (1, event->button.time, item_type, false);
1315 case RegionViewNameHighlight:
1316 case RegionViewName:
1317 popup_track_context_menu (1, event->button.time, item_type, false);
1321 popup_track_context_menu (1, event->button.time, item_type, true);
1324 case AutomationTrackItem:
1325 popup_track_context_menu (1, event->button.time, item_type, false);
1329 case RangeMarkerBarItem:
1330 case TransportMarkerBarItem:
1331 case CdMarkerBarItem:
1335 case TimecodeRulerItem:
1336 case SamplesRulerItem:
1337 case MinsecRulerItem:
1339 popup_ruler_menu (where, item_type);
1343 marker_context_menu (&event->button, item);
1346 case TempoMarkerItem:
1347 tempo_or_meter_marker_context_menu (&event->button, item);
1350 case MeterMarkerItem:
1351 tempo_or_meter_marker_context_menu (&event->button, item);
1354 case CrossfadeViewItem:
1355 popup_track_context_menu (1, event->button.time, item_type, false);
1358 case ControlPointItem:
1359 popup_control_point_context_menu (item, event);
1363 if (internal_editing()) {
1364 popup_note_context_menu (item, event);
1376 /* delete events get handled here */
1378 Editing::MouseMode const eff = effective_mouse_mode ();
1380 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1382 switch (item_type) {
1383 case TempoMarkerItem:
1384 remove_tempo_marker (item);
1387 case MeterMarkerItem:
1388 remove_meter_marker (item);
1392 remove_marker (*item, event);
1396 if (eff == MouseObject) {
1397 remove_clicked_region ();
1401 case ControlPointItem:
1402 remove_control_point (item);
1406 remove_midi_note (item, event);
1415 switch (event->button.button) {
1418 switch (item_type) {
1419 /* see comments in button_press_handler */
1420 case PlayheadCursorItem:
1423 case AutomationLineItem:
1424 case StartSelectionTrimItem:
1425 case EndSelectionTrimItem:
1429 if (!_dragging_playhead) {
1430 snap_to_with_modifier (where, event, RoundNearest, true);
1431 mouse_add_new_marker (where);
1435 case CdMarkerBarItem:
1436 if (!_dragging_playhead) {
1437 // if we get here then a dragged range wasn't done
1438 snap_to_with_modifier (where, event, RoundNearest, true);
1439 mouse_add_new_marker (where, true);
1444 if (!_dragging_playhead) {
1445 snap_to_with_modifier (where, event);
1446 mouse_add_new_tempo_event (where);
1451 if (!_dragging_playhead) {
1452 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1457 case TimecodeRulerItem:
1458 case SamplesRulerItem:
1459 case MinsecRulerItem:
1470 switch (item_type) {
1473 /* check that we didn't drag before releasing, since
1474 its really annoying to create new control
1475 points when doing this.
1477 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1478 if (!were_dragging && arv) {
1479 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1480 arv->add_gain_point_event (item, event, with_guard_points);
1486 case AutomationTrackItem: {
1487 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1488 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1490 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1501 if (scrubbing_direction == 0) {
1502 /* no drag, just a click */
1503 switch (item_type) {
1505 play_selected_region ();
1510 } else if (_session) {
1511 /* make sure we stop */
1512 _session->request_transport_speed (0.0);
1521 /* do any (de)selection operations that should occur on button release */
1522 button_selection (item, event, item_type);
1532 switch (item_type) {
1534 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1536 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1539 // Button2 click is unused
1554 // x_style_paste (where, 1.0);
1575 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1578 ArdourMarker * marker;
1582 /* by the time we reach here, entered_regionview and entered trackview
1583 * will have already been set as appropriate. Things are done this
1584 * way because this method isn't passed a pointer to a variable type of
1585 * thing that is entered (which may or may not be canvas item).
1586 * (e.g. the actual entered regionview)
1589 choose_canvas_cursor_on_entry (item_type);
1591 switch (item_type) {
1592 case ControlPointItem:
1593 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1594 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1597 fraction = 1.0 - (cp->get_y() / cp->line().height());
1599 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1600 _verbose_cursor->show ();
1605 if (mouse_mode == MouseDraw) {
1606 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1608 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1613 case AutomationLineItem:
1614 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1615 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1617 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1622 case AutomationTrackItem:
1623 AutomationTimeAxisView* atv;
1624 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1625 clear_entered_track = false;
1626 set_entered_track (atv);
1631 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1634 entered_marker = marker;
1635 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1637 case MeterMarkerItem:
1638 case TempoMarkerItem:
1641 case FadeInHandleItem:
1642 case FadeInTrimHandleItem:
1643 if (mouse_mode == MouseObject) {
1644 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1646 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1647 rect->set_fill_color (rv->get_fill_color());
1652 case FadeOutHandleItem:
1653 case FadeOutTrimHandleItem:
1654 if (mouse_mode == MouseObject) {
1655 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1657 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1658 rect->set_fill_color (rv->get_fill_color ());
1663 case FeatureLineItem:
1665 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1666 line->set_outline_color (0xFF0000FF);
1675 if (entered_regionview) {
1676 entered_regionview->entered();
1685 /* third pass to handle entered track status in a comprehensible way.
1688 switch (item_type) {
1690 case AutomationLineItem:
1691 case ControlPointItem:
1692 /* these do not affect the current entered track state */
1693 clear_entered_track = false;
1696 case AutomationTrackItem:
1697 /* handled above already */
1709 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1712 ArdourMarker *marker;
1717 if (!_enter_stack.empty()) {
1718 _enter_stack.pop_back();
1721 switch (item_type) {
1722 case ControlPointItem:
1723 _verbose_cursor->hide ();
1727 case AutomationLineItem:
1728 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1730 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1732 line->set_outline_color (al->get_line_color());
1738 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1742 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1743 location_flags_changed (loc);
1746 case MeterMarkerItem:
1747 case TempoMarkerItem:
1750 case FadeInTrimHandleItem:
1751 case FadeOutTrimHandleItem:
1752 case FadeInHandleItem:
1753 case FadeOutHandleItem:
1755 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1757 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1762 case AutomationTrackItem:
1765 case FeatureLineItem:
1767 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1768 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1780 Editor::scrub (framepos_t frame, double current_x)
1784 if (scrubbing_direction == 0) {
1786 _session->request_locate (frame, false);
1787 _session->request_transport_speed (0.1);
1788 scrubbing_direction = 1;
1792 if (last_scrub_x > current_x) {
1794 /* pointer moved to the left */
1796 if (scrubbing_direction > 0) {
1798 /* we reversed direction to go backwards */
1801 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1805 /* still moving to the left (backwards) */
1807 scrub_reversals = 0;
1808 scrub_reverse_distance = 0;
1810 delta = 0.01 * (last_scrub_x - current_x);
1811 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1815 /* pointer moved to the right */
1817 if (scrubbing_direction < 0) {
1818 /* we reversed direction to go forward */
1821 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1824 /* still moving to the right */
1826 scrub_reversals = 0;
1827 scrub_reverse_distance = 0;
1829 delta = 0.01 * (current_x - last_scrub_x);
1830 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1834 /* if there have been more than 2 opposite motion moves detected, or one that moves
1835 back more than 10 pixels, reverse direction
1838 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1840 if (scrubbing_direction > 0) {
1841 /* was forwards, go backwards */
1842 _session->request_transport_speed (-0.1);
1843 scrubbing_direction = -1;
1845 /* was backwards, go forwards */
1846 _session->request_transport_speed (0.1);
1847 scrubbing_direction = 1;
1850 scrub_reverse_distance = 0;
1851 scrub_reversals = 0;
1855 last_scrub_x = current_x;
1859 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1861 _last_motion_y = event->motion.y;
1863 if (event->motion.is_hint) {
1866 /* We call this so that MOTION_NOTIFY events continue to be
1867 delivered to the canvas. We need to do this because we set
1868 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1869 the density of the events, at the expense of a round-trip
1870 to the server. Given that this will mostly occur on cases
1871 where DISPLAY = :0.0, and given the cost of what the motion
1872 event might do, its a good tradeoff.
1875 _track_canvas->get_pointer (x, y);
1878 if (current_stepping_trackview) {
1879 /* don't keep the persistent stepped trackview if the mouse moves */
1880 current_stepping_trackview = 0;
1881 step_timeout.disconnect ();
1884 if (_session && _session->actively_recording()) {
1885 /* Sorry. no dragging stuff around while we record */
1889 update_join_object_range_location (event->motion.y);
1891 if (_drags->active ()) {
1892 return _drags->motion_handler (event, from_autoscroll);
1899 Editor::can_remove_control_point (ArdourCanvas::Item* item)
1901 ControlPoint* control_point;
1903 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1904 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1905 abort(); /*NOTREACHED*/
1908 AutomationLine& line = control_point->line ();
1909 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
1910 /* we shouldn't remove the first or last gain point in region gain lines */
1911 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
1920 Editor::remove_control_point (ArdourCanvas::Item* item)
1922 if (!can_remove_control_point (item)) {
1926 ControlPoint* control_point;
1928 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1929 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1930 abort(); /*NOTREACHED*/
1933 control_point->line().remove_point (*control_point);
1937 Editor::edit_control_point (ArdourCanvas::Item* item)
1939 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
1942 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1943 abort(); /*NOTREACHED*/
1946 ControlPointDialog d (p);
1948 if (d.run () != RESPONSE_ACCEPT) {
1952 p->line().modify_point_y (*p, d.get_y_fraction ());
1956 Editor::edit_notes (MidiRegionView* mrv)
1958 MidiRegionView::Selection const & s = mrv->selection();
1964 EditNoteDialog* d = new EditNoteDialog (mrv, s);
1967 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
1971 Editor::note_edit_done (int r, EditNoteDialog* d)
1978 Editor::visible_order_range (int* low, int* high) const
1980 *low = TimeAxisView::max_order ();
1983 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
1985 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
1987 if (!rtv->hidden()) {
1989 if (*high < rtv->order()) {
1990 *high = rtv->order ();
1993 if (*low > rtv->order()) {
1994 *low = rtv->order ();
2001 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2003 /* Either add to or set the set the region selection, unless
2004 this is an alignment click (control used)
2007 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2008 TimeAxisView* tv = &rv.get_time_axis_view();
2009 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2011 if (rtv && rtv->is_track()) {
2012 speed = rtv->track()->speed();
2015 framepos_t where = get_preferred_edit_position();
2019 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2021 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2023 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2025 align_region (rv.region(), End, (framepos_t) (where * speed));
2029 align_region (rv.region(), Start, (framepos_t) (where * speed));
2036 Editor::collect_new_region_view (RegionView* rv)
2038 latest_regionviews.push_back (rv);
2042 Editor::collect_and_select_new_region_view (RegionView* rv)
2045 latest_regionviews.push_back (rv);
2049 Editor::cancel_selection ()
2051 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2052 (*i)->hide_selection ();
2055 selection->clear ();
2056 clicked_selection = 0;
2060 Editor::cancel_time_selection ()
2062 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2063 (*i)->hide_selection ();
2065 selection->time.clear ();
2066 clicked_selection = 0;
2070 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2072 RegionView* rv = clicked_regionview;
2074 /* Choose action dependant on which button was pressed */
2075 switch (event->button.button) {
2077 begin_reversible_command (_("start point trim"));
2079 if (selection->selected (rv)) {
2080 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2081 i != selection->regions.by_layer().end(); ++i)
2083 if (!(*i)->region()->locked()) {
2084 (*i)->region()->clear_changes ();
2085 (*i)->region()->trim_front (new_bound);
2086 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2091 if (!rv->region()->locked()) {
2092 rv->region()->clear_changes ();
2093 rv->region()->trim_front (new_bound);
2094 _session->add_command(new StatefulDiffCommand (rv->region()));
2098 commit_reversible_command();
2102 begin_reversible_command (_("End point trim"));
2104 if (selection->selected (rv)) {
2106 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2108 if (!(*i)->region()->locked()) {
2109 (*i)->region()->clear_changes();
2110 (*i)->region()->trim_end (new_bound);
2111 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2117 if (!rv->region()->locked()) {
2118 rv->region()->clear_changes ();
2119 rv->region()->trim_end (new_bound);
2120 _session->add_command (new StatefulDiffCommand (rv->region()));
2124 commit_reversible_command();
2133 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2135 ArdourMarker* marker;
2138 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2139 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2140 abort(); /*NOTREACHED*/
2143 Location* location = find_location_from_marker (marker, is_start);
2144 location->set_hidden (true, this);
2148 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2150 using namespace Gtkmm2ext;
2152 ArdourPrompter prompter (false);
2154 prompter.set_prompt (_("Name for region:"));
2155 prompter.set_initial_text (clicked_regionview->region()->name());
2156 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2157 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2158 prompter.show_all ();
2159 switch (prompter.run ()) {
2160 case Gtk::RESPONSE_ACCEPT:
2162 prompter.get_result(str);
2164 clicked_regionview->region()->set_name (str);
2173 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2175 /* no brushing without a useful snap setting */
2177 switch (_snap_mode) {
2179 return; /* can't work because it allows region to be placed anywhere */
2184 switch (_snap_type) {
2192 /* don't brush a copy over the original */
2194 if (pos == rv->region()->position()) {
2198 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2200 if (rtv == 0 || !rtv->is_track()) {
2204 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2205 double speed = rtv->track()->speed();
2207 playlist->clear_changes ();
2208 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2209 playlist->add_region (new_region, (framepos_t) (pos * speed));
2210 _session->add_command (new StatefulDiffCommand (playlist));
2212 // playlist is frozen, so we have to update manually XXX this is disgusting
2214 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2218 Editor::track_height_step_timeout ()
2220 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2221 current_stepping_trackview = 0;
2228 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2230 assert (region_view);
2232 if (!region_view->region()->playlist()) {
2236 switch (Config->get_edit_mode()) {
2238 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2241 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2244 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2251 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2253 assert (region_view);
2255 if (!region_view->region()->playlist()) {
2259 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2263 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2265 assert (region_view);
2267 if (!region_view->region()->playlist()) {
2271 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2275 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2278 /** Start a grab where a time range is selected, track(s) are selected, and the
2279 * user clicks and drags a region with a modifier in order to create a new region containing
2280 * the section of the clicked region that lies within the time range.
2283 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2285 if (clicked_regionview == 0) {
2289 /* lets try to create new Region for the selection */
2291 vector<boost::shared_ptr<Region> > new_regions;
2292 create_region_from_selection (new_regions);
2294 if (new_regions.empty()) {
2298 /* XXX fix me one day to use all new regions */
2300 boost::shared_ptr<Region> region (new_regions.front());
2302 /* add it to the current stream/playlist.
2304 tricky: the streamview for the track will add a new regionview. we will
2305 catch the signal it sends when it creates the regionview to
2306 set the regionview we want to then drag.
2309 latest_regionviews.clear();
2310 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2312 /* A selection grab currently creates two undo/redo operations, one for
2313 creating the new region and another for moving it.
2315 begin_reversible_command (Operations::selection_grab);
2317 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2319 playlist->clear_changes ();
2320 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2321 _session->add_command(new StatefulDiffCommand (playlist));
2325 if (latest_regionviews.empty()) {
2326 /* something went wrong */
2327 abort_reversible_command ();
2331 /* we need to deselect all other regionviews, and select this one
2332 i'm ignoring undo stuff, because the region creation will take care of it
2335 selection->set (latest_regionviews);
2337 commit_reversible_command ();
2339 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2345 if (_drags->active ()) {
2348 selection->clear ();
2354 /** Update _join_object_range_state which indicate whether we are over the top
2355 * or bottom half of a route view, used by the `join object/range' tool
2356 * mode. Coordinates in canvas space.
2359 Editor::update_join_object_range_location (double y)
2361 if (!get_smart_mode()) {
2362 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2366 JoinObjectRangeState const old = _join_object_range_state;
2368 if (mouse_mode == MouseObject) {
2369 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2370 } else if (mouse_mode == MouseRange) {
2371 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2374 if (entered_regionview) {
2376 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2377 double const c = item_space.y / entered_regionview->height();
2379 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2381 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2382 if (_join_object_range_state != old && ctx) {
2383 ctx->cursor_ctx->change(which_track_cursor());
2386 } else if (entered_track) {
2388 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2390 if (entered_route_view) {
2395 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2397 double track_height = entered_route_view->view()->child_height();
2398 if (UIConfiguration::instance().get_show_name_highlight()) {
2399 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2401 double const c = cy / track_height;
2405 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2407 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2411 /* Other kinds of tracks use object mode */
2412 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2415 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2416 if (_join_object_range_state != old && ctx) {
2417 ctx->cursor_ctx->change(which_track_cursor());
2423 Editor::effective_mouse_mode () const
2425 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2427 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2435 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2437 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2440 e->region_view().delete_note (e->note ());
2443 /** Obtain the pointer position in canvas coordinates */
2445 Editor::get_pointer_position (double& x, double& y) const
2448 _track_canvas->get_pointer (px, py);
2449 _track_canvas->window_to_canvas (px, py, x, y);