2 Copyright (C) 2000-2001 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 #include "pbd/error.h"
30 #include "pbd/enumwriter.h"
31 #include "pbd/memento_command.h"
32 #include "pbd/basename.h"
33 #include "pbd/stateful_diff_command.h"
35 #include "gtkmm2ext/bindings.h"
36 #include "gtkmm2ext/utils.h"
37 #include "gtkmm2ext/tearoff.h"
39 #include "canvas/canvas.h"
41 #include "ardour/audioregion.h"
42 #include "ardour/operations.h"
43 #include "ardour/playlist.h"
44 #include "ardour/profile.h"
45 #include "ardour/region_factory.h"
46 #include "ardour/route.h"
47 #include "ardour/session.h"
48 #include "ardour/types.h"
52 #include "time_axis_view.h"
53 #include "audio_time_axis.h"
54 #include "audio_region_view.h"
55 #include "midi_region_view.h"
57 #include "streamview.h"
58 #include "region_gain_line.h"
59 #include "automation_time_axis.h"
60 #include "control_point.h"
62 #include "selection.h"
65 #include "rgb_macros.h"
66 #include "control_point_dialog.h"
67 #include "editor_drag.h"
68 #include "automation_region_view.h"
69 #include "edit_note_dialog.h"
70 #include "mouse_cursors.h"
71 #include "editor_cursors.h"
72 #include "verbose_cursor.h"
78 using namespace ARDOUR;
81 using namespace Editing;
82 using Gtkmm2ext::Keyboard;
85 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
87 /* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only
88 pays attentions to subwindows. this means that menu windows are ignored, and
89 if the pointer is in a menu, the return window from the call will be the
90 the regular subwindow *under* the menu.
92 this matters quite a lot if the pointer is moving around in a menu that overlaps
93 the track canvas because we will believe that we are within the track canvas
94 when we are not. therefore, we track enter/leave events for the track canvas
95 and allow that to override the result of gdk_window_get_pointer().
98 if (!within_track_canvas) {
103 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->_track_canvas->get_window();
105 if (!canvas_window) {
109 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
111 if (!pointer_window) {
115 if (pointer_window != canvas_window) {
116 in_track_canvas = false;
120 in_track_canvas = true;
123 event.type = GDK_BUTTON_RELEASE;
127 where = window_event_sample (&event, 0, 0);
133 Editor::window_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
135 ArdourCanvas::Duple d;
137 if (!gdk_event_get_coords (event, &d.x, &d.y)) {
141 /* event coordinates are in window units, so convert to canvas
144 d = _track_canvas->window_to_canvas (d);
154 return pixel_to_sample (d.x);
158 Editor::canvas_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
163 /* event coordinates are already in canvas units */
165 if (!gdk_event_get_coords (event, &x, &y)) {
166 cerr << "!NO c COORDS for event type " << event->type << endl;
178 /* note that pixel_to_sample_from_event() never returns less than zero, so even if the pixel
179 position is negative (as can be the case with motion events in particular),
180 the frame location is always positive.
183 return pixel_to_sample_from_event (x);
187 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
189 boost::shared_ptr<Trimmable> st = _trimmable.lock();
191 if (!st || st == t) {
197 Editor::set_current_movable (boost::shared_ptr<Movable> m)
199 boost::shared_ptr<Movable> sm = _movable.lock();
201 if (!sm || sm != m) {
207 Editor::mouse_mode_object_range_toggled()
209 MouseMode m = mouse_mode;
211 Glib::RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
213 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
216 set_mouse_mode(m, true); //call this so the button styles can get updated
219 static Glib::RefPtr<Action>
220 get_mouse_mode_action(MouseMode m)
224 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
226 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
228 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
230 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
232 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
234 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-content"));
236 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
238 return Glib::RefPtr<Action>();
242 Editor::set_mouse_mode (MouseMode m, bool force)
244 if (_drags->active ()) {
248 if (!force && m == mouse_mode) {
252 if (ARDOUR::Profile->get_mixbus()) {
253 if ( m == MouseCut) m = MouseObject;
256 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
257 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
259 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
260 tact->set_active (false);
261 tact->set_active (true);
263 //NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting
267 Editor::mouse_mode_toggled (MouseMode m)
269 if (ARDOUR::Profile->get_mixbus()) {
270 if ( m == MouseCut) m = MouseObject;
273 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
274 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
276 if (!tact->get_active()) {
277 /* this was just the notification that the old mode has been
278 * left. we'll get called again with the new mode active in a
284 if (_session && mouse_mode == MouseAudition) {
285 /* stop transport and reset default speed to avoid oddness with
287 _session->request_transport_speed (0.0, true);
290 const bool was_internal = internal_editing();
294 /* Switch snap type/mode if we're moving to/from an internal tool. Note
295 this must toggle the actions and not call set_snap_*() directly,
296 otherwise things get out of sync and the combo box stops working. */
297 if (!was_internal && internal_editing()) {
298 snap_type_action(internal_snap_type)->set_active(true);
299 snap_mode_action(internal_snap_mode)->set_active(true);
300 } else if (was_internal && !internal_editing()) {
301 snap_type_action(pre_internal_snap_type)->set_active(true);
302 snap_mode_action(pre_internal_snap_mode)->set_active(true);
307 /* this should generate a new enter event which will
308 trigger the appropiate cursor.
312 _track_canvas->re_enter ();
315 set_gain_envelope_visibility ();
317 update_time_selection_display ();
319 update_all_enter_cursors ();
321 MouseModeChanged (); /* EMIT SIGNAL */
325 Editor::internal_editing() const
327 return mouse_mode == Editing::MouseContent || mouse_mode == Editing::MouseDraw;
331 Editor::update_time_selection_display ()
333 switch (mouse_mode) {
335 selection->clear_objects ();
336 selection->ClearMidiNoteSelection (); /* EMIT SIGNAL */
339 selection->clear_time ();
340 selection->clear_tracks ();
341 selection->ClearMidiNoteSelection (); /* EMIT SIGNAL */
344 /* Clear regions, but not time or tracks, since that
345 would destroy the range selection rectangle, which we need to stick
346 around for AutomationRangeDrag. */
347 selection->clear_regions ();
348 selection->clear_playlists ();
351 /* This handles internal edit.
352 Clear everything except points and notes.
354 selection->clear_regions();
355 selection->clear_lines();
356 selection->clear_playlists ();
358 selection->clear_time ();
359 selection->clear_tracks ();
363 /* We probably want to keep region selection */
364 selection->clear_points ();
365 selection->clear_lines();
366 selection->clear_playlists ();
368 selection->clear_time ();
369 selection->clear_tracks ();
373 /*Don't lose lines or points if no action in this mode */
374 selection->clear_regions ();
375 selection->clear_playlists ();
376 selection->clear_time ();
377 selection->clear_tracks ();
381 /*Clear everything */
382 selection->clear_objects();
383 selection->clear_time ();
384 selection->clear_tracks ();
390 Editor::step_mouse_mode (bool next)
392 const int n_mouse_modes = (int)MouseContent + 1;
393 int current = (int)current_mouse_mode();
395 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
397 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
402 Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
405 /* in object/audition/timefx/gain-automation mode,
406 any button press sets the selection if the object
407 can be selected. this is a bit of hack, because
408 we want to avoid this if the mouse operation is a
411 note: not dbl-click or triple-click
413 Also note that there is no region selection in internal edit mode, otherwise
414 for operations operating on the selection (e.g. cut) it is not obvious whether
415 to cut notes or regions.
418 MouseMode eff_mouse_mode = effective_mouse_mode ();
420 if (eff_mouse_mode == MouseCut) {
421 /* never change selection in cut mode */
425 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
426 /* context clicks are always about object properties, even if
427 we're in range mode within smart mode.
429 eff_mouse_mode = MouseObject;
432 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
433 if (get_smart_mode()) {
435 case FadeInHandleItem:
436 case FadeInTrimHandleItem:
437 case FadeOutHandleItem:
438 case FadeOutTrimHandleItem:
439 eff_mouse_mode = MouseObject;
446 if (((mouse_mode != MouseObject) &&
447 (mouse_mode != MouseAudition || item_type != RegionItem) &&
448 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
449 (mouse_mode != MouseDraw) &&
450 (mouse_mode != MouseContent || item_type == RegionItem)) ||
451 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
455 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
457 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
459 /* almost no selection action on modified button-2 or button-3 events */
461 if ((item_type != RegionItem && event->button.button != 2) && !(item_type == ControlPointItem && event->button.button == 3)) {
467 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
468 bool press = (event->type == GDK_BUTTON_PRESS);
471 _mouse_changed_selection = false;
476 if (eff_mouse_mode == MouseDraw) {
480 if (eff_mouse_mode != MouseRange) {
481 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
483 /* don't change the selection unless the
484 clicked track is not currently selected. if
485 so, "collapse" the selection to just this
488 if (!selection->selected (clicked_axisview)) {
489 set_selected_track_as_side_effect (Selection::Set);
493 if (eff_mouse_mode != MouseRange) {
494 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
499 case RegionViewNameHighlight:
501 case LeftFrameHandle:
502 case RightFrameHandle:
503 case FadeInHandleItem:
504 case FadeInTrimHandleItem:
506 case FadeOutHandleItem:
507 case FadeOutTrimHandleItem:
509 case StartCrossFadeItem:
510 case EndCrossFadeItem:
511 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
512 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
513 } else if (event->type == GDK_BUTTON_PRESS) {
514 set_selected_track_as_side_effect (op);
518 case ControlPointItem:
519 /* for object/track exclusivity, we don't call set_selected_track_as_side_effect (op); */
521 if (eff_mouse_mode != MouseRange) {
522 if (event->button.button != 3) {
523 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
525 _mouse_changed_selection |= set_selected_control_point_from_click (press, Selection::Set);
531 if (eff_mouse_mode != MouseRange) {
532 AutomationLine* argl = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
534 std::list<Selectable*> selectables;
535 uint32_t before, after;
536 framecnt_t const where = (framecnt_t) floor (event->button.x * samples_per_pixel) - clicked_regionview->region ()->position ();
538 if (!argl || !argl->control_points_adjacent (where, before, after)) {
542 selectables.push_back (argl->nth (before));
543 selectables.push_back (argl->nth (after));
548 selection->set (selectables);
549 _mouse_changed_selection = true;
554 selection->add (selectables);
555 _mouse_changed_selection = true;
558 case Selection::Toggle:
560 selection->toggle (selectables);
561 _mouse_changed_selection = true;
565 case Selection::Extend:
572 case AutomationLineItem:
573 if (eff_mouse_mode != MouseRange) {
574 AutomationLine* al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
575 std::list<Selectable*> selectables;
576 uint32_t before, after;
577 framecnt_t const where = (framecnt_t) floor (event->button.x * samples_per_pixel);
579 if (!al || !al->control_points_adjacent (where, before, after)) {
583 selectables.push_back (al->nth (before));
584 selectables.push_back (al->nth (after));
589 selection->set (selectables);
590 _mouse_changed_selection = true;
595 selection->add (selectables);
596 _mouse_changed_selection = true;
599 case Selection::Toggle:
601 selection->toggle (selectables);
602 _mouse_changed_selection = true;
606 case Selection::Extend:
614 /* for context click, select track */
615 if (event->button.button == 3) {
616 selection->clear_tracks ();
617 set_selected_track_as_side_effect (op);
619 /* We won't get a release.*/
620 begin_reversible_selection_op (X_("Button 3 Menu Select"));
621 commit_reversible_selection_op ();
625 case AutomationTrackItem:
626 if (eff_mouse_mode != MouseDraw && op == Selection::Set) {
627 set_selected_track_as_side_effect (op);
635 if ((!press) && _mouse_changed_selection) {
636 begin_reversible_selection_op (X_("Button Selection"));
637 commit_reversible_selection_op ();
638 _mouse_changed_selection = false;
643 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
645 /* single mouse clicks on any of these item types operate
646 independent of mouse mode, mostly because they are
647 not on the main track canvas or because we want
651 NoteBase* note = NULL;
654 case PlayheadCursorItem:
655 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
659 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
660 hide_marker (item, event);
662 _drags->set (new MarkerDrag (this, item), event);
666 case TempoMarkerItem:
669 new TempoMarkerDrag (
672 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
679 case MeterMarkerItem:
682 new MeterMarkerDrag (
685 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
693 _drags->set (new VideoTimeLineDrag (this, item), event);
700 case TimecodeRulerItem:
701 case SamplesRulerItem:
702 case MinsecRulerItem:
704 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
705 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
711 case RangeMarkerBarItem:
712 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
713 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
714 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
715 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
717 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
722 case CdMarkerBarItem:
723 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
724 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
726 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
731 case TransportMarkerBarItem:
732 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
733 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
735 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
744 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
745 /* special case: allow trim of range selections in joined object mode;
746 in theory eff should equal MouseRange in this case, but it doesn't
747 because entering the range selection canvas item results in entered_regionview
748 being set to 0, so update_join_object_range_location acts as if we aren't
751 if (item_type == StartSelectionTrimItem) {
752 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
753 } else if (item_type == EndSelectionTrimItem) {
754 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
758 Editing::MouseMode eff = effective_mouse_mode ();
760 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
761 if (get_smart_mode()) {
763 case FadeInHandleItem:
764 case FadeInTrimHandleItem:
765 case FadeOutHandleItem:
766 case FadeOutTrimHandleItem:
777 case StartSelectionTrimItem:
778 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
781 case EndSelectionTrimItem:
782 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
786 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
787 start_selection_grab (item, event);
789 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
790 /* grab selection for moving */
791 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
793 /* this was debated, but decided the more common action was to
794 make a new selection */
795 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
800 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
801 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
803 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
808 case RegionViewNameHighlight:
809 if (!clicked_regionview->region()->locked()) {
810 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
816 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
817 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
819 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
828 case FadeInHandleItem:
829 case FadeOutHandleItem:
830 case LeftFrameHandle:
831 case RightFrameHandle:
832 case FeatureLineItem:
833 case RegionViewNameHighlight:
836 case AutomationTrackItem:
837 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
848 /* Existing note: allow trimming/motion */
849 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
850 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
851 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
853 _drags->set (new NoteDrag (this, item), event);
859 _drags->set (new LineDrag (this, item), event);
863 case ControlPointItem:
864 _drags->set (new ControlPointDrag (this, item), event);
868 case AutomationLineItem:
869 _drags->set (new LineDrag (this, item), event);
874 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
877 case AutomationTrackItem:
878 /* rubberband drag to select automation points */
879 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
884 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
885 /* rubberband drag to select automation points */
886 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
897 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
898 event->type == GDK_BUTTON_PRESS) {
900 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
902 } else if (event->type == GDK_BUTTON_PRESS) {
905 case FadeInHandleItem:
907 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
911 case FadeOutHandleItem:
913 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
917 case StartCrossFadeItem:
918 case EndCrossFadeItem:
919 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
920 // if (!clicked_regionview->region()->locked()) {
921 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
926 case FeatureLineItem:
928 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
929 remove_transient(item);
933 _drags->set (new FeatureLineDrag (this, item), event);
939 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
940 /* click on an automation region view; do nothing here and let the ARV's signal handler
946 /* click on a normal region view */
947 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
948 add_region_copy_drag (item, event, clicked_regionview);
949 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
950 add_region_brush_drag (item, event, clicked_regionview);
952 add_region_drag (item, event, clicked_regionview);
956 _drags->start_grab (event);
960 case RegionViewNameHighlight:
961 case LeftFrameHandle:
962 case RightFrameHandle:
963 if (!clicked_regionview->region()->locked()) {
964 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
969 case FadeInTrimHandleItem:
970 case FadeOutTrimHandleItem:
971 if (!clicked_regionview->region()->locked()) {
972 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
979 /* rename happens on edit clicks */
980 if (clicked_regionview->get_name_highlight()) {
981 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
987 case ControlPointItem:
988 _drags->set (new ControlPointDrag (this, item), event);
992 case AutomationLineItem:
993 _drags->set (new LineDrag (this, item), event);
998 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1001 case AutomationTrackItem:
1003 TimeAxisView* parent = clicked_axisview->get_parent ();
1004 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1006 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1008 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1010 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1011 if (pl->n_regions() == 0) {
1012 /* Parent has no regions; create one so that we have somewhere to put automation */
1013 _drags->set (new RegionCreateDrag (this, item, parent), event);
1015 /* See if there's a region before the click that we can extend, and extend it if so */
1016 framepos_t const t = canvas_event_sample (event);
1017 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1019 _drags->set (new RegionCreateDrag (this, item, parent), event);
1021 prev->set_length (t - prev->position ());
1025 /* rubberband drag to select automation points */
1026 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1048 switch (item_type) {
1050 _drags->set (new LineDrag (this, item), event);
1053 case ControlPointItem:
1054 _drags->set (new ControlPointDrag (this, item), event);
1060 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
1061 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1062 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
1063 event, _cursors->up_down);
1065 double const y = event->button.y;
1066 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
1068 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1070 /* smart "join" mode: drag automation */
1071 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1079 case AutomationLineItem:
1080 _drags->set (new LineDrag (this, item), event);
1084 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1085 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1086 /* Note is big and pointer is near the end, trim */
1087 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1090 _drags->set (new NoteDrag (this, item), event);
1097 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1098 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1109 if (item_type == NoteItem) {
1110 /* resize-drag notes */
1111 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1112 if (note->big_enough_to_trim()) {
1113 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1117 } else if (clicked_regionview) {
1119 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1125 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1126 scrub_reversals = 0;
1127 scrub_reverse_distance = 0;
1128 last_scrub_x = event->button.x;
1129 scrubbing_direction = 0;
1141 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1143 Editing::MouseMode const eff = effective_mouse_mode ();
1146 switch (item_type) {
1148 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1149 add_region_copy_drag (item, event, clicked_regionview);
1151 add_region_drag (item, event, clicked_regionview);
1153 _drags->start_grab (event);
1156 case ControlPointItem:
1157 _drags->set (new ControlPointDrag (this, item), event);
1165 switch (item_type) {
1166 case RegionViewNameHighlight:
1167 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1171 case LeftFrameHandle:
1172 case RightFrameHandle:
1173 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1177 case RegionViewName:
1178 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1192 /* relax till release */
1204 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1206 if (event->type == GDK_2BUTTON_PRESS) {
1207 _drags->mark_double_click ();
1208 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1212 if (event->type != GDK_BUTTON_PRESS) {
1216 _track_canvas->grab_focus();
1218 if (_session && _session->actively_recording()) {
1222 button_selection (item, event, item_type);
1224 if (!_drags->active () &&
1225 (Keyboard::is_delete_event (&event->button) ||
1226 Keyboard::is_context_menu_event (&event->button) ||
1227 Keyboard::is_edit_event (&event->button))) {
1229 /* handled by button release */
1233 //not rolling, range mode click + join_play_range : locate the PH here
1234 if ( !_drags->active () && _session && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && UIConfiguration::instance().get_follow_edits() && !_session->config.get_external_sync() ) {
1235 framepos_t where = canvas_event_sample (event);
1237 _session->request_locate (where, false);
1240 switch (event->button.button) {
1242 return button_press_handler_1 (item, event, item_type);
1246 return button_press_handler_2 (item, event, item_type);
1253 return button_press_dispatch (&event->button);
1262 Editor::button_press_dispatch (GdkEventButton* ev)
1264 /* this function is intended only for buttons 4 and above.
1267 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1268 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1272 Editor::button_release_dispatch (GdkEventButton* ev)
1274 /* this function is intended only for buttons 4 and above.
1277 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1278 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1282 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1284 framepos_t where = canvas_event_sample (event);
1285 AutomationTimeAxisView* atv = 0;
1287 _press_cursor_ctx.reset();
1289 /* no action if we're recording */
1291 if (_session && _session->actively_recording()) {
1295 bool were_dragging = false;
1297 if (!Keyboard::is_context_menu_event (&event->button)) {
1299 /* see if we're finishing a drag */
1301 if (_drags->active ()) {
1302 bool const r = _drags->end_grab (event);
1304 /* grab dragged, so do nothing else */
1308 were_dragging = true;
1311 update_region_layering_order_editor ();
1314 /* edit events get handled here */
1316 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1317 switch (item_type) {
1319 show_region_properties ();
1322 case TempoMarkerItem: {
1323 ArdourMarker* marker;
1324 TempoMarker* tempo_marker;
1326 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1327 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1328 abort(); /*NOTREACHED*/
1331 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1332 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1333 abort(); /*NOTREACHED*/
1336 edit_tempo_marker (*tempo_marker);
1340 case MeterMarkerItem: {
1341 ArdourMarker* marker;
1342 MeterMarker* meter_marker;
1344 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1345 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1346 abort(); /*NOTREACHED*/
1349 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1350 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1351 abort(); /*NOTREACHED*/
1353 edit_meter_marker (*meter_marker);
1357 case RegionViewName:
1358 if (clicked_regionview->name_active()) {
1359 return mouse_rename_region (item, event);
1363 case ControlPointItem:
1364 edit_control_point (item);
1373 /* context menu events get handled here */
1374 if (Keyboard::is_context_menu_event (&event->button)) {
1376 context_click_event = *event;
1378 if (!_drags->active ()) {
1380 /* no matter which button pops up the context menu, tell the menu
1381 widget to use button 1 to drive menu selection.
1384 switch (item_type) {
1386 case FadeInHandleItem:
1387 case FadeInTrimHandleItem:
1388 case StartCrossFadeItem:
1389 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1393 case FadeOutHandleItem:
1394 case FadeOutTrimHandleItem:
1395 case EndCrossFadeItem:
1396 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1399 case LeftFrameHandle:
1400 case RightFrameHandle:
1404 popup_track_context_menu (1, event->button.time, item_type, false);
1408 case RegionViewNameHighlight:
1409 case RegionViewName:
1410 popup_track_context_menu (1, event->button.time, item_type, false);
1414 popup_track_context_menu (1, event->button.time, item_type, true);
1417 case AutomationTrackItem:
1418 popup_track_context_menu (1, event->button.time, item_type, false);
1422 case RangeMarkerBarItem:
1423 case TransportMarkerBarItem:
1424 case CdMarkerBarItem:
1428 case TimecodeRulerItem:
1429 case SamplesRulerItem:
1430 case MinsecRulerItem:
1432 popup_ruler_menu (where, item_type);
1436 marker_context_menu (&event->button, item);
1439 case TempoMarkerItem:
1440 tempo_or_meter_marker_context_menu (&event->button, item);
1443 case MeterMarkerItem:
1444 tempo_or_meter_marker_context_menu (&event->button, item);
1447 case CrossfadeViewItem:
1448 popup_track_context_menu (1, event->button.time, item_type, false);
1451 case ControlPointItem:
1452 popup_control_point_context_menu (item, event);
1456 if (internal_editing()) {
1457 popup_note_context_menu (item, event);
1469 /* delete events get handled here */
1471 Editing::MouseMode const eff = effective_mouse_mode ();
1473 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1475 switch (item_type) {
1476 case TempoMarkerItem:
1477 remove_tempo_marker (item);
1480 case MeterMarkerItem:
1481 remove_meter_marker (item);
1485 remove_marker (*item, event);
1489 if (eff == MouseObject) {
1490 remove_clicked_region ();
1494 case ControlPointItem:
1495 remove_control_point (item);
1499 remove_midi_note (item, event);
1508 switch (event->button.button) {
1511 switch (item_type) {
1512 /* see comments in button_press_handler */
1513 case PlayheadCursorItem:
1516 case AutomationLineItem:
1517 case StartSelectionTrimItem:
1518 case EndSelectionTrimItem:
1522 if (!_dragging_playhead) {
1523 snap_to_with_modifier (where, event, RoundNearest, true);
1524 mouse_add_new_marker (where);
1528 case CdMarkerBarItem:
1529 if (!_dragging_playhead) {
1530 // if we get here then a dragged range wasn't done
1531 snap_to_with_modifier (where, event, RoundNearest, true);
1532 mouse_add_new_marker (where, true);
1537 if (!_dragging_playhead) {
1538 snap_to_with_modifier (where, event);
1539 mouse_add_new_tempo_event (where);
1544 if (!_dragging_playhead) {
1545 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1550 case TimecodeRulerItem:
1551 case SamplesRulerItem:
1552 case MinsecRulerItem:
1563 switch (item_type) {
1566 /* check that we didn't drag before releasing, since
1567 its really annoying to create new control
1568 points when doing this.
1570 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1571 if (!were_dragging && arv) {
1572 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1573 arv->add_gain_point_event (item, event, with_guard_points);
1579 case AutomationTrackItem: {
1580 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1581 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1583 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1594 if (scrubbing_direction == 0) {
1595 /* no drag, just a click */
1596 switch (item_type) {
1598 play_selected_region ();
1603 } else if (_session) {
1604 /* make sure we stop */
1605 _session->request_transport_speed (0.0);
1614 /* do any (de)selection operations that should occur on button release */
1615 button_selection (item, event, item_type);
1625 switch (item_type) {
1627 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1629 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1632 // Button2 click is unused
1647 // x_style_paste (where, 1.0);
1668 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1671 ArdourMarker * marker;
1675 /* by the time we reach here, entered_regionview and entered trackview
1676 * will have already been set as appropriate. Things are done this
1677 * way because this method isn't passed a pointer to a variable type of
1678 * thing that is entered (which may or may not be canvas item).
1679 * (e.g. the actual entered regionview)
1682 choose_canvas_cursor_on_entry (item_type);
1684 switch (item_type) {
1685 case ControlPointItem:
1686 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1687 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1690 fraction = 1.0 - (cp->get_y() / cp->line().height());
1692 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1693 _verbose_cursor->show ();
1698 if (mouse_mode == MouseDraw) {
1699 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1701 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1706 case AutomationLineItem:
1707 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1708 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1710 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1715 case AutomationTrackItem:
1716 AutomationTimeAxisView* atv;
1717 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1718 clear_entered_track = false;
1719 set_entered_track (atv);
1724 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1727 entered_marker = marker;
1728 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1730 case MeterMarkerItem:
1731 case TempoMarkerItem:
1734 case FadeInHandleItem:
1735 case FadeInTrimHandleItem:
1736 if (mouse_mode == MouseObject) {
1737 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1739 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1740 rect->set_fill_color (rv->get_fill_color());
1745 case FadeOutHandleItem:
1746 case FadeOutTrimHandleItem:
1747 if (mouse_mode == MouseObject) {
1748 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1750 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1751 rect->set_fill_color (rv->get_fill_color ());
1756 case FeatureLineItem:
1758 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1759 line->set_outline_color (0xFF0000FF);
1768 if (entered_regionview) {
1769 entered_regionview->entered();
1778 /* third pass to handle entered track status in a comprehensible way.
1781 switch (item_type) {
1783 case AutomationLineItem:
1784 case ControlPointItem:
1785 /* these do not affect the current entered track state */
1786 clear_entered_track = false;
1789 case AutomationTrackItem:
1790 /* handled above already */
1802 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1805 ArdourMarker *marker;
1810 if (!_enter_stack.empty()) {
1811 _enter_stack.pop_back();
1814 switch (item_type) {
1815 case ControlPointItem:
1816 _verbose_cursor->hide ();
1820 case AutomationLineItem:
1821 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1823 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1825 line->set_outline_color (al->get_line_color());
1831 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1835 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1836 location_flags_changed (loc);
1839 case MeterMarkerItem:
1840 case TempoMarkerItem:
1843 case FadeInTrimHandleItem:
1844 case FadeOutTrimHandleItem:
1845 case FadeInHandleItem:
1846 case FadeOutHandleItem:
1848 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1850 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1855 case AutomationTrackItem:
1858 case FeatureLineItem:
1860 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1861 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1873 Editor::scrub (framepos_t frame, double current_x)
1877 if (scrubbing_direction == 0) {
1879 _session->request_locate (frame, false);
1880 _session->request_transport_speed (0.1);
1881 scrubbing_direction = 1;
1885 if (last_scrub_x > current_x) {
1887 /* pointer moved to the left */
1889 if (scrubbing_direction > 0) {
1891 /* we reversed direction to go backwards */
1894 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1898 /* still moving to the left (backwards) */
1900 scrub_reversals = 0;
1901 scrub_reverse_distance = 0;
1903 delta = 0.01 * (last_scrub_x - current_x);
1904 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1908 /* pointer moved to the right */
1910 if (scrubbing_direction < 0) {
1911 /* we reversed direction to go forward */
1914 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1917 /* still moving to the right */
1919 scrub_reversals = 0;
1920 scrub_reverse_distance = 0;
1922 delta = 0.01 * (current_x - last_scrub_x);
1923 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1927 /* if there have been more than 2 opposite motion moves detected, or one that moves
1928 back more than 10 pixels, reverse direction
1931 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1933 if (scrubbing_direction > 0) {
1934 /* was forwards, go backwards */
1935 _session->request_transport_speed (-0.1);
1936 scrubbing_direction = -1;
1938 /* was backwards, go forwards */
1939 _session->request_transport_speed (0.1);
1940 scrubbing_direction = 1;
1943 scrub_reverse_distance = 0;
1944 scrub_reversals = 0;
1948 last_scrub_x = current_x;
1952 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1954 _last_motion_y = event->motion.y;
1956 if (event->motion.is_hint) {
1959 /* We call this so that MOTION_NOTIFY events continue to be
1960 delivered to the canvas. We need to do this because we set
1961 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1962 the density of the events, at the expense of a round-trip
1963 to the server. Given that this will mostly occur on cases
1964 where DISPLAY = :0.0, and given the cost of what the motion
1965 event might do, its a good tradeoff.
1968 _track_canvas->get_pointer (x, y);
1971 if (current_stepping_trackview) {
1972 /* don't keep the persistent stepped trackview if the mouse moves */
1973 current_stepping_trackview = 0;
1974 step_timeout.disconnect ();
1977 if (_session && _session->actively_recording()) {
1978 /* Sorry. no dragging stuff around while we record */
1982 update_join_object_range_location (event->motion.y);
1984 if (_drags->active ()) {
1985 return _drags->motion_handler (event, from_autoscroll);
1992 Editor::can_remove_control_point (ArdourCanvas::Item* item)
1994 ControlPoint* control_point;
1996 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1997 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1998 abort(); /*NOTREACHED*/
2001 AutomationLine& line = control_point->line ();
2002 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2003 /* we shouldn't remove the first or last gain point in region gain lines */
2004 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2013 Editor::remove_control_point (ArdourCanvas::Item* item)
2015 if (!can_remove_control_point (item)) {
2019 ControlPoint* control_point;
2021 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2022 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2023 abort(); /*NOTREACHED*/
2026 control_point->line().remove_point (*control_point);
2030 Editor::edit_control_point (ArdourCanvas::Item* item)
2032 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2035 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2036 abort(); /*NOTREACHED*/
2039 ControlPointDialog d (p);
2041 if (d.run () != RESPONSE_ACCEPT) {
2045 p->line().modify_point_y (*p, d.get_y_fraction ());
2049 Editor::edit_notes (MidiRegionView* mrv)
2051 MidiRegionView::Selection const & s = mrv->selection();
2057 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2060 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2064 Editor::note_edit_done (int r, EditNoteDialog* d)
2071 Editor::visible_order_range (int* low, int* high) const
2073 *low = TimeAxisView::max_order ();
2076 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2078 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2080 if (!rtv->hidden()) {
2082 if (*high < rtv->order()) {
2083 *high = rtv->order ();
2086 if (*low > rtv->order()) {
2087 *low = rtv->order ();
2094 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2096 /* Either add to or set the set the region selection, unless
2097 this is an alignment click (control used)
2100 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2101 TimeAxisView* tv = &rv.get_time_axis_view();
2102 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2104 if (rtv && rtv->is_track()) {
2105 speed = rtv->track()->speed();
2108 framepos_t where = get_preferred_edit_position();
2112 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2114 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2116 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2118 align_region (rv.region(), End, (framepos_t) (where * speed));
2122 align_region (rv.region(), Start, (framepos_t) (where * speed));
2129 Editor::collect_new_region_view (RegionView* rv)
2131 latest_regionviews.push_back (rv);
2135 Editor::collect_and_select_new_region_view (RegionView* rv)
2138 latest_regionviews.push_back (rv);
2142 Editor::cancel_selection ()
2144 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2145 (*i)->hide_selection ();
2148 selection->clear ();
2149 clicked_selection = 0;
2153 Editor::cancel_time_selection ()
2155 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2156 (*i)->hide_selection ();
2158 selection->time.clear ();
2159 clicked_selection = 0;
2163 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2165 RegionView* rv = clicked_regionview;
2167 /* Choose action dependant on which button was pressed */
2168 switch (event->button.button) {
2170 begin_reversible_command (_("start point trim"));
2172 if (selection->selected (rv)) {
2173 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2174 i != selection->regions.by_layer().end(); ++i)
2176 if (!(*i)->region()->locked()) {
2177 (*i)->region()->clear_changes ();
2178 (*i)->region()->trim_front (new_bound);
2179 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2184 if (!rv->region()->locked()) {
2185 rv->region()->clear_changes ();
2186 rv->region()->trim_front (new_bound);
2187 _session->add_command(new StatefulDiffCommand (rv->region()));
2191 commit_reversible_command();
2195 begin_reversible_command (_("end point trim"));
2197 if (selection->selected (rv)) {
2199 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2201 if (!(*i)->region()->locked()) {
2202 (*i)->region()->clear_changes();
2203 (*i)->region()->trim_end (new_bound);
2204 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2210 if (!rv->region()->locked()) {
2211 rv->region()->clear_changes ();
2212 rv->region()->trim_end (new_bound);
2213 _session->add_command (new StatefulDiffCommand (rv->region()));
2217 commit_reversible_command();
2226 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2228 ArdourMarker* marker;
2231 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2232 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2233 abort(); /*NOTREACHED*/
2236 Location* location = find_location_from_marker (marker, is_start);
2237 location->set_hidden (true, this);
2241 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2243 using namespace Gtkmm2ext;
2245 ArdourPrompter prompter (false);
2247 prompter.set_prompt (_("Name for region:"));
2248 prompter.set_initial_text (clicked_regionview->region()->name());
2249 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2250 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2251 prompter.show_all ();
2252 switch (prompter.run ()) {
2253 case Gtk::RESPONSE_ACCEPT:
2255 prompter.get_result(str);
2257 clicked_regionview->region()->set_name (str);
2266 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2268 /* no brushing without a useful snap setting */
2270 switch (_snap_mode) {
2272 return; /* can't work because it allows region to be placed anywhere */
2277 switch (_snap_type) {
2285 /* don't brush a copy over the original */
2287 if (pos == rv->region()->position()) {
2291 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2293 if (rtv == 0 || !rtv->is_track()) {
2297 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2298 double speed = rtv->track()->speed();
2300 playlist->clear_changes ();
2301 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2302 playlist->add_region (new_region, (framepos_t) (pos * speed));
2303 _session->add_command (new StatefulDiffCommand (playlist));
2305 // playlist is frozen, so we have to update manually XXX this is disgusting
2307 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2311 Editor::track_height_step_timeout ()
2313 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2314 current_stepping_trackview = 0;
2321 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2323 assert (region_view);
2325 if (!region_view->region()->playlist()) {
2329 switch (Config->get_edit_mode()) {
2331 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2334 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2337 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2344 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2346 assert (region_view);
2348 if (!region_view->region()->playlist()) {
2352 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2356 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2358 assert (region_view);
2360 if (!region_view->region()->playlist()) {
2364 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2368 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2371 /** Start a grab where a time range is selected, track(s) are selected, and the
2372 * user clicks and drags a region with a modifier in order to create a new region containing
2373 * the section of the clicked region that lies within the time range.
2376 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2378 if (clicked_regionview == 0) {
2382 /* lets try to create new Region for the selection */
2384 vector<boost::shared_ptr<Region> > new_regions;
2385 create_region_from_selection (new_regions);
2387 if (new_regions.empty()) {
2391 /* XXX fix me one day to use all new regions */
2393 boost::shared_ptr<Region> region (new_regions.front());
2395 /* add it to the current stream/playlist.
2397 tricky: the streamview for the track will add a new regionview. we will
2398 catch the signal it sends when it creates the regionview to
2399 set the regionview we want to then drag.
2402 latest_regionviews.clear();
2403 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2405 /* A selection grab currently creates two undo/redo operations, one for
2406 creating the new region and another for moving it.
2408 begin_reversible_command (Operations::selection_grab);
2410 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2412 playlist->clear_changes ();
2413 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2414 _session->add_command(new StatefulDiffCommand (playlist));
2418 if (latest_regionviews.empty()) {
2419 /* something went wrong */
2420 abort_reversible_command ();
2424 /* we need to deselect all other regionviews, and select this one
2425 i'm ignoring undo stuff, because the region creation will take care of it
2428 selection->set (latest_regionviews);
2430 commit_reversible_command ();
2432 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2438 if (_drags->active ()) {
2441 selection->clear ();
2447 /** Update _join_object_range_state which indicate whether we are over the top
2448 * or bottom half of a route view, used by the `join object/range' tool
2449 * mode. Coordinates in canvas space.
2452 Editor::update_join_object_range_location (double y)
2454 if (!get_smart_mode()) {
2455 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2459 JoinObjectRangeState const old = _join_object_range_state;
2461 if (mouse_mode == MouseObject) {
2462 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2463 } else if (mouse_mode == MouseRange) {
2464 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2467 if (entered_regionview) {
2469 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2470 double const c = item_space.y / entered_regionview->height();
2472 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2474 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2475 if (_join_object_range_state != old && ctx) {
2476 ctx->cursor_ctx->change(which_track_cursor());
2479 } else if (entered_track) {
2481 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2483 if (entered_route_view) {
2488 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2490 double track_height = entered_route_view->view()->child_height();
2491 if (UIConfiguration::instance().get_show_name_highlight()) {
2492 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2494 double const c = cy / track_height;
2498 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2500 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2504 /* Other kinds of tracks use object mode */
2505 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2508 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2509 if (_join_object_range_state != old && ctx) {
2510 ctx->cursor_ctx->change(which_track_cursor());
2516 Editor::effective_mouse_mode () const
2518 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2520 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2528 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2530 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2533 e->region_view().delete_note (e->note ());
2536 /** Obtain the pointer position in canvas coordinates */
2538 Editor::get_pointer_position (double& x, double& y) const
2541 _track_canvas->get_pointer (px, py);
2542 _track_canvas->window_to_canvas (px, py, x, y);