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 set_mouse_mode (mouse_mode, true); /* updates set-mouse-mode-range */
215 Editor::snap_mode_button_clicked (GdkEventButton* ev)
217 if (ev->button != 3) {
222 RCOptionEditor* rc_option_editor = ARDOUR_UI::instance()->get_rc_option_editor();
223 if (rc_option_editor) {
224 ARDOUR_UI::instance()->show_tabbable (rc_option_editor);
225 rc_option_editor->set_current_page (_("Editor/Snap"));
234 Editor::get_mouse_mode_action(MouseMode m) const
238 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
240 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
242 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
244 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
246 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
248 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-content"));
250 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
252 return Glib::RefPtr<Action>();
256 Editor::set_mouse_mode (MouseMode m, bool force)
258 if (_drags->active ()) {
262 if (!force && m == mouse_mode) {
266 if (ARDOUR::Profile->get_mixbus()) {
267 if (m == MouseCut) m = MouseObject;
268 if (m == MouseAudition) m = MouseRange;
271 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
272 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
274 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
275 tact->set_active (false);
276 tact->set_active (true);
278 /* NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting */
282 Editor::mouse_mode_toggled (MouseMode m)
284 if (ARDOUR::Profile->get_mixbus()) {
285 if (m == MouseCut) m = MouseObject;
286 if (m == MouseAudition) m = MouseRange;
289 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
290 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
292 if (!tact->get_active()) {
293 /* this was just the notification that the old mode has been
294 * left. we'll get called again with the new mode active in a
300 if (_session && mouse_mode == MouseAudition) {
301 /* stop transport and reset default speed to avoid oddness with
303 _session->request_transport_speed (0.0, true);
306 const bool was_internal = internal_editing();
310 /* Switch snap type/mode if we're moving to/from an internal tool. Note
311 this must toggle the actions and not call set_snap_*() directly,
312 otherwise things get out of sync and the combo box stops working. */
313 if (!UIConfiguration::instance().get_grid_follows_internal()) {
314 grid_type_action(pre_internal_grid_type)->set_active(true);
315 snap_mode_action(pre_internal_snap_mode)->set_active(true);
316 } else if (!was_internal && internal_editing()) {
317 grid_type_action(internal_grid_type)->set_active(true);
318 snap_mode_action(internal_snap_mode)->set_active(true);
319 } else if (was_internal && !internal_editing()) {
320 grid_type_action(pre_internal_grid_type)->set_active(true);
321 snap_mode_action(pre_internal_snap_mode)->set_active(true);
326 /* this should generate a new enter event which will
327 trigger the appropiate cursor.
331 _track_canvas->re_enter ();
334 set_gain_envelope_visibility ();
336 update_time_selection_display ();
338 update_all_enter_cursors ();
340 MouseModeChanged (); /* EMIT SIGNAL */
344 Editor::internal_editing() const
346 return mouse_mode == Editing::MouseContent || mouse_mode == Editing::MouseDraw;
350 Editor::update_time_selection_display ()
352 switch (mouse_mode) {
354 selection->clear_objects ();
355 selection->clear_midi_notes ();
358 selection->clear_time ();
359 selection->clear_midi_notes ();
362 /* Clear regions, but not time or tracks, since that
363 would destroy the range selection rectangle, which we need to stick
364 around for AutomationRangeDrag. */
365 selection->clear_regions ();
366 selection->clear_playlists ();
369 /* This handles internal edit.
370 Clear everything except points and notes.
372 selection->clear_regions();
373 selection->clear_lines();
374 selection->clear_playlists ();
376 selection->clear_time ();
377 selection->clear_tracks ();
381 /* We probably want to keep region selection */
382 selection->clear_points ();
383 selection->clear_lines();
384 selection->clear_playlists ();
386 selection->clear_time ();
387 selection->clear_tracks ();
391 /*Don't lose lines or points if no action in this mode */
392 selection->clear_regions ();
393 selection->clear_playlists ();
394 selection->clear_time ();
395 selection->clear_tracks ();
399 /*Clear everything */
400 selection->clear_objects();
401 selection->clear_time ();
402 selection->clear_tracks ();
408 Editor::step_mouse_mode (bool next)
410 const int n_mouse_modes = (int)MouseContent + 1;
411 int current = (int)current_mouse_mode();
413 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
415 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
420 Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
423 /* in object/audition/timefx/gain-automation mode,
424 any button press sets the selection if the object
425 can be selected. this is a bit of hack, because
426 we want to avoid this if the mouse operation is a
429 note: not dbl-click or triple-click
431 Also note that there is no region selection in internal edit mode, otherwise
432 for operations operating on the selection (e.g. cut) it is not obvious whether
433 to cut notes or regions.
436 MouseMode eff_mouse_mode = effective_mouse_mode ();
438 if (eff_mouse_mode == MouseCut) {
439 /* never change selection in cut mode */
443 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
444 /* context clicks are always about object properties, even if
445 we're in range mode within smart mode.
447 eff_mouse_mode = MouseObject;
450 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
451 if (get_smart_mode()) {
453 case FadeInHandleItem:
454 case FadeInTrimHandleItem:
455 case FadeOutHandleItem:
456 case FadeOutTrimHandleItem:
457 eff_mouse_mode = MouseObject;
464 if (((mouse_mode != MouseObject) &&
465 (mouse_mode != MouseAudition || item_type != RegionItem) &&
466 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
467 (mouse_mode != MouseDraw) &&
468 (mouse_mode != MouseContent || item_type == RegionItem)) ||
469 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
473 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
475 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
477 /* almost no selection action on modified button-2 or button-3 events */
479 if ((item_type != RegionItem && event->button.button != 2)
480 /* for selection of control points prior to delete (shift-right click) */
481 && !(item_type == ControlPointItem && event->button.button == 3 && event->type == GDK_BUTTON_PRESS)) {
487 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
488 bool press = (event->type == GDK_BUTTON_PRESS);
491 _mouse_changed_selection = false;
496 if (eff_mouse_mode == MouseDraw) {
500 if (eff_mouse_mode != MouseRange) {
501 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
503 /* don't change the selection unless the
504 clicked track is not currently selected. if
505 so, "collapse" the selection to just this
508 if (!selection->selected (clicked_axisview)) {
509 set_selected_track_as_side_effect (Selection::Set);
513 if (eff_mouse_mode != MouseRange) {
514 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
519 case RegionViewNameHighlight:
521 case LeftFrameHandle:
522 case RightFrameHandle:
523 case FadeInHandleItem:
524 case FadeInTrimHandleItem:
526 case FadeOutHandleItem:
527 case FadeOutTrimHandleItem:
529 case StartCrossFadeItem:
530 case EndCrossFadeItem:
531 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
532 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
533 } else if (event->type == GDK_BUTTON_PRESS) {
534 set_selected_track_as_side_effect (op);
538 case ControlPointItem:
539 /* for object/track exclusivity, we don't call set_selected_track_as_side_effect (op); */
541 if (eff_mouse_mode != MouseRange) {
542 if (event->button.button != 3) {
543 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
545 _mouse_changed_selection |= set_selected_control_point_from_click (press, Selection::Set);
551 if (eff_mouse_mode != MouseRange) {
552 AutomationLine* argl = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
554 std::list<Selectable*> selectables;
555 uint32_t before, after;
556 samplecnt_t const where = (samplecnt_t) floor (event->button.x * samples_per_pixel) - clicked_regionview->region ()->position ();
558 if (!argl || !argl->control_points_adjacent (where, before, after)) {
562 selectables.push_back (argl->nth (before));
563 selectables.push_back (argl->nth (after));
568 selection->set (selectables);
569 _mouse_changed_selection = true;
574 selection->add (selectables);
575 _mouse_changed_selection = true;
578 case Selection::Toggle:
580 selection->toggle (selectables);
581 _mouse_changed_selection = true;
585 case Selection::Extend:
592 case AutomationLineItem:
593 if (eff_mouse_mode != MouseRange) {
594 AutomationLine* al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
595 std::list<Selectable*> selectables;
596 double mx = event->button.x;
597 double my = event->button.y;
599 al->grab_item().canvas_to_item (mx, my);
601 uint32_t before, after;
602 samplecnt_t const where = (samplecnt_t) floor (mx * samples_per_pixel);
604 if (!al || !al->control_points_adjacent (where, before, after)) {
608 selectables.push_back (al->nth (before));
609 selectables.push_back (al->nth (after));
614 selection->set (selectables);
615 _mouse_changed_selection = true;
620 selection->add (selectables);
621 _mouse_changed_selection = true;
624 case Selection::Toggle:
626 selection->toggle (selectables);
627 _mouse_changed_selection = true;
631 case Selection::Extend:
639 /* for context click, select track */
640 if (event->button.button == 3) {
641 selection->clear_tracks ();
642 set_selected_track_as_side_effect (op);
644 /* We won't get a release.*/
645 begin_reversible_selection_op (X_("Button 3 Menu Select"));
646 commit_reversible_selection_op ();
650 case AutomationTrackItem:
651 if (eff_mouse_mode != MouseDraw && op == Selection::Set) {
652 set_selected_track_as_side_effect (op);
657 if (press && event->button.button == 3) {
658 NoteBase* cnote = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
660 if (cnote->region_view().selection_size() == 0 || !cnote->selected()) {
661 selection->clear_points();
662 cnote->region_view().unique_select (cnote);
663 /* we won't get the release, so store the selection change now */
664 begin_reversible_selection_op (X_("Button 3 Note Selection"));
665 commit_reversible_selection_op ();
674 if ((!press) && _mouse_changed_selection) {
675 begin_reversible_selection_op (X_("Button Selection"));
676 commit_reversible_selection_op ();
677 _mouse_changed_selection = false;
682 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
684 /* single mouse clicks on any of these item types operate
685 independent of mouse mode, mostly because they are
686 not on the main track canvas or because we want
690 NoteBase* note = NULL;
693 case PlayheadCursorItem:
694 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
698 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
699 hide_marker (item, event);
701 _drags->set (new MarkerDrag (this, item), event);
705 case TempoMarkerItem:
707 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
717 new TempoMarkerDrag (
720 ArdourKeyboard::indicates_copy (event->button.state)
729 case MeterMarkerItem:
732 new MeterMarkerDrag (
735 ArdourKeyboard::indicates_copy (event->button.state)
743 _drags->set (new VideoTimeLineDrag (this, item), event);
751 case TimecodeRulerItem:
752 case SamplesRulerItem:
753 case MinsecRulerItem:
755 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)
756 && !ArdourKeyboard::indicates_constraint (event->button.state)) {
757 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
758 } else if (ArdourKeyboard::indicates_constraint (event->button.state)
759 && Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
760 _drags->set (new TempoTwistDrag (this, item), event);
761 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
762 _drags->set (new BBTRulerDrag (this, item), event);
768 case RangeMarkerBarItem:
769 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
770 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
771 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
772 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
774 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
779 case CdMarkerBarItem:
780 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
781 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
783 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
788 case TransportMarkerBarItem:
789 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
790 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
792 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
801 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
802 /* special case: allow trim of range selections in joined object mode;
803 in theory eff should equal MouseRange in this case, but it doesn't
804 because entering the range selection canvas item results in entered_regionview
805 being set to 0, so update_join_object_range_location acts as if we aren't
808 if (item_type == StartSelectionTrimItem) {
809 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
810 } else if (item_type == EndSelectionTrimItem) {
811 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
815 Editing::MouseMode eff = effective_mouse_mode ();
817 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
818 if (get_smart_mode()) {
820 case FadeInHandleItem:
821 case FadeInTrimHandleItem:
822 case FadeOutHandleItem:
823 case FadeOutTrimHandleItem:
834 case StartSelectionTrimItem:
835 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
838 case EndSelectionTrimItem:
839 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
843 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
844 start_selection_grab (item, event);
846 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
847 /* grab selection for moving */
848 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
850 /* this was debated, but decided the more common action was to
851 make a new selection */
852 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
857 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
858 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
860 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
865 case RegionViewNameHighlight:
866 if (!clicked_regionview->region()->locked()) {
867 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
873 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
874 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
876 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
885 case FadeInHandleItem:
886 case FadeOutHandleItem:
887 case LeftFrameHandle:
888 case RightFrameHandle:
889 case FeatureLineItem:
890 case RegionViewNameHighlight:
893 case AutomationTrackItem:
894 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
905 /* Existing note: allow trimming/motion */
906 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
907 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
908 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
910 _drags->set (new NoteDrag (this, item), event);
916 _drags->set (new LineDrag (this, item), event);
920 case ControlPointItem:
921 _drags->set (new ControlPointDrag (this, item), event);
925 case AutomationLineItem:
926 _drags->set (new LineDrag (this, item), event);
931 /* in the past, we created a new midi region here, but perhaps that is best left to the Draw mode */
934 case AutomationTrackItem:
935 /* rubberband drag to select automation points */
936 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
941 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
942 /* rubberband drag to select automation points */
943 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
954 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
955 event->type == GDK_BUTTON_PRESS) {
957 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
959 } else if (event->type == GDK_BUTTON_PRESS) {
962 case FadeInHandleItem:
964 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
968 case FadeOutHandleItem:
970 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
974 case StartCrossFadeItem:
975 case EndCrossFadeItem:
976 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor.
977 * 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);
986 case FeatureLineItem:
988 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
989 remove_transient(item);
993 _drags->set (new FeatureLineDrag (this, item), event);
999 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
1000 /* click on an automation region view; do nothing here and let the ARV's signal handler
1006 /* click on a normal region view */
1007 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1008 add_region_copy_drag (item, event, clicked_regionview);
1009 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
1010 add_region_brush_drag (item, event, clicked_regionview);
1012 add_region_drag (item, event, clicked_regionview);
1016 _drags->start_grab (event);
1020 case RegionViewNameHighlight:
1021 case LeftFrameHandle:
1022 case RightFrameHandle:
1023 if (!clicked_regionview->region()->locked()) {
1024 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1029 case FadeInTrimHandleItem:
1030 case FadeOutTrimHandleItem:
1031 if (!clicked_regionview->region()->locked()) {
1032 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
1037 case RegionViewName:
1039 /* rename happens on edit clicks */
1040 if (clicked_regionview->get_name_highlight()) {
1041 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1047 case ControlPointItem:
1048 _drags->set (new ControlPointDrag (this, item), event);
1052 case AutomationLineItem:
1053 _drags->set (new LineDrag (this, item), event);
1058 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1061 case AutomationTrackItem:
1063 TimeAxisView* parent = clicked_axisview->get_parent ();
1064 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1066 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1068 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1070 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1071 if (pl->n_regions() == 0) {
1072 /* Parent has no regions; create one so that we have somewhere to put automation */
1073 _drags->set (new RegionCreateDrag (this, item, parent), event);
1075 /* See if there's a region before the click that we can extend, and extend it if so */
1076 samplepos_t const t = canvas_event_sample (event);
1077 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1079 _drags->set (new RegionCreateDrag (this, item, parent), event);
1081 prev->set_length (t - prev->position (), get_grid_music_divisions (event->button.state));
1085 /* rubberband drag to select automation points */
1086 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1108 switch (item_type) {
1110 _drags->set (new LineDrag (this, item), event);
1113 case ControlPointItem:
1114 _drags->set (new ControlPointDrag (this, item), event);
1120 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
1121 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1122 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
1123 event, _cursors->up_down);
1125 double const y = event->button.y;
1126 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
1128 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1130 /* smart "join" mode: drag automation */
1131 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1139 case AutomationLineItem:
1140 _drags->set (new LineDrag (this, item), event);
1144 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1145 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1146 /* Note is big and pointer is near the end, trim */
1147 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1150 _drags->set (new NoteDrag (this, item), event);
1157 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1158 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1169 if (item_type == NoteItem) {
1170 /* resize-drag notes */
1171 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1172 if (note->big_enough_to_trim()) {
1173 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1177 } else if (clicked_regionview) {
1179 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1185 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1186 scrub_reversals = 0;
1187 scrub_reverse_distance = 0;
1188 last_scrub_x = event->button.x;
1189 scrubbing_direction = 0;
1201 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1203 Editing::MouseMode const eff = effective_mouse_mode ();
1206 switch (item_type) {
1208 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1209 add_region_copy_drag (item, event, clicked_regionview);
1211 add_region_drag (item, event, clicked_regionview);
1213 _drags->start_grab (event);
1216 case ControlPointItem:
1217 _drags->set (new ControlPointDrag (this, item), event);
1225 switch (item_type) {
1226 case RegionViewNameHighlight:
1227 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1231 case LeftFrameHandle:
1232 case RightFrameHandle:
1233 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1237 case RegionViewName:
1238 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1252 /* relax till release */
1264 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1266 if (event->type == GDK_2BUTTON_PRESS) {
1267 _drags->mark_double_click ();
1268 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1272 if (event->type != GDK_BUTTON_PRESS) {
1276 _track_canvas->grab_focus();
1278 if (_session && _session->actively_recording()) {
1282 button_selection (item, event, item_type);
1284 if (!_drags->active () &&
1285 (Keyboard::is_delete_event (&event->button) ||
1286 Keyboard::is_context_menu_event (&event->button) ||
1287 Keyboard::is_edit_event (&event->button))) {
1289 /* handled by button release */
1293 /* not rolling, effectively in range mode, follow edits enabled (likely
1294 * to start range drag), not in a fade handle (since that means we are
1295 * not starting a range drag): locate the PH here
1298 if ((item_type != FadeInHandleItem) &&
1299 (item_type != FadeOutHandleItem) &&
1300 !_drags->active () &&
1302 !_session->transport_rolling() &&
1303 (effective_mouse_mode() == MouseRange) &&
1304 UIConfiguration::instance().get_follow_edits() &&
1305 !_session->config.get_external_sync()) {
1307 MusicSample where (canvas_event_sample (event), 0);
1309 _session->request_locate (where.sample, false);
1312 switch (event->button.button) {
1314 return button_press_handler_1 (item, event, item_type);
1318 return button_press_handler_2 (item, event, item_type);
1325 return button_press_dispatch (&event->button);
1334 Editor::button_press_dispatch (GdkEventButton* ev)
1336 /* this function is intended only for buttons 4 and above.
1339 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1340 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1344 Editor::button_release_dispatch (GdkEventButton* ev)
1346 /* this function is intended only for buttons 4 and above.
1349 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1350 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1354 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1356 MusicSample where (canvas_event_sample (event), 0);
1357 AutomationTimeAxisView* atv = 0;
1359 _press_cursor_ctx.reset();
1361 /* no action if we're recording */
1363 if (_session && _session->actively_recording()) {
1367 bool were_dragging = false;
1369 if (!Keyboard::is_context_menu_event (&event->button)) {
1371 /* see if we're finishing a drag */
1373 if (_drags->active ()) {
1374 bool const r = _drags->end_grab (event);
1376 /* grab dragged, so do nothing else */
1380 were_dragging = true;
1383 update_region_layering_order_editor ();
1386 /* edit events get handled here */
1388 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1389 switch (item_type) {
1391 show_region_properties ();
1393 case TempoMarkerItem: {
1394 ArdourMarker* marker;
1395 TempoMarker* tempo_marker;
1397 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1398 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1399 abort(); /*NOTREACHED*/
1402 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1403 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1404 abort(); /*NOTREACHED*/
1407 edit_tempo_marker (*tempo_marker);
1411 case MeterMarkerItem: {
1412 ArdourMarker* marker;
1413 MeterMarker* meter_marker;
1415 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1416 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1417 abort(); /*NOTREACHED*/
1420 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1421 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1422 abort(); /*NOTREACHED*/
1424 edit_meter_marker (*meter_marker);
1428 case RegionViewName:
1429 if (clicked_regionview->name_active()) {
1430 return mouse_rename_region (item, event);
1434 case ControlPointItem:
1435 edit_control_point (item);
1444 /* context menu events get handled here */
1445 if (Keyboard::is_context_menu_event (&event->button)) {
1447 context_click_event = *event;
1449 if (!_drags->active ()) {
1451 /* no matter which button pops up the context menu, tell the menu
1452 widget to use button 1 to drive menu selection.
1455 switch (item_type) {
1457 case FadeInHandleItem:
1458 case FadeInTrimHandleItem:
1459 case StartCrossFadeItem:
1460 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1464 case FadeOutHandleItem:
1465 case FadeOutTrimHandleItem:
1466 case EndCrossFadeItem:
1467 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1470 case LeftFrameHandle:
1471 case RightFrameHandle:
1475 popup_track_context_menu (1, event->button.time, item_type, false);
1479 case RegionViewNameHighlight:
1480 case RegionViewName:
1481 popup_track_context_menu (1, event->button.time, item_type, false);
1485 popup_track_context_menu (1, event->button.time, item_type, true);
1488 case AutomationTrackItem:
1489 popup_track_context_menu (1, event->button.time, item_type, false);
1493 case RangeMarkerBarItem:
1494 case TransportMarkerBarItem:
1495 case CdMarkerBarItem:
1497 case TempoCurveItem:
1500 case TimecodeRulerItem:
1501 case SamplesRulerItem:
1502 case MinsecRulerItem:
1504 popup_ruler_menu (where.sample, item_type);
1508 marker_context_menu (&event->button, item);
1511 case TempoMarkerItem:
1512 tempo_or_meter_marker_context_menu (&event->button, item);
1515 case MeterMarkerItem:
1516 tempo_or_meter_marker_context_menu (&event->button, item);
1519 case CrossfadeViewItem:
1520 popup_track_context_menu (1, event->button.time, item_type, false);
1523 case ControlPointItem:
1524 popup_control_point_context_menu (item, event);
1528 if (internal_editing()) {
1529 popup_note_context_menu (item, event);
1541 /* delete events get handled here */
1543 Editing::MouseMode const eff = effective_mouse_mode ();
1545 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1547 switch (item_type) {
1548 case TempoMarkerItem:
1549 remove_tempo_marker (item);
1552 case MeterMarkerItem:
1553 remove_meter_marker (item);
1557 remove_marker (*item, event);
1561 if (eff == MouseObject) {
1562 remove_clicked_region ();
1566 case ControlPointItem:
1567 remove_control_point (item);
1571 remove_midi_note (item, event);
1580 switch (event->button.button) {
1583 switch (item_type) {
1584 /* see comments in button_press_handler */
1585 case PlayheadCursorItem:
1588 case AutomationLineItem:
1589 case StartSelectionTrimItem:
1590 case EndSelectionTrimItem:
1594 if (!_dragging_playhead) {
1595 snap_to_with_modifier (where, event, RoundNearest, SnapToGrid_Scaled);
1596 mouse_add_new_marker (where.sample);
1600 case CdMarkerBarItem:
1601 if (!_dragging_playhead) {
1602 /* if we get here then a dragged range wasn't done */
1603 snap_to_with_modifier (where, event, RoundNearest, SnapToGrid_Scaled);
1604 mouse_add_new_marker (where.sample, true);
1608 case TempoCurveItem:
1609 if (!_dragging_playhead && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1610 snap_to_with_modifier (where, event);
1611 mouse_add_new_tempo_event (where.sample);
1616 if (!_dragging_playhead && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1617 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1622 case TimecodeRulerItem:
1623 case SamplesRulerItem:
1624 case MinsecRulerItem:
1635 switch (item_type) {
1638 /* check that we didn't drag before releasing, since
1639 its really annoying to create new control
1640 points when doing this.
1642 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1643 if (!were_dragging && arv) {
1644 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1645 arv->add_gain_point_event (item, event, with_guard_points);
1651 case AutomationTrackItem: {
1652 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1653 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1655 atv->add_automation_event (event, where.sample, event->button.y, with_guard_points);
1666 if (scrubbing_direction == 0) {
1667 /* no drag, just a click */
1668 switch (item_type) {
1670 play_selected_region ();
1675 } else if (_session) {
1676 /* make sure we stop */
1677 _session->request_transport_speed (0.0);
1686 /* do any (de)selection operations that should occur on button release */
1687 button_selection (item, event, item_type);
1697 switch (item_type) {
1699 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1701 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1704 /* Button2 click is unused */
1719 // x_style_paste (where, 1.0);
1740 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1743 ArdourMarker * marker;
1744 MeterMarker* m_marker = 0;
1745 TempoMarker* t_marker = 0;
1749 /* by the time we reach here, entered_regionview and entered trackview
1750 * will have already been set as appropriate. Things are done this
1751 * way because this method isn't passed a pointer to a variable type of
1752 * thing that is entered (which may or may not be canvas item).
1753 * (e.g. the actual entered regionview)
1756 choose_canvas_cursor_on_entry (item_type);
1758 switch (item_type) {
1759 case ControlPointItem:
1760 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1761 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1764 fraction = 1.0 - (cp->get_y() / cp->line().height());
1766 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1767 _verbose_cursor->show ();
1772 if (mouse_mode == MouseDraw) {
1773 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1775 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1780 case AutomationLineItem:
1781 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1782 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1784 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1789 case AutomationTrackItem:
1790 AutomationTimeAxisView* atv;
1791 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1792 clear_entered_track = false;
1793 set_entered_track (atv);
1798 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1801 entered_marker = marker;
1802 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1805 case MeterMarkerItem:
1806 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1809 entered_marker = m_marker;
1810 if (m_marker->meter().position_lock_style() == MusicTime) {
1811 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1813 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1817 case TempoMarkerItem:
1818 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1821 entered_marker = t_marker;
1822 if (t_marker->tempo().position_lock_style() == MusicTime) {
1823 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1825 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1829 case FadeInHandleItem:
1830 case FadeInTrimHandleItem:
1831 if (mouse_mode == MouseObject) {
1832 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1834 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1835 rect->set_fill_color (rv->get_fill_color());
1840 case FadeOutHandleItem:
1841 case FadeOutTrimHandleItem:
1842 if (mouse_mode == MouseObject) {
1843 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1845 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1846 rect->set_fill_color (rv->get_fill_color ());
1851 case FeatureLineItem:
1853 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1854 line->set_outline_color (0xFF0000FF);
1863 if (entered_regionview) {
1864 entered_regionview->entered();
1873 /* third pass to handle entered track status in a comprehensible way.
1876 switch (item_type) {
1878 case AutomationLineItem:
1879 case ControlPointItem:
1880 /* these do not affect the current entered track state */
1881 clear_entered_track = false;
1884 case AutomationTrackItem:
1885 /* handled above already */
1897 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1900 ArdourMarker *marker;
1901 TempoMarker *t_marker;
1902 MeterMarker *m_marker;
1907 if (!_enter_stack.empty()) {
1908 _enter_stack.pop_back();
1911 switch (item_type) {
1912 case ControlPointItem:
1913 _verbose_cursor->hide ();
1917 case AutomationLineItem:
1918 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1920 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1922 line->set_outline_color (al->get_line_color());
1928 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1932 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1933 location_flags_changed (loc);
1937 case MeterMarkerItem:
1938 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1942 if (m_marker->meter().position_lock_style() == MusicTime) {
1943 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1945 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1949 case TempoMarkerItem:
1950 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1954 if (t_marker->tempo().position_lock_style() == MusicTime) {
1955 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1957 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1961 case FadeInTrimHandleItem:
1962 case FadeOutTrimHandleItem:
1963 case FadeInHandleItem:
1964 case FadeOutHandleItem:
1966 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1968 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1973 case AutomationTrackItem:
1976 case FeatureLineItem:
1978 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1979 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1991 Editor::scrub (samplepos_t sample, double current_x)
1995 if (scrubbing_direction == 0) {
1997 _session->request_locate (sample, false);
1998 _session->request_transport_speed (0.1);
1999 scrubbing_direction = 1;
2003 if (last_scrub_x > current_x) {
2005 /* pointer moved to the left */
2007 if (scrubbing_direction > 0) {
2009 /* we reversed direction to go backwards */
2012 scrub_reverse_distance += (int) (last_scrub_x - current_x);
2016 /* still moving to the left (backwards) */
2018 scrub_reversals = 0;
2019 scrub_reverse_distance = 0;
2021 delta = 0.01 * (last_scrub_x - current_x);
2022 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
2026 /* pointer moved to the right */
2028 if (scrubbing_direction < 0) {
2029 /* we reversed direction to go forward */
2032 scrub_reverse_distance += (int) (current_x - last_scrub_x);
2035 /* still moving to the right */
2037 scrub_reversals = 0;
2038 scrub_reverse_distance = 0;
2040 delta = 0.01 * (current_x - last_scrub_x);
2041 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
2045 /* if there have been more than 2 opposite motion moves detected, or one that moves
2046 back more than 10 pixels, reverse direction
2049 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
2051 if (scrubbing_direction > 0) {
2052 /* was forwards, go backwards */
2053 _session->request_transport_speed (-0.1);
2054 scrubbing_direction = -1;
2056 /* was backwards, go forwards */
2057 _session->request_transport_speed (0.1);
2058 scrubbing_direction = 1;
2061 scrub_reverse_distance = 0;
2062 scrub_reversals = 0;
2066 last_scrub_x = current_x;
2070 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2072 _last_motion_y = event->motion.y;
2074 if (event->motion.is_hint) {
2077 /* We call this so that MOTION_NOTIFY events continue to be
2078 delivered to the canvas. We need to do this because we set
2079 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2080 the density of the events, at the expense of a round-trip
2081 to the server. Given that this will mostly occur on cases
2082 where DISPLAY = :0.0, and given the cost of what the motion
2083 event might do, its a good tradeoff.
2086 _track_canvas->get_pointer (x, y);
2089 if (current_stepping_trackview) {
2090 /* don't keep the persistent stepped trackview if the mouse moves */
2091 current_stepping_trackview = 0;
2092 step_timeout.disconnect ();
2095 if (_session && _session->actively_recording()) {
2096 /* Sorry. no dragging stuff around while we record */
2100 update_join_object_range_location (event->motion.y);
2102 if (_drags->active ()) {
2103 //drags change the snapped_cursor location, because we are snapping the thing being dragged, not the actual mouse cursor
2104 return _drags->motion_handler (event, from_autoscroll);
2106 //the snapped_cursor shows where an operation (like Split) is going to occur
2108 MusicSample where (0, 0);
2109 if (mouse_sample (where.sample, ignored)) {
2110 snap_to_with_modifier (where, event);
2111 set_snapped_cursor_position (where.sample);
2119 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2121 ControlPoint* control_point;
2123 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2124 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2125 abort(); /*NOTREACHED*/
2128 AutomationLine& line = control_point->line ();
2129 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2130 /* we shouldn't remove the first or last gain point in region gain lines */
2131 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2140 Editor::remove_control_point (ArdourCanvas::Item* item)
2142 if (!can_remove_control_point (item)) {
2146 ControlPoint* control_point;
2148 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2149 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2150 abort(); /*NOTREACHED*/
2153 control_point->line().remove_point (*control_point);
2157 Editor::edit_control_point (ArdourCanvas::Item* item)
2159 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2162 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2163 abort(); /*NOTREACHED*/
2166 ControlPointDialog d (p);
2168 if (d.run () != RESPONSE_ACCEPT) {
2172 p->line().modify_point_y (*p, d.get_y_fraction ());
2176 Editor::edit_notes (MidiRegionView* mrv)
2178 MidiRegionView::Selection const & s = mrv->selection();
2184 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2187 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2191 Editor::note_edit_done (int r, EditNoteDialog* d)
2198 Editor::edit_region (RegionView* rv)
2200 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
2201 temporal_zoom_selection (Both);
2203 rv->show_region_editor ();
2208 Editor::visible_order_range (int* low, int* high) const
2210 *low = TimeAxisView::max_order ();
2213 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2215 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2217 if (rtv && !rtv->hidden()) {
2219 if (*high < rtv->order()) {
2220 *high = rtv->order ();
2223 if (*low > rtv->order()) {
2224 *low = rtv->order ();
2231 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2233 /* Either add to or set the set the region selection, unless
2234 this is an alignment click (control used)
2237 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2238 TimeAxisView* tv = &rv.get_time_axis_view();
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 ()) {
2558 } else if (_session) {
2559 selection->clear ();
2561 /* if session is playing a range, cancel that */
2562 if (_session->get_play_range()) {
2563 _session->request_cancel_play_range();
2566 if (_session->solo_selection_active()) {
2568 _session->solo_selection (sl, false);
2572 ARDOUR_UI::instance()->reset_focus (&contents());
2575 /** Update _join_object_range_state which indicate whether we are over the top
2576 * or bottom half of a route view, used by the `join object/range' tool
2577 * mode. Coordinates in canvas space.
2580 Editor::update_join_object_range_location (double y)
2582 if (!get_smart_mode()) {
2583 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2587 JoinObjectRangeState const old = _join_object_range_state;
2589 if (mouse_mode == MouseObject) {
2590 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2591 } else if (mouse_mode == MouseRange) {
2592 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2595 if (entered_regionview) {
2597 /* TODO: there is currently a bug here(?)
2598 * 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
2599 * can it be fixed here?
2602 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2603 double const c = item_space.y / entered_regionview->height();
2605 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2607 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2608 if (_join_object_range_state != old && ctx) {
2609 ctx->cursor_ctx->change(which_track_cursor());
2612 } else if (entered_track) {
2614 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2616 if (entered_route_view) {
2621 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2623 double track_height = entered_route_view->view()->child_height();
2624 if (UIConfiguration::instance().get_show_name_highlight()) {
2625 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2627 double const c = cy / track_height;
2631 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2633 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2637 /* Other kinds of tracks use object mode */
2638 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2641 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2642 if (_join_object_range_state != old && ctx) {
2643 ctx->cursor_ctx->change(which_track_cursor());
2649 Editor::effective_mouse_mode () const
2651 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2653 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2661 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2663 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2666 e->region_view().delete_note (e->note ());
2669 /** Obtain the pointer position in canvas coordinates */
2671 Editor::get_pointer_position (double& x, double& y) const
2674 _track_canvas->get_pointer (px, py);
2675 _track_canvas->window_to_canvas (px, py, x, y);