put the issue of using a monitor section into ~/.config/ardour.rc, not the session...
[ardour2.git] / gtk2_ardour / editor_mouse.cc
bloba67406f93c5d48ec4cf12ff956df25849811b486
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 "editor.h"
39 #include "time_axis_view.h"
40 #include "audio_time_axis.h"
41 #include "audio_region_view.h"
42 #include "midi_region_view.h"
43 #include "marker.h"
44 #include "streamview.h"
45 #include "region_gain_line.h"
46 #include "automation_time_axis.h"
47 #include "control_point.h"
48 #include "prompter.h"
49 #include "utils.h"
50 #include "selection.h"
51 #include "keyboard.h"
52 #include "editing.h"
53 #include "rgb_macros.h"
54 #include "control_point_dialog.h"
55 #include "editor_drag.h"
57 #include "ardour/types.h"
58 #include "ardour/profile.h"
59 #include "ardour/route.h"
60 #include "ardour/audio_track.h"
61 #include "ardour/audio_diskstream.h"
62 #include "ardour/midi_diskstream.h"
63 #include "ardour/playlist.h"
64 #include "ardour/audioplaylist.h"
65 #include "ardour/audioregion.h"
66 #include "ardour/midi_region.h"
67 #include "ardour/dB.h"
68 #include "ardour/utils.h"
69 #include "ardour/region_factory.h"
70 #include "ardour/source_factory.h"
71 #include "ardour/session.h"
73 #include <bitset>
75 #include "i18n.h"
77 using namespace std;
78 using namespace ARDOUR;
79 using namespace PBD;
80 using namespace Gtk;
81 using namespace Editing;
82 using Gtkmm2ext::Keyboard;
84 bool
85 Editor::mouse_frame (nframes64_t& where, bool& in_track_canvas) const
87 int x, y;
88 double wx, wy;
89 Gdk::ModifierType mask;
90 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
91 Glib::RefPtr<const Gdk::Window> pointer_window;
93 if (!canvas_window) {
94 return false;
97 pointer_window = canvas_window->get_pointer (x, y, mask);
99 if (pointer_window == track_canvas->get_bin_window()) {
100 wx = x;
101 wy = y;
102 in_track_canvas = true;
104 } else {
105 in_track_canvas = false;
106 return false;
109 GdkEvent event;
110 event.type = GDK_BUTTON_RELEASE;
111 event.button.x = wx;
112 event.button.y = wy;
114 where = event_frame (&event, 0, 0);
115 return true;
118 nframes64_t
119 Editor::event_frame (GdkEvent const * event, double* pcx, double* pcy) const
121 double cx, cy;
123 if (pcx == 0) {
124 pcx = &cx;
126 if (pcy == 0) {
127 pcy = &cy;
130 *pcx = 0;
131 *pcy = 0;
133 switch (event->type) {
134 case GDK_BUTTON_RELEASE:
135 case GDK_BUTTON_PRESS:
136 case GDK_2BUTTON_PRESS:
137 case GDK_3BUTTON_PRESS:
138 *pcx = event->button.x;
139 *pcy = event->button.y;
140 _trackview_group->w2i(*pcx, *pcy);
141 break;
142 case GDK_MOTION_NOTIFY:
143 *pcx = event->motion.x;
144 *pcy = event->motion.y;
145 _trackview_group->w2i(*pcx, *pcy);
146 break;
147 case GDK_ENTER_NOTIFY:
148 case GDK_LEAVE_NOTIFY:
149 track_canvas->w2c(event->crossing.x, event->crossing.y, *pcx, *pcy);
150 break;
151 case GDK_KEY_PRESS:
152 case GDK_KEY_RELEASE:
153 // track_canvas->w2c(event->key.x, event->key.y, *pcx, *pcy);
154 break;
155 default:
156 warning << string_compose (_("Editor::event_frame() used on unhandled event type %1"), event->type) << endmsg;
157 break;
160 /* note that pixel_to_frame() never returns less than zero, so even if the pixel
161 position is negative (as can be the case with motion events in particular),
162 the frame location is always positive.
165 return pixel_to_frame (*pcx);
168 Gdk::Cursor*
169 Editor::which_grabber_cursor ()
171 Gdk::Cursor* c = grabber_cursor;
173 if (_internal_editing) {
174 switch (mouse_mode) {
175 case MouseRange:
176 c = midi_pencil_cursor;
177 break;
179 case MouseObject:
180 c = grabber_cursor;
181 break;
183 case MouseTimeFX:
184 c = midi_resize_cursor;
185 break;
187 default:
188 break;
191 } else {
193 switch (_edit_point) {
194 case EditAtMouse:
195 c = grabber_edit_point_cursor;
196 break;
197 default:
198 break;
202 return c;
205 void
206 Editor::set_canvas_cursor ()
208 if (_internal_editing) {
210 switch (mouse_mode) {
211 case MouseRange:
212 current_canvas_cursor = midi_pencil_cursor;
213 break;
215 case MouseObject:
216 current_canvas_cursor = which_grabber_cursor();
217 break;
219 case MouseTimeFX:
220 current_canvas_cursor = midi_resize_cursor;
221 break;
223 default:
224 return;
227 } else {
229 switch (mouse_mode) {
230 case MouseRange:
231 current_canvas_cursor = selector_cursor;
232 break;
234 case MouseObject:
235 current_canvas_cursor = which_grabber_cursor();
236 break;
238 case MouseGain:
239 current_canvas_cursor = cross_hair_cursor;
240 break;
242 case MouseZoom:
243 current_canvas_cursor = zoom_cursor;
244 break;
246 case MouseTimeFX:
247 current_canvas_cursor = time_fx_cursor; // just use playhead
248 break;
250 case MouseAudition:
251 current_canvas_cursor = speaker_cursor;
252 break;
256 switch (_join_object_range_state) {
257 case JOIN_OBJECT_RANGE_NONE:
258 break;
259 case JOIN_OBJECT_RANGE_OBJECT:
260 current_canvas_cursor = which_grabber_cursor ();
261 break;
262 case JOIN_OBJECT_RANGE_RANGE:
263 current_canvas_cursor = selector_cursor;
264 break;
267 if (is_drawable()) {
268 track_canvas->get_window()->set_cursor(*current_canvas_cursor);
272 void
273 Editor::set_mouse_mode (MouseMode m, bool force)
275 if (_drags->active ()) {
276 return;
279 if (!force && m == mouse_mode) {
280 return;
283 Glib::RefPtr<Action> act;
285 switch (m) {
286 case MouseRange:
287 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
288 break;
290 case MouseObject:
291 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
292 break;
294 case MouseGain:
295 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-gain"));
296 break;
298 case MouseZoom:
299 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-zoom"));
300 break;
302 case MouseTimeFX:
303 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
304 break;
306 case MouseAudition:
307 act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
308 break;
311 assert (act);
313 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
314 assert (tact);
316 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
317 tact->set_active (false);
318 tact->set_active (true);
321 void
322 Editor::mouse_mode_toggled (MouseMode m)
324 mouse_mode = m;
326 instant_save ();
328 if (mouse_mode != MouseRange && _join_object_range_state == JOIN_OBJECT_RANGE_NONE) {
330 /* in all modes except range and joined object/range, hide the range selection,
331 show the object (region) selection.
334 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
335 (*i)->set_should_show_selection (true);
337 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
338 (*i)->hide_selection ();
341 } else {
344 in range or object/range mode, show the range selection.
347 for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
348 (*i)->show_selection (selection->time);
352 set_canvas_cursor ();
355 void
356 Editor::step_mouse_mode (bool next)
358 switch (current_mouse_mode()) {
359 case MouseObject:
360 if (next) {
361 if (Profile->get_sae()) {
362 set_mouse_mode (MouseZoom);
363 } else {
364 set_mouse_mode (MouseRange);
366 } else {
367 set_mouse_mode (MouseTimeFX);
369 break;
371 case MouseRange:
372 if (next) set_mouse_mode (MouseZoom);
373 else set_mouse_mode (MouseObject);
374 break;
376 case MouseZoom:
377 if (next) {
378 if (Profile->get_sae()) {
379 set_mouse_mode (MouseTimeFX);
380 } else {
381 set_mouse_mode (MouseGain);
383 } else {
384 if (Profile->get_sae()) {
385 set_mouse_mode (MouseObject);
386 } else {
387 set_mouse_mode (MouseRange);
390 break;
392 case MouseGain:
393 if (next) set_mouse_mode (MouseTimeFX);
394 else set_mouse_mode (MouseZoom);
395 break;
397 case MouseTimeFX:
398 if (next) {
399 set_mouse_mode (MouseAudition);
400 } else {
401 if (Profile->get_sae()) {
402 set_mouse_mode (MouseZoom);
403 } else {
404 set_mouse_mode (MouseGain);
407 break;
409 case MouseAudition:
410 if (next) set_mouse_mode (MouseObject);
411 else set_mouse_mode (MouseTimeFX);
412 break;
416 void
417 Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemType item_type)
419 /* in object/audition/timefx/gain-automation mode,
420 any button press sets the selection if the object
421 can be selected. this is a bit of hack, because
422 we want to avoid this if the mouse operation is a
423 region alignment.
425 note: not dbl-click or triple-click
428 if (((mouse_mode != MouseObject) &&
429 (_join_object_range_state != JOIN_OBJECT_RANGE_OBJECT) &&
430 (mouse_mode != MouseAudition || item_type != RegionItem) &&
431 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
432 (mouse_mode != MouseGain) &&
433 (mouse_mode != MouseRange)) ||
435 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
437 return;
440 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
442 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
444 /* almost no selection action on modified button-2 or button-3 events */
446 if (item_type != RegionItem && event->button.button != 2) {
447 return;
452 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
453 bool press = (event->type == GDK_BUTTON_PRESS);
455 // begin_reversible_command (_("select on click"));
457 switch (item_type) {
458 case RegionItem:
459 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
460 set_selected_regionview_from_click (press, op, true);
461 } else if (event->type == GDK_BUTTON_PRESS) {
462 selection->clear_tracks ();
463 set_selected_track_as_side_effect (true);
465 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
466 clicked_selection = select_range_around_region (selection->regions.front());
469 break;
471 case RegionViewNameHighlight:
472 case RegionViewName:
473 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
474 set_selected_regionview_from_click (press, op, true);
475 } else if (event->type == GDK_BUTTON_PRESS) {
476 set_selected_track_as_side_effect ();
478 break;
481 case FadeInHandleItem:
482 case FadeInItem:
483 case FadeOutHandleItem:
484 case FadeOutItem:
485 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
486 set_selected_regionview_from_click (press, op, true);
487 } else if (event->type == GDK_BUTTON_PRESS) {
488 set_selected_track_as_side_effect ();
490 break;
492 case ControlPointItem:
493 set_selected_track_as_side_effect ();
494 if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
495 set_selected_control_point_from_click (op, false);
497 break;
499 case StreamItem:
500 /* for context click, select track */
501 if (event->button.button == 3) {
502 set_selected_track_as_side_effect ();
504 break;
506 case AutomationTrackItem:
507 set_selected_track_as_side_effect (true);
508 break;
510 default:
511 break;
515 bool
516 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
518 if (_drags->active ()) {
519 _drags->abort ();
522 /* single mouse clicks on any of these item types operate
523 independent of mouse mode, mostly because they are
524 not on the main track canvas or because we want
525 them to be modeless.
528 switch (item_type) {
529 case PlayheadCursorItem:
530 _drags->set (new CursorDrag (this, item, true), event);
531 return true;
533 case MarkerItem:
534 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
535 hide_marker (item, event);
536 } else {
537 _drags->set (new MarkerDrag (this, item), event);
539 return true;
541 case TempoMarkerItem:
542 _drags->set (
543 new TempoMarkerDrag (
544 this,
545 item,
546 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
548 event
550 return true;
552 case MeterMarkerItem:
553 _drags->set (
554 new MeterMarkerDrag (
555 this,
556 item,
557 Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
559 event
561 return true;
563 case MarkerBarItem:
564 case TempoBarItem:
565 case MeterBarItem:
566 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
567 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
569 return true;
570 break;
573 case RangeMarkerBarItem:
574 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
575 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
576 } else {
577 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
579 return true;
580 break;
582 case CdMarkerBarItem:
583 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
584 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
585 } else {
586 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
588 return true;
589 break;
591 case TransportMarkerBarItem:
592 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
593 _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
594 } else {
595 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
597 return true;
598 break;
600 default:
601 break;
604 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
605 /* special case: allow trim of range selections in joined object mode;
606 in theory eff should equal MouseRange in this case, but it doesn't
607 because entering the range selection canvas item results in entered_regionview
608 being set to 0, so update_join_object_range_location acts as if we aren't
609 over a region.
611 if (item_type == StartSelectionTrimItem) {
612 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
613 } else if (item_type == EndSelectionTrimItem) {
614 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
618 Editing::MouseMode eff = effective_mouse_mode ();
620 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
621 if (item_type == FadeInHandleItem || item_type == FadeOutHandleItem) {
622 eff = MouseObject;
625 switch (eff) {
626 case MouseRange:
627 switch (item_type) {
628 case StartSelectionTrimItem:
629 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
630 break;
632 case EndSelectionTrimItem:
633 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
634 break;
636 case SelectionItem:
637 if (Keyboard::modifier_state_contains
638 (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier))) {
639 // contains and not equals because I can't use alt as a modifier alone.
640 start_selection_grab (item, event);
641 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
642 /* grab selection for moving */
643 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
644 } else {
645 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
646 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
647 if (tvp.first) {
648 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
649 if (join_object_range_button.get_active() && atv) {
650 /* smart "join" mode: drag automation */
651 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event);
652 } else {
653 /* this was debated, but decided the more common action was to
654 make a new selection */
655 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
659 break;
661 default:
662 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
664 return true;
665 break;
667 case MouseObject:
668 switch (item_type) {
669 case NoteItem:
670 if (internal_editing()) {
671 /* Note: we don't get here if not in internal_editing() mode */
672 _drags->set (new NoteDrag (this, item), event);
673 return true;
675 break;
677 default:
678 break;
681 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
682 event->type == GDK_BUTTON_PRESS) {
684 _drags->set (new RubberbandSelectDrag (this, item), event);
686 } else if (event->type == GDK_BUTTON_PRESS) {
688 switch (item_type) {
689 case FadeInHandleItem:
691 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
692 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event);
693 return true;
696 case FadeOutHandleItem:
698 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
699 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event);
700 return true;
703 case RegionItem:
704 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
705 add_region_copy_drag (item, event, clicked_regionview);
706 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
707 add_region_brush_drag (item, event, clicked_regionview);
708 } else {
709 add_region_drag (item, event, clicked_regionview);
712 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
713 _drags->add (new SelectionDrag (this, clicked_axisview->get_selection_rect (clicked_selection)->rect, SelectionDrag::SelectionMove));
716 _drags->start_grab (event);
717 break;
719 case RegionViewNameHighlight:
721 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
722 _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
723 return true;
724 break;
727 case RegionViewName:
729 /* rename happens on edit clicks */
730 RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
731 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, s.by_layer()), event);
732 return true;
733 break;
736 case ControlPointItem:
737 _drags->set (new ControlPointDrag (this, item), event);
738 return true;
739 break;
741 case AutomationLineItem:
742 _drags->set (new LineDrag (this, item), event);
743 return true;
744 break;
746 case StreamItem:
747 if (internal_editing()) {
748 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
749 return true;
750 } else {
751 _drags->set (new RubberbandSelectDrag (this, item), event);
753 break;
755 case AutomationTrackItem:
756 /* rubberband drag to select automation points */
757 _drags->set (new RubberbandSelectDrag (this, item), event);
758 break;
760 case SelectionItem:
762 if (join_object_range_button.get_active()) {
763 /* we're in "smart" joined mode, and we've clicked on a Selection */
764 double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
765 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
766 if (tvp.first) {
767 /* if we're over an automation track, start a drag of its data */
768 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
769 if (atv) {
770 _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event);
773 /* if we're over a track and a region, and in the `object' part of a region,
774 put a selection around the region and drag both
776 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
777 if (rtv && _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
778 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (rtv->route ());
779 if (t) {
780 boost::shared_ptr<Playlist> pl = t->diskstream()->playlist ();
781 if (pl) {
783 boost::shared_ptr<Region> r = pl->top_region_at (event_frame (event));
784 if (r) {
785 RegionView* rv = rtv->view()->find_view (r);
786 clicked_selection = select_range_around_region (rv);
787 _drags->add (new SelectionDrag (this, item, SelectionDrag::SelectionMove));
788 list<RegionView*> rvs;
789 rvs.push_back (rv);
790 _drags->add (new RegionMoveDrag (this, item, rv, rvs, false, false));
791 _drags->start_grab (event);
798 break;
801 #ifdef WITH_CMT
802 case ImageFrameHandleStartItem:
803 imageframe_start_handle_op(item, event) ;
804 return(true) ;
805 break ;
806 case ImageFrameHandleEndItem:
807 imageframe_end_handle_op(item, event) ;
808 return(true) ;
809 break ;
810 case MarkerViewHandleStartItem:
811 markerview_item_start_handle_op(item, event) ;
812 return(true) ;
813 break ;
814 case MarkerViewHandleEndItem:
815 markerview_item_end_handle_op(item, event) ;
816 return(true) ;
817 break ;
818 case MarkerViewItem:
819 start_markerview_grab(item, event) ;
820 break ;
821 case ImageFrameItem:
822 start_imageframe_grab(item, event) ;
823 break ;
824 #endif
826 case MarkerBarItem:
828 break;
830 default:
831 break;
834 return true;
835 break;
837 case MouseGain:
838 switch (item_type) {
839 case RegionItem:
840 /* start a grab so that if we finish after moving
841 we can tell what happened.
843 _drags->set (new RegionGainDrag (this, item), event, current_canvas_cursor);
844 break;
846 case GainLineItem:
847 _drags->set (new LineDrag (this, item), event);
848 return true;
850 case ControlPointItem:
851 _drags->set (new ControlPointDrag (this, item), event);
852 return true;
853 break;
855 default:
856 break;
858 return true;
859 break;
861 switch (item_type) {
862 case ControlPointItem:
863 _drags->set (new ControlPointDrag (this, item), event);
864 break;
866 case AutomationLineItem:
867 _drags->set (new LineDrag (this, item), event);
868 break;
870 case RegionItem:
871 // XXX need automation mode to identify which
872 // line to use
873 // start_line_grab_from_regionview (item, event);
874 break;
876 default:
877 break;
879 return true;
880 break;
882 case MouseZoom:
883 if (event->type == GDK_BUTTON_PRESS) {
884 _drags->set (new MouseZoomDrag (this, item), event);
887 return true;
888 break;
890 case MouseTimeFX:
891 if (internal_editing() && item_type == NoteItem) {
892 _drags->set (new NoteResizeDrag (this, item), event);
893 return true;
894 } else if (!internal_editing() && item_type == RegionItem) {
895 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
896 return true;
898 break;
900 case MouseAudition:
901 _drags->set (new ScrubDrag (this, item), event);
902 scrub_reversals = 0;
903 scrub_reverse_distance = 0;
904 last_scrub_x = event->button.x;
905 scrubbing_direction = 0;
906 track_canvas->get_window()->set_cursor (*transparent_cursor);
907 return true;
908 break;
910 default:
911 break;
914 return false;
917 bool
918 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
920 Editing::MouseMode const eff = effective_mouse_mode ();
921 switch (eff) {
922 case MouseObject:
923 switch (item_type) {
924 case RegionItem:
925 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
926 add_region_copy_drag (item, event, clicked_regionview);
927 } else {
928 add_region_drag (item, event, clicked_regionview);
930 _drags->start_grab (event);
931 return true;
932 break;
933 case ControlPointItem:
934 _drags->set (new ControlPointDrag (this, item), event);
935 return true;
936 break;
938 default:
939 break;
942 switch (item_type) {
943 case RegionViewNameHighlight:
944 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
945 return true;
946 break;
948 case RegionViewName:
949 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
950 return true;
951 break;
953 default:
954 break;
957 break;
959 case MouseRange:
960 /* relax till release */
961 return true;
962 break;
965 case MouseZoom:
966 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
967 temporal_zoom_session();
968 } else {
969 temporal_zoom_to_frame (true, event_frame(event));
971 return true;
972 break;
974 default:
975 break;
978 return false;
981 bool
982 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
984 if (event->type != GDK_BUTTON_PRESS) {
985 return false;
988 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
990 if (canvas_window) {
991 Glib::RefPtr<const Gdk::Window> pointer_window;
992 int x, y;
993 double wx, wy;
994 Gdk::ModifierType mask;
996 pointer_window = canvas_window->get_pointer (x, y, mask);
998 if (pointer_window == track_canvas->get_bin_window()) {
999 track_canvas->window_to_world (x, y, wx, wy);
1000 allow_vertical_scroll = true;
1001 } else {
1002 allow_vertical_scroll = false;
1006 track_canvas->grab_focus();
1008 if (_session && _session->actively_recording()) {
1009 return true;
1012 button_selection (item, event, item_type);
1014 if (!_drags->active () &&
1015 (Keyboard::is_delete_event (&event->button) ||
1016 Keyboard::is_context_menu_event (&event->button) ||
1017 Keyboard::is_edit_event (&event->button))) {
1019 /* handled by button release */
1020 return true;
1023 switch (event->button.button) {
1024 case 1:
1025 return button_press_handler_1 (item, event, item_type);
1026 break;
1028 case 2:
1029 return button_press_handler_2 (item, event, item_type);
1030 break;
1032 case 3:
1033 break;
1035 default:
1036 break;
1040 return false;
1043 bool
1044 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1046 nframes64_t where = event_frame (event, 0, 0);
1047 AutomationTimeAxisView* atv = 0;
1049 /* no action if we're recording */
1051 if (_session && _session->actively_recording()) {
1052 return true;
1055 /* first, see if we're finishing a drag ... */
1057 bool were_dragging = false;
1058 if (_drags->active ()) {
1059 bool const r = _drags->end_grab (event);
1060 if (r) {
1061 /* grab dragged, so do nothing else */
1062 return true;
1065 were_dragging = true;
1068 button_selection (item, event, item_type);
1070 /* edit events get handled here */
1072 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1073 switch (item_type) {
1074 case RegionItem:
1075 edit_region ();
1076 break;
1078 case TempoMarkerItem:
1079 edit_tempo_marker (item);
1080 break;
1082 case MeterMarkerItem:
1083 edit_meter_marker (item);
1084 break;
1086 case RegionViewName:
1087 if (clicked_regionview->name_active()) {
1088 return mouse_rename_region (item, event);
1090 break;
1092 case ControlPointItem:
1093 edit_control_point (item);
1094 break;
1096 default:
1097 break;
1099 return true;
1102 /* context menu events get handled here */
1104 if (Keyboard::is_context_menu_event (&event->button)) {
1106 if (!_drags->active ()) {
1108 /* no matter which button pops up the context menu, tell the menu
1109 widget to use button 1 to drive menu selection.
1112 switch (item_type) {
1113 case FadeInItem:
1114 case FadeInHandleItem:
1115 case FadeOutItem:
1116 case FadeOutHandleItem:
1117 popup_fade_context_menu (1, event->button.time, item, item_type);
1118 break;
1120 case StreamItem:
1121 popup_track_context_menu (1, event->button.time, item_type, false, where);
1122 break;
1124 case RegionItem:
1125 case RegionViewNameHighlight:
1126 case RegionViewName:
1127 popup_track_context_menu (1, event->button.time, item_type, false, where);
1128 break;
1130 case SelectionItem:
1131 popup_track_context_menu (1, event->button.time, item_type, true, where);
1132 break;
1134 case AutomationTrackItem:
1135 popup_track_context_menu (1, event->button.time, item_type, false, where);
1136 break;
1138 case MarkerBarItem:
1139 case RangeMarkerBarItem:
1140 case TransportMarkerBarItem:
1141 case CdMarkerBarItem:
1142 case TempoBarItem:
1143 case MeterBarItem:
1144 popup_ruler_menu (where, item_type);
1145 break;
1147 case MarkerItem:
1148 marker_context_menu (&event->button, item);
1149 break;
1151 case TempoMarkerItem:
1152 tm_marker_context_menu (&event->button, item);
1153 break;
1155 case MeterMarkerItem:
1156 tm_marker_context_menu (&event->button, item);
1157 break;
1159 case CrossfadeViewItem:
1160 popup_track_context_menu (1, event->button.time, item_type, false, where);
1161 break;
1163 #ifdef WITH_CMT
1164 case ImageFrameItem:
1165 popup_imageframe_edit_menu(1, event->button.time, item, true) ;
1166 break ;
1167 case ImageFrameTimeAxisItem:
1168 popup_imageframe_edit_menu(1, event->button.time, item, false) ;
1169 break ;
1170 case MarkerViewItem:
1171 popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ;
1172 break ;
1173 case MarkerTimeAxisItem:
1174 popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
1175 break ;
1176 #endif
1178 default:
1179 break;
1182 return true;
1186 /* delete events get handled here */
1188 Editing::MouseMode const eff = effective_mouse_mode ();
1190 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1192 switch (item_type) {
1193 case TempoMarkerItem:
1194 remove_tempo_marker (item);
1195 break;
1197 case MeterMarkerItem:
1198 remove_meter_marker (item);
1199 break;
1201 case MarkerItem:
1202 remove_marker (*item, event);
1203 break;
1205 case RegionItem:
1206 if (eff == MouseObject) {
1207 remove_clicked_region ();
1209 break;
1211 case ControlPointItem:
1212 if (eff == MouseGain) {
1213 remove_gain_control_point (item, event);
1214 } else {
1215 remove_control_point (item, event);
1217 break;
1219 default:
1220 break;
1222 return true;
1225 switch (event->button.button) {
1226 case 1:
1228 switch (item_type) {
1229 /* see comments in button_press_handler */
1230 case PlayheadCursorItem:
1231 case MarkerItem:
1232 case GainLineItem:
1233 case AutomationLineItem:
1234 case StartSelectionTrimItem:
1235 case EndSelectionTrimItem:
1236 return true;
1238 case MarkerBarItem:
1239 if (!_dragging_playhead) {
1240 snap_to_with_modifier (where, event, 0, true);
1241 mouse_add_new_marker (where);
1243 return true;
1245 case CdMarkerBarItem:
1246 if (!_dragging_playhead) {
1247 // if we get here then a dragged range wasn't done
1248 snap_to_with_modifier (where, event, 0, true);
1249 mouse_add_new_marker (where, true);
1251 return true;
1253 case TempoBarItem:
1254 if (!_dragging_playhead) {
1255 snap_to_with_modifier (where, event);
1256 mouse_add_new_tempo_event (where);
1258 return true;
1260 case MeterBarItem:
1261 if (!_dragging_playhead) {
1262 mouse_add_new_meter_event (pixel_to_frame (event->button.x));
1264 return true;
1265 break;
1267 default:
1268 break;
1271 switch (eff) {
1272 case MouseObject:
1273 switch (item_type) {
1274 case AutomationTrackItem:
1275 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1276 if (atv) {
1277 atv->add_automation_event (item, event, where, event->button.y);
1279 return true;
1280 break;
1282 default:
1283 break;
1285 break;
1287 case MouseGain:
1288 // Gain only makes sense for audio regions
1290 if (!dynamic_cast<AudioRegionView*>(clicked_regionview)) {
1291 break;
1294 switch (item_type) {
1295 case RegionItem:
1296 /* check that we didn't drag before releasing, since
1297 its really annoying to create new control
1298 points when doing this.
1300 if (were_dragging) {
1301 dynamic_cast<AudioRegionView*>(clicked_regionview)->add_gain_point_event (item, event);
1303 return true;
1304 break;
1306 case AutomationTrackItem:
1307 dynamic_cast<AutomationTimeAxisView*>(clicked_axisview)->
1308 add_automation_event (item, event, where, event->button.y);
1309 return true;
1310 break;
1311 default:
1312 break;
1314 break;
1316 case MouseAudition:
1317 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1318 if (scrubbing_direction == 0) {
1319 /* no drag, just a click */
1320 switch (item_type) {
1321 case RegionItem:
1322 play_selected_region ();
1323 break;
1324 default:
1325 break;
1327 } else {
1328 /* make sure we stop */
1329 _session->request_transport_speed (0.0);
1331 break;
1333 default:
1334 break;
1338 return true;
1339 break;
1342 case 2:
1343 switch (eff) {
1345 case MouseObject:
1346 switch (item_type) {
1347 case RegionItem:
1348 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1349 raise_region ();
1350 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1351 lower_region ();
1352 } else {
1353 // Button2 click is unused
1355 return true;
1357 break;
1359 default:
1360 break;
1362 break;
1364 case MouseRange:
1366 // x_style_paste (where, 1.0);
1367 return true;
1368 break;
1370 default:
1371 break;
1374 break;
1376 case 3:
1377 break;
1379 default:
1380 break;
1382 return false;
1385 bool
1386 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1388 ControlPoint* cp;
1389 Marker * marker;
1390 double fraction;
1392 if (last_item_entered != item) {
1393 last_item_entered = item;
1394 last_item_entered_n = 0;
1397 switch (item_type) {
1398 case ControlPointItem:
1399 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1400 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1401 cp->set_visible (true);
1403 double at_x, at_y;
1404 at_x = cp->get_x();
1405 at_y = cp->get_y ();
1406 cp->i2w (at_x, at_y);
1407 at_x += 10.0;
1408 at_y += 10.0;
1410 fraction = 1.0 - (cp->get_y() / cp->line().height());
1412 if (is_drawable() && !_drags->active ()) {
1413 track_canvas->get_window()->set_cursor (*fader_cursor);
1416 last_item_entered_n++;
1417 set_verbose_canvas_cursor (cp->line().get_verbose_cursor_string (fraction), at_x, at_y);
1418 if (last_item_entered_n < 10) {
1419 show_verbose_canvas_cursor ();
1422 break;
1424 case GainLineItem:
1425 if (mouse_mode == MouseGain) {
1426 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1427 if (line)
1428 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
1429 if (is_drawable()) {
1430 track_canvas->get_window()->set_cursor (*fader_cursor);
1433 break;
1435 case AutomationLineItem:
1436 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1438 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1439 if (line)
1440 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
1442 if (is_drawable()) {
1443 track_canvas->get_window()->set_cursor (*fader_cursor);
1446 break;
1448 case RegionViewNameHighlight:
1449 if (is_drawable() && mouse_mode == MouseObject) {
1450 track_canvas->get_window()->set_cursor (*trimmer_cursor);
1452 break;
1454 case StartSelectionTrimItem:
1455 case EndSelectionTrimItem:
1457 #ifdef WITH_CMT
1458 case ImageFrameHandleStartItem:
1459 case ImageFrameHandleEndItem:
1460 case MarkerViewHandleStartItem:
1461 case MarkerViewHandleEndItem:
1462 #endif
1464 if (is_drawable()) {
1465 track_canvas->get_window()->set_cursor (*trimmer_cursor);
1467 break;
1469 case PlayheadCursorItem:
1470 if (is_drawable()) {
1471 switch (_edit_point) {
1472 case EditAtMouse:
1473 track_canvas->get_window()->set_cursor (*grabber_edit_point_cursor);
1474 break;
1475 default:
1476 track_canvas->get_window()->set_cursor (*grabber_cursor);
1477 break;
1480 break;
1482 case RegionViewName:
1484 /* when the name is not an active item, the entire name highlight is for trimming */
1486 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1487 if (mouse_mode == MouseObject && is_drawable()) {
1488 track_canvas->get_window()->set_cursor (*trimmer_cursor);
1491 break;
1494 case AutomationTrackItem:
1495 if (is_drawable()) {
1496 Gdk::Cursor *cursor;
1497 switch (mouse_mode) {
1498 case MouseRange:
1499 cursor = selector_cursor;
1500 break;
1501 case MouseZoom:
1502 cursor = zoom_cursor;
1503 break;
1504 default:
1505 cursor = cross_hair_cursor;
1506 break;
1509 track_canvas->get_window()->set_cursor (*cursor);
1511 AutomationTimeAxisView* atv;
1512 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1513 clear_entered_track = false;
1514 set_entered_track (atv);
1517 break;
1519 case MarkerBarItem:
1520 case RangeMarkerBarItem:
1521 case TransportMarkerBarItem:
1522 case CdMarkerBarItem:
1523 case MeterBarItem:
1524 case TempoBarItem:
1525 if (is_drawable()) {
1526 track_canvas->get_window()->set_cursor (*timebar_cursor);
1528 break;
1530 case MarkerItem:
1531 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1532 break;
1534 entered_marker = marker;
1535 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1536 // fall through
1537 case MeterMarkerItem:
1538 case TempoMarkerItem:
1539 if (is_drawable()) {
1540 track_canvas->get_window()->set_cursor (*timebar_cursor);
1542 break;
1543 case FadeInHandleItem:
1544 case FadeOutHandleItem:
1545 if (mouse_mode == MouseObject) {
1546 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1547 if (rect) {
1548 rect->property_fill_color_rgba() = 0;
1549 rect->property_outline_pixels() = 1;
1551 track_canvas->get_window()->set_cursor (*grabber_cursor);
1553 break;
1555 default:
1556 break;
1559 /* second pass to handle entered track status in a comprehensible way.
1562 switch (item_type) {
1563 case GainLineItem:
1564 case AutomationLineItem:
1565 case ControlPointItem:
1566 /* these do not affect the current entered track state */
1567 clear_entered_track = false;
1568 break;
1570 case AutomationTrackItem:
1571 /* handled above already */
1572 break;
1574 default:
1575 set_entered_track (0);
1576 break;
1579 return false;
1582 bool
1583 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1585 AutomationLine* al;
1586 ControlPoint* cp;
1587 Marker *marker;
1588 Location *loc;
1589 RegionView* rv;
1590 bool is_start;
1592 switch (item_type) {
1593 case ControlPointItem:
1594 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1595 if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
1596 if (cp->line().npoints() > 1 && !cp->selected()) {
1597 cp->set_visible (false);
1601 if (is_drawable()) {
1602 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1605 hide_verbose_canvas_cursor ();
1606 break;
1608 case RegionViewNameHighlight:
1609 case StartSelectionTrimItem:
1610 case EndSelectionTrimItem:
1611 case PlayheadCursorItem:
1613 #ifdef WITH_CMT
1614 case ImageFrameHandleStartItem:
1615 case ImageFrameHandleEndItem:
1616 case MarkerViewHandleStartItem:
1617 case MarkerViewHandleEndItem:
1618 #endif
1620 if (is_drawable()) {
1621 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1623 break;
1625 case GainLineItem:
1626 case AutomationLineItem:
1627 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1629 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1630 if (line)
1631 line->property_fill_color_rgba() = al->get_line_color();
1633 if (is_drawable()) {
1634 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1636 break;
1638 case RegionViewName:
1639 /* see enter_handler() for notes */
1640 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1641 if (is_drawable() && mouse_mode == MouseObject) {
1642 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1645 break;
1647 case RangeMarkerBarItem:
1648 case TransportMarkerBarItem:
1649 case CdMarkerBarItem:
1650 case MeterBarItem:
1651 case TempoBarItem:
1652 case MarkerBarItem:
1653 if (is_drawable()) {
1654 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1656 break;
1658 case MarkerItem:
1659 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1660 break;
1662 entered_marker = 0;
1663 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1664 location_flags_changed (loc, this);
1666 // fall through
1667 case MeterMarkerItem:
1668 case TempoMarkerItem:
1670 if (is_drawable()) {
1671 track_canvas->get_window()->set_cursor (*timebar_cursor);
1674 break;
1676 case FadeInHandleItem:
1677 case FadeOutHandleItem:
1678 rv = static_cast<RegionView*>(item->get_data ("regionview"));
1680 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1681 if (rect) {
1682 rect->property_fill_color_rgba() = rv->get_fill_color();
1683 rect->property_outline_pixels() = 0;
1686 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1687 break;
1689 case AutomationTrackItem:
1690 if (is_drawable()) {
1691 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1692 clear_entered_track = true;
1693 Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track));
1695 break;
1697 default:
1698 break;
1701 return false;
1704 gint
1705 Editor::left_automation_track ()
1707 if (clear_entered_track) {
1708 set_entered_track (0);
1709 clear_entered_track = false;
1711 return false;
1714 void
1715 Editor::scrub (nframes64_t frame, double current_x)
1717 double delta;
1719 if (scrubbing_direction == 0) {
1720 /* first move */
1721 _session->request_locate (frame, false);
1722 _session->request_transport_speed (0.1);
1723 scrubbing_direction = 1;
1725 } else {
1727 if (last_scrub_x > current_x) {
1729 /* pointer moved to the left */
1731 if (scrubbing_direction > 0) {
1733 /* we reversed direction to go backwards */
1735 scrub_reversals++;
1736 scrub_reverse_distance += (int) (last_scrub_x - current_x);
1738 } else {
1740 /* still moving to the left (backwards) */
1742 scrub_reversals = 0;
1743 scrub_reverse_distance = 0;
1745 delta = 0.01 * (last_scrub_x - current_x);
1746 _session->request_transport_speed (_session->transport_speed() - delta);
1749 } else {
1750 /* pointer moved to the right */
1752 if (scrubbing_direction < 0) {
1753 /* we reversed direction to go forward */
1755 scrub_reversals++;
1756 scrub_reverse_distance += (int) (current_x - last_scrub_x);
1758 } else {
1759 /* still moving to the right */
1761 scrub_reversals = 0;
1762 scrub_reverse_distance = 0;
1764 delta = 0.01 * (current_x - last_scrub_x);
1765 _session->request_transport_speed (_session->transport_speed() + delta);
1769 /* if there have been more than 2 opposite motion moves detected, or one that moves
1770 back more than 10 pixels, reverse direction
1773 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1775 if (scrubbing_direction > 0) {
1776 /* was forwards, go backwards */
1777 _session->request_transport_speed (-0.1);
1778 scrubbing_direction = -1;
1779 } else {
1780 /* was backwards, go forwards */
1781 _session->request_transport_speed (0.1);
1782 scrubbing_direction = 1;
1785 scrub_reverse_distance = 0;
1786 scrub_reversals = 0;
1790 last_scrub_x = current_x;
1793 bool
1794 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
1796 if (event->motion.is_hint) {
1797 gint x, y;
1799 /* We call this so that MOTION_NOTIFY events continue to be
1800 delivered to the canvas. We need to do this because we set
1801 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1802 the density of the events, at the expense of a round-trip
1803 to the server. Given that this will mostly occur on cases
1804 where DISPLAY = :0.0, and given the cost of what the motion
1805 event might do, its a good tradeoff.
1808 track_canvas->get_pointer (x, y);
1811 if (current_stepping_trackview) {
1812 /* don't keep the persistent stepped trackview if the mouse moves */
1813 current_stepping_trackview = 0;
1814 step_timeout.disconnect ();
1817 if (_session && _session->actively_recording()) {
1818 /* Sorry. no dragging stuff around while we record */
1819 return true;
1822 JoinObjectRangeState const old = _join_object_range_state;
1823 update_join_object_range_location (event->motion.x, event->motion.y);
1824 if (_join_object_range_state != old) {
1825 set_canvas_cursor ();
1828 bool handled = false;
1829 if (_drags->active ()) {
1830 handled = _drags->motion_handler (event, from_autoscroll);
1833 if (!handled) {
1834 return false;
1837 track_canvas_motion (event);
1838 return true;
1841 void
1842 Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* /*event*/)
1844 ControlPoint* control_point;
1846 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1847 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1848 /*NOTREACHED*/
1851 // We shouldn't remove the first or last gain point
1852 if (control_point->line().is_last_point(*control_point) ||
1853 control_point->line().is_first_point(*control_point)) {
1854 return;
1857 control_point->line().remove_point (*control_point);
1860 void
1861 Editor::remove_control_point (ArdourCanvas::Item* item, GdkEvent* /*event*/)
1863 ControlPoint* control_point;
1865 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
1866 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1867 /*NOTREACHED*/
1870 control_point->line().remove_point (*control_point);
1873 void
1874 Editor::edit_control_point (ArdourCanvas::Item* item)
1876 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
1878 if (p == 0) {
1879 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
1880 /*NOTREACHED*/
1883 ControlPointDialog d (p);
1884 d.set_position (Gtk::WIN_POS_MOUSE);
1885 ensure_float (d);
1887 if (d.run () != RESPONSE_ACCEPT) {
1888 return;
1891 p->line().modify_point_y (*p, d.get_y_fraction ());
1895 void
1896 Editor::visible_order_range (int* low, int* high) const
1898 *low = TimeAxisView::max_order ();
1899 *high = 0;
1901 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
1903 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
1905 if (!rtv->hidden()) {
1907 if (*high < rtv->order()) {
1908 *high = rtv->order ();
1911 if (*low > rtv->order()) {
1912 *low = rtv->order ();
1918 void
1919 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
1921 /* Either add to or set the set the region selection, unless
1922 this is an alignment click (control used)
1925 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
1926 TimeAxisView* tv = &rv.get_time_axis_view();
1927 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
1928 double speed = 1.0;
1929 if (rtv && rtv->is_track()) {
1930 speed = rtv->get_diskstream()->speed();
1933 nframes64_t where = get_preferred_edit_position();
1935 if (where >= 0) {
1937 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
1939 align_region (rv.region(), SyncPoint, (nframes64_t) (where * speed));
1941 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
1943 align_region (rv.region(), End, (nframes64_t) (where * speed));
1945 } else {
1947 align_region (rv.region(), Start, (nframes64_t) (where * speed));
1953 void
1954 Editor::show_verbose_time_cursor (nframes64_t frame, double offset, double xpos, double ypos)
1956 char buf[128];
1957 Timecode::Time timecode;
1958 BBT_Time bbt;
1959 int hours, mins;
1960 nframes64_t frame_rate;
1961 float secs;
1963 if (_session == 0) {
1964 return;
1967 AudioClock::Mode m;
1969 if (Profile->get_sae() || Profile->get_small_screen()) {
1970 m = ARDOUR_UI::instance()->primary_clock.mode();
1971 } else {
1972 m = ARDOUR_UI::instance()->secondary_clock.mode();
1975 switch (m) {
1976 case AudioClock::BBT:
1977 _session->bbt_time (frame, bbt);
1978 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, bbt.bars, bbt.beats, bbt.ticks);
1979 break;
1981 case AudioClock::Timecode:
1982 _session->timecode_time (frame, timecode);
1983 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
1984 break;
1986 case AudioClock::MinSec:
1987 /* XXX this is copied from show_verbose_duration_cursor() */
1988 frame_rate = _session->frame_rate();
1989 hours = frame / (frame_rate * 3600);
1990 frame = frame % (frame_rate * 3600);
1991 mins = frame / (frame_rate * 60);
1992 frame = frame % (frame_rate * 60);
1993 secs = (float) frame / (float) frame_rate;
1994 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%.4f", hours, mins, secs);
1995 break;
1997 default:
1998 snprintf (buf, sizeof(buf), "%" PRIi64, frame);
1999 break;
2002 if (xpos >= 0 && ypos >=0) {
2003 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2004 } else {
2005 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset - horizontal_adjustment.get_value(), _drags->current_pointer_y() + offset - vertical_adjustment.get_value() + canvas_timebars_vsize);
2007 show_verbose_canvas_cursor ();
2010 void
2011 Editor::show_verbose_duration_cursor (nframes64_t start, nframes64_t end, double offset, double xpos, double ypos)
2013 char buf[128];
2014 Timecode::Time timecode;
2015 BBT_Time sbbt;
2016 BBT_Time ebbt;
2017 int hours, mins;
2018 nframes64_t distance, frame_rate;
2019 float secs;
2020 Meter meter_at_start(_session->tempo_map().meter_at(start));
2022 if (_session == 0) {
2023 return;
2026 AudioClock::Mode m;
2028 if (Profile->get_sae() || Profile->get_small_screen()) {
2029 m = ARDOUR_UI::instance()->primary_clock.mode ();
2030 } else {
2031 m = ARDOUR_UI::instance()->secondary_clock.mode ();
2034 switch (m) {
2035 case AudioClock::BBT:
2036 _session->bbt_time (start, sbbt);
2037 _session->bbt_time (end, ebbt);
2039 /* subtract */
2040 /* XXX this computation won't work well if the
2041 user makes a selection that spans any meter changes.
2044 ebbt.bars -= sbbt.bars;
2045 if (ebbt.beats >= sbbt.beats) {
2046 ebbt.beats -= sbbt.beats;
2047 } else {
2048 ebbt.bars--;
2049 ebbt.beats = int(meter_at_start.beats_per_bar()) + ebbt.beats - sbbt.beats;
2051 if (ebbt.ticks >= sbbt.ticks) {
2052 ebbt.ticks -= sbbt.ticks;
2053 } else {
2054 ebbt.beats--;
2055 ebbt.ticks = int(Meter::ticks_per_beat) + ebbt.ticks - sbbt.ticks;
2058 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, ebbt.bars, ebbt.beats, ebbt.ticks);
2059 break;
2061 case AudioClock::Timecode:
2062 _session->timecode_duration (end - start, timecode);
2063 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2064 break;
2066 case AudioClock::MinSec:
2067 /* XXX this stuff should be elsewhere.. */
2068 distance = end - start;
2069 frame_rate = _session->frame_rate();
2070 hours = distance / (frame_rate * 3600);
2071 distance = distance % (frame_rate * 3600);
2072 mins = distance / (frame_rate * 60);
2073 distance = distance % (frame_rate * 60);
2074 secs = (float) distance / (float) frame_rate;
2075 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%.4f", hours, mins, secs);
2076 break;
2078 default:
2079 snprintf (buf, sizeof(buf), "%" PRIi64, end - start);
2080 break;
2083 if (xpos >= 0 && ypos >=0) {
2084 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
2086 else {
2087 set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset, _drags->current_pointer_y() + offset);
2090 show_verbose_canvas_cursor ();
2093 void
2094 Editor::collect_new_region_view (RegionView* rv)
2096 latest_regionviews.push_back (rv);
2099 void
2100 Editor::collect_and_select_new_region_view (RegionView* rv)
2102 selection->add(rv);
2103 latest_regionviews.push_back (rv);
2106 void
2107 Editor::cancel_selection ()
2109 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2110 (*i)->hide_selection ();
2113 selection->clear ();
2114 clicked_selection = 0;
2118 void
2119 Editor::single_contents_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool swap_direction, bool obey_snap)
2121 boost::shared_ptr<Region> region (rv.region());
2123 if (region->locked()) {
2124 return;
2127 nframes64_t new_bound;
2129 double speed = 1.0;
2130 TimeAxisView* tvp = clicked_axisview;
2131 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2133 if (tv && tv->is_track()) {
2134 speed = tv->get_diskstream()->speed();
2137 if (left_direction) {
2138 if (swap_direction) {
2139 new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
2140 } else {
2141 new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
2143 } else {
2144 if (swap_direction) {
2145 new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
2146 } else {
2147 new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
2151 if (obey_snap) {
2152 snap_to (new_bound);
2154 region->trim_start ((nframes64_t) (new_bound * speed), this);
2155 rv.region_changed (PropertyChange (ARDOUR::Properties::start));
2158 void
2159 Editor::single_start_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool obey_snap, bool no_overlap)
2161 boost::shared_ptr<Region> region (rv.region());
2163 if (region->locked()) {
2164 return;
2167 nframes64_t new_bound;
2169 double speed = 1.0;
2170 TimeAxisView* tvp = clicked_axisview;
2171 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2173 if (tv && tv->is_track()) {
2174 speed = tv->get_diskstream()->speed();
2177 if (left_direction) {
2178 new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
2179 } else {
2180 new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
2183 if (obey_snap) {
2184 snap_to (new_bound, (left_direction ? 0 : 1));
2187 nframes64_t pre_trim_first_frame = region->first_frame();
2189 region->trim_front ((nframes64_t) (new_bound * speed), this);
2191 if (no_overlap) {
2192 //Get the next region on the left of this region and shrink/expand it.
2193 boost::shared_ptr<Playlist> playlist (region->playlist());
2194 boost::shared_ptr<Region> region_left = playlist->find_next_region (pre_trim_first_frame, End, 0);
2196 bool regions_touching = false;
2198 if (region_left != 0 && (pre_trim_first_frame == region_left->last_frame() + 1)){
2199 regions_touching = true;
2202 //Only trim region on the left if the first frame has gone beyond the left region's last frame.
2203 if (region_left != 0 &&
2204 (region_left->last_frame() > region->first_frame() || regions_touching))
2206 region_left->trim_end(region->first_frame() - 1, this);
2210 rv.region_changed (ARDOUR::bounds_change);
2213 void
2214 Editor::single_end_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool obey_snap, bool no_overlap)
2216 boost::shared_ptr<Region> region (rv.region());
2218 if (region->locked()) {
2219 return;
2222 nframes64_t new_bound;
2224 double speed = 1.0;
2225 TimeAxisView* tvp = clicked_axisview;
2226 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2228 if (tv && tv->is_track()) {
2229 speed = tv->get_diskstream()->speed();
2232 if (left_direction) {
2233 new_bound = (nframes64_t) ((region->last_frame() + 1)/speed) - frame_delta;
2234 } else {
2235 new_bound = (nframes64_t) ((region->last_frame() + 1)/speed) + frame_delta;
2238 if (obey_snap) {
2239 snap_to (new_bound);
2242 nframes64_t pre_trim_last_frame = region->last_frame();
2244 region->trim_end ((nframes64_t) (new_bound * speed), this);
2246 if (no_overlap) {
2247 //Get the next region on the right of this region and shrink/expand it.
2248 boost::shared_ptr<Playlist> playlist (region->playlist());
2249 boost::shared_ptr<Region> region_right = playlist->find_next_region (pre_trim_last_frame, Start, 1);
2251 bool regions_touching = false;
2253 if (region_right != 0 && (pre_trim_last_frame == region_right->first_frame() - 1)){
2254 regions_touching = true;
2257 //Only trim region on the right if the last frame has gone beyond the right region's first frame.
2258 if (region_right != 0 &&
2259 (region_right->first_frame() < region->last_frame() || regions_touching))
2261 region_right->trim_front(region->last_frame() + 1, this);
2264 rv.region_changed (ARDOUR::bounds_change);
2266 } else {
2267 rv.region_changed (PropertyChange (ARDOUR::Properties::length));
2272 void
2273 Editor::point_trim (GdkEvent* event, nframes64_t new_bound)
2275 RegionView* rv = clicked_regionview;
2277 /* Choose action dependant on which button was pressed */
2278 switch (event->button.button) {
2279 case 1:
2280 begin_reversible_command (_("Start point trim"));
2282 if (selection->selected (rv)) {
2283 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2284 i != selection->regions.by_layer().end(); ++i)
2286 if ( (*i) == NULL){
2287 cerr << "region view contains null region" << endl;
2290 if (!(*i)->region()->locked()) {
2291 (*i)->region()->clear_history ();
2292 (*i)->region()->trim_front (new_bound, this);
2293 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2297 } else {
2298 if (!rv->region()->locked()) {
2299 rv->region()->clear_history ();
2300 rv->region()->trim_front (new_bound, this);
2301 _session->add_command(new StatefulDiffCommand (rv->region()));
2305 commit_reversible_command();
2307 break;
2308 case 2:
2309 begin_reversible_command (_("End point trim"));
2311 if (selection->selected (rv)) {
2313 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2315 if (!(*i)->region()->locked()) {
2316 (*i)->region()->clear_history();
2317 (*i)->region()->trim_end (new_bound, this);
2318 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2322 } else {
2324 if (!rv->region()->locked()) {
2325 rv->region()->clear_history ();
2326 rv->region()->trim_end (new_bound, this);
2327 _session->add_command (new StatefulDiffCommand (rv->region()));
2331 commit_reversible_command();
2333 break;
2334 default:
2335 break;
2339 void
2340 Editor::thaw_region_after_trim (RegionView& rv)
2342 boost::shared_ptr<Region> region (rv.region());
2344 if (region->locked()) {
2345 return;
2348 region->resume_property_changes ();
2350 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(&rv);
2352 if (arv) {
2353 arv->unhide_envelope ();
2357 void
2358 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2360 Marker* marker;
2361 bool is_start;
2363 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2364 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2365 /*NOTREACHED*/
2368 Location* location = find_location_from_marker (marker, is_start);
2369 location->set_hidden (true, this);
2373 void
2374 Editor::reposition_zoom_rect (nframes64_t start, nframes64_t end)
2376 double x1 = frame_to_pixel (start);
2377 double x2 = frame_to_pixel (end);
2378 double y2 = full_canvas_height - 1.0;
2380 zoom_rect->property_x1() = x1;
2381 zoom_rect->property_y1() = 1.0;
2382 zoom_rect->property_x2() = x2;
2383 zoom_rect->property_y2() = y2;
2387 gint
2388 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2390 using namespace Gtkmm2ext;
2392 ArdourPrompter prompter (false);
2394 prompter.set_prompt (_("Name for region:"));
2395 prompter.set_initial_text (clicked_regionview->region()->name());
2396 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2397 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2398 prompter.show_all ();
2399 switch (prompter.run ()) {
2400 case Gtk::RESPONSE_ACCEPT:
2401 string str;
2402 prompter.get_result(str);
2403 if (str.length()) {
2404 clicked_regionview->region()->set_name (str);
2406 break;
2408 return true;
2412 void
2413 Editor::mouse_brush_insert_region (RegionView* rv, nframes64_t pos)
2415 /* no brushing without a useful snap setting */
2417 switch (_snap_mode) {
2418 case SnapMagnetic:
2419 return; /* can't work because it allows region to be placed anywhere */
2420 default:
2421 break; /* OK */
2424 switch (_snap_type) {
2425 case SnapToMark:
2426 return;
2428 default:
2429 break;
2432 /* don't brush a copy over the original */
2434 if (pos == rv->region()->position()) {
2435 return;
2438 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2440 if (rtv == 0 || !rtv->is_track()) {
2441 return;
2444 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2445 double speed = rtv->get_diskstream()->speed();
2447 playlist->clear_history ();
2448 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region()));
2449 playlist->add_region (new_region, (nframes64_t) (pos * speed));
2450 _session->add_command (new StatefulDiffCommand (playlist));
2452 // playlist is frozen, so we have to update manually XXX this is disgusting
2454 playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2457 gint
2458 Editor::track_height_step_timeout ()
2460 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2461 current_stepping_trackview = 0;
2462 return false;
2464 return true;
2467 void
2468 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2470 assert (region_view);
2472 _region_motion_group->raise_to_top ();
2474 if (Config->get_edit_mode() == Splice) {
2475 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2476 } else {
2477 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2478 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false));
2481 /* sync the canvas to what we think is its current state */
2482 update_canvas_now();
2485 void
2486 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2488 assert (region_view);
2490 _region_motion_group->raise_to_top ();
2492 RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
2493 _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true));
2496 void
2497 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
2499 assert (region_view);
2501 if (Config->get_edit_mode() == Splice) {
2502 return;
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(), true, false));
2508 begin_reversible_command (_("Drag region brush"));
2511 /** Start a grab where a time range is selected, track(s) are selected, and the
2512 * user clicks and drags a region with a modifier in order to create a new region containing
2513 * the section of the clicked region that lies within the time range.
2515 void
2516 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2518 if (clicked_regionview == 0) {
2519 return;
2522 /* lets try to create new Region for the selection */
2524 vector<boost::shared_ptr<Region> > new_regions;
2525 create_region_from_selection (new_regions);
2527 if (new_regions.empty()) {
2528 return;
2531 /* XXX fix me one day to use all new regions */
2533 boost::shared_ptr<Region> region (new_regions.front());
2535 /* add it to the current stream/playlist.
2537 tricky: the streamview for the track will add a new regionview. we will
2538 catch the signal it sends when it creates the regionview to
2539 set the regionview we want to then drag.
2542 latest_regionviews.clear();
2543 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2545 /* A selection grab currently creates two undo/redo operations, one for
2546 creating the new region and another for moving it.
2549 begin_reversible_command (_("selection grab"));
2551 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2553 playlist->clear_history ();
2554 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2555 _session->add_command(new StatefulDiffCommand (playlist));
2557 commit_reversible_command ();
2559 c.disconnect ();
2561 if (latest_regionviews.empty()) {
2562 /* something went wrong */
2563 return;
2566 /* we need to deselect all other regionviews, and select this one
2567 i'm ignoring undo stuff, because the region creation will take care of it
2569 selection->set (latest_regionviews);
2571 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
2574 void
2575 Editor::escape ()
2577 if (_drags->active ()) {
2578 _drags->break_drag ();
2579 } else {
2580 selection->clear ();
2584 void
2585 Editor::set_internal_edit (bool yn)
2587 _internal_editing = yn;
2589 if (yn) {
2590 mouse_select_button.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
2591 mouse_select_button.get_image ()->show ();
2593 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2594 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (*i);
2595 if (mtv) {
2596 mtv->start_step_editing ();
2599 start_step_editing ();
2601 } else {
2603 mouse_select_button.set_image (*(manage (new Image (::get_icon("tool_range")))));
2604 mouse_select_button.get_image ()->show ();
2605 stop_step_editing ();
2607 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2608 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (*i);
2609 if (mtv) {
2610 mtv->stop_step_editing ();
2615 set_canvas_cursor ();
2620 /** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
2621 * used by the `join object/range' tool mode.
2623 void
2624 Editor::update_join_object_range_location (double x, double y)
2626 /* XXX: actually, this decides based on whether the mouse is in the top or bottom half of a RouteTimeAxisView;
2627 entered_{track,regionview} is not always setup (e.g. if the mouse is over a TimeSelection), and to get a Region
2628 that we're over requires searching the playlist.
2631 if (join_object_range_button.get_active() == false || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
2632 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2633 return;
2636 if (mouse_mode == MouseObject) {
2637 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2638 } else if (mouse_mode == MouseRange) {
2639 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2642 /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
2643 pair<TimeAxisView*, int> tvp = trackview_by_y_position (y + vertical_adjustment.get_value() - canvas_timebars_vsize);
2645 if (tvp.first) {
2647 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2648 if (rtv) {
2650 double cx = 0;
2651 double cy = y;
2652 rtv->canvas_display()->w2i (cx, cy);
2654 bool const top_half = cy < rtv->current_height () / 2;
2656 _join_object_range_state = top_half ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2661 Editing::MouseMode
2662 Editor::effective_mouse_mode () const
2664 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2665 return MouseObject;
2666 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2667 return MouseRange;
2670 return mouse_mode;