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;
275 if (m == MouseAudition) m = MouseRange;
278 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
279 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
281 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
282 tact->set_active (false);
283 tact->set_active (true);
285 /* NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting */
289 Editor::mouse_mode_toggled (MouseMode m)
291 if (ARDOUR::Profile->get_mixbus()) {
292 if (m == MouseCut) m = MouseObject;
293 if (m == MouseAudition) m = MouseRange;
296 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
297 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
299 if (!tact->get_active()) {
300 /* this was just the notification that the old mode has been
301 * left. we'll get called again with the new mode active in a
307 if (_session && mouse_mode == MouseAudition) {
308 /* stop transport and reset default speed to avoid oddness with
310 _session->request_transport_speed (0.0, true);
313 const bool was_internal = internal_editing();
317 /* Switch snap type/mode if we're moving to/from an internal tool. Note
318 this must toggle the actions and not call set_snap_*() directly,
319 otherwise things get out of sync and the combo box stops working. */
320 if (!UIConfiguration::instance().get_grid_follows_internal()) {
321 grid_type_action(pre_internal_grid_type)->set_active(true);
322 snap_mode_action(pre_internal_snap_mode)->set_active(true);
323 } else if (!was_internal && internal_editing()) {
324 grid_type_action(internal_grid_type)->set_active(true);
325 snap_mode_action(internal_snap_mode)->set_active(true);
326 } else if (was_internal && !internal_editing()) {
327 grid_type_action(pre_internal_grid_type)->set_active(true);
328 snap_mode_action(pre_internal_snap_mode)->set_active(true);
333 /* this should generate a new enter event which will
334 trigger the appropiate cursor.
338 _track_canvas->re_enter ();
341 set_gain_envelope_visibility ();
343 update_time_selection_display ();
345 update_all_enter_cursors ();
347 MouseModeChanged (); /* EMIT SIGNAL */
351 Editor::internal_editing() const
353 return mouse_mode == Editing::MouseContent || mouse_mode == Editing::MouseDraw;
357 Editor::update_time_selection_display ()
359 switch (mouse_mode) {
361 selection->clear_objects ();
362 selection->clear_midi_notes ();
365 selection->clear_time ();
366 selection->clear_midi_notes ();
369 /* Clear regions, but not time or tracks, since that
370 would destroy the range selection rectangle, which we need to stick
371 around for AutomationRangeDrag. */
372 selection->clear_regions ();
373 selection->clear_playlists ();
376 /* This handles internal edit.
377 Clear everything except points and notes.
379 selection->clear_regions();
380 selection->clear_lines();
381 selection->clear_playlists ();
383 selection->clear_time ();
384 selection->clear_tracks ();
388 /* We probably want to keep region selection */
389 selection->clear_points ();
390 selection->clear_lines();
391 selection->clear_playlists ();
393 selection->clear_time ();
394 selection->clear_tracks ();
398 /*Don't lose lines or points if no action in this mode */
399 selection->clear_regions ();
400 selection->clear_playlists ();
401 selection->clear_time ();
402 selection->clear_tracks ();
406 /*Clear everything */
407 selection->clear_objects();
408 selection->clear_time ();
409 selection->clear_tracks ();
415 Editor::step_mouse_mode (bool next)
417 const int n_mouse_modes = (int)MouseContent + 1;
418 int current = (int)current_mouse_mode();
420 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
422 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
427 Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
430 /* in object/audition/timefx/gain-automation mode,
431 any button press sets the selection if the object
432 can be selected. this is a bit of hack, because
433 we want to avoid this if the mouse operation is a
436 note: not dbl-click or triple-click
438 Also note that there is no region selection in internal edit mode, otherwise
439 for operations operating on the selection (e.g. cut) it is not obvious whether
440 to cut notes or regions.
443 MouseMode eff_mouse_mode = effective_mouse_mode ();
445 if (eff_mouse_mode == MouseCut) {
446 /* never change selection in cut mode */
450 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
451 /* context clicks are always about object properties, even if
452 we're in range mode within smart mode.
454 eff_mouse_mode = MouseObject;
457 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
458 if (get_smart_mode()) {
460 case FadeInHandleItem:
461 case FadeInTrimHandleItem:
462 case FadeOutHandleItem:
463 case FadeOutTrimHandleItem:
464 eff_mouse_mode = MouseObject;
471 if (((mouse_mode != MouseObject) &&
472 (mouse_mode != MouseAudition || item_type != RegionItem) &&
473 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
474 (mouse_mode != MouseDraw) &&
475 (mouse_mode != MouseContent || item_type == RegionItem)) ||
476 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
480 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
482 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
484 /* almost no selection action on modified button-2 or button-3 events */
486 if ((item_type != RegionItem && event->button.button != 2)
487 /* for selection of control points prior to delete (shift-right click) */
488 && !(item_type == ControlPointItem && event->button.button == 3 && event->type == GDK_BUTTON_PRESS)) {
494 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
495 bool press = (event->type == GDK_BUTTON_PRESS);
498 _mouse_changed_selection = false;
503 if (eff_mouse_mode == MouseDraw) {
507 if (eff_mouse_mode != MouseRange) {
508 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
510 /* don't change the selection unless the
511 clicked track is not currently selected. if
512 so, "collapse" the selection to just this
515 if (!selection->selected (clicked_axisview)) {
516 set_selected_track_as_side_effect (Selection::Set);
520 if (eff_mouse_mode != MouseRange) {
521 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
526 case RegionViewNameHighlight:
528 case LeftFrameHandle:
529 case RightFrameHandle:
530 case FadeInHandleItem:
531 case FadeInTrimHandleItem:
533 case FadeOutHandleItem:
534 case FadeOutTrimHandleItem:
536 case StartCrossFadeItem:
537 case EndCrossFadeItem:
538 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
539 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
540 } else if (event->type == GDK_BUTTON_PRESS) {
541 set_selected_track_as_side_effect (op);
545 case ControlPointItem:
546 /* for object/track exclusivity, we don't call set_selected_track_as_side_effect (op); */
548 if (eff_mouse_mode != MouseRange) {
549 if (event->button.button != 3) {
550 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
552 _mouse_changed_selection |= set_selected_control_point_from_click (press, Selection::Set);
558 if (eff_mouse_mode != MouseRange) {
559 AutomationLine* argl = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
561 std::list<Selectable*> selectables;
562 uint32_t before, after;
563 samplecnt_t const where = (samplecnt_t) floor (event->button.x * samples_per_pixel) - clicked_regionview->region ()->position ();
565 if (!argl || !argl->control_points_adjacent (where, before, after)) {
569 selectables.push_back (argl->nth (before));
570 selectables.push_back (argl->nth (after));
575 selection->set (selectables);
576 _mouse_changed_selection = true;
581 selection->add (selectables);
582 _mouse_changed_selection = true;
585 case Selection::Toggle:
587 selection->toggle (selectables);
588 _mouse_changed_selection = true;
592 case Selection::Extend:
599 case AutomationLineItem:
600 if (eff_mouse_mode != MouseRange) {
601 AutomationLine* al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
602 std::list<Selectable*> selectables;
603 double mx = event->button.x;
604 double my = event->button.y;
606 al->grab_item().canvas_to_item (mx, my);
608 uint32_t before, after;
609 samplecnt_t const where = (samplecnt_t) floor (mx * samples_per_pixel);
611 if (!al || !al->control_points_adjacent (where, before, after)) {
615 selectables.push_back (al->nth (before));
616 selectables.push_back (al->nth (after));
621 selection->set (selectables);
622 _mouse_changed_selection = true;
627 selection->add (selectables);
628 _mouse_changed_selection = true;
631 case Selection::Toggle:
633 selection->toggle (selectables);
634 _mouse_changed_selection = true;
638 case Selection::Extend:
646 /* for context click, select track */
647 if (event->button.button == 3) {
648 selection->clear_tracks ();
649 set_selected_track_as_side_effect (op);
651 /* We won't get a release.*/
652 begin_reversible_selection_op (X_("Button 3 Menu Select"));
653 commit_reversible_selection_op ();
657 case AutomationTrackItem:
658 if (eff_mouse_mode != MouseDraw && op == Selection::Set) {
659 set_selected_track_as_side_effect (op);
664 if (press && event->button.button == 3) {
665 NoteBase* cnote = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
667 if (cnote->region_view().selection_size() == 0 || !cnote->selected()) {
668 selection->clear_points();
669 cnote->region_view().unique_select (cnote);
670 /* we won't get the release, so store the selection change now */
671 begin_reversible_selection_op (X_("Button 3 Note Selection"));
672 commit_reversible_selection_op ();
681 if ((!press) && _mouse_changed_selection) {
682 begin_reversible_selection_op (X_("Button Selection"));
683 commit_reversible_selection_op ();
684 _mouse_changed_selection = false;
689 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
691 /* single mouse clicks on any of these item types operate
692 independent of mouse mode, mostly because they are
693 not on the main track canvas or because we want
697 NoteBase* note = NULL;
700 case PlayheadCursorItem:
701 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
705 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
706 hide_marker (item, event);
708 _drags->set (new MarkerDrag (this, item), event);
712 case TempoMarkerItem:
714 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
724 new TempoMarkerDrag (
727 ArdourKeyboard::indicates_copy (event->button.state)
736 case MeterMarkerItem:
739 new MeterMarkerDrag (
742 ArdourKeyboard::indicates_copy (event->button.state)
750 _drags->set (new VideoTimeLineDrag (this, item), event);
758 case TimecodeRulerItem:
759 case SamplesRulerItem:
760 case MinsecRulerItem:
762 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)
763 && !ArdourKeyboard::indicates_constraint (event->button.state)) {
764 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
765 } else if (ArdourKeyboard::indicates_constraint (event->button.state)
766 && Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
767 _drags->set (new TempoTwistDrag (this, item), event);
768 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
769 _drags->set (new BBTRulerDrag (this, item), event);
775 case RangeMarkerBarItem:
776 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
777 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
778 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
779 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
781 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
786 case CdMarkerBarItem:
787 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
788 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
790 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
795 case TransportMarkerBarItem:
796 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
797 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
799 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
808 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
809 /* special case: allow trim of range selections in joined object mode;
810 in theory eff should equal MouseRange in this case, but it doesn't
811 because entering the range selection canvas item results in entered_regionview
812 being set to 0, so update_join_object_range_location acts as if we aren't
815 if (item_type == StartSelectionTrimItem) {
816 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
817 } else if (item_type == EndSelectionTrimItem) {
818 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
822 Editing::MouseMode eff = effective_mouse_mode ();
824 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
825 if (get_smart_mode()) {
827 case FadeInHandleItem:
828 case FadeInTrimHandleItem:
829 case FadeOutHandleItem:
830 case FadeOutTrimHandleItem:
841 case StartSelectionTrimItem:
842 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
845 case EndSelectionTrimItem:
846 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
850 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
851 start_selection_grab (item, event);
853 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
854 /* grab selection for moving */
855 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
857 /* this was debated, but decided the more common action was to
858 make a new selection */
859 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
864 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
865 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
867 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
872 case RegionViewNameHighlight:
873 if (!clicked_regionview->region()->locked()) {
874 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
880 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
881 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
883 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
892 case FadeInHandleItem:
893 case FadeOutHandleItem:
894 case LeftFrameHandle:
895 case RightFrameHandle:
896 case FeatureLineItem:
897 case RegionViewNameHighlight:
900 case AutomationTrackItem:
901 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
912 /* Existing note: allow trimming/motion */
913 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
914 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
915 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
917 _drags->set (new NoteDrag (this, item), event);
923 _drags->set (new LineDrag (this, item), event);
927 case ControlPointItem:
928 _drags->set (new ControlPointDrag (this, item), event);
932 case AutomationLineItem:
933 _drags->set (new LineDrag (this, item), event);
938 /* in the past, we created a new midi region here, but perhaps that is best left to the Draw mode */
941 case AutomationTrackItem:
942 /* rubberband drag to select automation points */
943 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
948 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
949 /* rubberband drag to select automation points */
950 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
961 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
962 event->type == GDK_BUTTON_PRESS) {
964 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
966 } else if (event->type == GDK_BUTTON_PRESS) {
969 case FadeInHandleItem:
971 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
975 case FadeOutHandleItem:
977 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
981 case StartCrossFadeItem:
982 case EndCrossFadeItem:
983 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor.
984 * For not this is not fully implemented */
986 if (!clicked_regionview->region()->locked()) {
987 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
993 case FeatureLineItem:
995 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
996 remove_transient(item);
1000 _drags->set (new FeatureLineDrag (this, item), event);
1006 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
1007 /* click on an automation region view; do nothing here and let the ARV's signal handler
1013 /* click on a normal region view */
1014 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1015 add_region_copy_drag (item, event, clicked_regionview);
1016 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
1017 add_region_brush_drag (item, event, clicked_regionview);
1019 add_region_drag (item, event, clicked_regionview);
1023 _drags->start_grab (event);
1027 case RegionViewNameHighlight:
1028 case LeftFrameHandle:
1029 case RightFrameHandle:
1030 if (!clicked_regionview->region()->locked()) {
1031 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1036 case FadeInTrimHandleItem:
1037 case FadeOutTrimHandleItem:
1038 if (!clicked_regionview->region()->locked()) {
1039 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
1044 case RegionViewName:
1046 /* rename happens on edit clicks */
1047 if (clicked_regionview->get_name_highlight()) {
1048 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1054 case ControlPointItem:
1055 _drags->set (new ControlPointDrag (this, item), event);
1059 case AutomationLineItem:
1060 _drags->set (new LineDrag (this, item), event);
1065 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1068 case AutomationTrackItem:
1070 TimeAxisView* parent = clicked_axisview->get_parent ();
1071 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1073 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1075 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1077 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1078 if (pl->n_regions() == 0) {
1079 /* Parent has no regions; create one so that we have somewhere to put automation */
1080 _drags->set (new RegionCreateDrag (this, item, parent), event);
1082 /* See if there's a region before the click that we can extend, and extend it if so */
1083 samplepos_t const t = canvas_event_sample (event);
1084 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1086 _drags->set (new RegionCreateDrag (this, item, parent), event);
1088 prev->set_length (t - prev->position (), get_grid_music_divisions (event->button.state));
1092 /* rubberband drag to select automation points */
1093 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1115 switch (item_type) {
1117 _drags->set (new LineDrag (this, item), event);
1120 case ControlPointItem:
1121 _drags->set (new ControlPointDrag (this, item), event);
1127 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
1128 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1129 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
1130 event, _cursors->up_down);
1132 double const y = event->button.y;
1133 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
1135 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1137 /* smart "join" mode: drag automation */
1138 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1146 case AutomationLineItem:
1147 _drags->set (new LineDrag (this, item), event);
1151 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1152 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1153 /* Note is big and pointer is near the end, trim */
1154 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1157 _drags->set (new NoteDrag (this, item), event);
1164 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1165 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1176 if (item_type == NoteItem) {
1177 /* resize-drag notes */
1178 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1179 if (note->big_enough_to_trim()) {
1180 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1184 } else if (clicked_regionview) {
1186 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1192 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1193 scrub_reversals = 0;
1194 scrub_reverse_distance = 0;
1195 last_scrub_x = event->button.x;
1196 scrubbing_direction = 0;
1208 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1210 Editing::MouseMode const eff = effective_mouse_mode ();
1213 switch (item_type) {
1215 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1216 add_region_copy_drag (item, event, clicked_regionview);
1218 add_region_drag (item, event, clicked_regionview);
1220 _drags->start_grab (event);
1223 case ControlPointItem:
1224 _drags->set (new ControlPointDrag (this, item), event);
1232 switch (item_type) {
1233 case RegionViewNameHighlight:
1234 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1238 case LeftFrameHandle:
1239 case RightFrameHandle:
1240 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1244 case RegionViewName:
1245 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1259 /* relax till release */
1271 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1273 if (event->type == GDK_2BUTTON_PRESS) {
1274 _drags->mark_double_click ();
1275 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1279 if (event->type != GDK_BUTTON_PRESS) {
1283 _track_canvas->grab_focus();
1285 if (_session && _session->actively_recording()) {
1289 button_selection (item, event, item_type);
1291 if (!_drags->active () &&
1292 (Keyboard::is_delete_event (&event->button) ||
1293 Keyboard::is_context_menu_event (&event->button) ||
1294 Keyboard::is_edit_event (&event->button))) {
1296 /* handled by button release */
1300 /* not rolling, effectively in range mode, follow edits enabled (likely
1301 * to start range drag), not in a fade handle (since that means we are
1302 * not starting a range drag): locate the PH here
1305 if ((item_type != FadeInHandleItem) &&
1306 (item_type != FadeOutHandleItem) &&
1307 !_drags->active () &&
1309 !_session->transport_rolling() &&
1310 (effective_mouse_mode() == MouseRange) &&
1311 UIConfiguration::instance().get_follow_edits() &&
1312 !_session->config.get_external_sync()) {
1314 MusicSample where (canvas_event_sample (event), 0);
1316 _session->request_locate (where.sample, false);
1319 switch (event->button.button) {
1321 return button_press_handler_1 (item, event, item_type);
1325 return button_press_handler_2 (item, event, item_type);
1332 return button_press_dispatch (&event->button);
1341 Editor::button_press_dispatch (GdkEventButton* ev)
1343 /* this function is intended only for buttons 4 and above.
1346 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1347 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1351 Editor::button_release_dispatch (GdkEventButton* ev)
1353 /* this function is intended only for buttons 4 and above.
1356 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1357 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1361 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1363 MusicSample where (canvas_event_sample (event), 0);
1364 AutomationTimeAxisView* atv = 0;
1366 _press_cursor_ctx.reset();
1368 /* no action if we're recording */
1370 if (_session && _session->actively_recording()) {
1374 bool were_dragging = false;
1376 if (!Keyboard::is_context_menu_event (&event->button)) {
1378 /* see if we're finishing a drag */
1380 if (_drags->active ()) {
1381 bool const r = _drags->end_grab (event);
1383 /* grab dragged, so do nothing else */
1387 were_dragging = true;
1390 update_region_layering_order_editor ();
1393 /* edit events get handled here */
1395 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1396 switch (item_type) {
1398 show_region_properties ();
1400 case TempoMarkerItem: {
1401 ArdourMarker* marker;
1402 TempoMarker* tempo_marker;
1404 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1405 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1406 abort(); /*NOTREACHED*/
1409 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1410 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1411 abort(); /*NOTREACHED*/
1414 edit_tempo_marker (*tempo_marker);
1418 case MeterMarkerItem: {
1419 ArdourMarker* marker;
1420 MeterMarker* meter_marker;
1422 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1423 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1424 abort(); /*NOTREACHED*/
1427 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1428 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1429 abort(); /*NOTREACHED*/
1431 edit_meter_marker (*meter_marker);
1435 case RegionViewName:
1436 if (clicked_regionview->name_active()) {
1437 return mouse_rename_region (item, event);
1441 case ControlPointItem:
1442 edit_control_point (item);
1451 /* context menu events get handled here */
1452 if (Keyboard::is_context_menu_event (&event->button)) {
1454 context_click_event = *event;
1456 if (!_drags->active ()) {
1458 /* no matter which button pops up the context menu, tell the menu
1459 widget to use button 1 to drive menu selection.
1462 switch (item_type) {
1464 case FadeInHandleItem:
1465 case FadeInTrimHandleItem:
1466 case StartCrossFadeItem:
1467 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1471 case FadeOutHandleItem:
1472 case FadeOutTrimHandleItem:
1473 case EndCrossFadeItem:
1474 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1477 case LeftFrameHandle:
1478 case RightFrameHandle:
1482 popup_track_context_menu (1, event->button.time, item_type, false);
1486 case RegionViewNameHighlight:
1487 case RegionViewName:
1488 popup_track_context_menu (1, event->button.time, item_type, false);
1492 popup_track_context_menu (1, event->button.time, item_type, true);
1495 case AutomationTrackItem:
1496 popup_track_context_menu (1, event->button.time, item_type, false);
1500 case RangeMarkerBarItem:
1501 case TransportMarkerBarItem:
1502 case CdMarkerBarItem:
1504 case TempoCurveItem:
1507 case TimecodeRulerItem:
1508 case SamplesRulerItem:
1509 case MinsecRulerItem:
1511 popup_ruler_menu (where.sample, item_type);
1515 marker_context_menu (&event->button, item);
1518 case TempoMarkerItem:
1519 tempo_or_meter_marker_context_menu (&event->button, item);
1522 case MeterMarkerItem:
1523 tempo_or_meter_marker_context_menu (&event->button, item);
1526 case CrossfadeViewItem:
1527 popup_track_context_menu (1, event->button.time, item_type, false);
1530 case ControlPointItem:
1531 popup_control_point_context_menu (item, event);
1535 if (internal_editing()) {
1536 popup_note_context_menu (item, event);
1548 /* delete events get handled here */
1550 Editing::MouseMode const eff = effective_mouse_mode ();
1552 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1554 switch (item_type) {
1555 case TempoMarkerItem:
1556 remove_tempo_marker (item);
1559 case MeterMarkerItem:
1560 remove_meter_marker (item);
1564 remove_marker (*item, event);
1568 if (eff == MouseObject) {
1569 remove_clicked_region ();
1573 case ControlPointItem:
1574 remove_control_point (item);
1578 remove_midi_note (item, event);
1587 switch (event->button.button) {
1590 switch (item_type) {
1591 /* see comments in button_press_handler */
1592 case PlayheadCursorItem:
1595 case AutomationLineItem:
1596 case StartSelectionTrimItem:
1597 case EndSelectionTrimItem:
1601 if (!_dragging_playhead) {
1602 snap_to_with_modifier (where, event, RoundNearest, SnapToGrid_Scaled);
1603 mouse_add_new_marker (where.sample);
1607 case CdMarkerBarItem:
1608 if (!_dragging_playhead) {
1609 /* if we get here then a dragged range wasn't done */
1610 snap_to_with_modifier (where, event, RoundNearest, SnapToGrid_Scaled);
1611 mouse_add_new_marker (where.sample, true);
1615 case TempoCurveItem:
1616 if (!_dragging_playhead && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1617 snap_to_with_modifier (where, event);
1618 mouse_add_new_tempo_event (where.sample);
1623 if (!_dragging_playhead && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1624 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1629 case TimecodeRulerItem:
1630 case SamplesRulerItem:
1631 case MinsecRulerItem:
1642 switch (item_type) {
1645 /* check that we didn't drag before releasing, since
1646 its really annoying to create new control
1647 points when doing this.
1649 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1650 if (!were_dragging && arv) {
1651 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1652 arv->add_gain_point_event (item, event, with_guard_points);
1658 case AutomationTrackItem: {
1659 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1660 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1662 atv->add_automation_event (event, where.sample, event->button.y, with_guard_points);
1673 if (scrubbing_direction == 0) {
1674 /* no drag, just a click */
1675 switch (item_type) {
1677 play_selected_region ();
1682 } else if (_session) {
1683 /* make sure we stop */
1684 _session->request_transport_speed (0.0);
1693 /* do any (de)selection operations that should occur on button release */
1694 button_selection (item, event, item_type);
1704 switch (item_type) {
1706 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1708 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1711 /* Button2 click is unused */
1726 // x_style_paste (where, 1.0);
1747 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1750 ArdourMarker * marker;
1751 MeterMarker* m_marker = 0;
1752 TempoMarker* t_marker = 0;
1756 /* by the time we reach here, entered_regionview and entered trackview
1757 * will have already been set as appropriate. Things are done this
1758 * way because this method isn't passed a pointer to a variable type of
1759 * thing that is entered (which may or may not be canvas item).
1760 * (e.g. the actual entered regionview)
1763 choose_canvas_cursor_on_entry (item_type);
1765 switch (item_type) {
1766 case ControlPointItem:
1767 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1768 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1771 fraction = 1.0 - (cp->get_y() / cp->line().height());
1773 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1774 _verbose_cursor->show ();
1779 if (mouse_mode == MouseDraw) {
1780 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1782 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1787 case AutomationLineItem:
1788 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1789 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1791 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1796 case AutomationTrackItem:
1797 AutomationTimeAxisView* atv;
1798 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1799 clear_entered_track = false;
1800 set_entered_track (atv);
1805 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1808 entered_marker = marker;
1809 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1812 case MeterMarkerItem:
1813 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1816 entered_marker = m_marker;
1817 if (m_marker->meter().position_lock_style() == MusicTime) {
1818 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1820 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1824 case TempoMarkerItem:
1825 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1828 entered_marker = t_marker;
1829 if (t_marker->tempo().position_lock_style() == MusicTime) {
1830 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1832 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1836 case FadeInHandleItem:
1837 case FadeInTrimHandleItem:
1838 if (mouse_mode == MouseObject) {
1839 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1841 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1842 rect->set_fill_color (rv->get_fill_color());
1847 case FadeOutHandleItem:
1848 case FadeOutTrimHandleItem:
1849 if (mouse_mode == MouseObject) {
1850 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1852 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1853 rect->set_fill_color (rv->get_fill_color ());
1858 case FeatureLineItem:
1860 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1861 line->set_outline_color (0xFF0000FF);
1870 if (entered_regionview) {
1871 entered_regionview->entered();
1880 /* third pass to handle entered track status in a comprehensible way.
1883 switch (item_type) {
1885 case AutomationLineItem:
1886 case ControlPointItem:
1887 /* these do not affect the current entered track state */
1888 clear_entered_track = false;
1891 case AutomationTrackItem:
1892 /* handled above already */
1904 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1907 ArdourMarker *marker;
1908 TempoMarker *t_marker;
1909 MeterMarker *m_marker;
1914 if (!_enter_stack.empty()) {
1915 _enter_stack.pop_back();
1918 switch (item_type) {
1919 case ControlPointItem:
1920 _verbose_cursor->hide ();
1924 case AutomationLineItem:
1925 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1927 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1929 line->set_outline_color (al->get_line_color());
1935 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1939 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1940 location_flags_changed (loc);
1944 case MeterMarkerItem:
1945 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1949 if (m_marker->meter().position_lock_style() == MusicTime) {
1950 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1952 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1956 case TempoMarkerItem:
1957 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1961 if (t_marker->tempo().position_lock_style() == MusicTime) {
1962 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1964 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1968 case FadeInTrimHandleItem:
1969 case FadeOutTrimHandleItem:
1970 case FadeInHandleItem:
1971 case FadeOutHandleItem:
1973 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1975 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1980 case AutomationTrackItem:
1983 case FeatureLineItem:
1985 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1986 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1998 Editor::scrub (samplepos_t sample, double current_x)
2002 if (scrubbing_direction == 0) {
2004 _session->request_locate (sample, false);
2005 _session->request_transport_speed (0.1);
2006 scrubbing_direction = 1;
2010 if (last_scrub_x > current_x) {
2012 /* pointer moved to the left */
2014 if (scrubbing_direction > 0) {
2016 /* we reversed direction to go backwards */
2019 scrub_reverse_distance += (int) (last_scrub_x - current_x);
2023 /* still moving to the left (backwards) */
2025 scrub_reversals = 0;
2026 scrub_reverse_distance = 0;
2028 delta = 0.01 * (last_scrub_x - current_x);
2029 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
2033 /* pointer moved to the right */
2035 if (scrubbing_direction < 0) {
2036 /* we reversed direction to go forward */
2039 scrub_reverse_distance += (int) (current_x - last_scrub_x);
2042 /* still moving to the right */
2044 scrub_reversals = 0;
2045 scrub_reverse_distance = 0;
2047 delta = 0.01 * (current_x - last_scrub_x);
2048 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
2052 /* if there have been more than 2 opposite motion moves detected, or one that moves
2053 back more than 10 pixels, reverse direction
2056 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
2058 if (scrubbing_direction > 0) {
2059 /* was forwards, go backwards */
2060 _session->request_transport_speed (-0.1);
2061 scrubbing_direction = -1;
2063 /* was backwards, go forwards */
2064 _session->request_transport_speed (0.1);
2065 scrubbing_direction = 1;
2068 scrub_reverse_distance = 0;
2069 scrub_reversals = 0;
2073 last_scrub_x = current_x;
2077 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2079 _last_motion_y = event->motion.y;
2081 if (event->motion.is_hint) {
2084 /* We call this so that MOTION_NOTIFY events continue to be
2085 delivered to the canvas. We need to do this because we set
2086 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2087 the density of the events, at the expense of a round-trip
2088 to the server. Given that this will mostly occur on cases
2089 where DISPLAY = :0.0, and given the cost of what the motion
2090 event might do, its a good tradeoff.
2093 _track_canvas->get_pointer (x, y);
2096 if (current_stepping_trackview) {
2097 /* don't keep the persistent stepped trackview if the mouse moves */
2098 current_stepping_trackview = 0;
2099 step_timeout.disconnect ();
2102 if (_session && _session->actively_recording()) {
2103 /* Sorry. no dragging stuff around while we record */
2107 update_join_object_range_location (event->motion.y);
2109 if (_drags->active ()) {
2110 //drags change the snapped_cursor location, because we are snapping the thing being dragged, not the actual mouse cursor
2111 return _drags->motion_handler (event, from_autoscroll);
2113 //the snapped_cursor shows where an operation (like Split) is going to occur
2115 MusicSample where (0, 0);
2116 if (mouse_sample (where.sample, ignored)) {
2117 snap_to_with_modifier (where, event);
2118 set_snapped_cursor_position (where.sample);
2126 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2128 ControlPoint* control_point;
2130 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2131 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2132 abort(); /*NOTREACHED*/
2135 AutomationLine& line = control_point->line ();
2136 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2137 /* we shouldn't remove the first or last gain point in region gain lines */
2138 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2147 Editor::remove_control_point (ArdourCanvas::Item* item)
2149 if (!can_remove_control_point (item)) {
2153 ControlPoint* control_point;
2155 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2156 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2157 abort(); /*NOTREACHED*/
2160 control_point->line().remove_point (*control_point);
2164 Editor::edit_control_point (ArdourCanvas::Item* item)
2166 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2169 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2170 abort(); /*NOTREACHED*/
2173 ControlPointDialog d (p);
2175 if (d.run () != RESPONSE_ACCEPT) {
2179 p->line().modify_point_y (*p, d.get_y_fraction ());
2183 Editor::edit_notes (MidiRegionView* mrv)
2185 MidiRegionView::Selection const & s = mrv->selection();
2191 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2194 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2198 Editor::note_edit_done (int r, EditNoteDialog* d)
2205 Editor::edit_region (RegionView* rv)
2207 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
2208 temporal_zoom_selection (Both);
2210 rv->show_region_editor ();
2215 Editor::visible_order_range (int* low, int* high) const
2217 *low = TimeAxisView::max_order ();
2220 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2222 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2224 if (rtv && !rtv->hidden()) {
2226 if (*high < rtv->order()) {
2227 *high = rtv->order ();
2230 if (*low > rtv->order()) {
2231 *low = rtv->order ();
2238 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2240 /* Either add to or set the set the region selection, unless
2241 this is an alignment click (control used)
2244 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2245 TimeAxisView* tv = &rv.get_time_axis_view();
2247 samplepos_t where = get_preferred_edit_position();
2251 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2253 align_region (rv.region(), SyncPoint, where);
2255 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2257 align_region (rv.region(), End, where);
2261 align_region (rv.region(), Start, where);
2268 Editor::collect_new_region_view (RegionView* rv)
2270 latest_regionviews.push_back (rv);
2274 Editor::collect_and_select_new_region_view (RegionView* rv)
2277 latest_regionviews.push_back (rv);
2281 Editor::cancel_selection ()
2283 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2284 (*i)->hide_selection ();
2287 selection->clear ();
2288 clicked_selection = 0;
2292 Editor::cancel_time_selection ()
2294 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2295 (*i)->hide_selection ();
2297 selection->time.clear ();
2298 clicked_selection = 0;
2302 Editor::point_trim (GdkEvent* event, samplepos_t new_bound)
2304 RegionView* rv = clicked_regionview;
2306 /* Choose action dependant on which button was pressed */
2307 switch (event->button.button) {
2309 begin_reversible_command (_("start point trim"));
2311 if (selection->selected (rv)) {
2312 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2313 i != selection->regions.by_layer().end(); ++i)
2315 if (!(*i)->region()->locked()) {
2316 (*i)->region()->clear_changes ();
2317 (*i)->region()->trim_front (new_bound);
2318 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2323 if (!rv->region()->locked()) {
2324 rv->region()->clear_changes ();
2325 rv->region()->trim_front (new_bound);
2326 _session->add_command(new StatefulDiffCommand (rv->region()));
2330 commit_reversible_command();
2334 begin_reversible_command (_("end point trim"));
2336 if (selection->selected (rv)) {
2338 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2340 if (!(*i)->region()->locked()) {
2341 (*i)->region()->clear_changes();
2342 (*i)->region()->trim_end (new_bound);
2343 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2349 if (!rv->region()->locked()) {
2350 rv->region()->clear_changes ();
2351 rv->region()->trim_end (new_bound);
2352 _session->add_command (new StatefulDiffCommand (rv->region()));
2356 commit_reversible_command();
2365 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2367 ArdourMarker* marker;
2370 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2371 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2372 abort(); /*NOTREACHED*/
2375 Location* location = find_location_from_marker (marker, is_start);
2376 location->set_hidden (true, this);
2380 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2382 using namespace Gtkmm2ext;
2384 ArdourWidgets::Prompter prompter (false);
2386 prompter.set_prompt (_("Name for region:"));
2387 prompter.set_initial_text (clicked_regionview->region()->name());
2388 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2389 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2390 prompter.show_all ();
2391 switch (prompter.run ()) {
2392 case Gtk::RESPONSE_ACCEPT:
2394 prompter.get_result(str);
2396 clicked_regionview->region()->set_name (str);
2405 Editor::mouse_brush_insert_region (RegionView* rv, samplepos_t pos)
2407 /* no brushing without a useful quantize setting */
2408 if (_grid_type == GridTypeNone)
2411 /* don't brush a copy over the original */
2413 if (pos == rv->region()->position()) {
2417 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2419 if (!rtv || !rtv->is_track()) {
2423 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2425 playlist->clear_changes ();
2426 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2427 playlist->add_region (new_region, pos);
2428 _session->add_command (new StatefulDiffCommand (playlist));
2430 /* playlist is frozen, so we have to update manually XXX this is disgusting */
2432 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2436 Editor::track_height_step_timeout ()
2438 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2439 current_stepping_trackview = 0;
2446 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2448 assert (region_view);
2450 if (!region_view->region()->playlist()) {
2454 switch (Config->get_edit_mode()) {
2456 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2459 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2462 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2469 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2471 assert (region_view);
2473 if (!region_view->region()->playlist()) {
2477 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2481 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2483 assert (region_view);
2485 if (!region_view->region()->playlist()) {
2489 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2493 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2496 /** Start a grab where a time range is selected, track(s) are selected, and the
2497 * user clicks and drags a region with a modifier in order to create a new region containing
2498 * the section of the clicked region that lies within the time range.
2501 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2503 if (clicked_regionview == 0) {
2507 /* lets try to create new Region for the selection */
2509 vector<boost::shared_ptr<Region> > new_regions;
2510 create_region_from_selection (new_regions);
2512 if (new_regions.empty()) {
2516 /* XXX fix me one day to use all new regions */
2518 boost::shared_ptr<Region> region (new_regions.front());
2520 /* add it to the current stream/playlist.
2522 tricky: the streamview for the track will add a new regionview. we will
2523 catch the signal it sends when it creates the regionview to
2524 set the regionview we want to then drag.
2527 latest_regionviews.clear();
2528 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2530 /* A selection grab currently creates two undo/redo operations, one for
2531 creating the new region and another for moving it.
2533 begin_reversible_command (Operations::selection_grab);
2535 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2537 playlist->clear_changes ();
2538 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2539 _session->add_command(new StatefulDiffCommand (playlist));
2543 if (latest_regionviews.empty()) {
2544 /* something went wrong */
2545 abort_reversible_command ();
2549 /* we need to deselect all other regionviews, and select this one
2550 i'm ignoring undo stuff, because the region creation will take care of it
2553 selection->set (latest_regionviews);
2555 commit_reversible_command ();
2557 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2563 if (_drags->active ()) {
2566 selection->clear ();
2568 /* if session is playing a range, cancel that */
2569 if (_session->get_play_range()) {
2570 _session->request_cancel_play_range();
2573 if (_session->solo_selection_active()) {
2575 _session->solo_selection (sl, false);
2579 ARDOUR_UI::instance()->reset_focus (&contents());
2582 /** Update _join_object_range_state which indicate whether we are over the top
2583 * or bottom half of a route view, used by the `join object/range' tool
2584 * mode. Coordinates in canvas space.
2587 Editor::update_join_object_range_location (double y)
2589 if (!get_smart_mode()) {
2590 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2594 JoinObjectRangeState const old = _join_object_range_state;
2596 if (mouse_mode == MouseObject) {
2597 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2598 } else if (mouse_mode == MouseRange) {
2599 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2602 if (entered_regionview) {
2604 /* TODO: there is currently a bug here(?)
2605 * 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
2606 * can it be fixed here?
2609 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2610 double const c = item_space.y / entered_regionview->height();
2612 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2614 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2615 if (_join_object_range_state != old && ctx) {
2616 ctx->cursor_ctx->change(which_track_cursor());
2619 } else if (entered_track) {
2621 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2623 if (entered_route_view) {
2628 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2630 double track_height = entered_route_view->view()->child_height();
2631 if (UIConfiguration::instance().get_show_name_highlight()) {
2632 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2634 double const c = cy / track_height;
2638 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2640 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2644 /* Other kinds of tracks use object mode */
2645 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2648 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2649 if (_join_object_range_state != old && ctx) {
2650 ctx->cursor_ctx->change(which_track_cursor());
2656 Editor::effective_mouse_mode () const
2658 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2660 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2668 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2670 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2673 e->region_view().delete_note (e->note ());
2676 /** Obtain the pointer position in canvas coordinates */
2678 Editor::get_pointer_position (double& x, double& y) const
2681 _track_canvas->get_pointer (px, py);
2682 _track_canvas->window_to_canvas (px, py, x, y);