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"
60 #include "ardour/types.h"
61 #include "ardour/profile.h"
62 #include "ardour/route.h"
63 #include "ardour/audio_track.h"
64 #include "ardour/audio_diskstream.h"
65 #include "ardour/midi_diskstream.h"
66 #include "ardour/playlist.h"
67 #include "ardour/audioplaylist.h"
68 #include "ardour/audioregion.h"
69 #include "ardour/midi_region.h"
70 #include "ardour/dB.h"
71 #include "ardour/utils.h"
72 #include "ardour/region_factory.h"
73 #include "ardour/source_factory.h"
74 #include "ardour/session.h"
81 using namespace ARDOUR;
84 using namespace Editing;
85 using Gtkmm2ext::Keyboard;
88 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
92 Gdk::ModifierType mask;
93 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
94 Glib::RefPtr<const Gdk::Window> pointer_window;
100 pointer_window = canvas_window->get_pointer (x, y, mask);
102 if (pointer_window == track_canvas->get_bin_window()) {
105 in_track_canvas = true;
108 in_track_canvas = false;
113 event.type = GDK_BUTTON_RELEASE;
117 where = event_frame (&event, 0, 0);
122 Editor::event_frame (GdkEvent const * event, double* pcx, double* pcy) const
136 switch (event->type) {
137 case GDK_BUTTON_RELEASE:
138 case GDK_BUTTON_PRESS:
139 case GDK_2BUTTON_PRESS:
140 case GDK_3BUTTON_PRESS:
141 *pcx = event->button.x;
142 *pcy = event->button.y;
143 _trackview_group->w2i(*pcx, *pcy);
145 case GDK_MOTION_NOTIFY:
146 *pcx = event->motion.x;
147 *pcy = event->motion.y;
148 _trackview_group->w2i(*pcx, *pcy);
150 case GDK_ENTER_NOTIFY:
151 case GDK_LEAVE_NOTIFY:
152 track_canvas->w2c(event->crossing.x, event->crossing.y, *pcx, *pcy);
155 case GDK_KEY_RELEASE:
156 // track_canvas->w2c(event->key.x, event->key.y, *pcx, *pcy);
159 warning << string_compose (_("Editor::event_frame() used on unhandled event type %1"), event->type) << endmsg;
163 /* note that pixel_to_frame() never returns less than zero, so even if the pixel
164 position is negative (as can be the case with motion events in particular),
165 the frame location is always positive.
168 return pixel_to_frame (*pcx);
172 Editor::which_grabber_cursor ()
174 Gdk::Cursor* c = grabber_cursor;
176 if (_internal_editing) {
177 switch (mouse_mode) {
179 c = midi_pencil_cursor;
183 c = grabber_note_cursor;
187 c = midi_resize_cursor;
196 switch (_edit_point) {
198 c = grabber_edit_point_cursor;
201 boost::shared_ptr<Movable> m = _movable.lock();
202 if (m && m->locked()) {
213 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
215 boost::shared_ptr<Trimmable> st = _trimmable.lock();
217 if (!st || st == t) {
219 set_canvas_cursor ();
225 Editor::set_current_movable (boost::shared_ptr<Movable> m)
227 boost::shared_ptr<Movable> sm = _movable.lock();
229 if (!sm || sm != m) {
231 set_canvas_cursor ();
236 Editor::set_canvas_cursor ()
238 if (_internal_editing) {
240 switch (mouse_mode) {
242 current_canvas_cursor = midi_pencil_cursor;
246 current_canvas_cursor = which_grabber_cursor();
250 current_canvas_cursor = midi_resize_cursor;
259 switch (mouse_mode) {
261 current_canvas_cursor = selector_cursor;
265 current_canvas_cursor = which_grabber_cursor();
269 current_canvas_cursor = cross_hair_cursor;
273 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
274 current_canvas_cursor = zoom_out_cursor;
276 current_canvas_cursor = zoom_in_cursor;
281 current_canvas_cursor = time_fx_cursor; // just use playhead
285 current_canvas_cursor = speaker_cursor;
290 switch (_join_object_range_state) {
291 case JOIN_OBJECT_RANGE_NONE:
293 case JOIN_OBJECT_RANGE_OBJECT:
294 current_canvas_cursor = which_grabber_cursor ();
296 case JOIN_OBJECT_RANGE_RANGE:
297 current_canvas_cursor = selector_cursor;
301 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
302 if (join_object_range_button.get_active() && last_item_entered) {
303 if (last_item_entered->property_parent() && (*last_item_entered->property_parent()).get_data (X_("timeselection"))) {
304 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y + vertical_adjustment.get_value() - canvas_timebars_vsize);
305 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
306 current_canvas_cursor = up_down_cursor;
311 set_canvas_cursor (current_canvas_cursor, true);
315 Editor::set_mouse_mode (MouseMode m, bool force)
317 if (_drags->active ()) {
321 if (!force && m == mouse_mode) {
325 Glib::RefPtr<Action> act;
329 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
333 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
337 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-gain"));
341 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-zoom"));
345 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
349 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
355 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
358 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
359 tact->set_active (false);
360 tact->set_active (true);
364 Editor::mouse_mode_toggled (MouseMode m)
370 if (!internal_editing()) {
371 if (mouse_mode != MouseRange && _join_object_range_state == JOIN_OBJECT_RANGE_NONE) {
373 /* in all modes except range and joined object/range, hide the range selection,
374 show the object (region) selection.
377 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
378 (*i)->set_should_show_selection (true);
380 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
381 (*i)->hide_selection ();
387 in range or object/range mode, show the range selection.
390 for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
391 (*i)->show_selection (selection->time);
396 set_canvas_cursor ();
400 Editor::step_mouse_mode (bool next)
402 switch (current_mouse_mode()) {
405 if (Profile->get_sae()) {
406 set_mouse_mode (MouseZoom);
408 set_mouse_mode (MouseRange);
411 set_mouse_mode (MouseTimeFX);
416 if (next) set_mouse_mode (MouseZoom);
417 else set_mouse_mode (MouseObject);
422 if (Profile->get_sae()) {
423 set_mouse_mode (MouseTimeFX);
425 set_mouse_mode (MouseGain);
428 if (Profile->get_sae()) {
429 set_mouse_mode (MouseObject);
431 set_mouse_mode (MouseRange);
437 if (next) set_mouse_mode (MouseTimeFX);
438 else set_mouse_mode (MouseZoom);
443 set_mouse_mode (MouseAudition);
445 if (Profile->get_sae()) {
446 set_mouse_mode (MouseZoom);
448 set_mouse_mode (MouseGain);
454 if (next) set_mouse_mode (MouseObject);
455 else set_mouse_mode (MouseTimeFX);
461 Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemType item_type)
463 /* in object/audition/timefx/gain-automation mode,
464 any button press sets the selection if the object
465 can be selected. this is a bit of hack, because
466 we want to avoid this if the mouse operation is a
469 note: not dbl-click or triple-click
471 Also note that there is no region selection in internal edit mode, otherwise
472 for operations operating on the selection (e.g. cut) it is not obvious whether
473 to cut notes or regions.
476 if (((mouse_mode != MouseObject) &&
477 (_join_object_range_state != JOIN_OBJECT_RANGE_OBJECT) &&
478 (mouse_mode != MouseAudition || item_type != RegionItem) &&
479 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
480 (mouse_mode != MouseGain) &&
481 (mouse_mode != MouseRange)) ||
482 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3) ||
483 internal_editing()) {
488 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
490 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
492 /* almost no selection action on modified button-2 or button-3 events */
494 if (item_type != RegionItem && event->button.button != 2) {
500 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
501 bool press = (event->type == GDK_BUTTON_PRESS);
503 // begin_reversible_command (_("select on click"));
507 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
508 set_selected_regionview_from_click (press, op, true);
509 } else if (event->type == GDK_BUTTON_PRESS) {
510 selection->clear_tracks ();
511 set_selected_track_as_side_effect (op, true);
513 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
514 clicked_selection = select_range_around_region (selection->regions.front());
518 case RegionViewNameHighlight:
520 case LeftFrameHandle:
521 case RightFrameHandle:
522 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
523 set_selected_regionview_from_click (press, op, true);
524 } else if (event->type == GDK_BUTTON_PRESS) {
525 set_selected_track_as_side_effect (op);
530 case FadeInHandleItem:
532 case FadeOutHandleItem:
534 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
535 set_selected_regionview_from_click (press, op, true);
536 } else if (event->type == GDK_BUTTON_PRESS) {
537 set_selected_track_as_side_effect (op);
541 case ControlPointItem:
542 set_selected_track_as_side_effect (op, true);
543 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
544 set_selected_control_point_from_click (op, false);
549 /* for context click, select track */
550 if (event->button.button == 3) {
551 selection->clear_tracks ();
552 set_selected_track_as_side_effect (op, true);
556 case AutomationTrackItem:
557 set_selected_track_as_side_effect (op, true);
566 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
568 /* single mouse clicks on any of these item types operate
569 independent of mouse mode, mostly because they are
570 not on the main track canvas or because we want
575 case PlayheadCursorItem:
576 _drags->set (new CursorDrag (this, item, true), event);
580 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
581 hide_marker (item, event);
583 _drags->set (new MarkerDrag (this, item), event);
587 case TempoMarkerItem:
589 new TempoMarkerDrag (
592 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
598 case MeterMarkerItem:
600 new MeterMarkerDrag (
603 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
612 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
613 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
619 case RangeMarkerBarItem:
620 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
621 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
623 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
628 case CdMarkerBarItem:
629 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
630 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
632 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
637 case TransportMarkerBarItem:
638 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
639 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
641 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
650 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
651 /* special case: allow trim of range selections in joined object mode;
652 in theory eff should equal MouseRange in this case, but it doesn't
653 because entering the range selection canvas item results in entered_regionview
654 being set to 0, so update_join_object_range_location acts as if we aren't
657 if (item_type == StartSelectionTrimItem) {
658 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
659 } else if (item_type == EndSelectionTrimItem) {
660 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
664 Editing::MouseMode eff = effective_mouse_mode ();
666 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
667 if (item_type == FadeInHandleItem || item_type == FadeOutHandleItem) {
674 case StartSelectionTrimItem:
675 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
678 case EndSelectionTrimItem:
679 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
683 if (Keyboard::modifier_state_contains
684 (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier))) {
685 // contains and not equals because I can't use alt as a modifier alone.
686 start_selection_grab (item, event);
687 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
688 /* grab selection for moving */
689 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
691 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
692 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
694 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
695 if (join_object_range_button.get_active() && atv) {
696 /* smart "join" mode: drag automation */
697 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, up_down_cursor);
699 /* this was debated, but decided the more common action was to
700 make a new selection */
701 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
708 if (internal_editing()) {
709 /* trim notes if we're in internal edit mode and near the ends of the note */
710 ArdourCanvas::CanvasNote* cn = dynamic_cast<ArdourCanvas::CanvasNote*> (item);
711 cerr << "NoteItem button press, cursor = " << current_canvas_cursor << endl;
712 if (cn->mouse_near_ends()) {
713 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
715 _drags->set (new NoteDrag (this, item), event);
721 if (internal_editing()) {
722 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
723 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
727 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
732 case RegionViewNameHighlight:
733 case LeftFrameHandle:
734 case RightFrameHandle:
735 if (!clicked_regionview->region()->locked()) {
736 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
737 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
743 if (!internal_editing()) {
744 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
753 if (internal_editing()) {
754 ArdourCanvas::CanvasNote* cn = dynamic_cast<ArdourCanvas::CanvasNote*> (item);
755 if (cn->mouse_near_ends()) {
756 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
758 _drags->set (new NoteDrag (this, item), event);
768 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
769 event->type == GDK_BUTTON_PRESS) {
771 _drags->set (new RubberbandSelectDrag (this, item), event);
773 } else if (event->type == GDK_BUTTON_PRESS) {
776 case FadeInHandleItem:
778 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
779 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, fade_in_cursor);
783 case FadeOutHandleItem:
785 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
786 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, fade_out_cursor);
790 case FeatureLineItem:
792 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
793 remove_transient(item);
797 _drags->set (new FeatureLineDrag (this, item), event);
803 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
804 /* click on an automation region view; do nothing here and let the ARV's signal handler
810 if (internal_editing ()) {
811 /* no region drags in internal edit mode */
815 /* click on a normal region view */
816 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
817 add_region_copy_drag (item, event, clicked_regionview);
819 else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
820 add_region_brush_drag (item, event, clicked_regionview);
822 add_region_drag (item, event, clicked_regionview);
825 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
826 _drags->add (new SelectionDrag (this, clicked_axisview->get_selection_rect (clicked_selection)->rect, SelectionDrag::SelectionMove));
829 _drags->start_grab (event);
832 case RegionViewNameHighlight:
833 case LeftFrameHandle:
834 case RightFrameHandle:
835 if (!internal_editing () && !clicked_regionview->region()->locked()) {
836 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
837 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
844 /* rename happens on edit clicks */
845 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
846 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, s.by_layer()), event);
851 case ControlPointItem:
852 _drags->set (new ControlPointDrag (this, item), event);
856 case AutomationLineItem:
857 _drags->set (new LineDrag (this, item), event);
862 if (internal_editing()) {
863 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
864 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
868 _drags->set (new RubberbandSelectDrag (this, item), event);
872 case AutomationTrackItem:
873 /* rubberband drag to select automation points */
874 _drags->set (new RubberbandSelectDrag (this, item), event);
879 if (join_object_range_button.get_active()) {
880 /* we're in "smart" joined mode, and we've clicked on a Selection */
881 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
882 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
884 /* if we're over an automation track, start a drag of its data */
885 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
887 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, up_down_cursor);
890 /* if we're over a track and a region, and in the `object' part of a region,
891 put a selection around the region and drag both
893 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
894 if (rtv && _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
895 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (rtv->route ());
897 boost::shared_ptr<Playlist> pl = t->playlist ();
900 boost::shared_ptr<Region> r = pl->top_region_at (event_frame (event));
902 RegionView* rv = rtv->view()->find_view (r);
903 clicked_selection = select_range_around_region (rv);
904 _drags->add (new SelectionDrag (this, item, SelectionDrag::SelectionMove));
905 list<RegionView*> rvs;
907 _drags->add (new RegionMoveDrag (this, item, rv, rvs, false, false));
908 _drags->start_grab (event);
919 case ImageFrameHandleStartItem:
920 imageframe_start_handle_op(item, event) ;
923 case ImageFrameHandleEndItem:
924 imageframe_end_handle_op(item, event) ;
927 case MarkerViewHandleStartItem:
928 markerview_item_start_handle_op(item, event) ;
931 case MarkerViewHandleEndItem:
932 markerview_item_end_handle_op(item, event) ;
936 start_markerview_grab(item, event) ;
939 start_imageframe_grab(item, event) ;
957 /* start a grab so that if we finish after moving
958 we can tell what happened.
960 _drags->set (new RegionGainDrag (this, item), event, current_canvas_cursor);
964 _drags->set (new LineDrag (this, item), event);
967 case ControlPointItem:
968 _drags->set (new ControlPointDrag (this, item), event);
979 case ControlPointItem:
980 _drags->set (new ControlPointDrag (this, item), event);
983 case AutomationLineItem:
984 _drags->set (new LineDrag (this, item), event);
988 // XXX need automation mode to identify which
990 // start_line_grab_from_regionview (item, event);
1000 if (event->type == GDK_BUTTON_PRESS) {
1001 _drags->set (new MouseZoomDrag (this, item), event);
1008 if (internal_editing() && item_type == NoteItem) {
1009 /* drag notes if we're in internal edit mode */
1010 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
1012 } else if ((!internal_editing() || dynamic_cast<AudioRegionView*> (clicked_regionview)) && clicked_regionview) {
1013 /* do time-FX if we're not in internal edit mode, or we are but we clicked on an audio region */
1014 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1020 _drags->set (new ScrubDrag (this, item), event);
1021 scrub_reversals = 0;
1022 scrub_reverse_distance = 0;
1023 last_scrub_x = event->button.x;
1024 scrubbing_direction = 0;
1025 set_canvas_cursor (transparent_cursor);
1037 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1039 Editing::MouseMode const eff = effective_mouse_mode ();
1042 switch (item_type) {
1044 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1045 add_region_copy_drag (item, event, clicked_regionview);
1047 add_region_drag (item, event, clicked_regionview);
1049 _drags->start_grab (event);
1052 case ControlPointItem:
1053 _drags->set (new ControlPointDrag (this, item), event);
1061 switch (item_type) {
1062 case RegionViewNameHighlight:
1063 case LeftFrameHandle:
1064 case RightFrameHandle:
1065 if (!internal_editing ()) {
1066 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1071 case RegionViewName:
1072 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1083 /* relax till release */
1089 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1090 temporal_zoom_session();
1092 temporal_zoom_to_frame (true, event_frame(event));
1105 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1107 if (event->type != GDK_BUTTON_PRESS) {
1111 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
1113 if (canvas_window) {
1114 Glib::RefPtr<const Gdk::Window> pointer_window;
1117 Gdk::ModifierType mask;
1119 pointer_window = canvas_window->get_pointer (x, y, mask);
1121 if (pointer_window == track_canvas->get_bin_window()) {
1122 track_canvas->window_to_world (x, y, wx, wy);
1126 pre_press_cursor = current_canvas_cursor;
1128 track_canvas->grab_focus();
1130 if (_session && _session->actively_recording()) {
1134 button_selection (item, event, item_type);
1136 if (!_drags->active () &&
1137 (Keyboard::is_delete_event (&event->button) ||
1138 Keyboard::is_context_menu_event (&event->button) ||
1139 Keyboard::is_edit_event (&event->button))) {
1141 /* handled by button release */
1145 switch (event->button.button) {
1147 return button_press_handler_1 (item, event, item_type);
1151 return button_press_handler_2 (item, event, item_type);
1166 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1168 framepos_t where = event_frame (event, 0, 0);
1169 AutomationTimeAxisView* atv = 0;
1171 if (pre_press_cursor) {
1172 set_canvas_cursor (pre_press_cursor);
1173 pre_press_cursor = 0;
1176 /* no action if we're recording */
1178 if (_session && _session->actively_recording()) {
1182 /* see if we're finishing a drag */
1184 bool were_dragging = false;
1185 if (_drags->active ()) {
1186 bool const r = _drags->end_grab (event);
1188 /* grab dragged, so do nothing else */
1192 were_dragging = true;
1195 update_region_layering_order_editor ();
1197 /* edit events get handled here */
1199 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1200 switch (item_type) {
1202 show_region_properties ();
1205 case TempoMarkerItem:
1206 edit_tempo_marker (item);
1209 case MeterMarkerItem:
1210 edit_meter_marker (item);
1213 case RegionViewName:
1214 if (clicked_regionview->name_active()) {
1215 return mouse_rename_region (item, event);
1219 case ControlPointItem:
1220 edit_control_point (item);
1233 /* context menu events get handled here */
1235 if (Keyboard::is_context_menu_event (&event->button)) {
1237 if (!_drags->active ()) {
1239 /* no matter which button pops up the context menu, tell the menu
1240 widget to use button 1 to drive menu selection.
1243 switch (item_type) {
1245 case FadeInHandleItem:
1247 case FadeOutHandleItem:
1248 popup_fade_context_menu (1, event->button.time, item, item_type);
1252 popup_track_context_menu (1, event->button.time, item_type, false);
1256 case RegionViewNameHighlight:
1257 case LeftFrameHandle:
1258 case RightFrameHandle:
1259 case RegionViewName:
1260 popup_track_context_menu (1, event->button.time, item_type, false);
1264 popup_track_context_menu (1, event->button.time, item_type, true);
1267 case AutomationTrackItem:
1268 popup_track_context_menu (1, event->button.time, item_type, false);
1272 case RangeMarkerBarItem:
1273 case TransportMarkerBarItem:
1274 case CdMarkerBarItem:
1277 popup_ruler_menu (where, item_type);
1281 marker_context_menu (&event->button, item);
1284 case TempoMarkerItem:
1285 tempo_or_meter_marker_context_menu (&event->button, item);
1288 case MeterMarkerItem:
1289 tempo_or_meter_marker_context_menu (&event->button, item);
1292 case CrossfadeViewItem:
1293 popup_track_context_menu (1, event->button.time, item_type, false);
1297 case ImageFrameItem:
1298 popup_imageframe_edit_menu(1, event->button.time, item, true) ;
1300 case ImageFrameTimeAxisItem:
1301 popup_imageframe_edit_menu(1, event->button.time, item, false) ;
1303 case MarkerViewItem:
1304 popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ;
1306 case MarkerTimeAxisItem:
1307 popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
1319 /* delete events get handled here */
1321 Editing::MouseMode const eff = effective_mouse_mode ();
1323 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1325 switch (item_type) {
1326 case TempoMarkerItem:
1327 remove_tempo_marker (item);
1330 case MeterMarkerItem:
1331 remove_meter_marker (item);
1335 remove_marker (*item, event);
1339 if (eff == MouseObject) {
1340 remove_clicked_region ();
1344 case ControlPointItem:
1345 if (eff == MouseGain) {
1346 remove_gain_control_point (item, event);
1348 remove_control_point (item, event);
1353 remove_midi_note (item, event);
1362 switch (event->button.button) {
1365 switch (item_type) {
1366 /* see comments in button_press_handler */
1367 case PlayheadCursorItem:
1370 case AutomationLineItem:
1371 case StartSelectionTrimItem:
1372 case EndSelectionTrimItem:
1376 if (!_dragging_playhead) {
1377 snap_to_with_modifier (where, event, 0, true);
1378 mouse_add_new_marker (where);
1382 case CdMarkerBarItem:
1383 if (!_dragging_playhead) {
1384 // if we get here then a dragged range wasn't done
1385 snap_to_with_modifier (where, event, 0, true);
1386 mouse_add_new_marker (where, true);
1391 if (!_dragging_playhead) {
1392 snap_to_with_modifier (where, event);
1393 mouse_add_new_tempo_event (where);
1398 if (!_dragging_playhead) {
1399 mouse_add_new_meter_event (pixel_to_frame (event->button.x));
1410 switch (item_type) {
1411 case AutomationTrackItem:
1412 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1414 atv->add_automation_event (item, event, where, event->button.y);
1425 switch (item_type) {
1428 /* check that we didn't drag before releasing, since
1429 its really annoying to create new control
1430 points when doing this.
1432 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1433 if (were_dragging && arv) {
1434 arv->add_gain_point_event (item, event);
1440 case AutomationTrackItem:
1441 dynamic_cast<AutomationTimeAxisView*>(clicked_axisview)->
1442 add_automation_event (item, event, where, event->button.y);
1451 set_canvas_cursor (current_canvas_cursor);
1452 if (scrubbing_direction == 0) {
1453 /* no drag, just a click */
1454 switch (item_type) {
1456 play_selected_region ();
1462 /* make sure we stop */
1463 _session->request_transport_speed (0.0);
1480 switch (item_type) {
1482 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1484 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1487 // Button2 click is unused
1500 // x_style_paste (where, 1.0);
1520 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1527 last_item_entered = item;
1529 switch (item_type) {
1530 case ControlPointItem:
1531 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1532 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1533 cp->set_visible (true);
1537 at_y = cp->get_y ();
1538 cp->i2w (at_x, at_y);
1542 fraction = 1.0 - (cp->get_y() / cp->line().height());
1544 if (is_drawable() && !_drags->active ()) {
1545 set_canvas_cursor (fader_cursor);
1548 set_verbose_canvas_cursor (cp->line().get_verbose_cursor_string (fraction), at_x, at_y);
1549 show_verbose_canvas_cursor ();
1554 if (mouse_mode == MouseGain) {
1555 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1557 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
1558 if (is_drawable()) {
1559 set_canvas_cursor (fader_cursor);
1564 case AutomationLineItem:
1565 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1567 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1569 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
1571 if (is_drawable()) {
1572 set_canvas_cursor (fader_cursor);
1577 case RegionViewNameHighlight:
1578 if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
1579 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1580 _over_region_trim_target = true;
1584 case LeftFrameHandle:
1585 case RightFrameHandle:
1586 if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
1587 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1591 case StartSelectionTrimItem:
1592 case EndSelectionTrimItem:
1595 case ImageFrameHandleStartItem:
1596 case ImageFrameHandleEndItem:
1597 case MarkerViewHandleStartItem:
1598 case MarkerViewHandleEndItem:
1601 if (is_drawable()) {
1602 set_canvas_cursor (trimmer_cursor);
1606 case PlayheadCursorItem:
1607 if (is_drawable()) {
1608 switch (_edit_point) {
1610 set_canvas_cursor (grabber_edit_point_cursor);
1613 set_canvas_cursor (grabber_cursor);
1619 case RegionViewName:
1621 /* when the name is not an active item, the entire name highlight is for trimming */
1623 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1624 if (mouse_mode == MouseObject && is_drawable()) {
1625 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1626 _over_region_trim_target = true;
1632 case AutomationTrackItem:
1633 if (is_drawable()) {
1634 Gdk::Cursor *cursor;
1635 switch (mouse_mode) {
1637 cursor = selector_cursor;
1640 cursor = zoom_in_cursor;
1643 cursor = cross_hair_cursor;
1647 set_canvas_cursor (cursor);
1649 AutomationTimeAxisView* atv;
1650 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1651 clear_entered_track = false;
1652 set_entered_track (atv);
1658 case RangeMarkerBarItem:
1659 case TransportMarkerBarItem:
1660 case CdMarkerBarItem:
1663 if (is_drawable()) {
1664 set_canvas_cursor (timebar_cursor);
1669 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1672 entered_marker = marker;
1673 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1675 case MeterMarkerItem:
1676 case TempoMarkerItem:
1677 if (is_drawable()) {
1678 set_canvas_cursor (timebar_cursor);
1682 case FadeInHandleItem:
1683 if (mouse_mode == MouseObject && !internal_editing()) {
1684 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1686 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1688 set_canvas_cursor (fade_in_cursor);
1692 case FadeOutHandleItem:
1693 if (mouse_mode == MouseObject && !internal_editing()) {
1694 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1696 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1698 set_canvas_cursor (fade_out_cursor);
1701 case FeatureLineItem:
1703 ArdourCanvas::SimpleLine *line = dynamic_cast<ArdourCanvas::SimpleLine *> (item);
1704 line->property_color_rgba() = 0xFF0000FF;
1708 if (join_object_range_button.get_active()) {
1709 set_canvas_cursor ();
1717 /* second pass to handle entered track status in a comprehensible way.
1720 switch (item_type) {
1722 case AutomationLineItem:
1723 case ControlPointItem:
1724 /* these do not affect the current entered track state */
1725 clear_entered_track = false;
1728 case AutomationTrackItem:
1729 /* handled above already */
1733 set_entered_track (0);
1741 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1751 switch (item_type) {
1752 case ControlPointItem:
1753 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1754 if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
1755 if (cp->line().npoints() > 1 && !cp->get_selected()) {
1756 cp->set_visible (false);
1760 if (is_drawable()) {
1761 set_canvas_cursor (current_canvas_cursor);
1764 hide_verbose_canvas_cursor ();
1767 case RegionViewNameHighlight:
1768 case LeftFrameHandle:
1769 case RightFrameHandle:
1770 case StartSelectionTrimItem:
1771 case EndSelectionTrimItem:
1772 case PlayheadCursorItem:
1775 case ImageFrameHandleStartItem:
1776 case ImageFrameHandleEndItem:
1777 case MarkerViewHandleStartItem:
1778 case MarkerViewHandleEndItem:
1781 _over_region_trim_target = false;
1783 if (is_drawable()) {
1784 set_canvas_cursor (current_canvas_cursor);
1789 case AutomationLineItem:
1790 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1792 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1794 line->property_fill_color_rgba() = al->get_line_color();
1796 if (is_drawable()) {
1797 set_canvas_cursor (current_canvas_cursor);
1801 case RegionViewName:
1802 /* see enter_handler() for notes */
1803 _over_region_trim_target = false;
1805 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1806 if (is_drawable() && mouse_mode == MouseObject) {
1807 set_canvas_cursor (current_canvas_cursor);
1812 case RangeMarkerBarItem:
1813 case TransportMarkerBarItem:
1814 case CdMarkerBarItem:
1818 if (is_drawable()) {
1819 set_canvas_cursor (current_canvas_cursor);
1824 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1828 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1829 location_flags_changed (loc, this);
1832 case MeterMarkerItem:
1833 case TempoMarkerItem:
1835 if (is_drawable()) {
1836 set_canvas_cursor (timebar_cursor);
1841 case FadeInHandleItem:
1842 case FadeOutHandleItem:
1843 rv = static_cast<RegionView*>(item->get_data ("regionview"));
1845 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1847 rect->property_fill_color_rgba() = rv->get_fill_color();
1848 rect->property_outline_pixels() = 0;
1851 set_canvas_cursor (current_canvas_cursor);
1854 case AutomationTrackItem:
1855 if (is_drawable()) {
1856 set_canvas_cursor (current_canvas_cursor);
1857 clear_entered_track = true;
1858 Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track));
1861 case FeatureLineItem:
1863 ArdourCanvas::SimpleLine *line = dynamic_cast<ArdourCanvas::SimpleLine *> (item);
1864 line->property_color_rgba() = (guint) ARDOUR_UI::config()->canvasvar_ZeroLine.get();;
1876 Editor::left_automation_track ()
1878 if (clear_entered_track) {
1879 set_entered_track (0);
1880 clear_entered_track = false;
1886 Editor::scrub (framepos_t frame, double current_x)
1890 if (scrubbing_direction == 0) {
1892 _session->request_locate (frame, false);
1893 _session->request_transport_speed (0.1);
1894 scrubbing_direction = 1;
1898 if (last_scrub_x > current_x) {
1900 /* pointer moved to the left */
1902 if (scrubbing_direction > 0) {
1904 /* we reversed direction to go backwards */
1907 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1911 /* still moving to the left (backwards) */
1913 scrub_reversals = 0;
1914 scrub_reverse_distance = 0;
1916 delta = 0.01 * (last_scrub_x - current_x);
1917 _session->request_transport_speed (_session->transport_speed() - delta);
1921 /* pointer moved to the right */
1923 if (scrubbing_direction < 0) {
1924 /* we reversed direction to go forward */
1927 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1930 /* still moving to the right */
1932 scrub_reversals = 0;
1933 scrub_reverse_distance = 0;
1935 delta = 0.01 * (current_x - last_scrub_x);
1936 _session->request_transport_speed (_session->transport_speed() + delta);
1940 /* if there have been more than 2 opposite motion moves detected, or one that moves
1941 back more than 10 pixels, reverse direction
1944 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1946 if (scrubbing_direction > 0) {
1947 /* was forwards, go backwards */
1948 _session->request_transport_speed (-0.1);
1949 scrubbing_direction = -1;
1951 /* was backwards, go forwards */
1952 _session->request_transport_speed (0.1);
1953 scrubbing_direction = 1;
1956 scrub_reverse_distance = 0;
1957 scrub_reversals = 0;
1961 last_scrub_x = current_x;
1965 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1967 _last_motion_y = event->motion.y;
1969 if (event->motion.is_hint) {
1972 /* We call this so that MOTION_NOTIFY events continue to be
1973 delivered to the canvas. We need to do this because we set
1974 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1975 the density of the events, at the expense of a round-trip
1976 to the server. Given that this will mostly occur on cases
1977 where DISPLAY = :0.0, and given the cost of what the motion
1978 event might do, its a good tradeoff.
1981 track_canvas->get_pointer (x, y);
1984 if (current_stepping_trackview) {
1985 /* don't keep the persistent stepped trackview if the mouse moves */
1986 current_stepping_trackview = 0;
1987 step_timeout.disconnect ();
1990 if (_session && _session->actively_recording()) {
1991 /* Sorry. no dragging stuff around while we record */
1995 JoinObjectRangeState const old = _join_object_range_state;
1996 update_join_object_range_location (event->motion.x, event->motion.y);
1997 if (_join_object_range_state != old) {
1998 set_canvas_cursor ();
2001 if (_over_region_trim_target) {
2002 set_canvas_cursor_for_region_view (event->motion.x, entered_regionview);
2005 bool handled = false;
2006 if (_drags->active ()) {
2007 handled = _drags->motion_handler (event, from_autoscroll);
2014 track_canvas_motion (event);
2019 Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* /*event*/)
2021 ControlPoint* control_point;
2023 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2024 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2028 // We shouldn't remove the first or last gain point
2029 if (control_point->line().is_last_point(*control_point) ||
2030 control_point->line().is_first_point(*control_point)) {
2034 control_point->line().remove_point (*control_point);
2038 Editor::remove_control_point (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2040 ControlPoint* control_point;
2042 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2043 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2047 control_point->line().remove_point (*control_point);
2051 Editor::edit_control_point (ArdourCanvas::Item* item)
2053 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2056 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2060 ControlPointDialog d (p);
2061 d.set_position (Gtk::WIN_POS_MOUSE);
2064 if (d.run () != RESPONSE_ACCEPT) {
2068 p->line().modify_point_y (*p, d.get_y_fraction ());
2072 Editor::edit_note (ArdourCanvas::Item* item)
2074 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2077 EditNoteDialog d (&e->region_view(), e);
2078 d.set_position (Gtk::WIN_POS_MOUSE);
2086 Editor::visible_order_range (int* low, int* high) const
2088 *low = TimeAxisView::max_order ();
2091 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2093 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2095 if (!rtv->hidden()) {
2097 if (*high < rtv->order()) {
2098 *high = rtv->order ();
2101 if (*low > rtv->order()) {
2102 *low = rtv->order ();
2109 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2111 /* Either add to or set the set the region selection, unless
2112 this is an alignment click (control used)
2115 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2116 TimeAxisView* tv = &rv.get_time_axis_view();
2117 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2119 if (rtv && rtv->is_track()) {
2120 speed = rtv->track()->speed();
2123 framepos_t where = get_preferred_edit_position();
2127 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2129 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2131 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2133 align_region (rv.region(), End, (framepos_t) (where * speed));
2137 align_region (rv.region(), Start, (framepos_t) (where * speed));
2144 Editor::show_verbose_time_cursor (framepos_t frame, double offset, double xpos, double ypos)
2147 Timecode::Time timecode;
2150 framepos_t frame_rate;
2153 if (_session == 0) {
2159 if (Profile->get_sae() || Profile->get_small_screen()) {
2160 m = ARDOUR_UI::instance()->primary_clock.mode();
2162 m = ARDOUR_UI::instance()->secondary_clock.mode();
2166 case AudioClock::BBT:
2167 _session->bbt_time (frame, bbt);
2168 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, bbt.bars, bbt.beats, bbt.ticks);
2171 case AudioClock::Timecode:
2172 _session->timecode_time (frame, timecode);
2173 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2176 case AudioClock::MinSec:
2177 /* XXX this is copied from show_verbose_duration_cursor() */
2178 frame_rate = _session->frame_rate();
2179 hours = frame / (frame_rate * 3600);
2180 frame = frame % (frame_rate * 3600);
2181 mins = frame / (frame_rate * 60);
2182 frame = frame % (frame_rate * 60);
2183 secs = (float) frame / (float) frame_rate;
2184 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
2188 snprintf (buf, sizeof(buf), "%" PRIi64, frame);
2192 if (xpos >= 0 && ypos >=0) {
2193 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2195 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset - horizontal_position(), _drags->current_pointer_y() + offset - vertical_adjustment.get_value() + canvas_timebars_vsize);
2197 show_verbose_canvas_cursor ();
2201 Editor::show_verbose_duration_cursor (framepos_t start, framepos_t end, double offset, double xpos, double ypos)
2204 Timecode::Time timecode;
2208 framepos_t distance, frame_rate;
2210 Meter meter_at_start(_session->tempo_map().meter_at(start));
2212 if (_session == 0) {
2218 if (Profile->get_sae() || Profile->get_small_screen()) {
2219 m = ARDOUR_UI::instance()->primary_clock.mode ();
2221 m = ARDOUR_UI::instance()->secondary_clock.mode ();
2225 case AudioClock::BBT:
2226 _session->bbt_time (start, sbbt);
2227 _session->bbt_time (end, ebbt);
2230 /* XXX this computation won't work well if the
2231 user makes a selection that spans any meter changes.
2234 ebbt.bars -= sbbt.bars;
2235 if (ebbt.beats >= sbbt.beats) {
2236 ebbt.beats -= sbbt.beats;
2239 ebbt.beats = int(meter_at_start.beats_per_bar()) + ebbt.beats - sbbt.beats;
2241 if (ebbt.ticks >= sbbt.ticks) {
2242 ebbt.ticks -= sbbt.ticks;
2245 ebbt.ticks = int(Meter::ticks_per_beat) + ebbt.ticks - sbbt.ticks;
2248 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, ebbt.bars, ebbt.beats, ebbt.ticks);
2251 case AudioClock::Timecode:
2252 _session->timecode_duration (end - start, timecode);
2253 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2256 case AudioClock::MinSec:
2257 /* XXX this stuff should be elsewhere.. */
2258 distance = end - start;
2259 frame_rate = _session->frame_rate();
2260 hours = distance / (frame_rate * 3600);
2261 distance = distance % (frame_rate * 3600);
2262 mins = distance / (frame_rate * 60);
2263 distance = distance % (frame_rate * 60);
2264 secs = (float) distance / (float) frame_rate;
2265 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
2269 snprintf (buf, sizeof(buf), "%" PRIi64, end - start);
2273 if (xpos >= 0 && ypos >=0) {
2274 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2277 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset, _drags->current_pointer_y() + offset);
2280 show_verbose_canvas_cursor ();
2284 Editor::collect_new_region_view (RegionView* rv)
2286 latest_regionviews.push_back (rv);
2290 Editor::collect_and_select_new_region_view (RegionView* rv)
2293 latest_regionviews.push_back (rv);
2297 Editor::cancel_selection ()
2299 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2300 (*i)->hide_selection ();
2303 selection->clear ();
2304 clicked_selection = 0;
2309 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2311 RegionView* rv = clicked_regionview;
2313 /* Choose action dependant on which button was pressed */
2314 switch (event->button.button) {
2316 begin_reversible_command (_("start point trim"));
2318 if (selection->selected (rv)) {
2319 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2320 i != selection->regions.by_layer().end(); ++i)
2323 cerr << "region view contains null region" << endl;
2326 if (!(*i)->region()->locked()) {
2327 (*i)->region()->clear_changes ();
2328 (*i)->region()->trim_front (new_bound, this);
2329 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2334 if (!rv->region()->locked()) {
2335 rv->region()->clear_changes ();
2336 rv->region()->trim_front (new_bound, this);
2337 _session->add_command(new StatefulDiffCommand (rv->region()));
2341 commit_reversible_command();
2345 begin_reversible_command (_("End point trim"));
2347 if (selection->selected (rv)) {
2349 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2351 if (!(*i)->region()->locked()) {
2352 (*i)->region()->clear_changes();
2353 (*i)->region()->trim_end (new_bound, this);
2354 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2360 if (!rv->region()->locked()) {
2361 rv->region()->clear_changes ();
2362 rv->region()->trim_end (new_bound, this);
2363 _session->add_command (new StatefulDiffCommand (rv->region()));
2367 commit_reversible_command();
2376 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2381 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2382 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2386 Location* location = find_location_from_marker (marker, is_start);
2387 location->set_hidden (true, this);
2392 Editor::reposition_zoom_rect (framepos_t start, framepos_t end)
2394 double x1 = frame_to_pixel (start);
2395 double x2 = frame_to_pixel (end);
2396 double y2 = full_canvas_height - 1.0;
2398 zoom_rect->property_x1() = x1;
2399 zoom_rect->property_y1() = 1.0;
2400 zoom_rect->property_x2() = x2;
2401 zoom_rect->property_y2() = y2;
2406 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2408 using namespace Gtkmm2ext;
2410 ArdourPrompter prompter (false);
2412 prompter.set_prompt (_("Name for region:"));
2413 prompter.set_initial_text (clicked_regionview->region()->name());
2414 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2415 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2416 prompter.show_all ();
2417 switch (prompter.run ()) {
2418 case Gtk::RESPONSE_ACCEPT:
2420 prompter.get_result(str);
2422 clicked_regionview->region()->set_name (str);
2431 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2433 /* no brushing without a useful snap setting */
2435 switch (_snap_mode) {
2437 return; /* can't work because it allows region to be placed anywhere */
2442 switch (_snap_type) {
2450 /* don't brush a copy over the original */
2452 if (pos == rv->region()->position()) {
2456 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2458 if (rtv == 0 || !rtv->is_track()) {
2462 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2463 double speed = rtv->track()->speed();
2465 playlist->clear_changes ();
2466 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region()));
2467 playlist->add_region (new_region, (framepos_t) (pos * speed));
2468 _session->add_command (new StatefulDiffCommand (playlist));
2470 // playlist is frozen, so we have to update manually XXX this is disgusting
2472 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2476 Editor::track_height_step_timeout ()
2478 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2479 current_stepping_trackview = 0;
2486 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2488 assert (region_view);
2490 if (!region_view->region()->playlist()) {
2494 _region_motion_group->raise_to_top ();
2496 if (Config->get_edit_mode() == Splice) {
2497 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2499 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2500 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false));
2503 /* sync the canvas to what we think is its current state */
2504 update_canvas_now();
2508 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2510 assert (region_view);
2512 if (!region_view->region()->playlist()) {
2516 _region_motion_group->raise_to_top ();
2518 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2519 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true));
2523 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2525 assert (region_view);
2527 if (!region_view->region()->playlist()) {
2531 if (Config->get_edit_mode() == Splice) {
2535 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2536 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), true, false));
2538 begin_reversible_command (_("Drag region brush"));
2541 /** Start a grab where a time range is selected, track(s) are selected, and the
2542 * user clicks and drags a region with a modifier in order to create a new region containing
2543 * the section of the clicked region that lies within the time range.
2546 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2548 if (clicked_regionview == 0) {
2552 /* lets try to create new Region for the selection */
2554 vector<boost::shared_ptr<Region> > new_regions;
2555 create_region_from_selection (new_regions);
2557 if (new_regions.empty()) {
2561 /* XXX fix me one day to use all new regions */
2563 boost::shared_ptr<Region> region (new_regions.front());
2565 /* add it to the current stream/playlist.
2567 tricky: the streamview for the track will add a new regionview. we will
2568 catch the signal it sends when it creates the regionview to
2569 set the regionview we want to then drag.
2572 latest_regionviews.clear();
2573 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2575 /* A selection grab currently creates two undo/redo operations, one for
2576 creating the new region and another for moving it.
2579 begin_reversible_command (_("selection grab"));
2581 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2583 playlist->clear_changes ();
2584 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2585 _session->add_command(new StatefulDiffCommand (playlist));
2587 commit_reversible_command ();
2591 if (latest_regionviews.empty()) {
2592 /* something went wrong */
2596 /* we need to deselect all other regionviews, and select this one
2597 i'm ignoring undo stuff, because the region creation will take care of it
2599 selection->set (latest_regionviews);
2601 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2607 if (_drags->active ()) {
2610 selection->clear ();
2615 Editor::set_internal_edit (bool yn)
2617 _internal_editing = yn;
2620 mouse_select_button.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
2621 mouse_select_button.get_image ()->show ();
2622 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Draw/Edit MIDI Notes"));
2623 set_canvas_cursor ();
2625 /* deselect everything to avoid confusion when e.g. we can't now cut a previously selected
2626 region because cut means "cut note" rather than "cut region".
2628 selection->clear ();
2632 mouse_select_button.set_image (*(manage (new Image (::get_icon("tool_range")))));
2633 mouse_select_button.get_image ()->show ();
2634 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Select/Move Ranges"));
2635 mouse_mode_toggled (mouse_mode); // sets cursor
2639 /** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
2640 * used by the `join object/range' tool mode.
2643 Editor::update_join_object_range_location (double x, double y)
2645 /* XXX: actually, this decides based on whether the mouse is in the top or bottom half of a RouteTimeAxisView;
2646 entered_{track,regionview} is not always setup (e.g. if the mouse is over a TimeSelection), and to get a Region
2647 that we're over requires searching the playlist.
2650 if (join_object_range_button.get_active() == false || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
2651 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2655 if (mouse_mode == MouseObject) {
2656 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2657 } else if (mouse_mode == MouseRange) {
2658 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2661 /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
2662 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y + vertical_adjustment.get_value() - canvas_timebars_vsize);
2666 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2671 rtv->canvas_display()->w2i (cx, cy);
2673 double const c = cy / rtv->view()->child_height();
2675 double const f = modf (c, &d);
2677 _join_object_range_state = f < 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2683 Editor::effective_mouse_mode () const
2685 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2687 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2695 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2697 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2700 e->region_view().delete_note (e->note ());
2704 Editor::set_canvas_cursor_for_region_view (double x, RegionView* rv)
2706 ArdourCanvas::Group* g = rv->get_canvas_group ();
2707 ArdourCanvas::Group* p = g->get_parent_group ();
2709 /* Compute x in region view parent coordinates */
2713 double x1, x2, y1, y2;
2714 g->get_bounds (x1, y1, x2, y2);
2716 /* Halfway across the region */
2717 double const h = (x1 + x2) / 2;
2719 Trimmable::CanTrim ct = rv->region()->can_trim ();
2721 if (ct & Trimmable::FrontTrimEarlier) {
2722 set_canvas_cursor (left_side_trim_cursor);
2724 set_canvas_cursor (left_side_trim_right_only_cursor);
2727 if (ct & Trimmable::EndTrimLater) {
2728 set_canvas_cursor (right_side_trim_cursor);
2730 set_canvas_cursor (right_side_trim_left_only_cursor);