Make MIDI automation tracks behave the same as MIDI
[ardour2.git] / gtk2_ardour / editor_mouse.cc
blob30e117fb590019c5470df16f2567c12876e8cd7a
1 /*
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.
20 #include <cassert>
21 #include <cstdlib>
22 #include <stdint.h>
23 #include <cmath>
24 #include <set>
25 #include <string>
26 #include <algorithm>
28 #include "pbd/error.h"
29 #include "pbd/enumwriter.h"
30 #include "pbd/memento_command.h"
31 #include "pbd/basename.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "gtkmm2ext/bindings.h"
35 #include "gtkmm2ext/utils.h"
36 #include "gtkmm2ext/tearoff.h"
38 #include "ardour_ui.h"
39 #include "actions.h"
40 #include "canvas-note.h"
41 #include "editor.h"
42 #include "time_axis_view.h"
43 #include "audio_time_axis.h"
44 #include "audio_region_view.h"
45 #include "midi_region_view.h"
46 #include "marker.h"
47 #include "streamview.h"
48 #include "region_gain_line.h"
49 #include "automation_time_axis.h"
50 #include "control_point.h"
51 #include "prompter.h"
52 #include "utils.h"
53 #include "selection.h"
54 #include "keyboard.h"
55 #include "editing.h"
56 #include "rgb_macros.h"
57 #include "control_point_dialog.h"
58 #include "editor_drag.h"
59 #include "automation_region_view.h"
60 #include "edit_note_dialog.h"
61 #include "mouse_cursors.h"
62 #include "editor_cursors.h"
63 #include "verbose_cursor.h"
65 #include "ardour/types.h"
66 #include "ardour/profile.h"
67 #include "ardour/route.h"
68 #include "ardour/audio_track.h"
69 #include "ardour/audio_diskstream.h"
70 #include "ardour/midi_diskstream.h"
71 #include "ardour/playlist.h"
72 #include "ardour/audioplaylist.h"
73 #include "ardour/audioregion.h"
74 #include "ardour/midi_region.h"
75 #include "ardour/dB.h"
76 #include "ardour/utils.h"
77 #include "ardour/region_factory.h"
78 #include "ardour/source_factory.h"
79 #include "ardour/session.h"
80 #include "ardour/operations.h"
82 #include <bitset>
84 #include "i18n.h"
86 using namespace std;
87 using namespace ARDOUR;
88 using namespace PBD;
89 using namespace Gtk;
90 using namespace Editing;
91 using Gtkmm2ext::Keyboard;
93 bool
94 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
96 int x, y;
97 double wx, wy;
98 Gdk::ModifierType mask;
99 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
100 Glib::RefPtr<const Gdk::Window> pointer_window;
102 if (!canvas_window) {
103 return false;
106 pointer_window = canvas_window->get_pointer (x, y, mask);
108 if (pointer_window == track_canvas->get_bin_window()) {
109 wx = x;
110 wy = y;
111 in_track_canvas = true;
113 } else {
114 in_track_canvas = false;
115 return false;
118 GdkEvent event;
119 event.type = GDK_BUTTON_RELEASE;
120 event.button.x = wx;
121 event.button.y = wy;
123 where = event_frame (&event, 0, 0);
124 return true;
127 framepos_t
128 Editor::event_frame (GdkEvent const * event, double* pcx, double* pcy) const
130 double cx, cy;
132 if (pcx == 0) {
133 pcx = &cx;
135 if (pcy == 0) {
136 pcy = &cy;
139 *pcx = 0;
140 *pcy = 0;
142 switch (event->type) {
143 case GDK_BUTTON_RELEASE:
144 case GDK_BUTTON_PRESS:
145 case GDK_2BUTTON_PRESS:
146 case GDK_3BUTTON_PRESS:
147 *pcx = event->button.x;
148 *pcy = event->button.y;
149 _trackview_group->w2i(*pcx, *pcy);
150 break;
151 case GDK_MOTION_NOTIFY:
152 *pcx = event->motion.x;
153 *pcy = event->motion.y;
154 _trackview_group->w2i(*pcx, *pcy);
155 break;
156 case GDK_ENTER_NOTIFY:
157 case GDK_LEAVE_NOTIFY:
158 track_canvas->w2c(event->crossing.x, event->crossing.y, *pcx, *pcy);
159 break;
160 case GDK_KEY_PRESS:
161 case GDK_KEY_RELEASE:
162 // track_canvas->w2c(event->key.x, event->key.y, *pcx, *pcy);
163 break;
164 default:
165 warning << string_compose (_("Editor::event_frame() used on unhandled event type %1"), event->type) << endmsg;
166 break;
169 /* note that pixel_to_frame() never returns less than zero, so even if the pixel
170 position is negative (as can be the case with motion events in particular),
171 the frame location is always positive.
174 return pixel_to_frame (*pcx);
177 Gdk::Cursor*
178 Editor::which_grabber_cursor ()
180 Gdk::Cursor* c = _cursors->grabber;
182 if (_internal_editing) {
183 switch (mouse_mode) {
184 case MouseRange:
185 c = _cursors->midi_pencil;
186 break;
188 case MouseObject:
189 c = _cursors->grabber_note;
190 break;
192 case MouseTimeFX:
193 c = _cursors->midi_resize;
194 break;
196 default:
197 break;
200 } else {
202 switch (_edit_point) {
203 case EditAtMouse:
204 c = _cursors->grabber_edit_point;
205 break;
206 default:
207 boost::shared_ptr<Movable> m = _movable.lock();
208 if (m && m->locked()) {
209 c = _cursors->speaker;
211 break;
215 return c;
218 void
219 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
221 boost::shared_ptr<Trimmable> st = _trimmable.lock();
223 if (!st || st == t) {
224 _trimmable = t;
225 set_canvas_cursor ();
229 void
230 Editor::set_current_movable (boost::shared_ptr<Movable> m)
232 boost::shared_ptr<Movable> sm = _movable.lock();
234 if (!sm || sm != m) {
235 _movable = m;
236 set_canvas_cursor ();
240 void
241 Editor::set_canvas_cursor ()
243 if (_internal_editing) {
245 switch (mouse_mode) {
246 case MouseRange:
247 current_canvas_cursor = _cursors->midi_pencil;
248 break;
250 case MouseObject:
251 current_canvas_cursor = which_grabber_cursor();
252 break;
254 case MouseTimeFX:
255 current_canvas_cursor = _cursors->midi_resize;
256 break;
258 default:
259 return;
262 } else {
264 switch (mouse_mode) {
265 case MouseRange:
266 current_canvas_cursor = _cursors->selector;
267 break;
269 case MouseObject:
270 current_canvas_cursor = which_grabber_cursor();
271 break;
273 case MouseGain:
274 current_canvas_cursor = _cursors->cross_hair;
275 break;
277 case MouseZoom:
278 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
279 current_canvas_cursor = _cursors->zoom_out;
280 } else {
281 current_canvas_cursor = _cursors->zoom_in;
283 break;
285 case MouseTimeFX:
286 current_canvas_cursor = _cursors->time_fx; // just use playhead
287 break;
289 case MouseAudition:
290 current_canvas_cursor = _cursors->speaker;
291 break;
295 switch (_join_object_range_state) {
296 case JOIN_OBJECT_RANGE_NONE:
297 break;
298 case JOIN_OBJECT_RANGE_OBJECT:
299 current_canvas_cursor = which_grabber_cursor ();
300 break;
301 case JOIN_OBJECT_RANGE_RANGE:
302 current_canvas_cursor = _cursors->selector;
303 break;
306 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
307 if (join_object_range_button.get_active() && last_item_entered) {
308 if (last_item_entered->property_parent() && (*last_item_entered->property_parent()).get_data (X_("timeselection"))) {
309 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y + vertical_adjustment.get_value() - canvas_timebars_vsize);
310 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
311 current_canvas_cursor = _cursors->up_down;
316 set_canvas_cursor (current_canvas_cursor, true);
319 void
320 Editor::set_mouse_mode (MouseMode m, bool force)
322 if (_drags->active ()) {
323 return;
326 if (!force && m == mouse_mode) {
327 return;
330 Glib::RefPtr<Action> act;
332 switch (m) {
333 case MouseRange:
334 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
335 break;
337 case MouseObject:
338 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
339 break;
341 case MouseGain:
342 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-gain"));
343 break;
345 case MouseZoom:
346 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-zoom"));
347 break;
349 case MouseTimeFX:
350 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
351 break;
353 case MouseAudition:
354 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
355 break;
358 assert (act);
360 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
361 assert (tact);
363 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
364 tact->set_active (false);
365 tact->set_active (true);
367 MouseModeChanged (); /* EMIT SIGNAL */
370 void
371 Editor::mouse_mode_toggled (MouseMode m)
373 mouse_mode = m;
375 instant_save ();
377 if (!internal_editing()) {
378 if (mouse_mode != MouseRange && _join_object_range_state == JOIN_OBJECT_RANGE_NONE) {
380 /* in all modes except range and joined object/range, hide the range selection,
381 show the object (region) selection.
384 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
385 (*i)->hide_selection ();
388 } else {
391 in range or object/range mode, show the range selection.
394 for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
395 (*i)->show_selection (selection->time);
400 set_canvas_cursor ();
402 MouseModeChanged (); /* EMIT SIGNAL */
405 void
406 Editor::step_mouse_mode (bool next)
408 switch (current_mouse_mode()) {
409 case MouseObject:
410 if (next) {
411 if (Profile->get_sae()) {
412 set_mouse_mode (MouseZoom);
413 } else {
414 set_mouse_mode (MouseRange);
416 } else {
417 set_mouse_mode (MouseTimeFX);
419 break;
421 case MouseRange:
422 if (next) set_mouse_mode (MouseZoom);
423 else set_mouse_mode (MouseObject);
424 break;
426 case MouseZoom:
427 if (next) {
428 if (Profile->get_sae()) {
429 set_mouse_mode (MouseTimeFX);
430 } else {
431 set_mouse_mode (MouseGain);
433 } else {
434 if (Profile->get_sae()) {
435 set_mouse_mode (MouseObject);
436 } else {
437 set_mouse_mode (MouseRange);
440 break;
442 case MouseGain:
443 if (next) set_mouse_mode (MouseTimeFX);
444 else set_mouse_mode (MouseZoom);
445 break;
447 case MouseTimeFX:
448 if (next) {
449 set_mouse_mode (MouseAudition);
450 } else {
451 if (Profile->get_sae()) {
452 set_mouse_mode (MouseZoom);
453 } else {
454 set_mouse_mode (MouseGain);
457 break;
459 case MouseAudition:
460 if (next) set_mouse_mode (MouseObject);
461 else set_mouse_mode (MouseTimeFX);
462 break;
466 void
467 Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemType item_type)
469 /* in object/audition/timefx/gain-automation mode,
470 any button press sets the selection if the object
471 can be selected. this is a bit of hack, because
472 we want to avoid this if the mouse operation is a
473 region alignment.
475 note: not dbl-click or triple-click
477 Also note that there is no region selection in internal edit mode, otherwise
478 for operations operating on the selection (e.g. cut) it is not obvious whether
479 to cut notes or regions.
482 if (((mouse_mode != MouseObject) &&
483 (_join_object_range_state != JOIN_OBJECT_RANGE_OBJECT) &&
484 (mouse_mode != MouseAudition || item_type != RegionItem) &&
485 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
486 (mouse_mode != MouseGain) &&
487 (mouse_mode != MouseRange)) ||
488 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3) ||
489 internal_editing()) {
491 return;
494 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
496 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
498 /* almost no selection action on modified button-2 or button-3 events */
500 if (item_type != RegionItem && event->button.button != 2) {
501 return;
506 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
507 bool press = (event->type == GDK_BUTTON_PRESS);
509 switch (item_type) {
510 case RegionItem:
511 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
512 set_selected_regionview_from_click (press, op, true);
513 } else if (event->type == GDK_BUTTON_PRESS) {
514 selection->clear_tracks ();
515 set_selected_track_as_side_effect (op, true);
517 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
518 clicked_selection = select_range_around_region (selection->regions.front());
520 break;
522 case RegionViewNameHighlight:
523 case RegionViewName:
524 case LeftFrameHandle:
525 case RightFrameHandle:
526 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
527 set_selected_regionview_from_click (press, op, true);
528 } else if (event->type == GDK_BUTTON_PRESS) {
529 set_selected_track_as_side_effect (op);
531 break;
534 case FadeInHandleItem:
535 case FadeInItem:
536 case FadeOutHandleItem:
537 case FadeOutItem:
538 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
539 set_selected_regionview_from_click (press, op, true);
540 } else if (event->type == GDK_BUTTON_PRESS) {
541 set_selected_track_as_side_effect (op);
543 break;
545 case ControlPointItem:
546 set_selected_track_as_side_effect (op, true);
547 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
548 set_selected_control_point_from_click (op, false);
550 break;
552 case StreamItem:
553 /* for context click, select track */
554 if (event->button.button == 3) {
555 selection->clear_tracks ();
556 set_selected_track_as_side_effect (op, true);
558 break;
560 case AutomationTrackItem:
561 set_selected_track_as_side_effect (op, true);
562 break;
564 default:
565 break;
569 bool
570 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
572 /* single mouse clicks on any of these item types operate
573 independent of mouse mode, mostly because they are
574 not on the main track canvas or because we want
575 them to be modeless.
578 switch (item_type) {
579 case PlayheadCursorItem:
580 _drags->set (new CursorDrag (this, item, true), event);
581 return true;
583 case MarkerItem:
584 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
585 hide_marker (item, event);
586 } else {
587 _drags->set (new MarkerDrag (this, item), event);
589 return true;
591 case TempoMarkerItem:
592 _drags->set (
593 new TempoMarkerDrag (
594 this,
595 item,
596 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
598 event
600 return true;
602 case MeterMarkerItem:
603 _drags->set (
604 new MeterMarkerDrag (
605 this,
606 item,
607 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
609 event
611 return true;
613 case MarkerBarItem:
614 case TempoBarItem:
615 case MeterBarItem:
616 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
617 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
619 return true;
620 break;
623 case RangeMarkerBarItem:
624 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
625 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
626 } else {
627 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
629 return true;
630 break;
632 case CdMarkerBarItem:
633 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
634 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
635 } else {
636 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
638 return true;
639 break;
641 case TransportMarkerBarItem:
642 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
643 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
644 } else {
645 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
647 return true;
648 break;
650 default:
651 break;
654 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
655 /* special case: allow trim of range selections in joined object mode;
656 in theory eff should equal MouseRange in this case, but it doesn't
657 because entering the range selection canvas item results in entered_regionview
658 being set to 0, so update_join_object_range_location acts as if we aren't
659 over a region.
661 if (item_type == StartSelectionTrimItem) {
662 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
663 } else if (item_type == EndSelectionTrimItem) {
664 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
668 Editing::MouseMode eff = effective_mouse_mode ();
670 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
671 if (item_type == FadeInHandleItem || item_type == FadeOutHandleItem) {
672 eff = MouseObject;
675 switch (eff) {
676 case MouseRange:
677 switch (item_type) {
678 case StartSelectionTrimItem:
679 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
680 break;
682 case EndSelectionTrimItem:
683 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
684 break;
686 case SelectionItem:
687 if (Keyboard::modifier_state_contains
688 (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier))) {
689 // contains and not equals because I can't use alt as a modifier alone.
690 start_selection_grab (item, event);
691 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
692 /* grab selection for moving */
693 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
694 } else {
695 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
696 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
697 if (tvp.first) {
698 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
699 if (join_object_range_button.get_active() && atv) {
700 /* smart "join" mode: drag automation */
701 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, _cursors->up_down);
702 } else {
703 /* this was debated, but decided the more common action was to
704 make a new selection */
705 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
709 break;
711 case NoteItem:
712 if (internal_editing()) {
713 /* trim notes if we're in internal edit mode and near the ends of the note */
714 ArdourCanvas::CanvasNote* cn = dynamic_cast<ArdourCanvas::CanvasNote*> (item);
715 if (cn && cn->big_enough_to_trim() && cn->mouse_near_ends()) {
716 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
717 } else {
718 _drags->set (new NoteDrag (this, item), event);
721 return true;
723 case StreamItem:
724 if (internal_editing()) {
725 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
726 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
727 return true;
729 } else {
730 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
731 return true;
733 break;
735 case RegionViewNameHighlight:
736 if (!clicked_regionview->region()->locked()) {
737 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
738 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
739 return true;
741 break;
743 case LeftFrameHandle:
744 case RightFrameHandle:
745 if (!internal_editing() && !clicked_regionview->region()->locked()) {
746 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
747 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
748 return true;
750 break;
752 default:
753 if (!internal_editing()) {
754 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
757 return true;
758 break;
760 case MouseObject:
761 switch (item_type) {
762 case NoteItem:
763 if (internal_editing()) {
764 ArdourCanvas::CanvasNoteEvent* cn = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
765 if (cn->mouse_near_ends()) {
766 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
767 } else {
768 _drags->set (new NoteDrag (this, item), event);
770 return true;
772 break;
774 default:
775 break;
778 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
779 event->type == GDK_BUTTON_PRESS) {
781 _drags->set (new RubberbandSelectDrag (this, item), event);
783 } else if (event->type == GDK_BUTTON_PRESS) {
785 switch (item_type) {
786 case FadeInHandleItem:
788 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
789 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, _cursors->fade_in);
790 return true;
793 case FadeOutHandleItem:
795 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
796 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, _cursors->fade_out);
797 return true;
800 case FeatureLineItem:
802 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
803 remove_transient(item);
804 return true;
807 _drags->set (new FeatureLineDrag (this, item), event);
808 return true;
809 break;
812 case RegionItem:
813 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
814 /* click on an automation region view; do nothing here and let the ARV's signal handler
815 sort it out.
817 break;
820 if (internal_editing ()) {
821 /* no region drags in internal edit mode */
822 break;
825 /* click on a normal region view */
826 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
827 add_region_copy_drag (item, event, clicked_regionview);
828 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
829 add_region_brush_drag (item, event, clicked_regionview);
830 } else {
831 add_region_drag (item, event, clicked_regionview);
834 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
835 _drags->add (new SelectionDrag (this, clicked_axisview->get_selection_rect (clicked_selection)->rect, SelectionDrag::SelectionMove));
838 _drags->start_grab (event);
839 break;
841 case RegionViewNameHighlight:
842 case LeftFrameHandle:
843 case RightFrameHandle:
844 if (!clicked_regionview->region()->locked()) {
845 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
846 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
847 return true;
849 break;
851 case RegionViewName:
853 /* rename happens on edit clicks */
854 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
855 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, s.by_layer()), event);
856 return true;
857 break;
860 case ControlPointItem:
861 _drags->set (new ControlPointDrag (this, item), event);
862 return true;
863 break;
865 case AutomationLineItem:
866 _drags->set (new LineDrag (this, item), event);
867 return true;
868 break;
870 case StreamItem:
871 if (internal_editing()) {
872 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
873 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
875 return true;
876 } else {
877 _drags->set (new RubberbandSelectDrag (this, item), event);
879 break;
881 case AutomationTrackItem:
883 TimeAxisView* parent = clicked_axisview->get_parent ();
884 if (parent && dynamic_cast<MidiTimeAxisView*> (parent)) {
885 /* create a MIDI region so that we have somewhere to put automation */
886 _drags->set (new RegionCreateDrag (this, item, parent), event);
887 } else {
888 /* rubberband drag to select automation points */
889 _drags->set (new RubberbandSelectDrag (this, item), event);
891 break;
894 case SelectionItem:
896 if (join_object_range_button.get_active()) {
897 /* we're in "smart" joined mode, and we've clicked on a Selection */
898 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
899 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
900 if (tvp.first) {
901 /* if we're over an automation track, start a drag of its data */
902 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
903 if (atv) {
904 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, _cursors->up_down);
907 /* if we're over a track and a region, and in the `object' part of a region,
908 put a selection around the region and drag both
910 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
911 if (rtv && _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
912 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (rtv->route ());
913 if (t) {
914 boost::shared_ptr<Playlist> pl = t->playlist ();
915 if (pl) {
917 boost::shared_ptr<Region> r = pl->top_region_at (event_frame (event));
918 if (r) {
919 RegionView* rv = rtv->view()->find_view (r);
920 clicked_selection = select_range_around_region (rv);
921 _drags->add (new SelectionDrag (this, item, SelectionDrag::SelectionMove));
922 list<RegionView*> rvs;
923 rvs.push_back (rv);
924 _drags->add (new RegionMoveDrag (this, item, rv, rvs, false, false));
925 _drags->start_grab (event);
932 break;
935 #ifdef WITH_CMT
936 case ImageFrameHandleStartItem:
937 imageframe_start_handle_op(item, event) ;
938 return(true) ;
939 break ;
940 case ImageFrameHandleEndItem:
941 imageframe_end_handle_op(item, event) ;
942 return(true) ;
943 break ;
944 case MarkerViewHandleStartItem:
945 markerview_item_start_handle_op(item, event) ;
946 return(true) ;
947 break ;
948 case MarkerViewHandleEndItem:
949 markerview_item_end_handle_op(item, event) ;
950 return(true) ;
951 break ;
952 case MarkerViewItem:
953 start_markerview_grab(item, event) ;
954 break ;
955 case ImageFrameItem:
956 start_imageframe_grab(item, event) ;
957 break ;
958 #endif
960 case MarkerBarItem:
962 break;
964 default:
965 break;
968 return true;
969 break;
971 case MouseGain:
972 switch (item_type) {
973 case RegionItem:
974 /* start a grab so that if we finish after moving
975 we can tell what happened.
977 _drags->set (new RegionGainDrag (this, item), event, current_canvas_cursor);
978 break;
980 case GainLineItem:
981 _drags->set (new LineDrag (this, item), event);
982 return true;
984 case ControlPointItem:
985 _drags->set (new ControlPointDrag (this, item), event);
986 return true;
987 break;
989 default:
990 break;
992 return true;
993 break;
995 switch (item_type) {
996 case ControlPointItem:
997 _drags->set (new ControlPointDrag (this, item), event);
998 break;
1000 case AutomationLineItem:
1001 _drags->set (new LineDrag (this, item), event);
1002 break;
1004 case RegionItem:
1005 // XXX need automation mode to identify which
1006 // line to use
1007 // start_line_grab_from_regionview (item, event);
1008 break;
1010 default:
1011 break;
1013 return true;
1014 break;
1016 case MouseZoom:
1017 if (event->type == GDK_BUTTON_PRESS) {
1018 _drags->set (new MouseZoomDrag (this, item), event);
1021 return true;
1022 break;
1024 case MouseTimeFX:
1025 if (internal_editing() && item_type == NoteItem) {
1026 /* drag notes if we're in internal edit mode */
1027 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
1028 return true;
1029 } else if ((!internal_editing() || dynamic_cast<AudioRegionView*> (clicked_regionview)) && clicked_regionview) {
1030 /* do time-FX if we're not in internal edit mode, or we are but we clicked on an audio region */
1031 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1032 return true;
1034 break;
1036 case MouseAudition:
1037 _drags->set (new ScrubDrag (this, item), event);
1038 scrub_reversals = 0;
1039 scrub_reverse_distance = 0;
1040 last_scrub_x = event->button.x;
1041 scrubbing_direction = 0;
1042 set_canvas_cursor (_cursors->transparent);
1043 return true;
1044 break;
1046 default:
1047 break;
1050 return false;
1053 bool
1054 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1056 Editing::MouseMode const eff = effective_mouse_mode ();
1057 switch (eff) {
1058 case MouseObject:
1059 switch (item_type) {
1060 case RegionItem:
1061 if (internal_editing ()) {
1062 /* no region drags in internal edit mode */
1063 return false;
1066 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1067 add_region_copy_drag (item, event, clicked_regionview);
1068 } else {
1069 add_region_drag (item, event, clicked_regionview);
1071 _drags->start_grab (event);
1072 return true;
1073 break;
1074 case ControlPointItem:
1075 _drags->set (new ControlPointDrag (this, item), event);
1076 return true;
1077 break;
1079 default:
1080 break;
1083 switch (item_type) {
1084 case RegionViewNameHighlight:
1085 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1086 return true;
1087 break;
1089 case LeftFrameHandle:
1090 case RightFrameHandle:
1091 if (!internal_editing ()) {
1092 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1094 return true;
1095 break;
1097 case RegionViewName:
1098 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1099 return true;
1100 break;
1102 default:
1103 break;
1106 break;
1108 case MouseRange:
1109 /* relax till release */
1110 return true;
1111 break;
1114 case MouseZoom:
1115 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1116 temporal_zoom_to_frame (false, event_frame (event));
1117 } else {
1118 temporal_zoom_to_frame (true, event_frame(event));
1120 return true;
1121 break;
1123 default:
1124 break;
1127 return false;
1130 bool
1131 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1133 if (event->type != GDK_BUTTON_PRESS) {
1134 return false;
1137 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
1139 if (canvas_window) {
1140 Glib::RefPtr<const Gdk::Window> pointer_window;
1141 int x, y;
1142 double wx, wy;
1143 Gdk::ModifierType mask;
1145 pointer_window = canvas_window->get_pointer (x, y, mask);
1147 if (pointer_window == track_canvas->get_bin_window()) {
1148 track_canvas->window_to_world (x, y, wx, wy);
1152 pre_press_cursor = current_canvas_cursor;
1154 track_canvas->grab_focus();
1156 if (_session && _session->actively_recording()) {
1157 return true;
1160 button_selection (item, event, item_type);
1162 if (!_drags->active () &&
1163 (Keyboard::is_delete_event (&event->button) ||
1164 Keyboard::is_context_menu_event (&event->button) ||
1165 Keyboard::is_edit_event (&event->button))) {
1167 /* handled by button release */
1168 return true;
1171 switch (event->button.button) {
1172 case 1:
1173 return button_press_handler_1 (item, event, item_type);
1174 break;
1176 case 2:
1177 return button_press_handler_2 (item, event, item_type);
1178 break;
1180 case 3:
1181 break;
1183 default:
1184 return button_press_dispatch (&event->button);
1185 break;
1189 return false;
1192 bool
1193 Editor::button_press_dispatch (GdkEventButton* ev)
1195 /* this function is intended only for buttons 4 and above.
1198 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1199 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1202 bool
1203 Editor::button_release_dispatch (GdkEventButton* ev)
1205 /* this function is intended only for buttons 4 and above.
1208 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1209 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1212 bool
1213 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1215 framepos_t where = event_frame (event, 0, 0);
1216 AutomationTimeAxisView* atv = 0;
1218 if (pre_press_cursor) {
1219 set_canvas_cursor (pre_press_cursor);
1220 pre_press_cursor = 0;
1223 /* no action if we're recording */
1225 if (_session && _session->actively_recording()) {
1226 return true;
1229 /* see if we're finishing a drag */
1231 bool were_dragging = false;
1232 if (_drags->active ()) {
1233 bool const r = _drags->end_grab (event);
1234 if (r) {
1235 /* grab dragged, so do nothing else */
1236 return true;
1239 were_dragging = true;
1242 update_region_layering_order_editor ();
1244 /* edit events get handled here */
1246 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1247 switch (item_type) {
1248 case RegionItem:
1249 show_region_properties ();
1250 break;
1252 case TempoMarkerItem:
1253 edit_tempo_marker (item);
1254 break;
1256 case MeterMarkerItem:
1257 edit_meter_marker (item);
1258 break;
1260 case RegionViewName:
1261 if (clicked_regionview->name_active()) {
1262 return mouse_rename_region (item, event);
1264 break;
1266 case ControlPointItem:
1267 edit_control_point (item);
1268 break;
1270 case NoteItem:
1271 edit_note (item);
1272 break;
1274 default:
1275 break;
1277 return true;
1280 /* context menu events get handled here */
1282 if (Keyboard::is_context_menu_event (&event->button)) {
1284 if (!_drags->active ()) {
1286 /* no matter which button pops up the context menu, tell the menu
1287 widget to use button 1 to drive menu selection.
1290 switch (item_type) {
1291 case FadeInItem:
1292 case FadeInHandleItem:
1293 case FadeOutItem:
1294 case FadeOutHandleItem:
1295 popup_fade_context_menu (1, event->button.time, item, item_type);
1296 break;
1298 case StreamItem:
1299 popup_track_context_menu (1, event->button.time, item_type, false);
1300 break;
1302 case RegionItem:
1303 case RegionViewNameHighlight:
1304 case LeftFrameHandle:
1305 case RightFrameHandle:
1306 case RegionViewName:
1307 popup_track_context_menu (1, event->button.time, item_type, false);
1308 break;
1310 case SelectionItem:
1311 popup_track_context_menu (1, event->button.time, item_type, true);
1312 break;
1314 case AutomationTrackItem:
1315 popup_track_context_menu (1, event->button.time, item_type, false);
1316 break;
1318 case MarkerBarItem:
1319 case RangeMarkerBarItem:
1320 case TransportMarkerBarItem:
1321 case CdMarkerBarItem:
1322 case TempoBarItem:
1323 case MeterBarItem:
1324 popup_ruler_menu (where, item_type);
1325 break;
1327 case MarkerItem:
1328 marker_context_menu (&event->button, item);
1329 break;
1331 case TempoMarkerItem:
1332 tempo_or_meter_marker_context_menu (&event->button, item);
1333 break;
1335 case MeterMarkerItem:
1336 tempo_or_meter_marker_context_menu (&event->button, item);
1337 break;
1339 case CrossfadeViewItem:
1340 popup_track_context_menu (1, event->button.time, item_type, false);
1341 break;
1343 #ifdef WITH_CMT
1344 case ImageFrameItem:
1345 popup_imageframe_edit_menu(1, event->button.time, item, true) ;
1346 break ;
1347 case ImageFrameTimeAxisItem:
1348 popup_imageframe_edit_menu(1, event->button.time, item, false) ;
1349 break ;
1350 case MarkerViewItem:
1351 popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ;
1352 break ;
1353 case MarkerTimeAxisItem:
1354 popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
1355 break ;
1356 #endif
1358 default:
1359 break;
1362 return true;
1366 /* delete events get handled here */
1368 Editing::MouseMode const eff = effective_mouse_mode ();
1370 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1372 switch (item_type) {
1373 case TempoMarkerItem:
1374 remove_tempo_marker (item);
1375 break;
1377 case MeterMarkerItem:
1378 remove_meter_marker (item);
1379 break;
1381 case MarkerItem:
1382 remove_marker (*item, event);
1383 break;
1385 case RegionItem:
1386 if (eff == MouseObject) {
1387 remove_clicked_region ();
1389 break;
1391 case ControlPointItem:
1392 if (eff == MouseGain) {
1393 remove_gain_control_point (item, event);
1394 } else {
1395 remove_control_point (item, event);
1397 break;
1399 case NoteItem:
1400 remove_midi_note (item, event);
1401 break;
1403 default:
1404 break;
1406 return true;
1409 switch (event->button.button) {
1410 case 1:
1412 switch (item_type) {
1413 /* see comments in button_press_handler */
1414 case PlayheadCursorItem:
1415 case MarkerItem:
1416 case GainLineItem:
1417 case AutomationLineItem:
1418 case StartSelectionTrimItem:
1419 case EndSelectionTrimItem:
1420 return true;
1422 case MarkerBarItem:
1423 if (!_dragging_playhead) {
1424 snap_to_with_modifier (where, event, 0, true);
1425 mouse_add_new_marker (where);
1427 return true;
1429 case CdMarkerBarItem:
1430 if (!_dragging_playhead) {
1431 // if we get here then a dragged range wasn't done
1432 snap_to_with_modifier (where, event, 0, true);
1433 mouse_add_new_marker (where, true);
1435 return true;
1437 case TempoBarItem:
1438 if (!_dragging_playhead) {
1439 snap_to_with_modifier (where, event);
1440 mouse_add_new_tempo_event (where);
1442 return true;
1444 case MeterBarItem:
1445 if (!_dragging_playhead) {
1446 mouse_add_new_meter_event (pixel_to_frame (event->button.x));
1448 return true;
1449 break;
1451 default:
1452 break;
1455 switch (eff) {
1456 case MouseObject:
1457 switch (item_type) {
1458 case AutomationTrackItem:
1459 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1460 if (atv) {
1461 atv->add_automation_event (item, event, where, event->button.y);
1463 return true;
1464 break;
1466 default:
1467 break;
1469 break;
1471 case MouseGain:
1472 switch (item_type) {
1473 case RegionItem:
1475 /* check that we didn't drag before releasing, since
1476 its really annoying to create new control
1477 points when doing this.
1479 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1480 if (were_dragging && arv) {
1481 arv->add_gain_point_event (item, event);
1483 return true;
1484 break;
1487 case AutomationTrackItem:
1488 dynamic_cast<AutomationTimeAxisView*>(clicked_axisview)->
1489 add_automation_event (item, event, where, event->button.y);
1490 return true;
1491 break;
1492 default:
1493 break;
1495 break;
1497 case MouseAudition:
1498 set_canvas_cursor (current_canvas_cursor);
1499 if (scrubbing_direction == 0) {
1500 /* no drag, just a click */
1501 switch (item_type) {
1502 case RegionItem:
1503 play_selected_region ();
1504 break;
1505 default:
1506 break;
1508 } else {
1509 /* make sure we stop */
1510 _session->request_transport_speed (0.0);
1512 break;
1514 default:
1515 break;
1519 /* do any (de)selection operations that should occur on button release */
1520 button_selection (item, event, item_type);
1521 return true;
1522 break;
1525 case 2:
1526 switch (eff) {
1528 case MouseObject:
1529 switch (item_type) {
1530 case RegionItem:
1531 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1532 raise_region ();
1533 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1534 lower_region ();
1535 } else {
1536 // Button2 click is unused
1538 return true;
1540 break;
1542 default:
1543 break;
1545 break;
1547 case MouseRange:
1549 // x_style_paste (where, 1.0);
1550 return true;
1551 break;
1553 default:
1554 break;
1557 break;
1559 case 3:
1560 break;
1562 default:
1563 break;
1566 return false;
1569 bool
1570 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1572 ControlPoint* cp;
1573 Marker * marker;
1574 double fraction;
1575 bool ret = true;
1577 last_item_entered = item;
1579 switch (item_type) {
1580 case ControlPointItem:
1581 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1582 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1583 cp->set_visible (true);
1585 double at_x, at_y;
1586 at_x = cp->get_x();
1587 at_y = cp->get_y ();
1588 cp->i2w (at_x, at_y);
1589 at_x += 10.0;
1590 at_y += 10.0;
1592 fraction = 1.0 - (cp->get_y() / cp->line().height());
1594 if (is_drawable() && !_drags->active ()) {
1595 set_canvas_cursor (_cursors->fader);
1598 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction), at_x, at_y);
1599 _verbose_cursor->show ();
1601 break;
1603 case GainLineItem:
1604 if (mouse_mode == MouseGain) {
1605 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1606 if (line)
1607 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
1608 if (is_drawable()) {
1609 set_canvas_cursor (_cursors->fader);
1612 break;
1614 case AutomationLineItem:
1615 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1617 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1618 if (line)
1619 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
1621 if (is_drawable()) {
1622 set_canvas_cursor (_cursors->fader);
1625 break;
1627 case RegionViewNameHighlight:
1628 if (is_drawable() && mouse_mode == MouseObject && entered_regionview) {
1629 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1630 _over_region_trim_target = true;
1632 break;
1634 case LeftFrameHandle:
1635 case RightFrameHandle:
1636 if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
1637 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1639 break;
1641 case StartSelectionTrimItem:
1642 case EndSelectionTrimItem:
1644 #ifdef WITH_CMT
1645 case ImageFrameHandleStartItem:
1646 case ImageFrameHandleEndItem:
1647 case MarkerViewHandleStartItem:
1648 case MarkerViewHandleEndItem:
1649 #endif
1651 if (is_drawable()) {
1652 set_canvas_cursor (_cursors->trimmer);
1654 break;
1656 case PlayheadCursorItem:
1657 if (is_drawable()) {
1658 switch (_edit_point) {
1659 case EditAtMouse:
1660 set_canvas_cursor (_cursors->grabber_edit_point);
1661 break;
1662 default:
1663 set_canvas_cursor (_cursors->grabber);
1664 break;
1667 break;
1669 case RegionViewName:
1671 /* when the name is not an active item, the entire name highlight is for trimming */
1673 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1674 if (mouse_mode == MouseObject && is_drawable()) {
1675 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1676 _over_region_trim_target = true;
1679 break;
1682 case AutomationTrackItem:
1683 if (is_drawable()) {
1684 Gdk::Cursor *cursor;
1685 switch (mouse_mode) {
1686 case MouseRange:
1687 cursor = _cursors->selector;
1688 break;
1689 case MouseZoom:
1690 cursor = _cursors->zoom_in;
1691 break;
1692 default:
1693 cursor = _cursors->cross_hair;
1694 break;
1697 set_canvas_cursor (cursor);
1699 AutomationTimeAxisView* atv;
1700 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1701 clear_entered_track = false;
1702 set_entered_track (atv);
1705 break;
1707 case MarkerBarItem:
1708 case RangeMarkerBarItem:
1709 case TransportMarkerBarItem:
1710 case CdMarkerBarItem:
1711 case MeterBarItem:
1712 case TempoBarItem:
1713 if (is_drawable()) {
1714 set_canvas_cursor (_cursors->timebar);
1716 break;
1718 case MarkerItem:
1719 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1720 break;
1722 entered_marker = marker;
1723 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1724 // fall through
1725 case MeterMarkerItem:
1726 case TempoMarkerItem:
1727 if (is_drawable()) {
1728 set_canvas_cursor (_cursors->timebar);
1730 break;
1732 case FadeInHandleItem:
1733 if (mouse_mode == MouseObject && !internal_editing()) {
1734 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1735 if (rect) {
1736 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1738 set_canvas_cursor (_cursors->fade_in);
1740 break;
1742 case FadeOutHandleItem:
1743 if (mouse_mode == MouseObject && !internal_editing()) {
1744 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1745 if (rect) {
1746 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1748 set_canvas_cursor (_cursors->fade_out);
1750 break;
1751 case FeatureLineItem:
1753 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1754 line->property_fill_color_rgba() = 0xFF0000FF;
1756 break;
1757 case SelectionItem:
1758 if (join_object_range_button.get_active()) {
1759 set_canvas_cursor ();
1761 break;
1763 default:
1764 break;
1767 /* second pass to handle entered track status in a comprehensible way.
1770 switch (item_type) {
1771 case GainLineItem:
1772 case AutomationLineItem:
1773 case ControlPointItem:
1774 /* these do not affect the current entered track state */
1775 clear_entered_track = false;
1776 break;
1778 case AutomationTrackItem:
1779 /* handled above already */
1780 break;
1782 default:
1783 set_entered_track (0);
1784 break;
1787 return ret;
1790 bool
1791 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1793 AutomationLine* al;
1794 ControlPoint* cp;
1795 Marker *marker;
1796 Location *loc;
1797 RegionView* rv;
1798 bool is_start;
1799 bool ret = true;
1801 switch (item_type) {
1802 case ControlPointItem:
1803 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1804 if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
1805 if (cp->line().npoints() > 1 && !cp->get_selected()) {
1806 cp->set_visible (false);
1810 if (is_drawable()) {
1811 set_canvas_cursor (current_canvas_cursor);
1814 _verbose_cursor->hide ();
1815 break;
1817 case RegionViewNameHighlight:
1818 case LeftFrameHandle:
1819 case RightFrameHandle:
1820 case StartSelectionTrimItem:
1821 case EndSelectionTrimItem:
1822 case PlayheadCursorItem:
1824 #ifdef WITH_CMT
1825 case ImageFrameHandleStartItem:
1826 case ImageFrameHandleEndItem:
1827 case MarkerViewHandleStartItem:
1828 case MarkerViewHandleEndItem:
1829 #endif
1831 _over_region_trim_target = false;
1833 if (is_drawable()) {
1834 set_canvas_cursor (current_canvas_cursor);
1836 break;
1838 case GainLineItem:
1839 case AutomationLineItem:
1840 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1842 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1843 if (line)
1844 line->property_fill_color_rgba() = al->get_line_color();
1846 if (is_drawable()) {
1847 set_canvas_cursor (current_canvas_cursor);
1849 break;
1851 case RegionViewName:
1852 /* see enter_handler() for notes */
1853 _over_region_trim_target = false;
1855 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1856 if (is_drawable() && mouse_mode == MouseObject) {
1857 set_canvas_cursor (current_canvas_cursor);
1860 break;
1862 case RangeMarkerBarItem:
1863 case TransportMarkerBarItem:
1864 case CdMarkerBarItem:
1865 case MeterBarItem:
1866 case TempoBarItem:
1867 case MarkerBarItem:
1868 if (is_drawable()) {
1869 set_canvas_cursor (current_canvas_cursor);
1871 break;
1873 case MarkerItem:
1874 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1875 break;
1877 entered_marker = 0;
1878 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1879 location_flags_changed (loc, this);
1881 // fall through
1882 case MeterMarkerItem:
1883 case TempoMarkerItem:
1885 if (is_drawable()) {
1886 set_canvas_cursor (_cursors->timebar);
1889 break;
1891 case FadeInHandleItem:
1892 case FadeOutHandleItem:
1893 rv = static_cast<RegionView*>(item->get_data ("regionview"));
1895 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1896 if (rect) {
1897 rect->property_fill_color_rgba() = rv->get_fill_color();
1898 rect->property_outline_pixels() = 0;
1901 set_canvas_cursor (current_canvas_cursor);
1902 break;
1904 case AutomationTrackItem:
1905 if (is_drawable()) {
1906 set_canvas_cursor (current_canvas_cursor);
1907 clear_entered_track = true;
1908 Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track));
1910 break;
1911 case FeatureLineItem:
1913 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1914 line->property_fill_color_rgba() = (guint) ARDOUR_UI::config()->canvasvar_ZeroLine.get();;
1916 break;
1918 default:
1919 break;
1922 return ret;
1925 gint
1926 Editor::left_automation_track ()
1928 if (clear_entered_track) {
1929 set_entered_track (0);
1930 clear_entered_track = false;
1932 return false;
1935 void
1936 Editor::scrub (framepos_t frame, double current_x)
1938 double delta;
1940 if (scrubbing_direction == 0) {
1941 /* first move */
1942 _session->request_locate (frame, false);
1943 _session->request_transport_speed (0.1);
1944 scrubbing_direction = 1;
1946 } else {
1948 if (last_scrub_x > current_x) {
1950 /* pointer moved to the left */
1952 if (scrubbing_direction > 0) {
1954 /* we reversed direction to go backwards */
1956 scrub_reversals++;
1957 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1959 } else {
1961 /* still moving to the left (backwards) */
1963 scrub_reversals = 0;
1964 scrub_reverse_distance = 0;
1966 delta = 0.01 * (last_scrub_x - current_x);
1967 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1970 } else {
1971 /* pointer moved to the right */
1973 if (scrubbing_direction < 0) {
1974 /* we reversed direction to go forward */
1976 scrub_reversals++;
1977 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1979 } else {
1980 /* still moving to the right */
1982 scrub_reversals = 0;
1983 scrub_reverse_distance = 0;
1985 delta = 0.01 * (current_x - last_scrub_x);
1986 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1990 /* if there have been more than 2 opposite motion moves detected, or one that moves
1991 back more than 10 pixels, reverse direction
1994 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1996 if (scrubbing_direction > 0) {
1997 /* was forwards, go backwards */
1998 _session->request_transport_speed (-0.1);
1999 scrubbing_direction = -1;
2000 } else {
2001 /* was backwards, go forwards */
2002 _session->request_transport_speed (0.1);
2003 scrubbing_direction = 1;
2006 scrub_reverse_distance = 0;
2007 scrub_reversals = 0;
2011 last_scrub_x = current_x;
2014 bool
2015 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2017 _last_motion_y = event->motion.y;
2019 if (event->motion.is_hint) {
2020 gint x, y;
2022 /* We call this so that MOTION_NOTIFY events continue to be
2023 delivered to the canvas. We need to do this because we set
2024 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2025 the density of the events, at the expense of a round-trip
2026 to the server. Given that this will mostly occur on cases
2027 where DISPLAY = :0.0, and given the cost of what the motion
2028 event might do, its a good tradeoff.
2031 track_canvas->get_pointer (x, y);
2034 if (current_stepping_trackview) {
2035 /* don't keep the persistent stepped trackview if the mouse moves */
2036 current_stepping_trackview = 0;
2037 step_timeout.disconnect ();
2040 if (_session && _session->actively_recording()) {
2041 /* Sorry. no dragging stuff around while we record */
2042 return true;
2045 JoinObjectRangeState const old = _join_object_range_state;
2046 update_join_object_range_location (event->motion.x, event->motion.y);
2047 if (_join_object_range_state != old) {
2048 set_canvas_cursor ();
2051 if (_over_region_trim_target) {
2052 set_canvas_cursor_for_region_view (event->motion.x, entered_regionview);
2055 bool handled = false;
2056 if (_drags->active ()) {
2057 handled = _drags->motion_handler (event, from_autoscroll);
2060 if (!handled) {
2061 return false;
2064 track_canvas_motion (event);
2065 return true;
2068 void
2069 Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* /*event*/)
2071 ControlPoint* control_point;
2073 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2074 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2075 /*NOTREACHED*/
2078 // We shouldn't remove the first or last gain point
2079 if (control_point->line().is_last_point(*control_point) ||
2080 control_point->line().is_first_point(*control_point)) {
2081 return;
2084 control_point->line().remove_point (*control_point);
2087 void
2088 Editor::remove_control_point (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2090 ControlPoint* control_point;
2092 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2093 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2094 /*NOTREACHED*/
2097 control_point->line().remove_point (*control_point);
2100 void
2101 Editor::edit_control_point (ArdourCanvas::Item* item)
2103 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2105 if (p == 0) {
2106 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2107 /*NOTREACHED*/
2110 ControlPointDialog d (p);
2111 d.set_position (Gtk::WIN_POS_MOUSE);
2112 ensure_float (d);
2114 if (d.run () != RESPONSE_ACCEPT) {
2115 return;
2118 p->line().modify_point_y (*p, d.get_y_fraction ());
2121 void
2122 Editor::edit_note (ArdourCanvas::Item* item)
2124 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2125 assert (e);
2127 EditNoteDialog d (&e->region_view(), e);
2128 d.set_position (Gtk::WIN_POS_MOUSE);
2129 ensure_float (d);
2131 d.run ();
2135 void
2136 Editor::visible_order_range (int* low, int* high) const
2138 *low = TimeAxisView::max_order ();
2139 *high = 0;
2141 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2143 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2145 if (!rtv->hidden()) {
2147 if (*high < rtv->order()) {
2148 *high = rtv->order ();
2151 if (*low > rtv->order()) {
2152 *low = rtv->order ();
2158 void
2159 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2161 /* Either add to or set the set the region selection, unless
2162 this is an alignment click (control used)
2165 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2166 TimeAxisView* tv = &rv.get_time_axis_view();
2167 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2168 double speed = 1.0;
2169 if (rtv && rtv->is_track()) {
2170 speed = rtv->track()->speed();
2173 framepos_t where = get_preferred_edit_position();
2175 if (where >= 0) {
2177 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2179 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2181 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2183 align_region (rv.region(), End, (framepos_t) (where * speed));
2185 } else {
2187 align_region (rv.region(), Start, (framepos_t) (where * speed));
2193 void
2194 Editor::collect_new_region_view (RegionView* rv)
2196 latest_regionviews.push_back (rv);
2199 void
2200 Editor::collect_and_select_new_region_view (RegionView* rv)
2202 selection->add(rv);
2203 latest_regionviews.push_back (rv);
2206 void
2207 Editor::cancel_selection ()
2209 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2210 (*i)->hide_selection ();
2213 selection->clear ();
2214 clicked_selection = 0;
2218 void
2219 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2221 RegionView* rv = clicked_regionview;
2223 /* Choose action dependant on which button was pressed */
2224 switch (event->button.button) {
2225 case 1:
2226 begin_reversible_command (_("start point trim"));
2228 if (selection->selected (rv)) {
2229 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2230 i != selection->regions.by_layer().end(); ++i)
2232 if ( (*i) == NULL){
2233 cerr << "region view contains null region" << endl;
2236 if (!(*i)->region()->locked()) {
2237 (*i)->region()->clear_changes ();
2238 (*i)->region()->trim_front (new_bound);
2239 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2243 } else {
2244 if (!rv->region()->locked()) {
2245 rv->region()->clear_changes ();
2246 rv->region()->trim_front (new_bound);
2247 _session->add_command(new StatefulDiffCommand (rv->region()));
2251 commit_reversible_command();
2253 break;
2254 case 2:
2255 begin_reversible_command (_("End point trim"));
2257 if (selection->selected (rv)) {
2259 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2261 if (!(*i)->region()->locked()) {
2262 (*i)->region()->clear_changes();
2263 (*i)->region()->trim_end (new_bound);
2264 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2268 } else {
2270 if (!rv->region()->locked()) {
2271 rv->region()->clear_changes ();
2272 rv->region()->trim_end (new_bound);
2273 _session->add_command (new StatefulDiffCommand (rv->region()));
2277 commit_reversible_command();
2279 break;
2280 default:
2281 break;
2285 void
2286 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2288 Marker* marker;
2289 bool is_start;
2291 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2292 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2293 /*NOTREACHED*/
2296 Location* location = find_location_from_marker (marker, is_start);
2297 location->set_hidden (true, this);
2301 void
2302 Editor::reposition_zoom_rect (framepos_t start, framepos_t end)
2304 double x1 = frame_to_pixel (start);
2305 double x2 = frame_to_pixel (end);
2306 double y2 = full_canvas_height - 1.0;
2308 zoom_rect->property_x1() = x1;
2309 zoom_rect->property_y1() = 1.0;
2310 zoom_rect->property_x2() = x2;
2311 zoom_rect->property_y2() = y2;
2315 gint
2316 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2318 using namespace Gtkmm2ext;
2320 ArdourPrompter prompter (false);
2322 prompter.set_prompt (_("Name for region:"));
2323 prompter.set_initial_text (clicked_regionview->region()->name());
2324 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2325 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2326 prompter.show_all ();
2327 switch (prompter.run ()) {
2328 case Gtk::RESPONSE_ACCEPT:
2329 string str;
2330 prompter.get_result(str);
2331 if (str.length()) {
2332 clicked_regionview->region()->set_name (str);
2334 break;
2336 return true;
2340 void
2341 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2343 /* no brushing without a useful snap setting */
2345 switch (_snap_mode) {
2346 case SnapMagnetic:
2347 return; /* can't work because it allows region to be placed anywhere */
2348 default:
2349 break; /* OK */
2352 switch (_snap_type) {
2353 case SnapToMark:
2354 return;
2356 default:
2357 break;
2360 /* don't brush a copy over the original */
2362 if (pos == rv->region()->position()) {
2363 return;
2366 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2368 if (rtv == 0 || !rtv->is_track()) {
2369 return;
2372 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2373 double speed = rtv->track()->speed();
2375 playlist->clear_changes ();
2376 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2377 playlist->add_region (new_region, (framepos_t) (pos * speed));
2378 _session->add_command (new StatefulDiffCommand (playlist));
2380 // playlist is frozen, so we have to update manually XXX this is disgusting
2382 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2385 gint
2386 Editor::track_height_step_timeout ()
2388 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2389 current_stepping_trackview = 0;
2390 return false;
2392 return true;
2395 void
2396 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2398 assert (region_view);
2400 if (!region_view->region()->playlist()) {
2401 return;
2404 _region_motion_group->raise_to_top ();
2406 if (Config->get_edit_mode() == Splice) {
2407 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2408 } else {
2409 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2410 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false));
2413 /* sync the canvas to what we think is its current state */
2414 update_canvas_now();
2417 void
2418 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2420 assert (region_view);
2422 if (!region_view->region()->playlist()) {
2423 return;
2426 _region_motion_group->raise_to_top ();
2428 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2429 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true));
2432 void
2433 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2435 assert (region_view);
2437 if (!region_view->region()->playlist()) {
2438 return;
2441 if (Config->get_edit_mode() == Splice) {
2442 return;
2445 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2446 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), true, false));
2448 begin_reversible_command (Operations::drag_region_brush);
2451 /** Start a grab where a time range is selected, track(s) are selected, and the
2452 * user clicks and drags a region with a modifier in order to create a new region containing
2453 * the section of the clicked region that lies within the time range.
2455 void
2456 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2458 if (clicked_regionview == 0) {
2459 return;
2462 /* lets try to create new Region for the selection */
2464 vector<boost::shared_ptr<Region> > new_regions;
2465 create_region_from_selection (new_regions);
2467 if (new_regions.empty()) {
2468 return;
2471 /* XXX fix me one day to use all new regions */
2473 boost::shared_ptr<Region> region (new_regions.front());
2475 /* add it to the current stream/playlist.
2477 tricky: the streamview for the track will add a new regionview. we will
2478 catch the signal it sends when it creates the regionview to
2479 set the regionview we want to then drag.
2482 latest_regionviews.clear();
2483 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2485 /* A selection grab currently creates two undo/redo operations, one for
2486 creating the new region and another for moving it.
2489 begin_reversible_command (Operations::selection_grab);
2491 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2493 playlist->clear_changes ();
2494 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2495 _session->add_command(new StatefulDiffCommand (playlist));
2497 commit_reversible_command ();
2499 c.disconnect ();
2501 if (latest_regionviews.empty()) {
2502 /* something went wrong */
2503 return;
2506 /* we need to deselect all other regionviews, and select this one
2507 i'm ignoring undo stuff, because the region creation will take care of it
2509 selection->set (latest_regionviews);
2511 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2514 void
2515 Editor::escape ()
2517 if (_drags->active ()) {
2518 _drags->abort ();
2519 } else {
2520 selection->clear ();
2524 void
2525 Editor::set_internal_edit (bool yn)
2527 _internal_editing = yn;
2529 if (yn) {
2530 mouse_select_button.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
2531 mouse_select_button.get_image ()->show ();
2532 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Draw/Edit MIDI Notes"));
2533 mouse_mode_toggled (mouse_mode);
2535 pre_internal_mouse_mode = mouse_mode;
2537 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2538 (*i)->enter_internal_edit_mode ();
2541 } else {
2543 mouse_select_button.set_image (*(manage (new Image (::get_icon("tool_range")))));
2544 mouse_select_button.get_image ()->show ();
2545 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Select/Move Ranges"));
2546 mouse_mode_toggled (mouse_mode); // sets cursor
2548 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2549 (*i)->leave_internal_edit_mode ();
2552 if (mouse_mode == MouseRange && pre_internal_mouse_mode != MouseRange) {
2553 /* we were drawing .. flip back to something sensible */
2554 set_mouse_mode (pre_internal_mouse_mode);
2559 /** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
2560 * used by the `join object/range' tool mode.
2562 void
2563 Editor::update_join_object_range_location (double x, double y)
2565 /* XXX: actually, this decides based on whether the mouse is in the top or bottom half of a RouteTimeAxisView;
2566 entered_{track,regionview} is not always setup (e.g. if the mouse is over a TimeSelection), and to get a Region
2567 that we're over requires searching the playlist.
2570 if (join_object_range_button.get_active() == false || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
2571 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2572 return;
2575 if (mouse_mode == MouseObject) {
2576 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2577 } else if (mouse_mode == MouseRange) {
2578 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2581 /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
2582 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y + vertical_adjustment.get_value() - canvas_timebars_vsize);
2584 if (tvp.first) {
2586 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2587 if (rtv) {
2589 double cx = 0;
2590 double cy = y;
2591 rtv->canvas_display()->w2i (cx, cy);
2593 double const c = cy / rtv->view()->child_height();
2594 double d;
2595 double const f = modf (c, &d);
2597 _join_object_range_state = f < 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2602 Editing::MouseMode
2603 Editor::effective_mouse_mode () const
2605 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2606 return MouseObject;
2607 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2608 return MouseRange;
2611 return mouse_mode;
2614 void
2615 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2617 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2618 assert (e);
2620 e->region_view().delete_note (e->note ());
2623 void
2624 Editor::set_canvas_cursor_for_region_view (double x, RegionView* rv)
2626 assert (rv);
2628 ArdourCanvas::Group* g = rv->get_canvas_group ();
2629 ArdourCanvas::Group* p = g->get_parent_group ();
2631 /* Compute x in region view parent coordinates */
2632 double dy = 0;
2633 p->w2i (x, dy);
2635 double x1, x2, y1, y2;
2636 g->get_bounds (x1, y1, x2, y2);
2638 /* Halfway across the region */
2639 double const h = (x1 + x2) / 2;
2641 Trimmable::CanTrim ct = rv->region()->can_trim ();
2642 if (x <= h) {
2643 if (ct & Trimmable::FrontTrimEarlier) {
2644 set_canvas_cursor (_cursors->left_side_trim);
2645 } else {
2646 set_canvas_cursor (_cursors->left_side_trim_right_only);
2648 } else {
2649 if (ct & Trimmable::EndTrimLater) {
2650 set_canvas_cursor (_cursors->right_side_trim);
2651 } else {
2652 set_canvas_cursor (_cursors->right_side_trim_left_only);
2657 /** Obtain the pointer position in world coordinates */
2658 void
2659 Editor::get_pointer_position (double& x, double& y) const
2661 int px, py;
2662 track_canvas->get_pointer (px, py);
2663 track_canvas->window_to_world (px, py, x, y);