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"
38 #include "canvas/canvas.h"
40 #include "ardour/audioregion.h"
41 #include "ardour/operations.h"
42 #include "ardour/playlist.h"
43 #include "ardour/profile.h"
44 #include "ardour/region_factory.h"
45 #include "ardour/route.h"
46 #include "ardour/session.h"
47 #include "ardour/types.h"
50 #include "ardour_ui.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->clear_midi_notes ();
339 selection->clear_time ();
340 selection->clear_tracks ();
341 selection->clear_midi_notes ();
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)
405 /* in object/audition/timefx/gain-automation mode,
406 any button press sets the selection if the object
407 can be selected. this is a bit of hack, because
408 we want to avoid this if the mouse operation is a
411 note: not dbl-click or triple-click
413 Also note that there is no region selection in internal edit mode, otherwise
414 for operations operating on the selection (e.g. cut) it is not obvious whether
415 to cut notes or regions.
418 MouseMode eff_mouse_mode = effective_mouse_mode ();
420 if (eff_mouse_mode == MouseCut) {
421 /* never change selection in cut mode */
425 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
426 /* context clicks are always about object properties, even if
427 we're in range mode within smart mode.
429 eff_mouse_mode = MouseObject;
432 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
433 if (get_smart_mode()) {
435 case FadeInHandleItem:
436 case FadeInTrimHandleItem:
437 case FadeOutHandleItem:
438 case FadeOutTrimHandleItem:
439 eff_mouse_mode = MouseObject;
446 if (((mouse_mode != MouseObject) &&
447 (mouse_mode != MouseAudition || item_type != RegionItem) &&
448 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
449 (mouse_mode != MouseDraw) &&
450 (mouse_mode != MouseContent || item_type == RegionItem)) ||
451 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
455 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
457 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
459 /* almost no selection action on modified button-2 or button-3 events */
461 if ((item_type != RegionItem && event->button.button != 2)
462 /* for selection of control points prior to delete (shift-right click) */
463 && !(item_type == ControlPointItem && event->button.button == 3 && event->type == GDK_BUTTON_PRESS)) {
469 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
470 bool press = (event->type == GDK_BUTTON_PRESS);
473 _mouse_changed_selection = false;
478 if (eff_mouse_mode == MouseDraw) {
482 if (eff_mouse_mode != MouseRange) {
483 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
485 /* don't change the selection unless the
486 clicked track is not currently selected. if
487 so, "collapse" the selection to just this
490 if (!selection->selected (clicked_axisview)) {
491 set_selected_track_as_side_effect (Selection::Set);
495 if (eff_mouse_mode != MouseRange) {
496 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
501 case RegionViewNameHighlight:
503 case LeftFrameHandle:
504 case RightFrameHandle:
505 case FadeInHandleItem:
506 case FadeInTrimHandleItem:
508 case FadeOutHandleItem:
509 case FadeOutTrimHandleItem:
511 case StartCrossFadeItem:
512 case EndCrossFadeItem:
513 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
514 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
515 } else if (event->type == GDK_BUTTON_PRESS) {
516 set_selected_track_as_side_effect (op);
520 case ControlPointItem:
521 /* for object/track exclusivity, we don't call set_selected_track_as_side_effect (op); */
523 if (eff_mouse_mode != MouseRange) {
524 if (event->button.button != 3) {
525 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
527 _mouse_changed_selection |= set_selected_control_point_from_click (press, Selection::Set);
533 if (eff_mouse_mode != MouseRange) {
534 AutomationLine* argl = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
536 std::list<Selectable*> selectables;
537 uint32_t before, after;
538 framecnt_t const where = (framecnt_t) floor (event->button.x * samples_per_pixel) - clicked_regionview->region ()->position ();
540 if (!argl || !argl->control_points_adjacent (where, before, after)) {
544 selectables.push_back (argl->nth (before));
545 selectables.push_back (argl->nth (after));
550 selection->set (selectables);
551 _mouse_changed_selection = true;
556 selection->add (selectables);
557 _mouse_changed_selection = true;
560 case Selection::Toggle:
562 selection->toggle (selectables);
563 _mouse_changed_selection = true;
567 case Selection::Extend:
574 case AutomationLineItem:
575 if (eff_mouse_mode != MouseRange) {
576 AutomationLine* al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
577 std::list<Selectable*> selectables;
578 double mx = event->button.x;
579 double my = event->button.y;
581 al->grab_item().canvas_to_item (mx, my);
583 uint32_t before, after;
584 framecnt_t const where = (framecnt_t) floor (mx * samples_per_pixel);
586 if (!al || !al->control_points_adjacent (where, before, after)) {
590 selectables.push_back (al->nth (before));
591 selectables.push_back (al->nth (after));
596 selection->set (selectables);
597 _mouse_changed_selection = true;
602 selection->add (selectables);
603 _mouse_changed_selection = true;
606 case Selection::Toggle:
608 selection->toggle (selectables);
609 _mouse_changed_selection = true;
613 case Selection::Extend:
621 /* for context click, select track */
622 if (event->button.button == 3) {
623 selection->clear_tracks ();
624 set_selected_track_as_side_effect (op);
626 /* We won't get a release.*/
627 begin_reversible_selection_op (X_("Button 3 Menu Select"));
628 commit_reversible_selection_op ();
632 case AutomationTrackItem:
633 if (eff_mouse_mode != MouseDraw && op == Selection::Set) {
634 set_selected_track_as_side_effect (op);
639 if (press && event->button.button == 3) {
640 NoteBase* cnote = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
642 if (cnote->region_view().selection_size() == 0 || !cnote->selected()) {
643 selection->clear_points();
644 cnote->region_view().unique_select (cnote);
645 /* we won't get the release, so store the selection change now */
646 begin_reversible_selection_op (X_("Button 3 Note Selection"));
647 commit_reversible_selection_op ();
656 if ((!press) && _mouse_changed_selection) {
657 begin_reversible_selection_op (X_("Button Selection"));
658 commit_reversible_selection_op ();
659 _mouse_changed_selection = false;
664 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
666 /* single mouse clicks on any of these item types operate
667 independent of mouse mode, mostly because they are
668 not on the main track canvas or because we want
672 NoteBase* note = NULL;
675 case PlayheadCursorItem:
676 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
680 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
681 hide_marker (item, event);
683 _drags->set (new MarkerDrag (this, item), event);
687 case TempoMarkerItem:
689 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
699 new TempoMarkerDrag (
702 ArdourKeyboard::indicates_copy (event->button.state)
711 case MeterMarkerItem:
714 new MeterMarkerDrag (
717 ArdourKeyboard::indicates_copy (event->button.state)
725 _drags->set (new VideoTimeLineDrag (this, item), event);
733 case TimecodeRulerItem:
734 case SamplesRulerItem:
735 case MinsecRulerItem:
737 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)
738 && !ArdourKeyboard::indicates_constraint (event->button.state)) {
739 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
740 } else if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)
741 && Keyboard::modifier_state_contains (event->button.state, Keyboard::SecondaryModifier)) {
742 _drags->set (new TempoTwistDrag (this, item), event);
743 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
744 _drags->set (new BBTRulerDrag (this, item), event);
750 case RangeMarkerBarItem:
751 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
752 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
753 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
754 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
756 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
761 case CdMarkerBarItem:
762 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
763 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
765 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
770 case TransportMarkerBarItem:
771 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
772 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
774 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
783 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
784 /* special case: allow trim of range selections in joined object mode;
785 in theory eff should equal MouseRange in this case, but it doesn't
786 because entering the range selection canvas item results in entered_regionview
787 being set to 0, so update_join_object_range_location acts as if we aren't
790 if (item_type == StartSelectionTrimItem) {
791 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
792 } else if (item_type == EndSelectionTrimItem) {
793 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
797 Editing::MouseMode eff = effective_mouse_mode ();
799 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
800 if (get_smart_mode()) {
802 case FadeInHandleItem:
803 case FadeInTrimHandleItem:
804 case FadeOutHandleItem:
805 case FadeOutTrimHandleItem:
816 case StartSelectionTrimItem:
817 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
820 case EndSelectionTrimItem:
821 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
825 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
826 start_selection_grab (item, event);
828 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
829 /* grab selection for moving */
830 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
832 /* this was debated, but decided the more common action was to
833 make a new selection */
834 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
839 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
840 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
842 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
847 case RegionViewNameHighlight:
848 if (!clicked_regionview->region()->locked()) {
849 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
855 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
856 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
858 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
867 case FadeInHandleItem:
868 case FadeOutHandleItem:
869 case LeftFrameHandle:
870 case RightFrameHandle:
871 case FeatureLineItem:
872 case RegionViewNameHighlight:
875 case AutomationTrackItem:
876 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
887 /* Existing note: allow trimming/motion */
888 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
889 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
890 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
892 _drags->set (new NoteDrag (this, item), event);
898 _drags->set (new LineDrag (this, item), event);
902 case ControlPointItem:
903 _drags->set (new ControlPointDrag (this, item), event);
907 case AutomationLineItem:
908 _drags->set (new LineDrag (this, item), event);
913 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
916 case AutomationTrackItem:
917 /* rubberband drag to select automation points */
918 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
923 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
924 /* rubberband drag to select automation points */
925 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
936 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
937 event->type == GDK_BUTTON_PRESS) {
939 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
941 } else if (event->type == GDK_BUTTON_PRESS) {
944 case FadeInHandleItem:
946 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
950 case FadeOutHandleItem:
952 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
956 case StartCrossFadeItem:
957 case EndCrossFadeItem:
958 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
959 // if (!clicked_regionview->region()->locked()) {
960 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
965 case FeatureLineItem:
967 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
968 remove_transient(item);
972 _drags->set (new FeatureLineDrag (this, item), event);
978 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
979 /* click on an automation region view; do nothing here and let the ARV's signal handler
985 /* click on a normal region view */
986 if (ArdourKeyboard::indicates_copy (event->button.state)) {
987 add_region_copy_drag (item, event, clicked_regionview);
988 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
989 add_region_brush_drag (item, event, clicked_regionview);
991 add_region_drag (item, event, clicked_regionview);
995 _drags->start_grab (event);
999 case RegionViewNameHighlight:
1000 case LeftFrameHandle:
1001 case RightFrameHandle:
1002 if (!clicked_regionview->region()->locked()) {
1003 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1008 case FadeInTrimHandleItem:
1009 case FadeOutTrimHandleItem:
1010 if (!clicked_regionview->region()->locked()) {
1011 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
1016 case RegionViewName:
1018 /* rename happens on edit clicks */
1019 if (clicked_regionview->get_name_highlight()) {
1020 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1026 case ControlPointItem:
1027 _drags->set (new ControlPointDrag (this, item), event);
1031 case AutomationLineItem:
1032 _drags->set (new LineDrag (this, item), event);
1037 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1040 case AutomationTrackItem:
1042 TimeAxisView* parent = clicked_axisview->get_parent ();
1043 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1045 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1047 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1049 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1050 if (pl->n_regions() == 0) {
1051 /* Parent has no regions; create one so that we have somewhere to put automation */
1052 _drags->set (new RegionCreateDrag (this, item, parent), event);
1054 /* See if there's a region before the click that we can extend, and extend it if so */
1055 framepos_t const t = canvas_event_sample (event);
1056 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1058 _drags->set (new RegionCreateDrag (this, item, parent), event);
1060 prev->set_length (t - prev->position (), get_grid_music_divisions (event->button.state));
1064 /* rubberband drag to select automation points */
1065 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1087 switch (item_type) {
1089 _drags->set (new LineDrag (this, item), event);
1092 case ControlPointItem:
1093 _drags->set (new ControlPointDrag (this, item), event);
1099 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
1100 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1101 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
1102 event, _cursors->up_down);
1104 double const y = event->button.y;
1105 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
1107 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1109 /* smart "join" mode: drag automation */
1110 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1118 case AutomationLineItem:
1119 _drags->set (new LineDrag (this, item), event);
1123 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1124 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1125 /* Note is big and pointer is near the end, trim */
1126 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1129 _drags->set (new NoteDrag (this, item), event);
1136 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1137 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1148 if (item_type == NoteItem) {
1149 /* resize-drag notes */
1150 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1151 if (note->big_enough_to_trim()) {
1152 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1156 } else if (clicked_regionview) {
1158 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1164 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1165 scrub_reversals = 0;
1166 scrub_reverse_distance = 0;
1167 last_scrub_x = event->button.x;
1168 scrubbing_direction = 0;
1180 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1182 Editing::MouseMode const eff = effective_mouse_mode ();
1185 switch (item_type) {
1187 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1188 add_region_copy_drag (item, event, clicked_regionview);
1190 add_region_drag (item, event, clicked_regionview);
1192 _drags->start_grab (event);
1195 case ControlPointItem:
1196 _drags->set (new ControlPointDrag (this, item), event);
1204 switch (item_type) {
1205 case RegionViewNameHighlight:
1206 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1210 case LeftFrameHandle:
1211 case RightFrameHandle:
1212 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1216 case RegionViewName:
1217 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1231 /* relax till release */
1243 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1245 if (event->type == GDK_2BUTTON_PRESS) {
1246 _drags->mark_double_click ();
1247 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1251 if (event->type != GDK_BUTTON_PRESS) {
1255 _track_canvas->grab_focus();
1257 if (_session && _session->actively_recording()) {
1261 button_selection (item, event, item_type);
1263 if (!_drags->active () &&
1264 (Keyboard::is_delete_event (&event->button) ||
1265 Keyboard::is_context_menu_event (&event->button) ||
1266 Keyboard::is_edit_event (&event->button))) {
1268 /* handled by button release */
1272 /* not rolling, effectively in range mode, follow edits enabled (likely
1273 * to start range drag), not in a fade handle (since that means we are
1274 * not starting a range drag): locate the PH here
1277 if ((item_type != FadeInHandleItem) &&
1278 (item_type != FadeOutHandleItem) &&
1279 !_drags->active () &&
1281 !_session->transport_rolling() &&
1282 (effective_mouse_mode() == MouseRange) &&
1283 UIConfiguration::instance().get_follow_edits() &&
1284 !_session->config.get_external_sync()) {
1286 MusicFrame where (canvas_event_sample (event), 0);
1288 _session->request_locate (where.frame, false);
1291 switch (event->button.button) {
1293 return button_press_handler_1 (item, event, item_type);
1297 return button_press_handler_2 (item, event, item_type);
1304 return button_press_dispatch (&event->button);
1313 Editor::button_press_dispatch (GdkEventButton* ev)
1315 /* this function is intended only for buttons 4 and above.
1318 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1319 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1323 Editor::button_release_dispatch (GdkEventButton* ev)
1325 /* this function is intended only for buttons 4 and above.
1328 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1329 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1333 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1335 MusicFrame where (canvas_event_sample (event), 0);
1336 AutomationTimeAxisView* atv = 0;
1338 _press_cursor_ctx.reset();
1340 /* no action if we're recording */
1342 if (_session && _session->actively_recording()) {
1346 bool were_dragging = false;
1348 if (!Keyboard::is_context_menu_event (&event->button)) {
1350 /* see if we're finishing a drag */
1352 if (_drags->active ()) {
1353 bool const r = _drags->end_grab (event);
1355 /* grab dragged, so do nothing else */
1359 were_dragging = true;
1362 update_region_layering_order_editor ();
1365 /* edit events get handled here */
1367 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1368 switch (item_type) {
1370 show_region_properties ();
1372 case TempoMarkerItem: {
1373 ArdourMarker* marker;
1374 TempoMarker* tempo_marker;
1376 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1377 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1378 abort(); /*NOTREACHED*/
1381 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1382 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1383 abort(); /*NOTREACHED*/
1386 edit_tempo_marker (*tempo_marker);
1390 case MeterMarkerItem: {
1391 ArdourMarker* marker;
1392 MeterMarker* meter_marker;
1394 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1395 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1396 abort(); /*NOTREACHED*/
1399 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1400 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1401 abort(); /*NOTREACHED*/
1403 edit_meter_marker (*meter_marker);
1407 case RegionViewName:
1408 if (clicked_regionview->name_active()) {
1409 return mouse_rename_region (item, event);
1413 case ControlPointItem:
1414 edit_control_point (item);
1423 /* context menu events get handled here */
1424 if (Keyboard::is_context_menu_event (&event->button)) {
1426 context_click_event = *event;
1428 if (!_drags->active ()) {
1430 /* no matter which button pops up the context menu, tell the menu
1431 widget to use button 1 to drive menu selection.
1434 switch (item_type) {
1436 case FadeInHandleItem:
1437 case FadeInTrimHandleItem:
1438 case StartCrossFadeItem:
1439 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1443 case FadeOutHandleItem:
1444 case FadeOutTrimHandleItem:
1445 case EndCrossFadeItem:
1446 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1449 case LeftFrameHandle:
1450 case RightFrameHandle:
1454 popup_track_context_menu (1, event->button.time, item_type, false);
1458 case RegionViewNameHighlight:
1459 case RegionViewName:
1460 popup_track_context_menu (1, event->button.time, item_type, false);
1464 popup_track_context_menu (1, event->button.time, item_type, true);
1467 case AutomationTrackItem:
1468 popup_track_context_menu (1, event->button.time, item_type, false);
1472 case RangeMarkerBarItem:
1473 case TransportMarkerBarItem:
1474 case CdMarkerBarItem:
1476 case TempoCurveItem:
1479 case TimecodeRulerItem:
1480 case SamplesRulerItem:
1481 case MinsecRulerItem:
1483 popup_ruler_menu (where.frame, item_type);
1487 marker_context_menu (&event->button, item);
1490 case TempoMarkerItem:
1491 tempo_or_meter_marker_context_menu (&event->button, item);
1494 case MeterMarkerItem:
1495 tempo_or_meter_marker_context_menu (&event->button, item);
1498 case CrossfadeViewItem:
1499 popup_track_context_menu (1, event->button.time, item_type, false);
1502 case ControlPointItem:
1503 popup_control_point_context_menu (item, event);
1507 if (internal_editing()) {
1508 popup_note_context_menu (item, event);
1520 /* delete events get handled here */
1522 Editing::MouseMode const eff = effective_mouse_mode ();
1524 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1526 switch (item_type) {
1527 case TempoMarkerItem:
1528 remove_tempo_marker (item);
1531 case MeterMarkerItem:
1532 remove_meter_marker (item);
1536 remove_marker (*item, event);
1540 if (eff == MouseObject) {
1541 remove_clicked_region ();
1545 case ControlPointItem:
1546 remove_control_point (item);
1550 remove_midi_note (item, event);
1559 switch (event->button.button) {
1562 switch (item_type) {
1563 /* see comments in button_press_handler */
1564 case PlayheadCursorItem:
1567 case AutomationLineItem:
1568 case StartSelectionTrimItem:
1569 case EndSelectionTrimItem:
1573 if (!_dragging_playhead) {
1574 snap_to_with_modifier (where, event, RoundNearest, true);
1575 mouse_add_new_marker (where.frame);
1579 case CdMarkerBarItem:
1580 if (!_dragging_playhead) {
1581 // if we get here then a dragged range wasn't done
1582 snap_to_with_modifier (where, event, RoundNearest, true);
1583 mouse_add_new_marker (where.frame, true);
1587 case TempoCurveItem:
1588 if (!_dragging_playhead) {
1589 snap_to_with_modifier (where, event);
1590 mouse_add_new_tempo_event (where.frame);
1595 if (!_dragging_playhead) {
1596 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1601 case TimecodeRulerItem:
1602 case SamplesRulerItem:
1603 case MinsecRulerItem:
1614 switch (item_type) {
1617 /* check that we didn't drag before releasing, since
1618 its really annoying to create new control
1619 points when doing this.
1621 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1622 if (!were_dragging && arv) {
1623 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1624 arv->add_gain_point_event (item, event, with_guard_points);
1630 case AutomationTrackItem: {
1631 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1632 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1634 atv->add_automation_event (event, where.frame, event->button.y, with_guard_points);
1645 if (scrubbing_direction == 0) {
1646 /* no drag, just a click */
1647 switch (item_type) {
1649 play_selected_region ();
1654 } else if (_session) {
1655 /* make sure we stop */
1656 _session->request_transport_speed (0.0);
1665 /* do any (de)selection operations that should occur on button release */
1666 button_selection (item, event, item_type);
1676 switch (item_type) {
1678 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1680 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1683 // Button2 click is unused
1698 // x_style_paste (where, 1.0);
1719 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1722 ArdourMarker * marker;
1723 MeterMarker* m_marker = 0;
1724 TempoMarker* t_marker = 0;
1728 /* by the time we reach here, entered_regionview and entered trackview
1729 * will have already been set as appropriate. Things are done this
1730 * way because this method isn't passed a pointer to a variable type of
1731 * thing that is entered (which may or may not be canvas item).
1732 * (e.g. the actual entered regionview)
1735 choose_canvas_cursor_on_entry (item_type);
1737 switch (item_type) {
1738 case ControlPointItem:
1739 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1740 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1743 fraction = 1.0 - (cp->get_y() / cp->line().height());
1745 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1746 _verbose_cursor->show ();
1751 if (mouse_mode == MouseDraw) {
1752 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1754 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1759 case AutomationLineItem:
1760 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1761 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1763 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1768 case AutomationTrackItem:
1769 AutomationTimeAxisView* atv;
1770 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1771 clear_entered_track = false;
1772 set_entered_track (atv);
1777 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1780 entered_marker = marker;
1781 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1784 case MeterMarkerItem:
1785 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1788 entered_marker = m_marker;
1789 if (m_marker->meter().position_lock_style() == MusicTime) {
1790 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1792 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1796 case TempoMarkerItem:
1797 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1800 entered_marker = t_marker;
1801 if (t_marker->tempo().position_lock_style() == MusicTime) {
1802 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1804 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1808 case FadeInHandleItem:
1809 case FadeInTrimHandleItem:
1810 if (mouse_mode == MouseObject) {
1811 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1813 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1814 rect->set_fill_color (rv->get_fill_color());
1819 case FadeOutHandleItem:
1820 case FadeOutTrimHandleItem:
1821 if (mouse_mode == MouseObject) {
1822 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1824 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1825 rect->set_fill_color (rv->get_fill_color ());
1830 case FeatureLineItem:
1832 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1833 line->set_outline_color (0xFF0000FF);
1842 if (entered_regionview) {
1843 entered_regionview->entered();
1852 /* third pass to handle entered track status in a comprehensible way.
1855 switch (item_type) {
1857 case AutomationLineItem:
1858 case ControlPointItem:
1859 /* these do not affect the current entered track state */
1860 clear_entered_track = false;
1863 case AutomationTrackItem:
1864 /* handled above already */
1876 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1879 ArdourMarker *marker;
1880 TempoMarker *t_marker;
1881 MeterMarker *m_marker;
1886 if (!_enter_stack.empty()) {
1887 _enter_stack.pop_back();
1890 switch (item_type) {
1891 case ControlPointItem:
1892 _verbose_cursor->hide ();
1896 case AutomationLineItem:
1897 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1899 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1901 line->set_outline_color (al->get_line_color());
1907 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1911 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1912 location_flags_changed (loc);
1916 case MeterMarkerItem:
1917 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1921 if (m_marker->meter().position_lock_style() == MusicTime) {
1922 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1924 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1928 case TempoMarkerItem:
1929 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1933 if (t_marker->tempo().position_lock_style() == MusicTime) {
1934 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1936 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1940 case FadeInTrimHandleItem:
1941 case FadeOutTrimHandleItem:
1942 case FadeInHandleItem:
1943 case FadeOutHandleItem:
1945 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1947 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1952 case AutomationTrackItem:
1955 case FeatureLineItem:
1957 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1958 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1970 Editor::scrub (framepos_t frame, double current_x)
1974 if (scrubbing_direction == 0) {
1976 _session->request_locate (frame, false);
1977 _session->request_transport_speed (0.1);
1978 scrubbing_direction = 1;
1982 if (last_scrub_x > current_x) {
1984 /* pointer moved to the left */
1986 if (scrubbing_direction > 0) {
1988 /* we reversed direction to go backwards */
1991 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1995 /* still moving to the left (backwards) */
1997 scrub_reversals = 0;
1998 scrub_reverse_distance = 0;
2000 delta = 0.01 * (last_scrub_x - current_x);
2001 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
2005 /* pointer moved to the right */
2007 if (scrubbing_direction < 0) {
2008 /* we reversed direction to go forward */
2011 scrub_reverse_distance += (int) (current_x - last_scrub_x);
2014 /* still moving to the right */
2016 scrub_reversals = 0;
2017 scrub_reverse_distance = 0;
2019 delta = 0.01 * (current_x - last_scrub_x);
2020 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
2024 /* if there have been more than 2 opposite motion moves detected, or one that moves
2025 back more than 10 pixels, reverse direction
2028 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
2030 if (scrubbing_direction > 0) {
2031 /* was forwards, go backwards */
2032 _session->request_transport_speed (-0.1);
2033 scrubbing_direction = -1;
2035 /* was backwards, go forwards */
2036 _session->request_transport_speed (0.1);
2037 scrubbing_direction = 1;
2040 scrub_reverse_distance = 0;
2041 scrub_reversals = 0;
2045 last_scrub_x = current_x;
2049 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2051 _last_motion_y = event->motion.y;
2053 if (event->motion.is_hint) {
2056 /* We call this so that MOTION_NOTIFY events continue to be
2057 delivered to the canvas. We need to do this because we set
2058 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2059 the density of the events, at the expense of a round-trip
2060 to the server. Given that this will mostly occur on cases
2061 where DISPLAY = :0.0, and given the cost of what the motion
2062 event might do, its a good tradeoff.
2065 _track_canvas->get_pointer (x, y);
2068 if (current_stepping_trackview) {
2069 /* don't keep the persistent stepped trackview if the mouse moves */
2070 current_stepping_trackview = 0;
2071 step_timeout.disconnect ();
2074 if (_session && _session->actively_recording()) {
2075 /* Sorry. no dragging stuff around while we record */
2079 update_join_object_range_location (event->motion.y);
2081 if (_drags->active ()) {
2082 return _drags->motion_handler (event, from_autoscroll);
2089 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2091 ControlPoint* control_point;
2093 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2094 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2095 abort(); /*NOTREACHED*/
2098 AutomationLine& line = control_point->line ();
2099 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2100 /* we shouldn't remove the first or last gain point in region gain lines */
2101 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2110 Editor::remove_control_point (ArdourCanvas::Item* item)
2112 if (!can_remove_control_point (item)) {
2116 ControlPoint* control_point;
2118 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2119 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2120 abort(); /*NOTREACHED*/
2123 control_point->line().remove_point (*control_point);
2127 Editor::edit_control_point (ArdourCanvas::Item* item)
2129 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2132 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2133 abort(); /*NOTREACHED*/
2136 ControlPointDialog d (p);
2138 if (d.run () != RESPONSE_ACCEPT) {
2142 p->line().modify_point_y (*p, d.get_y_fraction ());
2146 Editor::edit_notes (MidiRegionView* mrv)
2148 MidiRegionView::Selection const & s = mrv->selection();
2154 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2157 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2161 Editor::note_edit_done (int r, EditNoteDialog* d)
2163 begin_reversible_command (_("edit note(s)"));
2168 commit_reversible_command();
2172 Editor::edit_region (RegionView* rv)
2174 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
2175 temporal_zoom_selection (Both);
2177 rv->show_region_editor ();
2182 Editor::visible_order_range (int* low, int* high) const
2184 *low = TimeAxisView::max_order ();
2187 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2189 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2191 if (rtv && !rtv->hidden()) {
2193 if (*high < rtv->order()) {
2194 *high = rtv->order ();
2197 if (*low > rtv->order()) {
2198 *low = rtv->order ();
2205 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2207 /* Either add to or set the set the region selection, unless
2208 this is an alignment click (control used)
2211 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2212 TimeAxisView* tv = &rv.get_time_axis_view();
2213 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2215 if (rtv && rtv->is_track()) {
2216 speed = rtv->track()->speed();
2219 framepos_t where = get_preferred_edit_position();
2223 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2225 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2227 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2229 align_region (rv.region(), End, (framepos_t) (where * speed));
2233 align_region (rv.region(), Start, (framepos_t) (where * speed));
2240 Editor::collect_new_region_view (RegionView* rv)
2242 latest_regionviews.push_back (rv);
2246 Editor::collect_and_select_new_region_view (RegionView* rv)
2249 latest_regionviews.push_back (rv);
2253 Editor::cancel_selection ()
2255 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2256 (*i)->hide_selection ();
2259 selection->clear ();
2260 clicked_selection = 0;
2264 Editor::cancel_time_selection ()
2266 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2267 (*i)->hide_selection ();
2269 selection->time.clear ();
2270 clicked_selection = 0;
2274 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2276 RegionView* rv = clicked_regionview;
2278 /* Choose action dependant on which button was pressed */
2279 switch (event->button.button) {
2281 begin_reversible_command (_("start point trim"));
2283 if (selection->selected (rv)) {
2284 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2285 i != selection->regions.by_layer().end(); ++i)
2287 if (!(*i)->region()->locked()) {
2288 (*i)->region()->clear_changes ();
2289 (*i)->region()->trim_front (new_bound);
2290 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2295 if (!rv->region()->locked()) {
2296 rv->region()->clear_changes ();
2297 rv->region()->trim_front (new_bound);
2298 _session->add_command(new StatefulDiffCommand (rv->region()));
2302 commit_reversible_command();
2306 begin_reversible_command (_("end point trim"));
2308 if (selection->selected (rv)) {
2310 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2312 if (!(*i)->region()->locked()) {
2313 (*i)->region()->clear_changes();
2314 (*i)->region()->trim_end (new_bound);
2315 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2321 if (!rv->region()->locked()) {
2322 rv->region()->clear_changes ();
2323 rv->region()->trim_end (new_bound);
2324 _session->add_command (new StatefulDiffCommand (rv->region()));
2328 commit_reversible_command();
2337 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2339 ArdourMarker* marker;
2342 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2343 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2344 abort(); /*NOTREACHED*/
2347 Location* location = find_location_from_marker (marker, is_start);
2348 location->set_hidden (true, this);
2352 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2354 using namespace Gtkmm2ext;
2356 ArdourPrompter prompter (false);
2358 prompter.set_prompt (_("Name for region:"));
2359 prompter.set_initial_text (clicked_regionview->region()->name());
2360 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2361 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2362 prompter.show_all ();
2363 switch (prompter.run ()) {
2364 case Gtk::RESPONSE_ACCEPT:
2366 prompter.get_result(str);
2368 clicked_regionview->region()->set_name (str);
2377 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2379 /* no brushing without a useful snap setting */
2381 switch (_snap_mode) {
2383 return; /* can't work because it allows region to be placed anywhere */
2388 switch (_snap_type) {
2396 /* don't brush a copy over the original */
2398 if (pos == rv->region()->position()) {
2402 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2404 if (!rtv || !rtv->is_track()) {
2408 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2409 double speed = rtv->track()->speed();
2411 playlist->clear_changes ();
2412 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2413 playlist->add_region (new_region, (framepos_t) (pos * speed));
2414 _session->add_command (new StatefulDiffCommand (playlist));
2416 // playlist is frozen, so we have to update manually XXX this is disgusting
2418 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2422 Editor::track_height_step_timeout ()
2424 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2425 current_stepping_trackview = 0;
2432 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2434 assert (region_view);
2436 if (!region_view->region()->playlist()) {
2440 switch (Config->get_edit_mode()) {
2442 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2445 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2448 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2455 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2457 assert (region_view);
2459 if (!region_view->region()->playlist()) {
2463 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2467 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2469 assert (region_view);
2471 if (!region_view->region()->playlist()) {
2475 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2479 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2482 /** Start a grab where a time range is selected, track(s) are selected, and the
2483 * user clicks and drags a region with a modifier in order to create a new region containing
2484 * the section of the clicked region that lies within the time range.
2487 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2489 if (clicked_regionview == 0) {
2493 /* lets try to create new Region for the selection */
2495 vector<boost::shared_ptr<Region> > new_regions;
2496 create_region_from_selection (new_regions);
2498 if (new_regions.empty()) {
2502 /* XXX fix me one day to use all new regions */
2504 boost::shared_ptr<Region> region (new_regions.front());
2506 /* add it to the current stream/playlist.
2508 tricky: the streamview for the track will add a new regionview. we will
2509 catch the signal it sends when it creates the regionview to
2510 set the regionview we want to then drag.
2513 latest_regionviews.clear();
2514 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2516 /* A selection grab currently creates two undo/redo operations, one for
2517 creating the new region and another for moving it.
2519 begin_reversible_command (Operations::selection_grab);
2521 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2523 playlist->clear_changes ();
2524 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2525 _session->add_command(new StatefulDiffCommand (playlist));
2529 if (latest_regionviews.empty()) {
2530 /* something went wrong */
2531 abort_reversible_command ();
2535 /* we need to deselect all other regionviews, and select this one
2536 i'm ignoring undo stuff, because the region creation will take care of it
2539 selection->set (latest_regionviews);
2541 commit_reversible_command ();
2543 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2549 if (_drags->active ()) {
2552 selection->clear ();
2555 ARDOUR_UI::instance()->reset_focus (&contents());
2558 /** Update _join_object_range_state which indicate whether we are over the top
2559 * or bottom half of a route view, used by the `join object/range' tool
2560 * mode. Coordinates in canvas space.
2563 Editor::update_join_object_range_location (double y)
2565 if (!get_smart_mode()) {
2566 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2570 JoinObjectRangeState const old = _join_object_range_state;
2572 if (mouse_mode == MouseObject) {
2573 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2574 } else if (mouse_mode == MouseRange) {
2575 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2578 if (entered_regionview) {
2580 //ToDo: there is currently a bug here(?)
2581 //when we are inside a region fade handle, it acts as though we are in range mode because it is in the top half of the region
2582 //can it be fixed here?
2584 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2585 double const c = item_space.y / entered_regionview->height();
2587 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2589 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2590 if (_join_object_range_state != old && ctx) {
2591 ctx->cursor_ctx->change(which_track_cursor());
2594 } else if (entered_track) {
2596 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2598 if (entered_route_view) {
2603 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2605 double track_height = entered_route_view->view()->child_height();
2606 if (UIConfiguration::instance().get_show_name_highlight()) {
2607 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2609 double const c = cy / track_height;
2613 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2615 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2619 /* Other kinds of tracks use object mode */
2620 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2623 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2624 if (_join_object_range_state != old && ctx) {
2625 ctx->cursor_ctx->change(which_track_cursor());
2631 Editor::effective_mouse_mode () const
2633 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2635 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2643 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2645 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2648 e->region_view().delete_note (e->note ());
2651 /** Obtain the pointer position in canvas coordinates */
2653 Editor::get_pointer_position (double& x, double& y) const
2656 _track_canvas->get_pointer (px, py);
2657 _track_canvas->window_to_canvas (px, py, x, y);