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, range mode click + join_play_range : locate the PH here
1259 if ( !_drags->active () && _session && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && UIConfiguration::instance().get_follow_edits() && !_session->config.get_external_sync() ) {
1260 framepos_t where = canvas_event_sample (event);
1262 _session->request_locate (where, false);
1265 switch (event->button.button) {
1267 return button_press_handler_1 (item, event, item_type);
1271 return button_press_handler_2 (item, event, item_type);
1278 return button_press_dispatch (&event->button);
1287 Editor::button_press_dispatch (GdkEventButton* ev)
1289 /* this function is intended only for buttons 4 and above.
1292 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1293 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1297 Editor::button_release_dispatch (GdkEventButton* ev)
1299 /* this function is intended only for buttons 4 and above.
1302 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1303 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1307 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1309 framepos_t where = canvas_event_sample (event);
1310 AutomationTimeAxisView* atv = 0;
1312 _press_cursor_ctx.reset();
1314 /* no action if we're recording */
1316 if (_session && _session->actively_recording()) {
1320 bool were_dragging = false;
1322 if (!Keyboard::is_context_menu_event (&event->button)) {
1324 /* see if we're finishing a drag */
1326 if (_drags->active ()) {
1327 bool const r = _drags->end_grab (event);
1329 /* grab dragged, so do nothing else */
1333 were_dragging = true;
1336 update_region_layering_order_editor ();
1339 /* edit events get handled here */
1341 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1342 switch (item_type) {
1344 show_region_properties ();
1346 case TempoMarkerItem: {
1347 ArdourMarker* marker;
1348 TempoMarker* tempo_marker;
1350 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1351 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1352 abort(); /*NOTREACHED*/
1355 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1356 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1357 abort(); /*NOTREACHED*/
1360 edit_tempo_marker (*tempo_marker);
1364 case MeterMarkerItem: {
1365 ArdourMarker* marker;
1366 MeterMarker* meter_marker;
1368 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1369 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1370 abort(); /*NOTREACHED*/
1373 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1374 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1375 abort(); /*NOTREACHED*/
1377 edit_meter_marker (*meter_marker);
1381 case RegionViewName:
1382 if (clicked_regionview->name_active()) {
1383 return mouse_rename_region (item, event);
1387 case ControlPointItem:
1388 edit_control_point (item);
1397 /* context menu events get handled here */
1398 if (Keyboard::is_context_menu_event (&event->button)) {
1400 context_click_event = *event;
1402 if (!_drags->active ()) {
1404 /* no matter which button pops up the context menu, tell the menu
1405 widget to use button 1 to drive menu selection.
1408 switch (item_type) {
1410 case FadeInHandleItem:
1411 case FadeInTrimHandleItem:
1412 case StartCrossFadeItem:
1413 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1417 case FadeOutHandleItem:
1418 case FadeOutTrimHandleItem:
1419 case EndCrossFadeItem:
1420 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1423 case LeftFrameHandle:
1424 case RightFrameHandle:
1428 popup_track_context_menu (1, event->button.time, item_type, false);
1432 case RegionViewNameHighlight:
1433 case RegionViewName:
1434 popup_track_context_menu (1, event->button.time, item_type, false);
1438 popup_track_context_menu (1, event->button.time, item_type, true);
1441 case AutomationTrackItem:
1442 popup_track_context_menu (1, event->button.time, item_type, false);
1446 case RangeMarkerBarItem:
1447 case TransportMarkerBarItem:
1448 case CdMarkerBarItem:
1450 case TempoCurveItem:
1453 case TimecodeRulerItem:
1454 case SamplesRulerItem:
1455 case MinsecRulerItem:
1457 popup_ruler_menu (where, item_type);
1461 marker_context_menu (&event->button, item);
1464 case TempoMarkerItem:
1465 tempo_or_meter_marker_context_menu (&event->button, item);
1468 case MeterMarkerItem:
1469 tempo_or_meter_marker_context_menu (&event->button, item);
1472 case CrossfadeViewItem:
1473 popup_track_context_menu (1, event->button.time, item_type, false);
1476 case ControlPointItem:
1477 popup_control_point_context_menu (item, event);
1481 if (internal_editing()) {
1482 popup_note_context_menu (item, event);
1494 /* delete events get handled here */
1496 Editing::MouseMode const eff = effective_mouse_mode ();
1498 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1500 switch (item_type) {
1501 case TempoMarkerItem:
1502 remove_tempo_marker (item);
1505 case MeterMarkerItem:
1506 remove_meter_marker (item);
1510 remove_marker (*item, event);
1514 if (eff == MouseObject) {
1515 remove_clicked_region ();
1519 case ControlPointItem:
1520 remove_control_point (item);
1524 remove_midi_note (item, event);
1533 switch (event->button.button) {
1536 switch (item_type) {
1537 /* see comments in button_press_handler */
1538 case PlayheadCursorItem:
1541 case AutomationLineItem:
1542 case StartSelectionTrimItem:
1543 case EndSelectionTrimItem:
1547 if (!_dragging_playhead) {
1548 snap_to_with_modifier (where, event, RoundNearest, true);
1549 mouse_add_new_marker (where);
1553 case CdMarkerBarItem:
1554 if (!_dragging_playhead) {
1555 // if we get here then a dragged range wasn't done
1556 snap_to_with_modifier (where, event, RoundNearest, true);
1557 mouse_add_new_marker (where, true);
1561 case TempoCurveItem:
1562 if (!_dragging_playhead) {
1563 snap_to_with_modifier (where, event);
1564 mouse_add_new_tempo_event (where);
1569 if (!_dragging_playhead) {
1570 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1575 case TimecodeRulerItem:
1576 case SamplesRulerItem:
1577 case MinsecRulerItem:
1588 switch (item_type) {
1591 /* check that we didn't drag before releasing, since
1592 its really annoying to create new control
1593 points when doing this.
1595 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1596 if (!were_dragging && arv) {
1597 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1598 arv->add_gain_point_event (item, event, with_guard_points);
1604 case AutomationTrackItem: {
1605 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1606 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1608 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1619 if (scrubbing_direction == 0) {
1620 /* no drag, just a click */
1621 switch (item_type) {
1623 play_selected_region ();
1628 } else if (_session) {
1629 /* make sure we stop */
1630 _session->request_transport_speed (0.0);
1639 /* do any (de)selection operations that should occur on button release */
1640 button_selection (item, event, item_type);
1650 switch (item_type) {
1652 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1654 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1657 // Button2 click is unused
1672 // x_style_paste (where, 1.0);
1693 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1696 ArdourMarker * marker;
1697 MeterMarker* m_marker = 0;
1698 TempoMarker* t_marker = 0;
1702 /* by the time we reach here, entered_regionview and entered trackview
1703 * will have already been set as appropriate. Things are done this
1704 * way because this method isn't passed a pointer to a variable type of
1705 * thing that is entered (which may or may not be canvas item).
1706 * (e.g. the actual entered regionview)
1709 choose_canvas_cursor_on_entry (item_type);
1711 switch (item_type) {
1712 case ControlPointItem:
1713 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1714 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1717 fraction = 1.0 - (cp->get_y() / cp->line().height());
1719 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1720 _verbose_cursor->show ();
1725 if (mouse_mode == MouseDraw) {
1726 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1728 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1733 case AutomationLineItem:
1734 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1735 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1737 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1742 case AutomationTrackItem:
1743 AutomationTimeAxisView* atv;
1744 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1745 clear_entered_track = false;
1746 set_entered_track (atv);
1751 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1754 entered_marker = marker;
1755 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1758 case MeterMarkerItem:
1759 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1762 entered_marker = m_marker;
1763 if (m_marker->meter().position_lock_style() == MusicTime) {
1764 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1766 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1770 case TempoMarkerItem:
1771 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1774 entered_marker = t_marker;
1775 if (t_marker->tempo().position_lock_style() == MusicTime) {
1776 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1778 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1782 case FadeInHandleItem:
1783 case FadeInTrimHandleItem:
1784 if (mouse_mode == MouseObject) {
1785 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1787 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1788 rect->set_fill_color (rv->get_fill_color());
1793 case FadeOutHandleItem:
1794 case FadeOutTrimHandleItem:
1795 if (mouse_mode == MouseObject) {
1796 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1798 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1799 rect->set_fill_color (rv->get_fill_color ());
1804 case FeatureLineItem:
1806 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1807 line->set_outline_color (0xFF0000FF);
1816 if (entered_regionview) {
1817 entered_regionview->entered();
1826 /* third pass to handle entered track status in a comprehensible way.
1829 switch (item_type) {
1831 case AutomationLineItem:
1832 case ControlPointItem:
1833 /* these do not affect the current entered track state */
1834 clear_entered_track = false;
1837 case AutomationTrackItem:
1838 /* handled above already */
1850 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1853 ArdourMarker *marker;
1854 TempoMarker *t_marker;
1855 MeterMarker *m_marker;
1860 if (!_enter_stack.empty()) {
1861 _enter_stack.pop_back();
1864 switch (item_type) {
1865 case ControlPointItem:
1866 _verbose_cursor->hide ();
1870 case AutomationLineItem:
1871 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1873 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1875 line->set_outline_color (al->get_line_color());
1881 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1885 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1886 location_flags_changed (loc);
1890 case MeterMarkerItem:
1891 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1895 if (m_marker->meter().position_lock_style() == MusicTime) {
1896 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1898 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1902 case TempoMarkerItem:
1903 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1907 if (t_marker->tempo().position_lock_style() == MusicTime) {
1908 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1910 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1914 case FadeInTrimHandleItem:
1915 case FadeOutTrimHandleItem:
1916 case FadeInHandleItem:
1917 case FadeOutHandleItem:
1919 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1921 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1926 case AutomationTrackItem:
1929 case FeatureLineItem:
1931 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1932 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1944 Editor::scrub (framepos_t frame, double current_x)
1948 if (scrubbing_direction == 0) {
1950 _session->request_locate (frame, false);
1951 _session->request_transport_speed (0.1);
1952 scrubbing_direction = 1;
1956 if (last_scrub_x > current_x) {
1958 /* pointer moved to the left */
1960 if (scrubbing_direction > 0) {
1962 /* we reversed direction to go backwards */
1965 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1969 /* still moving to the left (backwards) */
1971 scrub_reversals = 0;
1972 scrub_reverse_distance = 0;
1974 delta = 0.01 * (last_scrub_x - current_x);
1975 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1979 /* pointer moved to the right */
1981 if (scrubbing_direction < 0) {
1982 /* we reversed direction to go forward */
1985 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1988 /* still moving to the right */
1990 scrub_reversals = 0;
1991 scrub_reverse_distance = 0;
1993 delta = 0.01 * (current_x - last_scrub_x);
1994 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1998 /* if there have been more than 2 opposite motion moves detected, or one that moves
1999 back more than 10 pixels, reverse direction
2002 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
2004 if (scrubbing_direction > 0) {
2005 /* was forwards, go backwards */
2006 _session->request_transport_speed (-0.1);
2007 scrubbing_direction = -1;
2009 /* was backwards, go forwards */
2010 _session->request_transport_speed (0.1);
2011 scrubbing_direction = 1;
2014 scrub_reverse_distance = 0;
2015 scrub_reversals = 0;
2019 last_scrub_x = current_x;
2023 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2025 _last_motion_y = event->motion.y;
2027 if (event->motion.is_hint) {
2030 /* We call this so that MOTION_NOTIFY events continue to be
2031 delivered to the canvas. We need to do this because we set
2032 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2033 the density of the events, at the expense of a round-trip
2034 to the server. Given that this will mostly occur on cases
2035 where DISPLAY = :0.0, and given the cost of what the motion
2036 event might do, its a good tradeoff.
2039 _track_canvas->get_pointer (x, y);
2042 if (current_stepping_trackview) {
2043 /* don't keep the persistent stepped trackview if the mouse moves */
2044 current_stepping_trackview = 0;
2045 step_timeout.disconnect ();
2048 if (_session && _session->actively_recording()) {
2049 /* Sorry. no dragging stuff around while we record */
2053 update_join_object_range_location (event->motion.y);
2055 if (_drags->active ()) {
2056 return _drags->motion_handler (event, from_autoscroll);
2063 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2065 ControlPoint* control_point;
2067 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2068 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2069 abort(); /*NOTREACHED*/
2072 AutomationLine& line = control_point->line ();
2073 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2074 /* we shouldn't remove the first or last gain point in region gain lines */
2075 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2084 Editor::remove_control_point (ArdourCanvas::Item* item)
2086 if (!can_remove_control_point (item)) {
2090 ControlPoint* control_point;
2092 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2093 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2094 abort(); /*NOTREACHED*/
2097 control_point->line().remove_point (*control_point);
2101 Editor::edit_control_point (ArdourCanvas::Item* item)
2103 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2106 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2107 abort(); /*NOTREACHED*/
2110 ControlPointDialog d (p);
2112 if (d.run () != RESPONSE_ACCEPT) {
2116 p->line().modify_point_y (*p, d.get_y_fraction ());
2120 Editor::edit_notes (MidiRegionView* mrv)
2122 MidiRegionView::Selection const & s = mrv->selection();
2128 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2131 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2135 Editor::note_edit_done (int r, EditNoteDialog* d)
2137 begin_reversible_command (_("edit note(s)"));
2142 commit_reversible_command();
2146 Editor::visible_order_range (int* low, int* high) const
2148 *low = TimeAxisView::max_order ();
2151 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2153 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2155 if (rtv && !rtv->hidden()) {
2157 if (*high < rtv->order()) {
2158 *high = rtv->order ();
2161 if (*low > rtv->order()) {
2162 *low = rtv->order ();
2169 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2171 /* Either add to or set the set the region selection, unless
2172 this is an alignment click (control used)
2175 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2176 TimeAxisView* tv = &rv.get_time_axis_view();
2177 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2179 if (rtv && rtv->is_track()) {
2180 speed = rtv->track()->speed();
2183 framepos_t where = get_preferred_edit_position();
2187 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2189 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2191 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2193 align_region (rv.region(), End, (framepos_t) (where * speed));
2197 align_region (rv.region(), Start, (framepos_t) (where * speed));
2204 Editor::collect_new_region_view (RegionView* rv)
2206 latest_regionviews.push_back (rv);
2210 Editor::collect_and_select_new_region_view (RegionView* rv)
2213 latest_regionviews.push_back (rv);
2217 Editor::cancel_selection ()
2219 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2220 (*i)->hide_selection ();
2223 selection->clear ();
2224 clicked_selection = 0;
2228 Editor::cancel_time_selection ()
2230 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2231 (*i)->hide_selection ();
2233 selection->time.clear ();
2234 clicked_selection = 0;
2238 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2240 RegionView* rv = clicked_regionview;
2242 /* Choose action dependant on which button was pressed */
2243 switch (event->button.button) {
2245 begin_reversible_command (_("start point trim"));
2247 if (selection->selected (rv)) {
2248 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2249 i != selection->regions.by_layer().end(); ++i)
2251 if (!(*i)->region()->locked()) {
2252 (*i)->region()->clear_changes ();
2253 (*i)->region()->trim_front (new_bound);
2254 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2259 if (!rv->region()->locked()) {
2260 rv->region()->clear_changes ();
2261 rv->region()->trim_front (new_bound);
2262 _session->add_command(new StatefulDiffCommand (rv->region()));
2266 commit_reversible_command();
2270 begin_reversible_command (_("end point trim"));
2272 if (selection->selected (rv)) {
2274 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2276 if (!(*i)->region()->locked()) {
2277 (*i)->region()->clear_changes();
2278 (*i)->region()->trim_end (new_bound);
2279 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2285 if (!rv->region()->locked()) {
2286 rv->region()->clear_changes ();
2287 rv->region()->trim_end (new_bound);
2288 _session->add_command (new StatefulDiffCommand (rv->region()));
2292 commit_reversible_command();
2301 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2303 ArdourMarker* marker;
2306 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2307 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2308 abort(); /*NOTREACHED*/
2311 Location* location = find_location_from_marker (marker, is_start);
2312 location->set_hidden (true, this);
2316 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2318 using namespace Gtkmm2ext;
2320 ArdourPrompter prompter (false);
2322 prompter.set_prompt (_("Name for region:"));
2323 prompter.set_initial_text (clicked_regionview->region()->name());
2324 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2325 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2326 prompter.show_all ();
2327 switch (prompter.run ()) {
2328 case Gtk::RESPONSE_ACCEPT:
2330 prompter.get_result(str);
2332 clicked_regionview->region()->set_name (str);
2341 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2343 /* no brushing without a useful snap setting */
2345 switch (_snap_mode) {
2347 return; /* can't work because it allows region to be placed anywhere */
2352 switch (_snap_type) {
2360 /* don't brush a copy over the original */
2362 if (pos == rv->region()->position()) {
2366 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2368 if (!rtv || !rtv->is_track()) {
2372 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2373 double speed = rtv->track()->speed();
2375 playlist->clear_changes ();
2376 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2377 playlist->add_region (new_region, (framepos_t) (pos * speed));
2378 _session->add_command (new StatefulDiffCommand (playlist));
2380 // playlist is frozen, so we have to update manually XXX this is disgusting
2382 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2386 Editor::track_height_step_timeout ()
2388 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2389 current_stepping_trackview = 0;
2396 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2398 assert (region_view);
2400 if (!region_view->region()->playlist()) {
2404 switch (Config->get_edit_mode()) {
2406 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2409 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2412 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2419 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2421 assert (region_view);
2423 if (!region_view->region()->playlist()) {
2427 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2431 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2433 assert (region_view);
2435 if (!region_view->region()->playlist()) {
2439 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2443 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2446 /** Start a grab where a time range is selected, track(s) are selected, and the
2447 * user clicks and drags a region with a modifier in order to create a new region containing
2448 * the section of the clicked region that lies within the time range.
2451 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2453 if (clicked_regionview == 0) {
2457 /* lets try to create new Region for the selection */
2459 vector<boost::shared_ptr<Region> > new_regions;
2460 create_region_from_selection (new_regions);
2462 if (new_regions.empty()) {
2466 /* XXX fix me one day to use all new regions */
2468 boost::shared_ptr<Region> region (new_regions.front());
2470 /* add it to the current stream/playlist.
2472 tricky: the streamview for the track will add a new regionview. we will
2473 catch the signal it sends when it creates the regionview to
2474 set the regionview we want to then drag.
2477 latest_regionviews.clear();
2478 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2480 /* A selection grab currently creates two undo/redo operations, one for
2481 creating the new region and another for moving it.
2483 begin_reversible_command (Operations::selection_grab);
2485 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2487 playlist->clear_changes ();
2488 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2489 _session->add_command(new StatefulDiffCommand (playlist));
2493 if (latest_regionviews.empty()) {
2494 /* something went wrong */
2495 abort_reversible_command ();
2499 /* we need to deselect all other regionviews, and select this one
2500 i'm ignoring undo stuff, because the region creation will take care of it
2503 selection->set (latest_regionviews);
2505 commit_reversible_command ();
2507 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2513 if (_drags->active ()) {
2516 selection->clear ();
2519 ARDOUR_UI::instance()->reset_focus (&contents());
2522 /** Update _join_object_range_state which indicate whether we are over the top
2523 * or bottom half of a route view, used by the `join object/range' tool
2524 * mode. Coordinates in canvas space.
2527 Editor::update_join_object_range_location (double y)
2529 if (!get_smart_mode()) {
2530 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2534 JoinObjectRangeState const old = _join_object_range_state;
2536 if (mouse_mode == MouseObject) {
2537 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2538 } else if (mouse_mode == MouseRange) {
2539 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2542 if (entered_regionview) {
2544 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2545 double const c = item_space.y / entered_regionview->height();
2547 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2549 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2550 if (_join_object_range_state != old && ctx) {
2551 ctx->cursor_ctx->change(which_track_cursor());
2554 } else if (entered_track) {
2556 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2558 if (entered_route_view) {
2563 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2565 double track_height = entered_route_view->view()->child_height();
2566 if (UIConfiguration::instance().get_show_name_highlight()) {
2567 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2569 double const c = cy / track_height;
2573 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2575 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2579 /* Other kinds of tracks use object mode */
2580 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2583 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2584 if (_join_object_range_state != old && ctx) {
2585 ctx->cursor_ctx->change(which_track_cursor());
2591 Editor::effective_mouse_mode () const
2593 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2595 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2603 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2605 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2608 e->region_view().delete_note (e->note ());
2611 /** Obtain the pointer position in canvas coordinates */
2613 Editor::get_pointer_position (double& x, double& y) const
2616 _track_canvas->get_pointer (px, py);
2617 _track_canvas->window_to_canvas (px, py, x, y);