Remove erroneous assert which I added earlier.
[ardour2.git] / gtk2_ardour / editor_mouse.cc
blob0d5f75f60d96c49fc732aa7dee4488d0b6cfcd55
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:
882 /* rubberband drag to select automation points */
883 _drags->set (new RubberbandSelectDrag (this, item), event);
884 break;
886 case SelectionItem:
888 if (join_object_range_button.get_active()) {
889 /* we're in "smart" joined mode, and we've clicked on a Selection */
890 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
891 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
892 if (tvp.first) {
893 /* if we're over an automation track, start a drag of its data */
894 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
895 if (atv) {
896 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, _cursors->up_down);
899 /* if we're over a track and a region, and in the `object' part of a region,
900 put a selection around the region and drag both
902 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
903 if (rtv && _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
904 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (rtv->route ());
905 if (t) {
906 boost::shared_ptr<Playlist> pl = t->playlist ();
907 if (pl) {
909 boost::shared_ptr<Region> r = pl->top_region_at (event_frame (event));
910 if (r) {
911 RegionView* rv = rtv->view()->find_view (r);
912 clicked_selection = select_range_around_region (rv);
913 _drags->add (new SelectionDrag (this, item, SelectionDrag::SelectionMove));
914 list<RegionView*> rvs;
915 rvs.push_back (rv);
916 _drags->add (new RegionMoveDrag (this, item, rv, rvs, false, false));
917 _drags->start_grab (event);
924 break;
927 #ifdef WITH_CMT
928 case ImageFrameHandleStartItem:
929 imageframe_start_handle_op(item, event) ;
930 return(true) ;
931 break ;
932 case ImageFrameHandleEndItem:
933 imageframe_end_handle_op(item, event) ;
934 return(true) ;
935 break ;
936 case MarkerViewHandleStartItem:
937 markerview_item_start_handle_op(item, event) ;
938 return(true) ;
939 break ;
940 case MarkerViewHandleEndItem:
941 markerview_item_end_handle_op(item, event) ;
942 return(true) ;
943 break ;
944 case MarkerViewItem:
945 start_markerview_grab(item, event) ;
946 break ;
947 case ImageFrameItem:
948 start_imageframe_grab(item, event) ;
949 break ;
950 #endif
952 case MarkerBarItem:
954 break;
956 default:
957 break;
960 return true;
961 break;
963 case MouseGain:
964 switch (item_type) {
965 case RegionItem:
966 /* start a grab so that if we finish after moving
967 we can tell what happened.
969 _drags->set (new RegionGainDrag (this, item), event, current_canvas_cursor);
970 break;
972 case GainLineItem:
973 _drags->set (new LineDrag (this, item), event);
974 return true;
976 case ControlPointItem:
977 _drags->set (new ControlPointDrag (this, item), event);
978 return true;
979 break;
981 default:
982 break;
984 return true;
985 break;
987 switch (item_type) {
988 case ControlPointItem:
989 _drags->set (new ControlPointDrag (this, item), event);
990 break;
992 case AutomationLineItem:
993 _drags->set (new LineDrag (this, item), event);
994 break;
996 case RegionItem:
997 // XXX need automation mode to identify which
998 // line to use
999 // start_line_grab_from_regionview (item, event);
1000 break;
1002 default:
1003 break;
1005 return true;
1006 break;
1008 case MouseZoom:
1009 if (event->type == GDK_BUTTON_PRESS) {
1010 _drags->set (new MouseZoomDrag (this, item), event);
1013 return true;
1014 break;
1016 case MouseTimeFX:
1017 if (internal_editing() && item_type == NoteItem) {
1018 /* drag notes if we're in internal edit mode */
1019 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
1020 return true;
1021 } else if ((!internal_editing() || dynamic_cast<AudioRegionView*> (clicked_regionview)) && clicked_regionview) {
1022 /* do time-FX if we're not in internal edit mode, or we are but we clicked on an audio region */
1023 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1024 return true;
1026 break;
1028 case MouseAudition:
1029 _drags->set (new ScrubDrag (this, item), event);
1030 scrub_reversals = 0;
1031 scrub_reverse_distance = 0;
1032 last_scrub_x = event->button.x;
1033 scrubbing_direction = 0;
1034 set_canvas_cursor (_cursors->transparent);
1035 return true;
1036 break;
1038 default:
1039 break;
1042 return false;
1045 bool
1046 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1048 Editing::MouseMode const eff = effective_mouse_mode ();
1049 switch (eff) {
1050 case MouseObject:
1051 switch (item_type) {
1052 case RegionItem:
1053 if (internal_editing ()) {
1054 /* no region drags in internal edit mode */
1055 return false;
1058 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1059 add_region_copy_drag (item, event, clicked_regionview);
1060 } else {
1061 add_region_drag (item, event, clicked_regionview);
1063 _drags->start_grab (event);
1064 return true;
1065 break;
1066 case ControlPointItem:
1067 _drags->set (new ControlPointDrag (this, item), event);
1068 return true;
1069 break;
1071 default:
1072 break;
1075 switch (item_type) {
1076 case RegionViewNameHighlight:
1077 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1078 return true;
1079 break;
1081 case LeftFrameHandle:
1082 case RightFrameHandle:
1083 if (!internal_editing ()) {
1084 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1086 return true;
1087 break;
1089 case RegionViewName:
1090 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1091 return true;
1092 break;
1094 default:
1095 break;
1098 break;
1100 case MouseRange:
1101 /* relax till release */
1102 return true;
1103 break;
1106 case MouseZoom:
1107 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1108 temporal_zoom_to_frame (false, event_frame (event));
1109 } else {
1110 temporal_zoom_to_frame (true, event_frame(event));
1112 return true;
1113 break;
1115 default:
1116 break;
1119 return false;
1122 bool
1123 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1125 if (event->type != GDK_BUTTON_PRESS) {
1126 return false;
1129 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
1131 if (canvas_window) {
1132 Glib::RefPtr<const Gdk::Window> pointer_window;
1133 int x, y;
1134 double wx, wy;
1135 Gdk::ModifierType mask;
1137 pointer_window = canvas_window->get_pointer (x, y, mask);
1139 if (pointer_window == track_canvas->get_bin_window()) {
1140 track_canvas->window_to_world (x, y, wx, wy);
1144 pre_press_cursor = current_canvas_cursor;
1146 track_canvas->grab_focus();
1148 if (_session && _session->actively_recording()) {
1149 return true;
1152 button_selection (item, event, item_type);
1154 if (!_drags->active () &&
1155 (Keyboard::is_delete_event (&event->button) ||
1156 Keyboard::is_context_menu_event (&event->button) ||
1157 Keyboard::is_edit_event (&event->button))) {
1159 /* handled by button release */
1160 return true;
1163 switch (event->button.button) {
1164 case 1:
1165 return button_press_handler_1 (item, event, item_type);
1166 break;
1168 case 2:
1169 return button_press_handler_2 (item, event, item_type);
1170 break;
1172 case 3:
1173 break;
1175 default:
1176 return button_press_dispatch (&event->button);
1177 break;
1181 return false;
1184 bool
1185 Editor::button_press_dispatch (GdkEventButton* ev)
1187 /* this function is intended only for buttons 4 and above.
1190 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1191 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1194 bool
1195 Editor::button_release_dispatch (GdkEventButton* ev)
1197 /* this function is intended only for buttons 4 and above.
1200 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1201 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1204 bool
1205 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1207 framepos_t where = event_frame (event, 0, 0);
1208 AutomationTimeAxisView* atv = 0;
1210 if (pre_press_cursor) {
1211 set_canvas_cursor (pre_press_cursor);
1212 pre_press_cursor = 0;
1215 /* no action if we're recording */
1217 if (_session && _session->actively_recording()) {
1218 return true;
1221 /* see if we're finishing a drag */
1223 bool were_dragging = false;
1224 if (_drags->active ()) {
1225 bool const r = _drags->end_grab (event);
1226 if (r) {
1227 /* grab dragged, so do nothing else */
1228 return true;
1231 were_dragging = true;
1234 update_region_layering_order_editor ();
1236 /* edit events get handled here */
1238 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1239 switch (item_type) {
1240 case RegionItem:
1241 show_region_properties ();
1242 break;
1244 case TempoMarkerItem:
1245 edit_tempo_marker (item);
1246 break;
1248 case MeterMarkerItem:
1249 edit_meter_marker (item);
1250 break;
1252 case RegionViewName:
1253 if (clicked_regionview->name_active()) {
1254 return mouse_rename_region (item, event);
1256 break;
1258 case ControlPointItem:
1259 edit_control_point (item);
1260 break;
1262 case NoteItem:
1263 edit_note (item);
1264 break;
1266 default:
1267 break;
1269 return true;
1272 /* context menu events get handled here */
1274 if (Keyboard::is_context_menu_event (&event->button)) {
1276 if (!_drags->active ()) {
1278 /* no matter which button pops up the context menu, tell the menu
1279 widget to use button 1 to drive menu selection.
1282 switch (item_type) {
1283 case FadeInItem:
1284 case FadeInHandleItem:
1285 case FadeOutItem:
1286 case FadeOutHandleItem:
1287 popup_fade_context_menu (1, event->button.time, item, item_type);
1288 break;
1290 case StreamItem:
1291 popup_track_context_menu (1, event->button.time, item_type, false);
1292 break;
1294 case RegionItem:
1295 case RegionViewNameHighlight:
1296 case LeftFrameHandle:
1297 case RightFrameHandle:
1298 case RegionViewName:
1299 popup_track_context_menu (1, event->button.time, item_type, false);
1300 break;
1302 case SelectionItem:
1303 popup_track_context_menu (1, event->button.time, item_type, true);
1304 break;
1306 case AutomationTrackItem:
1307 popup_track_context_menu (1, event->button.time, item_type, false);
1308 break;
1310 case MarkerBarItem:
1311 case RangeMarkerBarItem:
1312 case TransportMarkerBarItem:
1313 case CdMarkerBarItem:
1314 case TempoBarItem:
1315 case MeterBarItem:
1316 popup_ruler_menu (where, item_type);
1317 break;
1319 case MarkerItem:
1320 marker_context_menu (&event->button, item);
1321 break;
1323 case TempoMarkerItem:
1324 tempo_or_meter_marker_context_menu (&event->button, item);
1325 break;
1327 case MeterMarkerItem:
1328 tempo_or_meter_marker_context_menu (&event->button, item);
1329 break;
1331 case CrossfadeViewItem:
1332 popup_track_context_menu (1, event->button.time, item_type, false);
1333 break;
1335 #ifdef WITH_CMT
1336 case ImageFrameItem:
1337 popup_imageframe_edit_menu(1, event->button.time, item, true) ;
1338 break ;
1339 case ImageFrameTimeAxisItem:
1340 popup_imageframe_edit_menu(1, event->button.time, item, false) ;
1341 break ;
1342 case MarkerViewItem:
1343 popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ;
1344 break ;
1345 case MarkerTimeAxisItem:
1346 popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
1347 break ;
1348 #endif
1350 default:
1351 break;
1354 return true;
1358 /* delete events get handled here */
1360 Editing::MouseMode const eff = effective_mouse_mode ();
1362 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1364 switch (item_type) {
1365 case TempoMarkerItem:
1366 remove_tempo_marker (item);
1367 break;
1369 case MeterMarkerItem:
1370 remove_meter_marker (item);
1371 break;
1373 case MarkerItem:
1374 remove_marker (*item, event);
1375 break;
1377 case RegionItem:
1378 if (eff == MouseObject) {
1379 remove_clicked_region ();
1381 break;
1383 case ControlPointItem:
1384 if (eff == MouseGain) {
1385 remove_gain_control_point (item, event);
1386 } else {
1387 remove_control_point (item, event);
1389 break;
1391 case NoteItem:
1392 remove_midi_note (item, event);
1393 break;
1395 default:
1396 break;
1398 return true;
1401 switch (event->button.button) {
1402 case 1:
1404 switch (item_type) {
1405 /* see comments in button_press_handler */
1406 case PlayheadCursorItem:
1407 case MarkerItem:
1408 case GainLineItem:
1409 case AutomationLineItem:
1410 case StartSelectionTrimItem:
1411 case EndSelectionTrimItem:
1412 return true;
1414 case MarkerBarItem:
1415 if (!_dragging_playhead) {
1416 snap_to_with_modifier (where, event, 0, true);
1417 mouse_add_new_marker (where);
1419 return true;
1421 case CdMarkerBarItem:
1422 if (!_dragging_playhead) {
1423 // if we get here then a dragged range wasn't done
1424 snap_to_with_modifier (where, event, 0, true);
1425 mouse_add_new_marker (where, true);
1427 return true;
1429 case TempoBarItem:
1430 if (!_dragging_playhead) {
1431 snap_to_with_modifier (where, event);
1432 mouse_add_new_tempo_event (where);
1434 return true;
1436 case MeterBarItem:
1437 if (!_dragging_playhead) {
1438 mouse_add_new_meter_event (pixel_to_frame (event->button.x));
1440 return true;
1441 break;
1443 default:
1444 break;
1447 switch (eff) {
1448 case MouseObject:
1449 switch (item_type) {
1450 case AutomationTrackItem:
1451 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1452 if (atv) {
1453 atv->add_automation_event (item, event, where, event->button.y);
1455 return true;
1456 break;
1458 default:
1459 break;
1461 break;
1463 case MouseGain:
1464 switch (item_type) {
1465 case RegionItem:
1467 /* check that we didn't drag before releasing, since
1468 its really annoying to create new control
1469 points when doing this.
1471 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1472 if (were_dragging && arv) {
1473 arv->add_gain_point_event (item, event);
1475 return true;
1476 break;
1479 case AutomationTrackItem:
1480 dynamic_cast<AutomationTimeAxisView*>(clicked_axisview)->
1481 add_automation_event (item, event, where, event->button.y);
1482 return true;
1483 break;
1484 default:
1485 break;
1487 break;
1489 case MouseAudition:
1490 set_canvas_cursor (current_canvas_cursor);
1491 if (scrubbing_direction == 0) {
1492 /* no drag, just a click */
1493 switch (item_type) {
1494 case RegionItem:
1495 play_selected_region ();
1496 break;
1497 default:
1498 break;
1500 } else {
1501 /* make sure we stop */
1502 _session->request_transport_speed (0.0);
1504 break;
1506 default:
1507 break;
1511 /* do any (de)selection operations that should occur on button release */
1512 button_selection (item, event, item_type);
1513 return true;
1514 break;
1517 case 2:
1518 switch (eff) {
1520 case MouseObject:
1521 switch (item_type) {
1522 case RegionItem:
1523 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1524 raise_region ();
1525 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1526 lower_region ();
1527 } else {
1528 // Button2 click is unused
1530 return true;
1532 break;
1534 default:
1535 break;
1537 break;
1539 case MouseRange:
1541 // x_style_paste (where, 1.0);
1542 return true;
1543 break;
1545 default:
1546 break;
1549 break;
1551 case 3:
1552 break;
1554 default:
1555 break;
1558 return false;
1561 bool
1562 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1564 ControlPoint* cp;
1565 Marker * marker;
1566 double fraction;
1567 bool ret = true;
1569 last_item_entered = item;
1571 switch (item_type) {
1572 case ControlPointItem:
1573 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1574 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1575 cp->set_visible (true);
1577 double at_x, at_y;
1578 at_x = cp->get_x();
1579 at_y = cp->get_y ();
1580 cp->i2w (at_x, at_y);
1581 at_x += 10.0;
1582 at_y += 10.0;
1584 fraction = 1.0 - (cp->get_y() / cp->line().height());
1586 if (is_drawable() && !_drags->active ()) {
1587 set_canvas_cursor (_cursors->fader);
1590 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction), at_x, at_y);
1591 _verbose_cursor->show ();
1593 break;
1595 case GainLineItem:
1596 if (mouse_mode == MouseGain) {
1597 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1598 if (line)
1599 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
1600 if (is_drawable()) {
1601 set_canvas_cursor (_cursors->fader);
1604 break;
1606 case AutomationLineItem:
1607 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1609 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1610 if (line)
1611 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
1613 if (is_drawable()) {
1614 set_canvas_cursor (_cursors->fader);
1617 break;
1619 case RegionViewNameHighlight:
1620 if (is_drawable() && mouse_mode == MouseObject && entered_regionview) {
1621 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1622 _over_region_trim_target = true;
1624 break;
1626 case LeftFrameHandle:
1627 case RightFrameHandle:
1628 if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
1629 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1631 break;
1633 case StartSelectionTrimItem:
1634 case EndSelectionTrimItem:
1636 #ifdef WITH_CMT
1637 case ImageFrameHandleStartItem:
1638 case ImageFrameHandleEndItem:
1639 case MarkerViewHandleStartItem:
1640 case MarkerViewHandleEndItem:
1641 #endif
1643 if (is_drawable()) {
1644 set_canvas_cursor (_cursors->trimmer);
1646 break;
1648 case PlayheadCursorItem:
1649 if (is_drawable()) {
1650 switch (_edit_point) {
1651 case EditAtMouse:
1652 set_canvas_cursor (_cursors->grabber_edit_point);
1653 break;
1654 default:
1655 set_canvas_cursor (_cursors->grabber);
1656 break;
1659 break;
1661 case RegionViewName:
1663 /* when the name is not an active item, the entire name highlight is for trimming */
1665 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1666 if (mouse_mode == MouseObject && is_drawable()) {
1667 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1668 _over_region_trim_target = true;
1671 break;
1674 case AutomationTrackItem:
1675 if (is_drawable()) {
1676 Gdk::Cursor *cursor;
1677 switch (mouse_mode) {
1678 case MouseRange:
1679 cursor = _cursors->selector;
1680 break;
1681 case MouseZoom:
1682 cursor = _cursors->zoom_in;
1683 break;
1684 default:
1685 cursor = _cursors->cross_hair;
1686 break;
1689 set_canvas_cursor (cursor);
1691 AutomationTimeAxisView* atv;
1692 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1693 clear_entered_track = false;
1694 set_entered_track (atv);
1697 break;
1699 case MarkerBarItem:
1700 case RangeMarkerBarItem:
1701 case TransportMarkerBarItem:
1702 case CdMarkerBarItem:
1703 case MeterBarItem:
1704 case TempoBarItem:
1705 if (is_drawable()) {
1706 set_canvas_cursor (_cursors->timebar);
1708 break;
1710 case MarkerItem:
1711 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1712 break;
1714 entered_marker = marker;
1715 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1716 // fall through
1717 case MeterMarkerItem:
1718 case TempoMarkerItem:
1719 if (is_drawable()) {
1720 set_canvas_cursor (_cursors->timebar);
1722 break;
1724 case FadeInHandleItem:
1725 if (mouse_mode == MouseObject && !internal_editing()) {
1726 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1727 if (rect) {
1728 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1730 set_canvas_cursor (_cursors->fade_in);
1732 break;
1734 case FadeOutHandleItem:
1735 if (mouse_mode == MouseObject && !internal_editing()) {
1736 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1737 if (rect) {
1738 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1740 set_canvas_cursor (_cursors->fade_out);
1742 break;
1743 case FeatureLineItem:
1745 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1746 line->property_fill_color_rgba() = 0xFF0000FF;
1748 break;
1749 case SelectionItem:
1750 if (join_object_range_button.get_active()) {
1751 set_canvas_cursor ();
1753 break;
1755 default:
1756 break;
1759 /* second pass to handle entered track status in a comprehensible way.
1762 switch (item_type) {
1763 case GainLineItem:
1764 case AutomationLineItem:
1765 case ControlPointItem:
1766 /* these do not affect the current entered track state */
1767 clear_entered_track = false;
1768 break;
1770 case AutomationTrackItem:
1771 /* handled above already */
1772 break;
1774 default:
1775 set_entered_track (0);
1776 break;
1779 return ret;
1782 bool
1783 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1785 AutomationLine* al;
1786 ControlPoint* cp;
1787 Marker *marker;
1788 Location *loc;
1789 RegionView* rv;
1790 bool is_start;
1791 bool ret = true;
1793 switch (item_type) {
1794 case ControlPointItem:
1795 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1796 if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
1797 if (cp->line().npoints() > 1 && !cp->get_selected()) {
1798 cp->set_visible (false);
1802 if (is_drawable()) {
1803 set_canvas_cursor (current_canvas_cursor);
1806 _verbose_cursor->hide ();
1807 break;
1809 case RegionViewNameHighlight:
1810 case LeftFrameHandle:
1811 case RightFrameHandle:
1812 case StartSelectionTrimItem:
1813 case EndSelectionTrimItem:
1814 case PlayheadCursorItem:
1816 #ifdef WITH_CMT
1817 case ImageFrameHandleStartItem:
1818 case ImageFrameHandleEndItem:
1819 case MarkerViewHandleStartItem:
1820 case MarkerViewHandleEndItem:
1821 #endif
1823 _over_region_trim_target = false;
1825 if (is_drawable()) {
1826 set_canvas_cursor (current_canvas_cursor);
1828 break;
1830 case GainLineItem:
1831 case AutomationLineItem:
1832 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1834 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1835 if (line)
1836 line->property_fill_color_rgba() = al->get_line_color();
1838 if (is_drawable()) {
1839 set_canvas_cursor (current_canvas_cursor);
1841 break;
1843 case RegionViewName:
1844 /* see enter_handler() for notes */
1845 _over_region_trim_target = false;
1847 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1848 if (is_drawable() && mouse_mode == MouseObject) {
1849 set_canvas_cursor (current_canvas_cursor);
1852 break;
1854 case RangeMarkerBarItem:
1855 case TransportMarkerBarItem:
1856 case CdMarkerBarItem:
1857 case MeterBarItem:
1858 case TempoBarItem:
1859 case MarkerBarItem:
1860 if (is_drawable()) {
1861 set_canvas_cursor (current_canvas_cursor);
1863 break;
1865 case MarkerItem:
1866 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1867 break;
1869 entered_marker = 0;
1870 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1871 location_flags_changed (loc, this);
1873 // fall through
1874 case MeterMarkerItem:
1875 case TempoMarkerItem:
1877 if (is_drawable()) {
1878 set_canvas_cursor (_cursors->timebar);
1881 break;
1883 case FadeInHandleItem:
1884 case FadeOutHandleItem:
1885 rv = static_cast<RegionView*>(item->get_data ("regionview"));
1887 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1888 if (rect) {
1889 rect->property_fill_color_rgba() = rv->get_fill_color();
1890 rect->property_outline_pixels() = 0;
1893 set_canvas_cursor (current_canvas_cursor);
1894 break;
1896 case AutomationTrackItem:
1897 if (is_drawable()) {
1898 set_canvas_cursor (current_canvas_cursor);
1899 clear_entered_track = true;
1900 Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track));
1902 break;
1903 case FeatureLineItem:
1905 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1906 line->property_fill_color_rgba() = (guint) ARDOUR_UI::config()->canvasvar_ZeroLine.get();;
1908 break;
1910 default:
1911 break;
1914 return ret;
1917 gint
1918 Editor::left_automation_track ()
1920 if (clear_entered_track) {
1921 set_entered_track (0);
1922 clear_entered_track = false;
1924 return false;
1927 void
1928 Editor::scrub (framepos_t frame, double current_x)
1930 double delta;
1932 if (scrubbing_direction == 0) {
1933 /* first move */
1934 _session->request_locate (frame, false);
1935 _session->request_transport_speed (0.1);
1936 scrubbing_direction = 1;
1938 } else {
1940 if (last_scrub_x > current_x) {
1942 /* pointer moved to the left */
1944 if (scrubbing_direction > 0) {
1946 /* we reversed direction to go backwards */
1948 scrub_reversals++;
1949 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1951 } else {
1953 /* still moving to the left (backwards) */
1955 scrub_reversals = 0;
1956 scrub_reverse_distance = 0;
1958 delta = 0.01 * (last_scrub_x - current_x);
1959 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1962 } else {
1963 /* pointer moved to the right */
1965 if (scrubbing_direction < 0) {
1966 /* we reversed direction to go forward */
1968 scrub_reversals++;
1969 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1971 } else {
1972 /* still moving to the right */
1974 scrub_reversals = 0;
1975 scrub_reverse_distance = 0;
1977 delta = 0.01 * (current_x - last_scrub_x);
1978 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1982 /* if there have been more than 2 opposite motion moves detected, or one that moves
1983 back more than 10 pixels, reverse direction
1986 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1988 if (scrubbing_direction > 0) {
1989 /* was forwards, go backwards */
1990 _session->request_transport_speed (-0.1);
1991 scrubbing_direction = -1;
1992 } else {
1993 /* was backwards, go forwards */
1994 _session->request_transport_speed (0.1);
1995 scrubbing_direction = 1;
1998 scrub_reverse_distance = 0;
1999 scrub_reversals = 0;
2003 last_scrub_x = current_x;
2006 bool
2007 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2009 _last_motion_y = event->motion.y;
2011 if (event->motion.is_hint) {
2012 gint x, y;
2014 /* We call this so that MOTION_NOTIFY events continue to be
2015 delivered to the canvas. We need to do this because we set
2016 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2017 the density of the events, at the expense of a round-trip
2018 to the server. Given that this will mostly occur on cases
2019 where DISPLAY = :0.0, and given the cost of what the motion
2020 event might do, its a good tradeoff.
2023 track_canvas->get_pointer (x, y);
2026 if (current_stepping_trackview) {
2027 /* don't keep the persistent stepped trackview if the mouse moves */
2028 current_stepping_trackview = 0;
2029 step_timeout.disconnect ();
2032 if (_session && _session->actively_recording()) {
2033 /* Sorry. no dragging stuff around while we record */
2034 return true;
2037 JoinObjectRangeState const old = _join_object_range_state;
2038 update_join_object_range_location (event->motion.x, event->motion.y);
2039 if (_join_object_range_state != old) {
2040 set_canvas_cursor ();
2043 if (_over_region_trim_target) {
2044 set_canvas_cursor_for_region_view (event->motion.x, entered_regionview);
2047 bool handled = false;
2048 if (_drags->active ()) {
2049 handled = _drags->motion_handler (event, from_autoscroll);
2052 if (!handled) {
2053 return false;
2056 track_canvas_motion (event);
2057 return true;
2060 void
2061 Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* /*event*/)
2063 ControlPoint* control_point;
2065 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2066 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2067 /*NOTREACHED*/
2070 // We shouldn't remove the first or last gain point
2071 if (control_point->line().is_last_point(*control_point) ||
2072 control_point->line().is_first_point(*control_point)) {
2073 return;
2076 control_point->line().remove_point (*control_point);
2079 void
2080 Editor::remove_control_point (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2082 ControlPoint* control_point;
2084 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2085 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2086 /*NOTREACHED*/
2089 control_point->line().remove_point (*control_point);
2092 void
2093 Editor::edit_control_point (ArdourCanvas::Item* item)
2095 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2097 if (p == 0) {
2098 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2099 /*NOTREACHED*/
2102 ControlPointDialog d (p);
2103 d.set_position (Gtk::WIN_POS_MOUSE);
2104 ensure_float (d);
2106 if (d.run () != RESPONSE_ACCEPT) {
2107 return;
2110 p->line().modify_point_y (*p, d.get_y_fraction ());
2113 void
2114 Editor::edit_note (ArdourCanvas::Item* item)
2116 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2117 assert (e);
2119 EditNoteDialog d (&e->region_view(), e);
2120 d.set_position (Gtk::WIN_POS_MOUSE);
2121 ensure_float (d);
2123 d.run ();
2127 void
2128 Editor::visible_order_range (int* low, int* high) const
2130 *low = TimeAxisView::max_order ();
2131 *high = 0;
2133 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2135 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2137 if (!rtv->hidden()) {
2139 if (*high < rtv->order()) {
2140 *high = rtv->order ();
2143 if (*low > rtv->order()) {
2144 *low = rtv->order ();
2150 void
2151 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2153 /* Either add to or set the set the region selection, unless
2154 this is an alignment click (control used)
2157 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2158 TimeAxisView* tv = &rv.get_time_axis_view();
2159 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2160 double speed = 1.0;
2161 if (rtv && rtv->is_track()) {
2162 speed = rtv->track()->speed();
2165 framepos_t where = get_preferred_edit_position();
2167 if (where >= 0) {
2169 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2171 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2173 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2175 align_region (rv.region(), End, (framepos_t) (where * speed));
2177 } else {
2179 align_region (rv.region(), Start, (framepos_t) (where * speed));
2185 void
2186 Editor::collect_new_region_view (RegionView* rv)
2188 latest_regionviews.push_back (rv);
2191 void
2192 Editor::collect_and_select_new_region_view (RegionView* rv)
2194 selection->add(rv);
2195 latest_regionviews.push_back (rv);
2198 void
2199 Editor::cancel_selection ()
2201 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2202 (*i)->hide_selection ();
2205 selection->clear ();
2206 clicked_selection = 0;
2210 void
2211 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2213 RegionView* rv = clicked_regionview;
2215 /* Choose action dependant on which button was pressed */
2216 switch (event->button.button) {
2217 case 1:
2218 begin_reversible_command (_("start point trim"));
2220 if (selection->selected (rv)) {
2221 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2222 i != selection->regions.by_layer().end(); ++i)
2224 if ( (*i) == NULL){
2225 cerr << "region view contains null region" << endl;
2228 if (!(*i)->region()->locked()) {
2229 (*i)->region()->clear_changes ();
2230 (*i)->region()->trim_front (new_bound);
2231 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2235 } else {
2236 if (!rv->region()->locked()) {
2237 rv->region()->clear_changes ();
2238 rv->region()->trim_front (new_bound);
2239 _session->add_command(new StatefulDiffCommand (rv->region()));
2243 commit_reversible_command();
2245 break;
2246 case 2:
2247 begin_reversible_command (_("End point trim"));
2249 if (selection->selected (rv)) {
2251 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2253 if (!(*i)->region()->locked()) {
2254 (*i)->region()->clear_changes();
2255 (*i)->region()->trim_end (new_bound);
2256 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2260 } else {
2262 if (!rv->region()->locked()) {
2263 rv->region()->clear_changes ();
2264 rv->region()->trim_end (new_bound);
2265 _session->add_command (new StatefulDiffCommand (rv->region()));
2269 commit_reversible_command();
2271 break;
2272 default:
2273 break;
2277 void
2278 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2280 Marker* marker;
2281 bool is_start;
2283 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2284 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2285 /*NOTREACHED*/
2288 Location* location = find_location_from_marker (marker, is_start);
2289 location->set_hidden (true, this);
2293 void
2294 Editor::reposition_zoom_rect (framepos_t start, framepos_t end)
2296 double x1 = frame_to_pixel (start);
2297 double x2 = frame_to_pixel (end);
2298 double y2 = full_canvas_height - 1.0;
2300 zoom_rect->property_x1() = x1;
2301 zoom_rect->property_y1() = 1.0;
2302 zoom_rect->property_x2() = x2;
2303 zoom_rect->property_y2() = y2;
2307 gint
2308 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2310 using namespace Gtkmm2ext;
2312 ArdourPrompter prompter (false);
2314 prompter.set_prompt (_("Name for region:"));
2315 prompter.set_initial_text (clicked_regionview->region()->name());
2316 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2317 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2318 prompter.show_all ();
2319 switch (prompter.run ()) {
2320 case Gtk::RESPONSE_ACCEPT:
2321 string str;
2322 prompter.get_result(str);
2323 if (str.length()) {
2324 clicked_regionview->region()->set_name (str);
2326 break;
2328 return true;
2332 void
2333 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2335 /* no brushing without a useful snap setting */
2337 switch (_snap_mode) {
2338 case SnapMagnetic:
2339 return; /* can't work because it allows region to be placed anywhere */
2340 default:
2341 break; /* OK */
2344 switch (_snap_type) {
2345 case SnapToMark:
2346 return;
2348 default:
2349 break;
2352 /* don't brush a copy over the original */
2354 if (pos == rv->region()->position()) {
2355 return;
2358 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2360 if (rtv == 0 || !rtv->is_track()) {
2361 return;
2364 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2365 double speed = rtv->track()->speed();
2367 playlist->clear_changes ();
2368 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2369 playlist->add_region (new_region, (framepos_t) (pos * speed));
2370 _session->add_command (new StatefulDiffCommand (playlist));
2372 // playlist is frozen, so we have to update manually XXX this is disgusting
2374 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2377 gint
2378 Editor::track_height_step_timeout ()
2380 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2381 current_stepping_trackview = 0;
2382 return false;
2384 return true;
2387 void
2388 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2390 assert (region_view);
2392 if (!region_view->region()->playlist()) {
2393 return;
2396 _region_motion_group->raise_to_top ();
2398 if (Config->get_edit_mode() == Splice) {
2399 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2400 } else {
2401 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2402 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false));
2405 /* sync the canvas to what we think is its current state */
2406 update_canvas_now();
2409 void
2410 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2412 assert (region_view);
2414 if (!region_view->region()->playlist()) {
2415 return;
2418 _region_motion_group->raise_to_top ();
2420 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2421 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true));
2424 void
2425 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2427 assert (region_view);
2429 if (!region_view->region()->playlist()) {
2430 return;
2433 if (Config->get_edit_mode() == Splice) {
2434 return;
2437 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2438 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), true, false));
2440 begin_reversible_command (Operations::drag_region_brush);
2443 /** Start a grab where a time range is selected, track(s) are selected, and the
2444 * user clicks and drags a region with a modifier in order to create a new region containing
2445 * the section of the clicked region that lies within the time range.
2447 void
2448 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2450 if (clicked_regionview == 0) {
2451 return;
2454 /* lets try to create new Region for the selection */
2456 vector<boost::shared_ptr<Region> > new_regions;
2457 create_region_from_selection (new_regions);
2459 if (new_regions.empty()) {
2460 return;
2463 /* XXX fix me one day to use all new regions */
2465 boost::shared_ptr<Region> region (new_regions.front());
2467 /* add it to the current stream/playlist.
2469 tricky: the streamview for the track will add a new regionview. we will
2470 catch the signal it sends when it creates the regionview to
2471 set the regionview we want to then drag.
2474 latest_regionviews.clear();
2475 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2477 /* A selection grab currently creates two undo/redo operations, one for
2478 creating the new region and another for moving it.
2481 begin_reversible_command (Operations::selection_grab);
2483 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2485 playlist->clear_changes ();
2486 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2487 _session->add_command(new StatefulDiffCommand (playlist));
2489 commit_reversible_command ();
2491 c.disconnect ();
2493 if (latest_regionviews.empty()) {
2494 /* something went wrong */
2495 return;
2498 /* we need to deselect all other regionviews, and select this one
2499 i'm ignoring undo stuff, because the region creation will take care of it
2501 selection->set (latest_regionviews);
2503 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2506 void
2507 Editor::escape ()
2509 if (_drags->active ()) {
2510 _drags->abort ();
2511 } else {
2512 selection->clear ();
2516 void
2517 Editor::set_internal_edit (bool yn)
2519 _internal_editing = yn;
2521 if (yn) {
2522 mouse_select_button.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
2523 mouse_select_button.get_image ()->show ();
2524 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Draw/Edit MIDI Notes"));
2525 mouse_mode_toggled (mouse_mode);
2527 pre_internal_mouse_mode = mouse_mode;
2529 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2530 (*i)->enter_internal_edit_mode ();
2533 } else {
2535 mouse_select_button.set_image (*(manage (new Image (::get_icon("tool_range")))));
2536 mouse_select_button.get_image ()->show ();
2537 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Select/Move Ranges"));
2538 mouse_mode_toggled (mouse_mode); // sets cursor
2540 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2541 (*i)->leave_internal_edit_mode ();
2544 if (mouse_mode == MouseRange && pre_internal_mouse_mode != MouseRange) {
2545 /* we were drawing .. flip back to something sensible */
2546 set_mouse_mode (pre_internal_mouse_mode);
2551 /** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
2552 * used by the `join object/range' tool mode.
2554 void
2555 Editor::update_join_object_range_location (double x, double y)
2557 /* XXX: actually, this decides based on whether the mouse is in the top or bottom half of a RouteTimeAxisView;
2558 entered_{track,regionview} is not always setup (e.g. if the mouse is over a TimeSelection), and to get a Region
2559 that we're over requires searching the playlist.
2562 if (join_object_range_button.get_active() == false || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
2563 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2564 return;
2567 if (mouse_mode == MouseObject) {
2568 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2569 } else if (mouse_mode == MouseRange) {
2570 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2573 /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
2574 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y + vertical_adjustment.get_value() - canvas_timebars_vsize);
2576 if (tvp.first) {
2578 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2579 if (rtv) {
2581 double cx = 0;
2582 double cy = y;
2583 rtv->canvas_display()->w2i (cx, cy);
2585 double const c = cy / rtv->view()->child_height();
2586 double d;
2587 double const f = modf (c, &d);
2589 _join_object_range_state = f < 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2594 Editing::MouseMode
2595 Editor::effective_mouse_mode () const
2597 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2598 return MouseObject;
2599 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2600 return MouseRange;
2603 return mouse_mode;
2606 void
2607 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2609 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2610 assert (e);
2612 e->region_view().delete_note (e->note ());
2615 void
2616 Editor::set_canvas_cursor_for_region_view (double x, RegionView* rv)
2618 assert (rv);
2620 ArdourCanvas::Group* g = rv->get_canvas_group ();
2621 ArdourCanvas::Group* p = g->get_parent_group ();
2623 /* Compute x in region view parent coordinates */
2624 double dy = 0;
2625 p->w2i (x, dy);
2627 double x1, x2, y1, y2;
2628 g->get_bounds (x1, y1, x2, y2);
2630 /* Halfway across the region */
2631 double const h = (x1 + x2) / 2;
2633 Trimmable::CanTrim ct = rv->region()->can_trim ();
2634 if (x <= h) {
2635 if (ct & Trimmable::FrontTrimEarlier) {
2636 set_canvas_cursor (_cursors->left_side_trim);
2637 } else {
2638 set_canvas_cursor (_cursors->left_side_trim_right_only);
2640 } else {
2641 if (ct & Trimmable::EndTrimLater) {
2642 set_canvas_cursor (_cursors->right_side_trim);
2643 } else {
2644 set_canvas_cursor (_cursors->right_side_trim_left_only);
2649 /** Obtain the pointer position in world coordinates */
2650 void
2651 Editor::get_pointer_position (double& x, double& y) const
2653 int px, py;
2654 track_canvas->get_pointer (px, py);
2655 track_canvas->window_to_world (px, py, x, y);