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"
37 #include "gtkmm2ext/tearoff.h"
39 #include "canvas/canvas.h"
41 #include "ardour/audioregion.h"
42 #include "ardour/operations.h"
43 #include "ardour/playlist.h"
44 #include "ardour/profile.h"
45 #include "ardour/region_factory.h"
46 #include "ardour/route.h"
47 #include "ardour/session.h"
48 #include "ardour/types.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->ClearMidiNoteSelection (); /* EMIT SIGNAL */
339 selection->clear_time ();
340 selection->clear_tracks ();
341 selection->ClearMidiNoteSelection (); /* EMIT SIGNAL */
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 uint32_t before, after;
579 framecnt_t const where = (framecnt_t) floor (event->button.x * samples_per_pixel);
581 if (!al || !al->control_points_adjacent (where, before, after)) {
585 selectables.push_back (al->nth (before));
586 selectables.push_back (al->nth (after));
591 selection->set (selectables);
592 _mouse_changed_selection = true;
597 selection->add (selectables);
598 _mouse_changed_selection = true;
601 case Selection::Toggle:
603 selection->toggle (selectables);
604 _mouse_changed_selection = true;
608 case Selection::Extend:
616 /* for context click, select track */
617 if (event->button.button == 3) {
618 selection->clear_tracks ();
619 set_selected_track_as_side_effect (op);
621 /* We won't get a release.*/
622 begin_reversible_selection_op (X_("Button 3 Menu Select"));
623 commit_reversible_selection_op ();
627 case AutomationTrackItem:
628 if (eff_mouse_mode != MouseDraw && op == Selection::Set) {
629 set_selected_track_as_side_effect (op);
634 if (press && event->button.button == 3) {
635 NoteBase* cnote = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
637 if (cnote->region_view().selection_size() == 0 || !cnote->selected()) {
638 selection->clear_points();
639 cnote->region_view().unique_select (cnote);
640 /* we won't get the release, so store the selection change now */
641 begin_reversible_selection_op (X_("Button 3 Note Selection"));
642 commit_reversible_selection_op ();
651 if ((!press) && _mouse_changed_selection) {
652 begin_reversible_selection_op (X_("Button Selection"));
653 commit_reversible_selection_op ();
654 _mouse_changed_selection = false;
659 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
661 /* single mouse clicks on any of these item types operate
662 independent of mouse mode, mostly because they are
663 not on the main track canvas or because we want
667 NoteBase* note = NULL;
670 case PlayheadCursorItem:
671 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
675 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
676 hide_marker (item, event);
678 _drags->set (new MarkerDrag (this, item), event);
682 case TempoMarkerItem:
685 new TempoMarkerDrag (
688 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
695 case MeterMarkerItem:
698 new MeterMarkerDrag (
701 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
709 _drags->set (new VideoTimeLineDrag (this, item), event);
716 case TimecodeRulerItem:
717 case SamplesRulerItem:
718 case MinsecRulerItem:
720 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
721 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
727 case RangeMarkerBarItem:
728 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
729 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
730 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
731 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
733 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
738 case CdMarkerBarItem:
739 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
740 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
742 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
747 case TransportMarkerBarItem:
748 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
749 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
751 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
760 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
761 /* special case: allow trim of range selections in joined object mode;
762 in theory eff should equal MouseRange in this case, but it doesn't
763 because entering the range selection canvas item results in entered_regionview
764 being set to 0, so update_join_object_range_location acts as if we aren't
767 if (item_type == StartSelectionTrimItem) {
768 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
769 } else if (item_type == EndSelectionTrimItem) {
770 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
774 Editing::MouseMode eff = effective_mouse_mode ();
776 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
777 if (get_smart_mode()) {
779 case FadeInHandleItem:
780 case FadeInTrimHandleItem:
781 case FadeOutHandleItem:
782 case FadeOutTrimHandleItem:
793 case StartSelectionTrimItem:
794 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
797 case EndSelectionTrimItem:
798 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
802 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
803 start_selection_grab (item, event);
805 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
806 /* grab selection for moving */
807 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
809 /* this was debated, but decided the more common action was to
810 make a new selection */
811 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
816 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
817 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
819 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
824 case RegionViewNameHighlight:
825 if (!clicked_regionview->region()->locked()) {
826 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
832 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
833 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
835 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
844 case FadeInHandleItem:
845 case FadeOutHandleItem:
846 case LeftFrameHandle:
847 case RightFrameHandle:
848 case FeatureLineItem:
849 case RegionViewNameHighlight:
852 case AutomationTrackItem:
853 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
864 /* Existing note: allow trimming/motion */
865 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
866 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
867 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
869 _drags->set (new NoteDrag (this, item), event);
875 _drags->set (new LineDrag (this, item), event);
879 case ControlPointItem:
880 _drags->set (new ControlPointDrag (this, item), event);
884 case AutomationLineItem:
885 _drags->set (new LineDrag (this, item), event);
890 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
893 case AutomationTrackItem:
894 /* rubberband drag to select automation points */
895 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
900 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
901 /* rubberband drag to select automation points */
902 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
913 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
914 event->type == GDK_BUTTON_PRESS) {
916 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
918 } else if (event->type == GDK_BUTTON_PRESS) {
921 case FadeInHandleItem:
923 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
927 case FadeOutHandleItem:
929 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
933 case StartCrossFadeItem:
934 case EndCrossFadeItem:
935 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
936 // if (!clicked_regionview->region()->locked()) {
937 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
942 case FeatureLineItem:
944 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
945 remove_transient(item);
949 _drags->set (new FeatureLineDrag (this, item), event);
955 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
956 /* click on an automation region view; do nothing here and let the ARV's signal handler
962 /* click on a normal region view */
963 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
964 add_region_copy_drag (item, event, clicked_regionview);
965 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
966 add_region_brush_drag (item, event, clicked_regionview);
968 add_region_drag (item, event, clicked_regionview);
972 _drags->start_grab (event);
976 case RegionViewNameHighlight:
977 case LeftFrameHandle:
978 case RightFrameHandle:
979 if (!clicked_regionview->region()->locked()) {
980 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
985 case FadeInTrimHandleItem:
986 case FadeOutTrimHandleItem:
987 if (!clicked_regionview->region()->locked()) {
988 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
995 /* rename happens on edit clicks */
996 if (clicked_regionview->get_name_highlight()) {
997 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1003 case ControlPointItem:
1004 _drags->set (new ControlPointDrag (this, item), event);
1008 case AutomationLineItem:
1009 _drags->set (new LineDrag (this, item), event);
1014 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1017 case AutomationTrackItem:
1019 TimeAxisView* parent = clicked_axisview->get_parent ();
1020 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1022 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1024 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1026 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1027 if (pl->n_regions() == 0) {
1028 /* Parent has no regions; create one so that we have somewhere to put automation */
1029 _drags->set (new RegionCreateDrag (this, item, parent), event);
1031 /* See if there's a region before the click that we can extend, and extend it if so */
1032 framepos_t const t = canvas_event_sample (event);
1033 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1035 _drags->set (new RegionCreateDrag (this, item, parent), event);
1037 prev->set_length (t - prev->position ());
1041 /* rubberband drag to select automation points */
1042 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1064 switch (item_type) {
1066 _drags->set (new LineDrag (this, item), event);
1069 case ControlPointItem:
1070 _drags->set (new ControlPointDrag (this, item), event);
1076 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
1077 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1078 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
1079 event, _cursors->up_down);
1081 double const y = event->button.y;
1082 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
1084 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1086 /* smart "join" mode: drag automation */
1087 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1095 case AutomationLineItem:
1096 _drags->set (new LineDrag (this, item), event);
1100 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1101 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1102 /* Note is big and pointer is near the end, trim */
1103 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1106 _drags->set (new NoteDrag (this, item), event);
1113 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1114 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1125 if (item_type == NoteItem) {
1126 /* resize-drag notes */
1127 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1128 if (note->big_enough_to_trim()) {
1129 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1133 } else if (clicked_regionview) {
1135 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1141 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1142 scrub_reversals = 0;
1143 scrub_reverse_distance = 0;
1144 last_scrub_x = event->button.x;
1145 scrubbing_direction = 0;
1157 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1159 Editing::MouseMode const eff = effective_mouse_mode ();
1162 switch (item_type) {
1164 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1165 add_region_copy_drag (item, event, clicked_regionview);
1167 add_region_drag (item, event, clicked_regionview);
1169 _drags->start_grab (event);
1172 case ControlPointItem:
1173 _drags->set (new ControlPointDrag (this, item), event);
1181 switch (item_type) {
1182 case RegionViewNameHighlight:
1183 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1187 case LeftFrameHandle:
1188 case RightFrameHandle:
1189 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1193 case RegionViewName:
1194 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1208 /* relax till release */
1220 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1222 if (event->type == GDK_2BUTTON_PRESS) {
1223 _drags->mark_double_click ();
1224 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1228 if (event->type != GDK_BUTTON_PRESS) {
1232 _track_canvas->grab_focus();
1234 if (_session && _session->actively_recording()) {
1238 button_selection (item, event, item_type);
1240 if (!_drags->active () &&
1241 (Keyboard::is_delete_event (&event->button) ||
1242 Keyboard::is_context_menu_event (&event->button) ||
1243 Keyboard::is_edit_event (&event->button))) {
1245 /* handled by button release */
1249 //not rolling, range mode click + join_play_range : locate the PH here
1250 if ( !_drags->active () && _session && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && UIConfiguration::instance().get_follow_edits() && !_session->config.get_external_sync() ) {
1251 framepos_t where = canvas_event_sample (event);
1253 _session->request_locate (where, false);
1256 switch (event->button.button) {
1258 return button_press_handler_1 (item, event, item_type);
1262 return button_press_handler_2 (item, event, item_type);
1269 return button_press_dispatch (&event->button);
1278 Editor::button_press_dispatch (GdkEventButton* ev)
1280 /* this function is intended only for buttons 4 and above.
1283 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1284 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1288 Editor::button_release_dispatch (GdkEventButton* ev)
1290 /* this function is intended only for buttons 4 and above.
1293 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1294 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1298 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1300 framepos_t where = canvas_event_sample (event);
1301 AutomationTimeAxisView* atv = 0;
1303 _press_cursor_ctx.reset();
1305 /* no action if we're recording */
1307 if (_session && _session->actively_recording()) {
1311 bool were_dragging = false;
1313 if (!Keyboard::is_context_menu_event (&event->button)) {
1315 /* see if we're finishing a drag */
1317 if (_drags->active ()) {
1318 bool const r = _drags->end_grab (event);
1320 /* grab dragged, so do nothing else */
1324 were_dragging = true;
1327 update_region_layering_order_editor ();
1330 /* edit events get handled here */
1332 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1333 switch (item_type) {
1335 show_region_properties ();
1338 case TempoMarkerItem: {
1339 ArdourMarker* marker;
1340 TempoMarker* tempo_marker;
1342 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1343 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1344 abort(); /*NOTREACHED*/
1347 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1348 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1349 abort(); /*NOTREACHED*/
1352 edit_tempo_marker (*tempo_marker);
1356 case MeterMarkerItem: {
1357 ArdourMarker* marker;
1358 MeterMarker* meter_marker;
1360 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1361 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1362 abort(); /*NOTREACHED*/
1365 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1366 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1367 abort(); /*NOTREACHED*/
1369 edit_meter_marker (*meter_marker);
1373 case RegionViewName:
1374 if (clicked_regionview->name_active()) {
1375 return mouse_rename_region (item, event);
1379 case ControlPointItem:
1380 edit_control_point (item);
1389 /* context menu events get handled here */
1390 if (Keyboard::is_context_menu_event (&event->button)) {
1392 context_click_event = *event;
1394 if (!_drags->active ()) {
1396 /* no matter which button pops up the context menu, tell the menu
1397 widget to use button 1 to drive menu selection.
1400 switch (item_type) {
1402 case FadeInHandleItem:
1403 case FadeInTrimHandleItem:
1404 case StartCrossFadeItem:
1405 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1409 case FadeOutHandleItem:
1410 case FadeOutTrimHandleItem:
1411 case EndCrossFadeItem:
1412 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1415 case LeftFrameHandle:
1416 case RightFrameHandle:
1420 popup_track_context_menu (1, event->button.time, item_type, false);
1424 case RegionViewNameHighlight:
1425 case RegionViewName:
1426 popup_track_context_menu (1, event->button.time, item_type, false);
1430 popup_track_context_menu (1, event->button.time, item_type, true);
1433 case AutomationTrackItem:
1434 popup_track_context_menu (1, event->button.time, item_type, false);
1438 case RangeMarkerBarItem:
1439 case TransportMarkerBarItem:
1440 case CdMarkerBarItem:
1444 case TimecodeRulerItem:
1445 case SamplesRulerItem:
1446 case MinsecRulerItem:
1448 popup_ruler_menu (where, item_type);
1452 marker_context_menu (&event->button, item);
1455 case TempoMarkerItem:
1456 tempo_or_meter_marker_context_menu (&event->button, item);
1459 case MeterMarkerItem:
1460 tempo_or_meter_marker_context_menu (&event->button, item);
1463 case CrossfadeViewItem:
1464 popup_track_context_menu (1, event->button.time, item_type, false);
1467 case ControlPointItem:
1468 popup_control_point_context_menu (item, event);
1472 if (internal_editing()) {
1473 popup_note_context_menu (item, event);
1485 /* delete events get handled here */
1487 Editing::MouseMode const eff = effective_mouse_mode ();
1489 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1491 switch (item_type) {
1492 case TempoMarkerItem:
1493 remove_tempo_marker (item);
1496 case MeterMarkerItem:
1497 remove_meter_marker (item);
1501 remove_marker (*item, event);
1505 if (eff == MouseObject) {
1506 remove_clicked_region ();
1510 case ControlPointItem:
1511 remove_control_point (item);
1515 remove_midi_note (item, event);
1524 switch (event->button.button) {
1527 switch (item_type) {
1528 /* see comments in button_press_handler */
1529 case PlayheadCursorItem:
1532 case AutomationLineItem:
1533 case StartSelectionTrimItem:
1534 case EndSelectionTrimItem:
1538 if (!_dragging_playhead) {
1539 snap_to_with_modifier (where, event, RoundNearest, true);
1540 mouse_add_new_marker (where);
1544 case CdMarkerBarItem:
1545 if (!_dragging_playhead) {
1546 // if we get here then a dragged range wasn't done
1547 snap_to_with_modifier (where, event, RoundNearest, true);
1548 mouse_add_new_marker (where, true);
1553 if (!_dragging_playhead) {
1554 snap_to_with_modifier (where, event);
1555 mouse_add_new_tempo_event (where);
1560 if (!_dragging_playhead) {
1561 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1566 case TimecodeRulerItem:
1567 case SamplesRulerItem:
1568 case MinsecRulerItem:
1579 switch (item_type) {
1582 /* check that we didn't drag before releasing, since
1583 its really annoying to create new control
1584 points when doing this.
1586 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1587 if (!were_dragging && arv) {
1588 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1589 arv->add_gain_point_event (item, event, with_guard_points);
1595 case AutomationTrackItem: {
1596 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1597 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1599 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1610 if (scrubbing_direction == 0) {
1611 /* no drag, just a click */
1612 switch (item_type) {
1614 play_selected_region ();
1619 } else if (_session) {
1620 /* make sure we stop */
1621 _session->request_transport_speed (0.0);
1630 /* do any (de)selection operations that should occur on button release */
1631 button_selection (item, event, item_type);
1641 switch (item_type) {
1643 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1645 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1648 // Button2 click is unused
1663 // x_style_paste (where, 1.0);
1684 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1687 ArdourMarker * marker;
1691 /* by the time we reach here, entered_regionview and entered trackview
1692 * will have already been set as appropriate. Things are done this
1693 * way because this method isn't passed a pointer to a variable type of
1694 * thing that is entered (which may or may not be canvas item).
1695 * (e.g. the actual entered regionview)
1698 choose_canvas_cursor_on_entry (item_type);
1700 switch (item_type) {
1701 case ControlPointItem:
1702 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1703 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1706 fraction = 1.0 - (cp->get_y() / cp->line().height());
1708 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1709 _verbose_cursor->show ();
1714 if (mouse_mode == MouseDraw) {
1715 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1717 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1722 case AutomationLineItem:
1723 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1724 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1726 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1731 case AutomationTrackItem:
1732 AutomationTimeAxisView* atv;
1733 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1734 clear_entered_track = false;
1735 set_entered_track (atv);
1740 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1743 entered_marker = marker;
1744 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1746 case MeterMarkerItem:
1747 case TempoMarkerItem:
1750 case FadeInHandleItem:
1751 case FadeInTrimHandleItem:
1752 if (mouse_mode == MouseObject) {
1753 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1755 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1756 rect->set_fill_color (rv->get_fill_color());
1761 case FadeOutHandleItem:
1762 case FadeOutTrimHandleItem:
1763 if (mouse_mode == MouseObject) {
1764 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1766 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1767 rect->set_fill_color (rv->get_fill_color ());
1772 case FeatureLineItem:
1774 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1775 line->set_outline_color (0xFF0000FF);
1784 if (entered_regionview) {
1785 entered_regionview->entered();
1794 /* third pass to handle entered track status in a comprehensible way.
1797 switch (item_type) {
1799 case AutomationLineItem:
1800 case ControlPointItem:
1801 /* these do not affect the current entered track state */
1802 clear_entered_track = false;
1805 case AutomationTrackItem:
1806 /* handled above already */
1818 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1821 ArdourMarker *marker;
1826 if (!_enter_stack.empty()) {
1827 _enter_stack.pop_back();
1830 switch (item_type) {
1831 case ControlPointItem:
1832 _verbose_cursor->hide ();
1836 case AutomationLineItem:
1837 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1839 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1841 line->set_outline_color (al->get_line_color());
1847 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1851 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1852 location_flags_changed (loc);
1855 case MeterMarkerItem:
1856 case TempoMarkerItem:
1859 case FadeInTrimHandleItem:
1860 case FadeOutTrimHandleItem:
1861 case FadeInHandleItem:
1862 case FadeOutHandleItem:
1864 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1866 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1871 case AutomationTrackItem:
1874 case FeatureLineItem:
1876 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1877 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1889 Editor::scrub (framepos_t frame, double current_x)
1893 if (scrubbing_direction == 0) {
1895 _session->request_locate (frame, false);
1896 _session->request_transport_speed (0.1);
1897 scrubbing_direction = 1;
1901 if (last_scrub_x > current_x) {
1903 /* pointer moved to the left */
1905 if (scrubbing_direction > 0) {
1907 /* we reversed direction to go backwards */
1910 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1914 /* still moving to the left (backwards) */
1916 scrub_reversals = 0;
1917 scrub_reverse_distance = 0;
1919 delta = 0.01 * (last_scrub_x - current_x);
1920 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1924 /* pointer moved to the right */
1926 if (scrubbing_direction < 0) {
1927 /* we reversed direction to go forward */
1930 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1933 /* still moving to the right */
1935 scrub_reversals = 0;
1936 scrub_reverse_distance = 0;
1938 delta = 0.01 * (current_x - last_scrub_x);
1939 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1943 /* if there have been more than 2 opposite motion moves detected, or one that moves
1944 back more than 10 pixels, reverse direction
1947 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1949 if (scrubbing_direction > 0) {
1950 /* was forwards, go backwards */
1951 _session->request_transport_speed (-0.1);
1952 scrubbing_direction = -1;
1954 /* was backwards, go forwards */
1955 _session->request_transport_speed (0.1);
1956 scrubbing_direction = 1;
1959 scrub_reverse_distance = 0;
1960 scrub_reversals = 0;
1964 last_scrub_x = current_x;
1968 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1970 _last_motion_y = event->motion.y;
1972 if (event->motion.is_hint) {
1975 /* We call this so that MOTION_NOTIFY events continue to be
1976 delivered to the canvas. We need to do this because we set
1977 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1978 the density of the events, at the expense of a round-trip
1979 to the server. Given that this will mostly occur on cases
1980 where DISPLAY = :0.0, and given the cost of what the motion
1981 event might do, its a good tradeoff.
1984 _track_canvas->get_pointer (x, y);
1987 if (current_stepping_trackview) {
1988 /* don't keep the persistent stepped trackview if the mouse moves */
1989 current_stepping_trackview = 0;
1990 step_timeout.disconnect ();
1993 if (_session && _session->actively_recording()) {
1994 /* Sorry. no dragging stuff around while we record */
1998 update_join_object_range_location (event->motion.y);
2000 if (_drags->active ()) {
2001 return _drags->motion_handler (event, from_autoscroll);
2008 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2010 ControlPoint* control_point;
2012 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2013 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2014 abort(); /*NOTREACHED*/
2017 AutomationLine& line = control_point->line ();
2018 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2019 /* we shouldn't remove the first or last gain point in region gain lines */
2020 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2029 Editor::remove_control_point (ArdourCanvas::Item* item)
2031 if (!can_remove_control_point (item)) {
2035 ControlPoint* control_point;
2037 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2038 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2039 abort(); /*NOTREACHED*/
2042 control_point->line().remove_point (*control_point);
2046 Editor::edit_control_point (ArdourCanvas::Item* item)
2048 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2051 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2052 abort(); /*NOTREACHED*/
2055 ControlPointDialog d (p);
2057 if (d.run () != RESPONSE_ACCEPT) {
2061 p->line().modify_point_y (*p, d.get_y_fraction ());
2065 Editor::edit_notes (MidiRegionView* mrv)
2067 MidiRegionView::Selection const & s = mrv->selection();
2073 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2076 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2080 Editor::note_edit_done (int r, EditNoteDialog* d)
2087 Editor::visible_order_range (int* low, int* high) const
2089 *low = TimeAxisView::max_order ();
2092 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2094 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2096 if (!rtv->hidden()) {
2098 if (*high < rtv->order()) {
2099 *high = rtv->order ();
2102 if (*low > rtv->order()) {
2103 *low = rtv->order ();
2110 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2112 /* Either add to or set the set the region selection, unless
2113 this is an alignment click (control used)
2116 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2117 TimeAxisView* tv = &rv.get_time_axis_view();
2118 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2120 if (rtv && rtv->is_track()) {
2121 speed = rtv->track()->speed();
2124 framepos_t where = get_preferred_edit_position();
2128 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2130 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2132 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2134 align_region (rv.region(), End, (framepos_t) (where * speed));
2138 align_region (rv.region(), Start, (framepos_t) (where * speed));
2145 Editor::collect_new_region_view (RegionView* rv)
2147 latest_regionviews.push_back (rv);
2151 Editor::collect_and_select_new_region_view (RegionView* rv)
2154 latest_regionviews.push_back (rv);
2158 Editor::cancel_selection ()
2160 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2161 (*i)->hide_selection ();
2164 selection->clear ();
2165 clicked_selection = 0;
2169 Editor::cancel_time_selection ()
2171 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2172 (*i)->hide_selection ();
2174 selection->time.clear ();
2175 clicked_selection = 0;
2179 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2181 RegionView* rv = clicked_regionview;
2183 /* Choose action dependant on which button was pressed */
2184 switch (event->button.button) {
2186 begin_reversible_command (_("start point trim"));
2188 if (selection->selected (rv)) {
2189 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2190 i != selection->regions.by_layer().end(); ++i)
2192 if (!(*i)->region()->locked()) {
2193 (*i)->region()->clear_changes ();
2194 (*i)->region()->trim_front (new_bound);
2195 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2200 if (!rv->region()->locked()) {
2201 rv->region()->clear_changes ();
2202 rv->region()->trim_front (new_bound);
2203 _session->add_command(new StatefulDiffCommand (rv->region()));
2207 commit_reversible_command();
2211 begin_reversible_command (_("end point trim"));
2213 if (selection->selected (rv)) {
2215 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2217 if (!(*i)->region()->locked()) {
2218 (*i)->region()->clear_changes();
2219 (*i)->region()->trim_end (new_bound);
2220 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2226 if (!rv->region()->locked()) {
2227 rv->region()->clear_changes ();
2228 rv->region()->trim_end (new_bound);
2229 _session->add_command (new StatefulDiffCommand (rv->region()));
2233 commit_reversible_command();
2242 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2244 ArdourMarker* marker;
2247 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2248 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2249 abort(); /*NOTREACHED*/
2252 Location* location = find_location_from_marker (marker, is_start);
2253 location->set_hidden (true, this);
2257 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2259 using namespace Gtkmm2ext;
2261 ArdourPrompter prompter (false);
2263 prompter.set_prompt (_("Name for region:"));
2264 prompter.set_initial_text (clicked_regionview->region()->name());
2265 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2266 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2267 prompter.show_all ();
2268 switch (prompter.run ()) {
2269 case Gtk::RESPONSE_ACCEPT:
2271 prompter.get_result(str);
2273 clicked_regionview->region()->set_name (str);
2282 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2284 /* no brushing without a useful snap setting */
2286 switch (_snap_mode) {
2288 return; /* can't work because it allows region to be placed anywhere */
2293 switch (_snap_type) {
2301 /* don't brush a copy over the original */
2303 if (pos == rv->region()->position()) {
2307 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2309 if (rtv == 0 || !rtv->is_track()) {
2313 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2314 double speed = rtv->track()->speed();
2316 playlist->clear_changes ();
2317 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2318 playlist->add_region (new_region, (framepos_t) (pos * speed));
2319 _session->add_command (new StatefulDiffCommand (playlist));
2321 // playlist is frozen, so we have to update manually XXX this is disgusting
2323 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2327 Editor::track_height_step_timeout ()
2329 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2330 current_stepping_trackview = 0;
2337 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2339 assert (region_view);
2341 if (!region_view->region()->playlist()) {
2345 switch (Config->get_edit_mode()) {
2347 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2350 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2353 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2360 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2362 assert (region_view);
2364 if (!region_view->region()->playlist()) {
2368 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2372 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2374 assert (region_view);
2376 if (!region_view->region()->playlist()) {
2380 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2384 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2387 /** Start a grab where a time range is selected, track(s) are selected, and the
2388 * user clicks and drags a region with a modifier in order to create a new region containing
2389 * the section of the clicked region that lies within the time range.
2392 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2394 if (clicked_regionview == 0) {
2398 /* lets try to create new Region for the selection */
2400 vector<boost::shared_ptr<Region> > new_regions;
2401 create_region_from_selection (new_regions);
2403 if (new_regions.empty()) {
2407 /* XXX fix me one day to use all new regions */
2409 boost::shared_ptr<Region> region (new_regions.front());
2411 /* add it to the current stream/playlist.
2413 tricky: the streamview for the track will add a new regionview. we will
2414 catch the signal it sends when it creates the regionview to
2415 set the regionview we want to then drag.
2418 latest_regionviews.clear();
2419 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2421 /* A selection grab currently creates two undo/redo operations, one for
2422 creating the new region and another for moving it.
2424 begin_reversible_command (Operations::selection_grab);
2426 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2428 playlist->clear_changes ();
2429 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2430 _session->add_command(new StatefulDiffCommand (playlist));
2434 if (latest_regionviews.empty()) {
2435 /* something went wrong */
2436 abort_reversible_command ();
2440 /* we need to deselect all other regionviews, and select this one
2441 i'm ignoring undo stuff, because the region creation will take care of it
2444 selection->set (latest_regionviews);
2446 commit_reversible_command ();
2448 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2454 if (_drags->active ()) {
2457 selection->clear ();
2463 /** Update _join_object_range_state which indicate whether we are over the top
2464 * or bottom half of a route view, used by the `join object/range' tool
2465 * mode. Coordinates in canvas space.
2468 Editor::update_join_object_range_location (double y)
2470 if (!get_smart_mode()) {
2471 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2475 JoinObjectRangeState const old = _join_object_range_state;
2477 if (mouse_mode == MouseObject) {
2478 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2479 } else if (mouse_mode == MouseRange) {
2480 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2483 if (entered_regionview) {
2485 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2486 double const c = item_space.y / entered_regionview->height();
2488 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2490 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2491 if (_join_object_range_state != old && ctx) {
2492 ctx->cursor_ctx->change(which_track_cursor());
2495 } else if (entered_track) {
2497 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2499 if (entered_route_view) {
2504 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2506 double track_height = entered_route_view->view()->child_height();
2507 if (UIConfiguration::instance().get_show_name_highlight()) {
2508 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2510 double const c = cy / track_height;
2514 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2516 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2520 /* Other kinds of tracks use object mode */
2521 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2524 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2525 if (_join_object_range_state != old && ctx) {
2526 ctx->cursor_ctx->change(which_track_cursor());
2532 Editor::effective_mouse_mode () const
2534 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2536 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2544 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2546 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2549 e->region_view().delete_note (e->note ());
2552 /** Obtain the pointer position in canvas coordinates */
2554 Editor::get_pointer_position (double& x, double& y) const
2557 _track_canvas->get_pointer (px, py);
2558 _track_canvas->window_to_canvas (px, py, x, y);