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 set_mouse_mode(m, true); //call this so the button styles can get updated
220 static Glib::RefPtr<Action>
221 get_mouse_mode_action(MouseMode m)
225 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
227 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
229 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
231 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
233 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
235 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-content"));
237 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
239 return Glib::RefPtr<Action>();
243 Editor::set_mouse_mode (MouseMode m, bool force)
245 if (_drags->active ()) {
249 if (!force && m == mouse_mode) {
253 if (ARDOUR::Profile->get_mixbus()) {
254 if ( m == MouseCut) m = MouseObject;
257 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
258 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
260 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
261 tact->set_active (false);
262 tact->set_active (true);
264 //NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting
268 Editor::mouse_mode_toggled (MouseMode m)
270 if (ARDOUR::Profile->get_mixbus()) {
271 if ( m == MouseCut) m = MouseObject;
274 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
275 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
277 if (!tact->get_active()) {
278 /* this was just the notification that the old mode has been
279 * left. we'll get called again with the new mode active in a
285 if (_session && mouse_mode == MouseAudition) {
286 /* stop transport and reset default speed to avoid oddness with
288 _session->request_transport_speed (0.0, true);
291 const bool was_internal = internal_editing();
295 /* Switch snap type/mode if we're moving to/from an internal tool. Note
296 this must toggle the actions and not call set_snap_*() directly,
297 otherwise things get out of sync and the combo box stops working. */
298 if (!was_internal && internal_editing()) {
299 snap_type_action(internal_snap_type)->set_active(true);
300 snap_mode_action(internal_snap_mode)->set_active(true);
301 } else if (was_internal && !internal_editing()) {
302 snap_type_action(pre_internal_snap_type)->set_active(true);
303 snap_mode_action(pre_internal_snap_mode)->set_active(true);
308 /* this should generate a new enter event which will
309 trigger the appropiate cursor.
313 _track_canvas->re_enter ();
316 set_gain_envelope_visibility ();
318 update_time_selection_display ();
320 update_all_enter_cursors ();
322 MouseModeChanged (); /* EMIT SIGNAL */
326 Editor::internal_editing() const
328 return mouse_mode == Editing::MouseContent || mouse_mode == Editing::MouseDraw;
332 Editor::update_time_selection_display ()
334 switch (mouse_mode) {
336 selection->clear_objects ();
337 selection->ClearMidiNoteSelection (); /* EMIT SIGNAL */
340 selection->clear_time ();
341 selection->clear_tracks ();
342 selection->ClearMidiNoteSelection (); /* EMIT SIGNAL */
345 /* Clear regions, but not time or tracks, since that
346 would destroy the range selection rectangle, which we need to stick
347 around for AutomationRangeDrag. */
348 selection->clear_regions ();
349 selection->clear_playlists ();
352 /* This handles internal edit.
353 Clear everything except points and notes.
355 selection->clear_regions();
356 selection->clear_lines();
357 selection->clear_playlists ();
359 selection->clear_time ();
360 selection->clear_tracks ();
364 /* We probably want to keep region selection */
365 selection->clear_points ();
366 selection->clear_lines();
367 selection->clear_playlists ();
369 selection->clear_time ();
370 selection->clear_tracks ();
374 /*Don't lose lines or points if no action in this mode */
375 selection->clear_regions ();
376 selection->clear_playlists ();
377 selection->clear_time ();
378 selection->clear_tracks ();
382 /*Clear everything */
383 selection->clear_objects();
384 selection->clear_time ();
385 selection->clear_tracks ();
391 Editor::step_mouse_mode (bool next)
393 const int n_mouse_modes = (int)MouseContent + 1;
394 int current = (int)current_mouse_mode();
396 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
398 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
403 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 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
454 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
456 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
458 /* almost no selection action on modified button-2 or button-3 events */
460 if (item_type != RegionItem && event->button.button != 2) {
466 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
467 bool press = (event->type == GDK_BUTTON_PRESS);
470 _mouse_changed_selection = false;
476 if (eff_mouse_mode != MouseRange) {
477 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
479 /* don't change the selection unless the
480 clicked track is not currently selected. if
481 so, "collapse" the selection to just this
484 if (!selection->selected (clicked_axisview)) {
485 set_selected_track_as_side_effect (Selection::Set);
489 if (eff_mouse_mode != MouseRange) {
490 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
495 case RegionViewNameHighlight:
497 case LeftFrameHandle:
498 case RightFrameHandle:
499 case FadeInHandleItem:
500 case FadeInTrimHandleItem:
502 case FadeOutHandleItem:
503 case FadeOutTrimHandleItem:
505 case StartCrossFadeItem:
506 case EndCrossFadeItem:
507 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
508 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
509 } else if (event->type == GDK_BUTTON_PRESS) {
510 set_selected_track_as_side_effect (op);
514 case ControlPointItem:
515 set_selected_track_as_side_effect (op);
516 if (eff_mouse_mode != MouseRange) {
517 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
522 /* for context click, select track */
523 if (event->button.button == 3) {
524 selection->clear_tracks ();
525 set_selected_track_as_side_effect (op);
527 /* We won't get a release.*/
528 begin_reversible_selection_op (_("Button 3 Menu Select"));
529 commit_reversible_selection_op ();
533 case AutomationTrackItem:
534 set_selected_track_as_side_effect (op);
541 if ((!press) && _mouse_changed_selection) {
542 begin_reversible_selection_op (_("Button Selection"));
543 commit_reversible_selection_op ();
544 _mouse_changed_selection = false;
549 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
551 /* single mouse clicks on any of these item types operate
552 independent of mouse mode, mostly because they are
553 not on the main track canvas or because we want
557 NoteBase* note = NULL;
560 case PlayheadCursorItem:
561 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
565 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
566 hide_marker (item, event);
568 _drags->set (new MarkerDrag (this, item), event);
572 case TempoMarkerItem:
575 new TempoMarkerDrag (
578 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
585 case MeterMarkerItem:
588 new MeterMarkerDrag (
591 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
599 _drags->set (new VideoTimeLineDrag (this, item), event);
606 case TimecodeRulerItem:
607 case SamplesRulerItem:
608 case MinsecRulerItem:
610 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
611 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
617 case RangeMarkerBarItem:
618 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
619 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
620 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
621 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
623 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
628 case CdMarkerBarItem:
629 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
630 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
632 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
637 case TransportMarkerBarItem:
638 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
639 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
641 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
650 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
651 /* special case: allow trim of range selections in joined object mode;
652 in theory eff should equal MouseRange in this case, but it doesn't
653 because entering the range selection canvas item results in entered_regionview
654 being set to 0, so update_join_object_range_location acts as if we aren't
657 if (item_type == StartSelectionTrimItem) {
658 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
659 } else if (item_type == EndSelectionTrimItem) {
660 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
664 Editing::MouseMode eff = effective_mouse_mode ();
666 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
667 if (get_smart_mode()) {
669 case FadeInHandleItem:
670 case FadeInTrimHandleItem:
671 case FadeOutHandleItem:
672 case FadeOutTrimHandleItem:
683 case StartSelectionTrimItem:
684 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
687 case EndSelectionTrimItem:
688 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
692 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
693 start_selection_grab (item, event);
695 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
696 /* grab selection for moving */
697 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
699 /* this was debated, but decided the more common action was to
700 make a new selection */
701 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), 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);
714 case RegionViewNameHighlight:
715 if (!clicked_regionview->region()->locked()) {
716 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
722 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
723 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
725 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
734 case FadeInHandleItem:
735 case FadeOutHandleItem:
736 case LeftFrameHandle:
737 case RightFrameHandle:
738 case FeatureLineItem:
739 case RegionViewNameHighlight:
742 case AutomationTrackItem:
743 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
754 /* Existing note: allow trimming/motion */
755 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
756 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
757 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
759 _drags->set (new NoteDrag (this, item), event);
764 case ControlPointItem:
765 _drags->set (new ControlPointDrag (this, item), event);
770 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
771 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
776 case AutomationTrackItem:
777 /* rubberband drag to select automation points */
778 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
783 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
784 /* rubberband drag to select automation points */
785 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
796 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
797 event->type == GDK_BUTTON_PRESS) {
799 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
801 } else if (event->type == GDK_BUTTON_PRESS) {
804 case FadeInHandleItem:
806 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
810 case FadeOutHandleItem:
812 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
816 case StartCrossFadeItem:
817 case EndCrossFadeItem:
818 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
819 // if (!clicked_regionview->region()->locked()) {
820 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
825 case FeatureLineItem:
827 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
828 remove_transient(item);
832 _drags->set (new FeatureLineDrag (this, item), event);
838 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
839 /* click on an automation region view; do nothing here and let the ARV's signal handler
845 /* click on a normal region view */
846 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
847 add_region_copy_drag (item, event, clicked_regionview);
848 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
849 add_region_brush_drag (item, event, clicked_regionview);
851 add_region_drag (item, event, clicked_regionview);
855 _drags->start_grab (event);
859 case RegionViewNameHighlight:
860 case LeftFrameHandle:
861 case RightFrameHandle:
862 if (!clicked_regionview->region()->locked()) {
863 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
868 case FadeInTrimHandleItem:
869 case FadeOutTrimHandleItem:
870 if (!clicked_regionview->region()->locked()) {
871 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
878 /* rename happens on edit clicks */
879 if (clicked_regionview->get_name_highlight()) {
880 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
886 case ControlPointItem:
887 _drags->set (new ControlPointDrag (this, item), event);
891 case AutomationLineItem:
892 _drags->set (new LineDrag (this, item), event);
897 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
900 case AutomationTrackItem:
902 TimeAxisView* parent = clicked_axisview->get_parent ();
903 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
905 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
907 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
909 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
910 if (pl->n_regions() == 0) {
911 /* Parent has no regions; create one so that we have somewhere to put automation */
912 _drags->set (new RegionCreateDrag (this, item, parent), event);
914 /* See if there's a region before the click that we can extend, and extend it if so */
915 framepos_t const t = canvas_event_sample (event);
916 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
918 _drags->set (new RegionCreateDrag (this, item, parent), event);
920 prev->set_length (t - prev->position ());
924 /* rubberband drag to select automation points */
925 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
949 _drags->set (new LineDrag (this, item), event);
952 case ControlPointItem:
953 _drags->set (new ControlPointDrag (this, item), event);
959 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
960 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
961 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
962 event, _cursors->up_down);
964 double const y = event->button.y;
965 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
967 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
969 /* smart "join" mode: drag automation */
970 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
978 case AutomationLineItem:
979 _drags->set (new LineDrag (this, item), event);
983 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
984 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
985 /* Note is big and pointer is near the end, trim */
986 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
989 _drags->set (new NoteDrag (this, item), event);
996 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
997 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1008 if (item_type == NoteItem) {
1009 /* resize-drag notes */
1010 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1011 if (note->big_enough_to_trim()) {
1012 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1016 } else if (clicked_regionview) {
1018 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1024 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1025 scrub_reversals = 0;
1026 scrub_reverse_distance = 0;
1027 last_scrub_x = event->button.x;
1028 scrubbing_direction = 0;
1040 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1042 Editing::MouseMode const eff = effective_mouse_mode ();
1045 switch (item_type) {
1047 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1048 add_region_copy_drag (item, event, clicked_regionview);
1050 add_region_drag (item, event, clicked_regionview);
1052 _drags->start_grab (event);
1055 case ControlPointItem:
1056 _drags->set (new ControlPointDrag (this, item), event);
1064 switch (item_type) {
1065 case RegionViewNameHighlight:
1066 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1070 case LeftFrameHandle:
1071 case RightFrameHandle:
1072 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1076 case RegionViewName:
1077 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1091 /* relax till release */
1103 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1105 if (event->type == GDK_2BUTTON_PRESS) {
1106 _drags->mark_double_click ();
1107 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1111 if (event->type != GDK_BUTTON_PRESS) {
1115 _track_canvas->grab_focus();
1117 if (_session && _session->actively_recording()) {
1121 button_selection (item, event, item_type);
1123 if (!_drags->active () &&
1124 (Keyboard::is_delete_event (&event->button) ||
1125 Keyboard::is_context_menu_event (&event->button) ||
1126 Keyboard::is_edit_event (&event->button))) {
1128 /* handled by button release */
1132 //not rolling, range mode click + join_play_range : locate the PH here
1133 if ( !_drags->active () && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && ARDOUR_UI::config()->get_follow_edits() ) {
1134 framepos_t where = canvas_event_sample (event);
1136 _session->request_locate (where, false);
1139 switch (event->button.button) {
1141 return button_press_handler_1 (item, event, item_type);
1145 return button_press_handler_2 (item, event, item_type);
1152 return button_press_dispatch (&event->button);
1161 Editor::button_press_dispatch (GdkEventButton* ev)
1163 /* this function is intended only for buttons 4 and above.
1166 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1167 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1171 Editor::button_release_dispatch (GdkEventButton* ev)
1173 /* this function is intended only for buttons 4 and above.
1176 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1177 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1181 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1183 framepos_t where = canvas_event_sample (event);
1184 AutomationTimeAxisView* atv = 0;
1186 _press_cursor_ctx.reset();
1188 /* no action if we're recording */
1190 if (_session && _session->actively_recording()) {
1194 /* see if we're finishing a drag */
1196 bool were_dragging = false;
1197 if (_drags->active ()) {
1198 bool const r = _drags->end_grab (event);
1200 /* grab dragged, so do nothing else */
1204 were_dragging = true;
1207 update_region_layering_order_editor ();
1209 /* edit events get handled here */
1211 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1212 switch (item_type) {
1214 show_region_properties ();
1217 case TempoMarkerItem: {
1219 TempoMarker* tempo_marker;
1221 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
1222 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1223 abort(); /*NOTREACHED*/
1226 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1227 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1228 abort(); /*NOTREACHED*/
1231 edit_tempo_marker (*tempo_marker);
1235 case MeterMarkerItem: {
1237 MeterMarker* meter_marker;
1239 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
1240 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1241 abort(); /*NOTREACHED*/
1244 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1245 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1246 abort(); /*NOTREACHED*/
1248 edit_meter_marker (*meter_marker);
1252 case RegionViewName:
1253 if (clicked_regionview->name_active()) {
1254 return mouse_rename_region (item, event);
1258 case ControlPointItem:
1259 edit_control_point (item);
1268 /* context menu events get handled here */
1269 if (Keyboard::is_context_menu_event (&event->button)) {
1271 context_click_event = *event;
1273 if (!_drags->active ()) {
1275 /* no matter which button pops up the context menu, tell the menu
1276 widget to use button 1 to drive menu selection.
1279 switch (item_type) {
1281 case FadeInHandleItem:
1282 case FadeInTrimHandleItem:
1283 case StartCrossFadeItem:
1284 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1288 case FadeOutHandleItem:
1289 case FadeOutTrimHandleItem:
1290 case EndCrossFadeItem:
1291 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1294 case LeftFrameHandle:
1295 case RightFrameHandle:
1299 popup_track_context_menu (1, event->button.time, item_type, false);
1303 case RegionViewNameHighlight:
1304 case RegionViewName:
1305 popup_track_context_menu (1, event->button.time, item_type, false);
1309 popup_track_context_menu (1, event->button.time, item_type, true);
1312 case AutomationTrackItem:
1313 popup_track_context_menu (1, event->button.time, item_type, false);
1317 case RangeMarkerBarItem:
1318 case TransportMarkerBarItem:
1319 case CdMarkerBarItem:
1323 case TimecodeRulerItem:
1324 case SamplesRulerItem:
1325 case MinsecRulerItem:
1327 popup_ruler_menu (where, item_type);
1331 marker_context_menu (&event->button, item);
1334 case TempoMarkerItem:
1335 tempo_or_meter_marker_context_menu (&event->button, item);
1338 case MeterMarkerItem:
1339 tempo_or_meter_marker_context_menu (&event->button, item);
1342 case CrossfadeViewItem:
1343 popup_track_context_menu (1, event->button.time, item_type, false);
1346 case ControlPointItem:
1347 popup_control_point_context_menu (item, event);
1351 if (internal_editing()) {
1352 popup_note_context_menu (item, event);
1364 /* delete events get handled here */
1366 Editing::MouseMode const eff = effective_mouse_mode ();
1368 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1370 switch (item_type) {
1371 case TempoMarkerItem:
1372 remove_tempo_marker (item);
1375 case MeterMarkerItem:
1376 remove_meter_marker (item);
1380 remove_marker (*item, event);
1384 if (eff == MouseObject) {
1385 remove_clicked_region ();
1389 case ControlPointItem:
1390 remove_control_point (item);
1394 remove_midi_note (item, event);
1403 switch (event->button.button) {
1406 switch (item_type) {
1407 /* see comments in button_press_handler */
1408 case PlayheadCursorItem:
1411 case AutomationLineItem:
1412 case StartSelectionTrimItem:
1413 case EndSelectionTrimItem:
1417 if (!_dragging_playhead) {
1418 snap_to_with_modifier (where, event, RoundNearest, true);
1419 mouse_add_new_marker (where);
1423 case CdMarkerBarItem:
1424 if (!_dragging_playhead) {
1425 // if we get here then a dragged range wasn't done
1426 snap_to_with_modifier (where, event, RoundNearest, true);
1427 mouse_add_new_marker (where, true);
1432 if (!_dragging_playhead) {
1433 snap_to_with_modifier (where, event);
1434 mouse_add_new_tempo_event (where);
1439 if (!_dragging_playhead) {
1440 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1445 case TimecodeRulerItem:
1446 case SamplesRulerItem:
1447 case MinsecRulerItem:
1458 switch (item_type) {
1461 /* check that we didn't drag before releasing, since
1462 its really annoying to create new control
1463 points when doing this.
1465 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1466 if (!were_dragging && arv) {
1467 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1468 arv->add_gain_point_event (item, event, with_guard_points);
1474 case AutomationTrackItem: {
1475 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1476 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1478 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1489 if (scrubbing_direction == 0) {
1490 /* no drag, just a click */
1491 switch (item_type) {
1493 play_selected_region ();
1499 /* make sure we stop */
1500 _session->request_transport_speed (0.0);
1509 /* do any (de)selection operations that should occur on button release */
1510 button_selection (item, event, item_type);
1520 switch (item_type) {
1522 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1524 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1527 // Button2 click is unused
1542 // x_style_paste (where, 1.0);
1563 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1570 /* by the time we reach here, entered_regionview and entered trackview
1571 * will have already been set as appropriate. Things are done this
1572 * way because this method isn't passed a pointer to a variable type of
1573 * thing that is entered (which may or may not be canvas item).
1574 * (e.g. the actual entered regionview)
1577 choose_canvas_cursor_on_entry (item_type);
1579 switch (item_type) {
1580 case ControlPointItem:
1581 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1582 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1585 fraction = 1.0 - (cp->get_y() / cp->line().height());
1587 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1588 _verbose_cursor->show ();
1593 if (mouse_mode == MouseDraw) {
1594 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1596 line->set_outline_color (ARDOUR_UI::config()->color ("entered gain line"));
1601 case AutomationLineItem:
1602 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1603 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1605 line->set_outline_color (ARDOUR_UI::config()->color ("entered automation line"));
1610 case AutomationTrackItem:
1611 AutomationTimeAxisView* atv;
1612 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1613 clear_entered_track = false;
1614 set_entered_track (atv);
1619 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1622 entered_marker = marker;
1623 marker->set_color_rgba (ARDOUR_UI::config()->color ("entered marker"));
1625 case MeterMarkerItem:
1626 case TempoMarkerItem:
1629 case FadeInHandleItem:
1630 case FadeInTrimHandleItem:
1631 if (mouse_mode == MouseObject) {
1632 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1634 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1635 rect->set_fill_color (rv->get_fill_color());
1640 case FadeOutHandleItem:
1641 case FadeOutTrimHandleItem:
1642 if (mouse_mode == MouseObject) {
1643 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1645 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1646 rect->set_fill_color (rv->get_fill_color ());
1651 case FeatureLineItem:
1653 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1654 line->set_outline_color (0xFF0000FF);
1665 /* third pass to handle entered track status in a comprehensible way.
1668 switch (item_type) {
1670 case AutomationLineItem:
1671 case ControlPointItem:
1672 /* these do not affect the current entered track state */
1673 clear_entered_track = false;
1676 case AutomationTrackItem:
1677 /* handled above already */
1689 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1697 if (!_enter_stack.empty()) {
1698 _enter_stack.pop_back();
1701 switch (item_type) {
1702 case ControlPointItem:
1703 _verbose_cursor->hide ();
1707 case AutomationLineItem:
1708 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1710 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1712 line->set_outline_color (al->get_line_color());
1718 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1722 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1723 location_flags_changed (loc);
1726 case MeterMarkerItem:
1727 case TempoMarkerItem:
1730 case FadeInTrimHandleItem:
1731 case FadeOutTrimHandleItem:
1732 case FadeInHandleItem:
1733 case FadeOutHandleItem:
1735 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1737 rect->set_fill_color (ARDOUR_UI::config()->color ("inactive fade handle"));
1742 case AutomationTrackItem:
1745 case FeatureLineItem:
1747 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1748 line->set_outline_color (ARDOUR_UI::config()->color ("zero line"));
1760 Editor::scrub (framepos_t frame, double current_x)
1764 if (scrubbing_direction == 0) {
1766 _session->request_locate (frame, false);
1767 _session->request_transport_speed (0.1);
1768 scrubbing_direction = 1;
1772 if (last_scrub_x > current_x) {
1774 /* pointer moved to the left */
1776 if (scrubbing_direction > 0) {
1778 /* we reversed direction to go backwards */
1781 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1785 /* still moving to the left (backwards) */
1787 scrub_reversals = 0;
1788 scrub_reverse_distance = 0;
1790 delta = 0.01 * (last_scrub_x - current_x);
1791 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1795 /* pointer moved to the right */
1797 if (scrubbing_direction < 0) {
1798 /* we reversed direction to go forward */
1801 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1804 /* still moving to the right */
1806 scrub_reversals = 0;
1807 scrub_reverse_distance = 0;
1809 delta = 0.01 * (current_x - last_scrub_x);
1810 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1814 /* if there have been more than 2 opposite motion moves detected, or one that moves
1815 back more than 10 pixels, reverse direction
1818 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1820 if (scrubbing_direction > 0) {
1821 /* was forwards, go backwards */
1822 _session->request_transport_speed (-0.1);
1823 scrubbing_direction = -1;
1825 /* was backwards, go forwards */
1826 _session->request_transport_speed (0.1);
1827 scrubbing_direction = 1;
1830 scrub_reverse_distance = 0;
1831 scrub_reversals = 0;
1835 last_scrub_x = current_x;
1839 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1841 _last_motion_y = event->motion.y;
1843 if (event->motion.is_hint) {
1846 /* We call this so that MOTION_NOTIFY events continue to be
1847 delivered to the canvas. We need to do this because we set
1848 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1849 the density of the events, at the expense of a round-trip
1850 to the server. Given that this will mostly occur on cases
1851 where DISPLAY = :0.0, and given the cost of what the motion
1852 event might do, its a good tradeoff.
1855 _track_canvas->get_pointer (x, y);
1858 if (current_stepping_trackview) {
1859 /* don't keep the persistent stepped trackview if the mouse moves */
1860 current_stepping_trackview = 0;
1861 step_timeout.disconnect ();
1864 if (_session && _session->actively_recording()) {
1865 /* Sorry. no dragging stuff around while we record */
1869 update_join_object_range_location (event->motion.y);
1871 if (_drags->active ()) {
1872 return _drags->motion_handler (event, from_autoscroll);
1879 Editor::can_remove_control_point (ArdourCanvas::Item* item)
1881 ControlPoint* control_point;
1883 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1884 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1885 abort(); /*NOTREACHED*/
1888 AutomationLine& line = control_point->line ();
1889 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
1890 /* we shouldn't remove the first or last gain point in region gain lines */
1891 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
1900 Editor::remove_control_point (ArdourCanvas::Item* item)
1902 if (!can_remove_control_point (item)) {
1906 ControlPoint* control_point;
1908 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1909 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1910 abort(); /*NOTREACHED*/
1913 control_point->line().remove_point (*control_point);
1917 Editor::edit_control_point (ArdourCanvas::Item* item)
1919 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
1922 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1923 abort(); /*NOTREACHED*/
1926 ControlPointDialog d (p);
1929 if (d.run () != RESPONSE_ACCEPT) {
1933 p->line().modify_point_y (*p, d.get_y_fraction ());
1937 Editor::edit_notes (MidiRegionView* mrv)
1939 MidiRegionView::Selection const & s = mrv->selection();
1945 EditNoteDialog* d = new EditNoteDialog (mrv, s);
1949 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
1953 Editor::note_edit_done (int r, EditNoteDialog* d)
1960 Editor::visible_order_range (int* low, int* high) const
1962 *low = TimeAxisView::max_order ();
1965 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
1967 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
1969 if (!rtv->hidden()) {
1971 if (*high < rtv->order()) {
1972 *high = rtv->order ();
1975 if (*low > rtv->order()) {
1976 *low = rtv->order ();
1983 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
1985 /* Either add to or set the set the region selection, unless
1986 this is an alignment click (control used)
1989 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
1990 TimeAxisView* tv = &rv.get_time_axis_view();
1991 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
1993 if (rtv && rtv->is_track()) {
1994 speed = rtv->track()->speed();
1997 framepos_t where = get_preferred_edit_position();
2001 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2003 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2005 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2007 align_region (rv.region(), End, (framepos_t) (where * speed));
2011 align_region (rv.region(), Start, (framepos_t) (where * speed));
2018 Editor::collect_new_region_view (RegionView* rv)
2020 latest_regionviews.push_back (rv);
2024 Editor::collect_and_select_new_region_view (RegionView* rv)
2027 latest_regionviews.push_back (rv);
2031 Editor::cancel_selection ()
2033 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2034 (*i)->hide_selection ();
2037 selection->clear ();
2038 clicked_selection = 0;
2042 Editor::cancel_time_selection ()
2044 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2045 (*i)->hide_selection ();
2047 selection->time.clear ();
2048 clicked_selection = 0;
2052 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2054 RegionView* rv = clicked_regionview;
2056 /* Choose action dependant on which button was pressed */
2057 switch (event->button.button) {
2059 begin_reversible_command (_("start point trim"));
2061 if (selection->selected (rv)) {
2062 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2063 i != selection->regions.by_layer().end(); ++i)
2065 if (!(*i)->region()->locked()) {
2066 (*i)->region()->clear_changes ();
2067 (*i)->region()->trim_front (new_bound);
2068 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2073 if (!rv->region()->locked()) {
2074 rv->region()->clear_changes ();
2075 rv->region()->trim_front (new_bound);
2076 _session->add_command(new StatefulDiffCommand (rv->region()));
2080 commit_reversible_command();
2084 begin_reversible_command (_("End point trim"));
2086 if (selection->selected (rv)) {
2088 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2090 if (!(*i)->region()->locked()) {
2091 (*i)->region()->clear_changes();
2092 (*i)->region()->trim_end (new_bound);
2093 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2099 if (!rv->region()->locked()) {
2100 rv->region()->clear_changes ();
2101 rv->region()->trim_end (new_bound);
2102 _session->add_command (new StatefulDiffCommand (rv->region()));
2106 commit_reversible_command();
2115 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2120 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2121 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2122 abort(); /*NOTREACHED*/
2125 Location* location = find_location_from_marker (marker, is_start);
2126 location->set_hidden (true, this);
2130 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2132 using namespace Gtkmm2ext;
2134 ArdourPrompter prompter (false);
2136 prompter.set_prompt (_("Name for region:"));
2137 prompter.set_initial_text (clicked_regionview->region()->name());
2138 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2139 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2140 prompter.show_all ();
2141 switch (prompter.run ()) {
2142 case Gtk::RESPONSE_ACCEPT:
2144 prompter.get_result(str);
2146 clicked_regionview->region()->set_name (str);
2155 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2157 /* no brushing without a useful snap setting */
2159 switch (_snap_mode) {
2161 return; /* can't work because it allows region to be placed anywhere */
2166 switch (_snap_type) {
2174 /* don't brush a copy over the original */
2176 if (pos == rv->region()->position()) {
2180 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2182 if (rtv == 0 || !rtv->is_track()) {
2186 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2187 double speed = rtv->track()->speed();
2189 playlist->clear_changes ();
2190 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2191 playlist->add_region (new_region, (framepos_t) (pos * speed));
2192 _session->add_command (new StatefulDiffCommand (playlist));
2194 // playlist is frozen, so we have to update manually XXX this is disgusting
2196 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2200 Editor::track_height_step_timeout ()
2202 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2203 current_stepping_trackview = 0;
2210 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2212 assert (region_view);
2214 if (!region_view->region()->playlist()) {
2218 switch (Config->get_edit_mode()) {
2220 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2223 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2226 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2233 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2235 assert (region_view);
2237 if (!region_view->region()->playlist()) {
2241 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2245 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2247 assert (region_view);
2249 if (!region_view->region()->playlist()) {
2253 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2257 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2259 begin_reversible_command (Operations::drag_region_brush);
2262 /** Start a grab where a time range is selected, track(s) are selected, and the
2263 * user clicks and drags a region with a modifier in order to create a new region containing
2264 * the section of the clicked region that lies within the time range.
2267 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2269 if (clicked_regionview == 0) {
2273 /* lets try to create new Region for the selection */
2275 vector<boost::shared_ptr<Region> > new_regions;
2276 create_region_from_selection (new_regions);
2278 if (new_regions.empty()) {
2282 /* XXX fix me one day to use all new regions */
2284 boost::shared_ptr<Region> region (new_regions.front());
2286 /* add it to the current stream/playlist.
2288 tricky: the streamview for the track will add a new regionview. we will
2289 catch the signal it sends when it creates the regionview to
2290 set the regionview we want to then drag.
2293 latest_regionviews.clear();
2294 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2296 /* A selection grab currently creates two undo/redo operations, one for
2297 creating the new region and another for moving it.
2300 begin_reversible_command (Operations::selection_grab);
2302 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2304 playlist->clear_changes ();
2305 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2306 _session->add_command(new StatefulDiffCommand (playlist));
2310 if (latest_regionviews.empty()) {
2311 /* something went wrong */
2315 /* we need to deselect all other regionviews, and select this one
2316 i'm ignoring undo stuff, because the region creation will take care of it
2319 selection->set (latest_regionviews);
2321 commit_reversible_command ();
2323 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2329 if (_drags->active ()) {
2332 selection->clear ();
2338 /** Update _join_object_range_state which indicate whether we are over the top
2339 * or bottom half of a route view, used by the `join object/range' tool
2340 * mode. Coordinates in canvas space.
2343 Editor::update_join_object_range_location (double y)
2345 if (!get_smart_mode()) {
2346 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2350 JoinObjectRangeState const old = _join_object_range_state;
2352 if (mouse_mode == MouseObject) {
2353 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2354 } else if (mouse_mode == MouseRange) {
2355 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2358 if (entered_regionview) {
2360 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2361 double const c = item_space.y / entered_regionview->height();
2363 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2365 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2366 if (_join_object_range_state != old && ctx) {
2367 ctx->cursor_ctx->change(which_track_cursor());
2370 } else if (entered_track) {
2372 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2374 if (entered_route_view) {
2379 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2381 double track_height = entered_route_view->view()->child_height();
2382 if (ARDOUR_UI::config()->get_show_name_highlight()) {
2383 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2385 double const c = cy / track_height;
2389 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2391 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2395 /* Other kinds of tracks use object mode */
2396 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2399 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2400 if (_join_object_range_state != old && ctx) {
2401 ctx->cursor_ctx->change(which_track_cursor());
2407 Editor::effective_mouse_mode () const
2409 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2411 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2419 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2421 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2424 e->region_view().delete_note (e->note ());
2427 /** Obtain the pointer position in canvas coordinates */
2429 Editor::get_pointer_position (double& x, double& y) const
2432 _track_canvas->get_pointer (px, py);
2433 _track_canvas->window_to_canvas (px, py, x, y);