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_midi_notes ();
343 /* Clear regions, but not time or tracks, since that
344 would destroy the range selection rectangle, which we need to stick
345 around for AutomationRangeDrag. */
346 selection->clear_regions ();
347 selection->clear_playlists ();
350 /* This handles internal edit.
351 Clear everything except points and notes.
353 selection->clear_regions();
354 selection->clear_lines();
355 selection->clear_playlists ();
357 selection->clear_time ();
358 selection->clear_tracks ();
362 /* We probably want to keep region selection */
363 selection->clear_points ();
364 selection->clear_lines();
365 selection->clear_playlists ();
367 selection->clear_time ();
368 selection->clear_tracks ();
372 /*Don't lose lines or points if no action in this mode */
373 selection->clear_regions ();
374 selection->clear_playlists ();
375 selection->clear_time ();
376 selection->clear_tracks ();
380 /*Clear everything */
381 selection->clear_objects();
382 selection->clear_time ();
383 selection->clear_tracks ();
389 Editor::step_mouse_mode (bool next)
391 const int n_mouse_modes = (int)MouseContent + 1;
392 int current = (int)current_mouse_mode();
394 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
396 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
401 Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
404 /* in object/audition/timefx/gain-automation mode,
405 any button press sets the selection if the object
406 can be selected. this is a bit of hack, because
407 we want to avoid this if the mouse operation is a
410 note: not dbl-click or triple-click
412 Also note that there is no region selection in internal edit mode, otherwise
413 for operations operating on the selection (e.g. cut) it is not obvious whether
414 to cut notes or regions.
417 MouseMode eff_mouse_mode = effective_mouse_mode ();
419 if (eff_mouse_mode == MouseCut) {
420 /* never change selection in cut mode */
424 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
425 /* context clicks are always about object properties, even if
426 we're in range mode within smart mode.
428 eff_mouse_mode = MouseObject;
431 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
432 if (get_smart_mode()) {
434 case FadeInHandleItem:
435 case FadeInTrimHandleItem:
436 case FadeOutHandleItem:
437 case FadeOutTrimHandleItem:
438 eff_mouse_mode = MouseObject;
445 if (((mouse_mode != MouseObject) &&
446 (mouse_mode != MouseAudition || item_type != RegionItem) &&
447 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
448 (mouse_mode != MouseDraw) &&
449 (mouse_mode != MouseContent || item_type == RegionItem)) ||
450 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
454 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
456 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
458 /* almost no selection action on modified button-2 or button-3 events */
460 if ((item_type != RegionItem && event->button.button != 2)
461 /* for selection of control points prior to delete (shift-right click) */
462 && !(item_type == ControlPointItem && event->button.button == 3 && event->type == GDK_BUTTON_PRESS)) {
468 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
469 bool press = (event->type == GDK_BUTTON_PRESS);
472 _mouse_changed_selection = false;
477 if (eff_mouse_mode == MouseDraw) {
481 if (eff_mouse_mode != MouseRange) {
482 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
484 /* don't change the selection unless the
485 clicked track is not currently selected. if
486 so, "collapse" the selection to just this
489 if (!selection->selected (clicked_axisview)) {
490 set_selected_track_as_side_effect (Selection::Set);
494 if (eff_mouse_mode != MouseRange) {
495 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
500 case RegionViewNameHighlight:
502 case LeftFrameHandle:
503 case RightFrameHandle:
504 case FadeInHandleItem:
505 case FadeInTrimHandleItem:
507 case FadeOutHandleItem:
508 case FadeOutTrimHandleItem:
510 case StartCrossFadeItem:
511 case EndCrossFadeItem:
512 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
513 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
514 } else if (event->type == GDK_BUTTON_PRESS) {
515 set_selected_track_as_side_effect (op);
519 case ControlPointItem:
520 /* for object/track exclusivity, we don't call set_selected_track_as_side_effect (op); */
522 if (eff_mouse_mode != MouseRange) {
523 if (event->button.button != 3) {
524 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
526 _mouse_changed_selection |= set_selected_control_point_from_click (press, Selection::Set);
532 if (eff_mouse_mode != MouseRange) {
533 AutomationLine* argl = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
535 std::list<Selectable*> selectables;
536 uint32_t before, after;
537 framecnt_t const where = (framecnt_t) floor (event->button.x * samples_per_pixel) - clicked_regionview->region ()->position ();
539 if (!argl || !argl->control_points_adjacent (where, before, after)) {
543 selectables.push_back (argl->nth (before));
544 selectables.push_back (argl->nth (after));
549 selection->set (selectables);
550 _mouse_changed_selection = true;
555 selection->add (selectables);
556 _mouse_changed_selection = true;
559 case Selection::Toggle:
561 selection->toggle (selectables);
562 _mouse_changed_selection = true;
566 case Selection::Extend:
573 case AutomationLineItem:
574 if (eff_mouse_mode != MouseRange) {
575 AutomationLine* al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
576 std::list<Selectable*> selectables;
577 double mx = event->button.x;
578 double my = event->button.y;
580 al->grab_item().canvas_to_item (mx, my);
582 uint32_t before, after;
583 framecnt_t const where = (framecnt_t) floor (mx * samples_per_pixel);
585 if (!al || !al->control_points_adjacent (where, before, after)) {
589 selectables.push_back (al->nth (before));
590 selectables.push_back (al->nth (after));
595 selection->set (selectables);
596 _mouse_changed_selection = true;
601 selection->add (selectables);
602 _mouse_changed_selection = true;
605 case Selection::Toggle:
607 selection->toggle (selectables);
608 _mouse_changed_selection = true;
612 case Selection::Extend:
620 /* for context click, select track */
621 if (event->button.button == 3) {
622 selection->clear_tracks ();
623 set_selected_track_as_side_effect (op);
625 /* We won't get a release.*/
626 begin_reversible_selection_op (X_("Button 3 Menu Select"));
627 commit_reversible_selection_op ();
631 case AutomationTrackItem:
632 if (eff_mouse_mode != MouseDraw && op == Selection::Set) {
633 set_selected_track_as_side_effect (op);
638 if (press && event->button.button == 3) {
639 NoteBase* cnote = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
641 if (cnote->region_view().selection_size() == 0 || !cnote->selected()) {
642 selection->clear_points();
643 cnote->region_view().unique_select (cnote);
644 /* we won't get the release, so store the selection change now */
645 begin_reversible_selection_op (X_("Button 3 Note Selection"));
646 commit_reversible_selection_op ();
655 if ((!press) && _mouse_changed_selection) {
656 begin_reversible_selection_op (X_("Button Selection"));
657 commit_reversible_selection_op ();
658 _mouse_changed_selection = false;
663 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
665 /* single mouse clicks on any of these item types operate
666 independent of mouse mode, mostly because they are
667 not on the main track canvas or because we want
671 NoteBase* note = NULL;
674 case PlayheadCursorItem:
675 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
679 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
680 hide_marker (item, event);
682 _drags->set (new MarkerDrag (this, item), event);
686 case TempoMarkerItem:
688 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
698 new TempoMarkerDrag (
701 ArdourKeyboard::indicates_copy (event->button.state)
710 case MeterMarkerItem:
713 new MeterMarkerDrag (
716 ArdourKeyboard::indicates_copy (event->button.state)
724 _drags->set (new VideoTimeLineDrag (this, item), event);
732 case TimecodeRulerItem:
733 case SamplesRulerItem:
734 case MinsecRulerItem:
736 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)
737 && !ArdourKeyboard::indicates_constraint (event->button.state)) {
738 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
739 } else if (ArdourKeyboard::indicates_constraint (event->button.state)
740 && Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
741 _drags->set (new TempoTwistDrag (this, item), event);
742 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
743 _drags->set (new BBTRulerDrag (this, item), event);
749 case RangeMarkerBarItem:
750 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
751 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
752 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
753 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
755 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
760 case CdMarkerBarItem:
761 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
762 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
764 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
769 case TransportMarkerBarItem:
770 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
771 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
773 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
782 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
783 /* special case: allow trim of range selections in joined object mode;
784 in theory eff should equal MouseRange in this case, but it doesn't
785 because entering the range selection canvas item results in entered_regionview
786 being set to 0, so update_join_object_range_location acts as if we aren't
789 if (item_type == StartSelectionTrimItem) {
790 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
791 } else if (item_type == EndSelectionTrimItem) {
792 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
796 Editing::MouseMode eff = effective_mouse_mode ();
798 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
799 if (get_smart_mode()) {
801 case FadeInHandleItem:
802 case FadeInTrimHandleItem:
803 case FadeOutHandleItem:
804 case FadeOutTrimHandleItem:
815 case StartSelectionTrimItem:
816 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
819 case EndSelectionTrimItem:
820 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
824 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
825 start_selection_grab (item, event);
827 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
828 /* grab selection for moving */
829 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
831 /* this was debated, but decided the more common action was to
832 make a new selection */
833 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
838 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
839 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
841 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
846 case RegionViewNameHighlight:
847 if (!clicked_regionview->region()->locked()) {
848 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
854 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
855 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
857 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
866 case FadeInHandleItem:
867 case FadeOutHandleItem:
868 case LeftFrameHandle:
869 case RightFrameHandle:
870 case FeatureLineItem:
871 case RegionViewNameHighlight:
874 case AutomationTrackItem:
875 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
886 /* Existing note: allow trimming/motion */
887 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
888 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
889 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
891 _drags->set (new NoteDrag (this, item), event);
897 _drags->set (new LineDrag (this, item), event);
901 case ControlPointItem:
902 _drags->set (new ControlPointDrag (this, item), event);
906 case AutomationLineItem:
907 _drags->set (new LineDrag (this, item), event);
912 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
915 case AutomationTrackItem:
916 /* rubberband drag to select automation points */
917 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
922 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
923 /* rubberband drag to select automation points */
924 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
935 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
936 event->type == GDK_BUTTON_PRESS) {
938 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
940 } else if (event->type == GDK_BUTTON_PRESS) {
943 case FadeInHandleItem:
945 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
949 case FadeOutHandleItem:
951 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
955 case StartCrossFadeItem:
956 case EndCrossFadeItem:
957 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
958 // if (!clicked_regionview->region()->locked()) {
959 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
964 case FeatureLineItem:
966 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
967 remove_transient(item);
971 _drags->set (new FeatureLineDrag (this, item), event);
977 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
978 /* click on an automation region view; do nothing here and let the ARV's signal handler
984 /* click on a normal region view */
985 if (ArdourKeyboard::indicates_copy (event->button.state)) {
986 add_region_copy_drag (item, event, clicked_regionview);
987 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
988 add_region_brush_drag (item, event, clicked_regionview);
990 add_region_drag (item, event, clicked_regionview);
994 _drags->start_grab (event);
998 case RegionViewNameHighlight:
999 case LeftFrameHandle:
1000 case RightFrameHandle:
1001 if (!clicked_regionview->region()->locked()) {
1002 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1007 case FadeInTrimHandleItem:
1008 case FadeOutTrimHandleItem:
1009 if (!clicked_regionview->region()->locked()) {
1010 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
1015 case RegionViewName:
1017 /* rename happens on edit clicks */
1018 if (clicked_regionview->get_name_highlight()) {
1019 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1025 case ControlPointItem:
1026 _drags->set (new ControlPointDrag (this, item), event);
1030 case AutomationLineItem:
1031 _drags->set (new LineDrag (this, item), event);
1036 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1039 case AutomationTrackItem:
1041 TimeAxisView* parent = clicked_axisview->get_parent ();
1042 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1044 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1046 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1048 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1049 if (pl->n_regions() == 0) {
1050 /* Parent has no regions; create one so that we have somewhere to put automation */
1051 _drags->set (new RegionCreateDrag (this, item, parent), event);
1053 /* See if there's a region before the click that we can extend, and extend it if so */
1054 framepos_t const t = canvas_event_sample (event);
1055 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1057 _drags->set (new RegionCreateDrag (this, item, parent), event);
1059 prev->set_length (t - prev->position (), get_grid_music_divisions (event->button.state));
1063 /* rubberband drag to select automation points */
1064 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1086 switch (item_type) {
1088 _drags->set (new LineDrag (this, item), event);
1091 case ControlPointItem:
1092 _drags->set (new ControlPointDrag (this, item), event);
1098 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
1099 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1100 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
1101 event, _cursors->up_down);
1103 double const y = event->button.y;
1104 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
1106 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1108 /* smart "join" mode: drag automation */
1109 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1117 case AutomationLineItem:
1118 _drags->set (new LineDrag (this, item), event);
1122 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1123 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1124 /* Note is big and pointer is near the end, trim */
1125 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1128 _drags->set (new NoteDrag (this, item), event);
1135 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1136 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1147 if (item_type == NoteItem) {
1148 /* resize-drag notes */
1149 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1150 if (note->big_enough_to_trim()) {
1151 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1155 } else if (clicked_regionview) {
1157 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1163 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1164 scrub_reversals = 0;
1165 scrub_reverse_distance = 0;
1166 last_scrub_x = event->button.x;
1167 scrubbing_direction = 0;
1179 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1181 Editing::MouseMode const eff = effective_mouse_mode ();
1184 switch (item_type) {
1186 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1187 add_region_copy_drag (item, event, clicked_regionview);
1189 add_region_drag (item, event, clicked_regionview);
1191 _drags->start_grab (event);
1194 case ControlPointItem:
1195 _drags->set (new ControlPointDrag (this, item), event);
1203 switch (item_type) {
1204 case RegionViewNameHighlight:
1205 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1209 case LeftFrameHandle:
1210 case RightFrameHandle:
1211 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1215 case RegionViewName:
1216 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1230 /* relax till release */
1242 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1244 if (event->type == GDK_2BUTTON_PRESS) {
1245 _drags->mark_double_click ();
1246 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1250 if (event->type != GDK_BUTTON_PRESS) {
1254 _track_canvas->grab_focus();
1256 if (_session && _session->actively_recording()) {
1260 button_selection (item, event, item_type);
1262 if (!_drags->active () &&
1263 (Keyboard::is_delete_event (&event->button) ||
1264 Keyboard::is_context_menu_event (&event->button) ||
1265 Keyboard::is_edit_event (&event->button))) {
1267 /* handled by button release */
1271 /* not rolling, effectively in range mode, follow edits enabled (likely
1272 * to start range drag), not in a fade handle (since that means we are
1273 * not starting a range drag): locate the PH here
1276 if ((item_type != FadeInHandleItem) &&
1277 (item_type != FadeOutHandleItem) &&
1278 !_drags->active () &&
1280 !_session->transport_rolling() &&
1281 (effective_mouse_mode() == MouseRange) &&
1282 UIConfiguration::instance().get_follow_edits() &&
1283 !_session->config.get_external_sync()) {
1285 MusicFrame where (canvas_event_sample (event), 0);
1287 _session->request_locate (where.frame, false);
1290 switch (event->button.button) {
1292 return button_press_handler_1 (item, event, item_type);
1296 return button_press_handler_2 (item, event, item_type);
1303 return button_press_dispatch (&event->button);
1312 Editor::button_press_dispatch (GdkEventButton* ev)
1314 /* this function is intended only for buttons 4 and above.
1317 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1318 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1322 Editor::button_release_dispatch (GdkEventButton* ev)
1324 /* this function is intended only for buttons 4 and above.
1327 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1328 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1332 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1334 MusicFrame where (canvas_event_sample (event), 0);
1335 AutomationTimeAxisView* atv = 0;
1337 _press_cursor_ctx.reset();
1339 /* no action if we're recording */
1341 if (_session && _session->actively_recording()) {
1345 bool were_dragging = false;
1347 if (!Keyboard::is_context_menu_event (&event->button)) {
1349 /* see if we're finishing a drag */
1351 if (_drags->active ()) {
1352 bool const r = _drags->end_grab (event);
1354 /* grab dragged, so do nothing else */
1358 were_dragging = true;
1361 update_region_layering_order_editor ();
1364 /* edit events get handled here */
1366 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1367 switch (item_type) {
1369 show_region_properties ();
1371 case TempoMarkerItem: {
1372 ArdourMarker* marker;
1373 TempoMarker* tempo_marker;
1375 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1376 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1377 abort(); /*NOTREACHED*/
1380 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1381 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1382 abort(); /*NOTREACHED*/
1385 edit_tempo_marker (*tempo_marker);
1389 case MeterMarkerItem: {
1390 ArdourMarker* marker;
1391 MeterMarker* meter_marker;
1393 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1394 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1395 abort(); /*NOTREACHED*/
1398 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1399 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1400 abort(); /*NOTREACHED*/
1402 edit_meter_marker (*meter_marker);
1406 case RegionViewName:
1407 if (clicked_regionview->name_active()) {
1408 return mouse_rename_region (item, event);
1412 case ControlPointItem:
1413 edit_control_point (item);
1422 /* context menu events get handled here */
1423 if (Keyboard::is_context_menu_event (&event->button)) {
1425 context_click_event = *event;
1427 if (!_drags->active ()) {
1429 /* no matter which button pops up the context menu, tell the menu
1430 widget to use button 1 to drive menu selection.
1433 switch (item_type) {
1435 case FadeInHandleItem:
1436 case FadeInTrimHandleItem:
1437 case StartCrossFadeItem:
1438 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1442 case FadeOutHandleItem:
1443 case FadeOutTrimHandleItem:
1444 case EndCrossFadeItem:
1445 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1448 case LeftFrameHandle:
1449 case RightFrameHandle:
1453 popup_track_context_menu (1, event->button.time, item_type, false);
1457 case RegionViewNameHighlight:
1458 case RegionViewName:
1459 popup_track_context_menu (1, event->button.time, item_type, false);
1463 popup_track_context_menu (1, event->button.time, item_type, true);
1466 case AutomationTrackItem:
1467 popup_track_context_menu (1, event->button.time, item_type, false);
1471 case RangeMarkerBarItem:
1472 case TransportMarkerBarItem:
1473 case CdMarkerBarItem:
1475 case TempoCurveItem:
1478 case TimecodeRulerItem:
1479 case SamplesRulerItem:
1480 case MinsecRulerItem:
1482 popup_ruler_menu (where.frame, item_type);
1486 marker_context_menu (&event->button, item);
1489 case TempoMarkerItem:
1490 tempo_or_meter_marker_context_menu (&event->button, item);
1493 case MeterMarkerItem:
1494 tempo_or_meter_marker_context_menu (&event->button, item);
1497 case CrossfadeViewItem:
1498 popup_track_context_menu (1, event->button.time, item_type, false);
1501 case ControlPointItem:
1502 popup_control_point_context_menu (item, event);
1506 if (internal_editing()) {
1507 popup_note_context_menu (item, event);
1519 /* delete events get handled here */
1521 Editing::MouseMode const eff = effective_mouse_mode ();
1523 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1525 switch (item_type) {
1526 case TempoMarkerItem:
1527 remove_tempo_marker (item);
1530 case MeterMarkerItem:
1531 remove_meter_marker (item);
1535 remove_marker (*item, event);
1539 if (eff == MouseObject) {
1540 remove_clicked_region ();
1544 case ControlPointItem:
1545 remove_control_point (item);
1549 remove_midi_note (item, event);
1558 switch (event->button.button) {
1561 switch (item_type) {
1562 /* see comments in button_press_handler */
1563 case PlayheadCursorItem:
1566 case AutomationLineItem:
1567 case StartSelectionTrimItem:
1568 case EndSelectionTrimItem:
1572 if (!_dragging_playhead) {
1573 snap_to_with_modifier (where, event, RoundNearest, true);
1574 mouse_add_new_marker (where.frame);
1578 case CdMarkerBarItem:
1579 if (!_dragging_playhead) {
1580 // if we get here then a dragged range wasn't done
1581 snap_to_with_modifier (where, event, RoundNearest, true);
1582 mouse_add_new_marker (where.frame, true);
1586 case TempoCurveItem:
1587 if (!_dragging_playhead) {
1588 snap_to_with_modifier (where, event);
1589 mouse_add_new_tempo_event (where.frame);
1594 if (!_dragging_playhead) {
1595 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1600 case TimecodeRulerItem:
1601 case SamplesRulerItem:
1602 case MinsecRulerItem:
1613 switch (item_type) {
1616 /* check that we didn't drag before releasing, since
1617 its really annoying to create new control
1618 points when doing this.
1620 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1621 if (!were_dragging && arv) {
1622 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1623 arv->add_gain_point_event (item, event, with_guard_points);
1629 case AutomationTrackItem: {
1630 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1631 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1633 atv->add_automation_event (event, where.frame, event->button.y, with_guard_points);
1644 if (scrubbing_direction == 0) {
1645 /* no drag, just a click */
1646 switch (item_type) {
1648 play_selected_region ();
1653 } else if (_session) {
1654 /* make sure we stop */
1655 _session->request_transport_speed (0.0);
1664 /* do any (de)selection operations that should occur on button release */
1665 button_selection (item, event, item_type);
1675 switch (item_type) {
1677 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1679 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1682 // Button2 click is unused
1697 // x_style_paste (where, 1.0);
1718 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1721 ArdourMarker * marker;
1722 MeterMarker* m_marker = 0;
1723 TempoMarker* t_marker = 0;
1727 /* by the time we reach here, entered_regionview and entered trackview
1728 * will have already been set as appropriate. Things are done this
1729 * way because this method isn't passed a pointer to a variable type of
1730 * thing that is entered (which may or may not be canvas item).
1731 * (e.g. the actual entered regionview)
1734 choose_canvas_cursor_on_entry (item_type);
1736 switch (item_type) {
1737 case ControlPointItem:
1738 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1739 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1742 fraction = 1.0 - (cp->get_y() / cp->line().height());
1744 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1745 _verbose_cursor->show ();
1750 if (mouse_mode == MouseDraw) {
1751 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1753 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1758 case AutomationLineItem:
1759 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1760 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1762 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1767 case AutomationTrackItem:
1768 AutomationTimeAxisView* atv;
1769 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1770 clear_entered_track = false;
1771 set_entered_track (atv);
1776 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1779 entered_marker = marker;
1780 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1783 case MeterMarkerItem:
1784 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1787 entered_marker = m_marker;
1788 if (m_marker->meter().position_lock_style() == MusicTime) {
1789 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1791 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1795 case TempoMarkerItem:
1796 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1799 entered_marker = t_marker;
1800 if (t_marker->tempo().position_lock_style() == MusicTime) {
1801 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1803 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1807 case FadeInHandleItem:
1808 case FadeInTrimHandleItem:
1809 if (mouse_mode == MouseObject) {
1810 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1812 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1813 rect->set_fill_color (rv->get_fill_color());
1818 case FadeOutHandleItem:
1819 case FadeOutTrimHandleItem:
1820 if (mouse_mode == MouseObject) {
1821 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1823 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1824 rect->set_fill_color (rv->get_fill_color ());
1829 case FeatureLineItem:
1831 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1832 line->set_outline_color (0xFF0000FF);
1841 if (entered_regionview) {
1842 entered_regionview->entered();
1851 /* third pass to handle entered track status in a comprehensible way.
1854 switch (item_type) {
1856 case AutomationLineItem:
1857 case ControlPointItem:
1858 /* these do not affect the current entered track state */
1859 clear_entered_track = false;
1862 case AutomationTrackItem:
1863 /* handled above already */
1875 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1878 ArdourMarker *marker;
1879 TempoMarker *t_marker;
1880 MeterMarker *m_marker;
1885 if (!_enter_stack.empty()) {
1886 _enter_stack.pop_back();
1889 switch (item_type) {
1890 case ControlPointItem:
1891 _verbose_cursor->hide ();
1895 case AutomationLineItem:
1896 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1898 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1900 line->set_outline_color (al->get_line_color());
1906 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1910 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1911 location_flags_changed (loc);
1915 case MeterMarkerItem:
1916 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1920 if (m_marker->meter().position_lock_style() == MusicTime) {
1921 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1923 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1927 case TempoMarkerItem:
1928 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1932 if (t_marker->tempo().position_lock_style() == MusicTime) {
1933 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1935 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1939 case FadeInTrimHandleItem:
1940 case FadeOutTrimHandleItem:
1941 case FadeInHandleItem:
1942 case FadeOutHandleItem:
1944 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1946 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1951 case AutomationTrackItem:
1954 case FeatureLineItem:
1956 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1957 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1969 Editor::scrub (framepos_t frame, double current_x)
1973 if (scrubbing_direction == 0) {
1975 _session->request_locate (frame, false);
1976 _session->request_transport_speed (0.1);
1977 scrubbing_direction = 1;
1981 if (last_scrub_x > current_x) {
1983 /* pointer moved to the left */
1985 if (scrubbing_direction > 0) {
1987 /* we reversed direction to go backwards */
1990 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1994 /* still moving to the left (backwards) */
1996 scrub_reversals = 0;
1997 scrub_reverse_distance = 0;
1999 delta = 0.01 * (last_scrub_x - current_x);
2000 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
2004 /* pointer moved to the right */
2006 if (scrubbing_direction < 0) {
2007 /* we reversed direction to go forward */
2010 scrub_reverse_distance += (int) (current_x - last_scrub_x);
2013 /* still moving to the right */
2015 scrub_reversals = 0;
2016 scrub_reverse_distance = 0;
2018 delta = 0.01 * (current_x - last_scrub_x);
2019 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
2023 /* if there have been more than 2 opposite motion moves detected, or one that moves
2024 back more than 10 pixels, reverse direction
2027 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
2029 if (scrubbing_direction > 0) {
2030 /* was forwards, go backwards */
2031 _session->request_transport_speed (-0.1);
2032 scrubbing_direction = -1;
2034 /* was backwards, go forwards */
2035 _session->request_transport_speed (0.1);
2036 scrubbing_direction = 1;
2039 scrub_reverse_distance = 0;
2040 scrub_reversals = 0;
2044 last_scrub_x = current_x;
2048 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2050 _last_motion_y = event->motion.y;
2052 if (event->motion.is_hint) {
2055 /* We call this so that MOTION_NOTIFY events continue to be
2056 delivered to the canvas. We need to do this because we set
2057 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2058 the density of the events, at the expense of a round-trip
2059 to the server. Given that this will mostly occur on cases
2060 where DISPLAY = :0.0, and given the cost of what the motion
2061 event might do, its a good tradeoff.
2064 _track_canvas->get_pointer (x, y);
2067 if (current_stepping_trackview) {
2068 /* don't keep the persistent stepped trackview if the mouse moves */
2069 current_stepping_trackview = 0;
2070 step_timeout.disconnect ();
2073 if (_session && _session->actively_recording()) {
2074 /* Sorry. no dragging stuff around while we record */
2078 update_join_object_range_location (event->motion.y);
2080 if (_drags->active ()) {
2081 return _drags->motion_handler (event, from_autoscroll);
2088 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2090 ControlPoint* control_point;
2092 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2093 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2094 abort(); /*NOTREACHED*/
2097 AutomationLine& line = control_point->line ();
2098 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2099 /* we shouldn't remove the first or last gain point in region gain lines */
2100 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2109 Editor::remove_control_point (ArdourCanvas::Item* item)
2111 if (!can_remove_control_point (item)) {
2115 ControlPoint* control_point;
2117 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2118 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2119 abort(); /*NOTREACHED*/
2122 control_point->line().remove_point (*control_point);
2126 Editor::edit_control_point (ArdourCanvas::Item* item)
2128 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2131 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2132 abort(); /*NOTREACHED*/
2135 ControlPointDialog d (p);
2137 if (d.run () != RESPONSE_ACCEPT) {
2141 p->line().modify_point_y (*p, d.get_y_fraction ());
2145 Editor::edit_notes (MidiRegionView* mrv)
2147 MidiRegionView::Selection const & s = mrv->selection();
2153 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2156 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2160 Editor::note_edit_done (int r, EditNoteDialog* d)
2162 begin_reversible_command (_("edit note(s)"));
2167 commit_reversible_command();
2171 Editor::edit_region (RegionView* rv)
2173 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
2174 temporal_zoom_selection (Both);
2176 rv->show_region_editor ();
2181 Editor::visible_order_range (int* low, int* high) const
2183 *low = TimeAxisView::max_order ();
2186 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2188 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2190 if (rtv && !rtv->hidden()) {
2192 if (*high < rtv->order()) {
2193 *high = rtv->order ();
2196 if (*low > rtv->order()) {
2197 *low = rtv->order ();
2204 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2206 /* Either add to or set the set the region selection, unless
2207 this is an alignment click (control used)
2210 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2211 TimeAxisView* tv = &rv.get_time_axis_view();
2212 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2214 if (rtv && rtv->is_track()) {
2215 speed = rtv->track()->speed();
2218 framepos_t where = get_preferred_edit_position();
2222 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2224 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2226 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2228 align_region (rv.region(), End, (framepos_t) (where * speed));
2232 align_region (rv.region(), Start, (framepos_t) (where * speed));
2239 Editor::collect_new_region_view (RegionView* rv)
2241 latest_regionviews.push_back (rv);
2245 Editor::collect_and_select_new_region_view (RegionView* rv)
2248 latest_regionviews.push_back (rv);
2252 Editor::cancel_selection ()
2254 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2255 (*i)->hide_selection ();
2258 selection->clear ();
2259 clicked_selection = 0;
2263 Editor::cancel_time_selection ()
2265 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2266 (*i)->hide_selection ();
2268 selection->time.clear ();
2269 clicked_selection = 0;
2273 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2275 RegionView* rv = clicked_regionview;
2277 /* Choose action dependant on which button was pressed */
2278 switch (event->button.button) {
2280 begin_reversible_command (_("start point trim"));
2282 if (selection->selected (rv)) {
2283 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2284 i != selection->regions.by_layer().end(); ++i)
2286 if (!(*i)->region()->locked()) {
2287 (*i)->region()->clear_changes ();
2288 (*i)->region()->trim_front (new_bound);
2289 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2294 if (!rv->region()->locked()) {
2295 rv->region()->clear_changes ();
2296 rv->region()->trim_front (new_bound);
2297 _session->add_command(new StatefulDiffCommand (rv->region()));
2301 commit_reversible_command();
2305 begin_reversible_command (_("end point trim"));
2307 if (selection->selected (rv)) {
2309 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2311 if (!(*i)->region()->locked()) {
2312 (*i)->region()->clear_changes();
2313 (*i)->region()->trim_end (new_bound);
2314 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2320 if (!rv->region()->locked()) {
2321 rv->region()->clear_changes ();
2322 rv->region()->trim_end (new_bound);
2323 _session->add_command (new StatefulDiffCommand (rv->region()));
2327 commit_reversible_command();
2336 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2338 ArdourMarker* marker;
2341 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2342 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2343 abort(); /*NOTREACHED*/
2346 Location* location = find_location_from_marker (marker, is_start);
2347 location->set_hidden (true, this);
2351 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2353 using namespace Gtkmm2ext;
2355 ArdourPrompter prompter (false);
2357 prompter.set_prompt (_("Name for region:"));
2358 prompter.set_initial_text (clicked_regionview->region()->name());
2359 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2360 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2361 prompter.show_all ();
2362 switch (prompter.run ()) {
2363 case Gtk::RESPONSE_ACCEPT:
2365 prompter.get_result(str);
2367 clicked_regionview->region()->set_name (str);
2376 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2378 /* no brushing without a useful snap setting */
2380 switch (_snap_mode) {
2382 return; /* can't work because it allows region to be placed anywhere */
2387 switch (_snap_type) {
2395 /* don't brush a copy over the original */
2397 if (pos == rv->region()->position()) {
2401 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2403 if (!rtv || !rtv->is_track()) {
2407 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2408 double speed = rtv->track()->speed();
2410 playlist->clear_changes ();
2411 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2412 playlist->add_region (new_region, (framepos_t) (pos * speed));
2413 _session->add_command (new StatefulDiffCommand (playlist));
2415 // playlist is frozen, so we have to update manually XXX this is disgusting
2417 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2421 Editor::track_height_step_timeout ()
2423 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2424 current_stepping_trackview = 0;
2431 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2433 assert (region_view);
2435 if (!region_view->region()->playlist()) {
2439 switch (Config->get_edit_mode()) {
2441 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2444 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2447 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2454 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2456 assert (region_view);
2458 if (!region_view->region()->playlist()) {
2462 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2466 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2468 assert (region_view);
2470 if (!region_view->region()->playlist()) {
2474 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2478 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2481 /** Start a grab where a time range is selected, track(s) are selected, and the
2482 * user clicks and drags a region with a modifier in order to create a new region containing
2483 * the section of the clicked region that lies within the time range.
2486 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2488 if (clicked_regionview == 0) {
2492 /* lets try to create new Region for the selection */
2494 vector<boost::shared_ptr<Region> > new_regions;
2495 create_region_from_selection (new_regions);
2497 if (new_regions.empty()) {
2501 /* XXX fix me one day to use all new regions */
2503 boost::shared_ptr<Region> region (new_regions.front());
2505 /* add it to the current stream/playlist.
2507 tricky: the streamview for the track will add a new regionview. we will
2508 catch the signal it sends when it creates the regionview to
2509 set the regionview we want to then drag.
2512 latest_regionviews.clear();
2513 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2515 /* A selection grab currently creates two undo/redo operations, one for
2516 creating the new region and another for moving it.
2518 begin_reversible_command (Operations::selection_grab);
2520 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2522 playlist->clear_changes ();
2523 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2524 _session->add_command(new StatefulDiffCommand (playlist));
2528 if (latest_regionviews.empty()) {
2529 /* something went wrong */
2530 abort_reversible_command ();
2534 /* we need to deselect all other regionviews, and select this one
2535 i'm ignoring undo stuff, because the region creation will take care of it
2538 selection->set (latest_regionviews);
2540 commit_reversible_command ();
2542 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2548 if (_drags->active ()) {
2551 selection->clear ();
2554 ARDOUR_UI::instance()->reset_focus (&contents());
2557 /** Update _join_object_range_state which indicate whether we are over the top
2558 * or bottom half of a route view, used by the `join object/range' tool
2559 * mode. Coordinates in canvas space.
2562 Editor::update_join_object_range_location (double y)
2564 if (!get_smart_mode()) {
2565 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2569 JoinObjectRangeState const old = _join_object_range_state;
2571 if (mouse_mode == MouseObject) {
2572 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2573 } else if (mouse_mode == MouseRange) {
2574 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2577 if (entered_regionview) {
2579 //ToDo: there is currently a bug here(?)
2580 //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
2581 //can it be fixed here?
2583 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2584 double const c = item_space.y / entered_regionview->height();
2586 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2588 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2589 if (_join_object_range_state != old && ctx) {
2590 ctx->cursor_ctx->change(which_track_cursor());
2593 } else if (entered_track) {
2595 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2597 if (entered_route_view) {
2602 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2604 double track_height = entered_route_view->view()->child_height();
2605 if (UIConfiguration::instance().get_show_name_highlight()) {
2606 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2608 double const c = cy / track_height;
2612 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2614 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2618 /* Other kinds of tracks use object mode */
2619 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2622 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2623 if (_join_object_range_state != old && ctx) {
2624 ctx->cursor_ctx->change(which_track_cursor());
2630 Editor::effective_mouse_mode () const
2632 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2634 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2642 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2644 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2647 e->region_view().delete_note (e->note ());
2650 /** Obtain the pointer position in canvas coordinates */
2652 Editor::get_pointer_position (double& x, double& y) const
2655 _track_canvas->get_pointer (px, py);
2656 _track_canvas->window_to_canvas (px, py, x, y);