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 <gtkmm2ext/utils.h>
31 #include <gtkmm2ext/tearoff.h>
32 #include "pbd/memento_command.h"
33 #include "pbd/basename.h"
34 #include "pbd/stateful_diff_command.h"
36 #include "ardour_ui.h"
38 #include "canvas-note.h"
40 #include "time_axis_view.h"
41 #include "audio_time_axis.h"
42 #include "audio_region_view.h"
43 #include "midi_region_view.h"
45 #include "streamview.h"
46 #include "region_gain_line.h"
47 #include "automation_time_axis.h"
48 #include "control_point.h"
51 #include "selection.h"
54 #include "rgb_macros.h"
55 #include "control_point_dialog.h"
56 #include "editor_drag.h"
57 #include "automation_region_view.h"
58 #include "edit_note_dialog.h"
59 #include "mouse_cursors.h"
60 #include "editor_cursors.h"
62 #include "ardour/types.h"
63 #include "ardour/profile.h"
64 #include "ardour/route.h"
65 #include "ardour/audio_track.h"
66 #include "ardour/audio_diskstream.h"
67 #include "ardour/midi_diskstream.h"
68 #include "ardour/playlist.h"
69 #include "ardour/audioplaylist.h"
70 #include "ardour/audioregion.h"
71 #include "ardour/midi_region.h"
72 #include "ardour/dB.h"
73 #include "ardour/utils.h"
74 #include "ardour/region_factory.h"
75 #include "ardour/source_factory.h"
76 #include "ardour/session.h"
77 #include "ardour/operations.h"
84 using namespace ARDOUR;
87 using namespace Editing;
88 using Gtkmm2ext::Keyboard;
91 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
95 Gdk::ModifierType mask;
96 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
97 Glib::RefPtr<const Gdk::Window> pointer_window;
103 pointer_window = canvas_window->get_pointer (x, y, mask);
105 if (pointer_window == track_canvas->get_bin_window()) {
108 in_track_canvas = true;
111 in_track_canvas = false;
116 event.type = GDK_BUTTON_RELEASE;
120 where = event_frame (&event, 0, 0);
125 Editor::event_frame (GdkEvent const * event, double* pcx, double* pcy) const
139 switch (event->type) {
140 case GDK_BUTTON_RELEASE:
141 case GDK_BUTTON_PRESS:
142 case GDK_2BUTTON_PRESS:
143 case GDK_3BUTTON_PRESS:
144 *pcx = event->button.x;
145 *pcy = event->button.y;
146 _trackview_group->w2i(*pcx, *pcy);
148 case GDK_MOTION_NOTIFY:
149 *pcx = event->motion.x;
150 *pcy = event->motion.y;
151 _trackview_group->w2i(*pcx, *pcy);
153 case GDK_ENTER_NOTIFY:
154 case GDK_LEAVE_NOTIFY:
155 track_canvas->w2c(event->crossing.x, event->crossing.y, *pcx, *pcy);
158 case GDK_KEY_RELEASE:
159 // track_canvas->w2c(event->key.x, event->key.y, *pcx, *pcy);
162 warning << string_compose (_("Editor::event_frame() used on unhandled event type %1"), event->type) << endmsg;
166 /* note that pixel_to_frame() never returns less than zero, so even if the pixel
167 position is negative (as can be the case with motion events in particular),
168 the frame location is always positive.
171 return pixel_to_frame (*pcx);
175 Editor::which_grabber_cursor ()
177 Gdk::Cursor* c = _cursors->grabber;
179 if (_internal_editing) {
180 switch (mouse_mode) {
182 c = _cursors->midi_pencil;
186 c = _cursors->grabber_note;
190 c = _cursors->midi_resize;
199 switch (_edit_point) {
201 c = _cursors->grabber_edit_point;
204 boost::shared_ptr<Movable> m = _movable.lock();
205 if (m && m->locked()) {
206 c = _cursors->speaker;
216 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
218 boost::shared_ptr<Trimmable> st = _trimmable.lock();
220 if (!st || st == t) {
222 set_canvas_cursor ();
227 Editor::set_current_movable (boost::shared_ptr<Movable> m)
229 boost::shared_ptr<Movable> sm = _movable.lock();
231 if (!sm || sm != m) {
233 set_canvas_cursor ();
238 Editor::set_canvas_cursor ()
240 if (_internal_editing) {
242 switch (mouse_mode) {
244 current_canvas_cursor = _cursors->midi_pencil;
248 current_canvas_cursor = which_grabber_cursor();
252 current_canvas_cursor = _cursors->midi_resize;
261 switch (mouse_mode) {
263 current_canvas_cursor = _cursors->selector;
267 current_canvas_cursor = which_grabber_cursor();
271 current_canvas_cursor = _cursors->cross_hair;
275 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
276 current_canvas_cursor = _cursors->zoom_out;
278 current_canvas_cursor = _cursors->zoom_in;
283 current_canvas_cursor = _cursors->time_fx; // just use playhead
287 current_canvas_cursor = _cursors->speaker;
292 switch (_join_object_range_state) {
293 case JOIN_OBJECT_RANGE_NONE:
295 case JOIN_OBJECT_RANGE_OBJECT:
296 current_canvas_cursor = which_grabber_cursor ();
298 case JOIN_OBJECT_RANGE_RANGE:
299 current_canvas_cursor = _cursors->selector;
303 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
304 if (join_object_range_button.get_active() && last_item_entered) {
305 if (last_item_entered->property_parent() && (*last_item_entered->property_parent()).get_data (X_("timeselection"))) {
306 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y + vertical_adjustment.get_value() - canvas_timebars_vsize);
307 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
308 current_canvas_cursor = _cursors->up_down;
313 set_canvas_cursor (current_canvas_cursor, true);
317 Editor::set_mouse_mode (MouseMode m, bool force)
319 if (_drags->active ()) {
323 if (!force && m == mouse_mode) {
327 Glib::RefPtr<Action> act;
331 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
335 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
339 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-gain"));
343 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-zoom"));
347 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
351 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
357 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
360 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
361 tact->set_active (false);
362 tact->set_active (true);
364 MouseModeChanged (); /* EMIT SIGNAL */
368 Editor::mouse_mode_toggled (MouseMode m)
374 if (!internal_editing()) {
375 if (mouse_mode != MouseRange && _join_object_range_state == JOIN_OBJECT_RANGE_NONE) {
377 /* in all modes except range and joined object/range, hide the range selection,
378 show the object (region) selection.
381 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
382 (*i)->hide_selection ();
388 in range or object/range mode, show the range selection.
391 for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
392 (*i)->show_selection (selection->time);
397 set_canvas_cursor ();
399 MouseModeChanged (); /* EMIT SIGNAL */
403 Editor::step_mouse_mode (bool next)
405 switch (current_mouse_mode()) {
408 if (Profile->get_sae()) {
409 set_mouse_mode (MouseZoom);
411 set_mouse_mode (MouseRange);
414 set_mouse_mode (MouseTimeFX);
419 if (next) set_mouse_mode (MouseZoom);
420 else set_mouse_mode (MouseObject);
425 if (Profile->get_sae()) {
426 set_mouse_mode (MouseTimeFX);
428 set_mouse_mode (MouseGain);
431 if (Profile->get_sae()) {
432 set_mouse_mode (MouseObject);
434 set_mouse_mode (MouseRange);
440 if (next) set_mouse_mode (MouseTimeFX);
441 else set_mouse_mode (MouseZoom);
446 set_mouse_mode (MouseAudition);
448 if (Profile->get_sae()) {
449 set_mouse_mode (MouseZoom);
451 set_mouse_mode (MouseGain);
457 if (next) set_mouse_mode (MouseObject);
458 else set_mouse_mode (MouseTimeFX);
464 Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemType item_type)
466 /* in object/audition/timefx/gain-automation mode,
467 any button press sets the selection if the object
468 can be selected. this is a bit of hack, because
469 we want to avoid this if the mouse operation is a
472 note: not dbl-click or triple-click
474 Also note that there is no region selection in internal edit mode, otherwise
475 for operations operating on the selection (e.g. cut) it is not obvious whether
476 to cut notes or regions.
479 if (((mouse_mode != MouseObject) &&
480 (_join_object_range_state != JOIN_OBJECT_RANGE_OBJECT) &&
481 (mouse_mode != MouseAudition || item_type != RegionItem) &&
482 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
483 (mouse_mode != MouseGain) &&
484 (mouse_mode != MouseRange)) ||
485 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3) ||
486 internal_editing()) {
491 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
493 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
495 /* almost no selection action on modified button-2 or button-3 events */
497 if (item_type != RegionItem && event->button.button != 2) {
503 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
504 bool press = (event->type == GDK_BUTTON_PRESS);
506 // begin_reversible_command (_("select on click"));
510 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
511 set_selected_regionview_from_click (press, op, true);
512 } else if (event->type == GDK_BUTTON_PRESS) {
513 selection->clear_tracks ();
514 set_selected_track_as_side_effect (op, true);
516 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
517 clicked_selection = select_range_around_region (selection->regions.front());
521 case RegionViewNameHighlight:
523 case LeftFrameHandle:
524 case RightFrameHandle:
525 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
526 set_selected_regionview_from_click (press, op, true);
527 } else if (event->type == GDK_BUTTON_PRESS) {
528 set_selected_track_as_side_effect (op);
533 case FadeInHandleItem:
535 case FadeOutHandleItem:
537 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
538 set_selected_regionview_from_click (press, op, true);
539 } else if (event->type == GDK_BUTTON_PRESS) {
540 set_selected_track_as_side_effect (op);
544 case ControlPointItem:
545 set_selected_track_as_side_effect (op, true);
546 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
547 set_selected_control_point_from_click (op, false);
552 /* for context click, select track */
553 if (event->button.button == 3) {
554 selection->clear_tracks ();
555 set_selected_track_as_side_effect (op, true);
559 case AutomationTrackItem:
560 set_selected_track_as_side_effect (op, true);
569 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
571 /* single mouse clicks on any of these item types operate
572 independent of mouse mode, mostly because they are
573 not on the main track canvas or because we want
578 case PlayheadCursorItem:
579 _drags->set (new CursorDrag (this, item, true), event);
583 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
584 hide_marker (item, event);
586 _drags->set (new MarkerDrag (this, item), event);
590 case TempoMarkerItem:
592 new TempoMarkerDrag (
595 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
601 case MeterMarkerItem:
603 new MeterMarkerDrag (
606 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
615 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
616 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
622 case RangeMarkerBarItem:
623 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
624 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
626 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
631 case CdMarkerBarItem:
632 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
633 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
635 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
640 case TransportMarkerBarItem:
641 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
642 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
644 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
653 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
654 /* special case: allow trim of range selections in joined object mode;
655 in theory eff should equal MouseRange in this case, but it doesn't
656 because entering the range selection canvas item results in entered_regionview
657 being set to 0, so update_join_object_range_location acts as if we aren't
660 if (item_type == StartSelectionTrimItem) {
661 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
662 } else if (item_type == EndSelectionTrimItem) {
663 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
667 Editing::MouseMode eff = effective_mouse_mode ();
669 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
670 if (item_type == FadeInHandleItem || item_type == FadeOutHandleItem) {
677 case StartSelectionTrimItem:
678 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
681 case EndSelectionTrimItem:
682 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
686 if (Keyboard::modifier_state_contains
687 (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier))) {
688 // contains and not equals because I can't use alt as a modifier alone.
689 start_selection_grab (item, event);
690 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
691 /* grab selection for moving */
692 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
694 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
695 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
697 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
698 if (join_object_range_button.get_active() && atv) {
699 /* smart "join" mode: drag automation */
700 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, _cursors->up_down);
702 /* this was debated, but decided the more common action was to
703 make a new selection */
704 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
711 if (internal_editing()) {
712 /* trim notes if we're in internal edit mode and near the ends of the note */
713 ArdourCanvas::CanvasNote* cn = dynamic_cast<ArdourCanvas::CanvasNote*> (item);
714 cerr << "NoteItem button press, cursor = " << current_canvas_cursor << endl;
715 if (cn->mouse_near_ends()) {
716 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
718 _drags->set (new NoteDrag (this, item), event);
724 if (internal_editing()) {
725 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
726 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
730 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
735 case RegionViewNameHighlight:
736 case LeftFrameHandle:
737 case RightFrameHandle:
738 if (!clicked_regionview->region()->locked()) {
739 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
740 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
746 if (!internal_editing()) {
747 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
756 if (internal_editing()) {
757 ArdourCanvas::CanvasNoteEvent* cn = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
758 if (cn->mouse_near_ends()) {
759 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
761 _drags->set (new NoteDrag (this, item), event);
771 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
772 event->type == GDK_BUTTON_PRESS) {
774 _drags->set (new RubberbandSelectDrag (this, item), event);
776 } else if (event->type == GDK_BUTTON_PRESS) {
779 case FadeInHandleItem:
781 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
782 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, _cursors->fade_in);
786 case FadeOutHandleItem:
788 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
789 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, _cursors->fade_out);
793 case FeatureLineItem:
795 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
796 remove_transient(item);
800 _drags->set (new FeatureLineDrag (this, item), event);
806 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
807 /* click on an automation region view; do nothing here and let the ARV's signal handler
813 if (internal_editing ()) {
814 /* no region drags in internal edit mode */
818 /* click on a normal region view */
819 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
820 add_region_copy_drag (item, event, clicked_regionview);
822 else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
823 add_region_brush_drag (item, event, clicked_regionview);
825 add_region_drag (item, event, clicked_regionview);
828 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
829 _drags->add (new SelectionDrag (this, clicked_axisview->get_selection_rect (clicked_selection)->rect, SelectionDrag::SelectionMove));
832 _drags->start_grab (event);
835 case RegionViewNameHighlight:
836 case LeftFrameHandle:
837 case RightFrameHandle:
838 if (!internal_editing () && !clicked_regionview->region()->locked()) {
839 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
840 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
847 /* rename happens on edit clicks */
848 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
849 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, s.by_layer()), event);
854 case ControlPointItem:
855 _drags->set (new ControlPointDrag (this, item), event);
859 case AutomationLineItem:
860 _drags->set (new LineDrag (this, item), event);
865 if (internal_editing()) {
866 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
867 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
871 _drags->set (new RubberbandSelectDrag (this, item), event);
875 case AutomationTrackItem:
876 /* rubberband drag to select automation points */
877 _drags->set (new RubberbandSelectDrag (this, item), event);
882 if (join_object_range_button.get_active()) {
883 /* we're in "smart" joined mode, and we've clicked on a Selection */
884 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
885 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
887 /* if we're over an automation track, start a drag of its data */
888 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
890 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, _cursors->up_down);
893 /* if we're over a track and a region, and in the `object' part of a region,
894 put a selection around the region and drag both
896 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
897 if (rtv && _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
898 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (rtv->route ());
900 boost::shared_ptr<Playlist> pl = t->playlist ();
903 boost::shared_ptr<Region> r = pl->top_region_at (event_frame (event));
905 RegionView* rv = rtv->view()->find_view (r);
906 clicked_selection = select_range_around_region (rv);
907 _drags->add (new SelectionDrag (this, item, SelectionDrag::SelectionMove));
908 list<RegionView*> rvs;
910 _drags->add (new RegionMoveDrag (this, item, rv, rvs, false, false));
911 _drags->start_grab (event);
922 case ImageFrameHandleStartItem:
923 imageframe_start_handle_op(item, event) ;
926 case ImageFrameHandleEndItem:
927 imageframe_end_handle_op(item, event) ;
930 case MarkerViewHandleStartItem:
931 markerview_item_start_handle_op(item, event) ;
934 case MarkerViewHandleEndItem:
935 markerview_item_end_handle_op(item, event) ;
939 start_markerview_grab(item, event) ;
942 start_imageframe_grab(item, event) ;
960 /* start a grab so that if we finish after moving
961 we can tell what happened.
963 _drags->set (new RegionGainDrag (this, item), event, current_canvas_cursor);
967 _drags->set (new LineDrag (this, item), event);
970 case ControlPointItem:
971 _drags->set (new ControlPointDrag (this, item), event);
982 case ControlPointItem:
983 _drags->set (new ControlPointDrag (this, item), event);
986 case AutomationLineItem:
987 _drags->set (new LineDrag (this, item), event);
991 // XXX need automation mode to identify which
993 // start_line_grab_from_regionview (item, event);
1003 if (event->type == GDK_BUTTON_PRESS) {
1004 _drags->set (new MouseZoomDrag (this, item), event);
1011 if (internal_editing() && item_type == NoteItem) {
1012 /* drag notes if we're in internal edit mode */
1013 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
1015 } else if ((!internal_editing() || dynamic_cast<AudioRegionView*> (clicked_regionview)) && clicked_regionview) {
1016 /* do time-FX if we're not in internal edit mode, or we are but we clicked on an audio region */
1017 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1023 _drags->set (new ScrubDrag (this, item), event);
1024 scrub_reversals = 0;
1025 scrub_reverse_distance = 0;
1026 last_scrub_x = event->button.x;
1027 scrubbing_direction = 0;
1028 set_canvas_cursor (_cursors->transparent);
1040 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1042 Editing::MouseMode const eff = effective_mouse_mode ();
1045 switch (item_type) {
1047 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1048 add_region_copy_drag (item, event, clicked_regionview);
1050 add_region_drag (item, event, clicked_regionview);
1052 _drags->start_grab (event);
1055 case ControlPointItem:
1056 _drags->set (new ControlPointDrag (this, item), event);
1064 switch (item_type) {
1065 case RegionViewNameHighlight:
1066 case LeftFrameHandle:
1067 case RightFrameHandle:
1068 if (!internal_editing ()) {
1069 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1074 case RegionViewName:
1075 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1086 /* relax till release */
1092 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1093 temporal_zoom_session();
1095 temporal_zoom_to_frame (true, event_frame(event));
1108 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1110 if (event->type != GDK_BUTTON_PRESS) {
1114 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
1116 if (canvas_window) {
1117 Glib::RefPtr<const Gdk::Window> pointer_window;
1120 Gdk::ModifierType mask;
1122 pointer_window = canvas_window->get_pointer (x, y, mask);
1124 if (pointer_window == track_canvas->get_bin_window()) {
1125 track_canvas->window_to_world (x, y, wx, wy);
1129 pre_press_cursor = current_canvas_cursor;
1131 track_canvas->grab_focus();
1133 if (_session && _session->actively_recording()) {
1137 button_selection (item, event, item_type);
1139 if (!_drags->active () &&
1140 (Keyboard::is_delete_event (&event->button) ||
1141 Keyboard::is_context_menu_event (&event->button) ||
1142 Keyboard::is_edit_event (&event->button))) {
1144 /* handled by button release */
1148 switch (event->button.button) {
1150 return button_press_handler_1 (item, event, item_type);
1154 return button_press_handler_2 (item, event, item_type);
1169 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1171 framepos_t where = event_frame (event, 0, 0);
1172 AutomationTimeAxisView* atv = 0;
1174 if (pre_press_cursor) {
1175 set_canvas_cursor (pre_press_cursor);
1176 pre_press_cursor = 0;
1179 /* no action if we're recording */
1181 if (_session && _session->actively_recording()) {
1185 /* see if we're finishing a drag */
1187 bool were_dragging = false;
1188 if (_drags->active ()) {
1189 bool const r = _drags->end_grab (event);
1191 /* grab dragged, so do nothing else */
1195 were_dragging = true;
1198 update_region_layering_order_editor ();
1200 /* edit events get handled here */
1202 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1203 switch (item_type) {
1205 show_region_properties ();
1208 case TempoMarkerItem:
1209 edit_tempo_marker (item);
1212 case MeterMarkerItem:
1213 edit_meter_marker (item);
1216 case RegionViewName:
1217 if (clicked_regionview->name_active()) {
1218 return mouse_rename_region (item, event);
1222 case ControlPointItem:
1223 edit_control_point (item);
1236 /* context menu events get handled here */
1238 if (Keyboard::is_context_menu_event (&event->button)) {
1240 if (!_drags->active ()) {
1242 /* no matter which button pops up the context menu, tell the menu
1243 widget to use button 1 to drive menu selection.
1246 switch (item_type) {
1248 case FadeInHandleItem:
1250 case FadeOutHandleItem:
1251 popup_fade_context_menu (1, event->button.time, item, item_type);
1255 popup_track_context_menu (1, event->button.time, item_type, false);
1259 case RegionViewNameHighlight:
1260 case LeftFrameHandle:
1261 case RightFrameHandle:
1262 case RegionViewName:
1263 popup_track_context_menu (1, event->button.time, item_type, false);
1267 popup_track_context_menu (1, event->button.time, item_type, true);
1270 case AutomationTrackItem:
1271 popup_track_context_menu (1, event->button.time, item_type, false);
1275 case RangeMarkerBarItem:
1276 case TransportMarkerBarItem:
1277 case CdMarkerBarItem:
1280 popup_ruler_menu (where, item_type);
1284 marker_context_menu (&event->button, item);
1287 case TempoMarkerItem:
1288 tempo_or_meter_marker_context_menu (&event->button, item);
1291 case MeterMarkerItem:
1292 tempo_or_meter_marker_context_menu (&event->button, item);
1295 case CrossfadeViewItem:
1296 popup_track_context_menu (1, event->button.time, item_type, false);
1300 case ImageFrameItem:
1301 popup_imageframe_edit_menu(1, event->button.time, item, true) ;
1303 case ImageFrameTimeAxisItem:
1304 popup_imageframe_edit_menu(1, event->button.time, item, false) ;
1306 case MarkerViewItem:
1307 popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ;
1309 case MarkerTimeAxisItem:
1310 popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
1322 /* delete events get handled here */
1324 Editing::MouseMode const eff = effective_mouse_mode ();
1326 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1328 switch (item_type) {
1329 case TempoMarkerItem:
1330 remove_tempo_marker (item);
1333 case MeterMarkerItem:
1334 remove_meter_marker (item);
1338 remove_marker (*item, event);
1342 if (eff == MouseObject) {
1343 remove_clicked_region ();
1347 case ControlPointItem:
1348 if (eff == MouseGain) {
1349 remove_gain_control_point (item, event);
1351 remove_control_point (item, event);
1356 remove_midi_note (item, event);
1365 switch (event->button.button) {
1368 switch (item_type) {
1369 /* see comments in button_press_handler */
1370 case PlayheadCursorItem:
1373 case AutomationLineItem:
1374 case StartSelectionTrimItem:
1375 case EndSelectionTrimItem:
1379 if (!_dragging_playhead) {
1380 snap_to_with_modifier (where, event, 0, true);
1381 mouse_add_new_marker (where);
1385 case CdMarkerBarItem:
1386 if (!_dragging_playhead) {
1387 // if we get here then a dragged range wasn't done
1388 snap_to_with_modifier (where, event, 0, true);
1389 mouse_add_new_marker (where, true);
1394 if (!_dragging_playhead) {
1395 snap_to_with_modifier (where, event);
1396 mouse_add_new_tempo_event (where);
1401 if (!_dragging_playhead) {
1402 mouse_add_new_meter_event (pixel_to_frame (event->button.x));
1413 switch (item_type) {
1414 case AutomationTrackItem:
1415 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1417 atv->add_automation_event (item, event, where, event->button.y);
1428 switch (item_type) {
1431 /* check that we didn't drag before releasing, since
1432 its really annoying to create new control
1433 points when doing this.
1435 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1436 if (were_dragging && arv) {
1437 arv->add_gain_point_event (item, event);
1443 case AutomationTrackItem:
1444 dynamic_cast<AutomationTimeAxisView*>(clicked_axisview)->
1445 add_automation_event (item, event, where, event->button.y);
1454 set_canvas_cursor (current_canvas_cursor);
1455 if (scrubbing_direction == 0) {
1456 /* no drag, just a click */
1457 switch (item_type) {
1459 play_selected_region ();
1465 /* make sure we stop */
1466 _session->request_transport_speed (0.0);
1483 switch (item_type) {
1485 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1487 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1490 // Button2 click is unused
1503 // x_style_paste (where, 1.0);
1523 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1530 last_item_entered = item;
1532 switch (item_type) {
1533 case ControlPointItem:
1534 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1535 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1536 cp->set_visible (true);
1540 at_y = cp->get_y ();
1541 cp->i2w (at_x, at_y);
1545 fraction = 1.0 - (cp->get_y() / cp->line().height());
1547 if (is_drawable() && !_drags->active ()) {
1548 set_canvas_cursor (_cursors->fader);
1551 set_verbose_canvas_cursor (cp->line().get_verbose_cursor_string (fraction), at_x, at_y);
1552 show_verbose_canvas_cursor ();
1557 if (mouse_mode == MouseGain) {
1558 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1560 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
1561 if (is_drawable()) {
1562 set_canvas_cursor (_cursors->fader);
1567 case AutomationLineItem:
1568 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1570 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1572 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
1574 if (is_drawable()) {
1575 set_canvas_cursor (_cursors->fader);
1580 case RegionViewNameHighlight:
1581 if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
1582 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1583 _over_region_trim_target = true;
1587 case LeftFrameHandle:
1588 case RightFrameHandle:
1589 if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
1590 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1594 case StartSelectionTrimItem:
1595 case EndSelectionTrimItem:
1598 case ImageFrameHandleStartItem:
1599 case ImageFrameHandleEndItem:
1600 case MarkerViewHandleStartItem:
1601 case MarkerViewHandleEndItem:
1604 if (is_drawable()) {
1605 set_canvas_cursor (_cursors->trimmer);
1609 case PlayheadCursorItem:
1610 if (is_drawable()) {
1611 switch (_edit_point) {
1613 set_canvas_cursor (_cursors->grabber_edit_point);
1616 set_canvas_cursor (_cursors->grabber);
1622 case RegionViewName:
1624 /* when the name is not an active item, the entire name highlight is for trimming */
1626 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1627 if (mouse_mode == MouseObject && is_drawable()) {
1628 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1629 _over_region_trim_target = true;
1635 case AutomationTrackItem:
1636 if (is_drawable()) {
1637 Gdk::Cursor *cursor;
1638 switch (mouse_mode) {
1640 cursor = _cursors->selector;
1643 cursor = _cursors->zoom_in;
1646 cursor = _cursors->cross_hair;
1650 set_canvas_cursor (cursor);
1652 AutomationTimeAxisView* atv;
1653 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1654 clear_entered_track = false;
1655 set_entered_track (atv);
1661 case RangeMarkerBarItem:
1662 case TransportMarkerBarItem:
1663 case CdMarkerBarItem:
1666 if (is_drawable()) {
1667 set_canvas_cursor (_cursors->timebar);
1672 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1675 entered_marker = marker;
1676 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1678 case MeterMarkerItem:
1679 case TempoMarkerItem:
1680 if (is_drawable()) {
1681 set_canvas_cursor (_cursors->timebar);
1685 case FadeInHandleItem:
1686 if (mouse_mode == MouseObject && !internal_editing()) {
1687 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1689 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1691 set_canvas_cursor (_cursors->fade_in);
1695 case FadeOutHandleItem:
1696 if (mouse_mode == MouseObject && !internal_editing()) {
1697 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1699 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1701 set_canvas_cursor (_cursors->fade_out);
1704 case FeatureLineItem:
1706 ArdourCanvas::SimpleLine *line = dynamic_cast<ArdourCanvas::SimpleLine *> (item);
1707 line->property_color_rgba() = 0xFF0000FF;
1711 if (join_object_range_button.get_active()) {
1712 set_canvas_cursor ();
1720 /* second pass to handle entered track status in a comprehensible way.
1723 switch (item_type) {
1725 case AutomationLineItem:
1726 case ControlPointItem:
1727 /* these do not affect the current entered track state */
1728 clear_entered_track = false;
1731 case AutomationTrackItem:
1732 /* handled above already */
1736 set_entered_track (0);
1744 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1754 switch (item_type) {
1755 case ControlPointItem:
1756 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1757 if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
1758 if (cp->line().npoints() > 1 && !cp->get_selected()) {
1759 cp->set_visible (false);
1763 if (is_drawable()) {
1764 set_canvas_cursor (current_canvas_cursor);
1767 hide_verbose_canvas_cursor ();
1770 case RegionViewNameHighlight:
1771 case LeftFrameHandle:
1772 case RightFrameHandle:
1773 case StartSelectionTrimItem:
1774 case EndSelectionTrimItem:
1775 case PlayheadCursorItem:
1778 case ImageFrameHandleStartItem:
1779 case ImageFrameHandleEndItem:
1780 case MarkerViewHandleStartItem:
1781 case MarkerViewHandleEndItem:
1784 _over_region_trim_target = false;
1786 if (is_drawable()) {
1787 set_canvas_cursor (current_canvas_cursor);
1792 case AutomationLineItem:
1793 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1795 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1797 line->property_fill_color_rgba() = al->get_line_color();
1799 if (is_drawable()) {
1800 set_canvas_cursor (current_canvas_cursor);
1804 case RegionViewName:
1805 /* see enter_handler() for notes */
1806 _over_region_trim_target = false;
1808 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1809 if (is_drawable() && mouse_mode == MouseObject) {
1810 set_canvas_cursor (current_canvas_cursor);
1815 case RangeMarkerBarItem:
1816 case TransportMarkerBarItem:
1817 case CdMarkerBarItem:
1821 if (is_drawable()) {
1822 set_canvas_cursor (current_canvas_cursor);
1827 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1831 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1832 location_flags_changed (loc, this);
1835 case MeterMarkerItem:
1836 case TempoMarkerItem:
1838 if (is_drawable()) {
1839 set_canvas_cursor (_cursors->timebar);
1844 case FadeInHandleItem:
1845 case FadeOutHandleItem:
1846 rv = static_cast<RegionView*>(item->get_data ("regionview"));
1848 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1850 rect->property_fill_color_rgba() = rv->get_fill_color();
1851 rect->property_outline_pixels() = 0;
1854 set_canvas_cursor (current_canvas_cursor);
1857 case AutomationTrackItem:
1858 if (is_drawable()) {
1859 set_canvas_cursor (current_canvas_cursor);
1860 clear_entered_track = true;
1861 Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track));
1864 case FeatureLineItem:
1866 ArdourCanvas::SimpleLine *line = dynamic_cast<ArdourCanvas::SimpleLine *> (item);
1867 line->property_color_rgba() = (guint) ARDOUR_UI::config()->canvasvar_ZeroLine.get();;
1879 Editor::left_automation_track ()
1881 if (clear_entered_track) {
1882 set_entered_track (0);
1883 clear_entered_track = false;
1889 Editor::scrub (framepos_t frame, double current_x)
1893 if (scrubbing_direction == 0) {
1895 _session->request_locate (frame, false);
1896 _session->request_transport_speed (0.1);
1897 scrubbing_direction = 1;
1901 if (last_scrub_x > current_x) {
1903 /* pointer moved to the left */
1905 if (scrubbing_direction > 0) {
1907 /* we reversed direction to go backwards */
1910 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1914 /* still moving to the left (backwards) */
1916 scrub_reversals = 0;
1917 scrub_reverse_distance = 0;
1919 delta = 0.01 * (last_scrub_x - current_x);
1920 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1924 /* pointer moved to the right */
1926 if (scrubbing_direction < 0) {
1927 /* we reversed direction to go forward */
1930 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1933 /* still moving to the right */
1935 scrub_reversals = 0;
1936 scrub_reverse_distance = 0;
1938 delta = 0.01 * (current_x - last_scrub_x);
1939 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1943 /* if there have been more than 2 opposite motion moves detected, or one that moves
1944 back more than 10 pixels, reverse direction
1947 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1949 if (scrubbing_direction > 0) {
1950 /* was forwards, go backwards */
1951 _session->request_transport_speed (-0.1);
1952 scrubbing_direction = -1;
1954 /* was backwards, go forwards */
1955 _session->request_transport_speed (0.1);
1956 scrubbing_direction = 1;
1959 scrub_reverse_distance = 0;
1960 scrub_reversals = 0;
1964 last_scrub_x = current_x;
1968 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1970 _last_motion_y = event->motion.y;
1972 if (event->motion.is_hint) {
1975 /* We call this so that MOTION_NOTIFY events continue to be
1976 delivered to the canvas. We need to do this because we set
1977 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1978 the density of the events, at the expense of a round-trip
1979 to the server. Given that this will mostly occur on cases
1980 where DISPLAY = :0.0, and given the cost of what the motion
1981 event might do, its a good tradeoff.
1984 track_canvas->get_pointer (x, y);
1987 if (current_stepping_trackview) {
1988 /* don't keep the persistent stepped trackview if the mouse moves */
1989 current_stepping_trackview = 0;
1990 step_timeout.disconnect ();
1993 if (_session && _session->actively_recording()) {
1994 /* Sorry. no dragging stuff around while we record */
1998 JoinObjectRangeState const old = _join_object_range_state;
1999 update_join_object_range_location (event->motion.x, event->motion.y);
2000 if (_join_object_range_state != old) {
2001 set_canvas_cursor ();
2004 if (_over_region_trim_target) {
2005 set_canvas_cursor_for_region_view (event->motion.x, entered_regionview);
2008 bool handled = false;
2009 if (_drags->active ()) {
2010 handled = _drags->motion_handler (event, from_autoscroll);
2017 track_canvas_motion (event);
2022 Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* /*event*/)
2024 ControlPoint* control_point;
2026 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2027 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2031 // We shouldn't remove the first or last gain point
2032 if (control_point->line().is_last_point(*control_point) ||
2033 control_point->line().is_first_point(*control_point)) {
2037 control_point->line().remove_point (*control_point);
2041 Editor::remove_control_point (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2043 ControlPoint* control_point;
2045 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2046 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2050 control_point->line().remove_point (*control_point);
2054 Editor::edit_control_point (ArdourCanvas::Item* item)
2056 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2059 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2063 ControlPointDialog d (p);
2064 d.set_position (Gtk::WIN_POS_MOUSE);
2067 if (d.run () != RESPONSE_ACCEPT) {
2071 p->line().modify_point_y (*p, d.get_y_fraction ());
2075 Editor::edit_note (ArdourCanvas::Item* item)
2077 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2080 EditNoteDialog d (&e->region_view(), e);
2081 d.set_position (Gtk::WIN_POS_MOUSE);
2089 Editor::visible_order_range (int* low, int* high) const
2091 *low = TimeAxisView::max_order ();
2094 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2096 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2098 if (!rtv->hidden()) {
2100 if (*high < rtv->order()) {
2101 *high = rtv->order ();
2104 if (*low > rtv->order()) {
2105 *low = rtv->order ();
2112 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2114 /* Either add to or set the set the region selection, unless
2115 this is an alignment click (control used)
2118 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2119 TimeAxisView* tv = &rv.get_time_axis_view();
2120 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2122 if (rtv && rtv->is_track()) {
2123 speed = rtv->track()->speed();
2126 framepos_t where = get_preferred_edit_position();
2130 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2132 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2134 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2136 align_region (rv.region(), End, (framepos_t) (where * speed));
2140 align_region (rv.region(), Start, (framepos_t) (where * speed));
2147 Editor::show_verbose_time_cursor (framepos_t frame, double offset, double xpos, double ypos)
2150 Timecode::Time timecode;
2151 Timecode::BBT_Time bbt;
2153 framepos_t frame_rate;
2156 if (_session == 0) {
2162 if (Profile->get_sae() || Profile->get_small_screen()) {
2163 m = ARDOUR_UI::instance()->primary_clock.mode();
2165 m = ARDOUR_UI::instance()->secondary_clock.mode();
2169 case AudioClock::BBT:
2170 _session->bbt_time (frame, bbt);
2171 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, bbt.bars, bbt.beats, bbt.ticks);
2174 case AudioClock::Timecode:
2175 _session->timecode_time (frame, timecode);
2176 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2179 case AudioClock::MinSec:
2180 /* XXX this is copied from show_verbose_duration_cursor() */
2181 frame_rate = _session->frame_rate();
2182 hours = frame / (frame_rate * 3600);
2183 frame = frame % (frame_rate * 3600);
2184 mins = frame / (frame_rate * 60);
2185 frame = frame % (frame_rate * 60);
2186 secs = (float) frame / (float) frame_rate;
2187 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
2191 snprintf (buf, sizeof(buf), "%" PRIi64, frame);
2195 if (xpos >= 0 && ypos >=0) {
2196 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2198 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset - horizontal_position(), _drags->current_pointer_y() + offset - vertical_adjustment.get_value() + canvas_timebars_vsize);
2200 show_verbose_canvas_cursor ();
2204 Editor::show_verbose_duration_cursor (framepos_t start, framepos_t end, double offset, double xpos, double ypos)
2207 Timecode::Time timecode;
2208 Timecode::BBT_Time sbbt;
2209 Timecode::BBT_Time ebbt;
2211 framepos_t distance, frame_rate;
2213 Meter meter_at_start(_session->tempo_map().meter_at(start));
2215 if (_session == 0) {
2221 if (Profile->get_sae() || Profile->get_small_screen()) {
2222 m = ARDOUR_UI::instance()->primary_clock.mode ();
2224 m = ARDOUR_UI::instance()->secondary_clock.mode ();
2228 case AudioClock::BBT:
2229 _session->bbt_time (start, sbbt);
2230 _session->bbt_time (end, ebbt);
2233 /* XXX this computation won't work well if the
2234 user makes a selection that spans any meter changes.
2237 ebbt.bars -= sbbt.bars;
2238 if (ebbt.beats >= sbbt.beats) {
2239 ebbt.beats -= sbbt.beats;
2242 ebbt.beats = int(meter_at_start.beats_per_bar()) + ebbt.beats - sbbt.beats;
2244 if (ebbt.ticks >= sbbt.ticks) {
2245 ebbt.ticks -= sbbt.ticks;
2248 ebbt.ticks = int(Timecode::BBT_Time::ticks_per_beat) + ebbt.ticks - sbbt.ticks;
2251 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, ebbt.bars, ebbt.beats, ebbt.ticks);
2254 case AudioClock::Timecode:
2255 _session->timecode_duration (end - start, timecode);
2256 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2259 case AudioClock::MinSec:
2260 /* XXX this stuff should be elsewhere.. */
2261 distance = end - start;
2262 frame_rate = _session->frame_rate();
2263 hours = distance / (frame_rate * 3600);
2264 distance = distance % (frame_rate * 3600);
2265 mins = distance / (frame_rate * 60);
2266 distance = distance % (frame_rate * 60);
2267 secs = (float) distance / (float) frame_rate;
2268 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
2272 snprintf (buf, sizeof(buf), "%" PRIi64, end - start);
2276 if (xpos >= 0 && ypos >=0) {
2277 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2280 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset, _drags->current_pointer_y() + offset);
2283 show_verbose_canvas_cursor ();
2287 Editor::collect_new_region_view (RegionView* rv)
2289 latest_regionviews.push_back (rv);
2293 Editor::collect_and_select_new_region_view (RegionView* rv)
2296 latest_regionviews.push_back (rv);
2300 Editor::cancel_selection ()
2302 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2303 (*i)->hide_selection ();
2306 selection->clear ();
2307 clicked_selection = 0;
2312 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2314 RegionView* rv = clicked_regionview;
2316 /* Choose action dependant on which button was pressed */
2317 switch (event->button.button) {
2319 begin_reversible_command (_("start point trim"));
2321 if (selection->selected (rv)) {
2322 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2323 i != selection->regions.by_layer().end(); ++i)
2326 cerr << "region view contains null region" << endl;
2329 if (!(*i)->region()->locked()) {
2330 (*i)->region()->clear_changes ();
2331 (*i)->region()->trim_front (new_bound, this);
2332 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2337 if (!rv->region()->locked()) {
2338 rv->region()->clear_changes ();
2339 rv->region()->trim_front (new_bound, this);
2340 _session->add_command(new StatefulDiffCommand (rv->region()));
2344 commit_reversible_command();
2348 begin_reversible_command (_("End point trim"));
2350 if (selection->selected (rv)) {
2352 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2354 if (!(*i)->region()->locked()) {
2355 (*i)->region()->clear_changes();
2356 (*i)->region()->trim_end (new_bound, this);
2357 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2363 if (!rv->region()->locked()) {
2364 rv->region()->clear_changes ();
2365 rv->region()->trim_end (new_bound, this);
2366 _session->add_command (new StatefulDiffCommand (rv->region()));
2370 commit_reversible_command();
2379 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2384 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2385 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2389 Location* location = find_location_from_marker (marker, is_start);
2390 location->set_hidden (true, this);
2395 Editor::reposition_zoom_rect (framepos_t start, framepos_t end)
2397 double x1 = frame_to_pixel (start);
2398 double x2 = frame_to_pixel (end);
2399 double y2 = full_canvas_height - 1.0;
2401 zoom_rect->property_x1() = x1;
2402 zoom_rect->property_y1() = 1.0;
2403 zoom_rect->property_x2() = x2;
2404 zoom_rect->property_y2() = y2;
2409 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2411 using namespace Gtkmm2ext;
2413 ArdourPrompter prompter (false);
2415 prompter.set_prompt (_("Name for region:"));
2416 prompter.set_initial_text (clicked_regionview->region()->name());
2417 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2418 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2419 prompter.show_all ();
2420 switch (prompter.run ()) {
2421 case Gtk::RESPONSE_ACCEPT:
2423 prompter.get_result(str);
2425 clicked_regionview->region()->set_name (str);
2434 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2436 /* no brushing without a useful snap setting */
2438 switch (_snap_mode) {
2440 return; /* can't work because it allows region to be placed anywhere */
2445 switch (_snap_type) {
2453 /* don't brush a copy over the original */
2455 if (pos == rv->region()->position()) {
2459 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2461 if (rtv == 0 || !rtv->is_track()) {
2465 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2466 double speed = rtv->track()->speed();
2468 playlist->clear_changes ();
2469 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region()));
2470 playlist->add_region (new_region, (framepos_t) (pos * speed));
2471 _session->add_command (new StatefulDiffCommand (playlist));
2473 // playlist is frozen, so we have to update manually XXX this is disgusting
2475 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2479 Editor::track_height_step_timeout ()
2481 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2482 current_stepping_trackview = 0;
2489 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2491 assert (region_view);
2493 if (!region_view->region()->playlist()) {
2497 _region_motion_group->raise_to_top ();
2499 if (Config->get_edit_mode() == Splice) {
2500 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2502 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2503 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false));
2506 /* sync the canvas to what we think is its current state */
2507 update_canvas_now();
2511 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2513 assert (region_view);
2515 if (!region_view->region()->playlist()) {
2519 _region_motion_group->raise_to_top ();
2521 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2522 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true));
2526 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2528 assert (region_view);
2530 if (!region_view->region()->playlist()) {
2534 if (Config->get_edit_mode() == Splice) {
2538 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2539 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), true, false));
2541 begin_reversible_command (Operations::drag_region_brush);
2544 /** Start a grab where a time range is selected, track(s) are selected, and the
2545 * user clicks and drags a region with a modifier in order to create a new region containing
2546 * the section of the clicked region that lies within the time range.
2549 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2551 if (clicked_regionview == 0) {
2555 /* lets try to create new Region for the selection */
2557 vector<boost::shared_ptr<Region> > new_regions;
2558 create_region_from_selection (new_regions);
2560 if (new_regions.empty()) {
2564 /* XXX fix me one day to use all new regions */
2566 boost::shared_ptr<Region> region (new_regions.front());
2568 /* add it to the current stream/playlist.
2570 tricky: the streamview for the track will add a new regionview. we will
2571 catch the signal it sends when it creates the regionview to
2572 set the regionview we want to then drag.
2575 latest_regionviews.clear();
2576 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2578 /* A selection grab currently creates two undo/redo operations, one for
2579 creating the new region and another for moving it.
2582 begin_reversible_command (Operations::selection_grab);
2584 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2586 playlist->clear_changes ();
2587 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2588 _session->add_command(new StatefulDiffCommand (playlist));
2590 commit_reversible_command ();
2594 if (latest_regionviews.empty()) {
2595 /* something went wrong */
2599 /* we need to deselect all other regionviews, and select this one
2600 i'm ignoring undo stuff, because the region creation will take care of it
2602 selection->set (latest_regionviews);
2604 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2610 if (_drags->active ()) {
2613 selection->clear ();
2618 Editor::set_internal_edit (bool yn)
2620 _internal_editing = yn;
2623 mouse_select_button.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
2624 mouse_select_button.get_image ()->show ();
2625 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Draw/Edit MIDI Notes"));
2626 mouse_mode_toggled (mouse_mode);
2630 mouse_select_button.set_image (*(manage (new Image (::get_icon("tool_range")))));
2631 mouse_select_button.get_image ()->show ();
2632 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Select/Move Ranges"));
2633 mouse_mode_toggled (mouse_mode); // sets cursor
2637 /** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
2638 * used by the `join object/range' tool mode.
2641 Editor::update_join_object_range_location (double x, double y)
2643 /* XXX: actually, this decides based on whether the mouse is in the top or bottom half of a RouteTimeAxisView;
2644 entered_{track,regionview} is not always setup (e.g. if the mouse is over a TimeSelection), and to get a Region
2645 that we're over requires searching the playlist.
2648 if (join_object_range_button.get_active() == false || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
2649 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2653 if (mouse_mode == MouseObject) {
2654 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2655 } else if (mouse_mode == MouseRange) {
2656 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2659 /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
2660 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y + vertical_adjustment.get_value() - canvas_timebars_vsize);
2664 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2669 rtv->canvas_display()->w2i (cx, cy);
2671 double const c = cy / rtv->view()->child_height();
2673 double const f = modf (c, &d);
2675 _join_object_range_state = f < 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2681 Editor::effective_mouse_mode () const
2683 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2685 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2693 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2695 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2698 e->region_view().delete_note (e->note ());
2702 Editor::set_canvas_cursor_for_region_view (double x, RegionView* rv)
2704 ArdourCanvas::Group* g = rv->get_canvas_group ();
2705 ArdourCanvas::Group* p = g->get_parent_group ();
2707 /* Compute x in region view parent coordinates */
2711 double x1, x2, y1, y2;
2712 g->get_bounds (x1, y1, x2, y2);
2714 /* Halfway across the region */
2715 double const h = (x1 + x2) / 2;
2717 Trimmable::CanTrim ct = rv->region()->can_trim ();
2719 if (ct & Trimmable::FrontTrimEarlier) {
2720 set_canvas_cursor (_cursors->left_side_trim);
2722 set_canvas_cursor (_cursors->left_side_trim_right_only);
2725 if (ct & Trimmable::EndTrimLater) {
2726 set_canvas_cursor (_cursors->right_side_trim);
2728 set_canvas_cursor (_cursors->right_side_trim_left_only);