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 (X_("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 (X_("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 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
773 case AutomationTrackItem:
774 /* rubberband drag to select automation points */
775 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
780 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
781 /* rubberband drag to select automation points */
782 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
793 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
794 event->type == GDK_BUTTON_PRESS) {
796 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
798 } else if (event->type == GDK_BUTTON_PRESS) {
801 case FadeInHandleItem:
803 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
807 case FadeOutHandleItem:
809 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
813 case StartCrossFadeItem:
814 case EndCrossFadeItem:
815 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
816 // if (!clicked_regionview->region()->locked()) {
817 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
822 case FeatureLineItem:
824 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
825 remove_transient(item);
829 _drags->set (new FeatureLineDrag (this, item), event);
835 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
836 /* click on an automation region view; do nothing here and let the ARV's signal handler
842 /* click on a normal region view */
843 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
844 add_region_copy_drag (item, event, clicked_regionview);
845 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
846 add_region_brush_drag (item, event, clicked_regionview);
848 add_region_drag (item, event, clicked_regionview);
852 _drags->start_grab (event);
856 case RegionViewNameHighlight:
857 case LeftFrameHandle:
858 case RightFrameHandle:
859 if (!clicked_regionview->region()->locked()) {
860 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
865 case FadeInTrimHandleItem:
866 case FadeOutTrimHandleItem:
867 if (!clicked_regionview->region()->locked()) {
868 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
875 /* rename happens on edit clicks */
876 if (clicked_regionview->get_name_highlight()) {
877 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
883 case ControlPointItem:
884 _drags->set (new ControlPointDrag (this, item), event);
888 case AutomationLineItem:
889 _drags->set (new LineDrag (this, item), event);
894 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
897 case AutomationTrackItem:
899 TimeAxisView* parent = clicked_axisview->get_parent ();
900 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
902 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
904 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
906 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
907 if (pl->n_regions() == 0) {
908 /* Parent has no regions; create one so that we have somewhere to put automation */
909 _drags->set (new RegionCreateDrag (this, item, parent), event);
911 /* See if there's a region before the click that we can extend, and extend it if so */
912 framepos_t const t = canvas_event_sample (event);
913 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
915 _drags->set (new RegionCreateDrag (this, item, parent), event);
917 prev->set_length (t - prev->position ());
921 /* rubberband drag to select automation points */
922 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
946 _drags->set (new LineDrag (this, item), event);
949 case ControlPointItem:
950 _drags->set (new ControlPointDrag (this, item), event);
956 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
957 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
958 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
959 event, _cursors->up_down);
961 double const y = event->button.y;
962 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
964 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
966 /* smart "join" mode: drag automation */
967 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
975 case AutomationLineItem:
976 _drags->set (new LineDrag (this, item), event);
980 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
981 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
982 /* Note is big and pointer is near the end, trim */
983 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
986 _drags->set (new NoteDrag (this, item), event);
993 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
994 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1005 if (item_type == NoteItem) {
1006 /* resize-drag notes */
1007 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1008 if (note->big_enough_to_trim()) {
1009 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1013 } else if (clicked_regionview) {
1015 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1021 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1022 scrub_reversals = 0;
1023 scrub_reverse_distance = 0;
1024 last_scrub_x = event->button.x;
1025 scrubbing_direction = 0;
1037 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1039 Editing::MouseMode const eff = effective_mouse_mode ();
1042 switch (item_type) {
1044 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1045 add_region_copy_drag (item, event, clicked_regionview);
1047 add_region_drag (item, event, clicked_regionview);
1049 _drags->start_grab (event);
1052 case ControlPointItem:
1053 _drags->set (new ControlPointDrag (this, item), event);
1061 switch (item_type) {
1062 case RegionViewNameHighlight:
1063 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1067 case LeftFrameHandle:
1068 case RightFrameHandle:
1069 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1073 case RegionViewName:
1074 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1088 /* relax till release */
1100 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1102 if (event->type == GDK_2BUTTON_PRESS) {
1103 _drags->mark_double_click ();
1104 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1108 if (event->type != GDK_BUTTON_PRESS) {
1112 _track_canvas->grab_focus();
1114 if (_session && _session->actively_recording()) {
1118 button_selection (item, event, item_type);
1120 if (!_drags->active () &&
1121 (Keyboard::is_delete_event (&event->button) ||
1122 Keyboard::is_context_menu_event (&event->button) ||
1123 Keyboard::is_edit_event (&event->button))) {
1125 /* handled by button release */
1129 //not rolling, range mode click + join_play_range : locate the PH here
1130 if ( !_drags->active () && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && ARDOUR_UI::config()->get_follow_edits() ) {
1131 framepos_t where = canvas_event_sample (event);
1133 _session->request_locate (where, false);
1136 switch (event->button.button) {
1138 return button_press_handler_1 (item, event, item_type);
1142 return button_press_handler_2 (item, event, item_type);
1149 return button_press_dispatch (&event->button);
1158 Editor::button_press_dispatch (GdkEventButton* ev)
1160 /* this function is intended only for buttons 4 and above.
1163 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1164 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1168 Editor::button_release_dispatch (GdkEventButton* ev)
1170 /* this function is intended only for buttons 4 and above.
1173 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1174 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1178 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1180 framepos_t where = canvas_event_sample (event);
1181 AutomationTimeAxisView* atv = 0;
1183 _press_cursor_ctx.reset();
1185 /* no action if we're recording */
1187 if (_session && _session->actively_recording()) {
1191 bool were_dragging = false;
1193 if (!Keyboard::is_context_menu_event (&event->button)) {
1195 /* see if we're finishing a drag */
1197 if (_drags->active ()) {
1198 bool const r = _drags->end_grab (event);
1200 /* grab dragged, so do nothing else */
1204 were_dragging = true;
1207 update_region_layering_order_editor ();
1210 /* edit events get handled here */
1212 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1213 switch (item_type) {
1215 show_region_properties ();
1218 case TempoMarkerItem: {
1220 TempoMarker* tempo_marker;
1222 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
1223 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1224 abort(); /*NOTREACHED*/
1227 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1228 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1229 abort(); /*NOTREACHED*/
1232 edit_tempo_marker (*tempo_marker);
1236 case MeterMarkerItem: {
1238 MeterMarker* meter_marker;
1240 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
1241 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1242 abort(); /*NOTREACHED*/
1245 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1246 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1247 abort(); /*NOTREACHED*/
1249 edit_meter_marker (*meter_marker);
1253 case RegionViewName:
1254 if (clicked_regionview->name_active()) {
1255 return mouse_rename_region (item, event);
1259 case ControlPointItem:
1260 edit_control_point (item);
1269 /* context menu events get handled here */
1270 if (Keyboard::is_context_menu_event (&event->button)) {
1272 context_click_event = *event;
1274 if (!_drags->active ()) {
1276 /* no matter which button pops up the context menu, tell the menu
1277 widget to use button 1 to drive menu selection.
1280 switch (item_type) {
1282 case FadeInHandleItem:
1283 case FadeInTrimHandleItem:
1284 case StartCrossFadeItem:
1285 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1289 case FadeOutHandleItem:
1290 case FadeOutTrimHandleItem:
1291 case EndCrossFadeItem:
1292 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1295 case LeftFrameHandle:
1296 case RightFrameHandle:
1300 popup_track_context_menu (1, event->button.time, item_type, false);
1304 case RegionViewNameHighlight:
1305 case RegionViewName:
1306 popup_track_context_menu (1, event->button.time, item_type, false);
1310 popup_track_context_menu (1, event->button.time, item_type, true);
1313 case AutomationTrackItem:
1314 popup_track_context_menu (1, event->button.time, item_type, false);
1318 case RangeMarkerBarItem:
1319 case TransportMarkerBarItem:
1320 case CdMarkerBarItem:
1324 case TimecodeRulerItem:
1325 case SamplesRulerItem:
1326 case MinsecRulerItem:
1328 popup_ruler_menu (where, item_type);
1332 marker_context_menu (&event->button, item);
1335 case TempoMarkerItem:
1336 tempo_or_meter_marker_context_menu (&event->button, item);
1339 case MeterMarkerItem:
1340 tempo_or_meter_marker_context_menu (&event->button, item);
1343 case CrossfadeViewItem:
1344 popup_track_context_menu (1, event->button.time, item_type, false);
1347 case ControlPointItem:
1348 popup_control_point_context_menu (item, event);
1352 if (internal_editing()) {
1353 popup_note_context_menu (item, event);
1365 /* delete events get handled here */
1367 Editing::MouseMode const eff = effective_mouse_mode ();
1369 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1371 switch (item_type) {
1372 case TempoMarkerItem:
1373 remove_tempo_marker (item);
1376 case MeterMarkerItem:
1377 remove_meter_marker (item);
1381 remove_marker (*item, event);
1385 if (eff == MouseObject) {
1386 remove_clicked_region ();
1390 case ControlPointItem:
1391 remove_control_point (item);
1395 remove_midi_note (item, event);
1404 switch (event->button.button) {
1407 switch (item_type) {
1408 /* see comments in button_press_handler */
1409 case PlayheadCursorItem:
1412 case AutomationLineItem:
1413 case StartSelectionTrimItem:
1414 case EndSelectionTrimItem:
1418 if (!_dragging_playhead) {
1419 snap_to_with_modifier (where, event, RoundNearest, true);
1420 mouse_add_new_marker (where);
1424 case CdMarkerBarItem:
1425 if (!_dragging_playhead) {
1426 // if we get here then a dragged range wasn't done
1427 snap_to_with_modifier (where, event, RoundNearest, true);
1428 mouse_add_new_marker (where, true);
1433 if (!_dragging_playhead) {
1434 snap_to_with_modifier (where, event);
1435 mouse_add_new_tempo_event (where);
1440 if (!_dragging_playhead) {
1441 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1446 case TimecodeRulerItem:
1447 case SamplesRulerItem:
1448 case MinsecRulerItem:
1459 switch (item_type) {
1462 /* check that we didn't drag before releasing, since
1463 its really annoying to create new control
1464 points when doing this.
1466 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1467 if (!were_dragging && arv) {
1468 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1469 arv->add_gain_point_event (item, event, with_guard_points);
1475 case AutomationTrackItem: {
1476 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1477 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1479 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1490 if (scrubbing_direction == 0) {
1491 /* no drag, just a click */
1492 switch (item_type) {
1494 play_selected_region ();
1499 } else if (_session) {
1500 /* make sure we stop */
1501 _session->request_transport_speed (0.0);
1510 /* do any (de)selection operations that should occur on button release */
1511 button_selection (item, event, item_type);
1521 switch (item_type) {
1523 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1525 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1528 // Button2 click is unused
1543 // x_style_paste (where, 1.0);
1564 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1571 /* by the time we reach here, entered_regionview and entered trackview
1572 * will have already been set as appropriate. Things are done this
1573 * way because this method isn't passed a pointer to a variable type of
1574 * thing that is entered (which may or may not be canvas item).
1575 * (e.g. the actual entered regionview)
1578 choose_canvas_cursor_on_entry (item_type);
1580 switch (item_type) {
1581 case ControlPointItem:
1582 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1583 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1586 fraction = 1.0 - (cp->get_y() / cp->line().height());
1588 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1589 _verbose_cursor->show ();
1594 if (mouse_mode == MouseDraw) {
1595 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1597 line->set_outline_color (ARDOUR_UI::config()->color ("entered gain line"));
1602 case AutomationLineItem:
1603 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1604 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1606 line->set_outline_color (ARDOUR_UI::config()->color ("entered automation line"));
1611 case AutomationTrackItem:
1612 AutomationTimeAxisView* atv;
1613 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1614 clear_entered_track = false;
1615 set_entered_track (atv);
1620 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1623 entered_marker = marker;
1624 marker->set_color_rgba (ARDOUR_UI::config()->color ("entered marker"));
1626 case MeterMarkerItem:
1627 case TempoMarkerItem:
1630 case FadeInHandleItem:
1631 case FadeInTrimHandleItem:
1632 if (mouse_mode == MouseObject) {
1633 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1635 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1636 rect->set_fill_color (rv->get_fill_color());
1641 case FadeOutHandleItem:
1642 case FadeOutTrimHandleItem:
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 FeatureLineItem:
1654 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1655 line->set_outline_color (0xFF0000FF);
1666 /* third pass to handle entered track status in a comprehensible way.
1669 switch (item_type) {
1671 case AutomationLineItem:
1672 case ControlPointItem:
1673 /* these do not affect the current entered track state */
1674 clear_entered_track = false;
1677 case AutomationTrackItem:
1678 /* handled above already */
1690 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1698 if (!_enter_stack.empty()) {
1699 _enter_stack.pop_back();
1702 switch (item_type) {
1703 case ControlPointItem:
1704 _verbose_cursor->hide ();
1708 case AutomationLineItem:
1709 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1711 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1713 line->set_outline_color (al->get_line_color());
1719 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1723 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1724 location_flags_changed (loc);
1727 case MeterMarkerItem:
1728 case TempoMarkerItem:
1731 case FadeInTrimHandleItem:
1732 case FadeOutTrimHandleItem:
1733 case FadeInHandleItem:
1734 case FadeOutHandleItem:
1736 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1738 rect->set_fill_color (ARDOUR_UI::config()->color ("inactive fade handle"));
1743 case AutomationTrackItem:
1746 case FeatureLineItem:
1748 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1749 line->set_outline_color (ARDOUR_UI::config()->color ("zero line"));
1761 Editor::scrub (framepos_t frame, double current_x)
1765 if (scrubbing_direction == 0) {
1767 _session->request_locate (frame, false);
1768 _session->request_transport_speed (0.1);
1769 scrubbing_direction = 1;
1773 if (last_scrub_x > current_x) {
1775 /* pointer moved to the left */
1777 if (scrubbing_direction > 0) {
1779 /* we reversed direction to go backwards */
1782 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1786 /* still moving to the left (backwards) */
1788 scrub_reversals = 0;
1789 scrub_reverse_distance = 0;
1791 delta = 0.01 * (last_scrub_x - current_x);
1792 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1796 /* pointer moved to the right */
1798 if (scrubbing_direction < 0) {
1799 /* we reversed direction to go forward */
1802 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1805 /* still moving to the right */
1807 scrub_reversals = 0;
1808 scrub_reverse_distance = 0;
1810 delta = 0.01 * (current_x - last_scrub_x);
1811 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1815 /* if there have been more than 2 opposite motion moves detected, or one that moves
1816 back more than 10 pixels, reverse direction
1819 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1821 if (scrubbing_direction > 0) {
1822 /* was forwards, go backwards */
1823 _session->request_transport_speed (-0.1);
1824 scrubbing_direction = -1;
1826 /* was backwards, go forwards */
1827 _session->request_transport_speed (0.1);
1828 scrubbing_direction = 1;
1831 scrub_reverse_distance = 0;
1832 scrub_reversals = 0;
1836 last_scrub_x = current_x;
1840 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1842 _last_motion_y = event->motion.y;
1844 if (event->motion.is_hint) {
1847 /* We call this so that MOTION_NOTIFY events continue to be
1848 delivered to the canvas. We need to do this because we set
1849 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1850 the density of the events, at the expense of a round-trip
1851 to the server. Given that this will mostly occur on cases
1852 where DISPLAY = :0.0, and given the cost of what the motion
1853 event might do, its a good tradeoff.
1856 _track_canvas->get_pointer (x, y);
1859 if (current_stepping_trackview) {
1860 /* don't keep the persistent stepped trackview if the mouse moves */
1861 current_stepping_trackview = 0;
1862 step_timeout.disconnect ();
1865 if (_session && _session->actively_recording()) {
1866 /* Sorry. no dragging stuff around while we record */
1870 update_join_object_range_location (event->motion.y);
1872 if (_drags->active ()) {
1873 return _drags->motion_handler (event, from_autoscroll);
1880 Editor::can_remove_control_point (ArdourCanvas::Item* item)
1882 ControlPoint* control_point;
1884 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1885 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1886 abort(); /*NOTREACHED*/
1889 AutomationLine& line = control_point->line ();
1890 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
1891 /* we shouldn't remove the first or last gain point in region gain lines */
1892 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
1901 Editor::remove_control_point (ArdourCanvas::Item* item)
1903 if (!can_remove_control_point (item)) {
1907 ControlPoint* control_point;
1909 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1910 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1911 abort(); /*NOTREACHED*/
1914 control_point->line().remove_point (*control_point);
1918 Editor::edit_control_point (ArdourCanvas::Item* item)
1920 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
1923 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1924 abort(); /*NOTREACHED*/
1927 ControlPointDialog d (p);
1929 if (d.run () != RESPONSE_ACCEPT) {
1933 p->line().modify_point_y (*p, d.get_y_fraction ());
1937 Editor::edit_notes (MidiRegionView* mrv)
1939 MidiRegionView::Selection const & s = mrv->selection();
1945 EditNoteDialog* d = new EditNoteDialog (mrv, s);
1948 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
1952 Editor::note_edit_done (int r, EditNoteDialog* d)
1959 Editor::visible_order_range (int* low, int* high) const
1961 *low = TimeAxisView::max_order ();
1964 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
1966 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
1968 if (!rtv->hidden()) {
1970 if (*high < rtv->order()) {
1971 *high = rtv->order ();
1974 if (*low > rtv->order()) {
1975 *low = rtv->order ();
1982 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
1984 /* Either add to or set the set the region selection, unless
1985 this is an alignment click (control used)
1988 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
1989 TimeAxisView* tv = &rv.get_time_axis_view();
1990 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
1992 if (rtv && rtv->is_track()) {
1993 speed = rtv->track()->speed();
1996 framepos_t where = get_preferred_edit_position();
2000 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2002 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2004 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2006 align_region (rv.region(), End, (framepos_t) (where * speed));
2010 align_region (rv.region(), Start, (framepos_t) (where * speed));
2017 Editor::collect_new_region_view (RegionView* rv)
2019 latest_regionviews.push_back (rv);
2023 Editor::collect_and_select_new_region_view (RegionView* rv)
2026 latest_regionviews.push_back (rv);
2030 Editor::cancel_selection ()
2032 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2033 (*i)->hide_selection ();
2036 selection->clear ();
2037 clicked_selection = 0;
2041 Editor::cancel_time_selection ()
2043 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2044 (*i)->hide_selection ();
2046 selection->time.clear ();
2047 clicked_selection = 0;
2051 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2053 RegionView* rv = clicked_regionview;
2055 /* Choose action dependant on which button was pressed */
2056 switch (event->button.button) {
2058 begin_reversible_command (_("start point trim"));
2060 if (selection->selected (rv)) {
2061 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2062 i != selection->regions.by_layer().end(); ++i)
2064 if (!(*i)->region()->locked()) {
2065 (*i)->region()->clear_changes ();
2066 (*i)->region()->trim_front (new_bound);
2067 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2072 if (!rv->region()->locked()) {
2073 rv->region()->clear_changes ();
2074 rv->region()->trim_front (new_bound);
2075 _session->add_command(new StatefulDiffCommand (rv->region()));
2079 commit_reversible_command();
2083 begin_reversible_command (_("End point trim"));
2085 if (selection->selected (rv)) {
2087 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2089 if (!(*i)->region()->locked()) {
2090 (*i)->region()->clear_changes();
2091 (*i)->region()->trim_end (new_bound);
2092 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2098 if (!rv->region()->locked()) {
2099 rv->region()->clear_changes ();
2100 rv->region()->trim_end (new_bound);
2101 _session->add_command (new StatefulDiffCommand (rv->region()));
2105 commit_reversible_command();
2114 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2119 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2120 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2121 abort(); /*NOTREACHED*/
2124 Location* location = find_location_from_marker (marker, is_start);
2125 location->set_hidden (true, this);
2129 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2131 using namespace Gtkmm2ext;
2133 ArdourPrompter prompter (false);
2135 prompter.set_prompt (_("Name for region:"));
2136 prompter.set_initial_text (clicked_regionview->region()->name());
2137 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2138 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2139 prompter.show_all ();
2140 switch (prompter.run ()) {
2141 case Gtk::RESPONSE_ACCEPT:
2143 prompter.get_result(str);
2145 clicked_regionview->region()->set_name (str);
2154 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2156 /* no brushing without a useful snap setting */
2158 switch (_snap_mode) {
2160 return; /* can't work because it allows region to be placed anywhere */
2165 switch (_snap_type) {
2173 /* don't brush a copy over the original */
2175 if (pos == rv->region()->position()) {
2179 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2181 if (rtv == 0 || !rtv->is_track()) {
2185 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2186 double speed = rtv->track()->speed();
2188 playlist->clear_changes ();
2189 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2190 playlist->add_region (new_region, (framepos_t) (pos * speed));
2191 _session->add_command (new StatefulDiffCommand (playlist));
2193 // playlist is frozen, so we have to update manually XXX this is disgusting
2195 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2199 Editor::track_height_step_timeout ()
2201 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2202 current_stepping_trackview = 0;
2209 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2211 assert (region_view);
2213 if (!region_view->region()->playlist()) {
2217 switch (Config->get_edit_mode()) {
2219 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2222 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2225 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2232 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2234 assert (region_view);
2236 if (!region_view->region()->playlist()) {
2240 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2244 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2246 assert (region_view);
2248 if (!region_view->region()->playlist()) {
2252 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2256 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2258 begin_reversible_command (Operations::drag_region_brush);
2261 /** Start a grab where a time range is selected, track(s) are selected, and the
2262 * user clicks and drags a region with a modifier in order to create a new region containing
2263 * the section of the clicked region that lies within the time range.
2266 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2268 if (clicked_regionview == 0) {
2272 /* lets try to create new Region for the selection */
2274 vector<boost::shared_ptr<Region> > new_regions;
2275 create_region_from_selection (new_regions);
2277 if (new_regions.empty()) {
2281 /* XXX fix me one day to use all new regions */
2283 boost::shared_ptr<Region> region (new_regions.front());
2285 /* add it to the current stream/playlist.
2287 tricky: the streamview for the track will add a new regionview. we will
2288 catch the signal it sends when it creates the regionview to
2289 set the regionview we want to then drag.
2292 latest_regionviews.clear();
2293 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2295 /* A selection grab currently creates two undo/redo operations, one for
2296 creating the new region and another for moving it.
2299 begin_reversible_command (Operations::selection_grab);
2301 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2303 playlist->clear_changes ();
2304 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2305 _session->add_command(new StatefulDiffCommand (playlist));
2309 if (latest_regionviews.empty()) {
2310 /* something went wrong */
2314 /* we need to deselect all other regionviews, and select this one
2315 i'm ignoring undo stuff, because the region creation will take care of it
2318 selection->set (latest_regionviews);
2320 commit_reversible_command ();
2322 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2328 if (_drags->active ()) {
2331 selection->clear ();
2337 /** Update _join_object_range_state which indicate whether we are over the top
2338 * or bottom half of a route view, used by the `join object/range' tool
2339 * mode. Coordinates in canvas space.
2342 Editor::update_join_object_range_location (double y)
2344 if (!get_smart_mode()) {
2345 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2349 JoinObjectRangeState const old = _join_object_range_state;
2351 if (mouse_mode == MouseObject) {
2352 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2353 } else if (mouse_mode == MouseRange) {
2354 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2357 if (entered_regionview) {
2359 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2360 double const c = item_space.y / entered_regionview->height();
2362 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2364 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2365 if (_join_object_range_state != old && ctx) {
2366 ctx->cursor_ctx->change(which_track_cursor());
2369 } else if (entered_track) {
2371 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2373 if (entered_route_view) {
2378 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2380 double track_height = entered_route_view->view()->child_height();
2381 if (ARDOUR_UI::config()->get_show_name_highlight()) {
2382 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2384 double const c = cy / track_height;
2388 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2390 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2394 /* Other kinds of tracks use object mode */
2395 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2398 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2399 if (_join_object_range_state != old && ctx) {
2400 ctx->cursor_ctx->change(which_track_cursor());
2406 Editor::effective_mouse_mode () const
2408 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2410 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2418 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2420 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2423 e->region_view().delete_note (e->note ());
2426 /** Obtain the pointer position in canvas coordinates */
2428 Editor::get_pointer_position (double& x, double& y) const
2431 _track_canvas->get_pointer (px, py);
2432 _track_canvas->window_to_canvas (px, py, x, y);