2 Copyright (C) 2000-2001 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 #include "pbd/error.h"
30 #include "pbd/enumwriter.h"
31 #include "pbd/memento_command.h"
32 #include "pbd/basename.h"
33 #include "pbd/stateful_diff_command.h"
35 #include "gtkmm2ext/bindings.h"
36 #include "gtkmm2ext/utils.h"
38 #include "canvas/canvas.h"
40 #include "ardour/audioregion.h"
41 #include "ardour/operations.h"
42 #include "ardour/playlist.h"
43 #include "ardour/profile.h"
44 #include "ardour/region_factory.h"
45 #include "ardour/route.h"
46 #include "ardour/session.h"
47 #include "ardour/types.h"
50 #include "ardour_ui.h"
52 #include "time_axis_view.h"
53 #include "audio_time_axis.h"
54 #include "audio_region_view.h"
55 #include "midi_region_view.h"
57 #include "streamview.h"
58 #include "region_gain_line.h"
59 #include "automation_time_axis.h"
60 #include "control_point.h"
62 #include "selection.h"
65 #include "rgb_macros.h"
66 #include "control_point_dialog.h"
67 #include "editor_drag.h"
68 #include "automation_region_view.h"
69 #include "edit_note_dialog.h"
70 #include "mouse_cursors.h"
71 #include "editor_cursors.h"
72 #include "verbose_cursor.h"
78 using namespace ARDOUR;
81 using namespace Editing;
82 using Gtkmm2ext::Keyboard;
85 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
87 /* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only
88 pays attentions to subwindows. this means that menu windows are ignored, and
89 if the pointer is in a menu, the return window from the call will be the
90 the regular subwindow *under* the menu.
92 this matters quite a lot if the pointer is moving around in a menu that overlaps
93 the track canvas because we will believe that we are within the track canvas
94 when we are not. therefore, we track enter/leave events for the track canvas
95 and allow that to override the result of gdk_window_get_pointer().
98 if (!within_track_canvas) {
103 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->_track_canvas->get_window();
105 if (!canvas_window) {
109 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
111 if (!pointer_window) {
115 if (pointer_window != canvas_window) {
116 in_track_canvas = false;
120 in_track_canvas = true;
123 event.type = GDK_BUTTON_RELEASE;
127 where = window_event_sample (&event, 0, 0);
133 Editor::window_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
135 ArdourCanvas::Duple d;
137 if (!gdk_event_get_coords (event, &d.x, &d.y)) {
141 /* event coordinates are in window units, so convert to canvas
144 d = _track_canvas->window_to_canvas (d);
154 return pixel_to_sample (d.x);
158 Editor::canvas_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
163 /* event coordinates are already in canvas units */
165 if (!gdk_event_get_coords (event, &x, &y)) {
166 cerr << "!NO c COORDS for event type " << event->type << endl;
178 /* note that pixel_to_sample_from_event() never returns less than zero, so even if the pixel
179 position is negative (as can be the case with motion events in particular),
180 the frame location is always positive.
183 return pixel_to_sample_from_event (x);
187 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
189 boost::shared_ptr<Trimmable> st = _trimmable.lock();
191 if (!st || st == t) {
197 Editor::set_current_movable (boost::shared_ptr<Movable> m)
199 boost::shared_ptr<Movable> sm = _movable.lock();
201 if (!sm || sm != m) {
207 Editor::mouse_mode_object_range_toggled()
209 MouseMode m = mouse_mode;
211 Glib::RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
213 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
216 set_mouse_mode(m, true); //call this so the button styles can get updated
219 static Glib::RefPtr<Action>
220 get_mouse_mode_action(MouseMode m)
224 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
226 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
228 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
230 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
232 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
234 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-content"));
236 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
238 return Glib::RefPtr<Action>();
242 Editor::set_mouse_mode (MouseMode m, bool force)
244 if (_drags->active ()) {
248 if (!force && m == mouse_mode) {
252 if (ARDOUR::Profile->get_mixbus()) {
253 if ( m == MouseCut) m = MouseObject;
256 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
257 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
259 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
260 tact->set_active (false);
261 tact->set_active (true);
263 //NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting
267 Editor::mouse_mode_toggled (MouseMode m)
269 if (ARDOUR::Profile->get_mixbus()) {
270 if ( m == MouseCut) m = MouseObject;
273 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
274 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
276 if (!tact->get_active()) {
277 /* this was just the notification that the old mode has been
278 * left. we'll get called again with the new mode active in a
284 if (_session && mouse_mode == MouseAudition) {
285 /* stop transport and reset default speed to avoid oddness with
287 _session->request_transport_speed (0.0, true);
290 const bool was_internal = internal_editing();
294 /* Switch snap type/mode if we're moving to/from an internal tool. Note
295 this must toggle the actions and not call set_snap_*() directly,
296 otherwise things get out of sync and the combo box stops working. */
297 if (!was_internal && internal_editing()) {
298 snap_type_action(internal_snap_type)->set_active(true);
299 snap_mode_action(internal_snap_mode)->set_active(true);
300 } else if (was_internal && !internal_editing()) {
301 snap_type_action(pre_internal_snap_type)->set_active(true);
302 snap_mode_action(pre_internal_snap_mode)->set_active(true);
307 /* this should generate a new enter event which will
308 trigger the appropiate cursor.
312 _track_canvas->re_enter ();
315 set_gain_envelope_visibility ();
317 update_time_selection_display ();
319 update_all_enter_cursors ();
321 MouseModeChanged (); /* EMIT SIGNAL */
325 Editor::internal_editing() const
327 return mouse_mode == Editing::MouseContent || mouse_mode == Editing::MouseDraw;
331 Editor::update_time_selection_display ()
333 switch (mouse_mode) {
335 selection->clear_objects ();
336 selection->clear_midi_notes ();
339 selection->clear_time ();
340 selection->clear_tracks ();
341 selection->clear_midi_notes ();
344 /* Clear regions, but not time or tracks, since that
345 would destroy the range selection rectangle, which we need to stick
346 around for AutomationRangeDrag. */
347 selection->clear_regions ();
348 selection->clear_playlists ();
351 /* This handles internal edit.
352 Clear everything except points and notes.
354 selection->clear_regions();
355 selection->clear_lines();
356 selection->clear_playlists ();
358 selection->clear_time ();
359 selection->clear_tracks ();
363 /* We probably want to keep region selection */
364 selection->clear_points ();
365 selection->clear_lines();
366 selection->clear_playlists ();
368 selection->clear_time ();
369 selection->clear_tracks ();
373 /*Don't lose lines or points if no action in this mode */
374 selection->clear_regions ();
375 selection->clear_playlists ();
376 selection->clear_time ();
377 selection->clear_tracks ();
381 /*Clear everything */
382 selection->clear_objects();
383 selection->clear_time ();
384 selection->clear_tracks ();
390 Editor::step_mouse_mode (bool next)
392 const int n_mouse_modes = (int)MouseContent + 1;
393 int current = (int)current_mouse_mode();
395 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
397 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
402 Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
405 /* in object/audition/timefx/gain-automation mode,
406 any button press sets the selection if the object
407 can be selected. this is a bit of hack, because
408 we want to avoid this if the mouse operation is a
411 note: not dbl-click or triple-click
413 Also note that there is no region selection in internal edit mode, otherwise
414 for operations operating on the selection (e.g. cut) it is not obvious whether
415 to cut notes or regions.
418 MouseMode eff_mouse_mode = effective_mouse_mode ();
420 if (eff_mouse_mode == MouseCut) {
421 /* never change selection in cut mode */
425 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
426 /* context clicks are always about object properties, even if
427 we're in range mode within smart mode.
429 eff_mouse_mode = MouseObject;
432 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
433 if (get_smart_mode()) {
435 case FadeInHandleItem:
436 case FadeInTrimHandleItem:
437 case FadeOutHandleItem:
438 case FadeOutTrimHandleItem:
439 eff_mouse_mode = MouseObject;
446 if (((mouse_mode != MouseObject) &&
447 (mouse_mode != MouseAudition || item_type != RegionItem) &&
448 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
449 (mouse_mode != MouseDraw) &&
450 (mouse_mode != MouseContent || item_type == RegionItem)) ||
451 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
455 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
457 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
459 /* almost no selection action on modified button-2 or button-3 events */
461 if ((item_type != RegionItem && event->button.button != 2)
462 /* for selection of control points prior to delete (shift-right click) */
463 && !(item_type == ControlPointItem && event->button.button == 3 && event->type == GDK_BUTTON_PRESS)) {
469 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
470 bool press = (event->type == GDK_BUTTON_PRESS);
473 _mouse_changed_selection = false;
478 if (eff_mouse_mode == MouseDraw) {
482 if (eff_mouse_mode != MouseRange) {
483 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
485 /* don't change the selection unless the
486 clicked track is not currently selected. if
487 so, "collapse" the selection to just this
490 if (!selection->selected (clicked_axisview)) {
491 set_selected_track_as_side_effect (Selection::Set);
495 if (eff_mouse_mode != MouseRange) {
496 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
501 case RegionViewNameHighlight:
503 case LeftFrameHandle:
504 case RightFrameHandle:
505 case FadeInHandleItem:
506 case FadeInTrimHandleItem:
508 case FadeOutHandleItem:
509 case FadeOutTrimHandleItem:
511 case StartCrossFadeItem:
512 case EndCrossFadeItem:
513 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
514 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
515 } else if (event->type == GDK_BUTTON_PRESS) {
516 set_selected_track_as_side_effect (op);
520 case ControlPointItem:
521 /* for object/track exclusivity, we don't call set_selected_track_as_side_effect (op); */
523 if (eff_mouse_mode != MouseRange) {
524 if (event->button.button != 3) {
525 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
527 _mouse_changed_selection |= set_selected_control_point_from_click (press, Selection::Set);
533 if (eff_mouse_mode != MouseRange) {
534 AutomationLine* argl = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
536 std::list<Selectable*> selectables;
537 uint32_t before, after;
538 framecnt_t const where = (framecnt_t) floor (event->button.x * samples_per_pixel) - clicked_regionview->region ()->position ();
540 if (!argl || !argl->control_points_adjacent (where, before, after)) {
544 selectables.push_back (argl->nth (before));
545 selectables.push_back (argl->nth (after));
550 selection->set (selectables);
551 _mouse_changed_selection = true;
556 selection->add (selectables);
557 _mouse_changed_selection = true;
560 case Selection::Toggle:
562 selection->toggle (selectables);
563 _mouse_changed_selection = true;
567 case Selection::Extend:
574 case AutomationLineItem:
575 if (eff_mouse_mode != MouseRange) {
576 AutomationLine* al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
577 std::list<Selectable*> selectables;
578 double mx = event->button.x;
579 double my = event->button.y;
581 al->grab_item().canvas_to_item (mx, my);
583 uint32_t before, after;
584 framecnt_t const where = (framecnt_t) floor (mx * samples_per_pixel);
586 if (!al || !al->control_points_adjacent (where, before, after)) {
590 selectables.push_back (al->nth (before));
591 selectables.push_back (al->nth (after));
596 selection->set (selectables);
597 _mouse_changed_selection = true;
602 selection->add (selectables);
603 _mouse_changed_selection = true;
606 case Selection::Toggle:
608 selection->toggle (selectables);
609 _mouse_changed_selection = true;
613 case Selection::Extend:
621 /* for context click, select track */
622 if (event->button.button == 3) {
623 selection->clear_tracks ();
624 set_selected_track_as_side_effect (op);
626 /* We won't get a release.*/
627 begin_reversible_selection_op (X_("Button 3 Menu Select"));
628 commit_reversible_selection_op ();
632 case AutomationTrackItem:
633 if (eff_mouse_mode != MouseDraw && op == Selection::Set) {
634 set_selected_track_as_side_effect (op);
639 if (press && event->button.button == 3) {
640 NoteBase* cnote = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
642 if (cnote->region_view().selection_size() == 0 || !cnote->selected()) {
643 selection->clear_points();
644 cnote->region_view().unique_select (cnote);
645 /* we won't get the release, so store the selection change now */
646 begin_reversible_selection_op (X_("Button 3 Note Selection"));
647 commit_reversible_selection_op ();
656 if ((!press) && _mouse_changed_selection) {
657 begin_reversible_selection_op (X_("Button Selection"));
658 commit_reversible_selection_op ();
659 _mouse_changed_selection = false;
664 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
666 /* single mouse clicks on any of these item types operate
667 independent of mouse mode, mostly because they are
668 not on the main track canvas or because we want
672 NoteBase* note = NULL;
675 case PlayheadCursorItem:
676 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
680 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
681 hide_marker (item, event);
683 _drags->set (new MarkerDrag (this, item), event);
687 case TempoMarkerItem:
690 new TempoMarkerDrag (
693 ArdourKeyboard::indicates_copy (event->button.state)
700 case MeterMarkerItem:
703 new MeterMarkerDrag (
706 ArdourKeyboard::indicates_copy (event->button.state)
714 _drags->set (new VideoTimeLineDrag (this, item), event);
722 case TimecodeRulerItem:
723 case SamplesRulerItem:
724 case MinsecRulerItem:
726 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)
727 && !ArdourKeyboard::indicates_constraint (event->button.state)) {
728 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
729 } else if (ArdourKeyboard::indicates_constraint (event->button.state) && ArdourKeyboard::indicates_copy (event->button.state)) {
730 _drags->set (new TempoTwistDrag (this, item), event);
731 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
732 _drags->set (new BBTRulerDrag (this, item), event);
733 } else if (ArdourKeyboard::indicates_copy (event->button.state)) {
734 _drags->set (new TempoEndDrag ( this, item), event);
740 case RangeMarkerBarItem:
741 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
742 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
743 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
744 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
746 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
751 case CdMarkerBarItem:
752 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
753 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
755 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
760 case TransportMarkerBarItem:
761 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
762 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
764 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
773 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
774 /* special case: allow trim of range selections in joined object mode;
775 in theory eff should equal MouseRange in this case, but it doesn't
776 because entering the range selection canvas item results in entered_regionview
777 being set to 0, so update_join_object_range_location acts as if we aren't
780 if (item_type == StartSelectionTrimItem) {
781 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
782 } else if (item_type == EndSelectionTrimItem) {
783 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
787 Editing::MouseMode eff = effective_mouse_mode ();
789 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
790 if (get_smart_mode()) {
792 case FadeInHandleItem:
793 case FadeInTrimHandleItem:
794 case FadeOutHandleItem:
795 case FadeOutTrimHandleItem:
806 case StartSelectionTrimItem:
807 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
810 case EndSelectionTrimItem:
811 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
815 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
816 start_selection_grab (item, event);
818 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
819 /* grab selection for moving */
820 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
822 /* this was debated, but decided the more common action was to
823 make a new selection */
824 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
829 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
830 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
832 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
837 case RegionViewNameHighlight:
838 if (!clicked_regionview->region()->locked()) {
839 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
845 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
846 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
848 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
857 case FadeInHandleItem:
858 case FadeOutHandleItem:
859 case LeftFrameHandle:
860 case RightFrameHandle:
861 case FeatureLineItem:
862 case RegionViewNameHighlight:
865 case AutomationTrackItem:
866 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
877 /* Existing note: allow trimming/motion */
878 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
879 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
880 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
882 _drags->set (new NoteDrag (this, item), event);
888 _drags->set (new LineDrag (this, item), event);
892 case ControlPointItem:
893 _drags->set (new ControlPointDrag (this, item), event);
897 case AutomationLineItem:
898 _drags->set (new LineDrag (this, item), event);
903 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
906 case AutomationTrackItem:
907 /* rubberband drag to select automation points */
908 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
913 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
914 /* rubberband drag to select automation points */
915 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
926 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
927 event->type == GDK_BUTTON_PRESS) {
929 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
931 } else if (event->type == GDK_BUTTON_PRESS) {
934 case FadeInHandleItem:
936 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
940 case FadeOutHandleItem:
942 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
946 case StartCrossFadeItem:
947 case EndCrossFadeItem:
948 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
949 // if (!clicked_regionview->region()->locked()) {
950 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
955 case FeatureLineItem:
957 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
958 remove_transient(item);
962 _drags->set (new FeatureLineDrag (this, item), event);
968 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
969 /* click on an automation region view; do nothing here and let the ARV's signal handler
975 /* click on a normal region view */
976 if (ArdourKeyboard::indicates_copy (event->button.state)) {
977 add_region_copy_drag (item, event, clicked_regionview);
978 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
979 add_region_brush_drag (item, event, clicked_regionview);
981 add_region_drag (item, event, clicked_regionview);
985 _drags->start_grab (event);
989 case RegionViewNameHighlight:
990 case LeftFrameHandle:
991 case RightFrameHandle:
992 if (!clicked_regionview->region()->locked()) {
993 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
998 case FadeInTrimHandleItem:
999 case FadeOutTrimHandleItem:
1000 if (!clicked_regionview->region()->locked()) {
1001 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
1006 case RegionViewName:
1008 /* rename happens on edit clicks */
1009 if (clicked_regionview->get_name_highlight()) {
1010 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1016 case ControlPointItem:
1017 _drags->set (new ControlPointDrag (this, item), event);
1021 case AutomationLineItem:
1022 _drags->set (new LineDrag (this, item), event);
1027 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1030 case AutomationTrackItem:
1032 TimeAxisView* parent = clicked_axisview->get_parent ();
1033 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1035 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1037 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1039 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1040 if (pl->n_regions() == 0) {
1041 /* Parent has no regions; create one so that we have somewhere to put automation */
1042 _drags->set (new RegionCreateDrag (this, item, parent), event);
1044 /* See if there's a region before the click that we can extend, and extend it if so */
1045 framepos_t const t = canvas_event_sample (event);
1046 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1048 _drags->set (new RegionCreateDrag (this, item, parent), event);
1050 prev->set_length (t - prev->position (), get_grid_music_divisions (event->button.state));
1054 /* rubberband drag to select automation points */
1055 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1077 switch (item_type) {
1079 _drags->set (new LineDrag (this, item), event);
1082 case ControlPointItem:
1083 _drags->set (new ControlPointDrag (this, item), event);
1089 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
1090 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1091 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
1092 event, _cursors->up_down);
1094 double const y = event->button.y;
1095 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
1097 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1099 /* smart "join" mode: drag automation */
1100 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1108 case AutomationLineItem:
1109 _drags->set (new LineDrag (this, item), event);
1113 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1114 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1115 /* Note is big and pointer is near the end, trim */
1116 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1119 _drags->set (new NoteDrag (this, item), event);
1126 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1127 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1138 if (item_type == NoteItem) {
1139 /* resize-drag notes */
1140 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1141 if (note->big_enough_to_trim()) {
1142 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1146 } else if (clicked_regionview) {
1148 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1154 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1155 scrub_reversals = 0;
1156 scrub_reverse_distance = 0;
1157 last_scrub_x = event->button.x;
1158 scrubbing_direction = 0;
1170 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1172 Editing::MouseMode const eff = effective_mouse_mode ();
1175 switch (item_type) {
1177 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1178 add_region_copy_drag (item, event, clicked_regionview);
1180 add_region_drag (item, event, clicked_regionview);
1182 _drags->start_grab (event);
1185 case ControlPointItem:
1186 _drags->set (new ControlPointDrag (this, item), event);
1194 switch (item_type) {
1195 case RegionViewNameHighlight:
1196 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1200 case LeftFrameHandle:
1201 case RightFrameHandle:
1202 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1206 case RegionViewName:
1207 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1221 /* relax till release */
1233 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1235 if (event->type == GDK_2BUTTON_PRESS) {
1236 _drags->mark_double_click ();
1237 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1241 if (event->type != GDK_BUTTON_PRESS) {
1245 _track_canvas->grab_focus();
1247 if (_session && _session->actively_recording()) {
1251 button_selection (item, event, item_type);
1253 if (!_drags->active () &&
1254 (Keyboard::is_delete_event (&event->button) ||
1255 Keyboard::is_context_menu_event (&event->button) ||
1256 Keyboard::is_edit_event (&event->button))) {
1258 /* handled by button release */
1262 /* not rolling, effectively in range mode, follow edits enabled (likely
1263 * to start range drag), not in a fade handle (since that means we are
1264 * not starting a range drag): locate the PH here
1267 if ((item_type != FadeInHandleItem) &&
1268 (item_type != FadeOutHandleItem) &&
1269 !_drags->active () &&
1271 !_session->transport_rolling() &&
1272 (effective_mouse_mode() == MouseRange) &&
1273 UIConfiguration::instance().get_follow_edits() &&
1274 !_session->config.get_external_sync()) {
1276 MusicFrame where (canvas_event_sample (event), 0);
1278 _session->request_locate (where.frame, false);
1281 switch (event->button.button) {
1283 return button_press_handler_1 (item, event, item_type);
1287 return button_press_handler_2 (item, event, item_type);
1294 return button_press_dispatch (&event->button);
1303 Editor::button_press_dispatch (GdkEventButton* ev)
1305 /* this function is intended only for buttons 4 and above.
1308 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1309 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1313 Editor::button_release_dispatch (GdkEventButton* ev)
1315 /* this function is intended only for buttons 4 and above.
1318 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1319 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1323 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1325 MusicFrame where (canvas_event_sample (event), 0);
1326 AutomationTimeAxisView* atv = 0;
1328 _press_cursor_ctx.reset();
1330 /* no action if we're recording */
1332 if (_session && _session->actively_recording()) {
1336 bool were_dragging = false;
1338 if (!Keyboard::is_context_menu_event (&event->button)) {
1340 /* see if we're finishing a drag */
1342 if (_drags->active ()) {
1343 bool const r = _drags->end_grab (event);
1345 /* grab dragged, so do nothing else */
1349 were_dragging = true;
1352 update_region_layering_order_editor ();
1355 /* edit events get handled here */
1357 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1358 switch (item_type) {
1360 show_region_properties ();
1362 case TempoMarkerItem: {
1363 ArdourMarker* marker;
1364 TempoMarker* tempo_marker;
1366 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1367 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1368 abort(); /*NOTREACHED*/
1371 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1372 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1373 abort(); /*NOTREACHED*/
1376 edit_tempo_marker (*tempo_marker);
1380 case MeterMarkerItem: {
1381 ArdourMarker* marker;
1382 MeterMarker* meter_marker;
1384 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1385 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1386 abort(); /*NOTREACHED*/
1389 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1390 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1391 abort(); /*NOTREACHED*/
1393 edit_meter_marker (*meter_marker);
1397 case RegionViewName:
1398 if (clicked_regionview->name_active()) {
1399 return mouse_rename_region (item, event);
1403 case ControlPointItem:
1404 edit_control_point (item);
1413 /* context menu events get handled here */
1414 if (Keyboard::is_context_menu_event (&event->button)) {
1416 context_click_event = *event;
1418 if (!_drags->active ()) {
1420 /* no matter which button pops up the context menu, tell the menu
1421 widget to use button 1 to drive menu selection.
1424 switch (item_type) {
1426 case FadeInHandleItem:
1427 case FadeInTrimHandleItem:
1428 case StartCrossFadeItem:
1429 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1433 case FadeOutHandleItem:
1434 case FadeOutTrimHandleItem:
1435 case EndCrossFadeItem:
1436 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1439 case LeftFrameHandle:
1440 case RightFrameHandle:
1444 popup_track_context_menu (1, event->button.time, item_type, false);
1448 case RegionViewNameHighlight:
1449 case RegionViewName:
1450 popup_track_context_menu (1, event->button.time, item_type, false);
1454 popup_track_context_menu (1, event->button.time, item_type, true);
1457 case AutomationTrackItem:
1458 popup_track_context_menu (1, event->button.time, item_type, false);
1462 case RangeMarkerBarItem:
1463 case TransportMarkerBarItem:
1464 case CdMarkerBarItem:
1466 case TempoCurveItem:
1469 case TimecodeRulerItem:
1470 case SamplesRulerItem:
1471 case MinsecRulerItem:
1473 popup_ruler_menu (where.frame, item_type);
1477 marker_context_menu (&event->button, item);
1480 case TempoMarkerItem:
1481 tempo_or_meter_marker_context_menu (&event->button, item);
1484 case MeterMarkerItem:
1485 tempo_or_meter_marker_context_menu (&event->button, item);
1488 case CrossfadeViewItem:
1489 popup_track_context_menu (1, event->button.time, item_type, false);
1492 case ControlPointItem:
1493 popup_control_point_context_menu (item, event);
1497 if (internal_editing()) {
1498 popup_note_context_menu (item, event);
1510 /* delete events get handled here */
1512 Editing::MouseMode const eff = effective_mouse_mode ();
1514 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1516 switch (item_type) {
1517 case TempoMarkerItem:
1518 remove_tempo_marker (item);
1521 case MeterMarkerItem:
1522 remove_meter_marker (item);
1526 remove_marker (*item, event);
1530 if (eff == MouseObject) {
1531 remove_clicked_region ();
1535 case ControlPointItem:
1536 remove_control_point (item);
1540 remove_midi_note (item, event);
1549 switch (event->button.button) {
1552 switch (item_type) {
1553 /* see comments in button_press_handler */
1554 case PlayheadCursorItem:
1557 case AutomationLineItem:
1558 case StartSelectionTrimItem:
1559 case EndSelectionTrimItem:
1563 if (!_dragging_playhead) {
1564 snap_to_with_modifier (where, event, RoundNearest, true);
1565 mouse_add_new_marker (where.frame);
1569 case CdMarkerBarItem:
1570 if (!_dragging_playhead) {
1571 // if we get here then a dragged range wasn't done
1572 snap_to_with_modifier (where, event, RoundNearest, true);
1573 mouse_add_new_marker (where.frame, true);
1577 case TempoCurveItem:
1578 if (!_dragging_playhead) {
1579 snap_to_with_modifier (where, event);
1580 mouse_add_new_tempo_event (where.frame);
1585 if (!_dragging_playhead) {
1586 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1591 case TimecodeRulerItem:
1592 case SamplesRulerItem:
1593 case MinsecRulerItem:
1604 switch (item_type) {
1607 /* check that we didn't drag before releasing, since
1608 its really annoying to create new control
1609 points when doing this.
1611 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1612 if (!were_dragging && arv) {
1613 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1614 arv->add_gain_point_event (item, event, with_guard_points);
1620 case AutomationTrackItem: {
1621 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1622 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1624 atv->add_automation_event (event, where.frame, event->button.y, with_guard_points);
1635 if (scrubbing_direction == 0) {
1636 /* no drag, just a click */
1637 switch (item_type) {
1639 play_selected_region ();
1644 } else if (_session) {
1645 /* make sure we stop */
1646 _session->request_transport_speed (0.0);
1655 /* do any (de)selection operations that should occur on button release */
1656 button_selection (item, event, item_type);
1666 switch (item_type) {
1668 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1670 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1673 // Button2 click is unused
1688 // x_style_paste (where, 1.0);
1709 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1712 ArdourMarker * marker;
1713 MeterMarker* m_marker = 0;
1714 TempoMarker* t_marker = 0;
1718 /* by the time we reach here, entered_regionview and entered trackview
1719 * will have already been set as appropriate. Things are done this
1720 * way because this method isn't passed a pointer to a variable type of
1721 * thing that is entered (which may or may not be canvas item).
1722 * (e.g. the actual entered regionview)
1725 choose_canvas_cursor_on_entry (item_type);
1727 switch (item_type) {
1728 case ControlPointItem:
1729 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1730 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1733 fraction = 1.0 - (cp->get_y() / cp->line().height());
1735 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1736 _verbose_cursor->show ();
1741 if (mouse_mode == MouseDraw) {
1742 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1744 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1749 case AutomationLineItem:
1750 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1751 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1753 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1758 case AutomationTrackItem:
1759 AutomationTimeAxisView* atv;
1760 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1761 clear_entered_track = false;
1762 set_entered_track (atv);
1767 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1770 entered_marker = marker;
1771 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1774 case MeterMarkerItem:
1775 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1778 entered_marker = m_marker;
1779 if (m_marker->meter().position_lock_style() == MusicTime) {
1780 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1782 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1786 case TempoMarkerItem:
1787 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1790 entered_marker = t_marker;
1791 if (t_marker->tempo().position_lock_style() == MusicTime) {
1792 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1794 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1798 case FadeInHandleItem:
1799 case FadeInTrimHandleItem:
1800 if (mouse_mode == MouseObject) {
1801 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1803 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1804 rect->set_fill_color (rv->get_fill_color());
1809 case FadeOutHandleItem:
1810 case FadeOutTrimHandleItem:
1811 if (mouse_mode == MouseObject) {
1812 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1814 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1815 rect->set_fill_color (rv->get_fill_color ());
1820 case FeatureLineItem:
1822 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1823 line->set_outline_color (0xFF0000FF);
1832 if (entered_regionview) {
1833 entered_regionview->entered();
1842 /* third pass to handle entered track status in a comprehensible way.
1845 switch (item_type) {
1847 case AutomationLineItem:
1848 case ControlPointItem:
1849 /* these do not affect the current entered track state */
1850 clear_entered_track = false;
1853 case AutomationTrackItem:
1854 /* handled above already */
1866 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1869 ArdourMarker *marker;
1870 TempoMarker *t_marker;
1871 MeterMarker *m_marker;
1876 if (!_enter_stack.empty()) {
1877 _enter_stack.pop_back();
1880 switch (item_type) {
1881 case ControlPointItem:
1882 _verbose_cursor->hide ();
1886 case AutomationLineItem:
1887 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1889 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1891 line->set_outline_color (al->get_line_color());
1897 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1901 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1902 location_flags_changed (loc);
1906 case MeterMarkerItem:
1907 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1911 if (m_marker->meter().position_lock_style() == MusicTime) {
1912 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1914 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1918 case TempoMarkerItem:
1919 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1923 if (t_marker->tempo().position_lock_style() == MusicTime) {
1924 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1926 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1930 case FadeInTrimHandleItem:
1931 case FadeOutTrimHandleItem:
1932 case FadeInHandleItem:
1933 case FadeOutHandleItem:
1935 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1937 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1942 case AutomationTrackItem:
1945 case FeatureLineItem:
1947 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1948 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1960 Editor::scrub (framepos_t frame, double current_x)
1964 if (scrubbing_direction == 0) {
1966 _session->request_locate (frame, false);
1967 _session->request_transport_speed (0.1);
1968 scrubbing_direction = 1;
1972 if (last_scrub_x > current_x) {
1974 /* pointer moved to the left */
1976 if (scrubbing_direction > 0) {
1978 /* we reversed direction to go backwards */
1981 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1985 /* still moving to the left (backwards) */
1987 scrub_reversals = 0;
1988 scrub_reverse_distance = 0;
1990 delta = 0.01 * (last_scrub_x - current_x);
1991 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1995 /* pointer moved to the right */
1997 if (scrubbing_direction < 0) {
1998 /* we reversed direction to go forward */
2001 scrub_reverse_distance += (int) (current_x - last_scrub_x);
2004 /* still moving to the right */
2006 scrub_reversals = 0;
2007 scrub_reverse_distance = 0;
2009 delta = 0.01 * (current_x - last_scrub_x);
2010 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
2014 /* if there have been more than 2 opposite motion moves detected, or one that moves
2015 back more than 10 pixels, reverse direction
2018 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
2020 if (scrubbing_direction > 0) {
2021 /* was forwards, go backwards */
2022 _session->request_transport_speed (-0.1);
2023 scrubbing_direction = -1;
2025 /* was backwards, go forwards */
2026 _session->request_transport_speed (0.1);
2027 scrubbing_direction = 1;
2030 scrub_reverse_distance = 0;
2031 scrub_reversals = 0;
2035 last_scrub_x = current_x;
2039 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2041 _last_motion_y = event->motion.y;
2043 if (event->motion.is_hint) {
2046 /* We call this so that MOTION_NOTIFY events continue to be
2047 delivered to the canvas. We need to do this because we set
2048 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2049 the density of the events, at the expense of a round-trip
2050 to the server. Given that this will mostly occur on cases
2051 where DISPLAY = :0.0, and given the cost of what the motion
2052 event might do, its a good tradeoff.
2055 _track_canvas->get_pointer (x, y);
2058 if (current_stepping_trackview) {
2059 /* don't keep the persistent stepped trackview if the mouse moves */
2060 current_stepping_trackview = 0;
2061 step_timeout.disconnect ();
2064 if (_session && _session->actively_recording()) {
2065 /* Sorry. no dragging stuff around while we record */
2069 update_join_object_range_location (event->motion.y);
2071 if (_drags->active ()) {
2072 return _drags->motion_handler (event, from_autoscroll);
2079 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2081 ControlPoint* control_point;
2083 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2084 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2085 abort(); /*NOTREACHED*/
2088 AutomationLine& line = control_point->line ();
2089 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2090 /* we shouldn't remove the first or last gain point in region gain lines */
2091 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2100 Editor::remove_control_point (ArdourCanvas::Item* item)
2102 if (!can_remove_control_point (item)) {
2106 ControlPoint* control_point;
2108 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2109 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2110 abort(); /*NOTREACHED*/
2113 control_point->line().remove_point (*control_point);
2117 Editor::edit_control_point (ArdourCanvas::Item* item)
2119 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2122 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2123 abort(); /*NOTREACHED*/
2126 ControlPointDialog d (p);
2128 if (d.run () != RESPONSE_ACCEPT) {
2132 p->line().modify_point_y (*p, d.get_y_fraction ());
2136 Editor::edit_notes (MidiRegionView* mrv)
2138 MidiRegionView::Selection const & s = mrv->selection();
2144 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2147 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2151 Editor::note_edit_done (int r, EditNoteDialog* d)
2153 begin_reversible_command (_("edit note(s)"));
2158 commit_reversible_command();
2162 Editor::edit_region (RegionView* rv)
2164 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
2165 temporal_zoom_selection (Both);
2167 rv->show_region_editor ();
2172 Editor::visible_order_range (int* low, int* high) const
2174 *low = TimeAxisView::max_order ();
2177 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2179 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2181 if (rtv && !rtv->hidden()) {
2183 if (*high < rtv->order()) {
2184 *high = rtv->order ();
2187 if (*low > rtv->order()) {
2188 *low = rtv->order ();
2195 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2197 /* Either add to or set the set the region selection, unless
2198 this is an alignment click (control used)
2201 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2202 TimeAxisView* tv = &rv.get_time_axis_view();
2203 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2205 if (rtv && rtv->is_track()) {
2206 speed = rtv->track()->speed();
2209 framepos_t where = get_preferred_edit_position();
2213 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2215 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2217 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2219 align_region (rv.region(), End, (framepos_t) (where * speed));
2223 align_region (rv.region(), Start, (framepos_t) (where * speed));
2230 Editor::collect_new_region_view (RegionView* rv)
2232 latest_regionviews.push_back (rv);
2236 Editor::collect_and_select_new_region_view (RegionView* rv)
2239 latest_regionviews.push_back (rv);
2243 Editor::cancel_selection ()
2245 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2246 (*i)->hide_selection ();
2249 selection->clear ();
2250 clicked_selection = 0;
2254 Editor::cancel_time_selection ()
2256 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2257 (*i)->hide_selection ();
2259 selection->time.clear ();
2260 clicked_selection = 0;
2264 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2266 RegionView* rv = clicked_regionview;
2268 /* Choose action dependant on which button was pressed */
2269 switch (event->button.button) {
2271 begin_reversible_command (_("start point trim"));
2273 if (selection->selected (rv)) {
2274 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2275 i != selection->regions.by_layer().end(); ++i)
2277 if (!(*i)->region()->locked()) {
2278 (*i)->region()->clear_changes ();
2279 (*i)->region()->trim_front (new_bound);
2280 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2285 if (!rv->region()->locked()) {
2286 rv->region()->clear_changes ();
2287 rv->region()->trim_front (new_bound);
2288 _session->add_command(new StatefulDiffCommand (rv->region()));
2292 commit_reversible_command();
2296 begin_reversible_command (_("end point trim"));
2298 if (selection->selected (rv)) {
2300 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2302 if (!(*i)->region()->locked()) {
2303 (*i)->region()->clear_changes();
2304 (*i)->region()->trim_end (new_bound);
2305 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2311 if (!rv->region()->locked()) {
2312 rv->region()->clear_changes ();
2313 rv->region()->trim_end (new_bound);
2314 _session->add_command (new StatefulDiffCommand (rv->region()));
2318 commit_reversible_command();
2327 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2329 ArdourMarker* marker;
2332 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2333 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2334 abort(); /*NOTREACHED*/
2337 Location* location = find_location_from_marker (marker, is_start);
2338 location->set_hidden (true, this);
2342 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2344 using namespace Gtkmm2ext;
2346 ArdourPrompter prompter (false);
2348 prompter.set_prompt (_("Name for region:"));
2349 prompter.set_initial_text (clicked_regionview->region()->name());
2350 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2351 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2352 prompter.show_all ();
2353 switch (prompter.run ()) {
2354 case Gtk::RESPONSE_ACCEPT:
2356 prompter.get_result(str);
2358 clicked_regionview->region()->set_name (str);
2367 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2369 /* no brushing without a useful snap setting */
2371 switch (_snap_mode) {
2373 return; /* can't work because it allows region to be placed anywhere */
2378 switch (_snap_type) {
2386 /* don't brush a copy over the original */
2388 if (pos == rv->region()->position()) {
2392 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2394 if (!rtv || !rtv->is_track()) {
2398 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2399 double speed = rtv->track()->speed();
2401 playlist->clear_changes ();
2402 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2403 playlist->add_region (new_region, (framepos_t) (pos * speed));
2404 _session->add_command (new StatefulDiffCommand (playlist));
2406 // playlist is frozen, so we have to update manually XXX this is disgusting
2408 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2412 Editor::track_height_step_timeout ()
2414 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2415 current_stepping_trackview = 0;
2422 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2424 assert (region_view);
2426 if (!region_view->region()->playlist()) {
2430 switch (Config->get_edit_mode()) {
2432 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2435 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2438 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2445 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2447 assert (region_view);
2449 if (!region_view->region()->playlist()) {
2453 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2457 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2459 assert (region_view);
2461 if (!region_view->region()->playlist()) {
2465 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2469 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2472 /** Start a grab where a time range is selected, track(s) are selected, and the
2473 * user clicks and drags a region with a modifier in order to create a new region containing
2474 * the section of the clicked region that lies within the time range.
2477 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2479 if (clicked_regionview == 0) {
2483 /* lets try to create new Region for the selection */
2485 vector<boost::shared_ptr<Region> > new_regions;
2486 create_region_from_selection (new_regions);
2488 if (new_regions.empty()) {
2492 /* XXX fix me one day to use all new regions */
2494 boost::shared_ptr<Region> region (new_regions.front());
2496 /* add it to the current stream/playlist.
2498 tricky: the streamview for the track will add a new regionview. we will
2499 catch the signal it sends when it creates the regionview to
2500 set the regionview we want to then drag.
2503 latest_regionviews.clear();
2504 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2506 /* A selection grab currently creates two undo/redo operations, one for
2507 creating the new region and another for moving it.
2509 begin_reversible_command (Operations::selection_grab);
2511 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2513 playlist->clear_changes ();
2514 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2515 _session->add_command(new StatefulDiffCommand (playlist));
2519 if (latest_regionviews.empty()) {
2520 /* something went wrong */
2521 abort_reversible_command ();
2525 /* we need to deselect all other regionviews, and select this one
2526 i'm ignoring undo stuff, because the region creation will take care of it
2529 selection->set (latest_regionviews);
2531 commit_reversible_command ();
2533 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2539 if (_drags->active ()) {
2542 selection->clear ();
2545 ARDOUR_UI::instance()->reset_focus (&contents());
2548 /** Update _join_object_range_state which indicate whether we are over the top
2549 * or bottom half of a route view, used by the `join object/range' tool
2550 * mode. Coordinates in canvas space.
2553 Editor::update_join_object_range_location (double y)
2555 if (!get_smart_mode()) {
2556 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2560 JoinObjectRangeState const old = _join_object_range_state;
2562 if (mouse_mode == MouseObject) {
2563 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2564 } else if (mouse_mode == MouseRange) {
2565 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2568 if (entered_regionview) {
2570 //ToDo: there is currently a bug here(?)
2571 //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
2572 //can it be fixed here?
2574 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2575 double const c = item_space.y / entered_regionview->height();
2577 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2579 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2580 if (_join_object_range_state != old && ctx) {
2581 ctx->cursor_ctx->change(which_track_cursor());
2584 } else if (entered_track) {
2586 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2588 if (entered_route_view) {
2593 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2595 double track_height = entered_route_view->view()->child_height();
2596 if (UIConfiguration::instance().get_show_name_highlight()) {
2597 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2599 double const c = cy / track_height;
2603 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2605 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2609 /* Other kinds of tracks use object mode */
2610 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2613 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2614 if (_join_object_range_state != old && ctx) {
2615 ctx->cursor_ctx->change(which_track_cursor());
2621 Editor::effective_mouse_mode () const
2623 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2625 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2633 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2635 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2638 e->region_view().delete_note (e->note ());
2641 /** Obtain the pointer position in canvas coordinates */
2643 Editor::get_pointer_position (double& x, double& y) const
2646 _track_canvas->get_pointer (px, py);
2647 _track_canvas->window_to_canvas (px, py, x, y);