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 Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)
694 case MeterMarkerItem:
697 new MeterMarkerDrag (
700 Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)
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 && !Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::constraint_modifier())) {
722 _drags->set (new CursorDrag (this, *playhead_cursor, false), event);
723 } else if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::constraint_modifier())) {
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 (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
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 (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
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;
1694 /* by the time we reach here, entered_regionview and entered trackview
1695 * will have already been set as appropriate. Things are done this
1696 * way because this method isn't passed a pointer to a variable type of
1697 * thing that is entered (which may or may not be canvas item).
1698 * (e.g. the actual entered regionview)
1701 choose_canvas_cursor_on_entry (item_type);
1703 switch (item_type) {
1704 case ControlPointItem:
1705 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1706 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1709 fraction = 1.0 - (cp->get_y() / cp->line().height());
1711 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1712 _verbose_cursor->show ();
1717 if (mouse_mode == MouseDraw) {
1718 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1720 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1725 case AutomationLineItem:
1726 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1727 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1729 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1734 case AutomationTrackItem:
1735 AutomationTimeAxisView* atv;
1736 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1737 clear_entered_track = false;
1738 set_entered_track (atv);
1743 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1746 entered_marker = marker;
1747 marker->set_color_rgba (UIConfiguration::instance().color ("entered marker"));
1749 case MeterMarkerItem:
1750 case TempoMarkerItem:
1753 case FadeInHandleItem:
1754 case FadeInTrimHandleItem:
1755 if (mouse_mode == MouseObject) {
1756 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1758 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1759 rect->set_fill_color (rv->get_fill_color());
1764 case FadeOutHandleItem:
1765 case FadeOutTrimHandleItem:
1766 if (mouse_mode == MouseObject) {
1767 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1769 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1770 rect->set_fill_color (rv->get_fill_color ());
1775 case FeatureLineItem:
1777 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1778 line->set_outline_color (0xFF0000FF);
1787 if (entered_regionview) {
1788 entered_regionview->entered();
1797 /* third pass to handle entered track status in a comprehensible way.
1800 switch (item_type) {
1802 case AutomationLineItem:
1803 case ControlPointItem:
1804 /* these do not affect the current entered track state */
1805 clear_entered_track = false;
1808 case AutomationTrackItem:
1809 /* handled above already */
1821 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
1824 ArdourMarker *marker;
1829 if (!_enter_stack.empty()) {
1830 _enter_stack.pop_back();
1833 switch (item_type) {
1834 case ControlPointItem:
1835 _verbose_cursor->hide ();
1839 case AutomationLineItem:
1840 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1842 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1844 line->set_outline_color (al->get_line_color());
1850 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1854 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1855 location_flags_changed (loc);
1858 case MeterMarkerItem:
1859 case TempoMarkerItem:
1862 case FadeInTrimHandleItem:
1863 case FadeOutTrimHandleItem:
1864 case FadeInHandleItem:
1865 case FadeOutHandleItem:
1867 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1869 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
1874 case AutomationTrackItem:
1877 case FeatureLineItem:
1879 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1880 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
1892 Editor::scrub (framepos_t frame, double current_x)
1896 if (scrubbing_direction == 0) {
1898 _session->request_locate (frame, false);
1899 _session->request_transport_speed (0.1);
1900 scrubbing_direction = 1;
1904 if (last_scrub_x > current_x) {
1906 /* pointer moved to the left */
1908 if (scrubbing_direction > 0) {
1910 /* we reversed direction to go backwards */
1913 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1917 /* still moving to the left (backwards) */
1919 scrub_reversals = 0;
1920 scrub_reverse_distance = 0;
1922 delta = 0.01 * (last_scrub_x - current_x);
1923 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1927 /* pointer moved to the right */
1929 if (scrubbing_direction < 0) {
1930 /* we reversed direction to go forward */
1933 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1936 /* still moving to the right */
1938 scrub_reversals = 0;
1939 scrub_reverse_distance = 0;
1941 delta = 0.01 * (current_x - last_scrub_x);
1942 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1946 /* if there have been more than 2 opposite motion moves detected, or one that moves
1947 back more than 10 pixels, reverse direction
1950 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1952 if (scrubbing_direction > 0) {
1953 /* was forwards, go backwards */
1954 _session->request_transport_speed (-0.1);
1955 scrubbing_direction = -1;
1957 /* was backwards, go forwards */
1958 _session->request_transport_speed (0.1);
1959 scrubbing_direction = 1;
1962 scrub_reverse_distance = 0;
1963 scrub_reversals = 0;
1967 last_scrub_x = current_x;
1971 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1973 _last_motion_y = event->motion.y;
1975 if (event->motion.is_hint) {
1978 /* We call this so that MOTION_NOTIFY events continue to be
1979 delivered to the canvas. We need to do this because we set
1980 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1981 the density of the events, at the expense of a round-trip
1982 to the server. Given that this will mostly occur on cases
1983 where DISPLAY = :0.0, and given the cost of what the motion
1984 event might do, its a good tradeoff.
1987 _track_canvas->get_pointer (x, y);
1990 if (current_stepping_trackview) {
1991 /* don't keep the persistent stepped trackview if the mouse moves */
1992 current_stepping_trackview = 0;
1993 step_timeout.disconnect ();
1996 if (_session && _session->actively_recording()) {
1997 /* Sorry. no dragging stuff around while we record */
2001 update_join_object_range_location (event->motion.y);
2003 if (_drags->active ()) {
2004 return _drags->motion_handler (event, from_autoscroll);
2011 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2013 ControlPoint* control_point;
2015 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2016 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2017 abort(); /*NOTREACHED*/
2020 AutomationLine& line = control_point->line ();
2021 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2022 /* we shouldn't remove the first or last gain point in region gain lines */
2023 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2032 Editor::remove_control_point (ArdourCanvas::Item* item)
2034 if (!can_remove_control_point (item)) {
2038 ControlPoint* control_point;
2040 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2041 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2042 abort(); /*NOTREACHED*/
2045 control_point->line().remove_point (*control_point);
2049 Editor::edit_control_point (ArdourCanvas::Item* item)
2051 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2054 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2055 abort(); /*NOTREACHED*/
2058 ControlPointDialog d (p);
2060 if (d.run () != RESPONSE_ACCEPT) {
2064 p->line().modify_point_y (*p, d.get_y_fraction ());
2068 Editor::edit_notes (MidiRegionView* mrv)
2070 MidiRegionView::Selection const & s = mrv->selection();
2076 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2079 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2083 Editor::note_edit_done (int r, EditNoteDialog* d)
2085 begin_reversible_command (_("edit note(s)"));
2090 commit_reversible_command();
2094 Editor::visible_order_range (int* low, int* high) const
2096 *low = TimeAxisView::max_order ();
2099 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2101 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2103 if (rtv && !rtv->hidden()) {
2105 if (*high < rtv->order()) {
2106 *high = rtv->order ();
2109 if (*low > rtv->order()) {
2110 *low = rtv->order ();
2117 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2119 /* Either add to or set the set the region selection, unless
2120 this is an alignment click (control used)
2123 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2124 TimeAxisView* tv = &rv.get_time_axis_view();
2125 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2127 if (rtv && rtv->is_track()) {
2128 speed = rtv->track()->speed();
2131 framepos_t where = get_preferred_edit_position();
2135 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2137 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2139 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2141 align_region (rv.region(), End, (framepos_t) (where * speed));
2145 align_region (rv.region(), Start, (framepos_t) (where * speed));
2152 Editor::collect_new_region_view (RegionView* rv)
2154 latest_regionviews.push_back (rv);
2158 Editor::collect_and_select_new_region_view (RegionView* rv)
2161 latest_regionviews.push_back (rv);
2165 Editor::cancel_selection ()
2167 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2168 (*i)->hide_selection ();
2171 selection->clear ();
2172 clicked_selection = 0;
2176 Editor::cancel_time_selection ()
2178 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2179 (*i)->hide_selection ();
2181 selection->time.clear ();
2182 clicked_selection = 0;
2186 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2188 RegionView* rv = clicked_regionview;
2190 /* Choose action dependant on which button was pressed */
2191 switch (event->button.button) {
2193 begin_reversible_command (_("start point trim"));
2195 if (selection->selected (rv)) {
2196 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2197 i != selection->regions.by_layer().end(); ++i)
2199 if (!(*i)->region()->locked()) {
2200 (*i)->region()->clear_changes ();
2201 (*i)->region()->trim_front (new_bound);
2202 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2207 if (!rv->region()->locked()) {
2208 rv->region()->clear_changes ();
2209 rv->region()->trim_front (new_bound);
2210 _session->add_command(new StatefulDiffCommand (rv->region()));
2214 commit_reversible_command();
2218 begin_reversible_command (_("end point trim"));
2220 if (selection->selected (rv)) {
2222 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2224 if (!(*i)->region()->locked()) {
2225 (*i)->region()->clear_changes();
2226 (*i)->region()->trim_end (new_bound);
2227 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2233 if (!rv->region()->locked()) {
2234 rv->region()->clear_changes ();
2235 rv->region()->trim_end (new_bound);
2236 _session->add_command (new StatefulDiffCommand (rv->region()));
2240 commit_reversible_command();
2249 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2251 ArdourMarker* marker;
2254 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2255 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2256 abort(); /*NOTREACHED*/
2259 Location* location = find_location_from_marker (marker, is_start);
2260 location->set_hidden (true, this);
2264 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2266 using namespace Gtkmm2ext;
2268 ArdourPrompter prompter (false);
2270 prompter.set_prompt (_("Name for region:"));
2271 prompter.set_initial_text (clicked_regionview->region()->name());
2272 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2273 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2274 prompter.show_all ();
2275 switch (prompter.run ()) {
2276 case Gtk::RESPONSE_ACCEPT:
2278 prompter.get_result(str);
2280 clicked_regionview->region()->set_name (str);
2289 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2291 /* no brushing without a useful snap setting */
2293 switch (_snap_mode) {
2295 return; /* can't work because it allows region to be placed anywhere */
2300 switch (_snap_type) {
2308 /* don't brush a copy over the original */
2310 if (pos == rv->region()->position()) {
2314 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2316 if (!rtv || !rtv->is_track()) {
2320 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2321 double speed = rtv->track()->speed();
2323 playlist->clear_changes ();
2324 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2325 playlist->add_region (new_region, (framepos_t) (pos * speed));
2326 _session->add_command (new StatefulDiffCommand (playlist));
2328 // playlist is frozen, so we have to update manually XXX this is disgusting
2330 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2334 Editor::track_height_step_timeout ()
2336 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2337 current_stepping_trackview = 0;
2344 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2346 assert (region_view);
2348 if (!region_view->region()->playlist()) {
2352 switch (Config->get_edit_mode()) {
2354 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2357 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2360 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
2367 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2369 assert (region_view);
2371 if (!region_view->region()->playlist()) {
2375 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, true));
2379 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2381 assert (region_view);
2383 if (!region_view->region()->playlist()) {
2387 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2391 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true, false));
2394 /** Start a grab where a time range is selected, track(s) are selected, and the
2395 * user clicks and drags a region with a modifier in order to create a new region containing
2396 * the section of the clicked region that lies within the time range.
2399 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2401 if (clicked_regionview == 0) {
2405 /* lets try to create new Region for the selection */
2407 vector<boost::shared_ptr<Region> > new_regions;
2408 create_region_from_selection (new_regions);
2410 if (new_regions.empty()) {
2414 /* XXX fix me one day to use all new regions */
2416 boost::shared_ptr<Region> region (new_regions.front());
2418 /* add it to the current stream/playlist.
2420 tricky: the streamview for the track will add a new regionview. we will
2421 catch the signal it sends when it creates the regionview to
2422 set the regionview we want to then drag.
2425 latest_regionviews.clear();
2426 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2428 /* A selection grab currently creates two undo/redo operations, one for
2429 creating the new region and another for moving it.
2431 begin_reversible_command (Operations::selection_grab);
2433 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2435 playlist->clear_changes ();
2436 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2437 _session->add_command(new StatefulDiffCommand (playlist));
2441 if (latest_regionviews.empty()) {
2442 /* something went wrong */
2443 abort_reversible_command ();
2447 /* we need to deselect all other regionviews, and select this one
2448 i'm ignoring undo stuff, because the region creation will take care of it
2451 selection->set (latest_regionviews);
2453 commit_reversible_command ();
2455 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2461 if (_drags->active ()) {
2464 selection->clear ();
2467 reset_focus (&contents());
2470 /** Update _join_object_range_state which indicate whether we are over the top
2471 * or bottom half of a route view, used by the `join object/range' tool
2472 * mode. Coordinates in canvas space.
2475 Editor::update_join_object_range_location (double y)
2477 if (!get_smart_mode()) {
2478 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2482 JoinObjectRangeState const old = _join_object_range_state;
2484 if (mouse_mode == MouseObject) {
2485 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2486 } else if (mouse_mode == MouseRange) {
2487 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2490 if (entered_regionview) {
2492 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2493 double const c = item_space.y / entered_regionview->height();
2495 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2497 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2498 if (_join_object_range_state != old && ctx) {
2499 ctx->cursor_ctx->change(which_track_cursor());
2502 } else if (entered_track) {
2504 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2506 if (entered_route_view) {
2511 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2513 double track_height = entered_route_view->view()->child_height();
2514 if (UIConfiguration::instance().get_show_name_highlight()) {
2515 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2517 double const c = cy / track_height;
2521 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2523 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2527 /* Other kinds of tracks use object mode */
2528 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2531 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2532 if (_join_object_range_state != old && ctx) {
2533 ctx->cursor_ctx->change(which_track_cursor());
2539 Editor::effective_mouse_mode () const
2541 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2543 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2551 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2553 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2556 e->region_view().delete_note (e->note ());
2559 /** Obtain the pointer position in canvas coordinates */
2561 Editor::get_pointer_position (double& x, double& y) const
2564 _track_canvas->get_pointer (px, py);
2565 _track_canvas->window_to_canvas (px, py, x, y);