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"
38 #include "canvas/canvas.h"
40 #include "ardour/audioplaylist.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 "widgets/prompter.h"
53 #include "ardour_ui.h"
55 #include "time_axis_view.h"
56 #include "audio_time_axis.h"
57 #include "audio_region_view.h"
58 #include "midi_region_view.h"
60 #include "streamview.h"
61 #include "region_gain_line.h"
62 #include "rc_option_editor.h"
63 #include "automation_time_axis.h"
64 #include "control_point.h"
65 #include "selection.h"
68 #include "rgb_macros.h"
69 #include "control_point_dialog.h"
70 #include "editor_drag.h"
71 #include "automation_region_view.h"
72 #include "edit_note_dialog.h"
73 #include "mouse_cursors.h"
74 #include "editor_cursors.h"
75 #include "verbose_cursor.h"
81 using namespace ARDOUR;
84 using namespace Editing;
85 using Gtkmm2ext::Keyboard;
88 Editor::mouse_sample (samplepos_t& where, bool& in_track_canvas) const
90 /* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only
91 * pays attentions to subwindows. this means that menu windows are ignored, and
92 * if the pointer is in a menu, the return window from the call will be the
93 * the regular subwindow *under* the menu.
95 * this matters quite a lot if the pointer is moving around in a menu that overlaps
96 * the track canvas because we will believe that we are within the track canvas
97 * when we are not. therefore, we track enter/leave events for the track canvas
98 * and allow that to override the result of gdk_window_get_pointer().
101 if (!within_track_canvas) {
106 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->_track_canvas->get_window();
108 if (!canvas_window) {
112 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
114 if (!pointer_window) {
118 if (pointer_window != canvas_window) {
119 in_track_canvas = false;
123 in_track_canvas = true;
126 event.type = GDK_BUTTON_RELEASE;
130 where = window_event_sample (&event, 0, 0);
136 Editor::window_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
138 ArdourCanvas::Duple d;
140 if (!gdk_event_get_coords (event, &d.x, &d.y)) {
144 /* event coordinates are in window units, so convert to canvas
147 d = _track_canvas->window_to_canvas (d);
157 return pixel_to_sample (d.x);
161 Editor::canvas_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
166 /* event coordinates are already in canvas units */
168 if (!gdk_event_get_coords (event, &x, &y)) {
169 cerr << "!NO c COORDS for event type " << event->type << endl;
181 /* note that pixel_to_sample_from_event() never returns less than zero, so even if the pixel
182 position is negative (as can be the case with motion events in particular),
183 the sample location is always positive.
186 return pixel_to_sample_from_event (x);
190 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
192 boost::shared_ptr<Trimmable> st = _trimmable.lock();
194 if (!st || st == t) {
200 Editor::set_current_movable (boost::shared_ptr<Movable> m)
202 boost::shared_ptr<Movable> sm = _movable.lock();
204 if (!sm || sm != m) {
210 Editor::mouse_mode_object_range_toggled()
212 set_mouse_mode (mouse_mode, true); /* updates set-mouse-mode-range */
216 Editor::snap_mode_button_clicked (GdkEventButton* ev)
218 if (ev->button != 3) {
223 RCOptionEditor* rc_option_editor = ARDOUR_UI::instance()->get_rc_option_editor();
224 if (rc_option_editor) {
225 ARDOUR_UI::instance()->show_tabbable (rc_option_editor);
226 rc_option_editor->set_current_page (_("Editor/Snap"));
235 Editor::get_mouse_mode_action(MouseMode m) const
239 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
241 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
243 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
245 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
247 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
249 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-content"));
251 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
253 return Glib::RefPtr<Action>();
257 Editor::set_mouse_mode (MouseMode m, bool force)
259 if (_drags->active ()) {
263 if (!force && m == mouse_mode) {
267 if (ARDOUR::Profile->get_mixbus()) {
268 if (m == MouseCut) m = MouseObject;
269 if (m == MouseAudition) m = MouseRange;
272 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
273 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
275 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
276 tact->set_active (false);
277 tact->set_active (true);
279 /* NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting */
283 Editor::mouse_mode_toggled (MouseMode m)
285 if (ARDOUR::Profile->get_mixbus()) {
286 if (m == MouseCut) m = MouseObject;
287 if (m == MouseAudition) m = MouseRange;
290 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
291 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
293 if (!tact->get_active()) {
294 /* this was just the notification that the old mode has been
295 * left. we'll get called again with the new mode active in a
301 if (_session && mouse_mode == MouseAudition) {
302 /* stop transport and reset default speed to avoid oddness with
304 _session->request_transport_speed (0.0, true);
307 const bool was_internal = internal_editing();
311 /* Switch snap type/mode if we're moving to/from an internal tool. Note
312 this must toggle the actions and not call set_snap_*() directly,
313 otherwise things get out of sync and the combo box stops working. */
314 if (!UIConfiguration::instance().get_grid_follows_internal()) {
315 grid_type_action(pre_internal_grid_type)->set_active(true);
316 snap_mode_action(pre_internal_snap_mode)->set_active(true);
317 } else if (!was_internal && internal_editing()) {
318 grid_type_action(internal_grid_type)->set_active(true);
319 snap_mode_action(internal_snap_mode)->set_active(true);
320 } else if (was_internal && !internal_editing()) {
321 grid_type_action(pre_internal_grid_type)->set_active(true);
322 snap_mode_action(pre_internal_snap_mode)->set_active(true);
327 /* this should generate a new enter event which will
328 trigger the appropiate cursor.
332 _track_canvas->re_enter ();
335 set_gain_envelope_visibility ();
337 update_time_selection_display ();
339 update_all_enter_cursors ();
341 MouseModeChanged (); /* EMIT SIGNAL */
345 Editor::internal_editing() const
347 return mouse_mode == Editing::MouseContent || mouse_mode == Editing::MouseDraw;
351 Editor::update_time_selection_display ()
353 switch (mouse_mode) {
355 selection->clear_objects ();
356 selection->clear_midi_notes ();
359 selection->clear_time ();
360 selection->clear_midi_notes ();
363 /* Clear regions, but not time or tracks, since that
364 would destroy the range selection rectangle, which we need to stick
365 around for AutomationRangeDrag. */
366 selection->clear_regions ();
367 selection->clear_playlists ();
370 /* This handles internal edit.
371 Clear everything except points and notes.
373 selection->clear_regions();
374 selection->clear_lines();
375 selection->clear_playlists ();
377 selection->clear_time ();
378 selection->clear_tracks ();
382 /* We probably want to keep region selection */
383 selection->clear_points ();
384 selection->clear_lines();
385 selection->clear_playlists ();
387 selection->clear_time ();
388 selection->clear_tracks ();
392 /*Don't lose lines or points if no action in this mode */
393 selection->clear_regions ();
394 selection->clear_playlists ();
395 selection->clear_time ();
396 selection->clear_tracks ();
400 /*Clear everything */
401 selection->clear_objects();
402 selection->clear_time ();
403 selection->clear_tracks ();
409 Editor::step_mouse_mode (bool next)
411 const int n_mouse_modes = (int)MouseContent + 1;
412 int current = (int)current_mouse_mode();
414 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
416 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
421 Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
423 /* in object/audition/timefx/gain-automation mode,
424 * any button press sets the selection if the object
425 * can be selected. this is a bit of hack, because
426 * we want to avoid this if the mouse operation is a
429 * note: not dbl-click or triple-click
431 * Also note that there is no region selection in internal edit mode, otherwise
432 * for operations operating on the selection (e.g. cut) it is not obvious whether
433 * to cut notes or regions.
436 MouseMode eff_mouse_mode = effective_mouse_mode ();
438 if (eff_mouse_mode == MouseCut) {
439 /* never change selection in cut mode */
443 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
444 /* context clicks are always about object properties, even if
445 * we're in range mode within smart mode.
447 eff_mouse_mode = MouseObject;
450 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
451 if (get_smart_mode()) {
453 case FadeInHandleItem:
454 case FadeInTrimHandleItem:
455 case FadeOutHandleItem:
456 case FadeOutTrimHandleItem:
457 eff_mouse_mode = MouseObject;
464 if (((mouse_mode != MouseObject) &&
465 (mouse_mode != MouseAudition || item_type != RegionItem) &&
466 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
467 (mouse_mode != MouseDraw) &&
468 (mouse_mode != MouseContent || item_type == RegionItem)) ||
469 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
473 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
475 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
477 /* almost no selection action on modified button-2 or button-3 events */
479 if ((item_type != RegionItem && event->button.button != 2)
480 /* for selection of control points prior to delete (shift-right click) */
481 && !(item_type == ControlPointItem && event->button.button == 3 && event->type == GDK_BUTTON_PRESS)) {
487 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
488 bool press = (event->type == GDK_BUTTON_PRESS);
491 _mouse_changed_selection = false;
496 if (eff_mouse_mode == MouseDraw) {
500 if (eff_mouse_mode != MouseRange) {
501 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
503 /* don't change the selection unless the
504 * clicked track is not currently selected. if
505 * so, "collapse" the selection to just this track
507 if (!selection->selected (clicked_axisview)) {
508 set_selected_track_as_side_effect (Selection::Set);
512 if (eff_mouse_mode != MouseRange) {
513 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
518 case RegionViewNameHighlight:
520 case LeftFrameHandle:
521 case RightFrameHandle:
522 case FadeInHandleItem:
523 case FadeInTrimHandleItem:
525 case FadeOutHandleItem:
526 case FadeOutTrimHandleItem:
528 case StartCrossFadeItem:
529 case EndCrossFadeItem:
530 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
531 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
532 } else if (event->type == GDK_BUTTON_PRESS) {
533 set_selected_track_as_side_effect (op);
537 case ControlPointItem:
538 /* for object/track exclusivity, we don't call set_selected_track_as_side_effect (op); */
540 if (eff_mouse_mode != MouseRange) {
541 if (event->button.button != 3) {
542 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
544 _mouse_changed_selection |= set_selected_control_point_from_click (press, Selection::Set);
550 if (eff_mouse_mode != MouseRange) {
551 AutomationLine* argl = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
553 std::list<Selectable*> selectables;
554 uint32_t before, after;
555 samplecnt_t const where = (samplecnt_t) floor (event->button.x * samples_per_pixel) - clicked_regionview->region ()->position ();
557 if (!argl || !argl->control_points_adjacent (where, before, after)) {
561 selectables.push_back (argl->nth (before));
562 selectables.push_back (argl->nth (after));
567 selection->set (selectables);
568 _mouse_changed_selection = true;
573 selection->add (selectables);
574 _mouse_changed_selection = true;
577 case Selection::Toggle:
579 selection->toggle (selectables);
580 _mouse_changed_selection = true;
584 case Selection::Extend:
591 case AutomationLineItem:
592 if (eff_mouse_mode != MouseRange) {
593 AutomationLine* al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
594 std::list<Selectable*> selectables;
595 double mx = event->button.x;
596 double my = event->button.y;
598 al->grab_item().canvas_to_item (mx, my);
600 uint32_t before, after;
601 samplecnt_t const where = (samplecnt_t) floor (mx * samples_per_pixel);
603 if (!al || !al->control_points_adjacent (where, before, after)) {
607 selectables.push_back (al->nth (before));
608 selectables.push_back (al->nth (after));
613 selection->set (selectables);
614 _mouse_changed_selection = true;
619 selection->add (selectables);
620 _mouse_changed_selection = true;
623 case Selection::Toggle:
625 selection->toggle (selectables);
626 _mouse_changed_selection = true;
630 case Selection::Extend:
638 /* for context click, select track */
639 if (event->button.button == 3) {
640 selection->clear_tracks ();
641 set_selected_track_as_side_effect (op);
643 /* We won't get a release.*/
644 begin_reversible_selection_op (X_("Button 3 Menu Select"));
645 commit_reversible_selection_op ();
649 case AutomationTrackItem:
650 if (eff_mouse_mode != MouseDraw && op == Selection::Set) {
651 set_selected_track_as_side_effect (op);
656 if (press && event->button.button == 3) {
657 NoteBase* cnote = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
659 if (cnote->region_view().selection_size() == 0 || !cnote->selected()) {
660 selection->clear_points();
661 cnote->region_view().unique_select (cnote);
662 /* we won't get the release, so store the selection change now */
663 begin_reversible_selection_op (X_("Button 3 Note Selection"));
664 commit_reversible_selection_op ();
673 if ((!press) && _mouse_changed_selection) {
674 begin_reversible_selection_op (X_("Button Selection"));
675 commit_reversible_selection_op ();
676 _mouse_changed_selection = false;
681 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
683 /* single mouse clicks on any of these item types operate
684 * independent of mouse mode, mostly because they are
685 * not on the main track canvas or because we want
686 * them to be modeless.
689 NoteBase* note = NULL;
692 case PlayheadCursorItem:
693 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
697 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
698 hide_marker (item, event);
700 _drags->set (new MarkerDrag (this, item), event);
704 case TempoMarkerItem:
706 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
716 new TempoMarkerDrag (
719 ArdourKeyboard::indicates_copy (event->button.state)
728 case MeterMarkerItem:
731 new MeterMarkerDrag (
734 ArdourKeyboard::indicates_copy (event->button.state)
742 _drags->set (new VideoTimeLineDrag (this, item), event);
750 case TimecodeRulerItem:
751 case SamplesRulerItem:
752 case MinsecRulerItem:
754 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)
755 && !ArdourKeyboard::indicates_constraint (event->button.state)) {
756 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
757 } else if (ArdourKeyboard::indicates_constraint (event->button.state)
758 && Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
759 _drags->set (new TempoTwistDrag (this, item), event);
760 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
761 _drags->set (new BBTRulerDrag (this, item), event);
767 case RangeMarkerBarItem:
768 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
769 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
770 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
771 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
773 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
778 case CdMarkerBarItem:
779 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
780 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
782 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
787 case TransportMarkerBarItem:
788 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
789 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
791 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
800 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
801 /* special case: allow trim of range selections in joined object mode;
802 * in theory eff should equal MouseRange in this case, but it doesn't
803 * because entering the range selection canvas item results in entered_regionview
804 * being set to 0, so update_join_object_range_location acts as if we aren't
807 if (item_type == StartSelectionTrimItem) {
808 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
809 } else if (item_type == EndSelectionTrimItem) {
810 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
814 Editing::MouseMode eff = effective_mouse_mode ();
816 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
817 if (get_smart_mode()) {
819 case FadeInHandleItem:
820 case FadeInTrimHandleItem:
821 case FadeOutHandleItem:
822 case FadeOutTrimHandleItem:
833 case StartSelectionTrimItem:
834 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
837 case EndSelectionTrimItem:
838 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
842 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
843 start_selection_grab (item, event);
845 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
846 /* grab selection for moving */
847 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
849 /* this was debated, but decided the more common action was to make a new selection */
850 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
855 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
856 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
858 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
863 case RegionViewNameHighlight:
864 if (!clicked_regionview->region()->locked()) {
865 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
871 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
872 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
874 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
883 case FadeInHandleItem:
884 case FadeOutHandleItem:
885 case LeftFrameHandle:
886 case RightFrameHandle:
887 case FeatureLineItem:
888 case RegionViewNameHighlight:
891 case AutomationTrackItem:
892 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
903 /* Existing note: allow trimming/motion */
904 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
905 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
906 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
908 _drags->set (new NoteDrag (this, item), event);
914 _drags->set (new LineDrag (this, item), event);
918 case ControlPointItem:
919 _drags->set (new ControlPointDrag (this, item), event);
923 case AutomationLineItem:
924 _drags->set (new LineDrag (this, item), event);
929 /* in the past, we created a new midi region here, but perhaps that is best left to the Draw mode */
932 case AutomationTrackItem:
933 /* rubberband drag to select automation points */
934 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
939 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
940 /* rubberband drag to select automation points */
941 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
952 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
953 event->type == GDK_BUTTON_PRESS) {
955 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
957 } else if (event->type == GDK_BUTTON_PRESS) {
960 case FadeInHandleItem:
962 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
966 case FadeOutHandleItem:
968 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
972 case StartCrossFadeItem:
973 case EndCrossFadeItem:
974 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor.
975 * For not this is not fully implemented */
977 if (!clicked_regionview->region()->locked()) {
978 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
984 case FeatureLineItem:
986 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
987 remove_transient(item);
991 _drags->set (new FeatureLineDrag (this, item), event);
997 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
998 /* click on an automation region view; do nothing here and let the ARV's signal handler
1004 /* click on a normal region view */
1005 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1006 add_region_copy_drag (item, event, clicked_regionview);
1007 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
1008 add_region_brush_drag (item, event, clicked_regionview);
1010 add_region_drag (item, event, clicked_regionview);
1014 _drags->start_grab (event);
1018 case RegionViewNameHighlight:
1019 case LeftFrameHandle:
1020 case RightFrameHandle:
1021 if (!clicked_regionview->region()->locked()) {
1022 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1027 case FadeInTrimHandleItem:
1028 case FadeOutTrimHandleItem:
1029 if (!clicked_regionview->region()->locked()) {
1030 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
1035 case RegionViewName:
1037 /* rename happens on edit clicks */
1038 if (clicked_regionview->get_name_highlight()) {
1039 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1045 case ControlPointItem:
1046 _drags->set (new ControlPointDrag (this, item), event);
1050 case AutomationLineItem:
1051 _drags->set (new LineDrag (this, item), event);
1056 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1059 case AutomationTrackItem:
1061 TimeAxisView* parent = clicked_axisview->get_parent ();
1062 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1064 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1066 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1068 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1069 if (pl->n_regions() == 0) {
1070 /* Parent has no regions; create one so that we have somewhere to put automation */
1071 _drags->set (new RegionCreateDrag (this, item, parent), event);
1073 /* See if there's a region before the click that we can extend, and extend it if so */
1074 samplepos_t const t = canvas_event_sample (event);
1075 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1077 _drags->set (new RegionCreateDrag (this, item, parent), event);
1079 prev->set_length (t - prev->position (), get_grid_music_divisions (event->button.state));
1083 /* rubberband drag to select automation points */
1084 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1106 switch (item_type) {
1108 _drags->set (new LineDrag (this, item), event);
1111 case ControlPointItem:
1112 _drags->set (new ControlPointDrag (this, item), event);
1118 if (selection->time.empty ()) {
1122 pair<TimeAxisView*, int> tvp = trackview_by_y_position (event->button.y, false);
1124 /* clicked outside of a track */
1127 /* handle automation lanes first */
1128 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1130 /* smart "join" mode: drag automation */
1131 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1134 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1135 /* MIDI CC or similar -- TODO handle multiple? */
1136 list<RegionView*> rvl;
1137 rvl.push_back (clicked_regionview);
1138 _drags->set (new AutomationRangeDrag (this, rvl, selection->time,
1139 clicked_regionview->get_time_axis_view().y_position(),
1140 clicked_regionview->get_time_axis_view().current_height()),
1141 event, _cursors->up_down);
1145 /* shift+drag: only apply to clicked_regionview (if any) */
1146 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
1147 if (dynamic_cast<AudioRegionView*>(clicked_regionview) == 0) {
1150 list<RegionView*> rvl;
1151 rvl.push_back (clicked_regionview);
1152 // TODO: handle layer_display() == Stacked
1153 _drags->set (new AutomationRangeDrag (this, rvl, selection->time,
1154 clicked_regionview->get_time_axis_view().y_position(),
1155 clicked_regionview->get_time_axis_view().current_height()),
1156 event, _cursors->up_down);
1160 /* collect all audio regions-views in the given range selection */
1161 list<RegionView*> rvl;
1162 TrackViewList ts = selection->tracks.filter_to_unique_playlists ();
1163 for (TrackViewList::iterator i = ts.begin(); i != ts.end(); ++i) {
1164 RouteTimeAxisView* tatv;
1165 boost::shared_ptr<Playlist> playlist;
1166 if ((tatv = dynamic_cast<RouteTimeAxisView*> (*i)) == 0) {
1169 if ((playlist = (*i)->playlist()) == 0) {
1172 if (boost::dynamic_pointer_cast<AudioPlaylist> (playlist) == 0) {
1175 for (list<AudioRange>::const_iterator j = selection->time.begin(); j != selection->time.end(); ++j) {
1176 boost::shared_ptr<RegionList> rl = playlist->regions_touched (j->start, j->end);
1177 for (RegionList::iterator ir = rl->begin(); ir != rl->end(); ++ir) {
1179 if ((rv = tatv->view()->find_view (*ir)) != 0) {
1185 /* region-gain drag */
1186 if (!rvl.empty ()) {
1187 double y_pos = tvp.first->y_position();
1188 double height = tvp.first->current_height();
1189 StreamView* cv = tvp.first->view ();
1190 if (cv->layer_display() == Stacked && cv->layers() > 1) {
1191 height /= cv->layers();
1192 double yy = event->button.y - _trackview_group->canvas_origin().y;
1193 y_pos += floor ((yy - y_pos) / height) * height;
1195 _drags->set (new AutomationRangeDrag (this, rvl, selection->time, y_pos, height),
1196 event, _cursors->up_down);
1202 case AutomationLineItem:
1203 _drags->set (new LineDrag (this, item), event);
1207 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1208 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1209 /* Note is big and pointer is near the end, trim */
1210 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1213 _drags->set (new NoteDrag (this, item), event);
1220 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1221 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1232 if (item_type == NoteItem) {
1233 /* resize-drag notes */
1234 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1235 if (note->big_enough_to_trim()) {
1236 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1240 } else if (clicked_regionview) {
1242 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1248 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1249 scrub_reversals = 0;
1250 scrub_reverse_distance = 0;
1251 last_scrub_x = event->button.x;
1252 scrubbing_direction = 0;
1264 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1266 Editing::MouseMode const eff = effective_mouse_mode ();
1269 switch (item_type) {
1271 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1272 add_region_copy_drag (item, event, clicked_regionview);
1274 add_region_drag (item, event, clicked_regionview);
1276 _drags->start_grab (event);
1279 case ControlPointItem:
1280 _drags->set (new ControlPointDrag (this, item), event);
1288 switch (item_type) {
1289 case RegionViewNameHighlight:
1290 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1294 case LeftFrameHandle:
1295 case RightFrameHandle:
1296 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1300 case RegionViewName:
1301 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1315 /* relax till release */
1327 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1329 if (event->type == GDK_2BUTTON_PRESS) {
1330 _drags->mark_double_click ();
1331 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1335 if (event->type != GDK_BUTTON_PRESS) {
1339 _track_canvas->grab_focus();
1341 if (_session && _session->actively_recording()) {
1345 button_selection (item, event, item_type);
1347 if (!_drags->active () &&
1348 (Keyboard::is_delete_event (&event->button) ||
1349 Keyboard::is_context_menu_event (&event->button) ||
1350 Keyboard::is_edit_event (&event->button))) {
1352 /* handled by button release */
1356 /* not rolling, effectively in range mode, follow edits enabled (likely
1357 * to start range drag), not in a fade handle (since that means we are
1358 * not starting a range drag): locate the PH here
1361 if ((item_type != FadeInHandleItem) &&
1362 (item_type != FadeOutHandleItem) &&
1363 !_drags->active () &&
1365 !_session->transport_rolling() &&
1366 (effective_mouse_mode() == MouseRange) &&
1367 UIConfiguration::instance().get_follow_edits() &&
1368 !_session->config.get_external_sync()) {
1370 MusicSample where (canvas_event_sample (event), 0);
1372 _session->request_locate (where.sample, false);
1375 switch (event->button.button) {
1377 return button_press_handler_1 (item, event, item_type);
1381 return button_press_handler_2 (item, event, item_type);
1388 return button_press_dispatch (&event->button);
1397 Editor::button_press_dispatch (GdkEventButton* ev)
1399 /* this function is intended only for buttons 4 and above. */
1401 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1402 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1406 Editor::button_release_dispatch (GdkEventButton* ev)
1408 /* this function is intended only for buttons 4 and above. */
1410 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1411 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1415 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1417 MusicSample where (canvas_event_sample (event), 0);
1418 AutomationTimeAxisView* atv = 0;
1420 _press_cursor_ctx.reset();
1422 /* no action if we're recording */
1424 if (_session && _session->actively_recording()) {
1428 bool were_dragging = false;
1430 if (!Keyboard::is_context_menu_event (&event->button)) {
1432 /* see if we're finishing a drag */
1434 if (_drags->active ()) {
1435 bool const r = _drags->end_grab (event);
1437 /* grab dragged, so do nothing else */
1441 were_dragging = true;
1444 update_region_layering_order_editor ();
1447 /* edit events get handled here */
1449 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1450 switch (item_type) {
1452 show_region_properties ();
1454 case TempoMarkerItem: {
1455 ArdourMarker* marker;
1456 TempoMarker* tempo_marker;
1458 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1459 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1460 abort(); /*NOTREACHED*/
1463 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1464 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1465 abort(); /*NOTREACHED*/
1468 edit_tempo_marker (*tempo_marker);
1472 case MeterMarkerItem: {
1473 ArdourMarker* marker;
1474 MeterMarker* meter_marker;
1476 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1477 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1478 abort(); /*NOTREACHED*/
1481 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1482 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1483 abort(); /*NOTREACHED*/
1485 edit_meter_marker (*meter_marker);
1489 case RegionViewName:
1490 if (clicked_regionview->name_active()) {
1491 return mouse_rename_region (item, event);
1495 case ControlPointItem:
1496 edit_control_point (item);
1505 /* context menu events get handled here */
1506 if (Keyboard::is_context_menu_event (&event->button)) {
1508 context_click_event = *event;
1510 if (!_drags->active ()) {
1512 /* no matter which button pops up the context menu, tell the menu
1513 widget to use button 1 to drive menu selection.
1516 switch (item_type) {
1518 case FadeInHandleItem:
1519 case FadeInTrimHandleItem:
1520 case StartCrossFadeItem:
1521 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1525 case FadeOutHandleItem:
1526 case FadeOutTrimHandleItem:
1527 case EndCrossFadeItem:
1528 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1531 case LeftFrameHandle:
1532 case RightFrameHandle:
1536 popup_track_context_menu (1, event->button.time, item_type, false);
1540 case RegionViewNameHighlight:
1541 case RegionViewName:
1542 popup_track_context_menu (1, event->button.time, item_type, false);
1546 popup_track_context_menu (1, event->button.time, item_type, true);
1549 case AutomationTrackItem:
1550 popup_track_context_menu (1, event->button.time, item_type, false);
1554 case RangeMarkerBarItem:
1555 case TransportMarkerBarItem:
1556 case CdMarkerBarItem:
1558 case TempoCurveItem:
1561 case TimecodeRulerItem:
1562 case SamplesRulerItem:
1563 case MinsecRulerItem:
1565 popup_ruler_menu (where.sample, item_type);
1569 marker_context_menu (&event->button, item);
1572 case TempoMarkerItem:
1573 tempo_or_meter_marker_context_menu (&event->button, item);
1576 case MeterMarkerItem:
1577 tempo_or_meter_marker_context_menu (&event->button, item);
1580 case CrossfadeViewItem:
1581 popup_track_context_menu (1, event->button.time, item_type, false);
1584 case ControlPointItem:
1585 popup_control_point_context_menu (item, event);
1589 if (internal_editing()) {
1590 popup_note_context_menu (item, event);
1602 /* delete events get handled here */
1604 Editing::MouseMode const eff = effective_mouse_mode ();
1606 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1608 switch (item_type) {
1609 case TempoMarkerItem:
1610 remove_tempo_marker (item);
1613 case MeterMarkerItem:
1614 remove_meter_marker (item);
1618 remove_marker (*item, event);
1622 if (eff == MouseObject) {
1623 remove_clicked_region ();
1627 case ControlPointItem:
1628 remove_control_point (item);
1632 remove_midi_note (item, event);
1641 switch (event->button.button) {
1644 switch (item_type) {
1645 /* see comments in button_press_handler */
1646 case PlayheadCursorItem:
1649 case AutomationLineItem:
1650 case StartSelectionTrimItem:
1651 case EndSelectionTrimItem:
1655 if (!_dragging_playhead) {
1656 snap_to_with_modifier (where, event, RoundNearest, SnapToGrid_Scaled);
1657 mouse_add_new_marker (where.sample);
1661 case CdMarkerBarItem:
1662 if (!_dragging_playhead) {
1663 /* if we get here then a dragged range wasn't done */
1664 snap_to_with_modifier (where, event, RoundNearest, SnapToGrid_Scaled);
1665 mouse_add_new_marker (where.sample, true);
1669 case TempoCurveItem:
1670 if (!_dragging_playhead && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1671 snap_to_with_modifier (where, event);
1672 mouse_add_new_tempo_event (where.sample);
1677 if (!_dragging_playhead && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1678 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1683 case TimecodeRulerItem:
1684 case SamplesRulerItem:
1685 case MinsecRulerItem:
1696 switch (item_type) {
1699 /* check that we didn't drag before releasing, since
1700 its really annoying to create new control
1701 points when doing this.
1703 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1704 if (!were_dragging && arv) {
1705 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1706 arv->add_gain_point_event (item, event, with_guard_points);
1712 case AutomationTrackItem: {
1713 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1714 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1716 atv->add_automation_event (event, where.sample, event->button.y, with_guard_points);
1727 if (scrubbing_direction == 0) {
1728 /* no drag, just a click */
1729 switch (item_type) {
1731 play_selected_region ();
1736 } else if (_session) {
1737 /* make sure we stop */
1738 _session->request_transport_speed (0.0);
1747 /* do any (de)selection operations that should occur on button release */
1748 button_selection (item, event, item_type);
1758 switch (item_type) {
1760 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1762 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1765 /* Button2 click is unused */
1780 // x_style_paste (where, 1.0);
1801 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1804 ArdourMarker * marker;
1805 MeterMarker* m_marker = 0;
1806 TempoMarker* t_marker = 0;
1810 /* by the time we reach here, entered_regionview and entered trackview
1811 * will have already been set as appropriate. Things are done this
1812 * way because this method isn't passed a pointer to a variable type of
1813 * thing that is entered (which may or may not be canvas item).
1814 * (e.g. the actual entered regionview)
1817 choose_canvas_cursor_on_entry (item_type);
1819 switch (item_type) {
1820 case ControlPointItem:
1821 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1822 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1825 fraction = 1.0 - (cp->get_y() / cp->line().height());
1827 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1828 _verbose_cursor->show ();
1833 if (mouse_mode == MouseDraw) {
1834 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1836 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1841 case AutomationLineItem:
1842 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1843 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1845 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1850 case AutomationTrackItem:
1851 AutomationTimeAxisView* atv;
1852 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1853 clear_entered_track = false;
1854 set_entered_track (atv);
1859 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1862 entered_marker = marker;
1863 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1866 case MeterMarkerItem:
1867 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1870 entered_marker = m_marker;
1871 if (m_marker->meter().position_lock_style() == MusicTime) {
1872 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1874 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1878 case TempoMarkerItem:
1879 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1882 entered_marker = t_marker;
1883 if (t_marker->tempo().position_lock_style() == MusicTime) {
1884 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1886 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1890 case FadeInHandleItem:
1891 case FadeInTrimHandleItem:
1892 if (mouse_mode == MouseObject) {
1893 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1895 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1896 rect->set_fill_color (rv->get_fill_color());
1901 case FadeOutHandleItem:
1902 case FadeOutTrimHandleItem:
1903 if (mouse_mode == MouseObject) {
1904 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1906 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1907 rect->set_fill_color (rv->get_fill_color ());
1912 case FeatureLineItem:
1914 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1915 line->set_outline_color (0xFF0000FF);
1924 if (entered_regionview) {
1925 entered_regionview->entered();
1934 /* third pass to handle entered track status in a comprehensible way.
1937 switch (item_type) {
1939 case AutomationLineItem:
1940 case ControlPointItem:
1941 /* these do not affect the current entered track state */
1942 clear_entered_track = false;
1945 case AutomationTrackItem:
1946 /* handled above already */
1958 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1961 ArdourMarker *marker;
1962 TempoMarker *t_marker;
1963 MeterMarker *m_marker;
1968 if (!_enter_stack.empty()) {
1969 _enter_stack.pop_back();
1972 switch (item_type) {
1973 case ControlPointItem:
1974 _verbose_cursor->hide ();
1978 case AutomationLineItem:
1979 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1981 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1983 line->set_outline_color (al->get_line_color());
1989 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1993 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1994 location_flags_changed (loc);
1998 case MeterMarkerItem:
1999 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
2003 if (m_marker->meter().position_lock_style() == MusicTime) {
2004 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
2006 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
2010 case TempoMarkerItem:
2011 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
2015 if (t_marker->tempo().position_lock_style() == MusicTime) {
2016 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
2018 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
2022 case FadeInTrimHandleItem:
2023 case FadeOutTrimHandleItem:
2024 case FadeInHandleItem:
2025 case FadeOutHandleItem:
2027 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
2029 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
2034 case AutomationTrackItem:
2037 case FeatureLineItem:
2039 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
2040 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
2052 Editor::scrub (samplepos_t sample, double current_x)
2056 if (scrubbing_direction == 0) {
2058 _session->request_locate (sample, false);
2059 _session->request_transport_speed (0.1);
2060 scrubbing_direction = 1;
2064 if (last_scrub_x > current_x) {
2066 /* pointer moved to the left */
2068 if (scrubbing_direction > 0) {
2070 /* we reversed direction to go backwards */
2073 scrub_reverse_distance += (int) (last_scrub_x - current_x);
2077 /* still moving to the left (backwards) */
2079 scrub_reversals = 0;
2080 scrub_reverse_distance = 0;
2082 delta = 0.01 * (last_scrub_x - current_x);
2083 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
2087 /* pointer moved to the right */
2089 if (scrubbing_direction < 0) {
2090 /* we reversed direction to go forward */
2093 scrub_reverse_distance += (int) (current_x - last_scrub_x);
2096 /* still moving to the right */
2098 scrub_reversals = 0;
2099 scrub_reverse_distance = 0;
2101 delta = 0.01 * (current_x - last_scrub_x);
2102 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
2106 /* if there have been more than 2 opposite motion moves detected, or one that moves
2107 back more than 10 pixels, reverse direction
2110 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
2112 if (scrubbing_direction > 0) {
2113 /* was forwards, go backwards */
2114 _session->request_transport_speed (-0.1);
2115 scrubbing_direction = -1;
2117 /* was backwards, go forwards */
2118 _session->request_transport_speed (0.1);
2119 scrubbing_direction = 1;
2122 scrub_reverse_distance = 0;
2123 scrub_reversals = 0;
2127 last_scrub_x = current_x;
2131 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2133 _last_motion_y = event->motion.y;
2135 if (event->motion.is_hint) {
2138 /* We call this so that MOTION_NOTIFY events continue to be
2139 * delivered to the canvas. We need to do this because we set
2140 * Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2141 * the density of the events, at the expense of a round-trip
2142 * to the server. Given that this will mostly occur on cases
2143 * where DISPLAY = :0.0, and given the cost of what the motion
2144 * event might do, its a good tradeoff.
2147 _track_canvas->get_pointer (x, y);
2150 if (current_stepping_trackview) {
2151 /* don't keep the persistent stepped trackview if the mouse moves */
2152 current_stepping_trackview = 0;
2153 step_timeout.disconnect ();
2156 if (_session && _session->actively_recording()) {
2157 /* Sorry. no dragging stuff around while we record */
2161 update_join_object_range_location (event->motion.y);
2163 if (_drags->active ()) {
2164 //drags change the snapped_cursor location, because we are snapping the thing being dragged, not the actual mouse cursor
2165 return _drags->motion_handler (event, from_autoscroll);
2167 //the snapped_cursor shows where an operation (like Split) is going to occur
2169 MusicSample where (0, 0);
2170 if (mouse_sample (where.sample, ignored)) {
2171 snap_to_with_modifier (where, event);
2172 set_snapped_cursor_position (where.sample);
2180 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2182 ControlPoint* control_point;
2184 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2185 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2186 abort(); /*NOTREACHED*/
2189 AutomationLine& line = control_point->line ();
2190 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2191 /* we shouldn't remove the first or last gain point in region gain lines */
2192 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2201 Editor::remove_control_point (ArdourCanvas::Item* item)
2203 if (!can_remove_control_point (item)) {
2207 ControlPoint* control_point;
2209 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2210 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2211 abort(); /*NOTREACHED*/
2214 control_point->line().remove_point (*control_point);
2218 Editor::edit_control_point (ArdourCanvas::Item* item)
2220 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2223 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2224 abort(); /*NOTREACHED*/
2227 ControlPointDialog d (p);
2229 if (d.run () != RESPONSE_ACCEPT) {
2233 p->line().modify_point_y (*p, d.get_y_fraction ());
2237 Editor::edit_notes (MidiRegionView* mrv)
2239 MidiRegionView::Selection const & s = mrv->selection();
2245 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2248 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2252 Editor::note_edit_done (int r, EditNoteDialog* d)
2259 Editor::edit_region (RegionView* rv)
2261 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
2262 temporal_zoom_selection (Both);
2264 rv->show_region_editor ();
2269 Editor::visible_order_range (int* low, int* high) const
2271 *low = TimeAxisView::max_order ();
2274 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2276 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2278 if (rtv && !rtv->hidden()) {
2280 if (*high < rtv->order()) {
2281 *high = rtv->order ();
2284 if (*low > rtv->order()) {
2285 *low = rtv->order ();
2292 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2294 /* Either add to or set the set the region selection, unless
2295 * this is an alignment click (control used)
2298 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2300 samplepos_t where = get_preferred_edit_position();
2304 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2306 align_region (rv.region(), SyncPoint, where);
2308 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2310 align_region (rv.region(), End, where);
2314 align_region (rv.region(), Start, where);
2321 Editor::collect_new_region_view (RegionView* rv)
2323 latest_regionviews.push_back (rv);
2327 Editor::collect_and_select_new_region_view (RegionView* rv)
2330 latest_regionviews.push_back (rv);
2334 Editor::cancel_selection ()
2336 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2337 (*i)->hide_selection ();
2340 selection->clear ();
2341 clicked_selection = 0;
2345 Editor::cancel_time_selection ()
2347 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2348 (*i)->hide_selection ();
2350 selection->time.clear ();
2351 clicked_selection = 0;
2355 Editor::point_trim (GdkEvent* event, samplepos_t new_bound)
2357 RegionView* rv = clicked_regionview;
2359 /* Choose action dependant on which button was pressed */
2360 switch (event->button.button) {
2362 begin_reversible_command (_("start point trim"));
2364 if (selection->selected (rv)) {
2365 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2366 i != selection->regions.by_layer().end(); ++i)
2368 if (!(*i)->region()->locked()) {
2369 (*i)->region()->clear_changes ();
2370 (*i)->region()->trim_front (new_bound);
2371 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2376 if (!rv->region()->locked()) {
2377 rv->region()->clear_changes ();
2378 rv->region()->trim_front (new_bound);
2379 _session->add_command(new StatefulDiffCommand (rv->region()));
2383 commit_reversible_command();
2387 begin_reversible_command (_("end point trim"));
2389 if (selection->selected (rv)) {
2391 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2393 if (!(*i)->region()->locked()) {
2394 (*i)->region()->clear_changes();
2395 (*i)->region()->trim_end (new_bound);
2396 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2402 if (!rv->region()->locked()) {
2403 rv->region()->clear_changes ();
2404 rv->region()->trim_end (new_bound);
2405 _session->add_command (new StatefulDiffCommand (rv->region()));
2409 commit_reversible_command();
2418 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2420 ArdourMarker* marker;
2423 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2424 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2425 abort(); /*NOTREACHED*/
2428 Location* location = find_location_from_marker (marker, is_start);
2429 location->set_hidden (true, this);
2433 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2435 using namespace Gtkmm2ext;
2437 ArdourWidgets::Prompter prompter (false);
2439 prompter.set_prompt (_("Name for region:"));
2440 prompter.set_initial_text (clicked_regionview->region()->name());
2441 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2442 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2443 prompter.show_all ();
2444 switch (prompter.run ()) {
2445 case Gtk::RESPONSE_ACCEPT:
2447 prompter.get_result(str);
2449 clicked_regionview->region()->set_name (str);
2458 Editor::mouse_brush_insert_region (RegionView* rv, samplepos_t pos)
2460 /* no brushing without a useful quantize setting */
2461 if (_grid_type == GridTypeNone)
2464 /* don't brush a copy over the original */
2466 if (pos == rv->region()->position()) {
2470 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2472 if (!rtv || !rtv->is_track()) {
2476 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2478 playlist->clear_changes ();
2479 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2480 playlist->add_region (new_region, pos);
2481 _session->add_command (new StatefulDiffCommand (playlist));
2483 /* playlist is frozen, so we have to update manually XXX this is disgusting */
2485 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2489 Editor::track_height_step_timeout ()
2491 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2492 current_stepping_trackview = 0;
2499 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2501 assert (region_view);
2503 if (!region_view->region()->playlist()) {
2507 switch (Config->get_edit_mode()) {
2509 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2512 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2515 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2522 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2524 assert (region_view);
2526 if (!region_view->region()->playlist()) {
2530 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2534 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2536 assert (region_view);
2538 if (!region_view->region()->playlist()) {
2542 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2546 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2549 /** Start a grab where a time range is selected, track(s) are selected, and the
2550 * user clicks and drags a region with a modifier in order to create a new region containing
2551 * the section of the clicked region that lies within the time range.
2554 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2556 if (clicked_regionview == 0) {
2560 /* lets try to create new Region for the selection */
2562 vector<boost::shared_ptr<Region> > new_regions;
2563 create_region_from_selection (new_regions);
2565 if (new_regions.empty()) {
2569 /* XXX fix me one day to use all new regions */
2571 boost::shared_ptr<Region> region (new_regions.front());
2573 /* add it to the current stream/playlist.
2575 * tricky: the streamview for the track will add a new regionview. we will
2576 * catch the signal it sends when it creates the regionview to
2577 * set the regionview we want to then drag.
2580 latest_regionviews.clear();
2581 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2583 /* A selection grab currently creates two undo/redo operations, one for
2584 * creating the new region and another for moving it.
2586 begin_reversible_command (Operations::selection_grab);
2588 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2590 playlist->clear_changes ();
2591 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2592 _session->add_command(new StatefulDiffCommand (playlist));
2596 if (latest_regionviews.empty()) {
2597 /* something went wrong */
2598 abort_reversible_command ();
2602 /* we need to deselect all other regionviews, and select this one
2603 * i'm ignoring undo stuff, because the region creation will take care of it
2606 selection->set (latest_regionviews);
2608 commit_reversible_command ();
2610 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2616 if (_drags->active ()) {
2618 } else if (_session) {
2619 selection->clear ();
2621 /* if session is playing a range, cancel that */
2622 if (_session->get_play_range()) {
2623 _session->request_cancel_play_range();
2626 if (_session->solo_selection_active()) {
2628 _session->solo_selection (sl, false);
2632 ARDOUR_UI::instance()->reset_focus (&contents());
2635 /** Update _join_object_range_state which indicate whether we are over the top
2636 * or bottom half of a route view, used by the `join object/range' tool
2637 * mode. Coordinates in canvas space.
2640 Editor::update_join_object_range_location (double y)
2642 if (!get_smart_mode()) {
2643 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2647 JoinObjectRangeState const old = _join_object_range_state;
2649 if (mouse_mode == MouseObject) {
2650 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2651 } else if (mouse_mode == MouseRange) {
2652 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2655 if (entered_regionview) {
2657 /* TODO: there is currently a bug here(?)
2658 * when we are inside a region fade handle, it acts as though we are in range mode because it is in the top half of the region
2659 * can it be fixed here?
2662 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2663 double const c = item_space.y / entered_regionview->height();
2665 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2667 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2668 if (_join_object_range_state != old && ctx) {
2669 ctx->cursor_ctx->change(which_track_cursor());
2672 } else if (entered_track) {
2674 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2676 if (entered_route_view) {
2681 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2683 double track_height = entered_route_view->view()->child_height();
2684 if (UIConfiguration::instance().get_show_name_highlight()) {
2685 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2687 double const c = cy / track_height;
2691 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2693 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2697 /* Other kinds of tracks use object mode */
2698 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2701 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2702 if (_join_object_range_state != old && ctx) {
2703 ctx->cursor_ctx->change(which_track_cursor());
2709 Editor::effective_mouse_mode () const
2711 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2713 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2721 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2723 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2726 e->region_view().delete_note (e->note ());
2729 /** Obtain the pointer position in canvas coordinates */
2731 Editor::get_pointer_position (double& x, double& y) const
2734 _track_canvas->get_pointer (px, py);
2735 _track_canvas->window_to_canvas (px, py, x, y);