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 Glib::RefPtr<Action> act;
239 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
243 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
247 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
251 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
255 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-gain"));
259 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-zoom"));
263 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
267 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
273 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
276 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
277 tact->set_active (false);
278 tact->set_active (true);
280 //NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting
284 Editor::mouse_mode_toggled (MouseMode m)
286 Glib::RefPtr<Action> act;
287 Glib::RefPtr<ToggleAction> tact;
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-gain"));
311 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-zoom"));
315 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
319 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
325 tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
328 if (!tact->get_active()) {
329 /* this was just the notification that the old mode has been
330 * left. we'll get called again with the new mode active in a
338 act = ActionManager::get_action (X_("MouseMode"), X_("toggle-internal-edit"));
339 tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
340 tact->set_active (true);
346 if (_session && mouse_mode == MouseAudition) {
347 /* stop transport and reset default speed to avoid oddness with
349 _session->request_transport_speed (0.0, true);
356 /* this should generate a new enter event which will
357 trigger the appropiate cursor.
361 _track_canvas->re_enter ();
364 set_gain_envelope_visibility ();
366 update_time_selection_display ();
368 MouseModeChanged (); /* EMIT SIGNAL */
372 Editor::update_time_selection_display ()
374 if (smart_mode_action->get_active()) {
375 /* not sure what to do here */
376 if (mouse_mode == MouseObject) {
380 switch (mouse_mode) {
382 selection->clear_objects ();
385 selection->clear_time ();
392 Editor::step_mouse_mode (bool next)
394 switch (current_mouse_mode()) {
397 if (Profile->get_sae()) {
398 set_mouse_mode (MouseZoom);
400 set_mouse_mode (MouseRange);
403 set_mouse_mode (MouseTimeFX);
408 if (next) set_mouse_mode (MouseDraw);
409 else set_mouse_mode (MouseCut);
413 if (next) set_mouse_mode (MouseRange);
414 else set_mouse_mode (MouseDraw);
418 if (next) set_mouse_mode (MouseCut);
419 else set_mouse_mode (MouseRange);
424 if (Profile->get_sae()) {
425 set_mouse_mode (MouseTimeFX);
427 set_mouse_mode (MouseGain);
430 if (Profile->get_sae()) {
431 set_mouse_mode (MouseObject);
433 set_mouse_mode (MouseDraw);
439 if (next) set_mouse_mode (MouseTimeFX);
440 else set_mouse_mode (MouseZoom);
445 set_mouse_mode (MouseAudition);
447 if (Profile->get_sae()) {
448 set_mouse_mode (MouseZoom);
450 set_mouse_mode (MouseGain);
456 if (next) set_mouse_mode (MouseObject);
457 else set_mouse_mode (MouseTimeFX);
463 Editor::toggle_internal_editing_from_double_click (GdkEvent* event)
465 if (_drags->active()) {
466 _drags->end_grab (event);
469 ActionManager::do_action ("MouseMode", "toggle-internal-edit");
471 /* prevent reversion of edit cursor on button release */
473 pre_press_cursor = 0;
479 Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemType item_type)
481 /* in object/audition/timefx/gain-automation mode,
482 any button press sets the selection if the object
483 can be selected. this is a bit of hack, because
484 we want to avoid this if the mouse operation is a
487 note: not dbl-click or triple-click
489 Also note that there is no region selection in internal edit mode, otherwise
490 for operations operating on the selection (e.g. cut) it is not obvious whether
491 to cut notes or regions.
494 MouseMode eff_mouse_mode = effective_mouse_mode ();
496 if (eff_mouse_mode == MouseCut) {
497 /* never change selection in cut mode */
501 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
502 /* context clicks are always about object properties, even if
503 we're in range mode within smart mode.
505 eff_mouse_mode = MouseObject;
508 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
509 if (get_smart_mode()) {
511 case FadeInHandleItem:
512 case FadeInTrimHandleItem:
513 case FadeOutHandleItem:
514 case FadeOutTrimHandleItem:
515 eff_mouse_mode = MouseObject;
522 if (((mouse_mode != MouseObject) &&
523 (mouse_mode != MouseAudition || item_type != RegionItem) &&
524 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
525 (mouse_mode != MouseGain) &&
526 (mouse_mode != MouseDraw)) ||
527 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3) ||
528 (internal_editing() && mouse_mode != MouseTimeFX)) {
533 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
535 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
537 /* almost no selection action on modified button-2 or button-3 events */
539 if (item_type != RegionItem && event->button.button != 2) {
545 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
546 bool press = (event->type == GDK_BUTTON_PRESS);
551 if (eff_mouse_mode != MouseRange) {
552 set_selected_regionview_from_click (press, op);
554 /* don't change the selection unless the
555 clicked track is not currently selected. if
556 so, "collapse" the selection to just this
559 if (!selection->selected (clicked_axisview)) {
560 set_selected_track_as_side_effect (Selection::Set);
564 if (eff_mouse_mode != MouseRange) {
565 set_selected_regionview_from_click (press, op);
570 case RegionViewNameHighlight:
572 case LeftFrameHandle:
573 case RightFrameHandle:
574 case FadeInHandleItem:
575 case FadeInTrimHandleItem:
577 case FadeOutHandleItem:
578 case FadeOutTrimHandleItem:
580 case StartCrossFadeItem:
581 case EndCrossFadeItem:
582 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
583 set_selected_regionview_from_click (press, op);
584 } else if (event->type == GDK_BUTTON_PRESS) {
585 set_selected_track_as_side_effect (op);
589 case ControlPointItem:
590 set_selected_track_as_side_effect (op);
591 if (eff_mouse_mode != MouseRange) {
592 set_selected_control_point_from_click (press, op);
597 /* for context click, select track */
598 if (event->button.button == 3) {
599 selection->clear_tracks ();
600 set_selected_track_as_side_effect (op);
604 case AutomationTrackItem:
605 set_selected_track_as_side_effect (op);
614 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
616 /* single mouse clicks on any of these item types operate
617 independent of mouse mode, mostly because they are
618 not on the main track canvas or because we want
623 case PlayheadCursorItem:
624 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
628 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
629 hide_marker (item, event);
631 _drags->set (new MarkerDrag (this, item), event);
635 case TempoMarkerItem:
637 TempoMarker* m = reinterpret_cast<TempoMarker*> (item->get_data ("marker"));
640 new TempoMarkerDrag (
643 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
650 case MeterMarkerItem:
652 MeterMarker* m = reinterpret_cast<MeterMarker*> (item->get_data ("marker"));
655 new MeterMarkerDrag (
658 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
666 _drags->set (new VideoTimeLineDrag (this, item), event);
673 case TimecodeRulerItem:
674 case SamplesRulerItem:
675 case MinsecRulerItem:
677 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
678 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
684 case RangeMarkerBarItem:
685 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
686 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
688 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
693 case CdMarkerBarItem:
694 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
695 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
697 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
702 case TransportMarkerBarItem:
703 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
704 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
706 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
715 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
716 /* special case: allow trim of range selections in joined object mode;
717 in theory eff should equal MouseRange in this case, but it doesn't
718 because entering the range selection canvas item results in entered_regionview
719 being set to 0, so update_join_object_range_location acts as if we aren't
722 if (item_type == StartSelectionTrimItem) {
723 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
724 } else if (item_type == EndSelectionTrimItem) {
725 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
729 Editing::MouseMode eff = effective_mouse_mode ();
731 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
732 if (get_smart_mode()) {
734 case FadeInHandleItem:
735 case FadeInTrimHandleItem:
736 case FadeOutHandleItem:
737 case FadeOutTrimHandleItem:
745 /* there is no Range mode when in internal edit mode */
746 if (eff == MouseRange && internal_editing()) {
753 case StartSelectionTrimItem:
754 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
757 case EndSelectionTrimItem:
758 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
762 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
763 start_selection_grab (item, event);
765 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
766 /* grab selection for moving */
767 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
769 double const y = event->button.y;
770 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
772 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
773 if ( get_smart_mode() && atv) {
774 /* smart "join" mode: drag automation */
775 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
777 /* this was debated, but decided the more common action was to
778 make a new selection */
779 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
786 if (internal_editing()) {
787 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
788 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
792 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
793 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
795 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
801 case RegionViewNameHighlight:
802 if (!clicked_regionview->region()->locked()) {
803 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
809 if (!internal_editing()) {
810 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
811 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
813 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
823 /* Existing note: allow trimming/motion */
824 if (internal_editing()) {
825 /* trim notes if we're in internal edit mode and near the ends of the note */
826 NoteBase* cn = reinterpret_cast<NoteBase*>(item->get_data ("notebase"));
828 if (cn->big_enough_to_trim() && cn->mouse_near_ends()) {
829 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
831 _drags->set (new NoteDrag (this, item), event);
837 if (internal_editing()) {
838 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
839 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
853 case FadeInHandleItem:
854 case FadeOutHandleItem:
855 case LeftFrameHandle:
856 case RightFrameHandle:
857 case FeatureLineItem:
858 case RegionViewNameHighlight:
861 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, current_canvas_cursor);
872 /* Existing note: allow trimming/motion */
873 if (internal_editing()) {
874 NoteBase* cn = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
876 if (cn->big_enough_to_trim() && cn->mouse_near_ends()) {
877 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
879 _drags->set (new NoteDrag (this, item), event);
889 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
890 event->type == GDK_BUTTON_PRESS) {
892 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
894 } else if (event->type == GDK_BUTTON_PRESS) {
897 case FadeInHandleItem:
899 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
903 case FadeOutHandleItem:
905 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
909 case StartCrossFadeItem:
910 case EndCrossFadeItem:
911 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
912 // if (!clicked_regionview->region()->locked()) {
913 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
918 case FeatureLineItem:
920 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
921 remove_transient(item);
925 _drags->set (new FeatureLineDrag (this, item), event);
931 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
932 /* click on an automation region view; do nothing here and let the ARV's signal handler
938 if (internal_editing ()) {
942 /* click on a normal region view */
943 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
944 add_region_copy_drag (item, event, clicked_regionview);
945 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
946 add_region_brush_drag (item, event, clicked_regionview);
948 add_region_drag (item, event, clicked_regionview);
952 // if (!internal_editing() && (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE && !selection->regions.empty())) {
953 // _drags->add (new SelectionDrag (this, clicked_axisview->get_selection_rect (clicked_selection)->rect, SelectionDrag::SelectionMove));
956 _drags->start_grab (event);
960 case RegionViewNameHighlight:
961 case LeftFrameHandle:
962 case RightFrameHandle:
963 if (!clicked_regionview->region()->locked()) {
964 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
969 case FadeInTrimHandleItem:
970 case FadeOutTrimHandleItem:
971 if (!clicked_regionview->region()->locked()) {
972 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
979 /* rename happens on edit clicks */
980 if (clicked_regionview->get_name_highlight()) {
981 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
987 case ControlPointItem:
988 _drags->set (new ControlPointDrag (this, item), event);
992 case AutomationLineItem:
993 _drags->set (new LineDrag (this, item), event);
998 if (internal_editing()) {
999 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1000 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1004 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1008 case AutomationTrackItem:
1010 TimeAxisView* parent = clicked_axisview->get_parent ();
1011 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1013 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1015 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1017 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1018 if (pl->n_regions() == 0) {
1019 /* Parent has no regions; create one so that we have somewhere to put automation */
1020 _drags->set (new RegionCreateDrag (this, item, parent), event);
1022 /* See if there's a region before the click that we can extend, and extend it if so */
1023 framepos_t const t = canvas_event_sample (event);
1024 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1026 _drags->set (new RegionCreateDrag (this, item, parent), event);
1028 prev->set_length (t - prev->position ());
1032 /* rubberband drag to select automation points */
1033 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1040 if ( get_smart_mode() ) {
1041 /* we're in "smart" joined mode, and we've clicked on a Selection */
1042 double const y = event->button.y;
1043 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
1045 /* if we're over an automation track, start a drag of its data */
1046 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1048 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1051 /* if we're over a track and a region, and in the `object' part of a region,
1052 put a selection around the region and drag both
1054 /* RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
1055 if (rtv && _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
1056 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (rtv->route ());
1058 boost::shared_ptr<Playlist> pl = t->playlist ();
1061 boost::shared_ptr<Region> r = pl->top_region_at (canvas_event_sample (event));
1063 RegionView* rv = rtv->view()->find_view (r);
1064 clicked_selection = select_range (rv->region()->position(),
1065 rv->region()->last_frame()+1);
1066 _drags->add (new SelectionDrag (this, item, SelectionDrag::SelectionMove));
1067 list<RegionView*> rvs;
1069 _drags->add (new RegionMoveDrag (this, item, rv, rvs, false, false));
1070 _drags->start_grab (event);
1094 switch (item_type) {
1096 _drags->set (new LineDrag (this, item), event);
1099 case ControlPointItem:
1100 _drags->set (new ControlPointDrag (this, item), event);
1106 AudioRegionView* arv = dynamic_cast<AudioRegionView *> (clicked_regionview);
1108 _drags->set (new AutomationRangeDrag (this, arv, selection->time), event, _cursors->up_down);
1109 _drags->start_grab (event);
1115 case AutomationLineItem:
1116 _drags->set (new LineDrag (this, item), event);
1126 if (event->type == GDK_BUTTON_PRESS) {
1127 _drags->set (new MouseZoomDrag (this, item), event);
1134 if (internal_editing() && item_type == NoteItem ) {
1135 /* drag notes if we're in internal edit mode */
1136 NoteBase* cn = reinterpret_cast<NoteBase*>(item->get_data ("notebase"));
1138 if (cn->big_enough_to_trim()) {
1139 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
1142 } else if (clicked_regionview) {
1144 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1150 _drags->set (new ScrubDrag (this, item), event);
1151 scrub_reversals = 0;
1152 scrub_reverse_distance = 0;
1153 last_scrub_x = event->button.x;
1154 scrubbing_direction = 0;
1155 push_canvas_cursor (_cursors->transparent);
1167 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1169 Editing::MouseMode const eff = effective_mouse_mode ();
1172 switch (item_type) {
1174 if (internal_editing ()) {
1175 /* no region drags in internal edit mode */
1179 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1180 add_region_copy_drag (item, event, clicked_regionview);
1182 add_region_drag (item, event, clicked_regionview);
1184 _drags->start_grab (event);
1187 case ControlPointItem:
1188 _drags->set (new ControlPointDrag (this, item), event);
1196 switch (item_type) {
1197 case RegionViewNameHighlight:
1198 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1202 case LeftFrameHandle:
1203 case RightFrameHandle:
1204 if (!internal_editing ()) {
1205 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1210 case RegionViewName:
1211 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1225 /* relax till release */
1231 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1232 temporal_zoom_to_frame (false, canvas_event_sample (event));
1234 temporal_zoom_to_frame (true, canvas_event_sample(event));
1247 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1249 if (event->type == GDK_2BUTTON_PRESS) {
1250 _drags->mark_double_click ();
1251 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1255 if (event->type != GDK_BUTTON_PRESS) {
1259 pre_press_cursor = current_canvas_cursor;
1261 _track_canvas->grab_focus();
1263 if (_session && _session->actively_recording()) {
1267 if (internal_editing()) {
1268 bool leave_internal_edit_mode = false;
1270 switch (item_type) {
1275 if (!dynamic_cast<MidiRegionView*> (clicked_regionview) && !dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
1276 leave_internal_edit_mode = true;
1280 case PlayheadCursorItem:
1282 case TempoMarkerItem:
1283 case MeterMarkerItem:
1287 case RangeMarkerBarItem:
1288 case CdMarkerBarItem:
1289 case TransportMarkerBarItem:
1291 case TimecodeRulerItem:
1292 case SamplesRulerItem:
1293 case MinsecRulerItem:
1295 /* button press on these items never does anything to
1296 change the editing mode.
1304 if (leave_internal_edit_mode) {
1305 ActionManager::do_action ("MouseMode", "toggle-internal-edit");
1309 button_selection (item, event, item_type);
1311 if (!_drags->active () &&
1312 (Keyboard::is_delete_event (&event->button) ||
1313 Keyboard::is_context_menu_event (&event->button) ||
1314 Keyboard::is_edit_event (&event->button))) {
1316 /* handled by button release */
1320 //not rolling, range mode click + join_play_range : locate the PH here
1321 if ( !_drags->active () && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && Config->get_follow_edits() ) {
1322 framepos_t where = canvas_event_sample (event);
1324 _session->request_locate (where, false);
1327 switch (event->button.button) {
1329 return button_press_handler_1 (item, event, item_type);
1333 return button_press_handler_2 (item, event, item_type);
1340 return button_press_dispatch (&event->button);
1349 Editor::button_press_dispatch (GdkEventButton* ev)
1351 /* this function is intended only for buttons 4 and above.
1354 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1355 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1359 Editor::button_release_dispatch (GdkEventButton* ev)
1361 /* this function is intended only for buttons 4 and above.
1364 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1365 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1369 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1371 framepos_t where = canvas_event_sample (event);
1372 AutomationTimeAxisView* atv = 0;
1374 if (pre_press_cursor) {
1375 set_canvas_cursor (pre_press_cursor);
1376 pre_press_cursor = 0;
1379 /* no action if we're recording */
1381 if (_session && _session->actively_recording()) {
1385 /* see if we're finishing a drag */
1387 bool were_dragging = false;
1388 if (_drags->active ()) {
1389 bool const r = _drags->end_grab (event);
1391 /* grab dragged, so do nothing else */
1395 were_dragging = true;
1398 update_region_layering_order_editor ();
1400 /* edit events get handled here */
1402 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1403 switch (item_type) {
1405 show_region_properties ();
1408 case TempoMarkerItem: {
1410 TempoMarker* tempo_marker;
1412 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
1413 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1417 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1418 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1422 edit_tempo_marker (*tempo_marker);
1426 case MeterMarkerItem: {
1428 MeterMarker* meter_marker;
1430 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
1431 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1435 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1436 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1439 edit_meter_marker (*meter_marker);
1443 case RegionViewName:
1444 if (clicked_regionview->name_active()) {
1445 return mouse_rename_region (item, event);
1449 case ControlPointItem:
1450 edit_control_point (item);
1459 /* context menu events get handled here */
1460 if (Keyboard::is_context_menu_event (&event->button)) {
1462 context_click_event = *event;
1464 if (!_drags->active ()) {
1466 /* no matter which button pops up the context menu, tell the menu
1467 widget to use button 1 to drive menu selection.
1470 switch (item_type) {
1472 case FadeInHandleItem:
1473 case FadeInTrimHandleItem:
1474 case StartCrossFadeItem:
1475 case LeftFrameHandle:
1476 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1480 case FadeOutHandleItem:
1481 case FadeOutTrimHandleItem:
1482 case EndCrossFadeItem:
1483 case RightFrameHandle:
1484 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1488 popup_track_context_menu (1, event->button.time, item_type, false);
1492 case RegionViewNameHighlight:
1493 case RegionViewName:
1494 popup_track_context_menu (1, event->button.time, item_type, false);
1498 popup_track_context_menu (1, event->button.time, item_type, true);
1501 case AutomationTrackItem:
1502 popup_track_context_menu (1, event->button.time, item_type, false);
1506 case RangeMarkerBarItem:
1507 case TransportMarkerBarItem:
1508 case CdMarkerBarItem:
1512 case TimecodeRulerItem:
1513 case SamplesRulerItem:
1514 case MinsecRulerItem:
1516 popup_ruler_menu (where, item_type);
1520 marker_context_menu (&event->button, item);
1523 case TempoMarkerItem:
1524 tempo_or_meter_marker_context_menu (&event->button, item);
1527 case MeterMarkerItem:
1528 tempo_or_meter_marker_context_menu (&event->button, item);
1531 case CrossfadeViewItem:
1532 popup_track_context_menu (1, event->button.time, item_type, false);
1535 case ControlPointItem:
1536 popup_control_point_context_menu (item, event);
1547 /* delete events get handled here */
1549 Editing::MouseMode const eff = effective_mouse_mode ();
1551 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1553 switch (item_type) {
1554 case TempoMarkerItem:
1555 remove_tempo_marker (item);
1558 case MeterMarkerItem:
1559 remove_meter_marker (item);
1563 remove_marker (*item, event);
1567 if (eff == MouseObject) {
1568 remove_clicked_region ();
1572 case ControlPointItem:
1573 remove_control_point (item);
1577 remove_midi_note (item, event);
1586 switch (event->button.button) {
1589 switch (item_type) {
1590 /* see comments in button_press_handler */
1591 case PlayheadCursorItem:
1594 case AutomationLineItem:
1595 case StartSelectionTrimItem:
1596 case EndSelectionTrimItem:
1600 if (!_dragging_playhead) {
1601 snap_to_with_modifier (where, event, 0, true);
1602 mouse_add_new_marker (where);
1606 case CdMarkerBarItem:
1607 if (!_dragging_playhead) {
1608 // if we get here then a dragged range wasn't done
1609 snap_to_with_modifier (where, event, 0, true);
1610 mouse_add_new_marker (where, true);
1615 if (!_dragging_playhead) {
1616 snap_to_with_modifier (where, event);
1617 mouse_add_new_tempo_event (where);
1622 if (!_dragging_playhead) {
1623 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1628 case TimecodeRulerItem:
1629 case SamplesRulerItem:
1630 case MinsecRulerItem:
1641 switch (item_type) {
1642 case AutomationTrackItem:
1643 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1645 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1646 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1656 switch (item_type) {
1659 /* check that we didn't drag before releasing, since
1660 its really annoying to create new control
1661 points when doing this.
1663 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1664 if (!were_dragging && arv) {
1665 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1666 arv->add_gain_point_event (item, event, with_guard_points);
1672 case AutomationTrackItem: {
1673 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1674 dynamic_cast<AutomationTimeAxisView*>(clicked_axisview)->
1675 add_automation_event (event, where, event->button.y, with_guard_points);
1685 pop_canvas_cursor ();
1686 if (scrubbing_direction == 0) {
1687 /* no drag, just a click */
1688 switch (item_type) {
1690 play_selected_region ();
1696 /* make sure we stop */
1697 _session->request_transport_speed (0.0);
1706 /* do any (de)selection operations that should occur on button release */
1707 button_selection (item, event, item_type);
1716 switch (item_type) {
1718 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1720 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1723 // Button2 click is unused
1738 // x_style_paste (where, 1.0);
1759 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1766 /* by the time we reach here, entered_regionview and entered trackview
1767 * will have already been set as appropriate. Things are done this
1768 * way because this method isn't passed a pointer to a variable type of
1769 * thing that is entered (which may or may not be canvas item).
1770 * (e.g. the actual entered regionview)
1773 choose_canvas_cursor_on_entry (&event->crossing, item_type);
1775 switch (item_type) {
1776 case ControlPointItem:
1777 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1778 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1781 fraction = 1.0 - (cp->get_y() / cp->line().height());
1783 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1784 _verbose_cursor->show ();
1789 if (mouse_mode == MouseGain) {
1790 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1792 line->set_outline_color (ARDOUR_UI::config()->get_canvasvar_EnteredGainLine());
1797 case AutomationLineItem:
1798 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1799 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1801 line->set_outline_color (ARDOUR_UI::config()->get_canvasvar_EnteredAutomationLine());
1806 case AutomationTrackItem:
1807 AutomationTimeAxisView* atv;
1808 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1809 clear_entered_track = false;
1810 set_entered_track (atv);
1815 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1818 entered_marker = marker;
1819 marker->set_color_rgba (ARDOUR_UI::config()->get_canvasvar_EnteredMarker());
1821 case MeterMarkerItem:
1822 case TempoMarkerItem:
1825 case FadeInHandleItem:
1826 case FadeInTrimHandleItem:
1827 if (mouse_mode == MouseObject && !internal_editing()) {
1828 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1830 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1831 rect->set_fill_color (rv->get_fill_color());
1836 case FadeOutHandleItem:
1837 case FadeOutTrimHandleItem:
1838 if (mouse_mode == MouseObject && !internal_editing()) {
1839 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1841 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1842 rect->set_fill_color (rv->get_fill_color ());
1847 case FeatureLineItem:
1849 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1850 line->set_outline_color (0xFF0000FF);
1861 /* third pass to handle entered track status in a comprehensible way.
1864 switch (item_type) {
1866 case AutomationLineItem:
1867 case ControlPointItem:
1868 /* these do not affect the current entered track state */
1869 clear_entered_track = false;
1872 case AutomationTrackItem:
1873 /* handled above already */
1885 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1893 switch (item_type) {
1894 case ControlPointItem:
1895 _verbose_cursor->hide ();
1899 case AutomationLineItem:
1900 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1902 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1904 line->set_outline_color (al->get_line_color());
1910 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1914 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1915 location_flags_changed (loc, this);
1918 case MeterMarkerItem:
1919 case TempoMarkerItem:
1922 case FadeInTrimHandleItem:
1923 case FadeOutTrimHandleItem:
1924 case FadeInHandleItem:
1925 case FadeOutHandleItem:
1927 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1929 rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_InactiveFadeHandle());
1934 case AutomationTrackItem:
1937 case FeatureLineItem:
1939 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1940 line->set_outline_color (ARDOUR_UI::config()->get_canvasvar_ZeroLine());
1952 Editor::scrub (framepos_t frame, double current_x)
1956 if (scrubbing_direction == 0) {
1958 _session->request_locate (frame, false);
1959 _session->request_transport_speed (0.1);
1960 scrubbing_direction = 1;
1964 if (last_scrub_x > current_x) {
1966 /* pointer moved to the left */
1968 if (scrubbing_direction > 0) {
1970 /* we reversed direction to go backwards */
1973 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1977 /* still moving to the left (backwards) */
1979 scrub_reversals = 0;
1980 scrub_reverse_distance = 0;
1982 delta = 0.01 * (last_scrub_x - current_x);
1983 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1987 /* pointer moved to the right */
1989 if (scrubbing_direction < 0) {
1990 /* we reversed direction to go forward */
1993 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1996 /* still moving to the right */
1998 scrub_reversals = 0;
1999 scrub_reverse_distance = 0;
2001 delta = 0.01 * (current_x - last_scrub_x);
2002 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
2006 /* if there have been more than 2 opposite motion moves detected, or one that moves
2007 back more than 10 pixels, reverse direction
2010 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
2012 if (scrubbing_direction > 0) {
2013 /* was forwards, go backwards */
2014 _session->request_transport_speed (-0.1);
2015 scrubbing_direction = -1;
2017 /* was backwards, go forwards */
2018 _session->request_transport_speed (0.1);
2019 scrubbing_direction = 1;
2022 scrub_reverse_distance = 0;
2023 scrub_reversals = 0;
2027 last_scrub_x = current_x;
2031 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2033 _last_motion_y = event->motion.y;
2035 if (event->motion.is_hint) {
2038 /* We call this so that MOTION_NOTIFY events continue to be
2039 delivered to the canvas. We need to do this because we set
2040 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2041 the density of the events, at the expense of a round-trip
2042 to the server. Given that this will mostly occur on cases
2043 where DISPLAY = :0.0, and given the cost of what the motion
2044 event might do, its a good tradeoff.
2047 _track_canvas->get_pointer (x, y);
2050 if (current_stepping_trackview) {
2051 /* don't keep the persistent stepped trackview if the mouse moves */
2052 current_stepping_trackview = 0;
2053 step_timeout.disconnect ();
2056 if (_session && _session->actively_recording()) {
2057 /* Sorry. no dragging stuff around while we record */
2061 update_join_object_range_location (event->motion.y);
2063 if (_drags->active ()) {
2064 return _drags->motion_handler (event, from_autoscroll);
2071 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2073 ControlPoint* control_point;
2075 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2076 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2080 AutomationLine& line = control_point->line ();
2081 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2082 /* we shouldn't remove the first or last gain point in region gain lines */
2083 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2092 Editor::remove_control_point (ArdourCanvas::Item* item)
2094 if (!can_remove_control_point (item)) {
2098 ControlPoint* control_point;
2100 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2101 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2105 control_point->line().remove_point (*control_point);
2109 Editor::edit_control_point (ArdourCanvas::Item* item)
2111 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2114 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2118 ControlPointDialog d (p);
2121 if (d.run () != RESPONSE_ACCEPT) {
2125 p->line().modify_point_y (*p, d.get_y_fraction ());
2129 Editor::edit_notes (TimeAxisViewItem& tavi)
2131 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(&tavi);
2137 MidiRegionView::Selection const & s = mrv->selection();
2143 EditNoteDialog* d = new EditNoteDialog (&(*s.begin())->region_view(), s);
2147 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2151 Editor::note_edit_done (int r, EditNoteDialog* d)
2158 Editor::visible_order_range (int* low, int* high) const
2160 *low = TimeAxisView::max_order ();
2163 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2165 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2167 if (!rtv->hidden()) {
2169 if (*high < rtv->order()) {
2170 *high = rtv->order ();
2173 if (*low > rtv->order()) {
2174 *low = rtv->order ();
2181 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2183 /* Either add to or set the set the region selection, unless
2184 this is an alignment click (control used)
2187 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2188 TimeAxisView* tv = &rv.get_time_axis_view();
2189 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2191 if (rtv && rtv->is_track()) {
2192 speed = rtv->track()->speed();
2195 framepos_t where = get_preferred_edit_position();
2199 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2201 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2203 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2205 align_region (rv.region(), End, (framepos_t) (where * speed));
2209 align_region (rv.region(), Start, (framepos_t) (where * speed));
2216 Editor::collect_new_region_view (RegionView* rv)
2218 latest_regionviews.push_back (rv);
2222 Editor::collect_and_select_new_region_view (RegionView* rv)
2225 latest_regionviews.push_back (rv);
2229 Editor::cancel_selection ()
2231 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2232 (*i)->hide_selection ();
2235 selection->clear ();
2236 clicked_selection = 0;
2240 Editor::cancel_time_selection ()
2242 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2243 (*i)->hide_selection ();
2245 selection->time.clear ();
2246 clicked_selection = 0;
2250 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2252 RegionView* rv = clicked_regionview;
2254 /* Choose action dependant on which button was pressed */
2255 switch (event->button.button) {
2257 begin_reversible_command (_("start point trim"));
2259 if (selection->selected (rv)) {
2260 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2261 i != selection->regions.by_layer().end(); ++i)
2263 if (!(*i)->region()->locked()) {
2264 (*i)->region()->clear_changes ();
2265 (*i)->region()->trim_front (new_bound);
2266 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2271 if (!rv->region()->locked()) {
2272 rv->region()->clear_changes ();
2273 rv->region()->trim_front (new_bound);
2274 _session->add_command(new StatefulDiffCommand (rv->region()));
2278 commit_reversible_command();
2282 begin_reversible_command (_("End point trim"));
2284 if (selection->selected (rv)) {
2286 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2288 if (!(*i)->region()->locked()) {
2289 (*i)->region()->clear_changes();
2290 (*i)->region()->trim_end (new_bound);
2291 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2297 if (!rv->region()->locked()) {
2298 rv->region()->clear_changes ();
2299 rv->region()->trim_end (new_bound);
2300 _session->add_command (new StatefulDiffCommand (rv->region()));
2304 commit_reversible_command();
2313 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2318 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2319 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2323 Location* location = find_location_from_marker (marker, is_start);
2324 location->set_hidden (true, this);
2329 Editor::reposition_zoom_rect (framepos_t start, framepos_t end)
2331 double x1 = sample_to_pixel (start);
2332 double x2 = sample_to_pixel (end);
2333 double y2 = _full_canvas_height - 1.0;
2335 zoom_rect->set (ArdourCanvas::Rect (x1, 1.0, x2, y2));
2340 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2342 using namespace Gtkmm2ext;
2344 ArdourPrompter prompter (false);
2346 prompter.set_prompt (_("Name for region:"));
2347 prompter.set_initial_text (clicked_regionview->region()->name());
2348 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2349 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2350 prompter.show_all ();
2351 switch (prompter.run ()) {
2352 case Gtk::RESPONSE_ACCEPT:
2354 prompter.get_result(str);
2356 clicked_regionview->region()->set_name (str);
2365 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2367 /* no brushing without a useful snap setting */
2369 switch (_snap_mode) {
2371 return; /* can't work because it allows region to be placed anywhere */
2376 switch (_snap_type) {
2384 /* don't brush a copy over the original */
2386 if (pos == rv->region()->position()) {
2390 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2392 if (rtv == 0 || !rtv->is_track()) {
2396 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2397 double speed = rtv->track()->speed();
2399 playlist->clear_changes ();
2400 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2401 playlist->add_region (new_region, (framepos_t) (pos * speed));
2402 _session->add_command (new StatefulDiffCommand (playlist));
2404 // playlist is frozen, so we have to update manually XXX this is disgusting
2406 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2410 Editor::track_height_step_timeout ()
2412 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2413 current_stepping_trackview = 0;
2420 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2422 assert (region_view);
2424 if (!region_view->region()->playlist()) {
2428 switch (Config->get_edit_mode()) {
2430 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2433 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2436 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2443 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2445 assert (region_view);
2447 if (!region_view->region()->playlist()) {
2451 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2455 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2457 assert (region_view);
2459 if (!region_view->region()->playlist()) {
2463 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2467 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2469 begin_reversible_command (Operations::drag_region_brush);
2472 /** Start a grab where a time range is selected, track(s) are selected, and the
2473 * user clicks and drags a region with a modifier in order to create a new region containing
2474 * the section of the clicked region that lies within the time range.
2477 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2479 if (clicked_regionview == 0) {
2483 /* lets try to create new Region for the selection */
2485 vector<boost::shared_ptr<Region> > new_regions;
2486 create_region_from_selection (new_regions);
2488 if (new_regions.empty()) {
2492 /* XXX fix me one day to use all new regions */
2494 boost::shared_ptr<Region> region (new_regions.front());
2496 /* add it to the current stream/playlist.
2498 tricky: the streamview for the track will add a new regionview. we will
2499 catch the signal it sends when it creates the regionview to
2500 set the regionview we want to then drag.
2503 latest_regionviews.clear();
2504 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2506 /* A selection grab currently creates two undo/redo operations, one for
2507 creating the new region and another for moving it.
2510 begin_reversible_command (Operations::selection_grab);
2512 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2514 playlist->clear_changes ();
2515 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2516 _session->add_command(new StatefulDiffCommand (playlist));
2518 commit_reversible_command ();
2522 if (latest_regionviews.empty()) {
2523 /* something went wrong */
2527 /* we need to deselect all other regionviews, and select this one
2528 i'm ignoring undo stuff, because the region creation will take care of it
2530 selection->set (latest_regionviews);
2532 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2538 if (_drags->active ()) {
2541 selection->clear ();
2546 Editor::set_internal_edit (bool yn)
2548 if (_internal_editing == yn) {
2552 _internal_editing = yn;
2555 pre_internal_mouse_mode = mouse_mode;
2556 pre_internal_snap_type = _snap_type;
2557 pre_internal_snap_mode = _snap_mode;
2559 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2560 (*i)->enter_internal_edit_mode ();
2563 set_snap_to (internal_snap_type);
2564 set_snap_mode (internal_snap_mode);
2568 internal_snap_mode = _snap_mode;
2569 internal_snap_type = _snap_type;
2571 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2572 (*i)->leave_internal_edit_mode ();
2575 if (mouse_mode == MouseDraw && pre_internal_mouse_mode != MouseDraw) {
2576 /* we were drawing .. flip back to something sensible */
2577 set_mouse_mode (pre_internal_mouse_mode);
2580 set_snap_to (pre_internal_snap_type);
2581 set_snap_mode (pre_internal_snap_mode);
2584 reset_canvas_cursor ();
2587 /** Update _join_object_range_state which indicate whether we are over the top
2588 * or bottom half of a route view, used by the `join object/range' tool
2589 * mode. Coordinates in canvas space.
2592 Editor::update_join_object_range_location (double y)
2594 if (_internal_editing || !get_smart_mode()) {
2595 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2599 JoinObjectRangeState const old = _join_object_range_state;
2601 if (mouse_mode == MouseObject) {
2602 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2603 } else if (mouse_mode == MouseRange) {
2604 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2607 if (entered_regionview) {
2609 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2610 double const c = item_space.y / entered_regionview->height();
2612 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2614 if (_join_object_range_state != old) {
2615 set_canvas_cursor (which_track_cursor ());
2618 } else if (entered_track) {
2620 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2622 if (entered_route_view) {
2627 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2629 double track_height = entered_route_view->view()->child_height();
2630 if (Config->get_show_name_highlight()) {
2631 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2633 double const c = cy / track_height;
2637 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2639 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2643 /* Other kinds of tracks use object mode */
2644 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2647 if (_join_object_range_state != old) {
2648 set_canvas_cursor (which_track_cursor ());
2654 Editor::effective_mouse_mode () const
2656 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2658 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2666 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2668 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2671 e->region_view().delete_note (e->note ());
2674 /** Obtain the pointer position in canvas coordinates */
2676 Editor::get_pointer_position (double& x, double& y) const
2679 _track_canvas->get_pointer (px, py);
2680 _track_canvas->window_to_canvas (px, py, x, y);