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.
28 #include "pbd/error.h"
29 #include "pbd/enumwriter.h"
30 #include "pbd/memento_command.h"
31 #include "pbd/basename.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "gtkmm2ext/bindings.h"
35 #include "gtkmm2ext/utils.h"
36 #include "gtkmm2ext/tearoff.h"
38 #include "ardour_ui.h"
40 #include "canvas-note.h"
42 #include "time_axis_view.h"
43 #include "audio_time_axis.h"
44 #include "audio_region_view.h"
45 #include "midi_region_view.h"
47 #include "streamview.h"
48 #include "region_gain_line.h"
49 #include "automation_time_axis.h"
50 #include "control_point.h"
53 #include "selection.h"
56 #include "rgb_macros.h"
57 #include "control_point_dialog.h"
58 #include "editor_drag.h"
59 #include "automation_region_view.h"
60 #include "edit_note_dialog.h"
61 #include "mouse_cursors.h"
62 #include "editor_cursors.h"
64 #include "ardour/types.h"
65 #include "ardour/profile.h"
66 #include "ardour/route.h"
67 #include "ardour/audio_track.h"
68 #include "ardour/audio_diskstream.h"
69 #include "ardour/midi_diskstream.h"
70 #include "ardour/playlist.h"
71 #include "ardour/audioplaylist.h"
72 #include "ardour/audioregion.h"
73 #include "ardour/midi_region.h"
74 #include "ardour/dB.h"
75 #include "ardour/utils.h"
76 #include "ardour/region_factory.h"
77 #include "ardour/source_factory.h"
78 #include "ardour/session.h"
79 #include "ardour/operations.h"
86 using namespace ARDOUR;
89 using namespace Editing;
90 using Gtkmm2ext::Keyboard;
93 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
97 Gdk::ModifierType mask;
98 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
99 Glib::RefPtr<const Gdk::Window> pointer_window;
101 if (!canvas_window) {
105 pointer_window = canvas_window->get_pointer (x, y, mask);
107 if (pointer_window == track_canvas->get_bin_window()) {
110 in_track_canvas = true;
113 in_track_canvas = false;
118 event.type = GDK_BUTTON_RELEASE;
122 where = event_frame (&event, 0, 0);
127 Editor::event_frame (GdkEvent const * event, double* pcx, double* pcy) const
141 switch (event->type) {
142 case GDK_BUTTON_RELEASE:
143 case GDK_BUTTON_PRESS:
144 case GDK_2BUTTON_PRESS:
145 case GDK_3BUTTON_PRESS:
146 *pcx = event->button.x;
147 *pcy = event->button.y;
148 _trackview_group->w2i(*pcx, *pcy);
150 case GDK_MOTION_NOTIFY:
151 *pcx = event->motion.x;
152 *pcy = event->motion.y;
153 _trackview_group->w2i(*pcx, *pcy);
155 case GDK_ENTER_NOTIFY:
156 case GDK_LEAVE_NOTIFY:
157 track_canvas->w2c(event->crossing.x, event->crossing.y, *pcx, *pcy);
160 case GDK_KEY_RELEASE:
161 // track_canvas->w2c(event->key.x, event->key.y, *pcx, *pcy);
164 warning << string_compose (_("Editor::event_frame() used on unhandled event type %1"), event->type) << endmsg;
168 /* note that pixel_to_frame() never returns less than zero, so even if the pixel
169 position is negative (as can be the case with motion events in particular),
170 the frame location is always positive.
173 return pixel_to_frame (*pcx);
177 Editor::which_grabber_cursor ()
179 Gdk::Cursor* c = _cursors->grabber;
181 if (_internal_editing) {
182 switch (mouse_mode) {
184 c = _cursors->midi_pencil;
188 c = _cursors->grabber_note;
192 c = _cursors->midi_resize;
201 switch (_edit_point) {
203 c = _cursors->grabber_edit_point;
206 boost::shared_ptr<Movable> m = _movable.lock();
207 if (m && m->locked()) {
208 c = _cursors->speaker;
218 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
220 boost::shared_ptr<Trimmable> st = _trimmable.lock();
222 if (!st || st == t) {
224 set_canvas_cursor ();
229 Editor::set_current_movable (boost::shared_ptr<Movable> m)
231 boost::shared_ptr<Movable> sm = _movable.lock();
233 if (!sm || sm != m) {
235 set_canvas_cursor ();
240 Editor::set_canvas_cursor ()
242 if (_internal_editing) {
244 switch (mouse_mode) {
246 current_canvas_cursor = _cursors->midi_pencil;
250 current_canvas_cursor = which_grabber_cursor();
254 current_canvas_cursor = _cursors->midi_resize;
263 switch (mouse_mode) {
265 current_canvas_cursor = _cursors->selector;
269 current_canvas_cursor = which_grabber_cursor();
273 current_canvas_cursor = _cursors->cross_hair;
277 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
278 current_canvas_cursor = _cursors->zoom_out;
280 current_canvas_cursor = _cursors->zoom_in;
285 current_canvas_cursor = _cursors->time_fx; // just use playhead
289 current_canvas_cursor = _cursors->speaker;
294 switch (_join_object_range_state) {
295 case JOIN_OBJECT_RANGE_NONE:
297 case JOIN_OBJECT_RANGE_OBJECT:
298 current_canvas_cursor = which_grabber_cursor ();
300 case JOIN_OBJECT_RANGE_RANGE:
301 current_canvas_cursor = _cursors->selector;
305 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
306 if (join_object_range_button.get_active() && last_item_entered) {
307 if (last_item_entered->property_parent() && (*last_item_entered->property_parent()).get_data (X_("timeselection"))) {
308 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y + vertical_adjustment.get_value() - canvas_timebars_vsize);
309 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
310 current_canvas_cursor = _cursors->up_down;
315 set_canvas_cursor (current_canvas_cursor, true);
319 Editor::set_mouse_mode (MouseMode m, bool force)
321 if (_drags->active ()) {
325 if (!force && m == mouse_mode) {
329 Glib::RefPtr<Action> act;
333 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
337 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
341 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-gain"));
345 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-zoom"));
349 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
353 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
359 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
362 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
363 tact->set_active (false);
364 tact->set_active (true);
366 MouseModeChanged (); /* EMIT SIGNAL */
370 Editor::mouse_mode_toggled (MouseMode m)
376 if (!internal_editing()) {
377 if (mouse_mode != MouseRange && _join_object_range_state == JOIN_OBJECT_RANGE_NONE) {
379 /* in all modes except range and joined object/range, hide the range selection,
380 show the object (region) selection.
383 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
384 (*i)->hide_selection ();
390 in range or object/range mode, show the range selection.
393 for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
394 (*i)->show_selection (selection->time);
399 set_canvas_cursor ();
401 MouseModeChanged (); /* EMIT SIGNAL */
405 Editor::step_mouse_mode (bool next)
407 switch (current_mouse_mode()) {
410 if (Profile->get_sae()) {
411 set_mouse_mode (MouseZoom);
413 set_mouse_mode (MouseRange);
416 set_mouse_mode (MouseTimeFX);
421 if (next) set_mouse_mode (MouseZoom);
422 else set_mouse_mode (MouseObject);
427 if (Profile->get_sae()) {
428 set_mouse_mode (MouseTimeFX);
430 set_mouse_mode (MouseGain);
433 if (Profile->get_sae()) {
434 set_mouse_mode (MouseObject);
436 set_mouse_mode (MouseRange);
442 if (next) set_mouse_mode (MouseTimeFX);
443 else set_mouse_mode (MouseZoom);
448 set_mouse_mode (MouseAudition);
450 if (Profile->get_sae()) {
451 set_mouse_mode (MouseZoom);
453 set_mouse_mode (MouseGain);
459 if (next) set_mouse_mode (MouseObject);
460 else set_mouse_mode (MouseTimeFX);
466 Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemType item_type)
468 /* in object/audition/timefx/gain-automation mode,
469 any button press sets the selection if the object
470 can be selected. this is a bit of hack, because
471 we want to avoid this if the mouse operation is a
474 note: not dbl-click or triple-click
476 Also note that there is no region selection in internal edit mode, otherwise
477 for operations operating on the selection (e.g. cut) it is not obvious whether
478 to cut notes or regions.
481 if (((mouse_mode != MouseObject) &&
482 (_join_object_range_state != JOIN_OBJECT_RANGE_OBJECT) &&
483 (mouse_mode != MouseAudition || item_type != RegionItem) &&
484 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
485 (mouse_mode != MouseGain) &&
486 (mouse_mode != MouseRange)) ||
487 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3) ||
488 internal_editing()) {
493 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
495 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
497 /* almost no selection action on modified button-2 or button-3 events */
499 if (item_type != RegionItem && event->button.button != 2) {
505 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
506 bool press = (event->type == GDK_BUTTON_PRESS);
508 // begin_reversible_command (_("select on click"));
512 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
513 set_selected_regionview_from_click (press, op, true);
514 } else if (event->type == GDK_BUTTON_PRESS) {
515 selection->clear_tracks ();
516 set_selected_track_as_side_effect (op, true);
518 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
519 clicked_selection = select_range_around_region (selection->regions.front());
523 case RegionViewNameHighlight:
525 case LeftFrameHandle:
526 case RightFrameHandle:
527 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
528 set_selected_regionview_from_click (press, op, true);
529 } else if (event->type == GDK_BUTTON_PRESS) {
530 set_selected_track_as_side_effect (op);
535 case FadeInHandleItem:
537 case FadeOutHandleItem:
539 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
540 set_selected_regionview_from_click (press, op, true);
541 } else if (event->type == GDK_BUTTON_PRESS) {
542 set_selected_track_as_side_effect (op);
546 case ControlPointItem:
547 set_selected_track_as_side_effect (op, true);
548 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
549 set_selected_control_point_from_click (op, false);
554 /* for context click, select track */
555 if (event->button.button == 3) {
556 selection->clear_tracks ();
557 set_selected_track_as_side_effect (op, true);
561 case AutomationTrackItem:
562 set_selected_track_as_side_effect (op, true);
571 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
573 /* single mouse clicks on any of these item types operate
574 independent of mouse mode, mostly because they are
575 not on the main track canvas or because we want
580 case PlayheadCursorItem:
581 _drags->set (new CursorDrag (this, item, true), event);
585 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
586 hide_marker (item, event);
588 _drags->set (new MarkerDrag (this, item), event);
592 case TempoMarkerItem:
594 new TempoMarkerDrag (
597 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
603 case MeterMarkerItem:
605 new MeterMarkerDrag (
608 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
617 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
618 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
624 case RangeMarkerBarItem:
625 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
626 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
628 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
633 case CdMarkerBarItem:
634 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
635 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
637 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
642 case TransportMarkerBarItem:
643 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
644 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
646 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
655 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
656 /* special case: allow trim of range selections in joined object mode;
657 in theory eff should equal MouseRange in this case, but it doesn't
658 because entering the range selection canvas item results in entered_regionview
659 being set to 0, so update_join_object_range_location acts as if we aren't
662 if (item_type == StartSelectionTrimItem) {
663 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
664 } else if (item_type == EndSelectionTrimItem) {
665 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
669 Editing::MouseMode eff = effective_mouse_mode ();
671 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
672 if (item_type == FadeInHandleItem || item_type == FadeOutHandleItem) {
679 case StartSelectionTrimItem:
680 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
683 case EndSelectionTrimItem:
684 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
688 if (Keyboard::modifier_state_contains
689 (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier))) {
690 // contains and not equals because I can't use alt as a modifier alone.
691 start_selection_grab (item, event);
692 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
693 /* grab selection for moving */
694 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
696 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
697 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
699 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
700 if (join_object_range_button.get_active() && atv) {
701 /* smart "join" mode: drag automation */
702 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, _cursors->up_down);
704 /* this was debated, but decided the more common action was to
705 make a new selection */
706 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
713 if (internal_editing()) {
714 /* trim notes if we're in internal edit mode and near the ends of the note */
715 ArdourCanvas::CanvasNote* cn = dynamic_cast<ArdourCanvas::CanvasNote*> (item);
716 if (cn->big_enough_to_trim() && cn->mouse_near_ends()) {
717 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
719 _drags->set (new NoteDrag (this, item), event);
725 if (internal_editing()) {
726 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
727 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
731 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
736 case RegionViewNameHighlight:
737 if (!clicked_regionview->region()->locked()) {
738 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
739 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
744 case LeftFrameHandle:
745 case RightFrameHandle:
746 if (!internal_editing() && !clicked_regionview->region()->locked()) {
747 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
748 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
754 if (!internal_editing()) {
755 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
764 if (internal_editing()) {
765 ArdourCanvas::CanvasNoteEvent* cn = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
766 if (cn->mouse_near_ends()) {
767 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
769 _drags->set (new NoteDrag (this, item), event);
779 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
780 event->type == GDK_BUTTON_PRESS) {
782 _drags->set (new RubberbandSelectDrag (this, item), event);
784 } else if (event->type == GDK_BUTTON_PRESS) {
787 case FadeInHandleItem:
789 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
790 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, _cursors->fade_in);
794 case FadeOutHandleItem:
796 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
797 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, _cursors->fade_out);
801 case FeatureLineItem:
803 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
804 remove_transient(item);
808 _drags->set (new FeatureLineDrag (this, item), event);
814 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
815 /* click on an automation region view; do nothing here and let the ARV's signal handler
821 if (internal_editing ()) {
822 /* no region drags in internal edit mode */
826 /* click on a normal region view */
827 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
828 add_region_copy_drag (item, event, clicked_regionview);
829 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
830 add_region_brush_drag (item, event, clicked_regionview);
832 add_region_drag (item, event, clicked_regionview);
835 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
836 _drags->add (new SelectionDrag (this, clicked_axisview->get_selection_rect (clicked_selection)->rect, SelectionDrag::SelectionMove));
839 _drags->start_grab (event);
842 case RegionViewNameHighlight:
843 case LeftFrameHandle:
844 case RightFrameHandle:
845 if (!clicked_regionview->region()->locked()) {
846 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
847 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
854 /* rename happens on edit clicks */
855 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
856 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, s.by_layer()), event);
861 case ControlPointItem:
862 _drags->set (new ControlPointDrag (this, item), event);
866 case AutomationLineItem:
867 _drags->set (new LineDrag (this, item), event);
872 if (internal_editing()) {
873 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
874 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
878 _drags->set (new RubberbandSelectDrag (this, item), event);
882 case AutomationTrackItem:
883 /* rubberband drag to select automation points */
884 _drags->set (new RubberbandSelectDrag (this, item), event);
889 if (join_object_range_button.get_active()) {
890 /* we're in "smart" joined mode, and we've clicked on a Selection */
891 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
892 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
894 /* if we're over an automation track, start a drag of its data */
895 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
897 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, _cursors->up_down);
900 /* if we're over a track and a region, and in the `object' part of a region,
901 put a selection around the region and drag both
903 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
904 if (rtv && _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
905 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (rtv->route ());
907 boost::shared_ptr<Playlist> pl = t->playlist ();
910 boost::shared_ptr<Region> r = pl->top_region_at (event_frame (event));
912 RegionView* rv = rtv->view()->find_view (r);
913 clicked_selection = select_range_around_region (rv);
914 _drags->add (new SelectionDrag (this, item, SelectionDrag::SelectionMove));
915 list<RegionView*> rvs;
917 _drags->add (new RegionMoveDrag (this, item, rv, rvs, false, false));
918 _drags->start_grab (event);
929 case ImageFrameHandleStartItem:
930 imageframe_start_handle_op(item, event) ;
933 case ImageFrameHandleEndItem:
934 imageframe_end_handle_op(item, event) ;
937 case MarkerViewHandleStartItem:
938 markerview_item_start_handle_op(item, event) ;
941 case MarkerViewHandleEndItem:
942 markerview_item_end_handle_op(item, event) ;
946 start_markerview_grab(item, event) ;
949 start_imageframe_grab(item, event) ;
967 /* start a grab so that if we finish after moving
968 we can tell what happened.
970 _drags->set (new RegionGainDrag (this, item), event, current_canvas_cursor);
974 _drags->set (new LineDrag (this, item), event);
977 case ControlPointItem:
978 _drags->set (new ControlPointDrag (this, item), event);
989 case ControlPointItem:
990 _drags->set (new ControlPointDrag (this, item), event);
993 case AutomationLineItem:
994 _drags->set (new LineDrag (this, item), event);
998 // XXX need automation mode to identify which
1000 // start_line_grab_from_regionview (item, event);
1010 if (event->type == GDK_BUTTON_PRESS) {
1011 _drags->set (new MouseZoomDrag (this, item), event);
1018 if (internal_editing() && item_type == NoteItem) {
1019 /* drag notes if we're in internal edit mode */
1020 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
1022 } else if ((!internal_editing() || dynamic_cast<AudioRegionView*> (clicked_regionview)) && clicked_regionview) {
1023 /* do time-FX if we're not in internal edit mode, or we are but we clicked on an audio region */
1024 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1030 _drags->set (new ScrubDrag (this, item), event);
1031 scrub_reversals = 0;
1032 scrub_reverse_distance = 0;
1033 last_scrub_x = event->button.x;
1034 scrubbing_direction = 0;
1035 set_canvas_cursor (_cursors->transparent);
1047 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1049 Editing::MouseMode const eff = effective_mouse_mode ();
1052 switch (item_type) {
1054 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1055 add_region_copy_drag (item, event, clicked_regionview);
1057 add_region_drag (item, event, clicked_regionview);
1059 _drags->start_grab (event);
1062 case ControlPointItem:
1063 _drags->set (new ControlPointDrag (this, item), event);
1071 switch (item_type) {
1072 case RegionViewNameHighlight:
1073 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1077 case LeftFrameHandle:
1078 case RightFrameHandle:
1079 if (!internal_editing ()) {
1080 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1085 case RegionViewName:
1086 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1097 /* relax till release */
1103 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1104 temporal_zoom_to_frame (false, event_frame (event));
1106 temporal_zoom_to_frame (true, event_frame(event));
1119 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1121 if (event->type != GDK_BUTTON_PRESS) {
1125 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
1127 if (canvas_window) {
1128 Glib::RefPtr<const Gdk::Window> pointer_window;
1131 Gdk::ModifierType mask;
1133 pointer_window = canvas_window->get_pointer (x, y, mask);
1135 if (pointer_window == track_canvas->get_bin_window()) {
1136 track_canvas->window_to_world (x, y, wx, wy);
1140 pre_press_cursor = current_canvas_cursor;
1142 track_canvas->grab_focus();
1144 if (_session && _session->actively_recording()) {
1148 button_selection (item, event, item_type);
1150 if (!_drags->active () &&
1151 (Keyboard::is_delete_event (&event->button) ||
1152 Keyboard::is_context_menu_event (&event->button) ||
1153 Keyboard::is_edit_event (&event->button))) {
1155 /* handled by button release */
1159 switch (event->button.button) {
1161 return button_press_handler_1 (item, event, item_type);
1165 return button_press_handler_2 (item, event, item_type);
1172 return button_press_dispatch (&event->button);
1181 Editor::button_press_dispatch (GdkEventButton* ev)
1183 /* this function is intended only for buttons 4 and above.
1186 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1187 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1191 Editor::button_release_dispatch (GdkEventButton* ev)
1193 /* this function is intended only for buttons 4 and above.
1196 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1197 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1201 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1203 framepos_t where = event_frame (event, 0, 0);
1204 AutomationTimeAxisView* atv = 0;
1206 if (pre_press_cursor) {
1207 set_canvas_cursor (pre_press_cursor);
1208 pre_press_cursor = 0;
1211 /* no action if we're recording */
1213 if (_session && _session->actively_recording()) {
1217 /* see if we're finishing a drag */
1219 bool were_dragging = false;
1220 if (_drags->active ()) {
1221 bool const r = _drags->end_grab (event);
1223 /* grab dragged, so do nothing else */
1227 were_dragging = true;
1230 update_region_layering_order_editor ();
1232 /* edit events get handled here */
1234 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1235 switch (item_type) {
1237 show_region_properties ();
1240 case TempoMarkerItem:
1241 edit_tempo_marker (item);
1244 case MeterMarkerItem:
1245 edit_meter_marker (item);
1248 case RegionViewName:
1249 if (clicked_regionview->name_active()) {
1250 return mouse_rename_region (item, event);
1254 case ControlPointItem:
1255 edit_control_point (item);
1268 /* context menu events get handled here */
1270 if (Keyboard::is_context_menu_event (&event->button)) {
1272 if (!_drags->active ()) {
1274 /* no matter which button pops up the context menu, tell the menu
1275 widget to use button 1 to drive menu selection.
1278 switch (item_type) {
1280 case FadeInHandleItem:
1282 case FadeOutHandleItem:
1283 popup_fade_context_menu (1, event->button.time, item, item_type);
1287 popup_track_context_menu (1, event->button.time, item_type, false);
1291 case RegionViewNameHighlight:
1292 case LeftFrameHandle:
1293 case RightFrameHandle:
1294 case RegionViewName:
1295 popup_track_context_menu (1, event->button.time, item_type, false);
1299 popup_track_context_menu (1, event->button.time, item_type, true);
1302 case AutomationTrackItem:
1303 popup_track_context_menu (1, event->button.time, item_type, false);
1307 case RangeMarkerBarItem:
1308 case TransportMarkerBarItem:
1309 case CdMarkerBarItem:
1312 popup_ruler_menu (where, item_type);
1316 marker_context_menu (&event->button, item);
1319 case TempoMarkerItem:
1320 tempo_or_meter_marker_context_menu (&event->button, item);
1323 case MeterMarkerItem:
1324 tempo_or_meter_marker_context_menu (&event->button, item);
1327 case CrossfadeViewItem:
1328 popup_track_context_menu (1, event->button.time, item_type, false);
1332 case ImageFrameItem:
1333 popup_imageframe_edit_menu(1, event->button.time, item, true) ;
1335 case ImageFrameTimeAxisItem:
1336 popup_imageframe_edit_menu(1, event->button.time, item, false) ;
1338 case MarkerViewItem:
1339 popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ;
1341 case MarkerTimeAxisItem:
1342 popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
1354 /* delete events get handled here */
1356 Editing::MouseMode const eff = effective_mouse_mode ();
1358 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1360 switch (item_type) {
1361 case TempoMarkerItem:
1362 remove_tempo_marker (item);
1365 case MeterMarkerItem:
1366 remove_meter_marker (item);
1370 remove_marker (*item, event);
1374 if (eff == MouseObject) {
1375 remove_clicked_region ();
1379 case ControlPointItem:
1380 if (eff == MouseGain) {
1381 remove_gain_control_point (item, event);
1383 remove_control_point (item, event);
1388 remove_midi_note (item, event);
1397 switch (event->button.button) {
1400 switch (item_type) {
1401 /* see comments in button_press_handler */
1402 case PlayheadCursorItem:
1405 case AutomationLineItem:
1406 case StartSelectionTrimItem:
1407 case EndSelectionTrimItem:
1411 if (!_dragging_playhead) {
1412 snap_to_with_modifier (where, event, 0, true);
1413 mouse_add_new_marker (where);
1417 case CdMarkerBarItem:
1418 if (!_dragging_playhead) {
1419 // if we get here then a dragged range wasn't done
1420 snap_to_with_modifier (where, event, 0, true);
1421 mouse_add_new_marker (where, true);
1426 if (!_dragging_playhead) {
1427 snap_to_with_modifier (where, event);
1428 mouse_add_new_tempo_event (where);
1433 if (!_dragging_playhead) {
1434 mouse_add_new_meter_event (pixel_to_frame (event->button.x));
1445 switch (item_type) {
1446 case AutomationTrackItem:
1447 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1449 atv->add_automation_event (item, event, where, event->button.y);
1460 switch (item_type) {
1463 /* check that we didn't drag before releasing, since
1464 its really annoying to create new control
1465 points when doing this.
1467 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1468 if (were_dragging && arv) {
1469 arv->add_gain_point_event (item, event);
1475 case AutomationTrackItem:
1476 dynamic_cast<AutomationTimeAxisView*>(clicked_axisview)->
1477 add_automation_event (item, event, where, event->button.y);
1486 set_canvas_cursor (current_canvas_cursor);
1487 if (scrubbing_direction == 0) {
1488 /* no drag, just a click */
1489 switch (item_type) {
1491 play_selected_region ();
1497 /* make sure we stop */
1498 _session->request_transport_speed (0.0);
1515 switch (item_type) {
1517 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1519 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1522 // Button2 click is unused
1535 // x_style_paste (where, 1.0);
1555 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1562 last_item_entered = item;
1564 switch (item_type) {
1565 case ControlPointItem:
1566 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1567 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1568 cp->set_visible (true);
1572 at_y = cp->get_y ();
1573 cp->i2w (at_x, at_y);
1577 fraction = 1.0 - (cp->get_y() / cp->line().height());
1579 if (is_drawable() && !_drags->active ()) {
1580 set_canvas_cursor (_cursors->fader);
1583 set_verbose_canvas_cursor (cp->line().get_verbose_cursor_string (fraction), at_x, at_y);
1584 show_verbose_canvas_cursor ();
1589 if (mouse_mode == MouseGain) {
1590 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1592 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
1593 if (is_drawable()) {
1594 set_canvas_cursor (_cursors->fader);
1599 case AutomationLineItem:
1600 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1602 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1604 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
1606 if (is_drawable()) {
1607 set_canvas_cursor (_cursors->fader);
1612 case RegionViewNameHighlight:
1613 if (is_drawable() && mouse_mode == MouseObject && entered_regionview) {
1614 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1615 _over_region_trim_target = true;
1619 case LeftFrameHandle:
1620 case RightFrameHandle:
1621 if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
1622 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1626 case StartSelectionTrimItem:
1627 case EndSelectionTrimItem:
1630 case ImageFrameHandleStartItem:
1631 case ImageFrameHandleEndItem:
1632 case MarkerViewHandleStartItem:
1633 case MarkerViewHandleEndItem:
1636 if (is_drawable()) {
1637 set_canvas_cursor (_cursors->trimmer);
1641 case PlayheadCursorItem:
1642 if (is_drawable()) {
1643 switch (_edit_point) {
1645 set_canvas_cursor (_cursors->grabber_edit_point);
1648 set_canvas_cursor (_cursors->grabber);
1654 case RegionViewName:
1656 /* when the name is not an active item, the entire name highlight is for trimming */
1658 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1659 if (mouse_mode == MouseObject && is_drawable()) {
1660 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1661 _over_region_trim_target = true;
1667 case AutomationTrackItem:
1668 if (is_drawable()) {
1669 Gdk::Cursor *cursor;
1670 switch (mouse_mode) {
1672 cursor = _cursors->selector;
1675 cursor = _cursors->zoom_in;
1678 cursor = _cursors->cross_hair;
1682 set_canvas_cursor (cursor);
1684 AutomationTimeAxisView* atv;
1685 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1686 clear_entered_track = false;
1687 set_entered_track (atv);
1693 case RangeMarkerBarItem:
1694 case TransportMarkerBarItem:
1695 case CdMarkerBarItem:
1698 if (is_drawable()) {
1699 set_canvas_cursor (_cursors->timebar);
1704 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1707 entered_marker = marker;
1708 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1710 case MeterMarkerItem:
1711 case TempoMarkerItem:
1712 if (is_drawable()) {
1713 set_canvas_cursor (_cursors->timebar);
1717 case FadeInHandleItem:
1718 if (mouse_mode == MouseObject && !internal_editing()) {
1719 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1721 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1723 set_canvas_cursor (_cursors->fade_in);
1727 case FadeOutHandleItem:
1728 if (mouse_mode == MouseObject && !internal_editing()) {
1729 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1731 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1733 set_canvas_cursor (_cursors->fade_out);
1736 case FeatureLineItem:
1738 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1739 line->property_fill_color_rgba() = 0xFF0000FF;
1743 if (join_object_range_button.get_active()) {
1744 set_canvas_cursor ();
1752 /* second pass to handle entered track status in a comprehensible way.
1755 switch (item_type) {
1757 case AutomationLineItem:
1758 case ControlPointItem:
1759 /* these do not affect the current entered track state */
1760 clear_entered_track = false;
1763 case AutomationTrackItem:
1764 /* handled above already */
1768 set_entered_track (0);
1776 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1786 switch (item_type) {
1787 case ControlPointItem:
1788 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1789 if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
1790 if (cp->line().npoints() > 1 && !cp->get_selected()) {
1791 cp->set_visible (false);
1795 if (is_drawable()) {
1796 set_canvas_cursor (current_canvas_cursor);
1799 hide_verbose_canvas_cursor ();
1802 case RegionViewNameHighlight:
1803 case LeftFrameHandle:
1804 case RightFrameHandle:
1805 case StartSelectionTrimItem:
1806 case EndSelectionTrimItem:
1807 case PlayheadCursorItem:
1810 case ImageFrameHandleStartItem:
1811 case ImageFrameHandleEndItem:
1812 case MarkerViewHandleStartItem:
1813 case MarkerViewHandleEndItem:
1816 _over_region_trim_target = false;
1818 if (is_drawable()) {
1819 set_canvas_cursor (current_canvas_cursor);
1824 case AutomationLineItem:
1825 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1827 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1829 line->property_fill_color_rgba() = al->get_line_color();
1831 if (is_drawable()) {
1832 set_canvas_cursor (current_canvas_cursor);
1836 case RegionViewName:
1837 /* see enter_handler() for notes */
1838 _over_region_trim_target = false;
1840 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1841 if (is_drawable() && mouse_mode == MouseObject) {
1842 set_canvas_cursor (current_canvas_cursor);
1847 case RangeMarkerBarItem:
1848 case TransportMarkerBarItem:
1849 case CdMarkerBarItem:
1853 if (is_drawable()) {
1854 set_canvas_cursor (current_canvas_cursor);
1859 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1863 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1864 location_flags_changed (loc, this);
1867 case MeterMarkerItem:
1868 case TempoMarkerItem:
1870 if (is_drawable()) {
1871 set_canvas_cursor (_cursors->timebar);
1876 case FadeInHandleItem:
1877 case FadeOutHandleItem:
1878 rv = static_cast<RegionView*>(item->get_data ("regionview"));
1880 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1882 rect->property_fill_color_rgba() = rv->get_fill_color();
1883 rect->property_outline_pixels() = 0;
1886 set_canvas_cursor (current_canvas_cursor);
1889 case AutomationTrackItem:
1890 if (is_drawable()) {
1891 set_canvas_cursor (current_canvas_cursor);
1892 clear_entered_track = true;
1893 Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track));
1896 case FeatureLineItem:
1898 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1899 line->property_fill_color_rgba() = (guint) ARDOUR_UI::config()->canvasvar_ZeroLine.get();;
1911 Editor::left_automation_track ()
1913 if (clear_entered_track) {
1914 set_entered_track (0);
1915 clear_entered_track = false;
1921 Editor::scrub (framepos_t frame, double current_x)
1925 if (scrubbing_direction == 0) {
1927 _session->request_locate (frame, false);
1928 _session->request_transport_speed (0.1);
1929 scrubbing_direction = 1;
1933 if (last_scrub_x > current_x) {
1935 /* pointer moved to the left */
1937 if (scrubbing_direction > 0) {
1939 /* we reversed direction to go backwards */
1942 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1946 /* still moving to the left (backwards) */
1948 scrub_reversals = 0;
1949 scrub_reverse_distance = 0;
1951 delta = 0.01 * (last_scrub_x - current_x);
1952 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1956 /* pointer moved to the right */
1958 if (scrubbing_direction < 0) {
1959 /* we reversed direction to go forward */
1962 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1965 /* still moving to the right */
1967 scrub_reversals = 0;
1968 scrub_reverse_distance = 0;
1970 delta = 0.01 * (current_x - last_scrub_x);
1971 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1975 /* if there have been more than 2 opposite motion moves detected, or one that moves
1976 back more than 10 pixels, reverse direction
1979 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1981 if (scrubbing_direction > 0) {
1982 /* was forwards, go backwards */
1983 _session->request_transport_speed (-0.1);
1984 scrubbing_direction = -1;
1986 /* was backwards, go forwards */
1987 _session->request_transport_speed (0.1);
1988 scrubbing_direction = 1;
1991 scrub_reverse_distance = 0;
1992 scrub_reversals = 0;
1996 last_scrub_x = current_x;
2000 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2002 _last_motion_y = event->motion.y;
2004 if (event->motion.is_hint) {
2007 /* We call this so that MOTION_NOTIFY events continue to be
2008 delivered to the canvas. We need to do this because we set
2009 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2010 the density of the events, at the expense of a round-trip
2011 to the server. Given that this will mostly occur on cases
2012 where DISPLAY = :0.0, and given the cost of what the motion
2013 event might do, its a good tradeoff.
2016 track_canvas->get_pointer (x, y);
2019 if (current_stepping_trackview) {
2020 /* don't keep the persistent stepped trackview if the mouse moves */
2021 current_stepping_trackview = 0;
2022 step_timeout.disconnect ();
2025 if (_session && _session->actively_recording()) {
2026 /* Sorry. no dragging stuff around while we record */
2030 JoinObjectRangeState const old = _join_object_range_state;
2031 update_join_object_range_location (event->motion.x, event->motion.y);
2032 if (_join_object_range_state != old) {
2033 set_canvas_cursor ();
2036 if (_over_region_trim_target) {
2037 set_canvas_cursor_for_region_view (event->motion.x, entered_regionview);
2040 bool handled = false;
2041 if (_drags->active ()) {
2042 handled = _drags->motion_handler (event, from_autoscroll);
2049 track_canvas_motion (event);
2054 Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* /*event*/)
2056 ControlPoint* control_point;
2058 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2059 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2063 // We shouldn't remove the first or last gain point
2064 if (control_point->line().is_last_point(*control_point) ||
2065 control_point->line().is_first_point(*control_point)) {
2069 control_point->line().remove_point (*control_point);
2073 Editor::remove_control_point (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2075 ControlPoint* control_point;
2077 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2078 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2082 control_point->line().remove_point (*control_point);
2086 Editor::edit_control_point (ArdourCanvas::Item* item)
2088 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2091 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2095 ControlPointDialog d (p);
2096 d.set_position (Gtk::WIN_POS_MOUSE);
2099 if (d.run () != RESPONSE_ACCEPT) {
2103 p->line().modify_point_y (*p, d.get_y_fraction ());
2107 Editor::edit_note (ArdourCanvas::Item* item)
2109 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2112 EditNoteDialog d (&e->region_view(), e);
2113 d.set_position (Gtk::WIN_POS_MOUSE);
2121 Editor::visible_order_range (int* low, int* high) const
2123 *low = TimeAxisView::max_order ();
2126 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2128 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2130 if (!rtv->hidden()) {
2132 if (*high < rtv->order()) {
2133 *high = rtv->order ();
2136 if (*low > rtv->order()) {
2137 *low = rtv->order ();
2144 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2146 /* Either add to or set the set the region selection, unless
2147 this is an alignment click (control used)
2150 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2151 TimeAxisView* tv = &rv.get_time_axis_view();
2152 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2154 if (rtv && rtv->is_track()) {
2155 speed = rtv->track()->speed();
2158 framepos_t where = get_preferred_edit_position();
2162 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2164 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2166 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2168 align_region (rv.region(), End, (framepos_t) (where * speed));
2172 align_region (rv.region(), Start, (framepos_t) (where * speed));
2179 Editor::show_verbose_time_cursor (framepos_t frame, double offset, double xpos, double ypos)
2182 Timecode::Time timecode;
2183 Timecode::BBT_Time bbt;
2185 framepos_t frame_rate;
2188 if (_session == 0) {
2194 if (Profile->get_sae() || Profile->get_small_screen()) {
2195 m = ARDOUR_UI::instance()->primary_clock.mode();
2197 m = ARDOUR_UI::instance()->secondary_clock.mode();
2201 case AudioClock::BBT:
2202 _session->bbt_time (frame, bbt);
2203 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, bbt.bars, bbt.beats, bbt.ticks);
2206 case AudioClock::Timecode:
2207 _session->timecode_time (frame, timecode);
2208 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2211 case AudioClock::MinSec:
2212 /* XXX this is copied from show_verbose_duration_cursor() */
2213 frame_rate = _session->frame_rate();
2214 hours = frame / (frame_rate * 3600);
2215 frame = frame % (frame_rate * 3600);
2216 mins = frame / (frame_rate * 60);
2217 frame = frame % (frame_rate * 60);
2218 secs = (float) frame / (float) frame_rate;
2219 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
2223 snprintf (buf, sizeof(buf), "%" PRIi64, frame);
2227 if (xpos >= 0 && ypos >=0) {
2228 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2230 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset - horizontal_position(), _drags->current_pointer_y() + offset - vertical_adjustment.get_value() + canvas_timebars_vsize);
2232 show_verbose_canvas_cursor ();
2236 Editor::show_verbose_duration_cursor (framepos_t start, framepos_t end, double offset, double xpos, double ypos)
2239 Timecode::Time timecode;
2240 Timecode::BBT_Time sbbt;
2241 Timecode::BBT_Time ebbt;
2243 framepos_t distance, frame_rate;
2245 Meter meter_at_start(_session->tempo_map().meter_at(start));
2247 if (_session == 0) {
2253 if (Profile->get_sae() || Profile->get_small_screen()) {
2254 m = ARDOUR_UI::instance()->primary_clock.mode ();
2256 m = ARDOUR_UI::instance()->secondary_clock.mode ();
2260 case AudioClock::BBT:
2261 _session->bbt_time (start, sbbt);
2262 _session->bbt_time (end, ebbt);
2265 /* XXX this computation won't work well if the
2266 user makes a selection that spans any meter changes.
2269 ebbt.bars -= sbbt.bars;
2270 if (ebbt.beats >= sbbt.beats) {
2271 ebbt.beats -= sbbt.beats;
2274 ebbt.beats = int(meter_at_start.beats_per_bar()) + ebbt.beats - sbbt.beats;
2276 if (ebbt.ticks >= sbbt.ticks) {
2277 ebbt.ticks -= sbbt.ticks;
2280 ebbt.ticks = int(Timecode::BBT_Time::ticks_per_beat) + ebbt.ticks - sbbt.ticks;
2283 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, ebbt.bars, ebbt.beats, ebbt.ticks);
2286 case AudioClock::Timecode:
2287 _session->timecode_duration (end - start, timecode);
2288 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2291 case AudioClock::MinSec:
2292 /* XXX this stuff should be elsewhere.. */
2293 distance = end - start;
2294 frame_rate = _session->frame_rate();
2295 hours = distance / (frame_rate * 3600);
2296 distance = distance % (frame_rate * 3600);
2297 mins = distance / (frame_rate * 60);
2298 distance = distance % (frame_rate * 60);
2299 secs = (float) distance / (float) frame_rate;
2300 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
2304 snprintf (buf, sizeof(buf), "%" PRIi64, end - start);
2308 if (xpos >= 0 && ypos >=0) {
2309 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2312 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset, _drags->current_pointer_y() + offset);
2315 show_verbose_canvas_cursor ();
2319 Editor::collect_new_region_view (RegionView* rv)
2321 latest_regionviews.push_back (rv);
2325 Editor::collect_and_select_new_region_view (RegionView* rv)
2328 latest_regionviews.push_back (rv);
2332 Editor::cancel_selection ()
2334 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2335 (*i)->hide_selection ();
2338 selection->clear ();
2339 clicked_selection = 0;
2344 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2346 RegionView* rv = clicked_regionview;
2348 /* Choose action dependant on which button was pressed */
2349 switch (event->button.button) {
2351 begin_reversible_command (_("start point trim"));
2353 if (selection->selected (rv)) {
2354 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2355 i != selection->regions.by_layer().end(); ++i)
2358 cerr << "region view contains null region" << endl;
2361 if (!(*i)->region()->locked()) {
2362 (*i)->region()->clear_changes ();
2363 (*i)->region()->trim_front (new_bound, this);
2364 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2369 if (!rv->region()->locked()) {
2370 rv->region()->clear_changes ();
2371 rv->region()->trim_front (new_bound, this);
2372 _session->add_command(new StatefulDiffCommand (rv->region()));
2376 commit_reversible_command();
2380 begin_reversible_command (_("End point trim"));
2382 if (selection->selected (rv)) {
2384 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2386 if (!(*i)->region()->locked()) {
2387 (*i)->region()->clear_changes();
2388 (*i)->region()->trim_end (new_bound, this);
2389 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2395 if (!rv->region()->locked()) {
2396 rv->region()->clear_changes ();
2397 rv->region()->trim_end (new_bound, this);
2398 _session->add_command (new StatefulDiffCommand (rv->region()));
2402 commit_reversible_command();
2411 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2416 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2417 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2421 Location* location = find_location_from_marker (marker, is_start);
2422 location->set_hidden (true, this);
2427 Editor::reposition_zoom_rect (framepos_t start, framepos_t end)
2429 double x1 = frame_to_pixel (start);
2430 double x2 = frame_to_pixel (end);
2431 double y2 = full_canvas_height - 1.0;
2433 zoom_rect->property_x1() = x1;
2434 zoom_rect->property_y1() = 1.0;
2435 zoom_rect->property_x2() = x2;
2436 zoom_rect->property_y2() = y2;
2441 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2443 using namespace Gtkmm2ext;
2445 ArdourPrompter prompter (false);
2447 prompter.set_prompt (_("Name for region:"));
2448 prompter.set_initial_text (clicked_regionview->region()->name());
2449 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2450 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2451 prompter.show_all ();
2452 switch (prompter.run ()) {
2453 case Gtk::RESPONSE_ACCEPT:
2455 prompter.get_result(str);
2457 clicked_regionview->region()->set_name (str);
2466 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2468 /* no brushing without a useful snap setting */
2470 switch (_snap_mode) {
2472 return; /* can't work because it allows region to be placed anywhere */
2477 switch (_snap_type) {
2485 /* don't brush a copy over the original */
2487 if (pos == rv->region()->position()) {
2491 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2493 if (rtv == 0 || !rtv->is_track()) {
2497 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2498 double speed = rtv->track()->speed();
2500 playlist->clear_changes ();
2501 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2502 playlist->add_region (new_region, (framepos_t) (pos * speed));
2503 _session->add_command (new StatefulDiffCommand (playlist));
2505 // playlist is frozen, so we have to update manually XXX this is disgusting
2507 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2511 Editor::track_height_step_timeout ()
2513 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2514 current_stepping_trackview = 0;
2521 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2523 assert (region_view);
2525 if (!region_view->region()->playlist()) {
2529 _region_motion_group->raise_to_top ();
2531 if (Config->get_edit_mode() == Splice) {
2532 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2534 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2535 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false));
2538 /* sync the canvas to what we think is its current state */
2539 update_canvas_now();
2543 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2545 assert (region_view);
2547 if (!region_view->region()->playlist()) {
2551 _region_motion_group->raise_to_top ();
2553 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2554 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true));
2558 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2560 assert (region_view);
2562 if (!region_view->region()->playlist()) {
2566 if (Config->get_edit_mode() == Splice) {
2570 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2571 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), true, false));
2573 begin_reversible_command (Operations::drag_region_brush);
2576 /** Start a grab where a time range is selected, track(s) are selected, and the
2577 * user clicks and drags a region with a modifier in order to create a new region containing
2578 * the section of the clicked region that lies within the time range.
2581 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2583 if (clicked_regionview == 0) {
2587 /* lets try to create new Region for the selection */
2589 vector<boost::shared_ptr<Region> > new_regions;
2590 create_region_from_selection (new_regions);
2592 if (new_regions.empty()) {
2596 /* XXX fix me one day to use all new regions */
2598 boost::shared_ptr<Region> region (new_regions.front());
2600 /* add it to the current stream/playlist.
2602 tricky: the streamview for the track will add a new regionview. we will
2603 catch the signal it sends when it creates the regionview to
2604 set the regionview we want to then drag.
2607 latest_regionviews.clear();
2608 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2610 /* A selection grab currently creates two undo/redo operations, one for
2611 creating the new region and another for moving it.
2614 begin_reversible_command (Operations::selection_grab);
2616 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2618 playlist->clear_changes ();
2619 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2620 _session->add_command(new StatefulDiffCommand (playlist));
2622 commit_reversible_command ();
2626 if (latest_regionviews.empty()) {
2627 /* something went wrong */
2631 /* we need to deselect all other regionviews, and select this one
2632 i'm ignoring undo stuff, because the region creation will take care of it
2634 selection->set (latest_regionviews);
2636 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2642 if (_drags->active ()) {
2645 selection->clear ();
2650 Editor::set_internal_edit (bool yn)
2652 _internal_editing = yn;
2655 mouse_select_button.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
2656 mouse_select_button.get_image ()->show ();
2657 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Draw/Edit MIDI Notes"));
2658 mouse_mode_toggled (mouse_mode);
2660 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2661 (*i)->enter_internal_edit_mode ();
2666 mouse_select_button.set_image (*(manage (new Image (::get_icon("tool_range")))));
2667 mouse_select_button.get_image ()->show ();
2668 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Select/Move Ranges"));
2669 mouse_mode_toggled (mouse_mode); // sets cursor
2671 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2672 (*i)->leave_internal_edit_mode ();
2677 /** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
2678 * used by the `join object/range' tool mode.
2681 Editor::update_join_object_range_location (double x, double y)
2683 /* XXX: actually, this decides based on whether the mouse is in the top or bottom half of a RouteTimeAxisView;
2684 entered_{track,regionview} is not always setup (e.g. if the mouse is over a TimeSelection), and to get a Region
2685 that we're over requires searching the playlist.
2688 if (join_object_range_button.get_active() == false || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
2689 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2693 if (mouse_mode == MouseObject) {
2694 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2695 } else if (mouse_mode == MouseRange) {
2696 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2699 /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
2700 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y + vertical_adjustment.get_value() - canvas_timebars_vsize);
2704 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2709 rtv->canvas_display()->w2i (cx, cy);
2711 double const c = cy / rtv->view()->child_height();
2713 double const f = modf (c, &d);
2715 _join_object_range_state = f < 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2721 Editor::effective_mouse_mode () const
2723 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2725 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2733 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2735 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2738 e->region_view().delete_note (e->note ());
2742 Editor::set_canvas_cursor_for_region_view (double x, RegionView* rv)
2744 ArdourCanvas::Group* g = rv->get_canvas_group ();
2745 ArdourCanvas::Group* p = g->get_parent_group ();
2747 /* Compute x in region view parent coordinates */
2751 double x1, x2, y1, y2;
2752 g->get_bounds (x1, y1, x2, y2);
2754 /* Halfway across the region */
2755 double const h = (x1 + x2) / 2;
2757 Trimmable::CanTrim ct = rv->region()->can_trim ();
2759 if (ct & Trimmable::FrontTrimEarlier) {
2760 set_canvas_cursor (_cursors->left_side_trim);
2762 set_canvas_cursor (_cursors->left_side_trim_right_only);
2765 if (ct & Trimmable::EndTrimLater) {
2766 set_canvas_cursor (_cursors->right_side_trim);
2768 set_canvas_cursor (_cursors->right_side_trim_left_only);