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 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 ArdourKeyboard::indicates_copy (event->button.state)
695 case MeterMarkerItem:
698 new MeterMarkerDrag (
701 ArdourKeyboard::indicates_copy (event->button.state)
709 _drags->set (new VideoTimeLineDrag (this, item), event);
717 case TimecodeRulerItem:
718 case SamplesRulerItem:
719 case MinsecRulerItem:
721 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)
722 && !ArdourKeyboard::indicates_constraint (event->button.state)) {
723 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
724 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
725 _drags->set (new BBTRulerDrag (this, item), event);
731 case RangeMarkerBarItem:
732 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
733 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
734 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
735 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
737 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
742 case CdMarkerBarItem:
743 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
744 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
746 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
751 case TransportMarkerBarItem:
752 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
753 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
755 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
764 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
765 /* special case: allow trim of range selections in joined object mode;
766 in theory eff should equal MouseRange in this case, but it doesn't
767 because entering the range selection canvas item results in entered_regionview
768 being set to 0, so update_join_object_range_location acts as if we aren't
771 if (item_type == StartSelectionTrimItem) {
772 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
773 } else if (item_type == EndSelectionTrimItem) {
774 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
778 Editing::MouseMode eff = effective_mouse_mode ();
780 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
781 if (get_smart_mode()) {
783 case FadeInHandleItem:
784 case FadeInTrimHandleItem:
785 case FadeOutHandleItem:
786 case FadeOutTrimHandleItem:
797 case StartSelectionTrimItem:
798 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
801 case EndSelectionTrimItem:
802 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
806 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
807 start_selection_grab (item, event);
809 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
810 /* grab selection for moving */
811 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
813 /* this was debated, but decided the more common action was to
814 make a new selection */
815 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
820 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
821 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
823 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
828 case RegionViewNameHighlight:
829 if (!clicked_regionview->region()->locked()) {
830 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
836 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
837 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
839 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
848 case FadeInHandleItem:
849 case FadeOutHandleItem:
850 case LeftFrameHandle:
851 case RightFrameHandle:
852 case FeatureLineItem:
853 case RegionViewNameHighlight:
856 case AutomationTrackItem:
857 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
868 /* Existing note: allow trimming/motion */
869 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
870 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
871 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
873 _drags->set (new NoteDrag (this, item), event);
879 _drags->set (new LineDrag (this, item), event);
883 case ControlPointItem:
884 _drags->set (new ControlPointDrag (this, item), event);
888 case AutomationLineItem:
889 _drags->set (new LineDrag (this, item), event);
894 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
897 case AutomationTrackItem:
898 /* rubberband drag to select automation points */
899 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
904 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
905 /* rubberband drag to select automation points */
906 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
917 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
918 event->type == GDK_BUTTON_PRESS) {
920 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
922 } else if (event->type == GDK_BUTTON_PRESS) {
925 case FadeInHandleItem:
927 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
931 case FadeOutHandleItem:
933 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
937 case StartCrossFadeItem:
938 case EndCrossFadeItem:
939 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
940 // if (!clicked_regionview->region()->locked()) {
941 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
946 case FeatureLineItem:
948 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
949 remove_transient(item);
953 _drags->set (new FeatureLineDrag (this, item), event);
959 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
960 /* click on an automation region view; do nothing here and let the ARV's signal handler
966 /* click on a normal region view */
967 if (ArdourKeyboard::indicates_copy (event->button.state)) {
968 add_region_copy_drag (item, event, clicked_regionview);
969 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
970 add_region_brush_drag (item, event, clicked_regionview);
972 add_region_drag (item, event, clicked_regionview);
976 _drags->start_grab (event);
980 case RegionViewNameHighlight:
981 case LeftFrameHandle:
982 case RightFrameHandle:
983 if (!clicked_regionview->region()->locked()) {
984 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
989 case FadeInTrimHandleItem:
990 case FadeOutTrimHandleItem:
991 if (!clicked_regionview->region()->locked()) {
992 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
999 /* rename happens on edit clicks */
1000 if (clicked_regionview->get_name_highlight()) {
1001 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1007 case ControlPointItem:
1008 _drags->set (new ControlPointDrag (this, item), event);
1012 case AutomationLineItem:
1013 _drags->set (new LineDrag (this, item), event);
1018 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1021 case AutomationTrackItem:
1023 TimeAxisView* parent = clicked_axisview->get_parent ();
1024 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1026 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1028 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1030 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1031 if (pl->n_regions() == 0) {
1032 /* Parent has no regions; create one so that we have somewhere to put automation */
1033 _drags->set (new RegionCreateDrag (this, item, parent), event);
1035 /* See if there's a region before the click that we can extend, and extend it if so */
1036 framepos_t const t = canvas_event_sample (event);
1037 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1039 _drags->set (new RegionCreateDrag (this, item, parent), event);
1041 prev->set_length (t - prev->position (), get_grid_music_divisions (event->button.state));
1045 /* rubberband drag to select automation points */
1046 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1068 switch (item_type) {
1070 _drags->set (new LineDrag (this, item), event);
1073 case ControlPointItem:
1074 _drags->set (new ControlPointDrag (this, item), event);
1080 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
1081 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1082 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
1083 event, _cursors->up_down);
1085 double const y = event->button.y;
1086 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
1088 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1090 /* smart "join" mode: drag automation */
1091 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1099 case AutomationLineItem:
1100 _drags->set (new LineDrag (this, item), event);
1104 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1105 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1106 /* Note is big and pointer is near the end, trim */
1107 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1110 _drags->set (new NoteDrag (this, item), event);
1117 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1118 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1129 if (item_type == NoteItem) {
1130 /* resize-drag notes */
1131 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1132 if (note->big_enough_to_trim()) {
1133 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1137 } else if (clicked_regionview) {
1139 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1145 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1146 scrub_reversals = 0;
1147 scrub_reverse_distance = 0;
1148 last_scrub_x = event->button.x;
1149 scrubbing_direction = 0;
1161 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1163 Editing::MouseMode const eff = effective_mouse_mode ();
1166 switch (item_type) {
1168 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1169 add_region_copy_drag (item, event, clicked_regionview);
1171 add_region_drag (item, event, clicked_regionview);
1173 _drags->start_grab (event);
1176 case ControlPointItem:
1177 _drags->set (new ControlPointDrag (this, item), event);
1185 switch (item_type) {
1186 case RegionViewNameHighlight:
1187 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1191 case LeftFrameHandle:
1192 case RightFrameHandle:
1193 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1197 case RegionViewName:
1198 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1212 /* relax till release */
1224 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1226 if (event->type == GDK_2BUTTON_PRESS) {
1227 _drags->mark_double_click ();
1228 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1232 if (event->type != GDK_BUTTON_PRESS) {
1236 _track_canvas->grab_focus();
1238 if (_session && _session->actively_recording()) {
1242 button_selection (item, event, item_type);
1244 if (!_drags->active () &&
1245 (Keyboard::is_delete_event (&event->button) ||
1246 Keyboard::is_context_menu_event (&event->button) ||
1247 Keyboard::is_edit_event (&event->button))) {
1249 /* handled by button release */
1253 //not rolling, range mode click + join_play_range : locate the PH here
1254 if ( !_drags->active () && _session && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && UIConfiguration::instance().get_follow_edits() && !_session->config.get_external_sync() ) {
1255 framepos_t where = canvas_event_sample (event);
1257 _session->request_locate (where, false);
1260 switch (event->button.button) {
1262 return button_press_handler_1 (item, event, item_type);
1266 return button_press_handler_2 (item, event, item_type);
1273 return button_press_dispatch (&event->button);
1282 Editor::button_press_dispatch (GdkEventButton* ev)
1284 /* this function is intended only for buttons 4 and above.
1287 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1288 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1292 Editor::button_release_dispatch (GdkEventButton* ev)
1294 /* this function is intended only for buttons 4 and above.
1297 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1298 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1302 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1304 framepos_t where = canvas_event_sample (event);
1305 AutomationTimeAxisView* atv = 0;
1307 _press_cursor_ctx.reset();
1309 /* no action if we're recording */
1311 if (_session && _session->actively_recording()) {
1315 bool were_dragging = false;
1317 if (!Keyboard::is_context_menu_event (&event->button)) {
1319 /* see if we're finishing a drag */
1321 if (_drags->active ()) {
1322 bool const r = _drags->end_grab (event);
1324 /* grab dragged, so do nothing else */
1328 were_dragging = true;
1331 update_region_layering_order_editor ();
1334 /* edit events get handled here */
1336 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1337 switch (item_type) {
1339 show_region_properties ();
1341 case TempoMarkerItem: {
1342 ArdourMarker* marker;
1343 TempoMarker* tempo_marker;
1345 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1346 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1347 abort(); /*NOTREACHED*/
1350 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1351 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1352 abort(); /*NOTREACHED*/
1355 edit_tempo_marker (*tempo_marker);
1359 case MeterMarkerItem: {
1360 ArdourMarker* marker;
1361 MeterMarker* meter_marker;
1363 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1364 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1365 abort(); /*NOTREACHED*/
1368 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1369 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1370 abort(); /*NOTREACHED*/
1372 edit_meter_marker (*meter_marker);
1376 case RegionViewName:
1377 if (clicked_regionview->name_active()) {
1378 return mouse_rename_region (item, event);
1382 case ControlPointItem:
1383 edit_control_point (item);
1392 /* context menu events get handled here */
1393 if (Keyboard::is_context_menu_event (&event->button)) {
1395 context_click_event = *event;
1397 if (!_drags->active ()) {
1399 /* no matter which button pops up the context menu, tell the menu
1400 widget to use button 1 to drive menu selection.
1403 switch (item_type) {
1405 case FadeInHandleItem:
1406 case FadeInTrimHandleItem:
1407 case StartCrossFadeItem:
1408 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1412 case FadeOutHandleItem:
1413 case FadeOutTrimHandleItem:
1414 case EndCrossFadeItem:
1415 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1418 case LeftFrameHandle:
1419 case RightFrameHandle:
1423 popup_track_context_menu (1, event->button.time, item_type, false);
1427 case RegionViewNameHighlight:
1428 case RegionViewName:
1429 popup_track_context_menu (1, event->button.time, item_type, false);
1433 popup_track_context_menu (1, event->button.time, item_type, true);
1436 case AutomationTrackItem:
1437 popup_track_context_menu (1, event->button.time, item_type, false);
1441 case RangeMarkerBarItem:
1442 case TransportMarkerBarItem:
1443 case CdMarkerBarItem:
1445 case TempoCurveItem:
1448 case TimecodeRulerItem:
1449 case SamplesRulerItem:
1450 case MinsecRulerItem:
1452 popup_ruler_menu (where, item_type);
1456 marker_context_menu (&event->button, item);
1459 case TempoMarkerItem:
1460 tempo_or_meter_marker_context_menu (&event->button, item);
1463 case MeterMarkerItem:
1464 tempo_or_meter_marker_context_menu (&event->button, item);
1467 case CrossfadeViewItem:
1468 popup_track_context_menu (1, event->button.time, item_type, false);
1471 case ControlPointItem:
1472 popup_control_point_context_menu (item, event);
1476 if (internal_editing()) {
1477 popup_note_context_menu (item, event);
1489 /* delete events get handled here */
1491 Editing::MouseMode const eff = effective_mouse_mode ();
1493 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1495 switch (item_type) {
1496 case TempoMarkerItem:
1497 remove_tempo_marker (item);
1500 case MeterMarkerItem:
1501 remove_meter_marker (item);
1505 remove_marker (*item, event);
1509 if (eff == MouseObject) {
1510 remove_clicked_region ();
1514 case ControlPointItem:
1515 remove_control_point (item);
1519 remove_midi_note (item, event);
1528 switch (event->button.button) {
1531 switch (item_type) {
1532 /* see comments in button_press_handler */
1533 case PlayheadCursorItem:
1536 case AutomationLineItem:
1537 case StartSelectionTrimItem:
1538 case EndSelectionTrimItem:
1542 if (!_dragging_playhead) {
1543 snap_to_with_modifier (where, event, RoundNearest, true);
1544 mouse_add_new_marker (where);
1548 case CdMarkerBarItem:
1549 if (!_dragging_playhead) {
1550 // if we get here then a dragged range wasn't done
1551 snap_to_with_modifier (where, event, RoundNearest, true);
1552 mouse_add_new_marker (where, true);
1556 case TempoCurveItem:
1557 if (!_dragging_playhead) {
1558 snap_to_with_modifier (where, event);
1559 mouse_add_new_tempo_event (where);
1564 if (!_dragging_playhead) {
1565 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1570 case TimecodeRulerItem:
1571 case SamplesRulerItem:
1572 case MinsecRulerItem:
1583 switch (item_type) {
1586 /* check that we didn't drag before releasing, since
1587 its really annoying to create new control
1588 points when doing this.
1590 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1591 if (!were_dragging && arv) {
1592 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1593 arv->add_gain_point_event (item, event, with_guard_points);
1599 case AutomationTrackItem: {
1600 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1601 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1603 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1614 if (scrubbing_direction == 0) {
1615 /* no drag, just a click */
1616 switch (item_type) {
1618 play_selected_region ();
1623 } else if (_session) {
1624 /* make sure we stop */
1625 _session->request_transport_speed (0.0);
1634 /* do any (de)selection operations that should occur on button release */
1635 button_selection (item, event, item_type);
1645 switch (item_type) {
1647 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1649 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1652 // Button2 click is unused
1667 // x_style_paste (where, 1.0);
1688 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1691 ArdourMarker * marker;
1692 MeterMarker* m_marker = 0;
1693 TempoMarker* t_marker = 0;
1697 /* by the time we reach here, entered_regionview and entered trackview
1698 * will have already been set as appropriate. Things are done this
1699 * way because this method isn't passed a pointer to a variable type of
1700 * thing that is entered (which may or may not be canvas item).
1701 * (e.g. the actual entered regionview)
1704 choose_canvas_cursor_on_entry (item_type);
1706 switch (item_type) {
1707 case ControlPointItem:
1708 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1709 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1712 fraction = 1.0 - (cp->get_y() / cp->line().height());
1714 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1715 _verbose_cursor->show ();
1720 if (mouse_mode == MouseDraw) {
1721 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1723 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1728 case AutomationLineItem:
1729 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1730 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1732 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1737 case AutomationTrackItem:
1738 AutomationTimeAxisView* atv;
1739 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1740 clear_entered_track = false;
1741 set_entered_track (atv);
1746 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1749 entered_marker = marker;
1750 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1753 case MeterMarkerItem:
1754 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1757 entered_marker = m_marker;
1758 if (m_marker->meter().position_lock_style() == MusicTime) {
1759 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1761 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1765 case TempoMarkerItem:
1766 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1769 entered_marker = t_marker;
1770 if (t_marker->tempo().position_lock_style() == MusicTime) {
1771 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1773 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1777 case FadeInHandleItem:
1778 case FadeInTrimHandleItem:
1779 if (mouse_mode == MouseObject) {
1780 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1782 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1783 rect->set_fill_color (rv->get_fill_color());
1788 case FadeOutHandleItem:
1789 case FadeOutTrimHandleItem:
1790 if (mouse_mode == MouseObject) {
1791 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1793 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1794 rect->set_fill_color (rv->get_fill_color ());
1799 case FeatureLineItem:
1801 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1802 line->set_outline_color (0xFF0000FF);
1811 if (entered_regionview) {
1812 entered_regionview->entered();
1821 /* third pass to handle entered track status in a comprehensible way.
1824 switch (item_type) {
1826 case AutomationLineItem:
1827 case ControlPointItem:
1828 /* these do not affect the current entered track state */
1829 clear_entered_track = false;
1832 case AutomationTrackItem:
1833 /* handled above already */
1845 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1848 ArdourMarker *marker;
1849 TempoMarker *t_marker;
1850 MeterMarker *m_marker;
1855 if (!_enter_stack.empty()) {
1856 _enter_stack.pop_back();
1859 switch (item_type) {
1860 case ControlPointItem:
1861 _verbose_cursor->hide ();
1865 case AutomationLineItem:
1866 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1868 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1870 line->set_outline_color (al->get_line_color());
1876 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1880 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1881 location_flags_changed (loc);
1885 case MeterMarkerItem:
1886 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1890 if (m_marker->meter().position_lock_style() == MusicTime) {
1891 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1893 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1897 case TempoMarkerItem:
1898 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1902 if (t_marker->tempo().position_lock_style() == MusicTime) {
1903 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1905 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1909 case FadeInTrimHandleItem:
1910 case FadeOutTrimHandleItem:
1911 case FadeInHandleItem:
1912 case FadeOutHandleItem:
1914 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1916 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1921 case AutomationTrackItem:
1924 case FeatureLineItem:
1926 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1927 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1939 Editor::scrub (framepos_t frame, double current_x)
1943 if (scrubbing_direction == 0) {
1945 _session->request_locate (frame, false);
1946 _session->request_transport_speed (0.1);
1947 scrubbing_direction = 1;
1951 if (last_scrub_x > current_x) {
1953 /* pointer moved to the left */
1955 if (scrubbing_direction > 0) {
1957 /* we reversed direction to go backwards */
1960 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1964 /* still moving to the left (backwards) */
1966 scrub_reversals = 0;
1967 scrub_reverse_distance = 0;
1969 delta = 0.01 * (last_scrub_x - current_x);
1970 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1974 /* pointer moved to the right */
1976 if (scrubbing_direction < 0) {
1977 /* we reversed direction to go forward */
1980 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1983 /* still moving to the right */
1985 scrub_reversals = 0;
1986 scrub_reverse_distance = 0;
1988 delta = 0.01 * (current_x - last_scrub_x);
1989 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1993 /* if there have been more than 2 opposite motion moves detected, or one that moves
1994 back more than 10 pixels, reverse direction
1997 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1999 if (scrubbing_direction > 0) {
2000 /* was forwards, go backwards */
2001 _session->request_transport_speed (-0.1);
2002 scrubbing_direction = -1;
2004 /* was backwards, go forwards */
2005 _session->request_transport_speed (0.1);
2006 scrubbing_direction = 1;
2009 scrub_reverse_distance = 0;
2010 scrub_reversals = 0;
2014 last_scrub_x = current_x;
2018 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2020 _last_motion_y = event->motion.y;
2022 if (event->motion.is_hint) {
2025 /* We call this so that MOTION_NOTIFY events continue to be
2026 delivered to the canvas. We need to do this because we set
2027 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2028 the density of the events, at the expense of a round-trip
2029 to the server. Given that this will mostly occur on cases
2030 where DISPLAY = :0.0, and given the cost of what the motion
2031 event might do, its a good tradeoff.
2034 _track_canvas->get_pointer (x, y);
2037 if (current_stepping_trackview) {
2038 /* don't keep the persistent stepped trackview if the mouse moves */
2039 current_stepping_trackview = 0;
2040 step_timeout.disconnect ();
2043 if (_session && _session->actively_recording()) {
2044 /* Sorry. no dragging stuff around while we record */
2048 update_join_object_range_location (event->motion.y);
2050 if (_drags->active ()) {
2051 return _drags->motion_handler (event, from_autoscroll);
2058 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2060 ControlPoint* control_point;
2062 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2063 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2064 abort(); /*NOTREACHED*/
2067 AutomationLine& line = control_point->line ();
2068 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2069 /* we shouldn't remove the first or last gain point in region gain lines */
2070 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2079 Editor::remove_control_point (ArdourCanvas::Item* item)
2081 if (!can_remove_control_point (item)) {
2085 ControlPoint* control_point;
2087 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2088 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2089 abort(); /*NOTREACHED*/
2092 control_point->line().remove_point (*control_point);
2096 Editor::edit_control_point (ArdourCanvas::Item* item)
2098 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2101 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2102 abort(); /*NOTREACHED*/
2105 ControlPointDialog d (p);
2107 if (d.run () != RESPONSE_ACCEPT) {
2111 p->line().modify_point_y (*p, d.get_y_fraction ());
2115 Editor::edit_notes (MidiRegionView* mrv)
2117 MidiRegionView::Selection const & s = mrv->selection();
2123 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2126 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2130 Editor::note_edit_done (int r, EditNoteDialog* d)
2132 begin_reversible_command (_("edit note(s)"));
2137 commit_reversible_command();
2141 Editor::visible_order_range (int* low, int* high) const
2143 *low = TimeAxisView::max_order ();
2146 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2148 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2150 if (rtv && !rtv->hidden()) {
2152 if (*high < rtv->order()) {
2153 *high = rtv->order ();
2156 if (*low > rtv->order()) {
2157 *low = rtv->order ();
2164 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2166 /* Either add to or set the set the region selection, unless
2167 this is an alignment click (control used)
2170 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2171 TimeAxisView* tv = &rv.get_time_axis_view();
2172 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2174 if (rtv && rtv->is_track()) {
2175 speed = rtv->track()->speed();
2178 framepos_t where = get_preferred_edit_position();
2182 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2184 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2186 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2188 align_region (rv.region(), End, (framepos_t) (where * speed));
2192 align_region (rv.region(), Start, (framepos_t) (where * speed));
2199 Editor::collect_new_region_view (RegionView* rv)
2201 latest_regionviews.push_back (rv);
2205 Editor::collect_and_select_new_region_view (RegionView* rv)
2208 latest_regionviews.push_back (rv);
2212 Editor::cancel_selection ()
2214 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2215 (*i)->hide_selection ();
2218 selection->clear ();
2219 clicked_selection = 0;
2223 Editor::cancel_time_selection ()
2225 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2226 (*i)->hide_selection ();
2228 selection->time.clear ();
2229 clicked_selection = 0;
2233 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2235 RegionView* rv = clicked_regionview;
2237 /* Choose action dependant on which button was pressed */
2238 switch (event->button.button) {
2240 begin_reversible_command (_("start point trim"));
2242 if (selection->selected (rv)) {
2243 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2244 i != selection->regions.by_layer().end(); ++i)
2246 if (!(*i)->region()->locked()) {
2247 (*i)->region()->clear_changes ();
2248 (*i)->region()->trim_front (new_bound);
2249 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2254 if (!rv->region()->locked()) {
2255 rv->region()->clear_changes ();
2256 rv->region()->trim_front (new_bound);
2257 _session->add_command(new StatefulDiffCommand (rv->region()));
2261 commit_reversible_command();
2265 begin_reversible_command (_("end point trim"));
2267 if (selection->selected (rv)) {
2269 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2271 if (!(*i)->region()->locked()) {
2272 (*i)->region()->clear_changes();
2273 (*i)->region()->trim_end (new_bound);
2274 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2280 if (!rv->region()->locked()) {
2281 rv->region()->clear_changes ();
2282 rv->region()->trim_end (new_bound);
2283 _session->add_command (new StatefulDiffCommand (rv->region()));
2287 commit_reversible_command();
2296 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2298 ArdourMarker* marker;
2301 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2302 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2303 abort(); /*NOTREACHED*/
2306 Location* location = find_location_from_marker (marker, is_start);
2307 location->set_hidden (true, this);
2311 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2313 using namespace Gtkmm2ext;
2315 ArdourPrompter prompter (false);
2317 prompter.set_prompt (_("Name for region:"));
2318 prompter.set_initial_text (clicked_regionview->region()->name());
2319 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2320 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2321 prompter.show_all ();
2322 switch (prompter.run ()) {
2323 case Gtk::RESPONSE_ACCEPT:
2325 prompter.get_result(str);
2327 clicked_regionview->region()->set_name (str);
2336 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2338 /* no brushing without a useful snap setting */
2340 switch (_snap_mode) {
2342 return; /* can't work because it allows region to be placed anywhere */
2347 switch (_snap_type) {
2355 /* don't brush a copy over the original */
2357 if (pos == rv->region()->position()) {
2361 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2363 if (!rtv || !rtv->is_track()) {
2367 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2368 double speed = rtv->track()->speed();
2370 playlist->clear_changes ();
2371 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2372 playlist->add_region (new_region, (framepos_t) (pos * speed));
2373 _session->add_command (new StatefulDiffCommand (playlist));
2375 // playlist is frozen, so we have to update manually XXX this is disgusting
2377 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2381 Editor::track_height_step_timeout ()
2383 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2384 current_stepping_trackview = 0;
2391 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2393 assert (region_view);
2395 if (!region_view->region()->playlist()) {
2399 switch (Config->get_edit_mode()) {
2401 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2404 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2407 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2414 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2416 assert (region_view);
2418 if (!region_view->region()->playlist()) {
2422 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2426 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2428 assert (region_view);
2430 if (!region_view->region()->playlist()) {
2434 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2438 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2441 /** Start a grab where a time range is selected, track(s) are selected, and the
2442 * user clicks and drags a region with a modifier in order to create a new region containing
2443 * the section of the clicked region that lies within the time range.
2446 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2448 if (clicked_regionview == 0) {
2452 /* lets try to create new Region for the selection */
2454 vector<boost::shared_ptr<Region> > new_regions;
2455 create_region_from_selection (new_regions);
2457 if (new_regions.empty()) {
2461 /* XXX fix me one day to use all new regions */
2463 boost::shared_ptr<Region> region (new_regions.front());
2465 /* add it to the current stream/playlist.
2467 tricky: the streamview for the track will add a new regionview. we will
2468 catch the signal it sends when it creates the regionview to
2469 set the regionview we want to then drag.
2472 latest_regionviews.clear();
2473 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2475 /* A selection grab currently creates two undo/redo operations, one for
2476 creating the new region and another for moving it.
2478 begin_reversible_command (Operations::selection_grab);
2480 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2482 playlist->clear_changes ();
2483 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2484 _session->add_command(new StatefulDiffCommand (playlist));
2488 if (latest_regionviews.empty()) {
2489 /* something went wrong */
2490 abort_reversible_command ();
2494 /* we need to deselect all other regionviews, and select this one
2495 i'm ignoring undo stuff, because the region creation will take care of it
2498 selection->set (latest_regionviews);
2500 commit_reversible_command ();
2502 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2508 if (_drags->active ()) {
2511 selection->clear ();
2514 ARDOUR_UI::instance()->reset_focus (&contents());
2517 /** Update _join_object_range_state which indicate whether we are over the top
2518 * or bottom half of a route view, used by the `join object/range' tool
2519 * mode. Coordinates in canvas space.
2522 Editor::update_join_object_range_location (double y)
2524 if (!get_smart_mode()) {
2525 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2529 JoinObjectRangeState const old = _join_object_range_state;
2531 if (mouse_mode == MouseObject) {
2532 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2533 } else if (mouse_mode == MouseRange) {
2534 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2537 if (entered_regionview) {
2539 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2540 double const c = item_space.y / entered_regionview->height();
2542 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2544 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2545 if (_join_object_range_state != old && ctx) {
2546 ctx->cursor_ctx->change(which_track_cursor());
2549 } else if (entered_track) {
2551 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2553 if (entered_route_view) {
2558 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2560 double track_height = entered_route_view->view()->child_height();
2561 if (UIConfiguration::instance().get_show_name_highlight()) {
2562 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2564 double const c = cy / track_height;
2568 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2570 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2574 /* Other kinds of tracks use object mode */
2575 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2578 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2579 if (_join_object_range_state != old && ctx) {
2580 ctx->cursor_ctx->change(which_track_cursor());
2586 Editor::effective_mouse_mode () const
2588 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2590 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2598 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2600 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2603 e->region_view().delete_note (e->note ());
2606 /** Obtain the pointer position in canvas coordinates */
2608 Editor::get_pointer_position (double& x, double& y) const
2611 _track_canvas->get_pointer (px, py);
2612 _track_canvas->window_to_canvas (px, py, x, y);