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"
83 using namespace ARDOUR;
86 using namespace Editing;
87 using Gtkmm2ext::Keyboard;
90 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
94 Gdk::ModifierType mask;
95 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
96 Glib::RefPtr<const Gdk::Window> pointer_window;
102 pointer_window = canvas_window->get_pointer (x, y, mask);
104 if (pointer_window == track_canvas->get_bin_window()) {
107 in_track_canvas = true;
110 in_track_canvas = false;
115 event.type = GDK_BUTTON_RELEASE;
119 where = event_frame (&event, 0, 0);
124 Editor::event_frame (GdkEvent const * event, double* pcx, double* pcy) const
138 switch (event->type) {
139 case GDK_BUTTON_RELEASE:
140 case GDK_BUTTON_PRESS:
141 case GDK_2BUTTON_PRESS:
142 case GDK_3BUTTON_PRESS:
143 *pcx = event->button.x;
144 *pcy = event->button.y;
145 _trackview_group->w2i(*pcx, *pcy);
147 case GDK_MOTION_NOTIFY:
148 *pcx = event->motion.x;
149 *pcy = event->motion.y;
150 _trackview_group->w2i(*pcx, *pcy);
152 case GDK_ENTER_NOTIFY:
153 case GDK_LEAVE_NOTIFY:
154 track_canvas->w2c(event->crossing.x, event->crossing.y, *pcx, *pcy);
157 case GDK_KEY_RELEASE:
158 // track_canvas->w2c(event->key.x, event->key.y, *pcx, *pcy);
161 warning << string_compose (_("Editor::event_frame() used on unhandled event type %1"), event->type) << endmsg;
165 /* note that pixel_to_frame() never returns less than zero, so even if the pixel
166 position is negative (as can be the case with motion events in particular),
167 the frame location is always positive.
170 return pixel_to_frame (*pcx);
174 Editor::which_grabber_cursor ()
176 Gdk::Cursor* c = _cursors->grabber;
178 if (_internal_editing) {
179 switch (mouse_mode) {
181 c = _cursors->midi_pencil;
185 c = _cursors->grabber_note;
189 c = _cursors->midi_resize;
198 switch (_edit_point) {
200 c = _cursors->grabber_edit_point;
203 boost::shared_ptr<Movable> m = _movable.lock();
204 if (m && m->locked()) {
205 c = _cursors->speaker;
215 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
217 boost::shared_ptr<Trimmable> st = _trimmable.lock();
219 if (!st || st == t) {
221 set_canvas_cursor ();
226 Editor::set_current_movable (boost::shared_ptr<Movable> m)
228 boost::shared_ptr<Movable> sm = _movable.lock();
230 if (!sm || sm != m) {
232 set_canvas_cursor ();
237 Editor::set_canvas_cursor ()
239 if (_internal_editing) {
241 switch (mouse_mode) {
243 current_canvas_cursor = _cursors->midi_pencil;
247 current_canvas_cursor = which_grabber_cursor();
251 current_canvas_cursor = _cursors->midi_resize;
260 switch (mouse_mode) {
262 current_canvas_cursor = _cursors->selector;
266 current_canvas_cursor = which_grabber_cursor();
270 current_canvas_cursor = _cursors->cross_hair;
274 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
275 current_canvas_cursor = _cursors->zoom_out;
277 current_canvas_cursor = _cursors->zoom_in;
282 current_canvas_cursor = _cursors->time_fx; // just use playhead
286 current_canvas_cursor = _cursors->speaker;
291 switch (_join_object_range_state) {
292 case JOIN_OBJECT_RANGE_NONE:
294 case JOIN_OBJECT_RANGE_OBJECT:
295 current_canvas_cursor = which_grabber_cursor ();
297 case JOIN_OBJECT_RANGE_RANGE:
298 current_canvas_cursor = _cursors->selector;
302 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
303 if (join_object_range_button.get_active() && last_item_entered) {
304 if (last_item_entered->property_parent() && (*last_item_entered->property_parent()).get_data (X_("timeselection"))) {
305 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y + vertical_adjustment.get_value() - canvas_timebars_vsize);
306 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
307 current_canvas_cursor = _cursors->up_down;
312 set_canvas_cursor (current_canvas_cursor, true);
316 Editor::set_mouse_mode (MouseMode m, bool force)
318 if (_drags->active ()) {
322 if (!force && m == mouse_mode) {
326 Glib::RefPtr<Action> act;
330 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
334 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
338 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-gain"));
342 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-zoom"));
346 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
350 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
356 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
359 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
360 tact->set_active (false);
361 tact->set_active (true);
363 MouseModeChanged (); /* EMIT SIGNAL */
367 Editor::mouse_mode_toggled (MouseMode m)
373 if (!internal_editing()) {
374 if (mouse_mode != MouseRange && _join_object_range_state == JOIN_OBJECT_RANGE_NONE) {
376 /* in all modes except range and joined object/range, hide the range selection,
377 show the object (region) selection.
380 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
381 (*i)->set_should_show_selection (true);
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 cerr << "NoteItem button press, cursor = " << current_canvas_cursor << endl;
717 if (cn->mouse_near_ends()) {
718 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
720 _drags->set (new NoteDrag (this, item), event);
726 if (internal_editing()) {
727 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
728 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
732 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
737 case RegionViewNameHighlight:
738 case LeftFrameHandle:
739 case RightFrameHandle:
740 if (!clicked_regionview->region()->locked()) {
741 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
742 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
748 if (!internal_editing()) {
749 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
758 if (internal_editing()) {
759 ArdourCanvas::CanvasNoteEvent* cn = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
760 if (cn->mouse_near_ends()) {
761 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
763 _drags->set (new NoteDrag (this, item), event);
773 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
774 event->type == GDK_BUTTON_PRESS) {
776 _drags->set (new RubberbandSelectDrag (this, item), event);
778 } else if (event->type == GDK_BUTTON_PRESS) {
781 case FadeInHandleItem:
783 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
784 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, _cursors->fade_in);
788 case FadeOutHandleItem:
790 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
791 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, _cursors->fade_out);
795 case FeatureLineItem:
797 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
798 remove_transient(item);
802 _drags->set (new FeatureLineDrag (this, item), event);
808 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
809 /* click on an automation region view; do nothing here and let the ARV's signal handler
815 if (internal_editing ()) {
816 /* no region drags in internal edit mode */
820 /* click on a normal region view */
821 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
822 add_region_copy_drag (item, event, clicked_regionview);
824 else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
825 add_region_brush_drag (item, event, clicked_regionview);
827 add_region_drag (item, event, clicked_regionview);
830 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
831 _drags->add (new SelectionDrag (this, clicked_axisview->get_selection_rect (clicked_selection)->rect, SelectionDrag::SelectionMove));
834 _drags->start_grab (event);
837 case RegionViewNameHighlight:
838 case LeftFrameHandle:
839 case RightFrameHandle:
840 if (!internal_editing () && !clicked_regionview->region()->locked()) {
841 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
842 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
849 /* rename happens on edit clicks */
850 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
851 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, s.by_layer()), event);
856 case ControlPointItem:
857 _drags->set (new ControlPointDrag (this, item), event);
861 case AutomationLineItem:
862 _drags->set (new LineDrag (this, item), event);
867 if (internal_editing()) {
868 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
869 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
873 _drags->set (new RubberbandSelectDrag (this, item), event);
877 case AutomationTrackItem:
878 /* rubberband drag to select automation points */
879 _drags->set (new RubberbandSelectDrag (this, item), event);
884 if (join_object_range_button.get_active()) {
885 /* we're in "smart" joined mode, and we've clicked on a Selection */
886 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
887 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
889 /* if we're over an automation track, start a drag of its data */
890 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
892 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, _cursors->up_down);
895 /* if we're over a track and a region, and in the `object' part of a region,
896 put a selection around the region and drag both
898 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
899 if (rtv && _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
900 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (rtv->route ());
902 boost::shared_ptr<Playlist> pl = t->playlist ();
905 boost::shared_ptr<Region> r = pl->top_region_at (event_frame (event));
907 RegionView* rv = rtv->view()->find_view (r);
908 clicked_selection = select_range_around_region (rv);
909 _drags->add (new SelectionDrag (this, item, SelectionDrag::SelectionMove));
910 list<RegionView*> rvs;
912 _drags->add (new RegionMoveDrag (this, item, rv, rvs, false, false));
913 _drags->start_grab (event);
924 case ImageFrameHandleStartItem:
925 imageframe_start_handle_op(item, event) ;
928 case ImageFrameHandleEndItem:
929 imageframe_end_handle_op(item, event) ;
932 case MarkerViewHandleStartItem:
933 markerview_item_start_handle_op(item, event) ;
936 case MarkerViewHandleEndItem:
937 markerview_item_end_handle_op(item, event) ;
941 start_markerview_grab(item, event) ;
944 start_imageframe_grab(item, event) ;
962 /* start a grab so that if we finish after moving
963 we can tell what happened.
965 _drags->set (new RegionGainDrag (this, item), event, current_canvas_cursor);
969 _drags->set (new LineDrag (this, item), event);
972 case ControlPointItem:
973 _drags->set (new ControlPointDrag (this, item), event);
984 case ControlPointItem:
985 _drags->set (new ControlPointDrag (this, item), event);
988 case AutomationLineItem:
989 _drags->set (new LineDrag (this, item), event);
993 // XXX need automation mode to identify which
995 // start_line_grab_from_regionview (item, event);
1005 if (event->type == GDK_BUTTON_PRESS) {
1006 _drags->set (new MouseZoomDrag (this, item), event);
1013 if (internal_editing() && item_type == NoteItem) {
1014 /* drag notes if we're in internal edit mode */
1015 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
1017 } else if ((!internal_editing() || dynamic_cast<AudioRegionView*> (clicked_regionview)) && clicked_regionview) {
1018 /* do time-FX if we're not in internal edit mode, or we are but we clicked on an audio region */
1019 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1025 _drags->set (new ScrubDrag (this, item), event);
1026 scrub_reversals = 0;
1027 scrub_reverse_distance = 0;
1028 last_scrub_x = event->button.x;
1029 scrubbing_direction = 0;
1030 set_canvas_cursor (_cursors->transparent);
1042 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1044 Editing::MouseMode const eff = effective_mouse_mode ();
1047 switch (item_type) {
1049 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1050 add_region_copy_drag (item, event, clicked_regionview);
1052 add_region_drag (item, event, clicked_regionview);
1054 _drags->start_grab (event);
1057 case ControlPointItem:
1058 _drags->set (new ControlPointDrag (this, item), event);
1066 switch (item_type) {
1067 case RegionViewNameHighlight:
1068 case LeftFrameHandle:
1069 case RightFrameHandle:
1070 if (!internal_editing ()) {
1071 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1076 case RegionViewName:
1077 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1088 /* relax till release */
1094 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1095 temporal_zoom_session();
1097 temporal_zoom_to_frame (true, event_frame(event));
1110 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1112 if (event->type != GDK_BUTTON_PRESS) {
1116 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
1118 if (canvas_window) {
1119 Glib::RefPtr<const Gdk::Window> pointer_window;
1122 Gdk::ModifierType mask;
1124 pointer_window = canvas_window->get_pointer (x, y, mask);
1126 if (pointer_window == track_canvas->get_bin_window()) {
1127 track_canvas->window_to_world (x, y, wx, wy);
1131 pre_press_cursor = current_canvas_cursor;
1133 track_canvas->grab_focus();
1135 if (_session && _session->actively_recording()) {
1139 button_selection (item, event, item_type);
1141 if (!_drags->active () &&
1142 (Keyboard::is_delete_event (&event->button) ||
1143 Keyboard::is_context_menu_event (&event->button) ||
1144 Keyboard::is_edit_event (&event->button))) {
1146 /* handled by button release */
1150 switch (event->button.button) {
1152 return button_press_handler_1 (item, event, item_type);
1156 return button_press_handler_2 (item, event, item_type);
1171 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1173 framepos_t where = event_frame (event, 0, 0);
1174 AutomationTimeAxisView* atv = 0;
1176 if (pre_press_cursor) {
1177 set_canvas_cursor (pre_press_cursor);
1178 pre_press_cursor = 0;
1181 /* no action if we're recording */
1183 if (_session && _session->actively_recording()) {
1187 /* see if we're finishing a drag */
1189 bool were_dragging = false;
1190 if (_drags->active ()) {
1191 bool const r = _drags->end_grab (event);
1193 /* grab dragged, so do nothing else */
1197 were_dragging = true;
1200 update_region_layering_order_editor ();
1202 /* edit events get handled here */
1204 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1205 switch (item_type) {
1207 show_region_properties ();
1210 case TempoMarkerItem:
1211 edit_tempo_marker (item);
1214 case MeterMarkerItem:
1215 edit_meter_marker (item);
1218 case RegionViewName:
1219 if (clicked_regionview->name_active()) {
1220 return mouse_rename_region (item, event);
1224 case ControlPointItem:
1225 edit_control_point (item);
1238 /* context menu events get handled here */
1240 if (Keyboard::is_context_menu_event (&event->button)) {
1242 if (!_drags->active ()) {
1244 /* no matter which button pops up the context menu, tell the menu
1245 widget to use button 1 to drive menu selection.
1248 switch (item_type) {
1250 case FadeInHandleItem:
1252 case FadeOutHandleItem:
1253 popup_fade_context_menu (1, event->button.time, item, item_type);
1257 popup_track_context_menu (1, event->button.time, item_type, false);
1261 case RegionViewNameHighlight:
1262 case LeftFrameHandle:
1263 case RightFrameHandle:
1264 case RegionViewName:
1265 popup_track_context_menu (1, event->button.time, item_type, false);
1269 popup_track_context_menu (1, event->button.time, item_type, true);
1272 case AutomationTrackItem:
1273 popup_track_context_menu (1, event->button.time, item_type, false);
1277 case RangeMarkerBarItem:
1278 case TransportMarkerBarItem:
1279 case CdMarkerBarItem:
1282 popup_ruler_menu (where, item_type);
1286 marker_context_menu (&event->button, item);
1289 case TempoMarkerItem:
1290 tempo_or_meter_marker_context_menu (&event->button, item);
1293 case MeterMarkerItem:
1294 tempo_or_meter_marker_context_menu (&event->button, item);
1297 case CrossfadeViewItem:
1298 popup_track_context_menu (1, event->button.time, item_type, false);
1302 case ImageFrameItem:
1303 popup_imageframe_edit_menu(1, event->button.time, item, true) ;
1305 case ImageFrameTimeAxisItem:
1306 popup_imageframe_edit_menu(1, event->button.time, item, false) ;
1308 case MarkerViewItem:
1309 popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ;
1311 case MarkerTimeAxisItem:
1312 popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
1324 /* delete events get handled here */
1326 Editing::MouseMode const eff = effective_mouse_mode ();
1328 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1330 switch (item_type) {
1331 case TempoMarkerItem:
1332 remove_tempo_marker (item);
1335 case MeterMarkerItem:
1336 remove_meter_marker (item);
1340 remove_marker (*item, event);
1344 if (eff == MouseObject) {
1345 remove_clicked_region ();
1349 case ControlPointItem:
1350 if (eff == MouseGain) {
1351 remove_gain_control_point (item, event);
1353 remove_control_point (item, event);
1358 remove_midi_note (item, event);
1367 switch (event->button.button) {
1370 switch (item_type) {
1371 /* see comments in button_press_handler */
1372 case PlayheadCursorItem:
1375 case AutomationLineItem:
1376 case StartSelectionTrimItem:
1377 case EndSelectionTrimItem:
1381 if (!_dragging_playhead) {
1382 snap_to_with_modifier (where, event, 0, true);
1383 mouse_add_new_marker (where);
1387 case CdMarkerBarItem:
1388 if (!_dragging_playhead) {
1389 // if we get here then a dragged range wasn't done
1390 snap_to_with_modifier (where, event, 0, true);
1391 mouse_add_new_marker (where, true);
1396 if (!_dragging_playhead) {
1397 snap_to_with_modifier (where, event);
1398 mouse_add_new_tempo_event (where);
1403 if (!_dragging_playhead) {
1404 mouse_add_new_meter_event (pixel_to_frame (event->button.x));
1415 switch (item_type) {
1416 case AutomationTrackItem:
1417 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1419 atv->add_automation_event (item, event, where, event->button.y);
1430 switch (item_type) {
1433 /* check that we didn't drag before releasing, since
1434 its really annoying to create new control
1435 points when doing this.
1437 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1438 if (were_dragging && arv) {
1439 arv->add_gain_point_event (item, event);
1445 case AutomationTrackItem:
1446 dynamic_cast<AutomationTimeAxisView*>(clicked_axisview)->
1447 add_automation_event (item, event, where, event->button.y);
1456 set_canvas_cursor (current_canvas_cursor);
1457 if (scrubbing_direction == 0) {
1458 /* no drag, just a click */
1459 switch (item_type) {
1461 play_selected_region ();
1467 /* make sure we stop */
1468 _session->request_transport_speed (0.0);
1485 switch (item_type) {
1487 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1489 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1492 // Button2 click is unused
1505 // x_style_paste (where, 1.0);
1525 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1532 last_item_entered = item;
1534 switch (item_type) {
1535 case ControlPointItem:
1536 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1537 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1538 cp->set_visible (true);
1542 at_y = cp->get_y ();
1543 cp->i2w (at_x, at_y);
1547 fraction = 1.0 - (cp->get_y() / cp->line().height());
1549 if (is_drawable() && !_drags->active ()) {
1550 set_canvas_cursor (_cursors->fader);
1553 set_verbose_canvas_cursor (cp->line().get_verbose_cursor_string (fraction), at_x, at_y);
1554 show_verbose_canvas_cursor ();
1559 if (mouse_mode == MouseGain) {
1560 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1562 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
1563 if (is_drawable()) {
1564 set_canvas_cursor (_cursors->fader);
1569 case AutomationLineItem:
1570 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1572 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1574 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
1576 if (is_drawable()) {
1577 set_canvas_cursor (_cursors->fader);
1582 case RegionViewNameHighlight:
1583 if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
1584 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1585 _over_region_trim_target = true;
1589 case LeftFrameHandle:
1590 case RightFrameHandle:
1591 if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
1592 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1596 case StartSelectionTrimItem:
1597 case EndSelectionTrimItem:
1600 case ImageFrameHandleStartItem:
1601 case ImageFrameHandleEndItem:
1602 case MarkerViewHandleStartItem:
1603 case MarkerViewHandleEndItem:
1606 if (is_drawable()) {
1607 set_canvas_cursor (_cursors->trimmer);
1611 case PlayheadCursorItem:
1612 if (is_drawable()) {
1613 switch (_edit_point) {
1615 set_canvas_cursor (_cursors->grabber_edit_point);
1618 set_canvas_cursor (_cursors->grabber);
1624 case RegionViewName:
1626 /* when the name is not an active item, the entire name highlight is for trimming */
1628 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1629 if (mouse_mode == MouseObject && is_drawable()) {
1630 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1631 _over_region_trim_target = true;
1637 case AutomationTrackItem:
1638 if (is_drawable()) {
1639 Gdk::Cursor *cursor;
1640 switch (mouse_mode) {
1642 cursor = _cursors->selector;
1645 cursor = _cursors->zoom_in;
1648 cursor = _cursors->cross_hair;
1652 set_canvas_cursor (cursor);
1654 AutomationTimeAxisView* atv;
1655 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1656 clear_entered_track = false;
1657 set_entered_track (atv);
1663 case RangeMarkerBarItem:
1664 case TransportMarkerBarItem:
1665 case CdMarkerBarItem:
1668 if (is_drawable()) {
1669 set_canvas_cursor (_cursors->timebar);
1674 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1677 entered_marker = marker;
1678 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1680 case MeterMarkerItem:
1681 case TempoMarkerItem:
1682 if (is_drawable()) {
1683 set_canvas_cursor (_cursors->timebar);
1687 case FadeInHandleItem:
1688 if (mouse_mode == MouseObject && !internal_editing()) {
1689 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1691 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1693 set_canvas_cursor (_cursors->fade_in);
1697 case FadeOutHandleItem:
1698 if (mouse_mode == MouseObject && !internal_editing()) {
1699 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1701 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1703 set_canvas_cursor (_cursors->fade_out);
1706 case FeatureLineItem:
1708 ArdourCanvas::SimpleLine *line = dynamic_cast<ArdourCanvas::SimpleLine *> (item);
1709 line->property_color_rgba() = 0xFF0000FF;
1713 if (join_object_range_button.get_active()) {
1714 set_canvas_cursor ();
1722 /* second pass to handle entered track status in a comprehensible way.
1725 switch (item_type) {
1727 case AutomationLineItem:
1728 case ControlPointItem:
1729 /* these do not affect the current entered track state */
1730 clear_entered_track = false;
1733 case AutomationTrackItem:
1734 /* handled above already */
1738 set_entered_track (0);
1746 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1756 switch (item_type) {
1757 case ControlPointItem:
1758 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1759 if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
1760 if (cp->line().npoints() > 1 && !cp->get_selected()) {
1761 cp->set_visible (false);
1765 if (is_drawable()) {
1766 set_canvas_cursor (current_canvas_cursor);
1769 hide_verbose_canvas_cursor ();
1772 case RegionViewNameHighlight:
1773 case LeftFrameHandle:
1774 case RightFrameHandle:
1775 case StartSelectionTrimItem:
1776 case EndSelectionTrimItem:
1777 case PlayheadCursorItem:
1780 case ImageFrameHandleStartItem:
1781 case ImageFrameHandleEndItem:
1782 case MarkerViewHandleStartItem:
1783 case MarkerViewHandleEndItem:
1786 _over_region_trim_target = false;
1788 if (is_drawable()) {
1789 set_canvas_cursor (current_canvas_cursor);
1794 case AutomationLineItem:
1795 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1797 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1799 line->property_fill_color_rgba() = al->get_line_color();
1801 if (is_drawable()) {
1802 set_canvas_cursor (current_canvas_cursor);
1806 case RegionViewName:
1807 /* see enter_handler() for notes */
1808 _over_region_trim_target = false;
1810 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1811 if (is_drawable() && mouse_mode == MouseObject) {
1812 set_canvas_cursor (current_canvas_cursor);
1817 case RangeMarkerBarItem:
1818 case TransportMarkerBarItem:
1819 case CdMarkerBarItem:
1823 if (is_drawable()) {
1824 set_canvas_cursor (current_canvas_cursor);
1829 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1833 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1834 location_flags_changed (loc, this);
1837 case MeterMarkerItem:
1838 case TempoMarkerItem:
1840 if (is_drawable()) {
1841 set_canvas_cursor (_cursors->timebar);
1846 case FadeInHandleItem:
1847 case FadeOutHandleItem:
1848 rv = static_cast<RegionView*>(item->get_data ("regionview"));
1850 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1852 rect->property_fill_color_rgba() = rv->get_fill_color();
1853 rect->property_outline_pixels() = 0;
1856 set_canvas_cursor (current_canvas_cursor);
1859 case AutomationTrackItem:
1860 if (is_drawable()) {
1861 set_canvas_cursor (current_canvas_cursor);
1862 clear_entered_track = true;
1863 Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track));
1866 case FeatureLineItem:
1868 ArdourCanvas::SimpleLine *line = dynamic_cast<ArdourCanvas::SimpleLine *> (item);
1869 line->property_color_rgba() = (guint) ARDOUR_UI::config()->canvasvar_ZeroLine.get();;
1881 Editor::left_automation_track ()
1883 if (clear_entered_track) {
1884 set_entered_track (0);
1885 clear_entered_track = false;
1891 Editor::scrub (framepos_t frame, double current_x)
1895 if (scrubbing_direction == 0) {
1897 _session->request_locate (frame, false);
1898 _session->request_transport_speed (0.1);
1899 scrubbing_direction = 1;
1903 if (last_scrub_x > current_x) {
1905 /* pointer moved to the left */
1907 if (scrubbing_direction > 0) {
1909 /* we reversed direction to go backwards */
1912 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1916 /* still moving to the left (backwards) */
1918 scrub_reversals = 0;
1919 scrub_reverse_distance = 0;
1921 delta = 0.01 * (last_scrub_x - current_x);
1922 _session->request_transport_speed (_session->transport_speed() - delta);
1926 /* pointer moved to the right */
1928 if (scrubbing_direction < 0) {
1929 /* we reversed direction to go forward */
1932 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1935 /* still moving to the right */
1937 scrub_reversals = 0;
1938 scrub_reverse_distance = 0;
1940 delta = 0.01 * (current_x - last_scrub_x);
1941 _session->request_transport_speed (_session->transport_speed() + delta);
1945 /* if there have been more than 2 opposite motion moves detected, or one that moves
1946 back more than 10 pixels, reverse direction
1949 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1951 if (scrubbing_direction > 0) {
1952 /* was forwards, go backwards */
1953 _session->request_transport_speed (-0.1);
1954 scrubbing_direction = -1;
1956 /* was backwards, go forwards */
1957 _session->request_transport_speed (0.1);
1958 scrubbing_direction = 1;
1961 scrub_reverse_distance = 0;
1962 scrub_reversals = 0;
1966 last_scrub_x = current_x;
1970 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1972 _last_motion_y = event->motion.y;
1974 if (event->motion.is_hint) {
1977 /* We call this so that MOTION_NOTIFY events continue to be
1978 delivered to the canvas. We need to do this because we set
1979 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1980 the density of the events, at the expense of a round-trip
1981 to the server. Given that this will mostly occur on cases
1982 where DISPLAY = :0.0, and given the cost of what the motion
1983 event might do, its a good tradeoff.
1986 track_canvas->get_pointer (x, y);
1989 if (current_stepping_trackview) {
1990 /* don't keep the persistent stepped trackview if the mouse moves */
1991 current_stepping_trackview = 0;
1992 step_timeout.disconnect ();
1995 if (_session && _session->actively_recording()) {
1996 /* Sorry. no dragging stuff around while we record */
2000 JoinObjectRangeState const old = _join_object_range_state;
2001 update_join_object_range_location (event->motion.x, event->motion.y);
2002 if (_join_object_range_state != old) {
2003 set_canvas_cursor ();
2006 if (_over_region_trim_target) {
2007 set_canvas_cursor_for_region_view (event->motion.x, entered_regionview);
2010 bool handled = false;
2011 if (_drags->active ()) {
2012 handled = _drags->motion_handler (event, from_autoscroll);
2019 track_canvas_motion (event);
2024 Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* /*event*/)
2026 ControlPoint* control_point;
2028 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2029 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2033 // We shouldn't remove the first or last gain point
2034 if (control_point->line().is_last_point(*control_point) ||
2035 control_point->line().is_first_point(*control_point)) {
2039 control_point->line().remove_point (*control_point);
2043 Editor::remove_control_point (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2045 ControlPoint* control_point;
2047 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2048 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2052 control_point->line().remove_point (*control_point);
2056 Editor::edit_control_point (ArdourCanvas::Item* item)
2058 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2061 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2065 ControlPointDialog d (p);
2066 d.set_position (Gtk::WIN_POS_MOUSE);
2069 if (d.run () != RESPONSE_ACCEPT) {
2073 p->line().modify_point_y (*p, d.get_y_fraction ());
2077 Editor::edit_note (ArdourCanvas::Item* item)
2079 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2082 EditNoteDialog d (&e->region_view(), e);
2083 d.set_position (Gtk::WIN_POS_MOUSE);
2091 Editor::visible_order_range (int* low, int* high) const
2093 *low = TimeAxisView::max_order ();
2096 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2098 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2100 if (!rtv->hidden()) {
2102 if (*high < rtv->order()) {
2103 *high = rtv->order ();
2106 if (*low > rtv->order()) {
2107 *low = rtv->order ();
2114 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2116 /* Either add to or set the set the region selection, unless
2117 this is an alignment click (control used)
2120 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2121 TimeAxisView* tv = &rv.get_time_axis_view();
2122 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2124 if (rtv && rtv->is_track()) {
2125 speed = rtv->track()->speed();
2128 framepos_t where = get_preferred_edit_position();
2132 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2134 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2136 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2138 align_region (rv.region(), End, (framepos_t) (where * speed));
2142 align_region (rv.region(), Start, (framepos_t) (where * speed));
2149 Editor::show_verbose_time_cursor (framepos_t frame, double offset, double xpos, double ypos)
2152 Timecode::Time timecode;
2155 framepos_t frame_rate;
2158 if (_session == 0) {
2164 if (Profile->get_sae() || Profile->get_small_screen()) {
2165 m = ARDOUR_UI::instance()->primary_clock.mode();
2167 m = ARDOUR_UI::instance()->secondary_clock.mode();
2171 case AudioClock::BBT:
2172 _session->bbt_time (frame, bbt);
2173 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, bbt.bars, bbt.beats, bbt.ticks);
2176 case AudioClock::Timecode:
2177 _session->timecode_time (frame, timecode);
2178 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2181 case AudioClock::MinSec:
2182 /* XXX this is copied from show_verbose_duration_cursor() */
2183 frame_rate = _session->frame_rate();
2184 hours = frame / (frame_rate * 3600);
2185 frame = frame % (frame_rate * 3600);
2186 mins = frame / (frame_rate * 60);
2187 frame = frame % (frame_rate * 60);
2188 secs = (float) frame / (float) frame_rate;
2189 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
2193 snprintf (buf, sizeof(buf), "%" PRIi64, frame);
2197 if (xpos >= 0 && ypos >=0) {
2198 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2200 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset - horizontal_position(), _drags->current_pointer_y() + offset - vertical_adjustment.get_value() + canvas_timebars_vsize);
2202 show_verbose_canvas_cursor ();
2206 Editor::show_verbose_duration_cursor (framepos_t start, framepos_t end, double offset, double xpos, double ypos)
2209 Timecode::Time timecode;
2213 framepos_t distance, frame_rate;
2215 Meter meter_at_start(_session->tempo_map().meter_at(start));
2217 if (_session == 0) {
2223 if (Profile->get_sae() || Profile->get_small_screen()) {
2224 m = ARDOUR_UI::instance()->primary_clock.mode ();
2226 m = ARDOUR_UI::instance()->secondary_clock.mode ();
2230 case AudioClock::BBT:
2231 _session->bbt_time (start, sbbt);
2232 _session->bbt_time (end, ebbt);
2235 /* XXX this computation won't work well if the
2236 user makes a selection that spans any meter changes.
2239 ebbt.bars -= sbbt.bars;
2240 if (ebbt.beats >= sbbt.beats) {
2241 ebbt.beats -= sbbt.beats;
2244 ebbt.beats = int(meter_at_start.beats_per_bar()) + ebbt.beats - sbbt.beats;
2246 if (ebbt.ticks >= sbbt.ticks) {
2247 ebbt.ticks -= sbbt.ticks;
2250 ebbt.ticks = int(Meter::ticks_per_beat) + ebbt.ticks - sbbt.ticks;
2253 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, ebbt.bars, ebbt.beats, ebbt.ticks);
2256 case AudioClock::Timecode:
2257 _session->timecode_duration (end - start, timecode);
2258 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2261 case AudioClock::MinSec:
2262 /* XXX this stuff should be elsewhere.. */
2263 distance = end - start;
2264 frame_rate = _session->frame_rate();
2265 hours = distance / (frame_rate * 3600);
2266 distance = distance % (frame_rate * 3600);
2267 mins = distance / (frame_rate * 60);
2268 distance = distance % (frame_rate * 60);
2269 secs = (float) distance / (float) frame_rate;
2270 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
2274 snprintf (buf, sizeof(buf), "%" PRIi64, end - start);
2278 if (xpos >= 0 && ypos >=0) {
2279 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2282 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset, _drags->current_pointer_y() + offset);
2285 show_verbose_canvas_cursor ();
2289 Editor::collect_new_region_view (RegionView* rv)
2291 latest_regionviews.push_back (rv);
2295 Editor::collect_and_select_new_region_view (RegionView* rv)
2298 latest_regionviews.push_back (rv);
2302 Editor::cancel_selection ()
2304 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2305 (*i)->hide_selection ();
2308 selection->clear ();
2309 clicked_selection = 0;
2314 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2316 RegionView* rv = clicked_regionview;
2318 /* Choose action dependant on which button was pressed */
2319 switch (event->button.button) {
2321 begin_reversible_command (_("start point trim"));
2323 if (selection->selected (rv)) {
2324 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2325 i != selection->regions.by_layer().end(); ++i)
2328 cerr << "region view contains null region" << endl;
2331 if (!(*i)->region()->locked()) {
2332 (*i)->region()->clear_changes ();
2333 (*i)->region()->trim_front (new_bound, this);
2334 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2339 if (!rv->region()->locked()) {
2340 rv->region()->clear_changes ();
2341 rv->region()->trim_front (new_bound, this);
2342 _session->add_command(new StatefulDiffCommand (rv->region()));
2346 commit_reversible_command();
2350 begin_reversible_command (_("End point trim"));
2352 if (selection->selected (rv)) {
2354 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2356 if (!(*i)->region()->locked()) {
2357 (*i)->region()->clear_changes();
2358 (*i)->region()->trim_end (new_bound, this);
2359 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2365 if (!rv->region()->locked()) {
2366 rv->region()->clear_changes ();
2367 rv->region()->trim_end (new_bound, this);
2368 _session->add_command (new StatefulDiffCommand (rv->region()));
2372 commit_reversible_command();
2381 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2386 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2387 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2391 Location* location = find_location_from_marker (marker, is_start);
2392 location->set_hidden (true, this);
2397 Editor::reposition_zoom_rect (framepos_t start, framepos_t end)
2399 double x1 = frame_to_pixel (start);
2400 double x2 = frame_to_pixel (end);
2401 double y2 = full_canvas_height - 1.0;
2403 zoom_rect->property_x1() = x1;
2404 zoom_rect->property_y1() = 1.0;
2405 zoom_rect->property_x2() = x2;
2406 zoom_rect->property_y2() = y2;
2411 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2413 using namespace Gtkmm2ext;
2415 ArdourPrompter prompter (false);
2417 prompter.set_prompt (_("Name for region:"));
2418 prompter.set_initial_text (clicked_regionview->region()->name());
2419 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2420 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2421 prompter.show_all ();
2422 switch (prompter.run ()) {
2423 case Gtk::RESPONSE_ACCEPT:
2425 prompter.get_result(str);
2427 clicked_regionview->region()->set_name (str);
2436 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2438 /* no brushing without a useful snap setting */
2440 switch (_snap_mode) {
2442 return; /* can't work because it allows region to be placed anywhere */
2447 switch (_snap_type) {
2455 /* don't brush a copy over the original */
2457 if (pos == rv->region()->position()) {
2461 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2463 if (rtv == 0 || !rtv->is_track()) {
2467 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2468 double speed = rtv->track()->speed();
2470 playlist->clear_changes ();
2471 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region()));
2472 playlist->add_region (new_region, (framepos_t) (pos * speed));
2473 _session->add_command (new StatefulDiffCommand (playlist));
2475 // playlist is frozen, so we have to update manually XXX this is disgusting
2477 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2481 Editor::track_height_step_timeout ()
2483 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2484 current_stepping_trackview = 0;
2491 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2493 assert (region_view);
2495 if (!region_view->region()->playlist()) {
2499 _region_motion_group->raise_to_top ();
2501 if (Config->get_edit_mode() == Splice) {
2502 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2504 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2505 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false));
2508 /* sync the canvas to what we think is its current state */
2509 update_canvas_now();
2513 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2515 assert (region_view);
2517 if (!region_view->region()->playlist()) {
2521 _region_motion_group->raise_to_top ();
2523 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2524 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true));
2528 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2530 assert (region_view);
2532 if (!region_view->region()->playlist()) {
2536 if (Config->get_edit_mode() == Splice) {
2540 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2541 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), true, false));
2543 begin_reversible_command (_("Drag region brush"));
2546 /** Start a grab where a time range is selected, track(s) are selected, and the
2547 * user clicks and drags a region with a modifier in order to create a new region containing
2548 * the section of the clicked region that lies within the time range.
2551 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2553 if (clicked_regionview == 0) {
2557 /* lets try to create new Region for the selection */
2559 vector<boost::shared_ptr<Region> > new_regions;
2560 create_region_from_selection (new_regions);
2562 if (new_regions.empty()) {
2566 /* XXX fix me one day to use all new regions */
2568 boost::shared_ptr<Region> region (new_regions.front());
2570 /* add it to the current stream/playlist.
2572 tricky: the streamview for the track will add a new regionview. we will
2573 catch the signal it sends when it creates the regionview to
2574 set the regionview we want to then drag.
2577 latest_regionviews.clear();
2578 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2580 /* A selection grab currently creates two undo/redo operations, one for
2581 creating the new region and another for moving it.
2584 begin_reversible_command (_("selection grab"));
2586 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2588 playlist->clear_changes ();
2589 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2590 _session->add_command(new StatefulDiffCommand (playlist));
2592 commit_reversible_command ();
2596 if (latest_regionviews.empty()) {
2597 /* something went wrong */
2601 /* we need to deselect all other regionviews, and select this one
2602 i'm ignoring undo stuff, because the region creation will take care of it
2604 selection->set (latest_regionviews);
2606 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2612 if (_drags->active ()) {
2615 selection->clear ();
2620 Editor::set_internal_edit (bool yn)
2622 _internal_editing = yn;
2625 mouse_select_button.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
2626 mouse_select_button.get_image ()->show ();
2627 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Draw/Edit MIDI Notes"));
2628 mouse_mode_toggled (mouse_mode);
2630 /* deselect everything to avoid confusion when e.g. we can't now cut a previously selected
2631 region because cut means "cut note" rather than "cut region".
2633 selection->clear ();
2637 mouse_select_button.set_image (*(manage (new Image (::get_icon("tool_range")))));
2638 mouse_select_button.get_image ()->show ();
2639 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Select/Move Ranges"));
2640 mouse_mode_toggled (mouse_mode); // sets cursor
2644 /** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
2645 * used by the `join object/range' tool mode.
2648 Editor::update_join_object_range_location (double x, double y)
2650 /* XXX: actually, this decides based on whether the mouse is in the top or bottom half of a RouteTimeAxisView;
2651 entered_{track,regionview} is not always setup (e.g. if the mouse is over a TimeSelection), and to get a Region
2652 that we're over requires searching the playlist.
2655 if (join_object_range_button.get_active() == false || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
2656 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2660 if (mouse_mode == MouseObject) {
2661 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2662 } else if (mouse_mode == MouseRange) {
2663 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2666 /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
2667 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y + vertical_adjustment.get_value() - canvas_timebars_vsize);
2671 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2676 rtv->canvas_display()->w2i (cx, cy);
2678 double const c = cy / rtv->view()->child_height();
2680 double const f = modf (c, &d);
2682 _join_object_range_state = f < 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2688 Editor::effective_mouse_mode () const
2690 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2692 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2700 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2702 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2705 e->region_view().delete_note (e->note ());
2709 Editor::set_canvas_cursor_for_region_view (double x, RegionView* rv)
2711 ArdourCanvas::Group* g = rv->get_canvas_group ();
2712 ArdourCanvas::Group* p = g->get_parent_group ();
2714 /* Compute x in region view parent coordinates */
2718 double x1, x2, y1, y2;
2719 g->get_bounds (x1, y1, x2, y2);
2721 /* Halfway across the region */
2722 double const h = (x1 + x2) / 2;
2724 Trimmable::CanTrim ct = rv->region()->can_trim ();
2726 if (ct & Trimmable::FrontTrimEarlier) {
2727 set_canvas_cursor (_cursors->left_side_trim);
2729 set_canvas_cursor (_cursors->left_side_trim_right_only);
2732 if (ct & Trimmable::EndTrimLater) {
2733 set_canvas_cursor (_cursors->right_side_trim);
2735 set_canvas_cursor (_cursors->right_side_trim_left_only);