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) {
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 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
527 if (eff_mouse_mode != MouseRange) {
528 AutomationLine* argl = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
530 std::list<Selectable*> selectables;
531 uint32_t before, after;
532 framecnt_t const where = (framecnt_t) floor (event->button.x * samples_per_pixel) - clicked_regionview->region ()->position ();
534 if (!argl || !argl->control_points_adjacent (where, before, after)) {
538 selectables.push_back (argl->nth (before));
539 selectables.push_back (argl->nth (after));
544 selection->set (selectables);
545 _mouse_changed_selection = true;
550 selection->add (selectables);
551 _mouse_changed_selection = true;
554 case Selection::Toggle:
556 selection->toggle (selectables);
557 _mouse_changed_selection = true;
561 case Selection::Extend:
568 case AutomationLineItem:
569 if (eff_mouse_mode != MouseRange) {
570 AutomationLine* al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
571 std::list<Selectable*> selectables;
572 uint32_t before, after;
573 framecnt_t const where = (framecnt_t) floor (event->button.x * samples_per_pixel);
575 if (!al || !al->control_points_adjacent (where, before, after)) {
579 selectables.push_back (al->nth (before));
580 selectables.push_back (al->nth (after));
585 selection->set (selectables);
586 _mouse_changed_selection = true;
591 selection->add (selectables);
592 _mouse_changed_selection = true;
595 case Selection::Toggle:
597 selection->toggle (selectables);
598 _mouse_changed_selection = true;
602 case Selection::Extend:
610 /* for context click, select track */
611 if (event->button.button == 3) {
612 selection->clear_tracks ();
613 set_selected_track_as_side_effect (op);
615 /* We won't get a release.*/
616 begin_reversible_selection_op (X_("Button 3 Menu Select"));
617 commit_reversible_selection_op ();
621 case AutomationTrackItem:
622 if (eff_mouse_mode != MouseDraw) {
623 set_selected_track_as_side_effect (op);
631 if ((!press) && _mouse_changed_selection) {
632 begin_reversible_selection_op (X_("Button Selection"));
633 commit_reversible_selection_op ();
634 _mouse_changed_selection = false;
639 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
641 /* single mouse clicks on any of these item types operate
642 independent of mouse mode, mostly because they are
643 not on the main track canvas or because we want
647 NoteBase* note = NULL;
650 case PlayheadCursorItem:
651 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
655 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
656 hide_marker (item, event);
658 _drags->set (new MarkerDrag (this, item), event);
662 case TempoMarkerItem:
665 new TempoMarkerDrag (
668 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
675 case MeterMarkerItem:
678 new MeterMarkerDrag (
681 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
689 _drags->set (new VideoTimeLineDrag (this, item), event);
696 case TimecodeRulerItem:
697 case SamplesRulerItem:
698 case MinsecRulerItem:
700 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
701 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
707 case RangeMarkerBarItem:
708 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
709 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
710 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
711 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
713 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
718 case CdMarkerBarItem:
719 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
720 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
722 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
727 case TransportMarkerBarItem:
728 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
729 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
731 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
740 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
741 /* special case: allow trim of range selections in joined object mode;
742 in theory eff should equal MouseRange in this case, but it doesn't
743 because entering the range selection canvas item results in entered_regionview
744 being set to 0, so update_join_object_range_location acts as if we aren't
747 if (item_type == StartSelectionTrimItem) {
748 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
749 } else if (item_type == EndSelectionTrimItem) {
750 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
754 Editing::MouseMode eff = effective_mouse_mode ();
756 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
757 if (get_smart_mode()) {
759 case FadeInHandleItem:
760 case FadeInTrimHandleItem:
761 case FadeOutHandleItem:
762 case FadeOutTrimHandleItem:
773 case StartSelectionTrimItem:
774 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
777 case EndSelectionTrimItem:
778 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
782 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
783 start_selection_grab (item, event);
785 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
786 /* grab selection for moving */
787 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
789 /* this was debated, but decided the more common action was to
790 make a new selection */
791 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
796 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
797 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
799 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
804 case RegionViewNameHighlight:
805 if (!clicked_regionview->region()->locked()) {
806 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
812 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
813 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
815 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
824 case FadeInHandleItem:
825 case FadeOutHandleItem:
826 case LeftFrameHandle:
827 case RightFrameHandle:
828 case FeatureLineItem:
829 case RegionViewNameHighlight:
832 case AutomationTrackItem:
833 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
844 /* Existing note: allow trimming/motion */
845 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
846 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
847 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
849 _drags->set (new NoteDrag (this, item), event);
855 _drags->set (new LineDrag (this, item), event);
859 case ControlPointItem:
860 _drags->set (new ControlPointDrag (this, item), event);
864 case AutomationLineItem:
865 _drags->set (new LineDrag (this, item), event);
870 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
873 case AutomationTrackItem:
874 /* rubberband drag to select automation points */
875 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
880 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
881 /* rubberband drag to select automation points */
882 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
893 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
894 event->type == GDK_BUTTON_PRESS) {
896 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
898 } else if (event->type == GDK_BUTTON_PRESS) {
901 case FadeInHandleItem:
903 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
907 case FadeOutHandleItem:
909 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
913 case StartCrossFadeItem:
914 case EndCrossFadeItem:
915 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
916 // if (!clicked_regionview->region()->locked()) {
917 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
922 case FeatureLineItem:
924 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
925 remove_transient(item);
929 _drags->set (new FeatureLineDrag (this, item), event);
935 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
936 /* click on an automation region view; do nothing here and let the ARV's signal handler
942 /* click on a normal region view */
943 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
944 add_region_copy_drag (item, event, clicked_regionview);
945 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
946 add_region_brush_drag (item, event, clicked_regionview);
948 add_region_drag (item, event, clicked_regionview);
952 _drags->start_grab (event);
956 case RegionViewNameHighlight:
957 case LeftFrameHandle:
958 case RightFrameHandle:
959 if (!clicked_regionview->region()->locked()) {
960 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
965 case FadeInTrimHandleItem:
966 case FadeOutTrimHandleItem:
967 if (!clicked_regionview->region()->locked()) {
968 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
975 /* rename happens on edit clicks */
976 if (clicked_regionview->get_name_highlight()) {
977 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
983 case ControlPointItem:
984 _drags->set (new ControlPointDrag (this, item), event);
988 case AutomationLineItem:
989 _drags->set (new LineDrag (this, item), event);
994 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
997 case AutomationTrackItem:
999 TimeAxisView* parent = clicked_axisview->get_parent ();
1000 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1002 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1004 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1006 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1007 if (pl->n_regions() == 0) {
1008 /* Parent has no regions; create one so that we have somewhere to put automation */
1009 _drags->set (new RegionCreateDrag (this, item, parent), event);
1011 /* See if there's a region before the click that we can extend, and extend it if so */
1012 framepos_t const t = canvas_event_sample (event);
1013 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1015 _drags->set (new RegionCreateDrag (this, item, parent), event);
1017 prev->set_length (t - prev->position ());
1021 /* rubberband drag to select automation points */
1022 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1044 switch (item_type) {
1046 _drags->set (new LineDrag (this, item), event);
1049 case ControlPointItem:
1050 _drags->set (new ControlPointDrag (this, item), event);
1056 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
1057 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1058 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
1059 event, _cursors->up_down);
1061 double const y = event->button.y;
1062 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
1064 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1066 /* smart "join" mode: drag automation */
1067 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1075 case AutomationLineItem:
1076 _drags->set (new LineDrag (this, item), event);
1080 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1081 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1082 /* Note is big and pointer is near the end, trim */
1083 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1086 _drags->set (new NoteDrag (this, item), event);
1093 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1094 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1105 if (item_type == NoteItem) {
1106 /* resize-drag notes */
1107 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1108 if (note->big_enough_to_trim()) {
1109 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1113 } else if (clicked_regionview) {
1115 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1121 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1122 scrub_reversals = 0;
1123 scrub_reverse_distance = 0;
1124 last_scrub_x = event->button.x;
1125 scrubbing_direction = 0;
1137 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1139 Editing::MouseMode const eff = effective_mouse_mode ();
1142 switch (item_type) {
1144 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1145 add_region_copy_drag (item, event, clicked_regionview);
1147 add_region_drag (item, event, clicked_regionview);
1149 _drags->start_grab (event);
1152 case ControlPointItem:
1153 _drags->set (new ControlPointDrag (this, item), event);
1161 switch (item_type) {
1162 case RegionViewNameHighlight:
1163 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1167 case LeftFrameHandle:
1168 case RightFrameHandle:
1169 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1173 case RegionViewName:
1174 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1188 /* relax till release */
1200 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1202 if (event->type == GDK_2BUTTON_PRESS) {
1203 _drags->mark_double_click ();
1204 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1208 if (event->type != GDK_BUTTON_PRESS) {
1212 _track_canvas->grab_focus();
1214 if (_session && _session->actively_recording()) {
1218 button_selection (item, event, item_type);
1220 if (!_drags->active () &&
1221 (Keyboard::is_delete_event (&event->button) ||
1222 Keyboard::is_context_menu_event (&event->button) ||
1223 Keyboard::is_edit_event (&event->button))) {
1225 /* handled by button release */
1229 //not rolling, range mode click + join_play_range : locate the PH here
1230 if ( !_drags->active () && _session && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && UIConfiguration::instance().get_follow_edits() && !_session->config.get_external_sync() ) {
1231 framepos_t where = canvas_event_sample (event);
1233 _session->request_locate (where, false);
1236 switch (event->button.button) {
1238 return button_press_handler_1 (item, event, item_type);
1242 return button_press_handler_2 (item, event, item_type);
1249 return button_press_dispatch (&event->button);
1258 Editor::button_press_dispatch (GdkEventButton* ev)
1260 /* this function is intended only for buttons 4 and above.
1263 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1264 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1268 Editor::button_release_dispatch (GdkEventButton* ev)
1270 /* this function is intended only for buttons 4 and above.
1273 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1274 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1278 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1280 framepos_t where = canvas_event_sample (event);
1281 AutomationTimeAxisView* atv = 0;
1283 _press_cursor_ctx.reset();
1285 /* no action if we're recording */
1287 if (_session && _session->actively_recording()) {
1291 bool were_dragging = false;
1293 if (!Keyboard::is_context_menu_event (&event->button)) {
1295 /* see if we're finishing a drag */
1297 if (_drags->active ()) {
1298 bool const r = _drags->end_grab (event);
1300 /* grab dragged, so do nothing else */
1304 were_dragging = true;
1307 update_region_layering_order_editor ();
1310 /* edit events get handled here */
1312 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1313 switch (item_type) {
1315 show_region_properties ();
1318 case TempoMarkerItem: {
1319 ArdourMarker* marker;
1320 TempoMarker* tempo_marker;
1322 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1323 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1324 abort(); /*NOTREACHED*/
1327 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1328 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1329 abort(); /*NOTREACHED*/
1332 edit_tempo_marker (*tempo_marker);
1336 case MeterMarkerItem: {
1337 ArdourMarker* marker;
1338 MeterMarker* meter_marker;
1340 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1341 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1342 abort(); /*NOTREACHED*/
1345 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1346 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1347 abort(); /*NOTREACHED*/
1349 edit_meter_marker (*meter_marker);
1353 case RegionViewName:
1354 if (clicked_regionview->name_active()) {
1355 return mouse_rename_region (item, event);
1359 case ControlPointItem:
1360 edit_control_point (item);
1369 /* context menu events get handled here */
1370 if (Keyboard::is_context_menu_event (&event->button)) {
1372 context_click_event = *event;
1374 if (!_drags->active ()) {
1376 /* no matter which button pops up the context menu, tell the menu
1377 widget to use button 1 to drive menu selection.
1380 switch (item_type) {
1382 case FadeInHandleItem:
1383 case FadeInTrimHandleItem:
1384 case StartCrossFadeItem:
1385 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1389 case FadeOutHandleItem:
1390 case FadeOutTrimHandleItem:
1391 case EndCrossFadeItem:
1392 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1395 case LeftFrameHandle:
1396 case RightFrameHandle:
1400 popup_track_context_menu (1, event->button.time, item_type, false);
1404 case RegionViewNameHighlight:
1405 case RegionViewName:
1406 popup_track_context_menu (1, event->button.time, item_type, false);
1410 popup_track_context_menu (1, event->button.time, item_type, true);
1413 case AutomationTrackItem:
1414 popup_track_context_menu (1, event->button.time, item_type, false);
1418 case RangeMarkerBarItem:
1419 case TransportMarkerBarItem:
1420 case CdMarkerBarItem:
1424 case TimecodeRulerItem:
1425 case SamplesRulerItem:
1426 case MinsecRulerItem:
1428 popup_ruler_menu (where, item_type);
1432 marker_context_menu (&event->button, item);
1435 case TempoMarkerItem:
1436 tempo_or_meter_marker_context_menu (&event->button, item);
1439 case MeterMarkerItem:
1440 tempo_or_meter_marker_context_menu (&event->button, item);
1443 case CrossfadeViewItem:
1444 popup_track_context_menu (1, event->button.time, item_type, false);
1447 case ControlPointItem:
1448 popup_control_point_context_menu (item, event);
1452 if (internal_editing()) {
1453 popup_note_context_menu (item, event);
1465 /* delete events get handled here */
1467 Editing::MouseMode const eff = effective_mouse_mode ();
1469 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1471 switch (item_type) {
1472 case TempoMarkerItem:
1473 remove_tempo_marker (item);
1476 case MeterMarkerItem:
1477 remove_meter_marker (item);
1481 remove_marker (*item, event);
1485 if (eff == MouseObject) {
1486 remove_clicked_region ();
1490 case ControlPointItem:
1491 remove_control_point (item);
1495 remove_midi_note (item, event);
1504 switch (event->button.button) {
1507 switch (item_type) {
1508 /* see comments in button_press_handler */
1509 case PlayheadCursorItem:
1512 case AutomationLineItem:
1513 case StartSelectionTrimItem:
1514 case EndSelectionTrimItem:
1518 if (!_dragging_playhead) {
1519 snap_to_with_modifier (where, event, RoundNearest, true);
1520 mouse_add_new_marker (where);
1524 case CdMarkerBarItem:
1525 if (!_dragging_playhead) {
1526 // if we get here then a dragged range wasn't done
1527 snap_to_with_modifier (where, event, RoundNearest, true);
1528 mouse_add_new_marker (where, true);
1533 if (!_dragging_playhead) {
1534 snap_to_with_modifier (where, event);
1535 mouse_add_new_tempo_event (where);
1540 if (!_dragging_playhead) {
1541 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1546 case TimecodeRulerItem:
1547 case SamplesRulerItem:
1548 case MinsecRulerItem:
1559 switch (item_type) {
1562 /* check that we didn't drag before releasing, since
1563 its really annoying to create new control
1564 points when doing this.
1566 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1567 if (!were_dragging && arv) {
1568 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1569 arv->add_gain_point_event (item, event, with_guard_points);
1575 case AutomationTrackItem: {
1576 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1577 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1579 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1590 if (scrubbing_direction == 0) {
1591 /* no drag, just a click */
1592 switch (item_type) {
1594 play_selected_region ();
1599 } else if (_session) {
1600 /* make sure we stop */
1601 _session->request_transport_speed (0.0);
1610 /* do any (de)selection operations that should occur on button release */
1611 button_selection (item, event, item_type);
1621 switch (item_type) {
1623 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1625 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1628 // Button2 click is unused
1643 // x_style_paste (where, 1.0);
1664 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1667 ArdourMarker * marker;
1671 /* by the time we reach here, entered_regionview and entered trackview
1672 * will have already been set as appropriate. Things are done this
1673 * way because this method isn't passed a pointer to a variable type of
1674 * thing that is entered (which may or may not be canvas item).
1675 * (e.g. the actual entered regionview)
1678 choose_canvas_cursor_on_entry (item_type);
1680 switch (item_type) {
1681 case ControlPointItem:
1682 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1683 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1686 fraction = 1.0 - (cp->get_y() / cp->line().height());
1688 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1689 _verbose_cursor->show ();
1694 if (mouse_mode == MouseDraw) {
1695 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1697 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1702 case AutomationLineItem:
1703 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1704 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1706 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1711 case AutomationTrackItem:
1712 AutomationTimeAxisView* atv;
1713 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1714 clear_entered_track = false;
1715 set_entered_track (atv);
1720 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1723 entered_marker = marker;
1724 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1726 case MeterMarkerItem:
1727 case TempoMarkerItem:
1730 case FadeInHandleItem:
1731 case FadeInTrimHandleItem:
1732 if (mouse_mode == MouseObject) {
1733 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1735 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1736 rect->set_fill_color (rv->get_fill_color());
1741 case FadeOutHandleItem:
1742 case FadeOutTrimHandleItem:
1743 if (mouse_mode == MouseObject) {
1744 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1746 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1747 rect->set_fill_color (rv->get_fill_color ());
1752 case FeatureLineItem:
1754 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1755 line->set_outline_color (0xFF0000FF);
1764 if (entered_regionview) {
1765 entered_regionview->entered();
1774 /* third pass to handle entered track status in a comprehensible way.
1777 switch (item_type) {
1779 case AutomationLineItem:
1780 case ControlPointItem:
1781 /* these do not affect the current entered track state */
1782 clear_entered_track = false;
1785 case AutomationTrackItem:
1786 /* handled above already */
1798 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1801 ArdourMarker *marker;
1806 if (!_enter_stack.empty()) {
1807 _enter_stack.pop_back();
1810 switch (item_type) {
1811 case ControlPointItem:
1812 _verbose_cursor->hide ();
1816 case AutomationLineItem:
1817 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1819 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1821 line->set_outline_color (al->get_line_color());
1827 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1831 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1832 location_flags_changed (loc);
1835 case MeterMarkerItem:
1836 case TempoMarkerItem:
1839 case FadeInTrimHandleItem:
1840 case FadeOutTrimHandleItem:
1841 case FadeInHandleItem:
1842 case FadeOutHandleItem:
1844 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1846 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1851 case AutomationTrackItem:
1854 case FeatureLineItem:
1856 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1857 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1869 Editor::scrub (framepos_t frame, double current_x)
1873 if (scrubbing_direction == 0) {
1875 _session->request_locate (frame, false);
1876 _session->request_transport_speed (0.1);
1877 scrubbing_direction = 1;
1881 if (last_scrub_x > current_x) {
1883 /* pointer moved to the left */
1885 if (scrubbing_direction > 0) {
1887 /* we reversed direction to go backwards */
1890 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1894 /* still moving to the left (backwards) */
1896 scrub_reversals = 0;
1897 scrub_reverse_distance = 0;
1899 delta = 0.01 * (last_scrub_x - current_x);
1900 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1904 /* pointer moved to the right */
1906 if (scrubbing_direction < 0) {
1907 /* we reversed direction to go forward */
1910 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1913 /* still moving to the right */
1915 scrub_reversals = 0;
1916 scrub_reverse_distance = 0;
1918 delta = 0.01 * (current_x - last_scrub_x);
1919 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1923 /* if there have been more than 2 opposite motion moves detected, or one that moves
1924 back more than 10 pixels, reverse direction
1927 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1929 if (scrubbing_direction > 0) {
1930 /* was forwards, go backwards */
1931 _session->request_transport_speed (-0.1);
1932 scrubbing_direction = -1;
1934 /* was backwards, go forwards */
1935 _session->request_transport_speed (0.1);
1936 scrubbing_direction = 1;
1939 scrub_reverse_distance = 0;
1940 scrub_reversals = 0;
1944 last_scrub_x = current_x;
1948 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1950 _last_motion_y = event->motion.y;
1952 if (event->motion.is_hint) {
1955 /* We call this so that MOTION_NOTIFY events continue to be
1956 delivered to the canvas. We need to do this because we set
1957 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1958 the density of the events, at the expense of a round-trip
1959 to the server. Given that this will mostly occur on cases
1960 where DISPLAY = :0.0, and given the cost of what the motion
1961 event might do, its a good tradeoff.
1964 _track_canvas->get_pointer (x, y);
1967 if (current_stepping_trackview) {
1968 /* don't keep the persistent stepped trackview if the mouse moves */
1969 current_stepping_trackview = 0;
1970 step_timeout.disconnect ();
1973 if (_session && _session->actively_recording()) {
1974 /* Sorry. no dragging stuff around while we record */
1978 update_join_object_range_location (event->motion.y);
1980 if (_drags->active ()) {
1981 return _drags->motion_handler (event, from_autoscroll);
1988 Editor::can_remove_control_point (ArdourCanvas::Item* item)
1990 ControlPoint* control_point;
1992 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1993 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1994 abort(); /*NOTREACHED*/
1997 AutomationLine& line = control_point->line ();
1998 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
1999 /* we shouldn't remove the first or last gain point in region gain lines */
2000 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2009 Editor::remove_control_point (ArdourCanvas::Item* item)
2011 if (!can_remove_control_point (item)) {
2015 ControlPoint* control_point;
2017 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2018 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2019 abort(); /*NOTREACHED*/
2022 control_point->line().remove_point (*control_point);
2026 Editor::edit_control_point (ArdourCanvas::Item* item)
2028 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2031 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2032 abort(); /*NOTREACHED*/
2035 ControlPointDialog d (p);
2037 if (d.run () != RESPONSE_ACCEPT) {
2041 p->line().modify_point_y (*p, d.get_y_fraction ());
2045 Editor::edit_notes (MidiRegionView* mrv)
2047 MidiRegionView::Selection const & s = mrv->selection();
2053 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2056 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2060 Editor::note_edit_done (int r, EditNoteDialog* d)
2067 Editor::visible_order_range (int* low, int* high) const
2069 *low = TimeAxisView::max_order ();
2072 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2074 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2076 if (!rtv->hidden()) {
2078 if (*high < rtv->order()) {
2079 *high = rtv->order ();
2082 if (*low > rtv->order()) {
2083 *low = rtv->order ();
2090 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2092 /* Either add to or set the set the region selection, unless
2093 this is an alignment click (control used)
2096 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2097 TimeAxisView* tv = &rv.get_time_axis_view();
2098 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2100 if (rtv && rtv->is_track()) {
2101 speed = rtv->track()->speed();
2104 framepos_t where = get_preferred_edit_position();
2108 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2110 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2112 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2114 align_region (rv.region(), End, (framepos_t) (where * speed));
2118 align_region (rv.region(), Start, (framepos_t) (where * speed));
2125 Editor::collect_new_region_view (RegionView* rv)
2127 latest_regionviews.push_back (rv);
2131 Editor::collect_and_select_new_region_view (RegionView* rv)
2134 latest_regionviews.push_back (rv);
2138 Editor::cancel_selection ()
2140 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2141 (*i)->hide_selection ();
2144 selection->clear ();
2145 clicked_selection = 0;
2149 Editor::cancel_time_selection ()
2151 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2152 (*i)->hide_selection ();
2154 selection->time.clear ();
2155 clicked_selection = 0;
2159 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2161 RegionView* rv = clicked_regionview;
2163 /* Choose action dependant on which button was pressed */
2164 switch (event->button.button) {
2166 begin_reversible_command (_("start point trim"));
2168 if (selection->selected (rv)) {
2169 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2170 i != selection->regions.by_layer().end(); ++i)
2172 if (!(*i)->region()->locked()) {
2173 (*i)->region()->clear_changes ();
2174 (*i)->region()->trim_front (new_bound);
2175 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2180 if (!rv->region()->locked()) {
2181 rv->region()->clear_changes ();
2182 rv->region()->trim_front (new_bound);
2183 _session->add_command(new StatefulDiffCommand (rv->region()));
2187 commit_reversible_command();
2191 begin_reversible_command (_("End point trim"));
2193 if (selection->selected (rv)) {
2195 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2197 if (!(*i)->region()->locked()) {
2198 (*i)->region()->clear_changes();
2199 (*i)->region()->trim_end (new_bound);
2200 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2206 if (!rv->region()->locked()) {
2207 rv->region()->clear_changes ();
2208 rv->region()->trim_end (new_bound);
2209 _session->add_command (new StatefulDiffCommand (rv->region()));
2213 commit_reversible_command();
2222 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2224 ArdourMarker* marker;
2227 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2228 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2229 abort(); /*NOTREACHED*/
2232 Location* location = find_location_from_marker (marker, is_start);
2233 location->set_hidden (true, this);
2237 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2239 using namespace Gtkmm2ext;
2241 ArdourPrompter prompter (false);
2243 prompter.set_prompt (_("Name for region:"));
2244 prompter.set_initial_text (clicked_regionview->region()->name());
2245 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2246 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2247 prompter.show_all ();
2248 switch (prompter.run ()) {
2249 case Gtk::RESPONSE_ACCEPT:
2251 prompter.get_result(str);
2253 clicked_regionview->region()->set_name (str);
2262 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2264 /* no brushing without a useful snap setting */
2266 switch (_snap_mode) {
2268 return; /* can't work because it allows region to be placed anywhere */
2273 switch (_snap_type) {
2281 /* don't brush a copy over the original */
2283 if (pos == rv->region()->position()) {
2287 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2289 if (rtv == 0 || !rtv->is_track()) {
2293 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2294 double speed = rtv->track()->speed();
2296 playlist->clear_changes ();
2297 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2298 playlist->add_region (new_region, (framepos_t) (pos * speed));
2299 _session->add_command (new StatefulDiffCommand (playlist));
2301 // playlist is frozen, so we have to update manually XXX this is disgusting
2303 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2307 Editor::track_height_step_timeout ()
2309 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2310 current_stepping_trackview = 0;
2317 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2319 assert (region_view);
2321 if (!region_view->region()->playlist()) {
2325 switch (Config->get_edit_mode()) {
2327 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2330 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2333 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2340 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2342 assert (region_view);
2344 if (!region_view->region()->playlist()) {
2348 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2352 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2354 assert (region_view);
2356 if (!region_view->region()->playlist()) {
2360 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2364 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2367 /** Start a grab where a time range is selected, track(s) are selected, and the
2368 * user clicks and drags a region with a modifier in order to create a new region containing
2369 * the section of the clicked region that lies within the time range.
2372 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2374 if (clicked_regionview == 0) {
2378 /* lets try to create new Region for the selection */
2380 vector<boost::shared_ptr<Region> > new_regions;
2381 create_region_from_selection (new_regions);
2383 if (new_regions.empty()) {
2387 /* XXX fix me one day to use all new regions */
2389 boost::shared_ptr<Region> region (new_regions.front());
2391 /* add it to the current stream/playlist.
2393 tricky: the streamview for the track will add a new regionview. we will
2394 catch the signal it sends when it creates the regionview to
2395 set the regionview we want to then drag.
2398 latest_regionviews.clear();
2399 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2401 /* A selection grab currently creates two undo/redo operations, one for
2402 creating the new region and another for moving it.
2404 begin_reversible_command (Operations::selection_grab);
2406 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2408 playlist->clear_changes ();
2409 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2410 _session->add_command(new StatefulDiffCommand (playlist));
2414 if (latest_regionviews.empty()) {
2415 /* something went wrong */
2416 abort_reversible_command ();
2420 /* we need to deselect all other regionviews, and select this one
2421 i'm ignoring undo stuff, because the region creation will take care of it
2424 selection->set (latest_regionviews);
2426 commit_reversible_command ();
2428 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2434 if (_drags->active ()) {
2437 selection->clear ();
2443 /** Update _join_object_range_state which indicate whether we are over the top
2444 * or bottom half of a route view, used by the `join object/range' tool
2445 * mode. Coordinates in canvas space.
2448 Editor::update_join_object_range_location (double y)
2450 if (!get_smart_mode()) {
2451 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2455 JoinObjectRangeState const old = _join_object_range_state;
2457 if (mouse_mode == MouseObject) {
2458 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2459 } else if (mouse_mode == MouseRange) {
2460 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2463 if (entered_regionview) {
2465 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2466 double const c = item_space.y / entered_regionview->height();
2468 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2470 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2471 if (_join_object_range_state != old && ctx) {
2472 ctx->cursor_ctx->change(which_track_cursor());
2475 } else if (entered_track) {
2477 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2479 if (entered_route_view) {
2484 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2486 double track_height = entered_route_view->view()->child_height();
2487 if (UIConfiguration::instance().get_show_name_highlight()) {
2488 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2490 double const c = cy / track_height;
2494 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2496 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2500 /* Other kinds of tracks use object mode */
2501 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2504 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2505 if (_join_object_range_state != old && ctx) {
2506 ctx->cursor_ctx->change(which_track_cursor());
2512 Editor::effective_mouse_mode () const
2514 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2516 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2524 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2526 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2529 e->region_view().delete_note (e->note ());
2532 /** Obtain the pointer position in canvas coordinates */
2534 Editor::get_pointer_position (double& x, double& y) const
2537 _track_canvas->get_pointer (px, py);
2538 _track_canvas->window_to_canvas (px, py, x, y);