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"
49 #include "widgets/prompter.h"
52 #include "ardour_ui.h"
54 #include "time_axis_view.h"
55 #include "audio_time_axis.h"
56 #include "audio_region_view.h"
57 #include "midi_region_view.h"
59 #include "streamview.h"
60 #include "region_gain_line.h"
61 #include "rc_option_editor.h"
62 #include "automation_time_axis.h"
63 #include "control_point.h"
64 #include "selection.h"
67 #include "rgb_macros.h"
68 #include "control_point_dialog.h"
69 #include "editor_drag.h"
70 #include "automation_region_view.h"
71 #include "edit_note_dialog.h"
72 #include "mouse_cursors.h"
73 #include "editor_cursors.h"
74 #include "verbose_cursor.h"
80 using namespace ARDOUR;
83 using namespace Editing;
84 using Gtkmm2ext::Keyboard;
87 Editor::mouse_sample (samplepos_t& where, bool& in_track_canvas) const
89 /* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only
90 pays attentions to subwindows. this means that menu windows are ignored, and
91 if the pointer is in a menu, the return window from the call will be the
92 the regular subwindow *under* the menu.
94 this matters quite a lot if the pointer is moving around in a menu that overlaps
95 the track canvas because we will believe that we are within the track canvas
96 when we are not. therefore, we track enter/leave events for the track canvas
97 and allow that to override the result of gdk_window_get_pointer().
100 if (!within_track_canvas) {
105 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->_track_canvas->get_window();
107 if (!canvas_window) {
111 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
113 if (!pointer_window) {
117 if (pointer_window != canvas_window) {
118 in_track_canvas = false;
122 in_track_canvas = true;
125 event.type = GDK_BUTTON_RELEASE;
129 where = window_event_sample (&event, 0, 0);
135 Editor::window_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
137 ArdourCanvas::Duple d;
139 if (!gdk_event_get_coords (event, &d.x, &d.y)) {
143 /* event coordinates are in window units, so convert to canvas
146 d = _track_canvas->window_to_canvas (d);
156 return pixel_to_sample (d.x);
160 Editor::canvas_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
165 /* event coordinates are already in canvas units */
167 if (!gdk_event_get_coords (event, &x, &y)) {
168 cerr << "!NO c COORDS for event type " << event->type << endl;
180 /* note that pixel_to_sample_from_event() never returns less than zero, so even if the pixel
181 position is negative (as can be the case with motion events in particular),
182 the sample location is always positive.
185 return pixel_to_sample_from_event (x);
189 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
191 boost::shared_ptr<Trimmable> st = _trimmable.lock();
193 if (!st || st == t) {
199 Editor::set_current_movable (boost::shared_ptr<Movable> m)
201 boost::shared_ptr<Movable> sm = _movable.lock();
203 if (!sm || sm != m) {
209 Editor::mouse_mode_object_range_toggled()
211 MouseMode m = mouse_mode;
213 Glib::RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
215 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
218 set_mouse_mode(m, true); //call this so the button styles can get updated
222 Editor::snap_mode_button_clicked (GdkEventButton* ev)
224 if (ev->button != 3) {
229 RCOptionEditor* rc_option_editor = ARDOUR_UI::instance()->get_rc_option_editor();
230 if ( rc_option_editor ) {
231 ARDOUR_UI::instance()->show_tabbable (rc_option_editor);
232 rc_option_editor->set_current_page (_("Editor/Snap"));
240 static Glib::RefPtr<Action>
241 get_mouse_mode_action(MouseMode m)
245 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
247 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
249 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
251 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
253 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
255 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-content"));
257 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
259 return Glib::RefPtr<Action>();
263 Editor::set_mouse_mode (MouseMode m, bool force)
265 if (_drags->active ()) {
269 if (!force && m == mouse_mode) {
273 if (ARDOUR::Profile->get_mixbus()) {
274 if ( m == MouseCut) m = MouseObject;
277 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
278 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
280 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
281 tact->set_active (false);
282 tact->set_active (true);
284 //NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting
288 Editor::mouse_mode_toggled (MouseMode m)
290 if (ARDOUR::Profile->get_mixbus()) {
291 if ( m == MouseCut) m = MouseObject;
294 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
295 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
297 if (!tact->get_active()) {
298 /* this was just the notification that the old mode has been
299 * left. we'll get called again with the new mode active in a
305 if (_session && mouse_mode == MouseAudition) {
306 /* stop transport and reset default speed to avoid oddness with
308 _session->request_transport_speed (0.0, true);
311 const bool was_internal = internal_editing();
315 /* Switch snap type/mode if we're moving to/from an internal tool. Note
316 this must toggle the actions and not call set_snap_*() directly,
317 otherwise things get out of sync and the combo box stops working. */
318 if (!was_internal && internal_editing()) {
319 grid_type_action(internal_grid_type)->set_active(true);
320 snap_mode_action(internal_snap_mode)->set_active(true);
321 } else if (was_internal && !internal_editing()) {
322 grid_type_action(pre_internal_grid_type)->set_active(true);
323 snap_mode_action(pre_internal_snap_mode)->set_active(true);
328 /* this should generate a new enter event which will
329 trigger the appropiate cursor.
333 _track_canvas->re_enter ();
336 set_gain_envelope_visibility ();
338 update_time_selection_display ();
340 update_all_enter_cursors ();
342 MouseModeChanged (); /* EMIT SIGNAL */
346 Editor::internal_editing() const
348 return mouse_mode == Editing::MouseContent || mouse_mode == Editing::MouseDraw;
352 Editor::update_time_selection_display ()
354 switch (mouse_mode) {
356 selection->clear_objects ();
357 selection->clear_midi_notes ();
360 selection->clear_time ();
361 selection->clear_midi_notes ();
364 /* Clear regions, but not time or tracks, since that
365 would destroy the range selection rectangle, which we need to stick
366 around for AutomationRangeDrag. */
367 selection->clear_regions ();
368 selection->clear_playlists ();
371 /* This handles internal edit.
372 Clear everything except points and notes.
374 selection->clear_regions();
375 selection->clear_lines();
376 selection->clear_playlists ();
378 selection->clear_time ();
379 selection->clear_tracks ();
383 /* We probably want to keep region selection */
384 selection->clear_points ();
385 selection->clear_lines();
386 selection->clear_playlists ();
388 selection->clear_time ();
389 selection->clear_tracks ();
393 /*Don't lose lines or points if no action in this mode */
394 selection->clear_regions ();
395 selection->clear_playlists ();
396 selection->clear_time ();
397 selection->clear_tracks ();
401 /*Clear everything */
402 selection->clear_objects();
403 selection->clear_time ();
404 selection->clear_tracks ();
410 Editor::step_mouse_mode (bool next)
412 const int n_mouse_modes = (int)MouseContent + 1;
413 int current = (int)current_mouse_mode();
415 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
417 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
422 Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
425 /* in object/audition/timefx/gain-automation mode,
426 any button press sets the selection if the object
427 can be selected. this is a bit of hack, because
428 we want to avoid this if the mouse operation is a
431 note: not dbl-click or triple-click
433 Also note that there is no region selection in internal edit mode, otherwise
434 for operations operating on the selection (e.g. cut) it is not obvious whether
435 to cut notes or regions.
438 MouseMode eff_mouse_mode = effective_mouse_mode ();
440 if (eff_mouse_mode == MouseCut) {
441 /* never change selection in cut mode */
445 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
446 /* context clicks are always about object properties, even if
447 we're in range mode within smart mode.
449 eff_mouse_mode = MouseObject;
452 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
453 if (get_smart_mode()) {
455 case FadeInHandleItem:
456 case FadeInTrimHandleItem:
457 case FadeOutHandleItem:
458 case FadeOutTrimHandleItem:
459 eff_mouse_mode = MouseObject;
466 if (((mouse_mode != MouseObject) &&
467 (mouse_mode != MouseAudition || item_type != RegionItem) &&
468 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
469 (mouse_mode != MouseDraw) &&
470 (mouse_mode != MouseContent || item_type == RegionItem)) ||
471 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
475 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
477 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
479 /* almost no selection action on modified button-2 or button-3 events */
481 if ((item_type != RegionItem && event->button.button != 2)
482 /* for selection of control points prior to delete (shift-right click) */
483 && !(item_type == ControlPointItem && event->button.button == 3 && event->type == GDK_BUTTON_PRESS)) {
489 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
490 bool press = (event->type == GDK_BUTTON_PRESS);
493 _mouse_changed_selection = false;
498 if (eff_mouse_mode == MouseDraw) {
502 if (eff_mouse_mode != MouseRange) {
503 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
505 /* don't change the selection unless the
506 clicked track is not currently selected. if
507 so, "collapse" the selection to just this
510 if (!selection->selected (clicked_axisview)) {
511 set_selected_track_as_side_effect (Selection::Set);
515 if (eff_mouse_mode != MouseRange) {
516 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
521 case RegionViewNameHighlight:
523 case LeftFrameHandle:
524 case RightFrameHandle:
525 case FadeInHandleItem:
526 case FadeInTrimHandleItem:
528 case FadeOutHandleItem:
529 case FadeOutTrimHandleItem:
531 case StartCrossFadeItem:
532 case EndCrossFadeItem:
533 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
534 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
535 } else if (event->type == GDK_BUTTON_PRESS) {
536 set_selected_track_as_side_effect (op);
540 case ControlPointItem:
541 /* for object/track exclusivity, we don't call set_selected_track_as_side_effect (op); */
543 if (eff_mouse_mode != MouseRange) {
544 if (event->button.button != 3) {
545 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
547 _mouse_changed_selection |= set_selected_control_point_from_click (press, Selection::Set);
553 if (eff_mouse_mode != MouseRange) {
554 AutomationLine* argl = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
556 std::list<Selectable*> selectables;
557 uint32_t before, after;
558 samplecnt_t const where = (samplecnt_t) floor (event->button.x * samples_per_pixel) - clicked_regionview->region ()->position ();
560 if (!argl || !argl->control_points_adjacent (where, before, after)) {
564 selectables.push_back (argl->nth (before));
565 selectables.push_back (argl->nth (after));
570 selection->set (selectables);
571 _mouse_changed_selection = true;
576 selection->add (selectables);
577 _mouse_changed_selection = true;
580 case Selection::Toggle:
582 selection->toggle (selectables);
583 _mouse_changed_selection = true;
587 case Selection::Extend:
594 case AutomationLineItem:
595 if (eff_mouse_mode != MouseRange) {
596 AutomationLine* al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
597 std::list<Selectable*> selectables;
598 double mx = event->button.x;
599 double my = event->button.y;
601 al->grab_item().canvas_to_item (mx, my);
603 uint32_t before, after;
604 samplecnt_t const where = (samplecnt_t) floor (mx * samples_per_pixel);
606 if (!al || !al->control_points_adjacent (where, before, after)) {
610 selectables.push_back (al->nth (before));
611 selectables.push_back (al->nth (after));
616 selection->set (selectables);
617 _mouse_changed_selection = true;
622 selection->add (selectables);
623 _mouse_changed_selection = true;
626 case Selection::Toggle:
628 selection->toggle (selectables);
629 _mouse_changed_selection = true;
633 case Selection::Extend:
641 /* for context click, select track */
642 if (event->button.button == 3) {
643 selection->clear_tracks ();
644 set_selected_track_as_side_effect (op);
646 /* We won't get a release.*/
647 begin_reversible_selection_op (X_("Button 3 Menu Select"));
648 commit_reversible_selection_op ();
652 case AutomationTrackItem:
653 if (eff_mouse_mode != MouseDraw && op == Selection::Set) {
654 set_selected_track_as_side_effect (op);
659 if (press && event->button.button == 3) {
660 NoteBase* cnote = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
662 if (cnote->region_view().selection_size() == 0 || !cnote->selected()) {
663 selection->clear_points();
664 cnote->region_view().unique_select (cnote);
665 /* we won't get the release, so store the selection change now */
666 begin_reversible_selection_op (X_("Button 3 Note Selection"));
667 commit_reversible_selection_op ();
676 if ((!press) && _mouse_changed_selection) {
677 begin_reversible_selection_op (X_("Button Selection"));
678 commit_reversible_selection_op ();
679 _mouse_changed_selection = false;
684 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
686 /* single mouse clicks on any of these item types operate
687 independent of mouse mode, mostly because they are
688 not on the main track canvas or because we want
692 NoteBase* note = NULL;
695 case PlayheadCursorItem:
696 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
700 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
701 hide_marker (item, event);
703 _drags->set (new MarkerDrag (this, item), event);
707 case TempoMarkerItem:
709 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
719 new TempoMarkerDrag (
722 ArdourKeyboard::indicates_copy (event->button.state)
731 case MeterMarkerItem:
734 new MeterMarkerDrag (
737 ArdourKeyboard::indicates_copy (event->button.state)
745 _drags->set (new VideoTimeLineDrag (this, item), event);
753 case TimecodeRulerItem:
754 case SamplesRulerItem:
755 case MinsecRulerItem:
757 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)
758 && !ArdourKeyboard::indicates_constraint (event->button.state)) {
759 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
760 } else if (ArdourKeyboard::indicates_constraint (event->button.state)
761 && Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
762 _drags->set (new TempoTwistDrag (this, item), event);
763 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
764 _drags->set (new BBTRulerDrag (this, item), event);
770 case RangeMarkerBarItem:
771 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
772 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
773 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
774 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
776 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
781 case CdMarkerBarItem:
782 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
783 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
785 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
790 case TransportMarkerBarItem:
791 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
792 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
794 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
803 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
804 /* special case: allow trim of range selections in joined object mode;
805 in theory eff should equal MouseRange in this case, but it doesn't
806 because entering the range selection canvas item results in entered_regionview
807 being set to 0, so update_join_object_range_location acts as if we aren't
810 if (item_type == StartSelectionTrimItem) {
811 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
812 } else if (item_type == EndSelectionTrimItem) {
813 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
817 Editing::MouseMode eff = effective_mouse_mode ();
819 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
820 if (get_smart_mode()) {
822 case FadeInHandleItem:
823 case FadeInTrimHandleItem:
824 case FadeOutHandleItem:
825 case FadeOutTrimHandleItem:
836 case StartSelectionTrimItem:
837 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
840 case EndSelectionTrimItem:
841 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
845 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
846 start_selection_grab (item, event);
848 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
849 /* grab selection for moving */
850 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
852 /* this was debated, but decided the more common action was to
853 make a new selection */
854 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
859 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
860 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
862 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
867 case RegionViewNameHighlight:
868 if (!clicked_regionview->region()->locked()) {
869 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
875 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
876 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
878 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
887 case FadeInHandleItem:
888 case FadeOutHandleItem:
889 case LeftFrameHandle:
890 case RightFrameHandle:
891 case FeatureLineItem:
892 case RegionViewNameHighlight:
895 case AutomationTrackItem:
896 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
907 /* Existing note: allow trimming/motion */
908 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
909 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
910 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
912 _drags->set (new NoteDrag (this, item), event);
918 _drags->set (new LineDrag (this, item), event);
922 case ControlPointItem:
923 _drags->set (new ControlPointDrag (this, item), event);
927 case AutomationLineItem:
928 _drags->set (new LineDrag (this, item), event);
933 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
936 case AutomationTrackItem:
937 /* rubberband drag to select automation points */
938 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
943 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
944 /* rubberband drag to select automation points */
945 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
956 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
957 event->type == GDK_BUTTON_PRESS) {
959 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
961 } else if (event->type == GDK_BUTTON_PRESS) {
964 case FadeInHandleItem:
966 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
970 case FadeOutHandleItem:
972 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
976 case StartCrossFadeItem:
977 case EndCrossFadeItem:
978 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
979 // if (!clicked_regionview->region()->locked()) {
980 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
985 case FeatureLineItem:
987 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
988 remove_transient(item);
992 _drags->set (new FeatureLineDrag (this, item), event);
998 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
999 /* click on an automation region view; do nothing here and let the ARV's signal handler
1005 /* click on a normal region view */
1006 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1007 add_region_copy_drag (item, event, clicked_regionview);
1008 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
1009 add_region_brush_drag (item, event, clicked_regionview);
1011 add_region_drag (item, event, clicked_regionview);
1015 _drags->start_grab (event);
1019 case RegionViewNameHighlight:
1020 case LeftFrameHandle:
1021 case RightFrameHandle:
1022 if (!clicked_regionview->region()->locked()) {
1023 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1028 case FadeInTrimHandleItem:
1029 case FadeOutTrimHandleItem:
1030 if (!clicked_regionview->region()->locked()) {
1031 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
1036 case RegionViewName:
1038 /* rename happens on edit clicks */
1039 if (clicked_regionview->get_name_highlight()) {
1040 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1046 case ControlPointItem:
1047 _drags->set (new ControlPointDrag (this, item), event);
1051 case AutomationLineItem:
1052 _drags->set (new LineDrag (this, item), event);
1057 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1060 case AutomationTrackItem:
1062 TimeAxisView* parent = clicked_axisview->get_parent ();
1063 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1065 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1067 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1069 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1070 if (pl->n_regions() == 0) {
1071 /* Parent has no regions; create one so that we have somewhere to put automation */
1072 _drags->set (new RegionCreateDrag (this, item, parent), event);
1074 /* See if there's a region before the click that we can extend, and extend it if so */
1075 samplepos_t const t = canvas_event_sample (event);
1076 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1078 _drags->set (new RegionCreateDrag (this, item, parent), event);
1080 prev->set_length (t - prev->position (), get_grid_music_divisions (event->button.state));
1084 /* rubberband drag to select automation points */
1085 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1107 switch (item_type) {
1109 _drags->set (new LineDrag (this, item), event);
1112 case ControlPointItem:
1113 _drags->set (new ControlPointDrag (this, item), event);
1119 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
1120 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1121 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
1122 event, _cursors->up_down);
1124 double const y = event->button.y;
1125 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
1127 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1129 /* smart "join" mode: drag automation */
1130 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1138 case AutomationLineItem:
1139 _drags->set (new LineDrag (this, item), event);
1143 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1144 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1145 /* Note is big and pointer is near the end, trim */
1146 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1149 _drags->set (new NoteDrag (this, item), event);
1156 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1157 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1168 if (item_type == NoteItem) {
1169 /* resize-drag notes */
1170 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1171 if (note->big_enough_to_trim()) {
1172 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1176 } else if (clicked_regionview) {
1178 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1184 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1185 scrub_reversals = 0;
1186 scrub_reverse_distance = 0;
1187 last_scrub_x = event->button.x;
1188 scrubbing_direction = 0;
1200 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1202 Editing::MouseMode const eff = effective_mouse_mode ();
1205 switch (item_type) {
1207 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1208 add_region_copy_drag (item, event, clicked_regionview);
1210 add_region_drag (item, event, clicked_regionview);
1212 _drags->start_grab (event);
1215 case ControlPointItem:
1216 _drags->set (new ControlPointDrag (this, item), event);
1224 switch (item_type) {
1225 case RegionViewNameHighlight:
1226 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1230 case LeftFrameHandle:
1231 case RightFrameHandle:
1232 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1236 case RegionViewName:
1237 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1251 /* relax till release */
1263 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1265 if (event->type == GDK_2BUTTON_PRESS) {
1266 _drags->mark_double_click ();
1267 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1271 if (event->type != GDK_BUTTON_PRESS) {
1275 _track_canvas->grab_focus();
1277 if (_session && _session->actively_recording()) {
1281 button_selection (item, event, item_type);
1283 if (!_drags->active () &&
1284 (Keyboard::is_delete_event (&event->button) ||
1285 Keyboard::is_context_menu_event (&event->button) ||
1286 Keyboard::is_edit_event (&event->button))) {
1288 /* handled by button release */
1292 /* not rolling, effectively in range mode, follow edits enabled (likely
1293 * to start range drag), not in a fade handle (since that means we are
1294 * not starting a range drag): locate the PH here
1297 if ((item_type != FadeInHandleItem) &&
1298 (item_type != FadeOutHandleItem) &&
1299 !_drags->active () &&
1301 !_session->transport_rolling() &&
1302 (effective_mouse_mode() == MouseRange) &&
1303 UIConfiguration::instance().get_follow_edits() &&
1304 !_session->config.get_external_sync()) {
1306 MusicSample where (canvas_event_sample (event), 0);
1308 _session->request_locate (where.sample, false);
1311 switch (event->button.button) {
1313 return button_press_handler_1 (item, event, item_type);
1317 return button_press_handler_2 (item, event, item_type);
1324 return button_press_dispatch (&event->button);
1333 Editor::button_press_dispatch (GdkEventButton* ev)
1335 /* this function is intended only for buttons 4 and above.
1338 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1339 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1343 Editor::button_release_dispatch (GdkEventButton* ev)
1345 /* this function is intended only for buttons 4 and above.
1348 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1349 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1353 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1355 MusicSample where (canvas_event_sample (event), 0);
1356 AutomationTimeAxisView* atv = 0;
1358 _press_cursor_ctx.reset();
1360 /* no action if we're recording */
1362 if (_session && _session->actively_recording()) {
1366 bool were_dragging = false;
1368 if (!Keyboard::is_context_menu_event (&event->button)) {
1370 /* see if we're finishing a drag */
1372 if (_drags->active ()) {
1373 bool const r = _drags->end_grab (event);
1375 /* grab dragged, so do nothing else */
1379 were_dragging = true;
1382 update_region_layering_order_editor ();
1385 /* edit events get handled here */
1387 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1388 switch (item_type) {
1390 show_region_properties ();
1392 case TempoMarkerItem: {
1393 ArdourMarker* marker;
1394 TempoMarker* tempo_marker;
1396 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1397 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1398 abort(); /*NOTREACHED*/
1401 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1402 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1403 abort(); /*NOTREACHED*/
1406 edit_tempo_marker (*tempo_marker);
1410 case MeterMarkerItem: {
1411 ArdourMarker* marker;
1412 MeterMarker* meter_marker;
1414 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1415 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1416 abort(); /*NOTREACHED*/
1419 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1420 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1421 abort(); /*NOTREACHED*/
1423 edit_meter_marker (*meter_marker);
1427 case RegionViewName:
1428 if (clicked_regionview->name_active()) {
1429 return mouse_rename_region (item, event);
1433 case ControlPointItem:
1434 edit_control_point (item);
1443 /* context menu events get handled here */
1444 if (Keyboard::is_context_menu_event (&event->button)) {
1446 context_click_event = *event;
1448 if (!_drags->active ()) {
1450 /* no matter which button pops up the context menu, tell the menu
1451 widget to use button 1 to drive menu selection.
1454 switch (item_type) {
1456 case FadeInHandleItem:
1457 case FadeInTrimHandleItem:
1458 case StartCrossFadeItem:
1459 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1463 case FadeOutHandleItem:
1464 case FadeOutTrimHandleItem:
1465 case EndCrossFadeItem:
1466 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1469 case LeftFrameHandle:
1470 case RightFrameHandle:
1474 popup_track_context_menu (1, event->button.time, item_type, false);
1478 case RegionViewNameHighlight:
1479 case RegionViewName:
1480 popup_track_context_menu (1, event->button.time, item_type, false);
1484 popup_track_context_menu (1, event->button.time, item_type, true);
1487 case AutomationTrackItem:
1488 popup_track_context_menu (1, event->button.time, item_type, false);
1492 case RangeMarkerBarItem:
1493 case TransportMarkerBarItem:
1494 case CdMarkerBarItem:
1496 case TempoCurveItem:
1499 case TimecodeRulerItem:
1500 case SamplesRulerItem:
1501 case MinsecRulerItem:
1503 popup_ruler_menu (where.sample, item_type);
1507 marker_context_menu (&event->button, item);
1510 case TempoMarkerItem:
1511 tempo_or_meter_marker_context_menu (&event->button, item);
1514 case MeterMarkerItem:
1515 tempo_or_meter_marker_context_menu (&event->button, item);
1518 case CrossfadeViewItem:
1519 popup_track_context_menu (1, event->button.time, item_type, false);
1522 case ControlPointItem:
1523 popup_control_point_context_menu (item, event);
1527 if (internal_editing()) {
1528 popup_note_context_menu (item, event);
1540 /* delete events get handled here */
1542 Editing::MouseMode const eff = effective_mouse_mode ();
1544 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1546 switch (item_type) {
1547 case TempoMarkerItem:
1548 remove_tempo_marker (item);
1551 case MeterMarkerItem:
1552 remove_meter_marker (item);
1556 remove_marker (*item, event);
1560 if (eff == MouseObject) {
1561 remove_clicked_region ();
1565 case ControlPointItem:
1566 remove_control_point (item);
1570 remove_midi_note (item, event);
1579 switch (event->button.button) {
1582 switch (item_type) {
1583 /* see comments in button_press_handler */
1584 case PlayheadCursorItem:
1587 case AutomationLineItem:
1588 case StartSelectionTrimItem:
1589 case EndSelectionTrimItem:
1593 if (!_dragging_playhead) {
1594 snap_to_with_modifier (where, event, RoundNearest, SnapToAny, true);
1595 mouse_add_new_marker (where.sample);
1599 case CdMarkerBarItem:
1600 if (!_dragging_playhead) {
1601 // if we get here then a dragged range wasn't done
1602 snap_to_with_modifier (where, event, RoundNearest, SnapToAny, true);
1603 mouse_add_new_marker (where.sample, true);
1607 case TempoCurveItem:
1608 if (!_dragging_playhead && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1609 snap_to_with_modifier (where, event);
1610 mouse_add_new_tempo_event (where.sample);
1615 if (!_dragging_playhead && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1616 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1621 case TimecodeRulerItem:
1622 case SamplesRulerItem:
1623 case MinsecRulerItem:
1634 switch (item_type) {
1637 /* check that we didn't drag before releasing, since
1638 its really annoying to create new control
1639 points when doing this.
1641 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1642 if (!were_dragging && arv) {
1643 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1644 arv->add_gain_point_event (item, event, with_guard_points);
1650 case AutomationTrackItem: {
1651 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1652 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1654 atv->add_automation_event (event, where.sample, event->button.y, with_guard_points);
1665 if (scrubbing_direction == 0) {
1666 /* no drag, just a click */
1667 switch (item_type) {
1669 play_selected_region ();
1674 } else if (_session) {
1675 /* make sure we stop */
1676 _session->request_transport_speed (0.0);
1685 /* do any (de)selection operations that should occur on button release */
1686 button_selection (item, event, item_type);
1696 switch (item_type) {
1698 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1700 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1703 // Button2 click is unused
1718 // x_style_paste (where, 1.0);
1739 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1742 ArdourMarker * marker;
1743 MeterMarker* m_marker = 0;
1744 TempoMarker* t_marker = 0;
1748 /* by the time we reach here, entered_regionview and entered trackview
1749 * will have already been set as appropriate. Things are done this
1750 * way because this method isn't passed a pointer to a variable type of
1751 * thing that is entered (which may or may not be canvas item).
1752 * (e.g. the actual entered regionview)
1755 choose_canvas_cursor_on_entry (item_type);
1757 switch (item_type) {
1758 case ControlPointItem:
1759 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1760 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1763 fraction = 1.0 - (cp->get_y() / cp->line().height());
1765 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1766 _verbose_cursor->show ();
1771 if (mouse_mode == MouseDraw) {
1772 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1774 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1779 case AutomationLineItem:
1780 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1781 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1783 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1788 case AutomationTrackItem:
1789 AutomationTimeAxisView* atv;
1790 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1791 clear_entered_track = false;
1792 set_entered_track (atv);
1797 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1800 entered_marker = marker;
1801 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1804 case MeterMarkerItem:
1805 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1808 entered_marker = m_marker;
1809 if (m_marker->meter().position_lock_style() == MusicTime) {
1810 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1812 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1816 case TempoMarkerItem:
1817 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1820 entered_marker = t_marker;
1821 if (t_marker->tempo().position_lock_style() == MusicTime) {
1822 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1824 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1828 case FadeInHandleItem:
1829 case FadeInTrimHandleItem:
1830 if (mouse_mode == MouseObject) {
1831 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1833 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1834 rect->set_fill_color (rv->get_fill_color());
1839 case FadeOutHandleItem:
1840 case FadeOutTrimHandleItem:
1841 if (mouse_mode == MouseObject) {
1842 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1844 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1845 rect->set_fill_color (rv->get_fill_color ());
1850 case FeatureLineItem:
1852 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1853 line->set_outline_color (0xFF0000FF);
1862 if (entered_regionview) {
1863 entered_regionview->entered();
1872 /* third pass to handle entered track status in a comprehensible way.
1875 switch (item_type) {
1877 case AutomationLineItem:
1878 case ControlPointItem:
1879 /* these do not affect the current entered track state */
1880 clear_entered_track = false;
1883 case AutomationTrackItem:
1884 /* handled above already */
1896 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1899 ArdourMarker *marker;
1900 TempoMarker *t_marker;
1901 MeterMarker *m_marker;
1906 if (!_enter_stack.empty()) {
1907 _enter_stack.pop_back();
1910 switch (item_type) {
1911 case ControlPointItem:
1912 _verbose_cursor->hide ();
1916 case AutomationLineItem:
1917 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1919 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1921 line->set_outline_color (al->get_line_color());
1927 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1931 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1932 location_flags_changed (loc);
1936 case MeterMarkerItem:
1937 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1941 if (m_marker->meter().position_lock_style() == MusicTime) {
1942 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1944 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1948 case TempoMarkerItem:
1949 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1953 if (t_marker->tempo().position_lock_style() == MusicTime) {
1954 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1956 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1960 case FadeInTrimHandleItem:
1961 case FadeOutTrimHandleItem:
1962 case FadeInHandleItem:
1963 case FadeOutHandleItem:
1965 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1967 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1972 case AutomationTrackItem:
1975 case FeatureLineItem:
1977 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1978 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1990 Editor::scrub (samplepos_t sample, double current_x)
1994 if (scrubbing_direction == 0) {
1996 _session->request_locate (sample, false);
1997 _session->request_transport_speed (0.1);
1998 scrubbing_direction = 1;
2002 if (last_scrub_x > current_x) {
2004 /* pointer moved to the left */
2006 if (scrubbing_direction > 0) {
2008 /* we reversed direction to go backwards */
2011 scrub_reverse_distance += (int) (last_scrub_x - current_x);
2015 /* still moving to the left (backwards) */
2017 scrub_reversals = 0;
2018 scrub_reverse_distance = 0;
2020 delta = 0.01 * (last_scrub_x - current_x);
2021 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
2025 /* pointer moved to the right */
2027 if (scrubbing_direction < 0) {
2028 /* we reversed direction to go forward */
2031 scrub_reverse_distance += (int) (current_x - last_scrub_x);
2034 /* still moving to the right */
2036 scrub_reversals = 0;
2037 scrub_reverse_distance = 0;
2039 delta = 0.01 * (current_x - last_scrub_x);
2040 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
2044 /* if there have been more than 2 opposite motion moves detected, or one that moves
2045 back more than 10 pixels, reverse direction
2048 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
2050 if (scrubbing_direction > 0) {
2051 /* was forwards, go backwards */
2052 _session->request_transport_speed (-0.1);
2053 scrubbing_direction = -1;
2055 /* was backwards, go forwards */
2056 _session->request_transport_speed (0.1);
2057 scrubbing_direction = 1;
2060 scrub_reverse_distance = 0;
2061 scrub_reversals = 0;
2065 last_scrub_x = current_x;
2069 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2071 _last_motion_y = event->motion.y;
2073 if (event->motion.is_hint) {
2076 /* We call this so that MOTION_NOTIFY events continue to be
2077 delivered to the canvas. We need to do this because we set
2078 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2079 the density of the events, at the expense of a round-trip
2080 to the server. Given that this will mostly occur on cases
2081 where DISPLAY = :0.0, and given the cost of what the motion
2082 event might do, its a good tradeoff.
2085 _track_canvas->get_pointer (x, y);
2088 if (current_stepping_trackview) {
2089 /* don't keep the persistent stepped trackview if the mouse moves */
2090 current_stepping_trackview = 0;
2091 step_timeout.disconnect ();
2094 if (_session && _session->actively_recording()) {
2095 /* Sorry. no dragging stuff around while we record */
2099 update_join_object_range_location (event->motion.y);
2101 //snapped_cursor stuff ( the snapped_cursor shows where an operation is going to occur )
2103 MusicSample where (0, 0);
2104 if (mouse_sample (where.sample, ignored)) {
2105 snap_to_with_modifier (where, event);
2106 set_snapped_cursor_position (where.sample);
2109 //drags might also change the snapped_cursor location, because we are snapping the thing being dragged, not the actual mouse cursor
2110 if (_drags->active ()) {
2111 return _drags->motion_handler (event, from_autoscroll);
2118 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2120 ControlPoint* control_point;
2122 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2123 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2124 abort(); /*NOTREACHED*/
2127 AutomationLine& line = control_point->line ();
2128 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2129 /* we shouldn't remove the first or last gain point in region gain lines */
2130 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2139 Editor::remove_control_point (ArdourCanvas::Item* item)
2141 if (!can_remove_control_point (item)) {
2145 ControlPoint* control_point;
2147 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2148 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2149 abort(); /*NOTREACHED*/
2152 control_point->line().remove_point (*control_point);
2156 Editor::edit_control_point (ArdourCanvas::Item* item)
2158 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2161 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2162 abort(); /*NOTREACHED*/
2165 ControlPointDialog d (p);
2167 if (d.run () != RESPONSE_ACCEPT) {
2171 p->line().modify_point_y (*p, d.get_y_fraction ());
2175 Editor::edit_notes (MidiRegionView* mrv)
2177 MidiRegionView::Selection const & s = mrv->selection();
2183 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2186 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2190 Editor::note_edit_done (int r, EditNoteDialog* d)
2197 Editor::edit_region (RegionView* rv)
2199 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
2200 temporal_zoom_selection (Both);
2202 rv->show_region_editor ();
2207 Editor::visible_order_range (int* low, int* high) const
2209 *low = TimeAxisView::max_order ();
2212 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2214 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2216 if (rtv && !rtv->hidden()) {
2218 if (*high < rtv->order()) {
2219 *high = rtv->order ();
2222 if (*low > rtv->order()) {
2223 *low = rtv->order ();
2230 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2232 /* Either add to or set the set the region selection, unless
2233 this is an alignment click (control used)
2236 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2237 TimeAxisView* tv = &rv.get_time_axis_view();
2238 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2240 samplepos_t where = get_preferred_edit_position();
2244 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2246 align_region (rv.region(), SyncPoint, where);
2248 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2250 align_region (rv.region(), End, where);
2254 align_region (rv.region(), Start, where);
2261 Editor::collect_new_region_view (RegionView* rv)
2263 latest_regionviews.push_back (rv);
2267 Editor::collect_and_select_new_region_view (RegionView* rv)
2270 latest_regionviews.push_back (rv);
2274 Editor::cancel_selection ()
2276 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2277 (*i)->hide_selection ();
2280 selection->clear ();
2281 clicked_selection = 0;
2285 Editor::cancel_time_selection ()
2287 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2288 (*i)->hide_selection ();
2290 selection->time.clear ();
2291 clicked_selection = 0;
2295 Editor::point_trim (GdkEvent* event, samplepos_t new_bound)
2297 RegionView* rv = clicked_regionview;
2299 /* Choose action dependant on which button was pressed */
2300 switch (event->button.button) {
2302 begin_reversible_command (_("start point trim"));
2304 if (selection->selected (rv)) {
2305 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2306 i != selection->regions.by_layer().end(); ++i)
2308 if (!(*i)->region()->locked()) {
2309 (*i)->region()->clear_changes ();
2310 (*i)->region()->trim_front (new_bound);
2311 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2316 if (!rv->region()->locked()) {
2317 rv->region()->clear_changes ();
2318 rv->region()->trim_front (new_bound);
2319 _session->add_command(new StatefulDiffCommand (rv->region()));
2323 commit_reversible_command();
2327 begin_reversible_command (_("end point trim"));
2329 if (selection->selected (rv)) {
2331 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2333 if (!(*i)->region()->locked()) {
2334 (*i)->region()->clear_changes();
2335 (*i)->region()->trim_end (new_bound);
2336 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2342 if (!rv->region()->locked()) {
2343 rv->region()->clear_changes ();
2344 rv->region()->trim_end (new_bound);
2345 _session->add_command (new StatefulDiffCommand (rv->region()));
2349 commit_reversible_command();
2358 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2360 ArdourMarker* marker;
2363 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2364 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2365 abort(); /*NOTREACHED*/
2368 Location* location = find_location_from_marker (marker, is_start);
2369 location->set_hidden (true, this);
2373 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2375 using namespace Gtkmm2ext;
2377 ArdourWidgets::Prompter prompter (false);
2379 prompter.set_prompt (_("Name for region:"));
2380 prompter.set_initial_text (clicked_regionview->region()->name());
2381 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2382 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2383 prompter.show_all ();
2384 switch (prompter.run ()) {
2385 case Gtk::RESPONSE_ACCEPT:
2387 prompter.get_result(str);
2389 clicked_regionview->region()->set_name (str);
2398 Editor::mouse_brush_insert_region (RegionView* rv, samplepos_t pos)
2400 /* no brushing without a useful quantize setting */
2401 if (_grid_type == GridTypeNone)
2404 /* don't brush a copy over the original */
2406 if (pos == rv->region()->position()) {
2410 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2412 if (!rtv || !rtv->is_track()) {
2416 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2418 playlist->clear_changes ();
2419 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2420 playlist->add_region (new_region, pos);
2421 _session->add_command (new StatefulDiffCommand (playlist));
2423 // playlist is frozen, so we have to update manually XXX this is disgusting
2425 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2429 Editor::track_height_step_timeout ()
2431 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2432 current_stepping_trackview = 0;
2439 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2441 assert (region_view);
2443 if (!region_view->region()->playlist()) {
2447 switch (Config->get_edit_mode()) {
2449 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2452 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2455 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2462 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2464 assert (region_view);
2466 if (!region_view->region()->playlist()) {
2470 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2474 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2476 assert (region_view);
2478 if (!region_view->region()->playlist()) {
2482 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2486 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2489 /** Start a grab where a time range is selected, track(s) are selected, and the
2490 * user clicks and drags a region with a modifier in order to create a new region containing
2491 * the section of the clicked region that lies within the time range.
2494 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2496 if (clicked_regionview == 0) {
2500 /* lets try to create new Region for the selection */
2502 vector<boost::shared_ptr<Region> > new_regions;
2503 create_region_from_selection (new_regions);
2505 if (new_regions.empty()) {
2509 /* XXX fix me one day to use all new regions */
2511 boost::shared_ptr<Region> region (new_regions.front());
2513 /* add it to the current stream/playlist.
2515 tricky: the streamview for the track will add a new regionview. we will
2516 catch the signal it sends when it creates the regionview to
2517 set the regionview we want to then drag.
2520 latest_regionviews.clear();
2521 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2523 /* A selection grab currently creates two undo/redo operations, one for
2524 creating the new region and another for moving it.
2526 begin_reversible_command (Operations::selection_grab);
2528 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2530 playlist->clear_changes ();
2531 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2532 _session->add_command(new StatefulDiffCommand (playlist));
2536 if (latest_regionviews.empty()) {
2537 /* something went wrong */
2538 abort_reversible_command ();
2542 /* we need to deselect all other regionviews, and select this one
2543 i'm ignoring undo stuff, because the region creation will take care of it
2546 selection->set (latest_regionviews);
2548 commit_reversible_command ();
2550 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2556 if (_drags->active ()) {
2559 selection->clear ();
2562 ARDOUR_UI::instance()->reset_focus (&contents());
2565 /** Update _join_object_range_state which indicate whether we are over the top
2566 * or bottom half of a route view, used by the `join object/range' tool
2567 * mode. Coordinates in canvas space.
2570 Editor::update_join_object_range_location (double y)
2572 if (!get_smart_mode()) {
2573 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2577 JoinObjectRangeState const old = _join_object_range_state;
2579 if (mouse_mode == MouseObject) {
2580 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2581 } else if (mouse_mode == MouseRange) {
2582 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2585 if (entered_regionview) {
2587 //ToDo: there is currently a bug here(?)
2588 //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
2589 //can it be fixed here?
2591 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2592 double const c = item_space.y / entered_regionview->height();
2594 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2596 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2597 if (_join_object_range_state != old && ctx) {
2598 ctx->cursor_ctx->change(which_track_cursor());
2601 } else if (entered_track) {
2603 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2605 if (entered_route_view) {
2610 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2612 double track_height = entered_route_view->view()->child_height();
2613 if (UIConfiguration::instance().get_show_name_highlight()) {
2614 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2616 double const c = cy / track_height;
2620 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2622 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2626 /* Other kinds of tracks use object mode */
2627 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2630 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2631 if (_join_object_range_state != old && ctx) {
2632 ctx->cursor_ctx->change(which_track_cursor());
2638 Editor::effective_mouse_mode () const
2640 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2642 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2650 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2652 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2655 e->region_view().delete_note (e->note ());
2658 /** Obtain the pointer position in canvas coordinates */
2660 Editor::get_pointer_position (double& x, double& y) const
2663 _track_canvas->get_pointer (px, py);
2664 _track_canvas->window_to_canvas (px, py, x, y);