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)) {
730 _drags->set (new BBTRulerDrag (this, item), event);
736 case RangeMarkerBarItem:
737 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
738 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
739 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
740 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
742 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
747 case CdMarkerBarItem:
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::CreateCDMarker), event);
756 case TransportMarkerBarItem:
757 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
758 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
760 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
769 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
770 /* special case: allow trim of range selections in joined object mode;
771 in theory eff should equal MouseRange in this case, but it doesn't
772 because entering the range selection canvas item results in entered_regionview
773 being set to 0, so update_join_object_range_location acts as if we aren't
776 if (item_type == StartSelectionTrimItem) {
777 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
778 } else if (item_type == EndSelectionTrimItem) {
779 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
783 Editing::MouseMode eff = effective_mouse_mode ();
785 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
786 if (get_smart_mode()) {
788 case FadeInHandleItem:
789 case FadeInTrimHandleItem:
790 case FadeOutHandleItem:
791 case FadeOutTrimHandleItem:
802 case StartSelectionTrimItem:
803 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
806 case EndSelectionTrimItem:
807 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
811 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
812 start_selection_grab (item, event);
814 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
815 /* grab selection for moving */
816 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
818 /* this was debated, but decided the more common action was to
819 make a new selection */
820 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
825 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
826 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
828 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
833 case RegionViewNameHighlight:
834 if (!clicked_regionview->region()->locked()) {
835 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
841 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
842 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
844 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
853 case FadeInHandleItem:
854 case FadeOutHandleItem:
855 case LeftFrameHandle:
856 case RightFrameHandle:
857 case FeatureLineItem:
858 case RegionViewNameHighlight:
861 case AutomationTrackItem:
862 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
873 /* Existing note: allow trimming/motion */
874 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
875 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
876 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
878 _drags->set (new NoteDrag (this, item), event);
884 _drags->set (new LineDrag (this, item), event);
888 case ControlPointItem:
889 _drags->set (new ControlPointDrag (this, item), event);
893 case AutomationLineItem:
894 _drags->set (new LineDrag (this, item), event);
899 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
902 case AutomationTrackItem:
903 /* rubberband drag to select automation points */
904 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
909 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
910 /* rubberband drag to select automation points */
911 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
922 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
923 event->type == GDK_BUTTON_PRESS) {
925 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
927 } else if (event->type == GDK_BUTTON_PRESS) {
930 case FadeInHandleItem:
932 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
936 case FadeOutHandleItem:
938 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
942 case StartCrossFadeItem:
943 case EndCrossFadeItem:
944 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
945 // if (!clicked_regionview->region()->locked()) {
946 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
951 case FeatureLineItem:
953 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
954 remove_transient(item);
958 _drags->set (new FeatureLineDrag (this, item), event);
964 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
965 /* click on an automation region view; do nothing here and let the ARV's signal handler
971 /* click on a normal region view */
972 if (ArdourKeyboard::indicates_copy (event->button.state)) {
973 add_region_copy_drag (item, event, clicked_regionview);
974 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
975 add_region_brush_drag (item, event, clicked_regionview);
977 add_region_drag (item, event, clicked_regionview);
981 _drags->start_grab (event);
985 case RegionViewNameHighlight:
986 case LeftFrameHandle:
987 case RightFrameHandle:
988 if (!clicked_regionview->region()->locked()) {
989 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
994 case FadeInTrimHandleItem:
995 case FadeOutTrimHandleItem:
996 if (!clicked_regionview->region()->locked()) {
997 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
1002 case RegionViewName:
1004 /* rename happens on edit clicks */
1005 if (clicked_regionview->get_name_highlight()) {
1006 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1012 case ControlPointItem:
1013 _drags->set (new ControlPointDrag (this, item), event);
1017 case AutomationLineItem:
1018 _drags->set (new LineDrag (this, item), event);
1023 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1026 case AutomationTrackItem:
1028 TimeAxisView* parent = clicked_axisview->get_parent ();
1029 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1031 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1033 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1035 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1036 if (pl->n_regions() == 0) {
1037 /* Parent has no regions; create one so that we have somewhere to put automation */
1038 _drags->set (new RegionCreateDrag (this, item, parent), event);
1040 /* See if there's a region before the click that we can extend, and extend it if so */
1041 framepos_t const t = canvas_event_sample (event);
1042 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1044 _drags->set (new RegionCreateDrag (this, item, parent), event);
1046 prev->set_length (t - prev->position (), get_grid_music_divisions (event->button.state));
1050 /* rubberband drag to select automation points */
1051 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1073 switch (item_type) {
1075 _drags->set (new LineDrag (this, item), event);
1078 case ControlPointItem:
1079 _drags->set (new ControlPointDrag (this, item), event);
1085 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
1086 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1087 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
1088 event, _cursors->up_down);
1090 double const y = event->button.y;
1091 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
1093 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1095 /* smart "join" mode: drag automation */
1096 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1104 case AutomationLineItem:
1105 _drags->set (new LineDrag (this, item), event);
1109 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1110 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1111 /* Note is big and pointer is near the end, trim */
1112 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1115 _drags->set (new NoteDrag (this, item), event);
1122 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1123 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1134 if (item_type == NoteItem) {
1135 /* resize-drag notes */
1136 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1137 if (note->big_enough_to_trim()) {
1138 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1142 } else if (clicked_regionview) {
1144 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1150 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1151 scrub_reversals = 0;
1152 scrub_reverse_distance = 0;
1153 last_scrub_x = event->button.x;
1154 scrubbing_direction = 0;
1166 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1168 Editing::MouseMode const eff = effective_mouse_mode ();
1171 switch (item_type) {
1173 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1174 add_region_copy_drag (item, event, clicked_regionview);
1176 add_region_drag (item, event, clicked_regionview);
1178 _drags->start_grab (event);
1181 case ControlPointItem:
1182 _drags->set (new ControlPointDrag (this, item), event);
1190 switch (item_type) {
1191 case RegionViewNameHighlight:
1192 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1196 case LeftFrameHandle:
1197 case RightFrameHandle:
1198 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1202 case RegionViewName:
1203 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1217 /* relax till release */
1229 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1231 if (event->type == GDK_2BUTTON_PRESS) {
1232 _drags->mark_double_click ();
1233 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1237 if (event->type != GDK_BUTTON_PRESS) {
1241 _track_canvas->grab_focus();
1243 if (_session && _session->actively_recording()) {
1247 button_selection (item, event, item_type);
1249 if (!_drags->active () &&
1250 (Keyboard::is_delete_event (&event->button) ||
1251 Keyboard::is_context_menu_event (&event->button) ||
1252 Keyboard::is_edit_event (&event->button))) {
1254 /* handled by button release */
1258 /* not rolling, effectively in range mode, follow edits enabled (likely
1259 * to start range drag), not in a fade handle (since that means we are
1260 * not starting a range drag): locate the PH here
1263 if ((item_type != FadeInHandleItem) &&
1264 (item_type != FadeOutHandleItem) &&
1265 !_drags->active () &&
1267 !_session->transport_rolling() &&
1268 (effective_mouse_mode() == MouseRange) &&
1269 UIConfiguration::instance().get_follow_edits() &&
1270 !_session->config.get_external_sync()) {
1272 MusicFrame where (canvas_event_sample (event), 0);
1274 _session->request_locate (where.frame, false);
1277 switch (event->button.button) {
1279 return button_press_handler_1 (item, event, item_type);
1283 return button_press_handler_2 (item, event, item_type);
1290 return button_press_dispatch (&event->button);
1299 Editor::button_press_dispatch (GdkEventButton* ev)
1301 /* this function is intended only for buttons 4 and above.
1304 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1305 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1309 Editor::button_release_dispatch (GdkEventButton* ev)
1311 /* this function is intended only for buttons 4 and above.
1314 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1315 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1319 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1321 MusicFrame where (canvas_event_sample (event), 0);
1322 AutomationTimeAxisView* atv = 0;
1324 _press_cursor_ctx.reset();
1326 /* no action if we're recording */
1328 if (_session && _session->actively_recording()) {
1332 bool were_dragging = false;
1334 if (!Keyboard::is_context_menu_event (&event->button)) {
1336 /* see if we're finishing a drag */
1338 if (_drags->active ()) {
1339 bool const r = _drags->end_grab (event);
1341 /* grab dragged, so do nothing else */
1345 were_dragging = true;
1348 update_region_layering_order_editor ();
1351 /* edit events get handled here */
1353 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1354 switch (item_type) {
1356 show_region_properties ();
1358 case TempoMarkerItem: {
1359 ArdourMarker* marker;
1360 TempoMarker* tempo_marker;
1362 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1363 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1364 abort(); /*NOTREACHED*/
1367 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1368 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1369 abort(); /*NOTREACHED*/
1372 edit_tempo_marker (*tempo_marker);
1376 case MeterMarkerItem: {
1377 ArdourMarker* marker;
1378 MeterMarker* meter_marker;
1380 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1381 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1382 abort(); /*NOTREACHED*/
1385 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1386 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1387 abort(); /*NOTREACHED*/
1389 edit_meter_marker (*meter_marker);
1393 case RegionViewName:
1394 if (clicked_regionview->name_active()) {
1395 return mouse_rename_region (item, event);
1399 case ControlPointItem:
1400 edit_control_point (item);
1409 /* context menu events get handled here */
1410 if (Keyboard::is_context_menu_event (&event->button)) {
1412 context_click_event = *event;
1414 if (!_drags->active ()) {
1416 /* no matter which button pops up the context menu, tell the menu
1417 widget to use button 1 to drive menu selection.
1420 switch (item_type) {
1422 case FadeInHandleItem:
1423 case FadeInTrimHandleItem:
1424 case StartCrossFadeItem:
1425 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1429 case FadeOutHandleItem:
1430 case FadeOutTrimHandleItem:
1431 case EndCrossFadeItem:
1432 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1435 case LeftFrameHandle:
1436 case RightFrameHandle:
1440 popup_track_context_menu (1, event->button.time, item_type, false);
1444 case RegionViewNameHighlight:
1445 case RegionViewName:
1446 popup_track_context_menu (1, event->button.time, item_type, false);
1450 popup_track_context_menu (1, event->button.time, item_type, true);
1453 case AutomationTrackItem:
1454 popup_track_context_menu (1, event->button.time, item_type, false);
1458 case RangeMarkerBarItem:
1459 case TransportMarkerBarItem:
1460 case CdMarkerBarItem:
1462 case TempoCurveItem:
1465 case TimecodeRulerItem:
1466 case SamplesRulerItem:
1467 case MinsecRulerItem:
1469 popup_ruler_menu (where.frame, item_type);
1473 marker_context_menu (&event->button, item);
1476 case TempoMarkerItem:
1477 tempo_or_meter_marker_context_menu (&event->button, item);
1480 case MeterMarkerItem:
1481 tempo_or_meter_marker_context_menu (&event->button, item);
1484 case CrossfadeViewItem:
1485 popup_track_context_menu (1, event->button.time, item_type, false);
1488 case ControlPointItem:
1489 popup_control_point_context_menu (item, event);
1493 if (internal_editing()) {
1494 popup_note_context_menu (item, event);
1506 /* delete events get handled here */
1508 Editing::MouseMode const eff = effective_mouse_mode ();
1510 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1512 switch (item_type) {
1513 case TempoMarkerItem:
1514 remove_tempo_marker (item);
1517 case MeterMarkerItem:
1518 remove_meter_marker (item);
1522 remove_marker (*item, event);
1526 if (eff == MouseObject) {
1527 remove_clicked_region ();
1531 case ControlPointItem:
1532 remove_control_point (item);
1536 remove_midi_note (item, event);
1545 switch (event->button.button) {
1548 switch (item_type) {
1549 /* see comments in button_press_handler */
1550 case PlayheadCursorItem:
1553 case AutomationLineItem:
1554 case StartSelectionTrimItem:
1555 case EndSelectionTrimItem:
1559 if (!_dragging_playhead) {
1560 snap_to_with_modifier (where, event, RoundNearest, true);
1561 mouse_add_new_marker (where.frame);
1565 case CdMarkerBarItem:
1566 if (!_dragging_playhead) {
1567 // if we get here then a dragged range wasn't done
1568 snap_to_with_modifier (where, event, RoundNearest, true);
1569 mouse_add_new_marker (where.frame, true);
1573 case TempoCurveItem:
1574 if (!_dragging_playhead) {
1575 snap_to_with_modifier (where, event);
1576 mouse_add_new_tempo_event (where.frame);
1581 if (!_dragging_playhead) {
1582 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1587 case TimecodeRulerItem:
1588 case SamplesRulerItem:
1589 case MinsecRulerItem:
1600 switch (item_type) {
1603 /* check that we didn't drag before releasing, since
1604 its really annoying to create new control
1605 points when doing this.
1607 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1608 if (!were_dragging && arv) {
1609 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1610 arv->add_gain_point_event (item, event, with_guard_points);
1616 case AutomationTrackItem: {
1617 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1618 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1620 atv->add_automation_event (event, where.frame, event->button.y, with_guard_points);
1631 if (scrubbing_direction == 0) {
1632 /* no drag, just a click */
1633 switch (item_type) {
1635 play_selected_region ();
1640 } else if (_session) {
1641 /* make sure we stop */
1642 _session->request_transport_speed (0.0);
1651 /* do any (de)selection operations that should occur on button release */
1652 button_selection (item, event, item_type);
1662 switch (item_type) {
1664 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1666 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1669 // Button2 click is unused
1684 // x_style_paste (where, 1.0);
1705 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1708 ArdourMarker * marker;
1709 MeterMarker* m_marker = 0;
1710 TempoMarker* t_marker = 0;
1714 /* by the time we reach here, entered_regionview and entered trackview
1715 * will have already been set as appropriate. Things are done this
1716 * way because this method isn't passed a pointer to a variable type of
1717 * thing that is entered (which may or may not be canvas item).
1718 * (e.g. the actual entered regionview)
1721 choose_canvas_cursor_on_entry (item_type);
1723 switch (item_type) {
1724 case ControlPointItem:
1725 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1726 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1729 fraction = 1.0 - (cp->get_y() / cp->line().height());
1731 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1732 _verbose_cursor->show ();
1737 if (mouse_mode == MouseDraw) {
1738 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1740 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1745 case AutomationLineItem:
1746 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1747 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1749 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1754 case AutomationTrackItem:
1755 AutomationTimeAxisView* atv;
1756 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1757 clear_entered_track = false;
1758 set_entered_track (atv);
1763 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1766 entered_marker = marker;
1767 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1770 case MeterMarkerItem:
1771 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1774 entered_marker = m_marker;
1775 if (m_marker->meter().position_lock_style() == MusicTime) {
1776 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1778 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1782 case TempoMarkerItem:
1783 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1786 entered_marker = t_marker;
1787 if (t_marker->tempo().position_lock_style() == MusicTime) {
1788 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1790 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1794 case FadeInHandleItem:
1795 case FadeInTrimHandleItem:
1796 if (mouse_mode == MouseObject) {
1797 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1799 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1800 rect->set_fill_color (rv->get_fill_color());
1805 case FadeOutHandleItem:
1806 case FadeOutTrimHandleItem:
1807 if (mouse_mode == MouseObject) {
1808 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1810 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1811 rect->set_fill_color (rv->get_fill_color ());
1816 case FeatureLineItem:
1818 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1819 line->set_outline_color (0xFF0000FF);
1828 if (entered_regionview) {
1829 entered_regionview->entered();
1838 /* third pass to handle entered track status in a comprehensible way.
1841 switch (item_type) {
1843 case AutomationLineItem:
1844 case ControlPointItem:
1845 /* these do not affect the current entered track state */
1846 clear_entered_track = false;
1849 case AutomationTrackItem:
1850 /* handled above already */
1862 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1865 ArdourMarker *marker;
1866 TempoMarker *t_marker;
1867 MeterMarker *m_marker;
1872 if (!_enter_stack.empty()) {
1873 _enter_stack.pop_back();
1876 switch (item_type) {
1877 case ControlPointItem:
1878 _verbose_cursor->hide ();
1882 case AutomationLineItem:
1883 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1885 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1887 line->set_outline_color (al->get_line_color());
1893 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1897 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1898 location_flags_changed (loc);
1902 case MeterMarkerItem:
1903 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1907 if (m_marker->meter().position_lock_style() == MusicTime) {
1908 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1910 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1914 case TempoMarkerItem:
1915 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1919 if (t_marker->tempo().position_lock_style() == MusicTime) {
1920 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1922 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1926 case FadeInTrimHandleItem:
1927 case FadeOutTrimHandleItem:
1928 case FadeInHandleItem:
1929 case FadeOutHandleItem:
1931 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1933 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1938 case AutomationTrackItem:
1941 case FeatureLineItem:
1943 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1944 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1956 Editor::scrub (framepos_t frame, double current_x)
1960 if (scrubbing_direction == 0) {
1962 _session->request_locate (frame, false);
1963 _session->request_transport_speed (0.1);
1964 scrubbing_direction = 1;
1968 if (last_scrub_x > current_x) {
1970 /* pointer moved to the left */
1972 if (scrubbing_direction > 0) {
1974 /* we reversed direction to go backwards */
1977 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1981 /* still moving to the left (backwards) */
1983 scrub_reversals = 0;
1984 scrub_reverse_distance = 0;
1986 delta = 0.01 * (last_scrub_x - current_x);
1987 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1991 /* pointer moved to the right */
1993 if (scrubbing_direction < 0) {
1994 /* we reversed direction to go forward */
1997 scrub_reverse_distance += (int) (current_x - last_scrub_x);
2000 /* still moving to the right */
2002 scrub_reversals = 0;
2003 scrub_reverse_distance = 0;
2005 delta = 0.01 * (current_x - last_scrub_x);
2006 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
2010 /* if there have been more than 2 opposite motion moves detected, or one that moves
2011 back more than 10 pixels, reverse direction
2014 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
2016 if (scrubbing_direction > 0) {
2017 /* was forwards, go backwards */
2018 _session->request_transport_speed (-0.1);
2019 scrubbing_direction = -1;
2021 /* was backwards, go forwards */
2022 _session->request_transport_speed (0.1);
2023 scrubbing_direction = 1;
2026 scrub_reverse_distance = 0;
2027 scrub_reversals = 0;
2031 last_scrub_x = current_x;
2035 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2037 _last_motion_y = event->motion.y;
2039 if (event->motion.is_hint) {
2042 /* We call this so that MOTION_NOTIFY events continue to be
2043 delivered to the canvas. We need to do this because we set
2044 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2045 the density of the events, at the expense of a round-trip
2046 to the server. Given that this will mostly occur on cases
2047 where DISPLAY = :0.0, and given the cost of what the motion
2048 event might do, its a good tradeoff.
2051 _track_canvas->get_pointer (x, y);
2054 if (current_stepping_trackview) {
2055 /* don't keep the persistent stepped trackview if the mouse moves */
2056 current_stepping_trackview = 0;
2057 step_timeout.disconnect ();
2060 if (_session && _session->actively_recording()) {
2061 /* Sorry. no dragging stuff around while we record */
2065 update_join_object_range_location (event->motion.y);
2067 if (_drags->active ()) {
2068 return _drags->motion_handler (event, from_autoscroll);
2075 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2077 ControlPoint* control_point;
2079 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2080 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2081 abort(); /*NOTREACHED*/
2084 AutomationLine& line = control_point->line ();
2085 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2086 /* we shouldn't remove the first or last gain point in region gain lines */
2087 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2096 Editor::remove_control_point (ArdourCanvas::Item* item)
2098 if (!can_remove_control_point (item)) {
2102 ControlPoint* control_point;
2104 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2105 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2106 abort(); /*NOTREACHED*/
2109 control_point->line().remove_point (*control_point);
2113 Editor::edit_control_point (ArdourCanvas::Item* item)
2115 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2118 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2119 abort(); /*NOTREACHED*/
2122 ControlPointDialog d (p);
2124 if (d.run () != RESPONSE_ACCEPT) {
2128 p->line().modify_point_y (*p, d.get_y_fraction ());
2132 Editor::edit_notes (MidiRegionView* mrv)
2134 MidiRegionView::Selection const & s = mrv->selection();
2140 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2143 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2147 Editor::note_edit_done (int r, EditNoteDialog* d)
2149 begin_reversible_command (_("edit note(s)"));
2154 commit_reversible_command();
2158 Editor::edit_region (RegionView* rv)
2160 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
2161 temporal_zoom_selection (Both);
2163 rv->show_region_editor ();
2168 Editor::visible_order_range (int* low, int* high) const
2170 *low = TimeAxisView::max_order ();
2173 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2175 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2177 if (rtv && !rtv->hidden()) {
2179 if (*high < rtv->order()) {
2180 *high = rtv->order ();
2183 if (*low > rtv->order()) {
2184 *low = rtv->order ();
2191 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2193 /* Either add to or set the set the region selection, unless
2194 this is an alignment click (control used)
2197 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2198 TimeAxisView* tv = &rv.get_time_axis_view();
2199 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2201 if (rtv && rtv->is_track()) {
2202 speed = rtv->track()->speed();
2205 framepos_t where = get_preferred_edit_position();
2209 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2211 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2213 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2215 align_region (rv.region(), End, (framepos_t) (where * speed));
2219 align_region (rv.region(), Start, (framepos_t) (where * speed));
2226 Editor::collect_new_region_view (RegionView* rv)
2228 latest_regionviews.push_back (rv);
2232 Editor::collect_and_select_new_region_view (RegionView* rv)
2235 latest_regionviews.push_back (rv);
2239 Editor::cancel_selection ()
2241 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2242 (*i)->hide_selection ();
2245 selection->clear ();
2246 clicked_selection = 0;
2250 Editor::cancel_time_selection ()
2252 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2253 (*i)->hide_selection ();
2255 selection->time.clear ();
2256 clicked_selection = 0;
2260 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2262 RegionView* rv = clicked_regionview;
2264 /* Choose action dependant on which button was pressed */
2265 switch (event->button.button) {
2267 begin_reversible_command (_("start point trim"));
2269 if (selection->selected (rv)) {
2270 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2271 i != selection->regions.by_layer().end(); ++i)
2273 if (!(*i)->region()->locked()) {
2274 (*i)->region()->clear_changes ();
2275 (*i)->region()->trim_front (new_bound);
2276 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2281 if (!rv->region()->locked()) {
2282 rv->region()->clear_changes ();
2283 rv->region()->trim_front (new_bound);
2284 _session->add_command(new StatefulDiffCommand (rv->region()));
2288 commit_reversible_command();
2292 begin_reversible_command (_("end point trim"));
2294 if (selection->selected (rv)) {
2296 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2298 if (!(*i)->region()->locked()) {
2299 (*i)->region()->clear_changes();
2300 (*i)->region()->trim_end (new_bound);
2301 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2307 if (!rv->region()->locked()) {
2308 rv->region()->clear_changes ();
2309 rv->region()->trim_end (new_bound);
2310 _session->add_command (new StatefulDiffCommand (rv->region()));
2314 commit_reversible_command();
2323 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2325 ArdourMarker* marker;
2328 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2329 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2330 abort(); /*NOTREACHED*/
2333 Location* location = find_location_from_marker (marker, is_start);
2334 location->set_hidden (true, this);
2338 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2340 using namespace Gtkmm2ext;
2342 ArdourPrompter prompter (false);
2344 prompter.set_prompt (_("Name for region:"));
2345 prompter.set_initial_text (clicked_regionview->region()->name());
2346 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2347 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2348 prompter.show_all ();
2349 switch (prompter.run ()) {
2350 case Gtk::RESPONSE_ACCEPT:
2352 prompter.get_result(str);
2354 clicked_regionview->region()->set_name (str);
2363 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2365 /* no brushing without a useful snap setting */
2367 switch (_snap_mode) {
2369 return; /* can't work because it allows region to be placed anywhere */
2374 switch (_snap_type) {
2382 /* don't brush a copy over the original */
2384 if (pos == rv->region()->position()) {
2388 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2390 if (!rtv || !rtv->is_track()) {
2394 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2395 double speed = rtv->track()->speed();
2397 playlist->clear_changes ();
2398 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2399 playlist->add_region (new_region, (framepos_t) (pos * speed));
2400 _session->add_command (new StatefulDiffCommand (playlist));
2402 // playlist is frozen, so we have to update manually XXX this is disgusting
2404 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2408 Editor::track_height_step_timeout ()
2410 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2411 current_stepping_trackview = 0;
2418 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2420 assert (region_view);
2422 if (!region_view->region()->playlist()) {
2426 switch (Config->get_edit_mode()) {
2428 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2431 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2434 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2441 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2443 assert (region_view);
2445 if (!region_view->region()->playlist()) {
2449 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2453 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2455 assert (region_view);
2457 if (!region_view->region()->playlist()) {
2461 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2465 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2468 /** Start a grab where a time range is selected, track(s) are selected, and the
2469 * user clicks and drags a region with a modifier in order to create a new region containing
2470 * the section of the clicked region that lies within the time range.
2473 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2475 if (clicked_regionview == 0) {
2479 /* lets try to create new Region for the selection */
2481 vector<boost::shared_ptr<Region> > new_regions;
2482 create_region_from_selection (new_regions);
2484 if (new_regions.empty()) {
2488 /* XXX fix me one day to use all new regions */
2490 boost::shared_ptr<Region> region (new_regions.front());
2492 /* add it to the current stream/playlist.
2494 tricky: the streamview for the track will add a new regionview. we will
2495 catch the signal it sends when it creates the regionview to
2496 set the regionview we want to then drag.
2499 latest_regionviews.clear();
2500 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2502 /* A selection grab currently creates two undo/redo operations, one for
2503 creating the new region and another for moving it.
2505 begin_reversible_command (Operations::selection_grab);
2507 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2509 playlist->clear_changes ();
2510 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2511 _session->add_command(new StatefulDiffCommand (playlist));
2515 if (latest_regionviews.empty()) {
2516 /* something went wrong */
2517 abort_reversible_command ();
2521 /* we need to deselect all other regionviews, and select this one
2522 i'm ignoring undo stuff, because the region creation will take care of it
2525 selection->set (latest_regionviews);
2527 commit_reversible_command ();
2529 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2535 if (_drags->active ()) {
2538 selection->clear ();
2541 ARDOUR_UI::instance()->reset_focus (&contents());
2544 /** Update _join_object_range_state which indicate whether we are over the top
2545 * or bottom half of a route view, used by the `join object/range' tool
2546 * mode. Coordinates in canvas space.
2549 Editor::update_join_object_range_location (double y)
2551 if (!get_smart_mode()) {
2552 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2556 JoinObjectRangeState const old = _join_object_range_state;
2558 if (mouse_mode == MouseObject) {
2559 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2560 } else if (mouse_mode == MouseRange) {
2561 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2564 if (entered_regionview) {
2566 //ToDo: there is currently a bug here(?)
2567 //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
2568 //can it be fixed here?
2570 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2571 double const c = item_space.y / entered_regionview->height();
2573 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2575 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2576 if (_join_object_range_state != old && ctx) {
2577 ctx->cursor_ctx->change(which_track_cursor());
2580 } else if (entered_track) {
2582 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2584 if (entered_route_view) {
2589 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2591 double track_height = entered_route_view->view()->child_height();
2592 if (UIConfiguration::instance().get_show_name_highlight()) {
2593 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2595 double const c = cy / track_height;
2599 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2601 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2605 /* Other kinds of tracks use object mode */
2606 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2609 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2610 if (_join_object_range_state != old && ctx) {
2611 ctx->cursor_ctx->change(which_track_cursor());
2617 Editor::effective_mouse_mode () const
2619 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2621 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2629 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2631 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2634 e->region_view().delete_note (e->note ());
2637 /** Obtain the pointer position in canvas coordinates */
2639 Editor::get_pointer_position (double& x, double& y) const
2642 _track_canvas->get_pointer (px, py);
2643 _track_canvas->window_to_canvas (px, py, x, y);