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"
50 #include "ardour_ui.h"
53 #include "time_axis_view.h"
54 #include "audio_time_axis.h"
55 #include "audio_region_view.h"
56 #include "midi_region_view.h"
58 #include "streamview.h"
59 #include "region_gain_line.h"
60 #include "automation_time_axis.h"
61 #include "control_point.h"
63 #include "selection.h"
66 #include "rgb_macros.h"
67 #include "control_point_dialog.h"
68 #include "editor_drag.h"
69 #include "automation_region_view.h"
70 #include "edit_note_dialog.h"
71 #include "mouse_cursors.h"
72 #include "editor_cursors.h"
73 #include "verbose_cursor.h"
79 using namespace ARDOUR;
82 using namespace Editing;
83 using Gtkmm2ext::Keyboard;
86 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
88 /* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only
89 pays attentions to subwindows. this means that menu windows are ignored, and
90 if the pointer is in a menu, the return window from the call will be the
91 the regular subwindow *under* the menu.
93 this matters quite a lot if the pointer is moving around in a menu that overlaps
94 the track canvas because we will believe that we are within the track canvas
95 when we are not. therefore, we track enter/leave events for the track canvas
96 and allow that to override the result of gdk_window_get_pointer().
99 if (!within_track_canvas) {
104 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->_track_canvas->get_window();
106 if (!canvas_window) {
110 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
112 if (!pointer_window) {
116 if (pointer_window != canvas_window) {
117 in_track_canvas = false;
121 in_track_canvas = true;
124 event.type = GDK_BUTTON_RELEASE;
128 where = window_event_sample (&event, 0, 0);
134 Editor::window_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
136 ArdourCanvas::Duple d;
138 if (!gdk_event_get_coords (event, &d.x, &d.y)) {
142 /* event coordinates are in window units, so convert to canvas
145 d = _track_canvas->window_to_canvas (d);
155 return pixel_to_sample (d.x);
159 Editor::canvas_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
164 /* event coordinates are already in canvas units */
166 if (!gdk_event_get_coords (event, &x, &y)) {
167 cerr << "!NO c COORDS for event type " << event->type << endl;
179 /* note that pixel_to_sample_from_event() never returns less than zero, so even if the pixel
180 position is negative (as can be the case with motion events in particular),
181 the frame location is always positive.
184 return pixel_to_sample_from_event (x);
188 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
190 boost::shared_ptr<Trimmable> st = _trimmable.lock();
192 if (!st || st == t) {
198 Editor::set_current_movable (boost::shared_ptr<Movable> m)
200 boost::shared_ptr<Movable> sm = _movable.lock();
202 if (!sm || sm != m) {
208 Editor::mouse_mode_object_range_toggled()
210 MouseMode m = mouse_mode;
212 Glib::RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
214 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
217 if (tact->get_active()) {
218 m = MouseObject; //Smart mode turned to ON, force editing to Object mode
221 set_mouse_mode(m, true); //call this so the button styles can get updated
225 Editor::set_mouse_mode (MouseMode m, bool force)
227 if (_drags->active ()) {
231 if (!force && m == mouse_mode) {
235 if (ARDOUR::Profile->get_mixbus()) {
236 if ( m == MouseCut) m = MouseObject;
239 Glib::RefPtr<Action> act;
243 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
247 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
251 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
255 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
259 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
263 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
269 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
272 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
273 tact->set_active (false);
274 tact->set_active (true);
276 //NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting
280 Editor::mouse_mode_toggled (MouseMode m)
282 Glib::RefPtr<Action> act;
283 Glib::RefPtr<ToggleAction> tact;
285 if (ARDOUR::Profile->get_mixbus()) {
286 if ( m == MouseCut) m = MouseObject;
291 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
295 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
299 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
303 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
307 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
311 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
317 tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
320 if (!tact->get_active()) {
321 /* this was just the notification that the old mode has been
322 * left. we'll get called again with the new mode active in a
328 if (_session && mouse_mode == MouseAudition) {
329 /* stop transport and reset default speed to avoid oddness with
331 _session->request_transport_speed (0.0, true);
338 /* this should generate a new enter event which will
339 trigger the appropiate cursor.
343 _track_canvas->re_enter ();
346 set_gain_envelope_visibility ();
348 update_time_selection_display ();
350 MouseModeChanged (); /* EMIT SIGNAL */
354 Editor::update_time_selection_display ()
356 if (smart_mode_action->get_active()) {
357 /* not sure what to do here */
358 if (mouse_mode == MouseObject) {
362 switch (mouse_mode) {
364 selection->clear_objects ();
367 selection->clear_time ();
374 Editor::step_mouse_mode (bool next)
376 const int n_mouse_modes = (int)MouseDraw + 1;
377 int current = (int)current_mouse_mode();
379 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
381 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
386 Editor::toggle_internal_editing_from_double_click (GdkEvent* event)
388 if (_drags->active()) {
389 _drags->end_grab (event);
392 ActionManager::do_action ("MouseMode", "toggle-internal-edit");
394 /* prevent reversion of edit cursor on button release */
396 pre_press_cursor = 0;
402 Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemType item_type)
404 /* in object/audition/timefx/gain-automation mode,
405 any button press sets the selection if the object
406 can be selected. this is a bit of hack, because
407 we want to avoid this if the mouse operation is a
410 note: not dbl-click or triple-click
412 Also note that there is no region selection in internal edit mode, otherwise
413 for operations operating on the selection (e.g. cut) it is not obvious whether
414 to cut notes or regions.
417 MouseMode eff_mouse_mode = effective_mouse_mode ();
419 if (eff_mouse_mode == MouseCut) {
420 /* never change selection in cut mode */
424 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
425 /* context clicks are always about object properties, even if
426 we're in range mode within smart mode.
428 eff_mouse_mode = MouseObject;
431 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
432 if (get_smart_mode()) {
434 case FadeInHandleItem:
435 case FadeInTrimHandleItem:
436 case FadeOutHandleItem:
437 case FadeOutTrimHandleItem:
438 eff_mouse_mode = MouseObject;
445 if (((mouse_mode != MouseObject) &&
446 (mouse_mode != MouseAudition || item_type != RegionItem) &&
447 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
448 (mouse_mode != MouseDraw)) ||
449 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3) ||
450 (internal_editing() && mouse_mode != MouseTimeFX)) {
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);
473 if (eff_mouse_mode != MouseRange) {
474 set_selected_regionview_from_click (press, op);
476 /* don't change the selection unless the
477 clicked track is not currently selected. if
478 so, "collapse" the selection to just this
481 if (!selection->selected (clicked_axisview)) {
482 set_selected_track_as_side_effect (Selection::Set);
486 if (eff_mouse_mode != MouseRange) {
487 set_selected_regionview_from_click (press, op);
492 case RegionViewNameHighlight:
494 case LeftFrameHandle:
495 case RightFrameHandle:
496 case FadeInHandleItem:
497 case FadeInTrimHandleItem:
499 case FadeOutHandleItem:
500 case FadeOutTrimHandleItem:
502 case StartCrossFadeItem:
503 case EndCrossFadeItem:
504 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
505 set_selected_regionview_from_click (press, op);
506 } else if (event->type == GDK_BUTTON_PRESS) {
507 set_selected_track_as_side_effect (op);
511 case ControlPointItem:
512 set_selected_track_as_side_effect (op);
513 if (eff_mouse_mode != MouseRange) {
514 set_selected_control_point_from_click (press, op);
519 /* for context click, select track */
520 if (event->button.button == 3) {
521 selection->clear_tracks ();
522 set_selected_track_as_side_effect (op);
526 case AutomationTrackItem:
527 set_selected_track_as_side_effect (op);
536 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
538 /* single mouse clicks on any of these item types operate
539 independent of mouse mode, mostly because they are
540 not on the main track canvas or because we want
545 case PlayheadCursorItem:
546 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
550 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
551 hide_marker (item, event);
553 _drags->set (new MarkerDrag (this, item), event);
557 case TempoMarkerItem:
559 TempoMarker* m = reinterpret_cast<TempoMarker*> (item->get_data ("marker"));
562 new TempoMarkerDrag (
565 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
572 case MeterMarkerItem:
574 MeterMarker* m = reinterpret_cast<MeterMarker*> (item->get_data ("marker"));
577 new MeterMarkerDrag (
580 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
588 _drags->set (new VideoTimeLineDrag (this, item), event);
595 case TimecodeRulerItem:
596 case SamplesRulerItem:
597 case MinsecRulerItem:
599 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
600 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
606 case RangeMarkerBarItem:
607 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
608 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
609 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
610 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
612 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
617 case CdMarkerBarItem:
618 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
619 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
621 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
626 case TransportMarkerBarItem:
627 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
628 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
630 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
639 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
640 /* special case: allow trim of range selections in joined object mode;
641 in theory eff should equal MouseRange in this case, but it doesn't
642 because entering the range selection canvas item results in entered_regionview
643 being set to 0, so update_join_object_range_location acts as if we aren't
646 if (item_type == StartSelectionTrimItem) {
647 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
648 } else if (item_type == EndSelectionTrimItem) {
649 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
653 Editing::MouseMode eff = effective_mouse_mode ();
655 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
656 if (get_smart_mode()) {
658 case FadeInHandleItem:
659 case FadeInTrimHandleItem:
660 case FadeOutHandleItem:
661 case FadeOutTrimHandleItem:
669 /* there is no Range mode when in internal edit mode */
670 if (eff == MouseRange && internal_editing()) {
677 case StartSelectionTrimItem:
678 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
681 case EndSelectionTrimItem:
682 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
686 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
687 start_selection_grab (item, event);
689 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
690 /* grab selection for moving */
691 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
693 /* this was debated, but decided the more common action was to
694 make a new selection */
695 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
700 if (internal_editing()) {
701 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
702 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
706 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
707 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
709 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
715 case RegionViewNameHighlight:
716 if (!clicked_regionview->region()->locked()) {
717 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
723 if (!internal_editing()) {
724 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
725 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
727 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
737 case FadeInHandleItem:
738 case FadeOutHandleItem:
739 case LeftFrameHandle:
740 case RightFrameHandle:
741 case FeatureLineItem:
742 case RegionViewNameHighlight:
745 case AutomationTrackItem:
746 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, current_canvas_cursor);
757 /* Existing note: allow trimming/motion */
758 if (internal_editing()) {
759 NoteBase* cn = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
761 if (cn->big_enough_to_trim() && cn->mouse_near_ends()) {
762 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
764 _drags->set (new NoteDrag (this, item), event);
774 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
775 event->type == GDK_BUTTON_PRESS) {
777 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
779 } else if (event->type == GDK_BUTTON_PRESS) {
782 case FadeInHandleItem:
784 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
788 case FadeOutHandleItem:
790 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
794 case StartCrossFadeItem:
795 case EndCrossFadeItem:
796 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
797 // if (!clicked_regionview->region()->locked()) {
798 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
803 case FeatureLineItem:
805 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
806 remove_transient(item);
810 _drags->set (new FeatureLineDrag (this, item), event);
816 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
817 /* click on an automation region view; do nothing here and let the ARV's signal handler
823 if (internal_editing ()) {
827 /* click on a normal region view */
828 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
829 add_region_copy_drag (item, event, clicked_regionview);
830 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
831 add_region_brush_drag (item, event, clicked_regionview);
833 add_region_drag (item, event, clicked_regionview);
837 // if (!internal_editing() && (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE && !selection->regions.empty())) {
838 // _drags->add (new SelectionDrag (this, clicked_axisview->get_selection_rect (clicked_selection)->rect, SelectionDrag::SelectionMove));
841 _drags->start_grab (event);
845 case RegionViewNameHighlight:
846 case LeftFrameHandle:
847 case RightFrameHandle:
848 if (!clicked_regionview->region()->locked()) {
849 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
854 case FadeInTrimHandleItem:
855 case FadeOutTrimHandleItem:
856 if (!clicked_regionview->region()->locked()) {
857 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
864 /* rename happens on edit clicks */
865 if (clicked_regionview->get_name_highlight()) {
866 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
872 case ControlPointItem:
873 _drags->set (new ControlPointDrag (this, item), event);
877 case AutomationLineItem:
878 _drags->set (new LineDrag (this, item), event);
883 if (internal_editing()) {
884 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
885 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
889 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
893 case AutomationTrackItem:
895 TimeAxisView* parent = clicked_axisview->get_parent ();
896 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
898 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
900 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
902 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
903 if (pl->n_regions() == 0) {
904 /* Parent has no regions; create one so that we have somewhere to put automation */
905 _drags->set (new RegionCreateDrag (this, item, parent), event);
907 /* See if there's a region before the click that we can extend, and extend it if so */
908 framepos_t const t = canvas_event_sample (event);
909 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
911 _drags->set (new RegionCreateDrag (this, item, parent), event);
913 prev->set_length (t - prev->position ());
917 /* rubberband drag to select automation points */
918 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
942 _drags->set (new LineDrag (this, item), event);
945 case ControlPointItem:
946 _drags->set (new ControlPointDrag (this, item), event);
952 AudioRegionView* arv = dynamic_cast<AudioRegionView *> (clicked_regionview);
954 _drags->set (new AutomationRangeDrag (this, arv, selection->time), event, _cursors->up_down);
956 double const y = event->button.y;
957 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
959 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
961 /* smart "join" mode: drag automation */
962 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
970 case AutomationLineItem:
971 _drags->set (new LineDrag (this, item), event);
975 /* Existing note: allow trimming/motion */
976 if (internal_editing()) {
977 /* trim notes if we're in internal edit mode and near the ends of the note */
978 NoteBase* cn = reinterpret_cast<NoteBase*>(item->get_data ("notebase"));
980 if (cn->big_enough_to_trim() && cn->mouse_near_ends()) {
981 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
983 _drags->set (new NoteDrag (this, item), event);
990 if (internal_editing()) {
991 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
992 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1005 if (internal_editing() && item_type == NoteItem ) {
1006 /* drag notes if we're in internal edit mode */
1007 NoteBase* cn = reinterpret_cast<NoteBase*>(item->get_data ("notebase"));
1009 if (cn->big_enough_to_trim()) {
1010 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
1013 } else if (clicked_regionview) {
1015 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1021 _drags->set (new ScrubDrag (this, item), event);
1022 scrub_reversals = 0;
1023 scrub_reverse_distance = 0;
1024 last_scrub_x = event->button.x;
1025 scrubbing_direction = 0;
1026 push_canvas_cursor (_cursors->transparent);
1038 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1040 Editing::MouseMode const eff = effective_mouse_mode ();
1043 switch (item_type) {
1045 if (internal_editing ()) {
1046 /* no region drags in internal edit mode */
1050 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1051 add_region_copy_drag (item, event, clicked_regionview);
1053 add_region_drag (item, event, clicked_regionview);
1055 _drags->start_grab (event);
1058 case ControlPointItem:
1059 _drags->set (new ControlPointDrag (this, item), event);
1067 switch (item_type) {
1068 case RegionViewNameHighlight:
1069 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1073 case LeftFrameHandle:
1074 case RightFrameHandle:
1075 if (!internal_editing ()) {
1076 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1081 case RegionViewName:
1082 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1096 /* relax till release */
1108 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1110 if (event->type == GDK_2BUTTON_PRESS) {
1111 _drags->mark_double_click ();
1112 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1116 if (event->type != GDK_BUTTON_PRESS) {
1120 pre_press_cursor = current_canvas_cursor;
1122 _track_canvas->grab_focus();
1124 if (_session && _session->actively_recording()) {
1128 if (internal_editing()) {
1129 bool leave_internal_edit_mode = false;
1131 switch (item_type) {
1136 if (!dynamic_cast<MidiRegionView*> (clicked_regionview) && !dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
1137 leave_internal_edit_mode = true;
1141 case PlayheadCursorItem:
1143 case TempoMarkerItem:
1144 case MeterMarkerItem:
1148 case RangeMarkerBarItem:
1149 case CdMarkerBarItem:
1150 case TransportMarkerBarItem:
1152 case TimecodeRulerItem:
1153 case SamplesRulerItem:
1154 case MinsecRulerItem:
1156 /* button press on these items never does anything to
1157 change the editing mode.
1165 if (leave_internal_edit_mode) {
1166 ActionManager::do_action ("MouseMode", "toggle-internal-edit");
1170 button_selection (item, event, item_type);
1172 if (!_drags->active () &&
1173 (Keyboard::is_delete_event (&event->button) ||
1174 Keyboard::is_context_menu_event (&event->button) ||
1175 Keyboard::is_edit_event (&event->button))) {
1177 /* handled by button release */
1181 //not rolling, range mode click + join_play_range : locate the PH here
1182 if ( !_drags->active () && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && Config->get_follow_edits() ) {
1183 framepos_t where = canvas_event_sample (event);
1185 _session->request_locate (where, false);
1188 switch (event->button.button) {
1190 return button_press_handler_1 (item, event, item_type);
1194 return button_press_handler_2 (item, event, item_type);
1201 return button_press_dispatch (&event->button);
1210 Editor::button_press_dispatch (GdkEventButton* ev)
1212 /* this function is intended only for buttons 4 and above.
1215 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1216 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1220 Editor::button_release_dispatch (GdkEventButton* ev)
1222 /* this function is intended only for buttons 4 and above.
1225 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1226 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1230 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1232 framepos_t where = canvas_event_sample (event);
1233 AutomationTimeAxisView* atv = 0;
1235 if (pre_press_cursor) {
1236 set_canvas_cursor (pre_press_cursor);
1237 pre_press_cursor = 0;
1240 /* no action if we're recording */
1242 if (_session && _session->actively_recording()) {
1246 /* see if we're finishing a drag */
1248 bool were_dragging = false;
1249 if (_drags->active ()) {
1250 bool const r = _drags->end_grab (event);
1252 /* grab dragged, so do nothing else */
1256 were_dragging = true;
1259 update_region_layering_order_editor ();
1261 /* edit events get handled here */
1263 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1264 switch (item_type) {
1266 show_region_properties ();
1269 case TempoMarkerItem: {
1271 TempoMarker* tempo_marker;
1273 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
1274 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1275 abort(); /*NOTREACHED*/
1278 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1279 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1280 abort(); /*NOTREACHED*/
1283 edit_tempo_marker (*tempo_marker);
1287 case MeterMarkerItem: {
1289 MeterMarker* meter_marker;
1291 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
1292 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1293 abort(); /*NOTREACHED*/
1296 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1297 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1298 abort(); /*NOTREACHED*/
1300 edit_meter_marker (*meter_marker);
1304 case RegionViewName:
1305 if (clicked_regionview->name_active()) {
1306 return mouse_rename_region (item, event);
1310 case ControlPointItem:
1311 edit_control_point (item);
1320 /* context menu events get handled here */
1321 if (Keyboard::is_context_menu_event (&event->button)) {
1323 context_click_event = *event;
1325 if (!_drags->active ()) {
1327 /* no matter which button pops up the context menu, tell the menu
1328 widget to use button 1 to drive menu selection.
1331 switch (item_type) {
1333 case FadeInHandleItem:
1334 case FadeInTrimHandleItem:
1335 case StartCrossFadeItem:
1336 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1340 case FadeOutHandleItem:
1341 case FadeOutTrimHandleItem:
1342 case EndCrossFadeItem:
1343 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1346 case LeftFrameHandle:
1347 case RightFrameHandle:
1351 popup_track_context_menu (1, event->button.time, item_type, false);
1355 case RegionViewNameHighlight:
1356 case RegionViewName:
1357 popup_track_context_menu (1, event->button.time, item_type, false);
1361 popup_track_context_menu (1, event->button.time, item_type, true);
1364 case AutomationTrackItem:
1365 popup_track_context_menu (1, event->button.time, item_type, false);
1369 case RangeMarkerBarItem:
1370 case TransportMarkerBarItem:
1371 case CdMarkerBarItem:
1375 case TimecodeRulerItem:
1376 case SamplesRulerItem:
1377 case MinsecRulerItem:
1379 popup_ruler_menu (where, item_type);
1383 marker_context_menu (&event->button, item);
1386 case TempoMarkerItem:
1387 tempo_or_meter_marker_context_menu (&event->button, item);
1390 case MeterMarkerItem:
1391 tempo_or_meter_marker_context_menu (&event->button, item);
1394 case CrossfadeViewItem:
1395 popup_track_context_menu (1, event->button.time, item_type, false);
1398 case ControlPointItem:
1399 popup_control_point_context_menu (item, event);
1410 /* delete events get handled here */
1412 Editing::MouseMode const eff = effective_mouse_mode ();
1414 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1416 switch (item_type) {
1417 case TempoMarkerItem:
1418 remove_tempo_marker (item);
1421 case MeterMarkerItem:
1422 remove_meter_marker (item);
1426 remove_marker (*item, event);
1430 if (eff == MouseObject) {
1431 remove_clicked_region ();
1435 case ControlPointItem:
1436 remove_control_point (item);
1440 remove_midi_note (item, event);
1449 switch (event->button.button) {
1452 switch (item_type) {
1453 /* see comments in button_press_handler */
1454 case PlayheadCursorItem:
1457 case AutomationLineItem:
1458 case StartSelectionTrimItem:
1459 case EndSelectionTrimItem:
1463 if (!_dragging_playhead) {
1464 snap_to_with_modifier (where, event, RoundNearest, true);
1465 mouse_add_new_marker (where);
1469 case CdMarkerBarItem:
1470 if (!_dragging_playhead) {
1471 // if we get here then a dragged range wasn't done
1472 snap_to_with_modifier (where, event, RoundNearest, true);
1473 mouse_add_new_marker (where, true);
1478 if (!_dragging_playhead) {
1479 snap_to_with_modifier (where, event);
1480 mouse_add_new_tempo_event (where);
1485 if (!_dragging_playhead) {
1486 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1491 case TimecodeRulerItem:
1492 case SamplesRulerItem:
1493 case MinsecRulerItem:
1504 switch (item_type) {
1507 /* check that we didn't drag before releasing, since
1508 its really annoying to create new control
1509 points when doing this.
1511 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1512 if (!were_dragging && arv) {
1513 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1514 arv->add_gain_point_event (item, event, with_guard_points);
1520 case AutomationTrackItem: {
1521 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1522 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1524 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1535 pop_canvas_cursor ();
1536 if (scrubbing_direction == 0) {
1537 /* no drag, just a click */
1538 switch (item_type) {
1540 play_selected_region ();
1546 /* make sure we stop */
1547 _session->request_transport_speed (0.0);
1556 /* do any (de)selection operations that should occur on button release */
1557 button_selection (item, event, item_type);
1566 switch (item_type) {
1568 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1570 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1573 // Button2 click is unused
1588 // x_style_paste (where, 1.0);
1609 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1616 /* by the time we reach here, entered_regionview and entered trackview
1617 * will have already been set as appropriate. Things are done this
1618 * way because this method isn't passed a pointer to a variable type of
1619 * thing that is entered (which may or may not be canvas item).
1620 * (e.g. the actual entered regionview)
1623 choose_canvas_cursor_on_entry (&event->crossing, item_type);
1625 switch (item_type) {
1626 case ControlPointItem:
1627 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1628 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1631 fraction = 1.0 - (cp->get_y() / cp->line().height());
1633 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1634 _verbose_cursor->show ();
1639 if (mouse_mode == MouseDraw) {
1640 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1642 line->set_outline_color (ARDOUR_UI::config()->get_EnteredGainLine());
1647 case AutomationLineItem:
1648 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1649 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1651 line->set_outline_color (ARDOUR_UI::config()->get_EnteredAutomationLine());
1656 case AutomationTrackItem:
1657 AutomationTimeAxisView* atv;
1658 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1659 clear_entered_track = false;
1660 set_entered_track (atv);
1665 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1668 entered_marker = marker;
1669 marker->set_color_rgba (ARDOUR_UI::config()->get_EnteredMarker());
1671 case MeterMarkerItem:
1672 case TempoMarkerItem:
1675 case FadeInHandleItem:
1676 case FadeInTrimHandleItem:
1677 if (mouse_mode == MouseObject && !internal_editing()) {
1678 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1680 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1681 rect->set_fill_color (rv->get_fill_color());
1686 case FadeOutHandleItem:
1687 case FadeOutTrimHandleItem:
1688 if (mouse_mode == MouseObject && !internal_editing()) {
1689 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1691 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1692 rect->set_fill_color (rv->get_fill_color ());
1697 case FeatureLineItem:
1699 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1700 line->set_outline_color (0xFF0000FF);
1711 /* third pass to handle entered track status in a comprehensible way.
1714 switch (item_type) {
1716 case AutomationLineItem:
1717 case ControlPointItem:
1718 /* these do not affect the current entered track state */
1719 clear_entered_track = false;
1722 case AutomationTrackItem:
1723 /* handled above already */
1735 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1743 reset_canvas_cursor ();
1745 switch (item_type) {
1746 case ControlPointItem:
1747 _verbose_cursor->hide ();
1751 case AutomationLineItem:
1752 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1754 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1756 line->set_outline_color (al->get_line_color());
1762 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1766 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1767 location_flags_changed (loc);
1770 case MeterMarkerItem:
1771 case TempoMarkerItem:
1774 case FadeInTrimHandleItem:
1775 case FadeOutTrimHandleItem:
1776 case FadeInHandleItem:
1777 case FadeOutHandleItem:
1779 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1781 rect->set_fill_color (ARDOUR_UI::config()->get_InactiveFadeHandle());
1786 case AutomationTrackItem:
1789 case FeatureLineItem:
1791 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1792 line->set_outline_color (ARDOUR_UI::config()->get_ZeroLine());
1804 Editor::scrub (framepos_t frame, double current_x)
1808 if (scrubbing_direction == 0) {
1810 _session->request_locate (frame, false);
1811 _session->request_transport_speed (0.1);
1812 scrubbing_direction = 1;
1816 if (last_scrub_x > current_x) {
1818 /* pointer moved to the left */
1820 if (scrubbing_direction > 0) {
1822 /* we reversed direction to go backwards */
1825 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1829 /* still moving to the left (backwards) */
1831 scrub_reversals = 0;
1832 scrub_reverse_distance = 0;
1834 delta = 0.01 * (last_scrub_x - current_x);
1835 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1839 /* pointer moved to the right */
1841 if (scrubbing_direction < 0) {
1842 /* we reversed direction to go forward */
1845 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1848 /* still moving to the right */
1850 scrub_reversals = 0;
1851 scrub_reverse_distance = 0;
1853 delta = 0.01 * (current_x - last_scrub_x);
1854 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1858 /* if there have been more than 2 opposite motion moves detected, or one that moves
1859 back more than 10 pixels, reverse direction
1862 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1864 if (scrubbing_direction > 0) {
1865 /* was forwards, go backwards */
1866 _session->request_transport_speed (-0.1);
1867 scrubbing_direction = -1;
1869 /* was backwards, go forwards */
1870 _session->request_transport_speed (0.1);
1871 scrubbing_direction = 1;
1874 scrub_reverse_distance = 0;
1875 scrub_reversals = 0;
1879 last_scrub_x = current_x;
1883 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1885 _last_motion_y = event->motion.y;
1887 if (event->motion.is_hint) {
1890 /* We call this so that MOTION_NOTIFY events continue to be
1891 delivered to the canvas. We need to do this because we set
1892 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1893 the density of the events, at the expense of a round-trip
1894 to the server. Given that this will mostly occur on cases
1895 where DISPLAY = :0.0, and given the cost of what the motion
1896 event might do, its a good tradeoff.
1899 _track_canvas->get_pointer (x, y);
1902 if (current_stepping_trackview) {
1903 /* don't keep the persistent stepped trackview if the mouse moves */
1904 current_stepping_trackview = 0;
1905 step_timeout.disconnect ();
1908 if (_session && _session->actively_recording()) {
1909 /* Sorry. no dragging stuff around while we record */
1913 update_join_object_range_location (event->motion.y);
1915 if (_drags->active ()) {
1916 return _drags->motion_handler (event, from_autoscroll);
1923 Editor::can_remove_control_point (ArdourCanvas::Item* item)
1925 ControlPoint* control_point;
1927 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1928 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1929 abort(); /*NOTREACHED*/
1932 AutomationLine& line = control_point->line ();
1933 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
1934 /* we shouldn't remove the first or last gain point in region gain lines */
1935 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
1944 Editor::remove_control_point (ArdourCanvas::Item* item)
1946 if (!can_remove_control_point (item)) {
1950 ControlPoint* control_point;
1952 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1953 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1954 abort(); /*NOTREACHED*/
1957 control_point->line().remove_point (*control_point);
1961 Editor::edit_control_point (ArdourCanvas::Item* item)
1963 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
1966 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1967 abort(); /*NOTREACHED*/
1970 ControlPointDialog d (p);
1973 if (d.run () != RESPONSE_ACCEPT) {
1977 p->line().modify_point_y (*p, d.get_y_fraction ());
1981 Editor::edit_notes (TimeAxisViewItem& tavi)
1983 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(&tavi);
1989 MidiRegionView::Selection const & s = mrv->selection();
1995 EditNoteDialog* d = new EditNoteDialog (&(*s.begin())->region_view(), s);
1999 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2003 Editor::note_edit_done (int r, EditNoteDialog* d)
2010 Editor::visible_order_range (int* low, int* high) const
2012 *low = TimeAxisView::max_order ();
2015 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2017 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2019 if (!rtv->hidden()) {
2021 if (*high < rtv->order()) {
2022 *high = rtv->order ();
2025 if (*low > rtv->order()) {
2026 *low = rtv->order ();
2033 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2035 /* Either add to or set the set the region selection, unless
2036 this is an alignment click (control used)
2039 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2040 TimeAxisView* tv = &rv.get_time_axis_view();
2041 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2043 if (rtv && rtv->is_track()) {
2044 speed = rtv->track()->speed();
2047 framepos_t where = get_preferred_edit_position();
2051 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2053 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2055 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2057 align_region (rv.region(), End, (framepos_t) (where * speed));
2061 align_region (rv.region(), Start, (framepos_t) (where * speed));
2068 Editor::collect_new_region_view (RegionView* rv)
2070 latest_regionviews.push_back (rv);
2074 Editor::collect_and_select_new_region_view (RegionView* rv)
2077 latest_regionviews.push_back (rv);
2081 Editor::cancel_selection ()
2083 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2084 (*i)->hide_selection ();
2087 selection->clear ();
2088 clicked_selection = 0;
2092 Editor::cancel_time_selection ()
2094 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2095 (*i)->hide_selection ();
2097 selection->time.clear ();
2098 clicked_selection = 0;
2102 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2104 RegionView* rv = clicked_regionview;
2106 /* Choose action dependant on which button was pressed */
2107 switch (event->button.button) {
2109 begin_reversible_command (_("start point trim"));
2111 if (selection->selected (rv)) {
2112 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2113 i != selection->regions.by_layer().end(); ++i)
2115 if (!(*i)->region()->locked()) {
2116 (*i)->region()->clear_changes ();
2117 (*i)->region()->trim_front (new_bound);
2118 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2123 if (!rv->region()->locked()) {
2124 rv->region()->clear_changes ();
2125 rv->region()->trim_front (new_bound);
2126 _session->add_command(new StatefulDiffCommand (rv->region()));
2130 commit_reversible_command();
2134 begin_reversible_command (_("End point trim"));
2136 if (selection->selected (rv)) {
2138 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2140 if (!(*i)->region()->locked()) {
2141 (*i)->region()->clear_changes();
2142 (*i)->region()->trim_end (new_bound);
2143 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2149 if (!rv->region()->locked()) {
2150 rv->region()->clear_changes ();
2151 rv->region()->trim_end (new_bound);
2152 _session->add_command (new StatefulDiffCommand (rv->region()));
2156 commit_reversible_command();
2165 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2170 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2171 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2172 abort(); /*NOTREACHED*/
2175 Location* location = find_location_from_marker (marker, is_start);
2176 location->set_hidden (true, this);
2180 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2182 using namespace Gtkmm2ext;
2184 ArdourPrompter prompter (false);
2186 prompter.set_prompt (_("Name for region:"));
2187 prompter.set_initial_text (clicked_regionview->region()->name());
2188 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2189 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2190 prompter.show_all ();
2191 switch (prompter.run ()) {
2192 case Gtk::RESPONSE_ACCEPT:
2194 prompter.get_result(str);
2196 clicked_regionview->region()->set_name (str);
2205 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2207 /* no brushing without a useful snap setting */
2209 switch (_snap_mode) {
2211 return; /* can't work because it allows region to be placed anywhere */
2216 switch (_snap_type) {
2224 /* don't brush a copy over the original */
2226 if (pos == rv->region()->position()) {
2230 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2232 if (rtv == 0 || !rtv->is_track()) {
2236 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2237 double speed = rtv->track()->speed();
2239 playlist->clear_changes ();
2240 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2241 playlist->add_region (new_region, (framepos_t) (pos * speed));
2242 _session->add_command (new StatefulDiffCommand (playlist));
2244 // playlist is frozen, so we have to update manually XXX this is disgusting
2246 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2250 Editor::track_height_step_timeout ()
2252 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2253 current_stepping_trackview = 0;
2260 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2262 assert (region_view);
2264 if (!region_view->region()->playlist()) {
2268 switch (Config->get_edit_mode()) {
2270 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2273 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2276 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2283 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2285 assert (region_view);
2287 if (!region_view->region()->playlist()) {
2291 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2295 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2297 assert (region_view);
2299 if (!region_view->region()->playlist()) {
2303 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2307 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2309 begin_reversible_command (Operations::drag_region_brush);
2312 /** Start a grab where a time range is selected, track(s) are selected, and the
2313 * user clicks and drags a region with a modifier in order to create a new region containing
2314 * the section of the clicked region that lies within the time range.
2317 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2319 if (clicked_regionview == 0) {
2323 /* lets try to create new Region for the selection */
2325 vector<boost::shared_ptr<Region> > new_regions;
2326 create_region_from_selection (new_regions);
2328 if (new_regions.empty()) {
2332 /* XXX fix me one day to use all new regions */
2334 boost::shared_ptr<Region> region (new_regions.front());
2336 /* add it to the current stream/playlist.
2338 tricky: the streamview for the track will add a new regionview. we will
2339 catch the signal it sends when it creates the regionview to
2340 set the regionview we want to then drag.
2343 latest_regionviews.clear();
2344 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2346 /* A selection grab currently creates two undo/redo operations, one for
2347 creating the new region and another for moving it.
2350 begin_reversible_command (Operations::selection_grab);
2352 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2354 playlist->clear_changes ();
2355 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2356 _session->add_command(new StatefulDiffCommand (playlist));
2358 commit_reversible_command ();
2362 if (latest_regionviews.empty()) {
2363 /* something went wrong */
2367 /* we need to deselect all other regionviews, and select this one
2368 i'm ignoring undo stuff, because the region creation will take care of it
2370 selection->set (latest_regionviews);
2372 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2378 if (_drags->active ()) {
2381 selection->clear ();
2388 Editor::set_internal_edit (bool yn)
2390 if (_internal_editing == yn) {
2394 _internal_editing = yn;
2397 pre_internal_mouse_mode = mouse_mode;
2398 pre_internal_snap_type = _snap_type;
2399 pre_internal_snap_mode = _snap_mode;
2401 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2402 (*i)->enter_internal_edit_mode ();
2405 set_snap_to (internal_snap_type);
2406 set_snap_mode (internal_snap_mode);
2410 internal_snap_mode = _snap_mode;
2411 internal_snap_type = _snap_type;
2413 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2414 (*i)->leave_internal_edit_mode ();
2417 set_snap_to (pre_internal_snap_type);
2418 set_snap_mode (pre_internal_snap_mode);
2421 reset_canvas_cursor ();
2422 MouseModeChanged ();
2425 /** Update _join_object_range_state which indicate whether we are over the top
2426 * or bottom half of a route view, used by the `join object/range' tool
2427 * mode. Coordinates in canvas space.
2430 Editor::update_join_object_range_location (double y)
2432 if (_internal_editing || !get_smart_mode()) {
2433 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2437 JoinObjectRangeState const old = _join_object_range_state;
2439 if (mouse_mode == MouseObject) {
2440 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2441 } else if (mouse_mode == MouseRange) {
2442 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2445 if (entered_regionview) {
2447 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2448 double const c = item_space.y / entered_regionview->height();
2450 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2452 if (_join_object_range_state != old) {
2453 set_canvas_cursor (which_track_cursor ());
2456 } else if (entered_track) {
2458 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2460 if (entered_route_view) {
2465 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2467 double track_height = entered_route_view->view()->child_height();
2468 if (Config->get_show_name_highlight()) {
2469 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2471 double const c = cy / track_height;
2475 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2477 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2481 /* Other kinds of tracks use object mode */
2482 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2485 if (_join_object_range_state != old) {
2486 set_canvas_cursor (which_track_cursor ());
2492 Editor::effective_mouse_mode () const
2494 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2496 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2504 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2506 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2509 e->region_view().delete_note (e->note ());
2512 /** Obtain the pointer position in canvas coordinates */
2514 Editor::get_pointer_position (double& x, double& y) const
2517 _track_canvas->get_pointer (px, py);
2518 _track_canvas->window_to_canvas (px, py, x, y);