lincoln's patch to use QM onset detection in RFerret, and other tweaks
[ardour2.git] / gtk2_ardour / editor_mouse.cc
blob94044933a8500bf42f94d0cf98416e00512e7867
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 <gtkmm2ext/utils.h>
31 #include <gtkmm2ext/tearoff.h>
32 #include "pbd/memento_command.h"
33 #include "pbd/basename.h"
34 #include "pbd/stateful_diff_command.h"
36 #include "ardour_ui.h"
37 #include "actions.h"
38 #include "canvas-note.h"
39 #include "editor.h"
40 #include "time_axis_view.h"
41 #include "audio_time_axis.h"
42 #include "audio_region_view.h"
43 #include "midi_region_view.h"
44 #include "marker.h"
45 #include "streamview.h"
46 #include "region_gain_line.h"
47 #include "automation_time_axis.h"
48 #include "control_point.h"
49 #include "prompter.h"
50 #include "utils.h"
51 #include "selection.h"
52 #include "keyboard.h"
53 #include "editing.h"
54 #include "rgb_macros.h"
55 #include "control_point_dialog.h"
56 #include "editor_drag.h"
57 #include "automation_region_view.h"
58 #include "edit_note_dialog.h"
59 #include "mouse_cursors.h"
60 #include "editor_cursors.h"
62 #include "ardour/types.h"
63 #include "ardour/profile.h"
64 #include "ardour/route.h"
65 #include "ardour/audio_track.h"
66 #include "ardour/audio_diskstream.h"
67 #include "ardour/midi_diskstream.h"
68 #include "ardour/playlist.h"
69 #include "ardour/audioplaylist.h"
70 #include "ardour/audioregion.h"
71 #include "ardour/midi_region.h"
72 #include "ardour/dB.h"
73 #include "ardour/utils.h"
74 #include "ardour/region_factory.h"
75 #include "ardour/source_factory.h"
76 #include "ardour/session.h"
77 #include "ardour/operations.h"
79 #include <bitset>
81 #include "i18n.h"
83 using namespace std;
84 using namespace ARDOUR;
85 using namespace PBD;
86 using namespace Gtk;
87 using namespace Editing;
88 using Gtkmm2ext::Keyboard;
90 bool
91 Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
93 int x, y;
94 double wx, wy;
95 Gdk::ModifierType mask;
96 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
97 Glib::RefPtr<const Gdk::Window> pointer_window;
99 if (!canvas_window) {
100 return false;
103 pointer_window = canvas_window->get_pointer (x, y, mask);
105 if (pointer_window == track_canvas->get_bin_window()) {
106 wx = x;
107 wy = y;
108 in_track_canvas = true;
110 } else {
111 in_track_canvas = false;
112 return false;
115 GdkEvent event;
116 event.type = GDK_BUTTON_RELEASE;
117 event.button.x = wx;
118 event.button.y = wy;
120 where = event_frame (&event, 0, 0);
121 return true;
124 framepos_t
125 Editor::event_frame (GdkEvent const * event, double* pcx, double* pcy) const
127 double cx, cy;
129 if (pcx == 0) {
130 pcx = &cx;
132 if (pcy == 0) {
133 pcy = &cy;
136 *pcx = 0;
137 *pcy = 0;
139 switch (event->type) {
140 case GDK_BUTTON_RELEASE:
141 case GDK_BUTTON_PRESS:
142 case GDK_2BUTTON_PRESS:
143 case GDK_3BUTTON_PRESS:
144 *pcx = event->button.x;
145 *pcy = event->button.y;
146 _trackview_group->w2i(*pcx, *pcy);
147 break;
148 case GDK_MOTION_NOTIFY:
149 *pcx = event->motion.x;
150 *pcy = event->motion.y;
151 _trackview_group->w2i(*pcx, *pcy);
152 break;
153 case GDK_ENTER_NOTIFY:
154 case GDK_LEAVE_NOTIFY:
155 track_canvas->w2c(event->crossing.x, event->crossing.y, *pcx, *pcy);
156 break;
157 case GDK_KEY_PRESS:
158 case GDK_KEY_RELEASE:
159 // track_canvas->w2c(event->key.x, event->key.y, *pcx, *pcy);
160 break;
161 default:
162 warning << string_compose (_("Editor::event_frame() used on unhandled event type %1"), event->type) << endmsg;
163 break;
166 /* note that pixel_to_frame() never returns less than zero, so even if the pixel
167 position is negative (as can be the case with motion events in particular),
168 the frame location is always positive.
171 return pixel_to_frame (*pcx);
174 Gdk::Cursor*
175 Editor::which_grabber_cursor ()
177 Gdk::Cursor* c = _cursors->grabber;
179 if (_internal_editing) {
180 switch (mouse_mode) {
181 case MouseRange:
182 c = _cursors->midi_pencil;
183 break;
185 case MouseObject:
186 c = _cursors->grabber_note;
187 break;
189 case MouseTimeFX:
190 c = _cursors->midi_resize;
191 break;
193 default:
194 break;
197 } else {
199 switch (_edit_point) {
200 case EditAtMouse:
201 c = _cursors->grabber_edit_point;
202 break;
203 default:
204 boost::shared_ptr<Movable> m = _movable.lock();
205 if (m && m->locked()) {
206 c = _cursors->speaker;
208 break;
212 return c;
215 void
216 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
218 boost::shared_ptr<Trimmable> st = _trimmable.lock();
220 if (!st || st == t) {
221 _trimmable = t;
222 set_canvas_cursor ();
226 void
227 Editor::set_current_movable (boost::shared_ptr<Movable> m)
229 boost::shared_ptr<Movable> sm = _movable.lock();
231 if (!sm || sm != m) {
232 _movable = m;
233 set_canvas_cursor ();
237 void
238 Editor::set_canvas_cursor ()
240 if (_internal_editing) {
242 switch (mouse_mode) {
243 case MouseRange:
244 current_canvas_cursor = _cursors->midi_pencil;
245 break;
247 case MouseObject:
248 current_canvas_cursor = which_grabber_cursor();
249 break;
251 case MouseTimeFX:
252 current_canvas_cursor = _cursors->midi_resize;
253 break;
255 default:
256 return;
259 } else {
261 switch (mouse_mode) {
262 case MouseRange:
263 current_canvas_cursor = _cursors->selector;
264 break;
266 case MouseObject:
267 current_canvas_cursor = which_grabber_cursor();
268 break;
270 case MouseGain:
271 current_canvas_cursor = _cursors->cross_hair;
272 break;
274 case MouseZoom:
275 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
276 current_canvas_cursor = _cursors->zoom_out;
277 } else {
278 current_canvas_cursor = _cursors->zoom_in;
280 break;
282 case MouseTimeFX:
283 current_canvas_cursor = _cursors->time_fx; // just use playhead
284 break;
286 case MouseAudition:
287 current_canvas_cursor = _cursors->speaker;
288 break;
292 switch (_join_object_range_state) {
293 case JOIN_OBJECT_RANGE_NONE:
294 break;
295 case JOIN_OBJECT_RANGE_OBJECT:
296 current_canvas_cursor = which_grabber_cursor ();
297 break;
298 case JOIN_OBJECT_RANGE_RANGE:
299 current_canvas_cursor = _cursors->selector;
300 break;
303 /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
304 if (join_object_range_button.get_active() && last_item_entered) {
305 if (last_item_entered->property_parent() && (*last_item_entered->property_parent()).get_data (X_("timeselection"))) {
306 pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y + vertical_adjustment.get_value() - canvas_timebars_vsize);
307 if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
308 current_canvas_cursor = _cursors->up_down;
313 set_canvas_cursor (current_canvas_cursor, true);
316 void
317 Editor::set_mouse_mode (MouseMode m, bool force)
319 if (_drags->active ()) {
320 return;
323 if (!force && m == mouse_mode) {
324 return;
327 Glib::RefPtr<Action> act;
329 switch (m) {
330 case MouseRange:
331 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
332 break;
334 case MouseObject:
335 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
336 break;
338 case MouseGain:
339 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-gain"));
340 break;
342 case MouseZoom:
343 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-zoom"));
344 break;
346 case MouseTimeFX:
347 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
348 break;
350 case MouseAudition:
351 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
352 break;
355 assert (act);
357 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
358 assert (tact);
360 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
361 tact->set_active (false);
362 tact->set_active (true);
364 MouseModeChanged (); /* EMIT SIGNAL */
367 void
368 Editor::mouse_mode_toggled (MouseMode m)
370 mouse_mode = m;
372 instant_save ();
374 if (!internal_editing()) {
375 if (mouse_mode != MouseRange && _join_object_range_state == JOIN_OBJECT_RANGE_NONE) {
377 /* in all modes except range and joined object/range, hide the range selection,
378 show the object (region) selection.
381 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
382 (*i)->hide_selection ();
385 } else {
388 in range or object/range mode, show the range selection.
391 for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
392 (*i)->show_selection (selection->time);
397 set_canvas_cursor ();
399 MouseModeChanged (); /* EMIT SIGNAL */
402 void
403 Editor::step_mouse_mode (bool next)
405 switch (current_mouse_mode()) {
406 case MouseObject:
407 if (next) {
408 if (Profile->get_sae()) {
409 set_mouse_mode (MouseZoom);
410 } else {
411 set_mouse_mode (MouseRange);
413 } else {
414 set_mouse_mode (MouseTimeFX);
416 break;
418 case MouseRange:
419 if (next) set_mouse_mode (MouseZoom);
420 else set_mouse_mode (MouseObject);
421 break;
423 case MouseZoom:
424 if (next) {
425 if (Profile->get_sae()) {
426 set_mouse_mode (MouseTimeFX);
427 } else {
428 set_mouse_mode (MouseGain);
430 } else {
431 if (Profile->get_sae()) {
432 set_mouse_mode (MouseObject);
433 } else {
434 set_mouse_mode (MouseRange);
437 break;
439 case MouseGain:
440 if (next) set_mouse_mode (MouseTimeFX);
441 else set_mouse_mode (MouseZoom);
442 break;
444 case MouseTimeFX:
445 if (next) {
446 set_mouse_mode (MouseAudition);
447 } else {
448 if (Profile->get_sae()) {
449 set_mouse_mode (MouseZoom);
450 } else {
451 set_mouse_mode (MouseGain);
454 break;
456 case MouseAudition:
457 if (next) set_mouse_mode (MouseObject);
458 else set_mouse_mode (MouseTimeFX);
459 break;
463 void
464 Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemType item_type)
466 /* in object/audition/timefx/gain-automation mode,
467 any button press sets the selection if the object
468 can be selected. this is a bit of hack, because
469 we want to avoid this if the mouse operation is a
470 region alignment.
472 note: not dbl-click or triple-click
474 Also note that there is no region selection in internal edit mode, otherwise
475 for operations operating on the selection (e.g. cut) it is not obvious whether
476 to cut notes or regions.
479 if (((mouse_mode != MouseObject) &&
480 (_join_object_range_state != JOIN_OBJECT_RANGE_OBJECT) &&
481 (mouse_mode != MouseAudition || item_type != RegionItem) &&
482 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
483 (mouse_mode != MouseGain) &&
484 (mouse_mode != MouseRange)) ||
485 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3) ||
486 internal_editing()) {
488 return;
491 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
493 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
495 /* almost no selection action on modified button-2 or button-3 events */
497 if (item_type != RegionItem && event->button.button != 2) {
498 return;
503 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
504 bool press = (event->type == GDK_BUTTON_PRESS);
506 // begin_reversible_command (_("select on click"));
508 switch (item_type) {
509 case RegionItem:
510 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
511 set_selected_regionview_from_click (press, op, true);
512 } else if (event->type == GDK_BUTTON_PRESS) {
513 selection->clear_tracks ();
514 set_selected_track_as_side_effect (op, true);
516 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
517 clicked_selection = select_range_around_region (selection->regions.front());
519 break;
521 case RegionViewNameHighlight:
522 case RegionViewName:
523 case LeftFrameHandle:
524 case RightFrameHandle:
525 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
526 set_selected_regionview_from_click (press, op, true);
527 } else if (event->type == GDK_BUTTON_PRESS) {
528 set_selected_track_as_side_effect (op);
530 break;
533 case FadeInHandleItem:
534 case FadeInItem:
535 case FadeOutHandleItem:
536 case FadeOutItem:
537 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
538 set_selected_regionview_from_click (press, op, true);
539 } else if (event->type == GDK_BUTTON_PRESS) {
540 set_selected_track_as_side_effect (op);
542 break;
544 case ControlPointItem:
545 set_selected_track_as_side_effect (op, true);
546 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
547 set_selected_control_point_from_click (op, false);
549 break;
551 case StreamItem:
552 /* for context click, select track */
553 if (event->button.button == 3) {
554 selection->clear_tracks ();
555 set_selected_track_as_side_effect (op, true);
557 break;
559 case AutomationTrackItem:
560 set_selected_track_as_side_effect (op, true);
561 break;
563 default:
564 break;
568 bool
569 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
571 /* single mouse clicks on any of these item types operate
572 independent of mouse mode, mostly because they are
573 not on the main track canvas or because we want
574 them to be modeless.
577 switch (item_type) {
578 case PlayheadCursorItem:
579 _drags->set (new CursorDrag (this, item, true), event);
580 return true;
582 case MarkerItem:
583 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
584 hide_marker (item, event);
585 } else {
586 _drags->set (new MarkerDrag (this, item), event);
588 return true;
590 case TempoMarkerItem:
591 _drags->set (
592 new TempoMarkerDrag (
593 this,
594 item,
595 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
597 event
599 return true;
601 case MeterMarkerItem:
602 _drags->set (
603 new MeterMarkerDrag (
604 this,
605 item,
606 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
608 event
610 return true;
612 case MarkerBarItem:
613 case TempoBarItem:
614 case MeterBarItem:
615 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
616 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
618 return true;
619 break;
622 case RangeMarkerBarItem:
623 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
624 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
625 } else {
626 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
628 return true;
629 break;
631 case CdMarkerBarItem:
632 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
633 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
634 } else {
635 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
637 return true;
638 break;
640 case TransportMarkerBarItem:
641 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
642 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
643 } else {
644 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
646 return true;
647 break;
649 default:
650 break;
653 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
654 /* special case: allow trim of range selections in joined object mode;
655 in theory eff should equal MouseRange in this case, but it doesn't
656 because entering the range selection canvas item results in entered_regionview
657 being set to 0, so update_join_object_range_location acts as if we aren't
658 over a region.
660 if (item_type == StartSelectionTrimItem) {
661 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
662 } else if (item_type == EndSelectionTrimItem) {
663 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
667 Editing::MouseMode eff = effective_mouse_mode ();
669 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
670 if (item_type == FadeInHandleItem || item_type == FadeOutHandleItem) {
671 eff = MouseObject;
674 switch (eff) {
675 case MouseRange:
676 switch (item_type) {
677 case StartSelectionTrimItem:
678 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
679 break;
681 case EndSelectionTrimItem:
682 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
683 break;
685 case SelectionItem:
686 if (Keyboard::modifier_state_contains
687 (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier))) {
688 // contains and not equals because I can't use alt as a modifier alone.
689 start_selection_grab (item, event);
690 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
691 /* grab selection for moving */
692 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
693 } else {
694 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
695 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
696 if (tvp.first) {
697 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
698 if (join_object_range_button.get_active() && atv) {
699 /* smart "join" mode: drag automation */
700 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, _cursors->up_down);
701 } else {
702 /* this was debated, but decided the more common action was to
703 make a new selection */
704 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
708 break;
710 case NoteItem:
711 if (internal_editing()) {
712 /* trim notes if we're in internal edit mode and near the ends of the note */
713 ArdourCanvas::CanvasNote* cn = dynamic_cast<ArdourCanvas::CanvasNote*> (item);
714 cerr << "NoteItem button press, cursor = " << current_canvas_cursor << endl;
715 if (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);
829 else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
830 add_region_brush_drag (item, event, clicked_regionview);
831 } else {
832 add_region_drag (item, event, clicked_regionview);
835 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
836 _drags->add (new SelectionDrag (this, clicked_axisview->get_selection_rect (clicked_selection)->rect, SelectionDrag::SelectionMove));
839 _drags->start_grab (event);
840 break;
842 case RegionViewNameHighlight:
843 case LeftFrameHandle:
844 case RightFrameHandle:
845 if (!clicked_regionview->region()->locked()) {
846 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
847 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
848 return true;
850 break;
852 case RegionViewName:
854 /* rename happens on edit clicks */
855 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
856 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, s.by_layer()), event);
857 return true;
858 break;
861 case ControlPointItem:
862 _drags->set (new ControlPointDrag (this, item), event);
863 return true;
864 break;
866 case AutomationLineItem:
867 _drags->set (new LineDrag (this, item), event);
868 return true;
869 break;
871 case StreamItem:
872 if (internal_editing()) {
873 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
874 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
876 return true;
877 } else {
878 _drags->set (new RubberbandSelectDrag (this, item), event);
880 break;
882 case AutomationTrackItem:
883 /* rubberband drag to select automation points */
884 _drags->set (new RubberbandSelectDrag (this, item), event);
885 break;
887 case SelectionItem:
889 if (join_object_range_button.get_active()) {
890 /* we're in "smart" joined mode, and we've clicked on a Selection */
891 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
892 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
893 if (tvp.first) {
894 /* if we're over an automation track, start a drag of its data */
895 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
896 if (atv) {
897 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, _cursors->up_down);
900 /* if we're over a track and a region, and in the `object' part of a region,
901 put a selection around the region and drag both
903 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
904 if (rtv && _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
905 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (rtv->route ());
906 if (t) {
907 boost::shared_ptr<Playlist> pl = t->playlist ();
908 if (pl) {
910 boost::shared_ptr<Region> r = pl->top_region_at (event_frame (event));
911 if (r) {
912 RegionView* rv = rtv->view()->find_view (r);
913 clicked_selection = select_range_around_region (rv);
914 _drags->add (new SelectionDrag (this, item, SelectionDrag::SelectionMove));
915 list<RegionView*> rvs;
916 rvs.push_back (rv);
917 _drags->add (new RegionMoveDrag (this, item, rv, rvs, false, false));
918 _drags->start_grab (event);
925 break;
928 #ifdef WITH_CMT
929 case ImageFrameHandleStartItem:
930 imageframe_start_handle_op(item, event) ;
931 return(true) ;
932 break ;
933 case ImageFrameHandleEndItem:
934 imageframe_end_handle_op(item, event) ;
935 return(true) ;
936 break ;
937 case MarkerViewHandleStartItem:
938 markerview_item_start_handle_op(item, event) ;
939 return(true) ;
940 break ;
941 case MarkerViewHandleEndItem:
942 markerview_item_end_handle_op(item, event) ;
943 return(true) ;
944 break ;
945 case MarkerViewItem:
946 start_markerview_grab(item, event) ;
947 break ;
948 case ImageFrameItem:
949 start_imageframe_grab(item, event) ;
950 break ;
951 #endif
953 case MarkerBarItem:
955 break;
957 default:
958 break;
961 return true;
962 break;
964 case MouseGain:
965 switch (item_type) {
966 case RegionItem:
967 /* start a grab so that if we finish after moving
968 we can tell what happened.
970 _drags->set (new RegionGainDrag (this, item), event, current_canvas_cursor);
971 break;
973 case GainLineItem:
974 _drags->set (new LineDrag (this, item), event);
975 return true;
977 case ControlPointItem:
978 _drags->set (new ControlPointDrag (this, item), event);
979 return true;
980 break;
982 default:
983 break;
985 return true;
986 break;
988 switch (item_type) {
989 case ControlPointItem:
990 _drags->set (new ControlPointDrag (this, item), event);
991 break;
993 case AutomationLineItem:
994 _drags->set (new LineDrag (this, item), event);
995 break;
997 case RegionItem:
998 // XXX need automation mode to identify which
999 // line to use
1000 // start_line_grab_from_regionview (item, event);
1001 break;
1003 default:
1004 break;
1006 return true;
1007 break;
1009 case MouseZoom:
1010 if (event->type == GDK_BUTTON_PRESS) {
1011 _drags->set (new MouseZoomDrag (this, item), event);
1014 return true;
1015 break;
1017 case MouseTimeFX:
1018 if (internal_editing() && item_type == NoteItem) {
1019 /* drag notes if we're in internal edit mode */
1020 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
1021 return true;
1022 } else if ((!internal_editing() || dynamic_cast<AudioRegionView*> (clicked_regionview)) && clicked_regionview) {
1023 /* do time-FX if we're not in internal edit mode, or we are but we clicked on an audio region */
1024 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1025 return true;
1027 break;
1029 case MouseAudition:
1030 _drags->set (new ScrubDrag (this, item), event);
1031 scrub_reversals = 0;
1032 scrub_reverse_distance = 0;
1033 last_scrub_x = event->button.x;
1034 scrubbing_direction = 0;
1035 set_canvas_cursor (_cursors->transparent);
1036 return true;
1037 break;
1039 default:
1040 break;
1043 return false;
1046 bool
1047 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1049 Editing::MouseMode const eff = effective_mouse_mode ();
1050 switch (eff) {
1051 case MouseObject:
1052 switch (item_type) {
1053 case RegionItem:
1054 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1055 add_region_copy_drag (item, event, clicked_regionview);
1056 } else {
1057 add_region_drag (item, event, clicked_regionview);
1059 _drags->start_grab (event);
1060 return true;
1061 break;
1062 case ControlPointItem:
1063 _drags->set (new ControlPointDrag (this, item), event);
1064 return true;
1065 break;
1067 default:
1068 break;
1071 switch (item_type) {
1072 case RegionViewNameHighlight:
1073 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1074 return true;
1075 break;
1077 case LeftFrameHandle:
1078 case RightFrameHandle:
1079 if (!internal_editing ()) {
1080 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1082 return true;
1083 break;
1085 case RegionViewName:
1086 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1087 return true;
1088 break;
1090 default:
1091 break;
1094 break;
1096 case MouseRange:
1097 /* relax till release */
1098 return true;
1099 break;
1102 case MouseZoom:
1103 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1104 temporal_zoom_to_frame (false, event_frame (event));
1105 } else {
1106 temporal_zoom_to_frame (true, event_frame(event));
1108 return true;
1109 break;
1111 default:
1112 break;
1115 return false;
1118 bool
1119 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1121 if (event->type != GDK_BUTTON_PRESS) {
1122 return false;
1125 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
1127 if (canvas_window) {
1128 Glib::RefPtr<const Gdk::Window> pointer_window;
1129 int x, y;
1130 double wx, wy;
1131 Gdk::ModifierType mask;
1133 pointer_window = canvas_window->get_pointer (x, y, mask);
1135 if (pointer_window == track_canvas->get_bin_window()) {
1136 track_canvas->window_to_world (x, y, wx, wy);
1140 pre_press_cursor = current_canvas_cursor;
1142 track_canvas->grab_focus();
1144 if (_session && _session->actively_recording()) {
1145 return true;
1148 button_selection (item, event, item_type);
1150 if (!_drags->active () &&
1151 (Keyboard::is_delete_event (&event->button) ||
1152 Keyboard::is_context_menu_event (&event->button) ||
1153 Keyboard::is_edit_event (&event->button))) {
1155 /* handled by button release */
1156 return true;
1159 switch (event->button.button) {
1160 case 1:
1161 return button_press_handler_1 (item, event, item_type);
1162 break;
1164 case 2:
1165 return button_press_handler_2 (item, event, item_type);
1166 break;
1168 case 3:
1169 break;
1171 default:
1172 break;
1176 return false;
1179 bool
1180 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1182 framepos_t where = event_frame (event, 0, 0);
1183 AutomationTimeAxisView* atv = 0;
1185 if (pre_press_cursor) {
1186 set_canvas_cursor (pre_press_cursor);
1187 pre_press_cursor = 0;
1190 /* no action if we're recording */
1192 if (_session && _session->actively_recording()) {
1193 return true;
1196 /* see if we're finishing a drag */
1198 bool were_dragging = false;
1199 if (_drags->active ()) {
1200 bool const r = _drags->end_grab (event);
1201 if (r) {
1202 /* grab dragged, so do nothing else */
1203 return true;
1206 were_dragging = true;
1209 update_region_layering_order_editor ();
1211 /* edit events get handled here */
1213 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1214 switch (item_type) {
1215 case RegionItem:
1216 show_region_properties ();
1217 break;
1219 case TempoMarkerItem:
1220 edit_tempo_marker (item);
1221 break;
1223 case MeterMarkerItem:
1224 edit_meter_marker (item);
1225 break;
1227 case RegionViewName:
1228 if (clicked_regionview->name_active()) {
1229 return mouse_rename_region (item, event);
1231 break;
1233 case ControlPointItem:
1234 edit_control_point (item);
1235 break;
1237 case NoteItem:
1238 edit_note (item);
1239 break;
1241 default:
1242 break;
1244 return true;
1247 /* context menu events get handled here */
1249 if (Keyboard::is_context_menu_event (&event->button)) {
1251 if (!_drags->active ()) {
1253 /* no matter which button pops up the context menu, tell the menu
1254 widget to use button 1 to drive menu selection.
1257 switch (item_type) {
1258 case FadeInItem:
1259 case FadeInHandleItem:
1260 case FadeOutItem:
1261 case FadeOutHandleItem:
1262 popup_fade_context_menu (1, event->button.time, item, item_type);
1263 break;
1265 case StreamItem:
1266 popup_track_context_menu (1, event->button.time, item_type, false);
1267 break;
1269 case RegionItem:
1270 case RegionViewNameHighlight:
1271 case LeftFrameHandle:
1272 case RightFrameHandle:
1273 case RegionViewName:
1274 popup_track_context_menu (1, event->button.time, item_type, false);
1275 break;
1277 case SelectionItem:
1278 popup_track_context_menu (1, event->button.time, item_type, true);
1279 break;
1281 case AutomationTrackItem:
1282 popup_track_context_menu (1, event->button.time, item_type, false);
1283 break;
1285 case MarkerBarItem:
1286 case RangeMarkerBarItem:
1287 case TransportMarkerBarItem:
1288 case CdMarkerBarItem:
1289 case TempoBarItem:
1290 case MeterBarItem:
1291 popup_ruler_menu (where, item_type);
1292 break;
1294 case MarkerItem:
1295 marker_context_menu (&event->button, item);
1296 break;
1298 case TempoMarkerItem:
1299 tempo_or_meter_marker_context_menu (&event->button, item);
1300 break;
1302 case MeterMarkerItem:
1303 tempo_or_meter_marker_context_menu (&event->button, item);
1304 break;
1306 case CrossfadeViewItem:
1307 popup_track_context_menu (1, event->button.time, item_type, false);
1308 break;
1310 #ifdef WITH_CMT
1311 case ImageFrameItem:
1312 popup_imageframe_edit_menu(1, event->button.time, item, true) ;
1313 break ;
1314 case ImageFrameTimeAxisItem:
1315 popup_imageframe_edit_menu(1, event->button.time, item, false) ;
1316 break ;
1317 case MarkerViewItem:
1318 popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ;
1319 break ;
1320 case MarkerTimeAxisItem:
1321 popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
1322 break ;
1323 #endif
1325 default:
1326 break;
1329 return true;
1333 /* delete events get handled here */
1335 Editing::MouseMode const eff = effective_mouse_mode ();
1337 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1339 switch (item_type) {
1340 case TempoMarkerItem:
1341 remove_tempo_marker (item);
1342 break;
1344 case MeterMarkerItem:
1345 remove_meter_marker (item);
1346 break;
1348 case MarkerItem:
1349 remove_marker (*item, event);
1350 break;
1352 case RegionItem:
1353 if (eff == MouseObject) {
1354 remove_clicked_region ();
1356 break;
1358 case ControlPointItem:
1359 if (eff == MouseGain) {
1360 remove_gain_control_point (item, event);
1361 } else {
1362 remove_control_point (item, event);
1364 break;
1366 case NoteItem:
1367 remove_midi_note (item, event);
1368 break;
1370 default:
1371 break;
1373 return true;
1376 switch (event->button.button) {
1377 case 1:
1379 switch (item_type) {
1380 /* see comments in button_press_handler */
1381 case PlayheadCursorItem:
1382 case MarkerItem:
1383 case GainLineItem:
1384 case AutomationLineItem:
1385 case StartSelectionTrimItem:
1386 case EndSelectionTrimItem:
1387 return true;
1389 case MarkerBarItem:
1390 if (!_dragging_playhead) {
1391 snap_to_with_modifier (where, event, 0, true);
1392 mouse_add_new_marker (where);
1394 return true;
1396 case CdMarkerBarItem:
1397 if (!_dragging_playhead) {
1398 // if we get here then a dragged range wasn't done
1399 snap_to_with_modifier (where, event, 0, true);
1400 mouse_add_new_marker (where, true);
1402 return true;
1404 case TempoBarItem:
1405 if (!_dragging_playhead) {
1406 snap_to_with_modifier (where, event);
1407 mouse_add_new_tempo_event (where);
1409 return true;
1411 case MeterBarItem:
1412 if (!_dragging_playhead) {
1413 mouse_add_new_meter_event (pixel_to_frame (event->button.x));
1415 return true;
1416 break;
1418 default:
1419 break;
1422 switch (eff) {
1423 case MouseObject:
1424 switch (item_type) {
1425 case AutomationTrackItem:
1426 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1427 if (atv) {
1428 atv->add_automation_event (item, event, where, event->button.y);
1430 return true;
1431 break;
1433 default:
1434 break;
1436 break;
1438 case MouseGain:
1439 switch (item_type) {
1440 case RegionItem:
1442 /* check that we didn't drag before releasing, since
1443 its really annoying to create new control
1444 points when doing this.
1446 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1447 if (were_dragging && arv) {
1448 arv->add_gain_point_event (item, event);
1450 return true;
1451 break;
1454 case AutomationTrackItem:
1455 dynamic_cast<AutomationTimeAxisView*>(clicked_axisview)->
1456 add_automation_event (item, event, where, event->button.y);
1457 return true;
1458 break;
1459 default:
1460 break;
1462 break;
1464 case MouseAudition:
1465 set_canvas_cursor (current_canvas_cursor);
1466 if (scrubbing_direction == 0) {
1467 /* no drag, just a click */
1468 switch (item_type) {
1469 case RegionItem:
1470 play_selected_region ();
1471 break;
1472 default:
1473 break;
1475 } else {
1476 /* make sure we stop */
1477 _session->request_transport_speed (0.0);
1479 break;
1481 default:
1482 break;
1486 return true;
1487 break;
1490 case 2:
1491 switch (eff) {
1493 case MouseObject:
1494 switch (item_type) {
1495 case RegionItem:
1496 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1497 raise_region ();
1498 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1499 lower_region ();
1500 } else {
1501 // Button2 click is unused
1503 return true;
1505 break;
1507 default:
1508 break;
1510 break;
1512 case MouseRange:
1514 // x_style_paste (where, 1.0);
1515 return true;
1516 break;
1518 default:
1519 break;
1522 break;
1524 case 3:
1525 break;
1527 default:
1528 break;
1530 return false;
1533 bool
1534 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1536 ControlPoint* cp;
1537 Marker * marker;
1538 double fraction;
1539 bool ret = true;
1541 last_item_entered = item;
1543 switch (item_type) {
1544 case ControlPointItem:
1545 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1546 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1547 cp->set_visible (true);
1549 double at_x, at_y;
1550 at_x = cp->get_x();
1551 at_y = cp->get_y ();
1552 cp->i2w (at_x, at_y);
1553 at_x += 10.0;
1554 at_y += 10.0;
1556 fraction = 1.0 - (cp->get_y() / cp->line().height());
1558 if (is_drawable() && !_drags->active ()) {
1559 set_canvas_cursor (_cursors->fader);
1562 set_verbose_canvas_cursor (cp->line().get_verbose_cursor_string (fraction), at_x, at_y);
1563 show_verbose_canvas_cursor ();
1565 break;
1567 case GainLineItem:
1568 if (mouse_mode == MouseGain) {
1569 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1570 if (line)
1571 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
1572 if (is_drawable()) {
1573 set_canvas_cursor (_cursors->fader);
1576 break;
1578 case AutomationLineItem:
1579 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1581 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1582 if (line)
1583 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
1585 if (is_drawable()) {
1586 set_canvas_cursor (_cursors->fader);
1589 break;
1591 case RegionViewNameHighlight:
1592 if (is_drawable() && mouse_mode == MouseObject && entered_regionview) {
1593 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1594 _over_region_trim_target = true;
1596 break;
1598 case LeftFrameHandle:
1599 case RightFrameHandle:
1600 if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
1601 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1603 break;
1605 case StartSelectionTrimItem:
1606 case EndSelectionTrimItem:
1608 #ifdef WITH_CMT
1609 case ImageFrameHandleStartItem:
1610 case ImageFrameHandleEndItem:
1611 case MarkerViewHandleStartItem:
1612 case MarkerViewHandleEndItem:
1613 #endif
1615 if (is_drawable()) {
1616 set_canvas_cursor (_cursors->trimmer);
1618 break;
1620 case PlayheadCursorItem:
1621 if (is_drawable()) {
1622 switch (_edit_point) {
1623 case EditAtMouse:
1624 set_canvas_cursor (_cursors->grabber_edit_point);
1625 break;
1626 default:
1627 set_canvas_cursor (_cursors->grabber);
1628 break;
1631 break;
1633 case RegionViewName:
1635 /* when the name is not an active item, the entire name highlight is for trimming */
1637 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1638 if (mouse_mode == MouseObject && is_drawable()) {
1639 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1640 _over_region_trim_target = true;
1643 break;
1646 case AutomationTrackItem:
1647 if (is_drawable()) {
1648 Gdk::Cursor *cursor;
1649 switch (mouse_mode) {
1650 case MouseRange:
1651 cursor = _cursors->selector;
1652 break;
1653 case MouseZoom:
1654 cursor = _cursors->zoom_in;
1655 break;
1656 default:
1657 cursor = _cursors->cross_hair;
1658 break;
1661 set_canvas_cursor (cursor);
1663 AutomationTimeAxisView* atv;
1664 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1665 clear_entered_track = false;
1666 set_entered_track (atv);
1669 break;
1671 case MarkerBarItem:
1672 case RangeMarkerBarItem:
1673 case TransportMarkerBarItem:
1674 case CdMarkerBarItem:
1675 case MeterBarItem:
1676 case TempoBarItem:
1677 if (is_drawable()) {
1678 set_canvas_cursor (_cursors->timebar);
1680 break;
1682 case MarkerItem:
1683 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1684 break;
1686 entered_marker = marker;
1687 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1688 // fall through
1689 case MeterMarkerItem:
1690 case TempoMarkerItem:
1691 if (is_drawable()) {
1692 set_canvas_cursor (_cursors->timebar);
1694 break;
1696 case FadeInHandleItem:
1697 if (mouse_mode == MouseObject && !internal_editing()) {
1698 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1699 if (rect) {
1700 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1702 set_canvas_cursor (_cursors->fade_in);
1704 break;
1706 case FadeOutHandleItem:
1707 if (mouse_mode == MouseObject && !internal_editing()) {
1708 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1709 if (rect) {
1710 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1712 set_canvas_cursor (_cursors->fade_out);
1714 break;
1715 case FeatureLineItem:
1717 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1718 line->property_fill_color_rgba() = 0xFF0000FF;
1720 break;
1721 case SelectionItem:
1722 if (join_object_range_button.get_active()) {
1723 set_canvas_cursor ();
1725 break;
1727 default:
1728 break;
1731 /* second pass to handle entered track status in a comprehensible way.
1734 switch (item_type) {
1735 case GainLineItem:
1736 case AutomationLineItem:
1737 case ControlPointItem:
1738 /* these do not affect the current entered track state */
1739 clear_entered_track = false;
1740 break;
1742 case AutomationTrackItem:
1743 /* handled above already */
1744 break;
1746 default:
1747 set_entered_track (0);
1748 break;
1751 return ret;
1754 bool
1755 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1757 AutomationLine* al;
1758 ControlPoint* cp;
1759 Marker *marker;
1760 Location *loc;
1761 RegionView* rv;
1762 bool is_start;
1763 bool ret = true;
1765 switch (item_type) {
1766 case ControlPointItem:
1767 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1768 if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
1769 if (cp->line().npoints() > 1 && !cp->get_selected()) {
1770 cp->set_visible (false);
1774 if (is_drawable()) {
1775 set_canvas_cursor (current_canvas_cursor);
1778 hide_verbose_canvas_cursor ();
1779 break;
1781 case RegionViewNameHighlight:
1782 case LeftFrameHandle:
1783 case RightFrameHandle:
1784 case StartSelectionTrimItem:
1785 case EndSelectionTrimItem:
1786 case PlayheadCursorItem:
1788 #ifdef WITH_CMT
1789 case ImageFrameHandleStartItem:
1790 case ImageFrameHandleEndItem:
1791 case MarkerViewHandleStartItem:
1792 case MarkerViewHandleEndItem:
1793 #endif
1795 _over_region_trim_target = false;
1797 if (is_drawable()) {
1798 set_canvas_cursor (current_canvas_cursor);
1800 break;
1802 case GainLineItem:
1803 case AutomationLineItem:
1804 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1806 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1807 if (line)
1808 line->property_fill_color_rgba() = al->get_line_color();
1810 if (is_drawable()) {
1811 set_canvas_cursor (current_canvas_cursor);
1813 break;
1815 case RegionViewName:
1816 /* see enter_handler() for notes */
1817 _over_region_trim_target = false;
1819 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1820 if (is_drawable() && mouse_mode == MouseObject) {
1821 set_canvas_cursor (current_canvas_cursor);
1824 break;
1826 case RangeMarkerBarItem:
1827 case TransportMarkerBarItem:
1828 case CdMarkerBarItem:
1829 case MeterBarItem:
1830 case TempoBarItem:
1831 case MarkerBarItem:
1832 if (is_drawable()) {
1833 set_canvas_cursor (current_canvas_cursor);
1835 break;
1837 case MarkerItem:
1838 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1839 break;
1841 entered_marker = 0;
1842 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1843 location_flags_changed (loc, this);
1845 // fall through
1846 case MeterMarkerItem:
1847 case TempoMarkerItem:
1849 if (is_drawable()) {
1850 set_canvas_cursor (_cursors->timebar);
1853 break;
1855 case FadeInHandleItem:
1856 case FadeOutHandleItem:
1857 rv = static_cast<RegionView*>(item->get_data ("regionview"));
1859 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1860 if (rect) {
1861 rect->property_fill_color_rgba() = rv->get_fill_color();
1862 rect->property_outline_pixels() = 0;
1865 set_canvas_cursor (current_canvas_cursor);
1866 break;
1868 case AutomationTrackItem:
1869 if (is_drawable()) {
1870 set_canvas_cursor (current_canvas_cursor);
1871 clear_entered_track = true;
1872 Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track));
1874 break;
1875 case FeatureLineItem:
1877 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1878 line->property_fill_color_rgba() = (guint) ARDOUR_UI::config()->canvasvar_ZeroLine.get();;
1880 break;
1882 default:
1883 break;
1886 return ret;
1889 gint
1890 Editor::left_automation_track ()
1892 if (clear_entered_track) {
1893 set_entered_track (0);
1894 clear_entered_track = false;
1896 return false;
1899 void
1900 Editor::scrub (framepos_t frame, double current_x)
1902 double delta;
1904 if (scrubbing_direction == 0) {
1905 /* first move */
1906 _session->request_locate (frame, false);
1907 _session->request_transport_speed (0.1);
1908 scrubbing_direction = 1;
1910 } else {
1912 if (last_scrub_x > current_x) {
1914 /* pointer moved to the left */
1916 if (scrubbing_direction > 0) {
1918 /* we reversed direction to go backwards */
1920 scrub_reversals++;
1921 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1923 } else {
1925 /* still moving to the left (backwards) */
1927 scrub_reversals = 0;
1928 scrub_reverse_distance = 0;
1930 delta = 0.01 * (last_scrub_x - current_x);
1931 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1934 } else {
1935 /* pointer moved to the right */
1937 if (scrubbing_direction < 0) {
1938 /* we reversed direction to go forward */
1940 scrub_reversals++;
1941 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1943 } else {
1944 /* still moving to the right */
1946 scrub_reversals = 0;
1947 scrub_reverse_distance = 0;
1949 delta = 0.01 * (current_x - last_scrub_x);
1950 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1954 /* if there have been more than 2 opposite motion moves detected, or one that moves
1955 back more than 10 pixels, reverse direction
1958 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1960 if (scrubbing_direction > 0) {
1961 /* was forwards, go backwards */
1962 _session->request_transport_speed (-0.1);
1963 scrubbing_direction = -1;
1964 } else {
1965 /* was backwards, go forwards */
1966 _session->request_transport_speed (0.1);
1967 scrubbing_direction = 1;
1970 scrub_reverse_distance = 0;
1971 scrub_reversals = 0;
1975 last_scrub_x = current_x;
1978 bool
1979 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1981 _last_motion_y = event->motion.y;
1983 if (event->motion.is_hint) {
1984 gint x, y;
1986 /* We call this so that MOTION_NOTIFY events continue to be
1987 delivered to the canvas. We need to do this because we set
1988 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1989 the density of the events, at the expense of a round-trip
1990 to the server. Given that this will mostly occur on cases
1991 where DISPLAY = :0.0, and given the cost of what the motion
1992 event might do, its a good tradeoff.
1995 track_canvas->get_pointer (x, y);
1998 if (current_stepping_trackview) {
1999 /* don't keep the persistent stepped trackview if the mouse moves */
2000 current_stepping_trackview = 0;
2001 step_timeout.disconnect ();
2004 if (_session && _session->actively_recording()) {
2005 /* Sorry. no dragging stuff around while we record */
2006 return true;
2009 JoinObjectRangeState const old = _join_object_range_state;
2010 update_join_object_range_location (event->motion.x, event->motion.y);
2011 if (_join_object_range_state != old) {
2012 set_canvas_cursor ();
2015 if (_over_region_trim_target) {
2016 set_canvas_cursor_for_region_view (event->motion.x, entered_regionview);
2019 bool handled = false;
2020 if (_drags->active ()) {
2021 handled = _drags->motion_handler (event, from_autoscroll);
2024 if (!handled) {
2025 return false;
2028 track_canvas_motion (event);
2029 return true;
2032 void
2033 Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* /*event*/)
2035 ControlPoint* control_point;
2037 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2038 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2039 /*NOTREACHED*/
2042 // We shouldn't remove the first or last gain point
2043 if (control_point->line().is_last_point(*control_point) ||
2044 control_point->line().is_first_point(*control_point)) {
2045 return;
2048 control_point->line().remove_point (*control_point);
2051 void
2052 Editor::remove_control_point (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2054 ControlPoint* control_point;
2056 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2057 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2058 /*NOTREACHED*/
2061 control_point->line().remove_point (*control_point);
2064 void
2065 Editor::edit_control_point (ArdourCanvas::Item* item)
2067 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2069 if (p == 0) {
2070 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2071 /*NOTREACHED*/
2074 ControlPointDialog d (p);
2075 d.set_position (Gtk::WIN_POS_MOUSE);
2076 ensure_float (d);
2078 if (d.run () != RESPONSE_ACCEPT) {
2079 return;
2082 p->line().modify_point_y (*p, d.get_y_fraction ());
2085 void
2086 Editor::edit_note (ArdourCanvas::Item* item)
2088 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2089 assert (e);
2091 EditNoteDialog d (&e->region_view(), e);
2092 d.set_position (Gtk::WIN_POS_MOUSE);
2093 ensure_float (d);
2095 d.run ();
2099 void
2100 Editor::visible_order_range (int* low, int* high) const
2102 *low = TimeAxisView::max_order ();
2103 *high = 0;
2105 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2107 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2109 if (!rtv->hidden()) {
2111 if (*high < rtv->order()) {
2112 *high = rtv->order ();
2115 if (*low > rtv->order()) {
2116 *low = rtv->order ();
2122 void
2123 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2125 /* Either add to or set the set the region selection, unless
2126 this is an alignment click (control used)
2129 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2130 TimeAxisView* tv = &rv.get_time_axis_view();
2131 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2132 double speed = 1.0;
2133 if (rtv && rtv->is_track()) {
2134 speed = rtv->track()->speed();
2137 framepos_t where = get_preferred_edit_position();
2139 if (where >= 0) {
2141 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2143 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2145 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2147 align_region (rv.region(), End, (framepos_t) (where * speed));
2149 } else {
2151 align_region (rv.region(), Start, (framepos_t) (where * speed));
2157 void
2158 Editor::show_verbose_time_cursor (framepos_t frame, double offset, double xpos, double ypos)
2160 char buf[128];
2161 Timecode::Time timecode;
2162 Timecode::BBT_Time bbt;
2163 int hours, mins;
2164 framepos_t frame_rate;
2165 float secs;
2167 if (_session == 0) {
2168 return;
2171 AudioClock::Mode m;
2173 if (Profile->get_sae() || Profile->get_small_screen()) {
2174 m = ARDOUR_UI::instance()->primary_clock.mode();
2175 } else {
2176 m = ARDOUR_UI::instance()->secondary_clock.mode();
2179 switch (m) {
2180 case AudioClock::BBT:
2181 _session->bbt_time (frame, bbt);
2182 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, bbt.bars, bbt.beats, bbt.ticks);
2183 break;
2185 case AudioClock::Timecode:
2186 _session->timecode_time (frame, timecode);
2187 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2188 break;
2190 case AudioClock::MinSec:
2191 /* XXX this is copied from show_verbose_duration_cursor() */
2192 frame_rate = _session->frame_rate();
2193 hours = frame / (frame_rate * 3600);
2194 frame = frame % (frame_rate * 3600);
2195 mins = frame / (frame_rate * 60);
2196 frame = frame % (frame_rate * 60);
2197 secs = (float) frame / (float) frame_rate;
2198 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
2199 break;
2201 default:
2202 snprintf (buf, sizeof(buf), "%" PRIi64, frame);
2203 break;
2206 if (xpos >= 0 && ypos >=0) {
2207 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2208 } else {
2209 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset - horizontal_position(), _drags->current_pointer_y() + offset - vertical_adjustment.get_value() + canvas_timebars_vsize);
2211 show_verbose_canvas_cursor ();
2214 void
2215 Editor::show_verbose_duration_cursor (framepos_t start, framepos_t end, double offset, double xpos, double ypos)
2217 char buf[128];
2218 Timecode::Time timecode;
2219 Timecode::BBT_Time sbbt;
2220 Timecode::BBT_Time ebbt;
2221 int hours, mins;
2222 framepos_t distance, frame_rate;
2223 float secs;
2224 Meter meter_at_start(_session->tempo_map().meter_at(start));
2226 if (_session == 0) {
2227 return;
2230 AudioClock::Mode m;
2232 if (Profile->get_sae() || Profile->get_small_screen()) {
2233 m = ARDOUR_UI::instance()->primary_clock.mode ();
2234 } else {
2235 m = ARDOUR_UI::instance()->secondary_clock.mode ();
2238 switch (m) {
2239 case AudioClock::BBT:
2240 _session->bbt_time (start, sbbt);
2241 _session->bbt_time (end, ebbt);
2243 /* subtract */
2244 /* XXX this computation won't work well if the
2245 user makes a selection that spans any meter changes.
2248 ebbt.bars -= sbbt.bars;
2249 if (ebbt.beats >= sbbt.beats) {
2250 ebbt.beats -= sbbt.beats;
2251 } else {
2252 ebbt.bars--;
2253 ebbt.beats = int(meter_at_start.beats_per_bar()) + ebbt.beats - sbbt.beats;
2255 if (ebbt.ticks >= sbbt.ticks) {
2256 ebbt.ticks -= sbbt.ticks;
2257 } else {
2258 ebbt.beats--;
2259 ebbt.ticks = int(Timecode::BBT_Time::ticks_per_beat) + ebbt.ticks - sbbt.ticks;
2262 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, ebbt.bars, ebbt.beats, ebbt.ticks);
2263 break;
2265 case AudioClock::Timecode:
2266 _session->timecode_duration (end - start, timecode);
2267 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2268 break;
2270 case AudioClock::MinSec:
2271 /* XXX this stuff should be elsewhere.. */
2272 distance = end - start;
2273 frame_rate = _session->frame_rate();
2274 hours = distance / (frame_rate * 3600);
2275 distance = distance % (frame_rate * 3600);
2276 mins = distance / (frame_rate * 60);
2277 distance = distance % (frame_rate * 60);
2278 secs = (float) distance / (float) frame_rate;
2279 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
2280 break;
2282 default:
2283 snprintf (buf, sizeof(buf), "%" PRIi64, end - start);
2284 break;
2287 if (xpos >= 0 && ypos >=0) {
2288 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2290 else {
2291 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset, _drags->current_pointer_y() + offset);
2294 show_verbose_canvas_cursor ();
2297 void
2298 Editor::collect_new_region_view (RegionView* rv)
2300 latest_regionviews.push_back (rv);
2303 void
2304 Editor::collect_and_select_new_region_view (RegionView* rv)
2306 selection->add(rv);
2307 latest_regionviews.push_back (rv);
2310 void
2311 Editor::cancel_selection ()
2313 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2314 (*i)->hide_selection ();
2317 selection->clear ();
2318 clicked_selection = 0;
2322 void
2323 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2325 RegionView* rv = clicked_regionview;
2327 /* Choose action dependant on which button was pressed */
2328 switch (event->button.button) {
2329 case 1:
2330 begin_reversible_command (_("start point trim"));
2332 if (selection->selected (rv)) {
2333 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2334 i != selection->regions.by_layer().end(); ++i)
2336 if ( (*i) == NULL){
2337 cerr << "region view contains null region" << endl;
2340 if (!(*i)->region()->locked()) {
2341 (*i)->region()->clear_changes ();
2342 (*i)->region()->trim_front (new_bound, this);
2343 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2347 } else {
2348 if (!rv->region()->locked()) {
2349 rv->region()->clear_changes ();
2350 rv->region()->trim_front (new_bound, this);
2351 _session->add_command(new StatefulDiffCommand (rv->region()));
2355 commit_reversible_command();
2357 break;
2358 case 2:
2359 begin_reversible_command (_("End point trim"));
2361 if (selection->selected (rv)) {
2363 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2365 if (!(*i)->region()->locked()) {
2366 (*i)->region()->clear_changes();
2367 (*i)->region()->trim_end (new_bound, this);
2368 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2372 } else {
2374 if (!rv->region()->locked()) {
2375 rv->region()->clear_changes ();
2376 rv->region()->trim_end (new_bound, this);
2377 _session->add_command (new StatefulDiffCommand (rv->region()));
2381 commit_reversible_command();
2383 break;
2384 default:
2385 break;
2389 void
2390 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2392 Marker* marker;
2393 bool is_start;
2395 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2396 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2397 /*NOTREACHED*/
2400 Location* location = find_location_from_marker (marker, is_start);
2401 location->set_hidden (true, this);
2405 void
2406 Editor::reposition_zoom_rect (framepos_t start, framepos_t end)
2408 double x1 = frame_to_pixel (start);
2409 double x2 = frame_to_pixel (end);
2410 double y2 = full_canvas_height - 1.0;
2412 zoom_rect->property_x1() = x1;
2413 zoom_rect->property_y1() = 1.0;
2414 zoom_rect->property_x2() = x2;
2415 zoom_rect->property_y2() = y2;
2419 gint
2420 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2422 using namespace Gtkmm2ext;
2424 ArdourPrompter prompter (false);
2426 prompter.set_prompt (_("Name for region:"));
2427 prompter.set_initial_text (clicked_regionview->region()->name());
2428 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2429 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2430 prompter.show_all ();
2431 switch (prompter.run ()) {
2432 case Gtk::RESPONSE_ACCEPT:
2433 string str;
2434 prompter.get_result(str);
2435 if (str.length()) {
2436 clicked_regionview->region()->set_name (str);
2438 break;
2440 return true;
2444 void
2445 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2447 /* no brushing without a useful snap setting */
2449 switch (_snap_mode) {
2450 case SnapMagnetic:
2451 return; /* can't work because it allows region to be placed anywhere */
2452 default:
2453 break; /* OK */
2456 switch (_snap_type) {
2457 case SnapToMark:
2458 return;
2460 default:
2461 break;
2464 /* don't brush a copy over the original */
2466 if (pos == rv->region()->position()) {
2467 return;
2470 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2472 if (rtv == 0 || !rtv->is_track()) {
2473 return;
2476 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2477 double speed = rtv->track()->speed();
2479 playlist->clear_changes ();
2480 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2481 playlist->add_region (new_region, (framepos_t) (pos * speed));
2482 _session->add_command (new StatefulDiffCommand (playlist));
2484 // playlist is frozen, so we have to update manually XXX this is disgusting
2486 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2489 gint
2490 Editor::track_height_step_timeout ()
2492 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2493 current_stepping_trackview = 0;
2494 return false;
2496 return true;
2499 void
2500 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2502 assert (region_view);
2504 if (!region_view->region()->playlist()) {
2505 return;
2508 _region_motion_group->raise_to_top ();
2510 if (Config->get_edit_mode() == Splice) {
2511 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2512 } else {
2513 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2514 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false));
2517 /* sync the canvas to what we think is its current state */
2518 update_canvas_now();
2521 void
2522 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2524 assert (region_view);
2526 if (!region_view->region()->playlist()) {
2527 return;
2530 _region_motion_group->raise_to_top ();
2532 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2533 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true));
2536 void
2537 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2539 assert (region_view);
2541 if (!region_view->region()->playlist()) {
2542 return;
2545 if (Config->get_edit_mode() == Splice) {
2546 return;
2549 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2550 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), true, false));
2552 begin_reversible_command (Operations::drag_region_brush);
2555 /** Start a grab where a time range is selected, track(s) are selected, and the
2556 * user clicks and drags a region with a modifier in order to create a new region containing
2557 * the section of the clicked region that lies within the time range.
2559 void
2560 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2562 if (clicked_regionview == 0) {
2563 return;
2566 /* lets try to create new Region for the selection */
2568 vector<boost::shared_ptr<Region> > new_regions;
2569 create_region_from_selection (new_regions);
2571 if (new_regions.empty()) {
2572 return;
2575 /* XXX fix me one day to use all new regions */
2577 boost::shared_ptr<Region> region (new_regions.front());
2579 /* add it to the current stream/playlist.
2581 tricky: the streamview for the track will add a new regionview. we will
2582 catch the signal it sends when it creates the regionview to
2583 set the regionview we want to then drag.
2586 latest_regionviews.clear();
2587 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2589 /* A selection grab currently creates two undo/redo operations, one for
2590 creating the new region and another for moving it.
2593 begin_reversible_command (Operations::selection_grab);
2595 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2597 playlist->clear_changes ();
2598 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2599 _session->add_command(new StatefulDiffCommand (playlist));
2601 commit_reversible_command ();
2603 c.disconnect ();
2605 if (latest_regionviews.empty()) {
2606 /* something went wrong */
2607 return;
2610 /* we need to deselect all other regionviews, and select this one
2611 i'm ignoring undo stuff, because the region creation will take care of it
2613 selection->set (latest_regionviews);
2615 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2618 void
2619 Editor::escape ()
2621 if (_drags->active ()) {
2622 _drags->abort ();
2623 } else {
2624 selection->clear ();
2628 void
2629 Editor::set_internal_edit (bool yn)
2631 _internal_editing = yn;
2633 if (yn) {
2634 mouse_select_button.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
2635 mouse_select_button.get_image ()->show ();
2636 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Draw/Edit MIDI Notes"));
2637 mouse_mode_toggled (mouse_mode);
2639 } else {
2641 mouse_select_button.set_image (*(manage (new Image (::get_icon("tool_range")))));
2642 mouse_select_button.get_image ()->show ();
2643 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Select/Move Ranges"));
2644 mouse_mode_toggled (mouse_mode); // sets cursor
2648 /** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
2649 * used by the `join object/range' tool mode.
2651 void
2652 Editor::update_join_object_range_location (double x, double y)
2654 /* XXX: actually, this decides based on whether the mouse is in the top or bottom half of a RouteTimeAxisView;
2655 entered_{track,regionview} is not always setup (e.g. if the mouse is over a TimeSelection), and to get a Region
2656 that we're over requires searching the playlist.
2659 if (join_object_range_button.get_active() == false || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
2660 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2661 return;
2664 if (mouse_mode == MouseObject) {
2665 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2666 } else if (mouse_mode == MouseRange) {
2667 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2670 /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
2671 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y + vertical_adjustment.get_value() - canvas_timebars_vsize);
2673 if (tvp.first) {
2675 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2676 if (rtv) {
2678 double cx = 0;
2679 double cy = y;
2680 rtv->canvas_display()->w2i (cx, cy);
2682 double const c = cy / rtv->view()->child_height();
2683 double d;
2684 double const f = modf (c, &d);
2686 _join_object_range_state = f < 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2691 Editing::MouseMode
2692 Editor::effective_mouse_mode () const
2694 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2695 return MouseObject;
2696 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2697 return MouseRange;
2700 return mouse_mode;
2703 void
2704 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2706 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2707 assert (e);
2709 e->region_view().delete_note (e->note ());
2712 void
2713 Editor::set_canvas_cursor_for_region_view (double x, RegionView* rv)
2715 ArdourCanvas::Group* g = rv->get_canvas_group ();
2716 ArdourCanvas::Group* p = g->get_parent_group ();
2718 /* Compute x in region view parent coordinates */
2719 double dy = 0;
2720 p->w2i (x, dy);
2722 double x1, x2, y1, y2;
2723 g->get_bounds (x1, y1, x2, y2);
2725 /* Halfway across the region */
2726 double const h = (x1 + x2) / 2;
2728 Trimmable::CanTrim ct = rv->region()->can_trim ();
2729 if (x <= h) {
2730 if (ct & Trimmable::FrontTrimEarlier) {
2731 set_canvas_cursor (_cursors->left_side_trim);
2732 } else {
2733 set_canvas_cursor (_cursors->left_side_trim_right_only);
2735 } else {
2736 if (ct & Trimmable::EndTrimLater) {
2737 set_canvas_cursor (_cursors->right_side_trim);
2738 } else {
2739 set_canvas_cursor (_cursors->right_side_trim_left_only);