make panner data popups more contrasty and appear in a better position
[ardour2.git] / gtk2_ardour / editor_mouse.cc
blobe73975ba85d91346533c95adc64e5852d03340c0
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 (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
382 (*i)->set_should_show_selection (true);
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 // begin_reversible_command (_("select on click"));
511 switch (item_type) {
512 case RegionItem:
513 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
514 set_selected_regionview_from_click (press, op, true);
515 } else if (event->type == GDK_BUTTON_PRESS) {
516 selection->clear_tracks ();
517 set_selected_track_as_side_effect (op, true);
519 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
520 clicked_selection = select_range_around_region (selection->regions.front());
522 break;
524 case RegionViewNameHighlight:
525 case RegionViewName:
526 case LeftFrameHandle:
527 case RightFrameHandle:
528 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
529 set_selected_regionview_from_click (press, op, true);
530 } else if (event->type == GDK_BUTTON_PRESS) {
531 set_selected_track_as_side_effect (op);
533 break;
536 case FadeInHandleItem:
537 case FadeInItem:
538 case FadeOutHandleItem:
539 case FadeOutItem:
540 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
541 set_selected_regionview_from_click (press, op, true);
542 } else if (event->type == GDK_BUTTON_PRESS) {
543 set_selected_track_as_side_effect (op);
545 break;
547 case ControlPointItem:
548 set_selected_track_as_side_effect (op, true);
549 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
550 set_selected_control_point_from_click (op, false);
552 break;
554 case StreamItem:
555 /* for context click, select track */
556 if (event->button.button == 3) {
557 selection->clear_tracks ();
558 set_selected_track_as_side_effect (op, true);
560 break;
562 case AutomationTrackItem:
563 set_selected_track_as_side_effect (op, true);
564 break;
566 default:
567 break;
571 bool
572 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
574 /* single mouse clicks on any of these item types operate
575 independent of mouse mode, mostly because they are
576 not on the main track canvas or because we want
577 them to be modeless.
580 switch (item_type) {
581 case PlayheadCursorItem:
582 _drags->set (new CursorDrag (this, item, true), event);
583 return true;
585 case MarkerItem:
586 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
587 hide_marker (item, event);
588 } else {
589 _drags->set (new MarkerDrag (this, item), event);
591 return true;
593 case TempoMarkerItem:
594 _drags->set (
595 new TempoMarkerDrag (
596 this,
597 item,
598 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
600 event
602 return true;
604 case MeterMarkerItem:
605 _drags->set (
606 new MeterMarkerDrag (
607 this,
608 item,
609 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
611 event
613 return true;
615 case MarkerBarItem:
616 case TempoBarItem:
617 case MeterBarItem:
618 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
619 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
621 return true;
622 break;
625 case RangeMarkerBarItem:
626 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
627 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
628 } else {
629 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
631 return true;
632 break;
634 case CdMarkerBarItem:
635 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
636 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
637 } else {
638 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
640 return true;
641 break;
643 case TransportMarkerBarItem:
644 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
645 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
646 } else {
647 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
649 return true;
650 break;
652 default:
653 break;
656 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
657 /* special case: allow trim of range selections in joined object mode;
658 in theory eff should equal MouseRange in this case, but it doesn't
659 because entering the range selection canvas item results in entered_regionview
660 being set to 0, so update_join_object_range_location acts as if we aren't
661 over a region.
663 if (item_type == StartSelectionTrimItem) {
664 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
665 } else if (item_type == EndSelectionTrimItem) {
666 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
670 Editing::MouseMode eff = effective_mouse_mode ();
672 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
673 if (item_type == FadeInHandleItem || item_type == FadeOutHandleItem) {
674 eff = MouseObject;
677 switch (eff) {
678 case MouseRange:
679 switch (item_type) {
680 case StartSelectionTrimItem:
681 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
682 break;
684 case EndSelectionTrimItem:
685 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
686 break;
688 case SelectionItem:
689 if (Keyboard::modifier_state_contains
690 (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier))) {
691 // contains and not equals because I can't use alt as a modifier alone.
692 start_selection_grab (item, event);
693 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
694 /* grab selection for moving */
695 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
696 } else {
697 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
698 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
699 if (tvp.first) {
700 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
701 if (join_object_range_button.get_active() && atv) {
702 /* smart "join" mode: drag automation */
703 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, _cursors->up_down);
704 } else {
705 /* this was debated, but decided the more common action was to
706 make a new selection */
707 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
711 break;
713 case NoteItem:
714 if (internal_editing()) {
715 /* trim notes if we're in internal edit mode and near the ends of the note */
716 ArdourCanvas::CanvasNote* cn = dynamic_cast<ArdourCanvas::CanvasNote*> (item);
717 cerr << "NoteItem button press, cursor = " << current_canvas_cursor << endl;
718 if (cn->mouse_near_ends()) {
719 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
720 } else {
721 _drags->set (new NoteDrag (this, item), event);
724 return true;
726 case StreamItem:
727 if (internal_editing()) {
728 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
729 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
730 return true;
732 } else {
733 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
734 return true;
736 break;
738 case RegionViewNameHighlight:
739 case LeftFrameHandle:
740 case RightFrameHandle:
741 if (!clicked_regionview->region()->locked()) {
742 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
743 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
744 return true;
746 break;
748 default:
749 if (!internal_editing()) {
750 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
753 return true;
754 break;
756 case MouseObject:
757 switch (item_type) {
758 case NoteItem:
759 if (internal_editing()) {
760 ArdourCanvas::CanvasNoteEvent* cn = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
761 if (cn->mouse_near_ends()) {
762 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
763 } else {
764 _drags->set (new NoteDrag (this, item), event);
766 return true;
768 break;
770 default:
771 break;
774 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
775 event->type == GDK_BUTTON_PRESS) {
777 _drags->set (new RubberbandSelectDrag (this, item), event);
779 } else if (event->type == GDK_BUTTON_PRESS) {
781 switch (item_type) {
782 case FadeInHandleItem:
784 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
785 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, _cursors->fade_in);
786 return true;
789 case FadeOutHandleItem:
791 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
792 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, _cursors->fade_out);
793 return true;
796 case FeatureLineItem:
798 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
799 remove_transient(item);
800 return true;
803 _drags->set (new FeatureLineDrag (this, item), event);
804 return true;
805 break;
808 case RegionItem:
809 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
810 /* click on an automation region view; do nothing here and let the ARV's signal handler
811 sort it out.
813 break;
816 if (internal_editing ()) {
817 /* no region drags in internal edit mode */
818 break;
821 /* click on a normal region view */
822 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
823 add_region_copy_drag (item, event, clicked_regionview);
825 else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
826 add_region_brush_drag (item, event, clicked_regionview);
827 } else {
828 add_region_drag (item, event, clicked_regionview);
831 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
832 _drags->add (new SelectionDrag (this, clicked_axisview->get_selection_rect (clicked_selection)->rect, SelectionDrag::SelectionMove));
835 _drags->start_grab (event);
836 break;
838 case RegionViewNameHighlight:
839 case LeftFrameHandle:
840 case RightFrameHandle:
841 if (!internal_editing () && !clicked_regionview->region()->locked()) {
842 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
843 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
844 return true;
846 break;
848 case RegionViewName:
850 /* rename happens on edit clicks */
851 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
852 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, s.by_layer()), event);
853 return true;
854 break;
857 case ControlPointItem:
858 _drags->set (new ControlPointDrag (this, item), event);
859 return true;
860 break;
862 case AutomationLineItem:
863 _drags->set (new LineDrag (this, item), event);
864 return true;
865 break;
867 case StreamItem:
868 if (internal_editing()) {
869 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
870 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
872 return true;
873 } else {
874 _drags->set (new RubberbandSelectDrag (this, item), event);
876 break;
878 case AutomationTrackItem:
879 /* rubberband drag to select automation points */
880 _drags->set (new RubberbandSelectDrag (this, item), event);
881 break;
883 case SelectionItem:
885 if (join_object_range_button.get_active()) {
886 /* we're in "smart" joined mode, and we've clicked on a Selection */
887 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
888 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
889 if (tvp.first) {
890 /* if we're over an automation track, start a drag of its data */
891 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
892 if (atv) {
893 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, _cursors->up_down);
896 /* if we're over a track and a region, and in the `object' part of a region,
897 put a selection around the region and drag both
899 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
900 if (rtv && _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
901 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (rtv->route ());
902 if (t) {
903 boost::shared_ptr<Playlist> pl = t->playlist ();
904 if (pl) {
906 boost::shared_ptr<Region> r = pl->top_region_at (event_frame (event));
907 if (r) {
908 RegionView* rv = rtv->view()->find_view (r);
909 clicked_selection = select_range_around_region (rv);
910 _drags->add (new SelectionDrag (this, item, SelectionDrag::SelectionMove));
911 list<RegionView*> rvs;
912 rvs.push_back (rv);
913 _drags->add (new RegionMoveDrag (this, item, rv, rvs, false, false));
914 _drags->start_grab (event);
921 break;
924 #ifdef WITH_CMT
925 case ImageFrameHandleStartItem:
926 imageframe_start_handle_op(item, event) ;
927 return(true) ;
928 break ;
929 case ImageFrameHandleEndItem:
930 imageframe_end_handle_op(item, event) ;
931 return(true) ;
932 break ;
933 case MarkerViewHandleStartItem:
934 markerview_item_start_handle_op(item, event) ;
935 return(true) ;
936 break ;
937 case MarkerViewHandleEndItem:
938 markerview_item_end_handle_op(item, event) ;
939 return(true) ;
940 break ;
941 case MarkerViewItem:
942 start_markerview_grab(item, event) ;
943 break ;
944 case ImageFrameItem:
945 start_imageframe_grab(item, event) ;
946 break ;
947 #endif
949 case MarkerBarItem:
951 break;
953 default:
954 break;
957 return true;
958 break;
960 case MouseGain:
961 switch (item_type) {
962 case RegionItem:
963 /* start a grab so that if we finish after moving
964 we can tell what happened.
966 _drags->set (new RegionGainDrag (this, item), event, current_canvas_cursor);
967 break;
969 case GainLineItem:
970 _drags->set (new LineDrag (this, item), event);
971 return true;
973 case ControlPointItem:
974 _drags->set (new ControlPointDrag (this, item), event);
975 return true;
976 break;
978 default:
979 break;
981 return true;
982 break;
984 switch (item_type) {
985 case ControlPointItem:
986 _drags->set (new ControlPointDrag (this, item), event);
987 break;
989 case AutomationLineItem:
990 _drags->set (new LineDrag (this, item), event);
991 break;
993 case RegionItem:
994 // XXX need automation mode to identify which
995 // line to use
996 // start_line_grab_from_regionview (item, event);
997 break;
999 default:
1000 break;
1002 return true;
1003 break;
1005 case MouseZoom:
1006 if (event->type == GDK_BUTTON_PRESS) {
1007 _drags->set (new MouseZoomDrag (this, item), event);
1010 return true;
1011 break;
1013 case MouseTimeFX:
1014 if (internal_editing() && item_type == NoteItem) {
1015 /* drag notes if we're in internal edit mode */
1016 _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
1017 return true;
1018 } else if ((!internal_editing() || dynamic_cast<AudioRegionView*> (clicked_regionview)) && clicked_regionview) {
1019 /* do time-FX if we're not in internal edit mode, or we are but we clicked on an audio region */
1020 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1021 return true;
1023 break;
1025 case MouseAudition:
1026 _drags->set (new ScrubDrag (this, item), event);
1027 scrub_reversals = 0;
1028 scrub_reverse_distance = 0;
1029 last_scrub_x = event->button.x;
1030 scrubbing_direction = 0;
1031 set_canvas_cursor (_cursors->transparent);
1032 return true;
1033 break;
1035 default:
1036 break;
1039 return false;
1042 bool
1043 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1045 Editing::MouseMode const eff = effective_mouse_mode ();
1046 switch (eff) {
1047 case MouseObject:
1048 switch (item_type) {
1049 case RegionItem:
1050 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
1051 add_region_copy_drag (item, event, clicked_regionview);
1052 } else {
1053 add_region_drag (item, event, clicked_regionview);
1055 _drags->start_grab (event);
1056 return true;
1057 break;
1058 case ControlPointItem:
1059 _drags->set (new ControlPointDrag (this, item), event);
1060 return true;
1061 break;
1063 default:
1064 break;
1067 switch (item_type) {
1068 case RegionViewNameHighlight:
1069 case LeftFrameHandle:
1070 case RightFrameHandle:
1071 if (!internal_editing ()) {
1072 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1074 return true;
1075 break;
1077 case RegionViewName:
1078 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1079 return true;
1080 break;
1082 default:
1083 break;
1086 break;
1088 case MouseRange:
1089 /* relax till release */
1090 return true;
1091 break;
1094 case MouseZoom:
1095 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1096 temporal_zoom_session();
1097 } else {
1098 temporal_zoom_to_frame (true, event_frame(event));
1100 return true;
1101 break;
1103 default:
1104 break;
1107 return false;
1110 bool
1111 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1113 if (event->type != GDK_BUTTON_PRESS) {
1114 return false;
1117 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
1119 if (canvas_window) {
1120 Glib::RefPtr<const Gdk::Window> pointer_window;
1121 int x, y;
1122 double wx, wy;
1123 Gdk::ModifierType mask;
1125 pointer_window = canvas_window->get_pointer (x, y, mask);
1127 if (pointer_window == track_canvas->get_bin_window()) {
1128 track_canvas->window_to_world (x, y, wx, wy);
1132 pre_press_cursor = current_canvas_cursor;
1134 track_canvas->grab_focus();
1136 if (_session && _session->actively_recording()) {
1137 return true;
1140 button_selection (item, event, item_type);
1142 if (!_drags->active () &&
1143 (Keyboard::is_delete_event (&event->button) ||
1144 Keyboard::is_context_menu_event (&event->button) ||
1145 Keyboard::is_edit_event (&event->button))) {
1147 /* handled by button release */
1148 return true;
1151 switch (event->button.button) {
1152 case 1:
1153 return button_press_handler_1 (item, event, item_type);
1154 break;
1156 case 2:
1157 return button_press_handler_2 (item, event, item_type);
1158 break;
1160 case 3:
1161 break;
1163 default:
1164 break;
1168 return false;
1171 bool
1172 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1174 framepos_t where = event_frame (event, 0, 0);
1175 AutomationTimeAxisView* atv = 0;
1177 if (pre_press_cursor) {
1178 set_canvas_cursor (pre_press_cursor);
1179 pre_press_cursor = 0;
1182 /* no action if we're recording */
1184 if (_session && _session->actively_recording()) {
1185 return true;
1188 /* see if we're finishing a drag */
1190 bool were_dragging = false;
1191 if (_drags->active ()) {
1192 bool const r = _drags->end_grab (event);
1193 if (r) {
1194 /* grab dragged, so do nothing else */
1195 return true;
1198 were_dragging = true;
1201 update_region_layering_order_editor ();
1203 /* edit events get handled here */
1205 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1206 switch (item_type) {
1207 case RegionItem:
1208 show_region_properties ();
1209 break;
1211 case TempoMarkerItem:
1212 edit_tempo_marker (item);
1213 break;
1215 case MeterMarkerItem:
1216 edit_meter_marker (item);
1217 break;
1219 case RegionViewName:
1220 if (clicked_regionview->name_active()) {
1221 return mouse_rename_region (item, event);
1223 break;
1225 case ControlPointItem:
1226 edit_control_point (item);
1227 break;
1229 case NoteItem:
1230 edit_note (item);
1231 break;
1233 default:
1234 break;
1236 return true;
1239 /* context menu events get handled here */
1241 if (Keyboard::is_context_menu_event (&event->button)) {
1243 if (!_drags->active ()) {
1245 /* no matter which button pops up the context menu, tell the menu
1246 widget to use button 1 to drive menu selection.
1249 switch (item_type) {
1250 case FadeInItem:
1251 case FadeInHandleItem:
1252 case FadeOutItem:
1253 case FadeOutHandleItem:
1254 popup_fade_context_menu (1, event->button.time, item, item_type);
1255 break;
1257 case StreamItem:
1258 popup_track_context_menu (1, event->button.time, item_type, false);
1259 break;
1261 case RegionItem:
1262 case RegionViewNameHighlight:
1263 case LeftFrameHandle:
1264 case RightFrameHandle:
1265 case RegionViewName:
1266 popup_track_context_menu (1, event->button.time, item_type, false);
1267 break;
1269 case SelectionItem:
1270 popup_track_context_menu (1, event->button.time, item_type, true);
1271 break;
1273 case AutomationTrackItem:
1274 popup_track_context_menu (1, event->button.time, item_type, false);
1275 break;
1277 case MarkerBarItem:
1278 case RangeMarkerBarItem:
1279 case TransportMarkerBarItem:
1280 case CdMarkerBarItem:
1281 case TempoBarItem:
1282 case MeterBarItem:
1283 popup_ruler_menu (where, item_type);
1284 break;
1286 case MarkerItem:
1287 marker_context_menu (&event->button, item);
1288 break;
1290 case TempoMarkerItem:
1291 tempo_or_meter_marker_context_menu (&event->button, item);
1292 break;
1294 case MeterMarkerItem:
1295 tempo_or_meter_marker_context_menu (&event->button, item);
1296 break;
1298 case CrossfadeViewItem:
1299 popup_track_context_menu (1, event->button.time, item_type, false);
1300 break;
1302 #ifdef WITH_CMT
1303 case ImageFrameItem:
1304 popup_imageframe_edit_menu(1, event->button.time, item, true) ;
1305 break ;
1306 case ImageFrameTimeAxisItem:
1307 popup_imageframe_edit_menu(1, event->button.time, item, false) ;
1308 break ;
1309 case MarkerViewItem:
1310 popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ;
1311 break ;
1312 case MarkerTimeAxisItem:
1313 popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
1314 break ;
1315 #endif
1317 default:
1318 break;
1321 return true;
1325 /* delete events get handled here */
1327 Editing::MouseMode const eff = effective_mouse_mode ();
1329 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1331 switch (item_type) {
1332 case TempoMarkerItem:
1333 remove_tempo_marker (item);
1334 break;
1336 case MeterMarkerItem:
1337 remove_meter_marker (item);
1338 break;
1340 case MarkerItem:
1341 remove_marker (*item, event);
1342 break;
1344 case RegionItem:
1345 if (eff == MouseObject) {
1346 remove_clicked_region ();
1348 break;
1350 case ControlPointItem:
1351 if (eff == MouseGain) {
1352 remove_gain_control_point (item, event);
1353 } else {
1354 remove_control_point (item, event);
1356 break;
1358 case NoteItem:
1359 remove_midi_note (item, event);
1360 break;
1362 default:
1363 break;
1365 return true;
1368 switch (event->button.button) {
1369 case 1:
1371 switch (item_type) {
1372 /* see comments in button_press_handler */
1373 case PlayheadCursorItem:
1374 case MarkerItem:
1375 case GainLineItem:
1376 case AutomationLineItem:
1377 case StartSelectionTrimItem:
1378 case EndSelectionTrimItem:
1379 return true;
1381 case MarkerBarItem:
1382 if (!_dragging_playhead) {
1383 snap_to_with_modifier (where, event, 0, true);
1384 mouse_add_new_marker (where);
1386 return true;
1388 case CdMarkerBarItem:
1389 if (!_dragging_playhead) {
1390 // if we get here then a dragged range wasn't done
1391 snap_to_with_modifier (where, event, 0, true);
1392 mouse_add_new_marker (where, true);
1394 return true;
1396 case TempoBarItem:
1397 if (!_dragging_playhead) {
1398 snap_to_with_modifier (where, event);
1399 mouse_add_new_tempo_event (where);
1401 return true;
1403 case MeterBarItem:
1404 if (!_dragging_playhead) {
1405 mouse_add_new_meter_event (pixel_to_frame (event->button.x));
1407 return true;
1408 break;
1410 default:
1411 break;
1414 switch (eff) {
1415 case MouseObject:
1416 switch (item_type) {
1417 case AutomationTrackItem:
1418 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1419 if (atv) {
1420 atv->add_automation_event (item, event, where, event->button.y);
1422 return true;
1423 break;
1425 default:
1426 break;
1428 break;
1430 case MouseGain:
1431 switch (item_type) {
1432 case RegionItem:
1434 /* check that we didn't drag before releasing, since
1435 its really annoying to create new control
1436 points when doing this.
1438 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1439 if (were_dragging && arv) {
1440 arv->add_gain_point_event (item, event);
1442 return true;
1443 break;
1446 case AutomationTrackItem:
1447 dynamic_cast<AutomationTimeAxisView*>(clicked_axisview)->
1448 add_automation_event (item, event, where, event->button.y);
1449 return true;
1450 break;
1451 default:
1452 break;
1454 break;
1456 case MouseAudition:
1457 set_canvas_cursor (current_canvas_cursor);
1458 if (scrubbing_direction == 0) {
1459 /* no drag, just a click */
1460 switch (item_type) {
1461 case RegionItem:
1462 play_selected_region ();
1463 break;
1464 default:
1465 break;
1467 } else {
1468 /* make sure we stop */
1469 _session->request_transport_speed (0.0);
1471 break;
1473 default:
1474 break;
1478 return true;
1479 break;
1482 case 2:
1483 switch (eff) {
1485 case MouseObject:
1486 switch (item_type) {
1487 case RegionItem:
1488 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1489 raise_region ();
1490 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1491 lower_region ();
1492 } else {
1493 // Button2 click is unused
1495 return true;
1497 break;
1499 default:
1500 break;
1502 break;
1504 case MouseRange:
1506 // x_style_paste (where, 1.0);
1507 return true;
1508 break;
1510 default:
1511 break;
1514 break;
1516 case 3:
1517 break;
1519 default:
1520 break;
1522 return false;
1525 bool
1526 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1528 ControlPoint* cp;
1529 Marker * marker;
1530 double fraction;
1531 bool ret = true;
1533 last_item_entered = item;
1535 switch (item_type) {
1536 case ControlPointItem:
1537 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1538 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1539 cp->set_visible (true);
1541 double at_x, at_y;
1542 at_x = cp->get_x();
1543 at_y = cp->get_y ();
1544 cp->i2w (at_x, at_y);
1545 at_x += 10.0;
1546 at_y += 10.0;
1548 fraction = 1.0 - (cp->get_y() / cp->line().height());
1550 if (is_drawable() && !_drags->active ()) {
1551 set_canvas_cursor (_cursors->fader);
1554 set_verbose_canvas_cursor (cp->line().get_verbose_cursor_string (fraction), at_x, at_y);
1555 show_verbose_canvas_cursor ();
1557 break;
1559 case GainLineItem:
1560 if (mouse_mode == MouseGain) {
1561 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1562 if (line)
1563 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
1564 if (is_drawable()) {
1565 set_canvas_cursor (_cursors->fader);
1568 break;
1570 case AutomationLineItem:
1571 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1573 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1574 if (line)
1575 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
1577 if (is_drawable()) {
1578 set_canvas_cursor (_cursors->fader);
1581 break;
1583 case RegionViewNameHighlight:
1584 if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
1585 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1586 _over_region_trim_target = true;
1588 break;
1590 case LeftFrameHandle:
1591 case RightFrameHandle:
1592 if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
1593 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1595 break;
1597 case StartSelectionTrimItem:
1598 case EndSelectionTrimItem:
1600 #ifdef WITH_CMT
1601 case ImageFrameHandleStartItem:
1602 case ImageFrameHandleEndItem:
1603 case MarkerViewHandleStartItem:
1604 case MarkerViewHandleEndItem:
1605 #endif
1607 if (is_drawable()) {
1608 set_canvas_cursor (_cursors->trimmer);
1610 break;
1612 case PlayheadCursorItem:
1613 if (is_drawable()) {
1614 switch (_edit_point) {
1615 case EditAtMouse:
1616 set_canvas_cursor (_cursors->grabber_edit_point);
1617 break;
1618 default:
1619 set_canvas_cursor (_cursors->grabber);
1620 break;
1623 break;
1625 case RegionViewName:
1627 /* when the name is not an active item, the entire name highlight is for trimming */
1629 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1630 if (mouse_mode == MouseObject && is_drawable()) {
1631 set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
1632 _over_region_trim_target = true;
1635 break;
1638 case AutomationTrackItem:
1639 if (is_drawable()) {
1640 Gdk::Cursor *cursor;
1641 switch (mouse_mode) {
1642 case MouseRange:
1643 cursor = _cursors->selector;
1644 break;
1645 case MouseZoom:
1646 cursor = _cursors->zoom_in;
1647 break;
1648 default:
1649 cursor = _cursors->cross_hair;
1650 break;
1653 set_canvas_cursor (cursor);
1655 AutomationTimeAxisView* atv;
1656 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1657 clear_entered_track = false;
1658 set_entered_track (atv);
1661 break;
1663 case MarkerBarItem:
1664 case RangeMarkerBarItem:
1665 case TransportMarkerBarItem:
1666 case CdMarkerBarItem:
1667 case MeterBarItem:
1668 case TempoBarItem:
1669 if (is_drawable()) {
1670 set_canvas_cursor (_cursors->timebar);
1672 break;
1674 case MarkerItem:
1675 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1676 break;
1678 entered_marker = marker;
1679 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1680 // fall through
1681 case MeterMarkerItem:
1682 case TempoMarkerItem:
1683 if (is_drawable()) {
1684 set_canvas_cursor (_cursors->timebar);
1686 break;
1688 case FadeInHandleItem:
1689 if (mouse_mode == MouseObject && !internal_editing()) {
1690 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1691 if (rect) {
1692 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1694 set_canvas_cursor (_cursors->fade_in);
1696 break;
1698 case FadeOutHandleItem:
1699 if (mouse_mode == MouseObject && !internal_editing()) {
1700 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1701 if (rect) {
1702 rect->property_fill_color_rgba() = 0xBBBBBBAA;
1704 set_canvas_cursor (_cursors->fade_out);
1706 break;
1707 case FeatureLineItem:
1709 ArdourCanvas::SimpleLine *line = dynamic_cast<ArdourCanvas::SimpleLine *> (item);
1710 line->property_color_rgba() = 0xFF0000FF;
1712 break;
1713 case SelectionItem:
1714 if (join_object_range_button.get_active()) {
1715 set_canvas_cursor ();
1717 break;
1719 default:
1720 break;
1723 /* second pass to handle entered track status in a comprehensible way.
1726 switch (item_type) {
1727 case GainLineItem:
1728 case AutomationLineItem:
1729 case ControlPointItem:
1730 /* these do not affect the current entered track state */
1731 clear_entered_track = false;
1732 break;
1734 case AutomationTrackItem:
1735 /* handled above already */
1736 break;
1738 default:
1739 set_entered_track (0);
1740 break;
1743 return ret;
1746 bool
1747 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1749 AutomationLine* al;
1750 ControlPoint* cp;
1751 Marker *marker;
1752 Location *loc;
1753 RegionView* rv;
1754 bool is_start;
1755 bool ret = true;
1757 switch (item_type) {
1758 case ControlPointItem:
1759 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1760 if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
1761 if (cp->line().npoints() > 1 && !cp->get_selected()) {
1762 cp->set_visible (false);
1766 if (is_drawable()) {
1767 set_canvas_cursor (current_canvas_cursor);
1770 hide_verbose_canvas_cursor ();
1771 break;
1773 case RegionViewNameHighlight:
1774 case LeftFrameHandle:
1775 case RightFrameHandle:
1776 case StartSelectionTrimItem:
1777 case EndSelectionTrimItem:
1778 case PlayheadCursorItem:
1780 #ifdef WITH_CMT
1781 case ImageFrameHandleStartItem:
1782 case ImageFrameHandleEndItem:
1783 case MarkerViewHandleStartItem:
1784 case MarkerViewHandleEndItem:
1785 #endif
1787 _over_region_trim_target = false;
1789 if (is_drawable()) {
1790 set_canvas_cursor (current_canvas_cursor);
1792 break;
1794 case GainLineItem:
1795 case AutomationLineItem:
1796 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1798 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1799 if (line)
1800 line->property_fill_color_rgba() = al->get_line_color();
1802 if (is_drawable()) {
1803 set_canvas_cursor (current_canvas_cursor);
1805 break;
1807 case RegionViewName:
1808 /* see enter_handler() for notes */
1809 _over_region_trim_target = false;
1811 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1812 if (is_drawable() && mouse_mode == MouseObject) {
1813 set_canvas_cursor (current_canvas_cursor);
1816 break;
1818 case RangeMarkerBarItem:
1819 case TransportMarkerBarItem:
1820 case CdMarkerBarItem:
1821 case MeterBarItem:
1822 case TempoBarItem:
1823 case MarkerBarItem:
1824 if (is_drawable()) {
1825 set_canvas_cursor (current_canvas_cursor);
1827 break;
1829 case MarkerItem:
1830 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1831 break;
1833 entered_marker = 0;
1834 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1835 location_flags_changed (loc, this);
1837 // fall through
1838 case MeterMarkerItem:
1839 case TempoMarkerItem:
1841 if (is_drawable()) {
1842 set_canvas_cursor (_cursors->timebar);
1845 break;
1847 case FadeInHandleItem:
1848 case FadeOutHandleItem:
1849 rv = static_cast<RegionView*>(item->get_data ("regionview"));
1851 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1852 if (rect) {
1853 rect->property_fill_color_rgba() = rv->get_fill_color();
1854 rect->property_outline_pixels() = 0;
1857 set_canvas_cursor (current_canvas_cursor);
1858 break;
1860 case AutomationTrackItem:
1861 if (is_drawable()) {
1862 set_canvas_cursor (current_canvas_cursor);
1863 clear_entered_track = true;
1864 Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track));
1866 break;
1867 case FeatureLineItem:
1869 ArdourCanvas::SimpleLine *line = dynamic_cast<ArdourCanvas::SimpleLine *> (item);
1870 line->property_color_rgba() = (guint) ARDOUR_UI::config()->canvasvar_ZeroLine.get();;
1872 break;
1874 default:
1875 break;
1878 return ret;
1881 gint
1882 Editor::left_automation_track ()
1884 if (clear_entered_track) {
1885 set_entered_track (0);
1886 clear_entered_track = false;
1888 return false;
1891 void
1892 Editor::scrub (framepos_t frame, double current_x)
1894 double delta;
1896 if (scrubbing_direction == 0) {
1897 /* first move */
1898 _session->request_locate (frame, false);
1899 _session->request_transport_speed (0.1);
1900 scrubbing_direction = 1;
1902 } else {
1904 if (last_scrub_x > current_x) {
1906 /* pointer moved to the left */
1908 if (scrubbing_direction > 0) {
1910 /* we reversed direction to go backwards */
1912 scrub_reversals++;
1913 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1915 } else {
1917 /* still moving to the left (backwards) */
1919 scrub_reversals = 0;
1920 scrub_reverse_distance = 0;
1922 delta = 0.01 * (last_scrub_x - current_x);
1923 _session->request_transport_speed_nonzero (_session->transport_speed() - delta);
1926 } else {
1927 /* pointer moved to the right */
1929 if (scrubbing_direction < 0) {
1930 /* we reversed direction to go forward */
1932 scrub_reversals++;
1933 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1935 } else {
1936 /* still moving to the right */
1938 scrub_reversals = 0;
1939 scrub_reverse_distance = 0;
1941 delta = 0.01 * (current_x - last_scrub_x);
1942 _session->request_transport_speed_nonzero (_session->transport_speed() + delta);
1946 /* if there have been more than 2 opposite motion moves detected, or one that moves
1947 back more than 10 pixels, reverse direction
1950 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1952 if (scrubbing_direction > 0) {
1953 /* was forwards, go backwards */
1954 _session->request_transport_speed (-0.1);
1955 scrubbing_direction = -1;
1956 } else {
1957 /* was backwards, go forwards */
1958 _session->request_transport_speed (0.1);
1959 scrubbing_direction = 1;
1962 scrub_reverse_distance = 0;
1963 scrub_reversals = 0;
1967 last_scrub_x = current_x;
1970 bool
1971 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1973 _last_motion_y = event->motion.y;
1975 if (event->motion.is_hint) {
1976 gint x, y;
1978 /* We call this so that MOTION_NOTIFY events continue to be
1979 delivered to the canvas. We need to do this because we set
1980 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1981 the density of the events, at the expense of a round-trip
1982 to the server. Given that this will mostly occur on cases
1983 where DISPLAY = :0.0, and given the cost of what the motion
1984 event might do, its a good tradeoff.
1987 track_canvas->get_pointer (x, y);
1990 if (current_stepping_trackview) {
1991 /* don't keep the persistent stepped trackview if the mouse moves */
1992 current_stepping_trackview = 0;
1993 step_timeout.disconnect ();
1996 if (_session && _session->actively_recording()) {
1997 /* Sorry. no dragging stuff around while we record */
1998 return true;
2001 JoinObjectRangeState const old = _join_object_range_state;
2002 update_join_object_range_location (event->motion.x, event->motion.y);
2003 if (_join_object_range_state != old) {
2004 set_canvas_cursor ();
2007 if (_over_region_trim_target) {
2008 set_canvas_cursor_for_region_view (event->motion.x, entered_regionview);
2011 bool handled = false;
2012 if (_drags->active ()) {
2013 handled = _drags->motion_handler (event, from_autoscroll);
2016 if (!handled) {
2017 return false;
2020 track_canvas_motion (event);
2021 return true;
2024 void
2025 Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* /*event*/)
2027 ControlPoint* control_point;
2029 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2030 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2031 /*NOTREACHED*/
2034 // We shouldn't remove the first or last gain point
2035 if (control_point->line().is_last_point(*control_point) ||
2036 control_point->line().is_first_point(*control_point)) {
2037 return;
2040 control_point->line().remove_point (*control_point);
2043 void
2044 Editor::remove_control_point (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2046 ControlPoint* control_point;
2048 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2049 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2050 /*NOTREACHED*/
2053 control_point->line().remove_point (*control_point);
2056 void
2057 Editor::edit_control_point (ArdourCanvas::Item* item)
2059 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2061 if (p == 0) {
2062 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2063 /*NOTREACHED*/
2066 ControlPointDialog d (p);
2067 d.set_position (Gtk::WIN_POS_MOUSE);
2068 ensure_float (d);
2070 if (d.run () != RESPONSE_ACCEPT) {
2071 return;
2074 p->line().modify_point_y (*p, d.get_y_fraction ());
2077 void
2078 Editor::edit_note (ArdourCanvas::Item* item)
2080 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2081 assert (e);
2083 EditNoteDialog d (&e->region_view(), e);
2084 d.set_position (Gtk::WIN_POS_MOUSE);
2085 ensure_float (d);
2087 d.run ();
2091 void
2092 Editor::visible_order_range (int* low, int* high) const
2094 *low = TimeAxisView::max_order ();
2095 *high = 0;
2097 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2099 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2101 if (!rtv->hidden()) {
2103 if (*high < rtv->order()) {
2104 *high = rtv->order ();
2107 if (*low > rtv->order()) {
2108 *low = rtv->order ();
2114 void
2115 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2117 /* Either add to or set the set the region selection, unless
2118 this is an alignment click (control used)
2121 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2122 TimeAxisView* tv = &rv.get_time_axis_view();
2123 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
2124 double speed = 1.0;
2125 if (rtv && rtv->is_track()) {
2126 speed = rtv->track()->speed();
2129 framepos_t where = get_preferred_edit_position();
2131 if (where >= 0) {
2133 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2135 align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
2137 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2139 align_region (rv.region(), End, (framepos_t) (where * speed));
2141 } else {
2143 align_region (rv.region(), Start, (framepos_t) (where * speed));
2149 void
2150 Editor::show_verbose_time_cursor (framepos_t frame, double offset, double xpos, double ypos)
2152 char buf[128];
2153 Timecode::Time timecode;
2154 Timecode::BBT_Time bbt;
2155 int hours, mins;
2156 framepos_t frame_rate;
2157 float secs;
2159 if (_session == 0) {
2160 return;
2163 AudioClock::Mode m;
2165 if (Profile->get_sae() || Profile->get_small_screen()) {
2166 m = ARDOUR_UI::instance()->primary_clock.mode();
2167 } else {
2168 m = ARDOUR_UI::instance()->secondary_clock.mode();
2171 switch (m) {
2172 case AudioClock::BBT:
2173 _session->bbt_time (frame, bbt);
2174 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, bbt.bars, bbt.beats, bbt.ticks);
2175 break;
2177 case AudioClock::Timecode:
2178 _session->timecode_time (frame, timecode);
2179 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2180 break;
2182 case AudioClock::MinSec:
2183 /* XXX this is copied from show_verbose_duration_cursor() */
2184 frame_rate = _session->frame_rate();
2185 hours = frame / (frame_rate * 3600);
2186 frame = frame % (frame_rate * 3600);
2187 mins = frame / (frame_rate * 60);
2188 frame = frame % (frame_rate * 60);
2189 secs = (float) frame / (float) frame_rate;
2190 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
2191 break;
2193 default:
2194 snprintf (buf, sizeof(buf), "%" PRIi64, frame);
2195 break;
2198 if (xpos >= 0 && ypos >=0) {
2199 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2200 } else {
2201 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset - horizontal_position(), _drags->current_pointer_y() + offset - vertical_adjustment.get_value() + canvas_timebars_vsize);
2203 show_verbose_canvas_cursor ();
2206 void
2207 Editor::show_verbose_duration_cursor (framepos_t start, framepos_t end, double offset, double xpos, double ypos)
2209 char buf[128];
2210 Timecode::Time timecode;
2211 Timecode::BBT_Time sbbt;
2212 Timecode::BBT_Time ebbt;
2213 int hours, mins;
2214 framepos_t distance, frame_rate;
2215 float secs;
2216 Meter meter_at_start(_session->tempo_map().meter_at(start));
2218 if (_session == 0) {
2219 return;
2222 AudioClock::Mode m;
2224 if (Profile->get_sae() || Profile->get_small_screen()) {
2225 m = ARDOUR_UI::instance()->primary_clock.mode ();
2226 } else {
2227 m = ARDOUR_UI::instance()->secondary_clock.mode ();
2230 switch (m) {
2231 case AudioClock::BBT:
2232 _session->bbt_time (start, sbbt);
2233 _session->bbt_time (end, ebbt);
2235 /* subtract */
2236 /* XXX this computation won't work well if the
2237 user makes a selection that spans any meter changes.
2240 ebbt.bars -= sbbt.bars;
2241 if (ebbt.beats >= sbbt.beats) {
2242 ebbt.beats -= sbbt.beats;
2243 } else {
2244 ebbt.bars--;
2245 ebbt.beats = int(meter_at_start.beats_per_bar()) + ebbt.beats - sbbt.beats;
2247 if (ebbt.ticks >= sbbt.ticks) {
2248 ebbt.ticks -= sbbt.ticks;
2249 } else {
2250 ebbt.beats--;
2251 ebbt.ticks = int(Timecode::BBT_Time::ticks_per_beat) + ebbt.ticks - sbbt.ticks;
2254 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, ebbt.bars, ebbt.beats, ebbt.ticks);
2255 break;
2257 case AudioClock::Timecode:
2258 _session->timecode_duration (end - start, timecode);
2259 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2260 break;
2262 case AudioClock::MinSec:
2263 /* XXX this stuff should be elsewhere.. */
2264 distance = end - start;
2265 frame_rate = _session->frame_rate();
2266 hours = distance / (frame_rate * 3600);
2267 distance = distance % (frame_rate * 3600);
2268 mins = distance / (frame_rate * 60);
2269 distance = distance % (frame_rate * 60);
2270 secs = (float) distance / (float) frame_rate;
2271 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
2272 break;
2274 default:
2275 snprintf (buf, sizeof(buf), "%" PRIi64, end - start);
2276 break;
2279 if (xpos >= 0 && ypos >=0) {
2280 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2282 else {
2283 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset, _drags->current_pointer_y() + offset);
2286 show_verbose_canvas_cursor ();
2289 void
2290 Editor::collect_new_region_view (RegionView* rv)
2292 latest_regionviews.push_back (rv);
2295 void
2296 Editor::collect_and_select_new_region_view (RegionView* rv)
2298 selection->add(rv);
2299 latest_regionviews.push_back (rv);
2302 void
2303 Editor::cancel_selection ()
2305 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2306 (*i)->hide_selection ();
2309 selection->clear ();
2310 clicked_selection = 0;
2314 void
2315 Editor::point_trim (GdkEvent* event, framepos_t new_bound)
2317 RegionView* rv = clicked_regionview;
2319 /* Choose action dependant on which button was pressed */
2320 switch (event->button.button) {
2321 case 1:
2322 begin_reversible_command (_("start point trim"));
2324 if (selection->selected (rv)) {
2325 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2326 i != selection->regions.by_layer().end(); ++i)
2328 if ( (*i) == NULL){
2329 cerr << "region view contains null region" << endl;
2332 if (!(*i)->region()->locked()) {
2333 (*i)->region()->clear_changes ();
2334 (*i)->region()->trim_front (new_bound, this);
2335 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2339 } else {
2340 if (!rv->region()->locked()) {
2341 rv->region()->clear_changes ();
2342 rv->region()->trim_front (new_bound, this);
2343 _session->add_command(new StatefulDiffCommand (rv->region()));
2347 commit_reversible_command();
2349 break;
2350 case 2:
2351 begin_reversible_command (_("End point trim"));
2353 if (selection->selected (rv)) {
2355 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2357 if (!(*i)->region()->locked()) {
2358 (*i)->region()->clear_changes();
2359 (*i)->region()->trim_end (new_bound, this);
2360 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2364 } else {
2366 if (!rv->region()->locked()) {
2367 rv->region()->clear_changes ();
2368 rv->region()->trim_end (new_bound, this);
2369 _session->add_command (new StatefulDiffCommand (rv->region()));
2373 commit_reversible_command();
2375 break;
2376 default:
2377 break;
2381 void
2382 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2384 Marker* marker;
2385 bool is_start;
2387 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2388 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2389 /*NOTREACHED*/
2392 Location* location = find_location_from_marker (marker, is_start);
2393 location->set_hidden (true, this);
2397 void
2398 Editor::reposition_zoom_rect (framepos_t start, framepos_t end)
2400 double x1 = frame_to_pixel (start);
2401 double x2 = frame_to_pixel (end);
2402 double y2 = full_canvas_height - 1.0;
2404 zoom_rect->property_x1() = x1;
2405 zoom_rect->property_y1() = 1.0;
2406 zoom_rect->property_x2() = x2;
2407 zoom_rect->property_y2() = y2;
2411 gint
2412 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2414 using namespace Gtkmm2ext;
2416 ArdourPrompter prompter (false);
2418 prompter.set_prompt (_("Name for region:"));
2419 prompter.set_initial_text (clicked_regionview->region()->name());
2420 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2421 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2422 prompter.show_all ();
2423 switch (prompter.run ()) {
2424 case Gtk::RESPONSE_ACCEPT:
2425 string str;
2426 prompter.get_result(str);
2427 if (str.length()) {
2428 clicked_regionview->region()->set_name (str);
2430 break;
2432 return true;
2436 void
2437 Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
2439 /* no brushing without a useful snap setting */
2441 switch (_snap_mode) {
2442 case SnapMagnetic:
2443 return; /* can't work because it allows region to be placed anywhere */
2444 default:
2445 break; /* OK */
2448 switch (_snap_type) {
2449 case SnapToMark:
2450 return;
2452 default:
2453 break;
2456 /* don't brush a copy over the original */
2458 if (pos == rv->region()->position()) {
2459 return;
2462 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2464 if (rtv == 0 || !rtv->is_track()) {
2465 return;
2468 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2469 double speed = rtv->track()->speed();
2471 playlist->clear_changes ();
2472 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region()));
2473 playlist->add_region (new_region, (framepos_t) (pos * speed));
2474 _session->add_command (new StatefulDiffCommand (playlist));
2476 // playlist is frozen, so we have to update manually XXX this is disgusting
2478 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2481 gint
2482 Editor::track_height_step_timeout ()
2484 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2485 current_stepping_trackview = 0;
2486 return false;
2488 return true;
2491 void
2492 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2494 assert (region_view);
2496 if (!region_view->region()->playlist()) {
2497 return;
2500 _region_motion_group->raise_to_top ();
2502 if (Config->get_edit_mode() == Splice) {
2503 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2504 } else {
2505 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2506 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false));
2509 /* sync the canvas to what we think is its current state */
2510 update_canvas_now();
2513 void
2514 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2516 assert (region_view);
2518 if (!region_view->region()->playlist()) {
2519 return;
2522 _region_motion_group->raise_to_top ();
2524 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2525 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true));
2528 void
2529 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2531 assert (region_view);
2533 if (!region_view->region()->playlist()) {
2534 return;
2537 if (Config->get_edit_mode() == Splice) {
2538 return;
2541 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2542 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), true, false));
2544 begin_reversible_command (Operations::drag_region_brush);
2547 /** Start a grab where a time range is selected, track(s) are selected, and the
2548 * user clicks and drags a region with a modifier in order to create a new region containing
2549 * the section of the clicked region that lies within the time range.
2551 void
2552 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2554 if (clicked_regionview == 0) {
2555 return;
2558 /* lets try to create new Region for the selection */
2560 vector<boost::shared_ptr<Region> > new_regions;
2561 create_region_from_selection (new_regions);
2563 if (new_regions.empty()) {
2564 return;
2567 /* XXX fix me one day to use all new regions */
2569 boost::shared_ptr<Region> region (new_regions.front());
2571 /* add it to the current stream/playlist.
2573 tricky: the streamview for the track will add a new regionview. we will
2574 catch the signal it sends when it creates the regionview to
2575 set the regionview we want to then drag.
2578 latest_regionviews.clear();
2579 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2581 /* A selection grab currently creates two undo/redo operations, one for
2582 creating the new region and another for moving it.
2585 begin_reversible_command (Operations::selection_grab);
2587 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2589 playlist->clear_changes ();
2590 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2591 _session->add_command(new StatefulDiffCommand (playlist));
2593 commit_reversible_command ();
2595 c.disconnect ();
2597 if (latest_regionviews.empty()) {
2598 /* something went wrong */
2599 return;
2602 /* we need to deselect all other regionviews, and select this one
2603 i'm ignoring undo stuff, because the region creation will take care of it
2605 selection->set (latest_regionviews);
2607 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2610 void
2611 Editor::escape ()
2613 if (_drags->active ()) {
2614 _drags->abort ();
2615 } else {
2616 selection->clear ();
2620 void
2621 Editor::set_internal_edit (bool yn)
2623 _internal_editing = yn;
2625 if (yn) {
2626 mouse_select_button.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
2627 mouse_select_button.get_image ()->show ();
2628 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Draw/Edit MIDI Notes"));
2629 mouse_mode_toggled (mouse_mode);
2631 } else {
2633 mouse_select_button.set_image (*(manage (new Image (::get_icon("tool_range")))));
2634 mouse_select_button.get_image ()->show ();
2635 ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Select/Move Ranges"));
2636 mouse_mode_toggled (mouse_mode); // sets cursor
2640 /** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
2641 * used by the `join object/range' tool mode.
2643 void
2644 Editor::update_join_object_range_location (double x, double y)
2646 /* XXX: actually, this decides based on whether the mouse is in the top or bottom half of a RouteTimeAxisView;
2647 entered_{track,regionview} is not always setup (e.g. if the mouse is over a TimeSelection), and to get a Region
2648 that we're over requires searching the playlist.
2651 if (join_object_range_button.get_active() == false || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
2652 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2653 return;
2656 if (mouse_mode == MouseObject) {
2657 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2658 } else if (mouse_mode == MouseRange) {
2659 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2662 /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
2663 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y + vertical_adjustment.get_value() - canvas_timebars_vsize);
2665 if (tvp.first) {
2667 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2668 if (rtv) {
2670 double cx = 0;
2671 double cy = y;
2672 rtv->canvas_display()->w2i (cx, cy);
2674 double const c = cy / rtv->view()->child_height();
2675 double d;
2676 double const f = modf (c, &d);
2678 _join_object_range_state = f < 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2683 Editing::MouseMode
2684 Editor::effective_mouse_mode () const
2686 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2687 return MouseObject;
2688 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2689 return MouseRange;
2692 return mouse_mode;
2695 void
2696 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2698 ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
2699 assert (e);
2701 e->region_view().delete_note (e->note ());
2704 void
2705 Editor::set_canvas_cursor_for_region_view (double x, RegionView* rv)
2707 ArdourCanvas::Group* g = rv->get_canvas_group ();
2708 ArdourCanvas::Group* p = g->get_parent_group ();
2710 /* Compute x in region view parent coordinates */
2711 double dy = 0;
2712 p->w2i (x, dy);
2714 double x1, x2, y1, y2;
2715 g->get_bounds (x1, y1, x2, y2);
2717 /* Halfway across the region */
2718 double const h = (x1 + x2) / 2;
2720 Trimmable::CanTrim ct = rv->region()->can_trim ();
2721 if (x <= h) {
2722 if (ct & Trimmable::FrontTrimEarlier) {
2723 set_canvas_cursor (_cursors->left_side_trim);
2724 } else {
2725 set_canvas_cursor (_cursors->left_side_trim_right_only);
2727 } else {
2728 if (ct & Trimmable::EndTrimLater) {
2729 set_canvas_cursor (_cursors->right_side_trim);
2730 } else {
2731 set_canvas_cursor (_cursors->right_side_trim_left_only);