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/audioregion.h"
41 #include "ardour/operations.h"
42 #include "ardour/playlist.h"
43 #include "ardour/profile.h"
44 #include "ardour/region_factory.h"
45 #include "ardour/route.h"
46 #include "ardour/session.h"
47 #include "ardour/types.h"
51 #include "time_axis_view.h"
52 #include "audio_time_axis.h"
53 #include "audio_region_view.h"
54 #include "midi_region_view.h"
56 #include "streamview.h"
57 #include "region_gain_line.h"
58 #include "automation_time_axis.h"
59 #include "control_point.h"
61 #include "selection.h"
64 #include "rgb_macros.h"
65 #include "control_point_dialog.h"
66 #include "editor_drag.h"
67 #include "automation_region_view.h"
68 #include "edit_note_dialog.h"
69 #include "mouse_cursors.h"
70 #include "editor_cursors.h"
71 #include "verbose_cursor.h"
77 using namespace ARDOUR;
80 using namespace Editing;
81 using Gtkmm2ext::Keyboard;
84 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
86 /* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only
87 pays attentions to subwindows. this means that menu windows are ignored, and
88 if the pointer is in a menu, the return window from the call will be the
89 the regular subwindow *under* the menu.
91 this matters quite a lot if the pointer is moving around in a menu that overlaps
92 the track canvas because we will believe that we are within the track canvas
93 when we are not. therefore, we track enter/leave events for the track canvas
94 and allow that to override the result of gdk_window_get_pointer().
97 if (!within_track_canvas) {
102 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->_track_canvas->get_window();
104 if (!canvas_window) {
108 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
110 if (!pointer_window) {
114 if (pointer_window != canvas_window) {
115 in_track_canvas = false;
119 in_track_canvas = true;
122 event.type = GDK_BUTTON_RELEASE;
126 where = window_event_sample (&event, 0, 0);
132 Editor::window_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
134 ArdourCanvas::Duple d;
136 if (!gdk_event_get_coords (event, &d.x, &d.y)) {
140 /* event coordinates are in window units, so convert to canvas
143 d = _track_canvas->window_to_canvas (d);
153 return pixel_to_sample (d.x);
157 Editor::canvas_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
162 /* event coordinates are already in canvas units */
164 if (!gdk_event_get_coords (event, &x, &y)) {
165 cerr << "!NO c COORDS for event type " << event->type << endl;
177 /* note that pixel_to_sample_from_event() never returns less than zero, so even if the pixel
178 position is negative (as can be the case with motion events in particular),
179 the frame location is always positive.
182 return pixel_to_sample_from_event (x);
186 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
188 boost::shared_ptr<Trimmable> st = _trimmable.lock();
190 if (!st || st == t) {
196 Editor::set_current_movable (boost::shared_ptr<Movable> m)
198 boost::shared_ptr<Movable> sm = _movable.lock();
200 if (!sm || sm != m) {
206 Editor::mouse_mode_object_range_toggled()
208 MouseMode m = mouse_mode;
210 Glib::RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
212 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
215 set_mouse_mode(m, true); //call this so the button styles can get updated
218 static Glib::RefPtr<Action>
219 get_mouse_mode_action(MouseMode m)
223 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
225 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
227 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
229 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
231 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
233 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-content"));
235 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
237 return Glib::RefPtr<Action>();
241 Editor::set_mouse_mode (MouseMode m, bool force)
243 if (_drags->active ()) {
247 if (!force && m == mouse_mode) {
251 if (ARDOUR::Profile->get_mixbus()) {
252 if ( m == MouseCut) m = MouseObject;
255 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
256 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
258 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
259 tact->set_active (false);
260 tact->set_active (true);
262 //NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting
266 Editor::mouse_mode_toggled (MouseMode m)
268 if (ARDOUR::Profile->get_mixbus()) {
269 if ( m == MouseCut) m = MouseObject;
272 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
273 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
275 if (!tact->get_active()) {
276 /* this was just the notification that the old mode has been
277 * left. we'll get called again with the new mode active in a
283 if (_session && mouse_mode == MouseAudition) {
284 /* stop transport and reset default speed to avoid oddness with
286 _session->request_transport_speed (0.0, true);
289 const bool was_internal = internal_editing();
293 /* Switch snap type/mode if we're moving to/from an internal tool. Note
294 this must toggle the actions and not call set_snap_*() directly,
295 otherwise things get out of sync and the combo box stops working. */
296 if (!was_internal && internal_editing()) {
297 snap_type_action(internal_snap_type)->set_active(true);
298 snap_mode_action(internal_snap_mode)->set_active(true);
299 } else if (was_internal && !internal_editing()) {
300 snap_type_action(pre_internal_snap_type)->set_active(true);
301 snap_mode_action(pre_internal_snap_mode)->set_active(true);
306 /* this should generate a new enter event which will
307 trigger the appropiate cursor.
311 _track_canvas->re_enter ();
314 set_gain_envelope_visibility ();
316 update_time_selection_display ();
318 update_all_enter_cursors ();
320 MouseModeChanged (); /* EMIT SIGNAL */
324 Editor::internal_editing() const
326 return mouse_mode == Editing::MouseContent || mouse_mode == Editing::MouseDraw;
330 Editor::update_time_selection_display ()
332 switch (mouse_mode) {
334 selection->clear_objects ();
335 selection->clear_midi_notes ();
338 selection->clear_time ();
339 selection->clear_tracks ();
340 selection->clear_midi_notes ();
343 /* Clear regions, but not time or tracks, since that
344 would destroy the range selection rectangle, which we need to stick
345 around for AutomationRangeDrag. */
346 selection->clear_regions ();
347 selection->clear_playlists ();
350 /* This handles internal edit.
351 Clear everything except points and notes.
353 selection->clear_regions();
354 selection->clear_lines();
355 selection->clear_playlists ();
357 selection->clear_time ();
358 selection->clear_tracks ();
362 /* We probably want to keep region selection */
363 selection->clear_points ();
364 selection->clear_lines();
365 selection->clear_playlists ();
367 selection->clear_time ();
368 selection->clear_tracks ();
372 /*Don't lose lines or points if no action in this mode */
373 selection->clear_regions ();
374 selection->clear_playlists ();
375 selection->clear_time ();
376 selection->clear_tracks ();
380 /*Clear everything */
381 selection->clear_objects();
382 selection->clear_time ();
383 selection->clear_tracks ();
389 Editor::step_mouse_mode (bool next)
391 const int n_mouse_modes = (int)MouseContent + 1;
392 int current = (int)current_mouse_mode();
394 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
396 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
401 Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
404 /* in object/audition/timefx/gain-automation mode,
405 any button press sets the selection if the object
406 can be selected. this is a bit of hack, because
407 we want to avoid this if the mouse operation is a
410 note: not dbl-click or triple-click
412 Also note that there is no region selection in internal edit mode, otherwise
413 for operations operating on the selection (e.g. cut) it is not obvious whether
414 to cut notes or regions.
417 MouseMode eff_mouse_mode = effective_mouse_mode ();
419 if (eff_mouse_mode == MouseCut) {
420 /* never change selection in cut mode */
424 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
425 /* context clicks are always about object properties, even if
426 we're in range mode within smart mode.
428 eff_mouse_mode = MouseObject;
431 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
432 if (get_smart_mode()) {
434 case FadeInHandleItem:
435 case FadeInTrimHandleItem:
436 case FadeOutHandleItem:
437 case FadeOutTrimHandleItem:
438 eff_mouse_mode = MouseObject;
445 if (((mouse_mode != MouseObject) &&
446 (mouse_mode != MouseAudition || item_type != RegionItem) &&
447 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
448 (mouse_mode != MouseDraw) &&
449 (mouse_mode != MouseContent || item_type == RegionItem)) ||
450 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
454 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
456 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
458 /* almost no selection action on modified button-2 or button-3 events */
460 if ((item_type != RegionItem && event->button.button != 2)
461 /* for selection of control points prior to delete (shift-right click) */
462 && !(item_type == ControlPointItem && event->button.button == 3 && event->type == GDK_BUTTON_PRESS)) {
468 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
469 bool press = (event->type == GDK_BUTTON_PRESS);
472 _mouse_changed_selection = false;
477 if (eff_mouse_mode == MouseDraw) {
481 if (eff_mouse_mode != MouseRange) {
482 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
484 /* don't change the selection unless the
485 clicked track is not currently selected. if
486 so, "collapse" the selection to just this
489 if (!selection->selected (clicked_axisview)) {
490 set_selected_track_as_side_effect (Selection::Set);
494 if (eff_mouse_mode != MouseRange) {
495 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
500 case RegionViewNameHighlight:
502 case LeftFrameHandle:
503 case RightFrameHandle:
504 case FadeInHandleItem:
505 case FadeInTrimHandleItem:
507 case FadeOutHandleItem:
508 case FadeOutTrimHandleItem:
510 case StartCrossFadeItem:
511 case EndCrossFadeItem:
512 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
513 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
514 } else if (event->type == GDK_BUTTON_PRESS) {
515 set_selected_track_as_side_effect (op);
519 case ControlPointItem:
520 /* for object/track exclusivity, we don't call set_selected_track_as_side_effect (op); */
522 if (eff_mouse_mode != MouseRange) {
523 if (event->button.button != 3) {
524 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
526 _mouse_changed_selection |= set_selected_control_point_from_click (press, Selection::Set);
532 if (eff_mouse_mode != MouseRange) {
533 AutomationLine* argl = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
535 std::list<Selectable*> selectables;
536 uint32_t before, after;
537 framecnt_t const where = (framecnt_t) floor (event->button.x * samples_per_pixel) - clicked_regionview->region ()->position ();
539 if (!argl || !argl->control_points_adjacent (where, before, after)) {
543 selectables.push_back (argl->nth (before));
544 selectables.push_back (argl->nth (after));
549 selection->set (selectables);
550 _mouse_changed_selection = true;
555 selection->add (selectables);
556 _mouse_changed_selection = true;
559 case Selection::Toggle:
561 selection->toggle (selectables);
562 _mouse_changed_selection = true;
566 case Selection::Extend:
573 case AutomationLineItem:
574 if (eff_mouse_mode != MouseRange) {
575 AutomationLine* al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
576 std::list<Selectable*> selectables;
577 uint32_t before, after;
578 framecnt_t const where = (framecnt_t) floor (event->button.x * samples_per_pixel);
580 if (!al || !al->control_points_adjacent (where, before, after)) {
584 selectables.push_back (al->nth (before));
585 selectables.push_back (al->nth (after));
590 selection->set (selectables);
591 _mouse_changed_selection = true;
596 selection->add (selectables);
597 _mouse_changed_selection = true;
600 case Selection::Toggle:
602 selection->toggle (selectables);
603 _mouse_changed_selection = true;
607 case Selection::Extend:
615 /* for context click, select track */
616 if (event->button.button == 3) {
617 selection->clear_tracks ();
618 set_selected_track_as_side_effect (op);
620 /* We won't get a release.*/
621 begin_reversible_selection_op (X_("Button 3 Menu Select"));
622 commit_reversible_selection_op ();
626 case AutomationTrackItem:
627 if (eff_mouse_mode != MouseDraw && op == Selection::Set) {
628 set_selected_track_as_side_effect (op);
633 if (press && event->button.button == 3) {
634 NoteBase* cnote = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
636 if (cnote->region_view().selection_size() == 0 || !cnote->selected()) {
637 selection->clear_points();
638 cnote->region_view().unique_select (cnote);
639 /* we won't get the release, so store the selection change now */
640 begin_reversible_selection_op (X_("Button 3 Note Selection"));
641 commit_reversible_selection_op ();
650 if ((!press) && _mouse_changed_selection) {
651 begin_reversible_selection_op (X_("Button Selection"));
652 commit_reversible_selection_op ();
653 _mouse_changed_selection = false;
658 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
660 /* single mouse clicks on any of these item types operate
661 independent of mouse mode, mostly because they are
662 not on the main track canvas or because we want
666 NoteBase* note = NULL;
669 case PlayheadCursorItem:
670 _drags->set (new CursorDrag (this, *playhead_cursor, true), event);
674 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
675 hide_marker (item, event);
677 _drags->set (new MarkerDrag (this, item), event);
681 case TempoMarkerItem:
684 new TempoMarkerDrag (
687 ArdourKeyboard::indicates_copy (event->button.state)
694 case MeterMarkerItem:
697 new MeterMarkerDrag (
700 ArdourKeyboard::indicates_copy (event->button.state)
708 _drags->set (new VideoTimeLineDrag (this, item), event);
716 case TimecodeRulerItem:
717 case SamplesRulerItem:
718 case MinsecRulerItem:
720 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)
721 && !ArdourKeyboard::indicates_constraint (event->button.state)) {
722 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
723 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
724 _drags->set (new BBTRulerDrag (this, item), event);
730 case RangeMarkerBarItem:
731 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
732 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
733 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
734 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
736 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
741 case CdMarkerBarItem:
742 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
743 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
745 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
750 case TransportMarkerBarItem:
751 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
752 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
754 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
763 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
764 /* special case: allow trim of range selections in joined object mode;
765 in theory eff should equal MouseRange in this case, but it doesn't
766 because entering the range selection canvas item results in entered_regionview
767 being set to 0, so update_join_object_range_location acts as if we aren't
770 if (item_type == StartSelectionTrimItem) {
771 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
772 } else if (item_type == EndSelectionTrimItem) {
773 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
777 Editing::MouseMode eff = effective_mouse_mode ();
779 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
780 if (get_smart_mode()) {
782 case FadeInHandleItem:
783 case FadeInTrimHandleItem:
784 case FadeOutHandleItem:
785 case FadeOutTrimHandleItem:
796 case StartSelectionTrimItem:
797 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
800 case EndSelectionTrimItem:
801 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
805 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
806 start_selection_grab (item, event);
808 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
809 /* grab selection for moving */
810 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
812 /* this was debated, but decided the more common action was to
813 make a new selection */
814 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
819 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
820 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
822 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
827 case RegionViewNameHighlight:
828 if (!clicked_regionview->region()->locked()) {
829 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
835 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
836 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
838 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
847 case FadeInHandleItem:
848 case FadeOutHandleItem:
849 case LeftFrameHandle:
850 case RightFrameHandle:
851 case FeatureLineItem:
852 case RegionViewNameHighlight:
855 case AutomationTrackItem:
856 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
867 /* Existing note: allow trimming/motion */
868 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
869 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
870 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
872 _drags->set (new NoteDrag (this, item), event);
878 _drags->set (new LineDrag (this, item), event);
882 case ControlPointItem:
883 _drags->set (new ControlPointDrag (this, item), event);
887 case AutomationLineItem:
888 _drags->set (new LineDrag (this, item), event);
893 //in the past, we created a new midi region here, but perhaps that is best left to the Draw mode
896 case AutomationTrackItem:
897 /* rubberband drag to select automation points */
898 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
903 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
904 /* rubberband drag to select automation points */
905 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
916 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
917 event->type == GDK_BUTTON_PRESS) {
919 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
921 } else if (event->type == GDK_BUTTON_PRESS) {
924 case FadeInHandleItem:
926 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
930 case FadeOutHandleItem:
932 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
936 case StartCrossFadeItem:
937 case EndCrossFadeItem:
938 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor. for not this is not fully implemented */
939 // if (!clicked_regionview->region()->locked()) {
940 // _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
945 case FeatureLineItem:
947 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
948 remove_transient(item);
952 _drags->set (new FeatureLineDrag (this, item), event);
958 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
959 /* click on an automation region view; do nothing here and let the ARV's signal handler
965 /* click on a normal region view */
966 if (ArdourKeyboard::indicates_copy (event->button.state)) {
967 add_region_copy_drag (item, event, clicked_regionview);
968 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
969 add_region_brush_drag (item, event, clicked_regionview);
971 add_region_drag (item, event, clicked_regionview);
975 _drags->start_grab (event);
979 case RegionViewNameHighlight:
980 case LeftFrameHandle:
981 case RightFrameHandle:
982 if (!clicked_regionview->region()->locked()) {
983 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
988 case FadeInTrimHandleItem:
989 case FadeOutTrimHandleItem:
990 if (!clicked_regionview->region()->locked()) {
991 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
998 /* rename happens on edit clicks */
999 if (clicked_regionview->get_name_highlight()) {
1000 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1006 case ControlPointItem:
1007 _drags->set (new ControlPointDrag (this, item), event);
1011 case AutomationLineItem:
1012 _drags->set (new LineDrag (this, item), event);
1017 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1020 case AutomationTrackItem:
1022 TimeAxisView* parent = clicked_axisview->get_parent ();
1023 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1025 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1027 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1029 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1030 if (pl->n_regions() == 0) {
1031 /* Parent has no regions; create one so that we have somewhere to put automation */
1032 _drags->set (new RegionCreateDrag (this, item, parent), event);
1034 /* See if there's a region before the click that we can extend, and extend it if so */
1035 framepos_t const t = canvas_event_sample (event);
1036 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1038 _drags->set (new RegionCreateDrag (this, item, parent), event);
1040 prev->set_length (t - prev->position (), get_grid_music_divisions (event->button.state));
1044 /* rubberband drag to select automation points */
1045 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1067 switch (item_type) {
1069 _drags->set (new LineDrag (this, item), event);
1072 case ControlPointItem:
1073 _drags->set (new ControlPointDrag (this, item), event);
1079 if (dynamic_cast<AudioRegionView*>(clicked_regionview) ||
1080 dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1081 _drags->set (new AutomationRangeDrag (this, clicked_regionview, selection->time),
1082 event, _cursors->up_down);
1084 double const y = event->button.y;
1085 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y, false);
1087 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1089 /* smart "join" mode: drag automation */
1090 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1098 case AutomationLineItem:
1099 _drags->set (new LineDrag (this, item), event);
1103 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1104 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1105 /* Note is big and pointer is near the end, trim */
1106 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1109 _drags->set (new NoteDrag (this, item), event);
1116 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1117 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1128 if (item_type == NoteItem) {
1129 /* resize-drag notes */
1130 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1131 if (note->big_enough_to_trim()) {
1132 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1136 } else if (clicked_regionview) {
1138 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1144 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1145 scrub_reversals = 0;
1146 scrub_reverse_distance = 0;
1147 last_scrub_x = event->button.x;
1148 scrubbing_direction = 0;
1160 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1162 Editing::MouseMode const eff = effective_mouse_mode ();
1165 switch (item_type) {
1167 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1168 add_region_copy_drag (item, event, clicked_regionview);
1170 add_region_drag (item, event, clicked_regionview);
1172 _drags->start_grab (event);
1175 case ControlPointItem:
1176 _drags->set (new ControlPointDrag (this, item), event);
1184 switch (item_type) {
1185 case RegionViewNameHighlight:
1186 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1190 case LeftFrameHandle:
1191 case RightFrameHandle:
1192 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1196 case RegionViewName:
1197 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1211 /* relax till release */
1223 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1225 if (event->type == GDK_2BUTTON_PRESS) {
1226 _drags->mark_double_click ();
1227 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1231 if (event->type != GDK_BUTTON_PRESS) {
1235 _track_canvas->grab_focus();
1237 if (_session && _session->actively_recording()) {
1241 button_selection (item, event, item_type);
1243 if (!_drags->active () &&
1244 (Keyboard::is_delete_event (&event->button) ||
1245 Keyboard::is_context_menu_event (&event->button) ||
1246 Keyboard::is_edit_event (&event->button))) {
1248 /* handled by button release */
1252 //not rolling, range mode click + join_play_range : locate the PH here
1253 if ( !_drags->active () && _session && !_session->transport_rolling() && ( effective_mouse_mode() == MouseRange ) && UIConfiguration::instance().get_follow_edits() && !_session->config.get_external_sync() ) {
1254 framepos_t where = canvas_event_sample (event);
1256 _session->request_locate (where, false);
1259 switch (event->button.button) {
1261 return button_press_handler_1 (item, event, item_type);
1265 return button_press_handler_2 (item, event, item_type);
1272 return button_press_dispatch (&event->button);
1281 Editor::button_press_dispatch (GdkEventButton* ev)
1283 /* this function is intended only for buttons 4 and above.
1286 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1287 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1291 Editor::button_release_dispatch (GdkEventButton* ev)
1293 /* this function is intended only for buttons 4 and above.
1296 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1297 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1301 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1303 framepos_t where = canvas_event_sample (event);
1304 AutomationTimeAxisView* atv = 0;
1306 _press_cursor_ctx.reset();
1308 /* no action if we're recording */
1310 if (_session && _session->actively_recording()) {
1314 bool were_dragging = false;
1316 if (!Keyboard::is_context_menu_event (&event->button)) {
1318 /* see if we're finishing a drag */
1320 if (_drags->active ()) {
1321 bool const r = _drags->end_grab (event);
1323 /* grab dragged, so do nothing else */
1327 were_dragging = true;
1330 update_region_layering_order_editor ();
1333 /* edit events get handled here */
1335 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1336 switch (item_type) {
1338 show_region_properties ();
1340 case TempoMarkerItem: {
1341 ArdourMarker* marker;
1342 TempoMarker* tempo_marker;
1344 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1345 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1346 abort(); /*NOTREACHED*/
1349 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1350 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1351 abort(); /*NOTREACHED*/
1354 edit_tempo_marker (*tempo_marker);
1358 case MeterMarkerItem: {
1359 ArdourMarker* marker;
1360 MeterMarker* meter_marker;
1362 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1363 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1364 abort(); /*NOTREACHED*/
1367 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1368 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1369 abort(); /*NOTREACHED*/
1371 edit_meter_marker (*meter_marker);
1375 case RegionViewName:
1376 if (clicked_regionview->name_active()) {
1377 return mouse_rename_region (item, event);
1381 case ControlPointItem:
1382 edit_control_point (item);
1391 /* context menu events get handled here */
1392 if (Keyboard::is_context_menu_event (&event->button)) {
1394 context_click_event = *event;
1396 if (!_drags->active ()) {
1398 /* no matter which button pops up the context menu, tell the menu
1399 widget to use button 1 to drive menu selection.
1402 switch (item_type) {
1404 case FadeInHandleItem:
1405 case FadeInTrimHandleItem:
1406 case StartCrossFadeItem:
1407 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1411 case FadeOutHandleItem:
1412 case FadeOutTrimHandleItem:
1413 case EndCrossFadeItem:
1414 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1417 case LeftFrameHandle:
1418 case RightFrameHandle:
1422 popup_track_context_menu (1, event->button.time, item_type, false);
1426 case RegionViewNameHighlight:
1427 case RegionViewName:
1428 popup_track_context_menu (1, event->button.time, item_type, false);
1432 popup_track_context_menu (1, event->button.time, item_type, true);
1435 case AutomationTrackItem:
1436 popup_track_context_menu (1, event->button.time, item_type, false);
1440 case RangeMarkerBarItem:
1441 case TransportMarkerBarItem:
1442 case CdMarkerBarItem:
1444 case TempoCurveItem:
1447 case TimecodeRulerItem:
1448 case SamplesRulerItem:
1449 case MinsecRulerItem:
1451 popup_ruler_menu (where, item_type);
1455 marker_context_menu (&event->button, item);
1458 case TempoMarkerItem:
1459 tempo_or_meter_marker_context_menu (&event->button, item);
1462 case MeterMarkerItem:
1463 tempo_or_meter_marker_context_menu (&event->button, item);
1466 case CrossfadeViewItem:
1467 popup_track_context_menu (1, event->button.time, item_type, false);
1470 case ControlPointItem:
1471 popup_control_point_context_menu (item, event);
1475 if (internal_editing()) {
1476 popup_note_context_menu (item, event);
1488 /* delete events get handled here */
1490 Editing::MouseMode const eff = effective_mouse_mode ();
1492 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1494 switch (item_type) {
1495 case TempoMarkerItem:
1496 remove_tempo_marker (item);
1499 case MeterMarkerItem:
1500 remove_meter_marker (item);
1504 remove_marker (*item, event);
1508 if (eff == MouseObject) {
1509 remove_clicked_region ();
1513 case ControlPointItem:
1514 remove_control_point (item);
1518 remove_midi_note (item, event);
1527 switch (event->button.button) {
1530 switch (item_type) {
1531 /* see comments in button_press_handler */
1532 case PlayheadCursorItem:
1535 case AutomationLineItem:
1536 case StartSelectionTrimItem:
1537 case EndSelectionTrimItem:
1541 if (!_dragging_playhead) {
1542 snap_to_with_modifier (where, event, RoundNearest, true);
1543 mouse_add_new_marker (where);
1547 case CdMarkerBarItem:
1548 if (!_dragging_playhead) {
1549 // if we get here then a dragged range wasn't done
1550 snap_to_with_modifier (where, event, RoundNearest, true);
1551 mouse_add_new_marker (where, true);
1555 case TempoCurveItem:
1556 if (!_dragging_playhead) {
1557 snap_to_with_modifier (where, event);
1558 mouse_add_new_tempo_event (where);
1563 if (!_dragging_playhead) {
1564 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1569 case TimecodeRulerItem:
1570 case SamplesRulerItem:
1571 case MinsecRulerItem:
1582 switch (item_type) {
1585 /* check that we didn't drag before releasing, since
1586 its really annoying to create new control
1587 points when doing this.
1589 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1590 if (!were_dragging && arv) {
1591 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1592 arv->add_gain_point_event (item, event, with_guard_points);
1598 case AutomationTrackItem: {
1599 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1600 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1602 atv->add_automation_event (event, where, event->button.y, with_guard_points);
1613 if (scrubbing_direction == 0) {
1614 /* no drag, just a click */
1615 switch (item_type) {
1617 play_selected_region ();
1622 } else if (_session) {
1623 /* make sure we stop */
1624 _session->request_transport_speed (0.0);
1633 /* do any (de)selection operations that should occur on button release */
1634 button_selection (item, event, item_type);
1644 switch (item_type) {
1646 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1648 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1651 // Button2 click is unused
1666 // x_style_paste (where, 1.0);
1687 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1690 ArdourMarker * marker;
1691 MeterMarker* m_marker = 0;
1692 TempoMarker* t_marker = 0;
1696 /* by the time we reach here, entered_regionview and entered trackview
1697 * will have already been set as appropriate. Things are done this
1698 * way because this method isn't passed a pointer to a variable type of
1699 * thing that is entered (which may or may not be canvas item).
1700 * (e.g. the actual entered regionview)
1703 choose_canvas_cursor_on_entry (item_type);
1705 switch (item_type) {
1706 case ControlPointItem:
1707 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1708 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1711 fraction = 1.0 - (cp->get_y() / cp->line().height());
1713 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1714 _verbose_cursor->show ();
1719 if (mouse_mode == MouseDraw) {
1720 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1722 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1727 case AutomationLineItem:
1728 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1729 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1731 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1736 case AutomationTrackItem:
1737 AutomationTimeAxisView* atv;
1738 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1739 clear_entered_track = false;
1740 set_entered_track (atv);
1745 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1748 entered_marker = marker;
1749 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1752 case MeterMarkerItem:
1753 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1756 entered_marker = m_marker;
1757 if (m_marker->meter().position_lock_style() == MusicTime) {
1758 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1760 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1764 case TempoMarkerItem:
1765 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1768 entered_marker = t_marker;
1769 if (t_marker->tempo().position_lock_style() == MusicTime) {
1770 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1772 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1776 case FadeInHandleItem:
1777 case FadeInTrimHandleItem:
1778 if (mouse_mode == MouseObject) {
1779 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1781 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1782 rect->set_fill_color (rv->get_fill_color());
1787 case FadeOutHandleItem:
1788 case FadeOutTrimHandleItem:
1789 if (mouse_mode == MouseObject) {
1790 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1792 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1793 rect->set_fill_color (rv->get_fill_color ());
1798 case FeatureLineItem:
1800 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1801 line->set_outline_color (0xFF0000FF);
1810 if (entered_regionview) {
1811 entered_regionview->entered();
1820 /* third pass to handle entered track status in a comprehensible way.
1823 switch (item_type) {
1825 case AutomationLineItem:
1826 case ControlPointItem:
1827 /* these do not affect the current entered track state */
1828 clear_entered_track = false;
1831 case AutomationTrackItem:
1832 /* handled above already */
1844 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1847 ArdourMarker *marker;
1848 TempoMarker *t_marker;
1849 MeterMarker *m_marker;
1854 if (!_enter_stack.empty()) {
1855 _enter_stack.pop_back();
1858 switch (item_type) {
1859 case ControlPointItem:
1860 _verbose_cursor->hide ();
1864 case AutomationLineItem:
1865 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1867 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1869 line->set_outline_color (al->get_line_color());
1875 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1879 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1880 location_flags_changed (loc);
1884 case MeterMarkerItem:
1885 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1889 if (m_marker->meter().position_lock_style() == MusicTime) {
1890 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1892 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1896 case TempoMarkerItem:
1897 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1901 if (t_marker->tempo().position_lock_style() == MusicTime) {
1902 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1904 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1908 case FadeInTrimHandleItem:
1909 case FadeOutTrimHandleItem:
1910 case FadeInHandleItem:
1911 case FadeOutHandleItem:
1913 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1915 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1920 case AutomationTrackItem:
1923 case FeatureLineItem:
1925 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1926 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1938 Editor::scrub (framepos_t frame, double current_x)
1942 if (scrubbing_direction == 0) {
1944 _session->request_locate (frame, false);
1945 _session->request_transport_speed (0.1);
1946 scrubbing_direction = 1;
1950 if (last_scrub_x > current_x) {
1952 /* pointer moved to the left */
1954 if (scrubbing_direction > 0) {
1956 /* we reversed direction to go backwards */
1959 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1963 /* still moving to the left (backwards) */
1965 scrub_reversals = 0;
1966 scrub_reverse_distance = 0;
1968 delta = 0.01 * (last_scrub_x - current_x);
1969 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1973 /* pointer moved to the right */
1975 if (scrubbing_direction < 0) {
1976 /* we reversed direction to go forward */
1979 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1982 /* still moving to the right */
1984 scrub_reversals = 0;
1985 scrub_reverse_distance = 0;
1987 delta = 0.01 * (current_x - last_scrub_x);
1988 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1992 /* if there have been more than 2 opposite motion moves detected, or one that moves
1993 back more than 10 pixels, reverse direction
1996 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1998 if (scrubbing_direction > 0) {
1999 /* was forwards, go backwards */
2000 _session->request_transport_speed (-0.1);
2001 scrubbing_direction = -1;
2003 /* was backwards, go forwards */
2004 _session->request_transport_speed (0.1);
2005 scrubbing_direction = 1;
2008 scrub_reverse_distance = 0;
2009 scrub_reversals = 0;
2013 last_scrub_x = current_x;
2017 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2019 _last_motion_y = event->motion.y;
2021 if (event->motion.is_hint) {
2024 /* We call this so that MOTION_NOTIFY events continue to be
2025 delivered to the canvas. We need to do this because we set
2026 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2027 the density of the events, at the expense of a round-trip
2028 to the server. Given that this will mostly occur on cases
2029 where DISPLAY = :0.0, and given the cost of what the motion
2030 event might do, its a good tradeoff.
2033 _track_canvas->get_pointer (x, y);
2036 if (current_stepping_trackview) {
2037 /* don't keep the persistent stepped trackview if the mouse moves */
2038 current_stepping_trackview = 0;
2039 step_timeout.disconnect ();
2042 if (_session && _session->actively_recording()) {
2043 /* Sorry. no dragging stuff around while we record */
2047 update_join_object_range_location (event->motion.y);
2049 if (_drags->active ()) {
2050 return _drags->motion_handler (event, from_autoscroll);
2057 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2059 ControlPoint* control_point;
2061 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2062 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2063 abort(); /*NOTREACHED*/
2066 AutomationLine& line = control_point->line ();
2067 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2068 /* we shouldn't remove the first or last gain point in region gain lines */
2069 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2078 Editor::remove_control_point (ArdourCanvas::Item* item)
2080 if (!can_remove_control_point (item)) {
2084 ControlPoint* control_point;
2086 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2087 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2088 abort(); /*NOTREACHED*/
2091 control_point->line().remove_point (*control_point);
2095 Editor::edit_control_point (ArdourCanvas::Item* item)
2097 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2100 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2101 abort(); /*NOTREACHED*/
2104 ControlPointDialog d (p);
2106 if (d.run () != RESPONSE_ACCEPT) {
2110 p->line().modify_point_y (*p, d.get_y_fraction ());
2114 Editor::edit_notes (MidiRegionView* mrv)
2116 MidiRegionView::Selection const & s = mrv->selection();
2122 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2125 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2129 Editor::note_edit_done (int r, EditNoteDialog* d)
2131 begin_reversible_command (_("edit note(s)"));
2136 commit_reversible_command();
2140 Editor::visible_order_range (int* low, int* high) const
2142 *low = TimeAxisView::max_order ();
2145 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2147 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2149 if (rtv && !rtv->hidden()) {
2151 if (*high < rtv->order()) {
2152 *high = rtv->order ();
2155 if (*low > rtv->order()) {
2156 *low = rtv->order ();
2163 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2165 /* Either add to or set the set the region selection, unless
2166 this is an alignment click (control used)
2169 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2170 TimeAxisView* tv = &rv.get_time_axis_view();
2171 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2173 if (rtv && rtv->is_track()) {
2174 speed = rtv->track()->speed();
2177 framepos_t where = get_preferred_edit_position();
2181 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2183 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2185 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2187 align_region (rv.region(), End, (framepos_t) (where * speed));
2191 align_region (rv.region(), Start, (framepos_t) (where * speed));
2198 Editor::collect_new_region_view (RegionView* rv)
2200 latest_regionviews.push_back (rv);
2204 Editor::collect_and_select_new_region_view (RegionView* rv)
2207 latest_regionviews.push_back (rv);
2211 Editor::cancel_selection ()
2213 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2214 (*i)->hide_selection ();
2217 selection->clear ();
2218 clicked_selection = 0;
2222 Editor::cancel_time_selection ()
2224 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2225 (*i)->hide_selection ();
2227 selection->time.clear ();
2228 clicked_selection = 0;
2232 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2234 RegionView* rv = clicked_regionview;
2236 /* Choose action dependant on which button was pressed */
2237 switch (event->button.button) {
2239 begin_reversible_command (_("start point trim"));
2241 if (selection->selected (rv)) {
2242 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2243 i != selection->regions.by_layer().end(); ++i)
2245 if (!(*i)->region()->locked()) {
2246 (*i)->region()->clear_changes ();
2247 (*i)->region()->trim_front (new_bound);
2248 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2253 if (!rv->region()->locked()) {
2254 rv->region()->clear_changes ();
2255 rv->region()->trim_front (new_bound);
2256 _session->add_command(new StatefulDiffCommand (rv->region()));
2260 commit_reversible_command();
2264 begin_reversible_command (_("end point trim"));
2266 if (selection->selected (rv)) {
2268 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2270 if (!(*i)->region()->locked()) {
2271 (*i)->region()->clear_changes();
2272 (*i)->region()->trim_end (new_bound);
2273 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2279 if (!rv->region()->locked()) {
2280 rv->region()->clear_changes ();
2281 rv->region()->trim_end (new_bound);
2282 _session->add_command (new StatefulDiffCommand (rv->region()));
2286 commit_reversible_command();
2295 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2297 ArdourMarker* marker;
2300 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2301 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2302 abort(); /*NOTREACHED*/
2305 Location* location = find_location_from_marker (marker, is_start);
2306 location->set_hidden (true, this);
2310 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2312 using namespace Gtkmm2ext;
2314 ArdourPrompter prompter (false);
2316 prompter.set_prompt (_("Name for region:"));
2317 prompter.set_initial_text (clicked_regionview->region()->name());
2318 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2319 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2320 prompter.show_all ();
2321 switch (prompter.run ()) {
2322 case Gtk::RESPONSE_ACCEPT:
2324 prompter.get_result(str);
2326 clicked_regionview->region()->set_name (str);
2335 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2337 /* no brushing without a useful snap setting */
2339 switch (_snap_mode) {
2341 return; /* can't work because it allows region to be placed anywhere */
2346 switch (_snap_type) {
2354 /* don't brush a copy over the original */
2356 if (pos == rv->region()->position()) {
2360 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2362 if (!rtv || !rtv->is_track()) {
2366 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2367 double speed = rtv->track()->speed();
2369 playlist->clear_changes ();
2370 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2371 playlist->add_region (new_region, (framepos_t) (pos * speed));
2372 _session->add_command (new StatefulDiffCommand (playlist));
2374 // playlist is frozen, so we have to update manually XXX this is disgusting
2376 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2380 Editor::track_height_step_timeout ()
2382 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2383 current_stepping_trackview = 0;
2390 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2392 assert (region_view);
2394 if (!region_view->region()->playlist()) {
2398 switch (Config->get_edit_mode()) {
2400 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2403 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2406 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2413 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2415 assert (region_view);
2417 if (!region_view->region()->playlist()) {
2421 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2425 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2427 assert (region_view);
2429 if (!region_view->region()->playlist()) {
2433 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2437 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2440 /** Start a grab where a time range is selected, track(s) are selected, and the
2441 * user clicks and drags a region with a modifier in order to create a new region containing
2442 * the section of the clicked region that lies within the time range.
2445 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2447 if (clicked_regionview == 0) {
2451 /* lets try to create new Region for the selection */
2453 vector<boost::shared_ptr<Region> > new_regions;
2454 create_region_from_selection (new_regions);
2456 if (new_regions.empty()) {
2460 /* XXX fix me one day to use all new regions */
2462 boost::shared_ptr<Region> region (new_regions.front());
2464 /* add it to the current stream/playlist.
2466 tricky: the streamview for the track will add a new regionview. we will
2467 catch the signal it sends when it creates the regionview to
2468 set the regionview we want to then drag.
2471 latest_regionviews.clear();
2472 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2474 /* A selection grab currently creates two undo/redo operations, one for
2475 creating the new region and another for moving it.
2477 begin_reversible_command (Operations::selection_grab);
2479 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2481 playlist->clear_changes ();
2482 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2483 _session->add_command(new StatefulDiffCommand (playlist));
2487 if (latest_regionviews.empty()) {
2488 /* something went wrong */
2489 abort_reversible_command ();
2493 /* we need to deselect all other regionviews, and select this one
2494 i'm ignoring undo stuff, because the region creation will take care of it
2497 selection->set (latest_regionviews);
2499 commit_reversible_command ();
2501 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2507 if (_drags->active ()) {
2510 selection->clear ();
2513 reset_focus (&contents());
2516 /** Update _join_object_range_state which indicate whether we are over the top
2517 * or bottom half of a route view, used by the `join object/range' tool
2518 * mode. Coordinates in canvas space.
2521 Editor::update_join_object_range_location (double y)
2523 if (!get_smart_mode()) {
2524 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2528 JoinObjectRangeState const old = _join_object_range_state;
2530 if (mouse_mode == MouseObject) {
2531 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2532 } else if (mouse_mode == MouseRange) {
2533 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2536 if (entered_regionview) {
2538 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2539 double const c = item_space.y / entered_regionview->height();
2541 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2543 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2544 if (_join_object_range_state != old && ctx) {
2545 ctx->cursor_ctx->change(which_track_cursor());
2548 } else if (entered_track) {
2550 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2552 if (entered_route_view) {
2557 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2559 double track_height = entered_route_view->view()->child_height();
2560 if (UIConfiguration::instance().get_show_name_highlight()) {
2561 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2563 double const c = cy / track_height;
2567 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2569 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2573 /* Other kinds of tracks use object mode */
2574 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2577 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2578 if (_join_object_range_state != old && ctx) {
2579 ctx->cursor_ctx->change(which_track_cursor());
2585 Editor::effective_mouse_mode () const
2587 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2589 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2597 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2599 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2602 e->region_view().delete_note (e->note ());
2605 /** Obtain the pointer position in canvas coordinates */
2607 Editor::get_pointer_position (double& x, double& y) const
2610 _track_canvas->get_pointer (px, py);
2611 _track_canvas->window_to_canvas (px, py, x, y);