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);
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);
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, where);
1256 case RegionViewNameHighlight:
1257 case LeftFrameHandle:
1258 case RightFrameHandle:
1259 case RegionViewName:
1260 popup_track_context_menu (1, event->button.time, item_type, false, where);
1264 popup_track_context_menu (1, event->button.time, item_type, true, where);
1267 case AutomationTrackItem:
1268 popup_track_context_menu (1, event->button.time, item_type, false, where);
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, where);
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()) {
1579 set_canvas_cursor (trimmer_cursor);
1583 case LeftFrameHandle:
1584 if (is_drawable() && mouse_mode == MouseObject && !internal_editing()) {
1585 if (entered_regionview) {
1586 Trimmable::CanTrim ct = entered_regionview->region()->can_trim();
1587 if ((ct & Trimmable::EndTrimEarlier) || (ct & Trimmable::EndTrimLater)) {
1588 set_canvas_cursor (left_side_trim_cursor);
1594 case RightFrameHandle:
1595 if (is_drawable() && mouse_mode == MouseObject && !internal_editing()) {
1596 if (entered_regionview) {
1597 Trimmable::CanTrim ct = entered_regionview->region()->can_trim();
1598 if ((ct & Trimmable::FrontTrimEarlier) || (ct & Trimmable::FrontTrimLater)) {
1599 set_canvas_cursor (right_side_trim_cursor);
1605 case StartSelectionTrimItem:
1606 case EndSelectionTrimItem:
1609 case ImageFrameHandleStartItem:
1610 case ImageFrameHandleEndItem:
1611 case MarkerViewHandleStartItem:
1612 case MarkerViewHandleEndItem:
1615 if (is_drawable()) {
1616 set_canvas_cursor (trimmer_cursor);
1620 case PlayheadCursorItem:
1621 if (is_drawable()) {
1622 switch (_edit_point) {
1624 set_canvas_cursor (grabber_edit_point_cursor);
1627 set_canvas_cursor (grabber_cursor);
1633 case RegionViewName:
1635 /* when the name is not an active item, the entire name highlight is for trimming */
1637 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1638 if (mouse_mode == MouseObject && is_drawable()) {
1639 set_canvas_cursor (trimmer_cursor);
1645 case AutomationTrackItem:
1646 if (is_drawable()) {
1647 Gdk::Cursor *cursor;
1648 switch (mouse_mode) {
1650 cursor = selector_cursor;
1653 cursor = zoom_in_cursor;
1656 cursor = cross_hair_cursor;
1660 set_canvas_cursor (cursor);
1662 AutomationTimeAxisView* atv;
1663 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1664 clear_entered_track = false;
1665 set_entered_track (atv);
1671 case RangeMarkerBarItem:
1672 case TransportMarkerBarItem:
1673 case CdMarkerBarItem:
1676 if (is_drawable()) {
1677 set_canvas_cursor (timebar_cursor);
1682 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1685 entered_marker = marker;
1686 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1688 case MeterMarkerItem:
1689 case TempoMarkerItem:
1690 if (is_drawable()) {
1691 set_canvas_cursor (timebar_cursor);
1695 case FadeInHandleItem:
1696 if (mouse_mode == MouseObject && !internal_editing()) {
1697 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1699 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1701 set_canvas_cursor (fade_in_cursor);
1705 case FadeOutHandleItem:
1706 if (mouse_mode == MouseObject && !internal_editing()) {
1707 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1709 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1711 set_canvas_cursor (fade_out_cursor);
1714 case FeatureLineItem:
1716 ArdourCanvas::SimpleLine *line = dynamic_cast<ArdourCanvas::SimpleLine *> (item);
1717 line->property_color_rgba() = 0xFF0000FF;
1721 if (join_object_range_button.get_active()) {
1722 set_canvas_cursor ();
1730 /* second pass to handle entered track status in a comprehensible way.
1733 switch (item_type) {
1735 case AutomationLineItem:
1736 case ControlPointItem:
1737 /* these do not affect the current entered track state */
1738 clear_entered_track = false;
1741 case AutomationTrackItem:
1742 /* handled above already */
1746 set_entered_track (0);
1754 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1764 switch (item_type) {
1765 case ControlPointItem:
1766 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1767 if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
1768 if (cp->line().npoints() > 1 && !cp->get_selected()) {
1769 cp->set_visible (false);
1773 if (is_drawable()) {
1774 set_canvas_cursor (current_canvas_cursor);
1777 hide_verbose_canvas_cursor ();
1780 case RegionViewNameHighlight:
1781 case LeftFrameHandle:
1782 case RightFrameHandle:
1783 case StartSelectionTrimItem:
1784 case EndSelectionTrimItem:
1785 case PlayheadCursorItem:
1788 case ImageFrameHandleStartItem:
1789 case ImageFrameHandleEndItem:
1790 case MarkerViewHandleStartItem:
1791 case MarkerViewHandleEndItem:
1794 if (is_drawable()) {
1795 set_canvas_cursor (current_canvas_cursor);
1800 case AutomationLineItem:
1801 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1803 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1805 line->property_fill_color_rgba() = al->get_line_color();
1807 if (is_drawable()) {
1808 set_canvas_cursor (current_canvas_cursor);
1812 case RegionViewName:
1813 /* see enter_handler() for notes */
1814 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1815 if (is_drawable() && mouse_mode == MouseObject) {
1816 set_canvas_cursor (current_canvas_cursor);
1821 case RangeMarkerBarItem:
1822 case TransportMarkerBarItem:
1823 case CdMarkerBarItem:
1827 if (is_drawable()) {
1828 set_canvas_cursor (current_canvas_cursor);
1833 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1837 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1838 location_flags_changed (loc, this);
1841 case MeterMarkerItem:
1842 case TempoMarkerItem:
1844 if (is_drawable()) {
1845 set_canvas_cursor (timebar_cursor);
1850 case FadeInHandleItem:
1851 case FadeOutHandleItem:
1852 rv = static_cast<RegionView*>(item->get_data ("regionview"));
1854 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1856 rect->property_fill_color_rgba() = rv->get_fill_color();
1857 rect->property_outline_pixels() = 0;
1860 set_canvas_cursor (current_canvas_cursor);
1863 case AutomationTrackItem:
1864 if (is_drawable()) {
1865 set_canvas_cursor (current_canvas_cursor);
1866 clear_entered_track = true;
1867 Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track));
1870 case FeatureLineItem:
1872 ArdourCanvas::SimpleLine *line = dynamic_cast<ArdourCanvas::SimpleLine *> (item);
1873 line->property_color_rgba() = (guint) ARDOUR_UI::config()->canvasvar_ZeroLine.get();;
1885 Editor::left_automation_track ()
1887 if (clear_entered_track) {
1888 set_entered_track (0);
1889 clear_entered_track = false;
1895 Editor::scrub (framepos_t frame, double current_x)
1899 if (scrubbing_direction == 0) {
1901 _session->request_locate (frame, false);
1902 _session->request_transport_speed (0.1);
1903 scrubbing_direction = 1;
1907 if (last_scrub_x > current_x) {
1909 /* pointer moved to the left */
1911 if (scrubbing_direction > 0) {
1913 /* we reversed direction to go backwards */
1916 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1920 /* still moving to the left (backwards) */
1922 scrub_reversals = 0;
1923 scrub_reverse_distance = 0;
1925 delta = 0.01 * (last_scrub_x - current_x);
1926 _session->request_transport_speed (_session->transport_speed() - delta);
1930 /* pointer moved to the right */
1932 if (scrubbing_direction < 0) {
1933 /* we reversed direction to go forward */
1936 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1939 /* still moving to the right */
1941 scrub_reversals = 0;
1942 scrub_reverse_distance = 0;
1944 delta = 0.01 * (current_x - last_scrub_x);
1945 _session->request_transport_speed (_session->transport_speed() + delta);
1949 /* if there have been more than 2 opposite motion moves detected, or one that moves
1950 back more than 10 pixels, reverse direction
1953 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1955 if (scrubbing_direction > 0) {
1956 /* was forwards, go backwards */
1957 _session->request_transport_speed (-0.1);
1958 scrubbing_direction = -1;
1960 /* was backwards, go forwards */
1961 _session->request_transport_speed (0.1);
1962 scrubbing_direction = 1;
1965 scrub_reverse_distance = 0;
1966 scrub_reversals = 0;
1970 last_scrub_x = current_x;
1974 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1976 _last_motion_y = event->motion.y;
1978 if (event->motion.is_hint) {
1981 /* We call this so that MOTION_NOTIFY events continue to be
1982 delivered to the canvas. We need to do this because we set
1983 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1984 the density of the events, at the expense of a round-trip
1985 to the server. Given that this will mostly occur on cases
1986 where DISPLAY = :0.0, and given the cost of what the motion
1987 event might do, its a good tradeoff.
1990 track_canvas->get_pointer (x, y);
1993 if (current_stepping_trackview) {
1994 /* don't keep the persistent stepped trackview if the mouse moves */
1995 current_stepping_trackview = 0;
1996 step_timeout.disconnect ();
1999 if (_session && _session->actively_recording()) {
2000 /* Sorry. no dragging stuff around while we record */
2004 JoinObjectRangeState const old = _join_object_range_state;
2005 update_join_object_range_location (event->motion.x, event->motion.y);
2006 if (_join_object_range_state != old) {
2007 set_canvas_cursor ();
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::single_contents_trim (RegionView& rv, framepos_t frame_delta, bool left_direction, bool swap_direction)
2316 boost::shared_ptr<Region> region (rv.region());
2318 if (region->locked()) {
2322 framepos_t new_bound;
2325 TimeAxisView* tvp = clicked_axisview;
2326 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2328 if (tv && tv->is_track()) {
2329 speed = tv->track()->speed();
2332 if (left_direction) {
2333 if (swap_direction) {
2334 new_bound = (framepos_t) (region->position()/speed) + frame_delta;
2336 new_bound = (framepos_t) (region->position()/speed) - frame_delta;
2339 if (swap_direction) {
2340 new_bound = (framepos_t) (region->position()/speed) - frame_delta;
2342 new_bound = (framepos_t) (region->position()/speed) + frame_delta;
2346 region->trim_start ((framepos_t) (new_bound * speed), this);
2347 rv.region_changed (PropertyChange (ARDOUR::Properties::start));
2351 Editor::single_start_trim (RegionView& rv, framepos_t new_bound, bool no_overlap)
2353 boost::shared_ptr<Region> region (rv.region());
2355 if (region->locked()) {
2360 TimeAxisView* tvp = clicked_axisview;
2361 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2363 if (tv && tv->is_track()) {
2364 speed = tv->track()->speed();
2367 framepos_t pre_trim_first_frame = region->first_frame();
2369 region->trim_front ((framepos_t) (new_bound * speed), this);
2372 //Get the next region on the left of this region and shrink/expand it.
2373 boost::shared_ptr<Playlist> playlist (region->playlist());
2374 boost::shared_ptr<Region> region_left = playlist->find_next_region (pre_trim_first_frame, End, 0);
2376 bool regions_touching = false;
2378 if (region_left != 0 && (pre_trim_first_frame == region_left->last_frame() + 1)){
2379 regions_touching = true;
2382 //Only trim region on the left if the first frame has gone beyond the left region's last frame.
2383 if (region_left != 0 &&
2384 (region_left->last_frame() > region->first_frame() || regions_touching))
2386 region_left->trim_end(region->first_frame() - 1, this);
2390 rv.region_changed (ARDOUR::bounds_change);
2394 Editor::single_end_trim (RegionView& rv, framepos_t new_bound, bool no_overlap)
2396 boost::shared_ptr<Region> region (rv.region());
2398 if (region->locked()) {
2403 TimeAxisView* tvp = clicked_axisview;
2404 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2406 if (tv && tv->is_track()) {
2407 speed = tv->track()->speed();
2410 framepos_t pre_trim_last_frame = region->last_frame();
2412 region->trim_end ((framepos_t) (new_bound * speed), this);
2415 //Get the next region on the right of this region and shrink/expand it.
2416 boost::shared_ptr<Playlist> playlist (region->playlist());
2417 boost::shared_ptr<Region> region_right = playlist->find_next_region (pre_trim_last_frame, Start, 1);
2419 bool regions_touching = false;
2421 if (region_right != 0 && (pre_trim_last_frame == region_right->first_frame() - 1)) {
2422 regions_touching = true;
2425 //Only trim region on the right if the last frame has gone beyond the right region's first frame.
2426 if (region_right != 0 &&
2427 (region_right->first_frame() < region->last_frame() || regions_touching))
2429 region_right->trim_front(region->last_frame() + 1, this);
2432 rv.region_changed (ARDOUR::bounds_change);
2435 rv.region_changed (PropertyChange (ARDOUR::Properties::length));
2441 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2443 RegionView* rv = clicked_regionview;
2445 /* Choose action dependant on which button was pressed */
2446 switch (event->button.button) {
2448 begin_reversible_command (_("start point trim"));
2450 if (selection->selected (rv)) {
2451 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2452 i != selection->regions.by_layer().end(); ++i)
2455 cerr << "region view contains null region" << endl;
2458 if (!(*i)->region()->locked()) {
2459 (*i)->region()->clear_changes ();
2460 (*i)->region()->trim_front (new_bound, this);
2461 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2466 if (!rv->region()->locked()) {
2467 rv->region()->clear_changes ();
2468 rv->region()->trim_front (new_bound, this);
2469 _session->add_command(new StatefulDiffCommand (rv->region()));
2473 commit_reversible_command();
2477 begin_reversible_command (_("End point trim"));
2479 if (selection->selected (rv)) {
2481 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2483 if (!(*i)->region()->locked()) {
2484 (*i)->region()->clear_changes();
2485 (*i)->region()->trim_end (new_bound, this);
2486 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2492 if (!rv->region()->locked()) {
2493 rv->region()->clear_changes ();
2494 rv->region()->trim_end (new_bound, this);
2495 _session->add_command (new StatefulDiffCommand (rv->region()));
2499 commit_reversible_command();
2508 Editor::thaw_region_after_trim (RegionView& rv)
2510 boost::shared_ptr<Region> region (rv.region());
2512 if (region->locked()) {
2516 region->resume_property_changes ();
2518 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(&rv);
2521 arv->unhide_envelope ();
2526 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2531 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2532 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2536 Location* location = find_location_from_marker (marker, is_start);
2537 location->set_hidden (true, this);
2542 Editor::reposition_zoom_rect (framepos_t start, framepos_t end)
2544 double x1 = frame_to_pixel (start);
2545 double x2 = frame_to_pixel (end);
2546 double y2 = full_canvas_height - 1.0;
2548 zoom_rect->property_x1() = x1;
2549 zoom_rect->property_y1() = 1.0;
2550 zoom_rect->property_x2() = x2;
2551 zoom_rect->property_y2() = y2;
2556 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2558 using namespace Gtkmm2ext;
2560 ArdourPrompter prompter (false);
2562 prompter.set_prompt (_("Name for region:"));
2563 prompter.set_initial_text (clicked_regionview->region()->name());
2564 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2565 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2566 prompter.show_all ();
2567 switch (prompter.run ()) {
2568 case Gtk::RESPONSE_ACCEPT:
2570 prompter.get_result(str);
2572 clicked_regionview->region()->set_name (str);
2581 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2583 /* no brushing without a useful snap setting */
2585 switch (_snap_mode) {
2587 return; /* can't work because it allows region to be placed anywhere */
2592 switch (_snap_type) {
2600 /* don't brush a copy over the original */
2602 if (pos == rv->region()->position()) {
2606 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2608 if (rtv == 0 || !rtv->is_track()) {
2612 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2613 double speed = rtv->track()->speed();
2615 playlist->clear_changes ();
2616 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region()));
2617 playlist->add_region (new_region, (framepos_t) (pos * speed));
2618 _session->add_command (new StatefulDiffCommand (playlist));
2620 // playlist is frozen, so we have to update manually XXX this is disgusting
2622 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2626 Editor::track_height_step_timeout ()
2628 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2629 current_stepping_trackview = 0;
2636 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2638 assert (region_view);
2640 if (!region_view->region()->playlist()) {
2644 _region_motion_group->raise_to_top ();
2646 if (Config->get_edit_mode() == Splice) {
2647 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2649 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2650 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false));
2653 /* sync the canvas to what we think is its current state */
2654 update_canvas_now();
2658 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2660 assert (region_view);
2662 if (!region_view->region()->playlist()) {
2666 _region_motion_group->raise_to_top ();
2668 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2669 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true));
2673 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2675 assert (region_view);
2677 if (!region_view->region()->playlist()) {
2681 if (Config->get_edit_mode() == Splice) {
2685 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2686 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), true, false));
2688 begin_reversible_command (_("Drag region brush"));
2691 /** Start a grab where a time range is selected, track(s) are selected, and the
2692 * user clicks and drags a region with a modifier in order to create a new region containing
2693 * the section of the clicked region that lies within the time range.
2696 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2698 if (clicked_regionview == 0) {
2702 /* lets try to create new Region for the selection */
2704 vector<boost::shared_ptr<Region> > new_regions;
2705 create_region_from_selection (new_regions);
2707 if (new_regions.empty()) {
2711 /* XXX fix me one day to use all new regions */
2713 boost::shared_ptr<Region> region (new_regions.front());
2715 /* add it to the current stream/playlist.
2717 tricky: the streamview for the track will add a new regionview. we will
2718 catch the signal it sends when it creates the regionview to
2719 set the regionview we want to then drag.
2722 latest_regionviews.clear();
2723 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2725 /* A selection grab currently creates two undo/redo operations, one for
2726 creating the new region and another for moving it.
2729 begin_reversible_command (_("selection grab"));
2731 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2733 playlist->clear_changes ();
2734 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2735 _session->add_command(new StatefulDiffCommand (playlist));
2737 commit_reversible_command ();
2741 if (latest_regionviews.empty()) {
2742 /* something went wrong */
2746 /* we need to deselect all other regionviews, and select this one
2747 i'm ignoring undo stuff, because the region creation will take care of it
2749 selection->set (latest_regionviews);
2751 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2757 if (_drags->active ()) {
2760 selection->clear ();
2765 Editor::set_internal_edit (bool yn)
2767 _internal_editing = yn;
2770 mouse_select_button.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
2771 mouse_select_button.get_image ()->show ();
2772 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Draw/Edit MIDI Notes"));
2773 set_canvas_cursor ();
2775 /* deselect everything to avoid confusion when e.g. we can't now cut a previously selected
2776 region because cut means "cut note" rather than "cut region".
2778 selection->clear ();
2782 mouse_select_button.set_image (*(manage (new Image (::get_icon("tool_range")))));
2783 mouse_select_button.get_image ()->show ();
2784 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Select/Move Ranges"));
2785 mouse_mode_toggled (mouse_mode); // sets cursor
2789 /** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
2790 * used by the `join object/range' tool mode.
2793 Editor::update_join_object_range_location (double x, double y)
2795 /* XXX: actually, this decides based on whether the mouse is in the top or bottom half of a RouteTimeAxisView;
2796 entered_{track,regionview} is not always setup (e.g. if the mouse is over a TimeSelection), and to get a Region
2797 that we're over requires searching the playlist.
2800 if (join_object_range_button.get_active() == false || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
2801 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2805 if (mouse_mode == MouseObject) {
2806 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2807 } else if (mouse_mode == MouseRange) {
2808 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2811 /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
2812 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y + vertical_adjustment.get_value() - canvas_timebars_vsize);
2816 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2821 rtv->canvas_display()->w2i (cx, cy);
2823 double const c = cy / rtv->view()->child_height();
2825 double const f = modf (c, &d);
2827 _join_object_range_state = f < 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2833 Editor::effective_mouse_mode () const
2835 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2837 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2845 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2847 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2850 e->region_view().delete_note (e->note ());