1 /*
2 * Copyright (C) 2005-2006 Taybin Rutkin <taybin@taybin.com>
3 * Copyright (C) 2005-2018 Paul Davis <paul@linuxaudiosystems.com>
4 * Copyright (C) 2006-2015 David Robillard <d@drobilla.net>
5 * Copyright (C) 2006-2016 Tim Mayberry <mojofunk@gmail.com>
6 * Copyright (C) 2006 Sampo Savolainen <v2@iki.fi>
7 * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
8 * Copyright (C) 2007 Doug McLain <doug@nostar.net>
9 * Copyright (C) 2013-2014 Colin Fletcher <colin.m.fletcher@googlemail.com>
10 * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
11 * Copyright (C) 2014-2017 Nick Mainsbridge <mainsbridge@gmail.com>
12 * Copyright (C) 2014-2018 Ben Loftis <ben@harrisonconsoles.com>
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 */
28
29 #include <cassert>
30 #include <cstdlib>
31 #include <stdint.h>
32 #include <cmath>
33 #include <set>
34 #include <string>
35 #include <algorithm>
36 #include <bitset>
37
38 #include "pbd/error.h"
39 #include "pbd/enumwriter.h"
40 #include "pbd/memento_command.h"
41 #include "pbd/basename.h"
42 #include "pbd/stateful_diff_command.h"
43
44 #include "gtkmm2ext/bindings.h"
45 #include "gtkmm2ext/utils.h"
46
47 #include "canvas/canvas.h"
48
49 #include "ardour/audioplaylist.h"
50 #include "ardour/audioregion.h"
51 #include "ardour/operations.h"
52 #include "ardour/playlist.h"
53 #include "ardour/profile.h"
54 #include "ardour/region_factory.h"
55 #include "ardour/route.h"
56 #include "ardour/session.h"
57 #include "ardour/types.h"
58
59 #include "widgets/prompter.h"
60
61 #include "actions.h"
62 #include "ardour_ui.h"
63 #include "editor.h"
64 #include "time_axis_view.h"
65 #include "audio_time_axis.h"
66 #include "audio_region_view.h"
67 #include "midi_region_view.h"
68 #include "marker.h"
69 #include "streamview.h"
70 #include "region_gain_line.h"
71 #include "rc_option_editor.h"
72 #include "automation_time_axis.h"
73 #include "control_point.h"
74 #include "selection.h"
75 #include "keyboard.h"
76 #include "editing.h"
77 #include "rgb_macros.h"
78 #include "control_point_dialog.h"
79 #include "editor_drag.h"
80 #include "automation_region_view.h"
81 #include "edit_note_dialog.h"
82 #include "mouse_cursors.h"
83 #include "editor_cursors.h"
84 #include "region_peak_cursor.h"
85 #include "verbose_cursor.h"
86 #include "note.h"
87
88 #include "pbd/i18n.h"
89
90 using namespace std;
91 using namespace ARDOUR;
92 using namespace PBD;
93 using namespace Gtk;
94 using namespace Editing;
95 using Gtkmm2ext::Keyboard;
96
97 bool
mouse_sample(samplepos_t & where,bool & in_track_canvas) const98 Editor::mouse_sample (samplepos_t& where, bool& in_track_canvas) const
99 {
100 /* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only
101 * pays attentions to subwindows. this means that menu windows are ignored, and
102 * if the pointer is in a menu, the return window from the call will be the
103 * the regular subwindow *under* the menu.
104 *
105 * this matters quite a lot if the pointer is moving around in a menu that overlaps
106 * the track canvas because we will believe that we are within the track canvas
107 * when we are not. therefore, we track enter/leave events for the track canvas
108 * and allow that to override the result of gdk_window_get_pointer().
109 */
110
111 if (!within_track_canvas) {
112 return false;
113 }
114
115 int x, y;
116 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->_track_canvas->get_window();
117
118 if (!canvas_window) {
119 return false;
120 }
121
122 Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
123
124 if (!pointer_window) {
125 return false;
126 }
127
128 if (pointer_window != canvas_window) {
129 in_track_canvas = false;
130 return false;
131 }
132
133 in_track_canvas = true;
134
135 GdkEvent event;
136 event.type = GDK_BUTTON_RELEASE;
137 event.button.x = x;
138 event.button.y = y;
139
140 where = window_event_sample (&event, 0, 0);
141
142 return true;
143 }
144
145 samplepos_t
window_event_sample(GdkEvent const * event,double * pcx,double * pcy) const146 Editor::window_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
147 {
148 ArdourCanvas::Duple d;
149
150 if (!gdk_event_get_coords (event, &d.x, &d.y)) {
151 return 0;
152 }
153
154 /* event coordinates are in window units, so convert to canvas
155 */
156
157 d = _track_canvas->window_to_canvas (d);
158
159 if (pcx) {
160 *pcx = d.x;
161 }
162
163 if (pcy) {
164 *pcy = d.y;
165 }
166
167 return pixel_to_sample (d.x);
168 }
169
170 samplepos_t
canvas_event_sample(GdkEvent const * event,double * pcx,double * pcy) const171 Editor::canvas_event_sample (GdkEvent const * event, double* pcx, double* pcy) const
172 {
173 double x;
174 double y;
175
176 /* event coordinates are already in canvas units */
177
178 if (!gdk_event_get_coords (event, &x, &y)) {
179 cerr << "!NO c COORDS for event type " << event->type << endl;
180 return 0;
181 }
182
183 if (pcx) {
184 *pcx = x;
185 }
186
187 if (pcy) {
188 *pcy = y;
189 }
190
191 /* note that pixel_to_sample_from_event() never returns less than zero, so even if the pixel
192 position is negative (as can be the case with motion events in particular),
193 the sample location is always positive.
194 */
195
196 return pixel_to_sample_from_event (x);
197 }
198
199 void
set_current_trimmable(boost::shared_ptr<Trimmable> t)200 Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
201 {
202 boost::shared_ptr<Trimmable> st = _trimmable.lock();
203
204 if (!st || st == t) {
205 _trimmable = t;
206 }
207 }
208
209 void
set_current_movable(boost::shared_ptr<Movable> m)210 Editor::set_current_movable (boost::shared_ptr<Movable> m)
211 {
212 boost::shared_ptr<Movable> sm = _movable.lock();
213
214 if (!sm || sm != m) {
215 _movable = m;
216 }
217 }
218
219 void
mouse_mode_object_range_toggled()220 Editor::mouse_mode_object_range_toggled()
221 {
222 set_mouse_mode (mouse_mode, true); /* updates set-mouse-mode-range */
223 }
224
225 bool
snap_mode_button_clicked(GdkEventButton * ev)226 Editor::snap_mode_button_clicked (GdkEventButton* ev)
227 {
228 if (ev->button != 3) {
229 cycle_snap_mode();
230 return true;
231 }
232
233 RCOptionEditor* rc_option_editor = ARDOUR_UI::instance()->get_rc_option_editor();
234 if (rc_option_editor) {
235 ARDOUR_UI::instance()->show_tabbable (rc_option_editor);
236 rc_option_editor->set_current_page (_("Editor/Snap"));
237 }
238
239 return true;
240 }
241
242
243
244 Glib::RefPtr<Action>
get_mouse_mode_action(MouseMode m) const245 Editor::get_mouse_mode_action(MouseMode m) const
246 {
247 switch (m) {
248 case MouseRange:
249 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-range"));
250 case MouseObject:
251 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object"));
252 case MouseCut:
253 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-cut"));
254 case MouseDraw:
255 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-draw"));
256 case MouseTimeFX:
257 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-timefx"));
258 case MouseContent:
259 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-content"));
260 case MouseAudition:
261 return ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-audition"));
262 }
263 return Glib::RefPtr<Action>();
264 }
265
266 void
set_mouse_mode(MouseMode m,bool force)267 Editor::set_mouse_mode (MouseMode m, bool force)
268 {
269 if (_drags->active ()) {
270 return;
271 }
272
273 if (!force && m == mouse_mode) {
274 return;
275 }
276
277 if (ARDOUR::Profile->get_mixbus()) {
278 if (m == MouseAudition) {
279 m = MouseRange;
280 }
281 }
282
283 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
284 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
285
286 /* go there and back to ensure that the toggled handler is called to set up mouse_mode */
287 tact->set_active (false);
288 tact->set_active (true);
289
290 /* NOTE: this will result in a call to mouse_mode_toggled which does the heavy lifting */
291 }
292
293 void
mouse_mode_toggled(MouseMode m)294 Editor::mouse_mode_toggled (MouseMode m)
295 {
296 if (ARDOUR::Profile->get_mixbus()) {
297 if (m == MouseAudition) {
298 m = MouseRange;
299 }
300 }
301
302 Glib::RefPtr<Action> act = get_mouse_mode_action(m);
303 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
304
305 if (!tact->get_active()) {
306 /* this was just the notification that the old mode has been
307 * left. we'll get called again with the new mode active in a
308 * jiffy.
309 */
310 return;
311 }
312
313 if (_session && mouse_mode == MouseAudition) {
314 /* stop transport and reset default speed to avoid oddness with
315 auditioning */
316 _session->request_stop ();
317 _session->reset_transport_speed ();
318 }
319
320 const bool was_internal = internal_editing();
321
322 mouse_mode = m;
323
324 /* Switch snap type/mode if we're moving to/from an internal tool. Note
325 this must toggle the actions and not call set_snap_*() directly,
326 otherwise things get out of sync and the combo box stops working. */
327 if (!UIConfiguration::instance().get_grid_follows_internal()) {
328 grid_type_action(pre_internal_grid_type)->set_active(true);
329 snap_mode_action(pre_internal_snap_mode)->set_active(true);
330 } else if (!was_internal && internal_editing()) {
331 grid_type_action(internal_grid_type)->set_active(true);
332 snap_mode_action(internal_snap_mode)->set_active(true);
333 } else if (was_internal && !internal_editing()) {
334 grid_type_action(pre_internal_grid_type)->set_active(true);
335 snap_mode_action(pre_internal_snap_mode)->set_active(true);
336 }
337
338 instant_save ();
339
340 /* this should generate a new enter event which will
341 trigger the appropiate cursor.
342 */
343
344 if (_track_canvas) {
345 _track_canvas->re_enter ();
346 }
347
348 set_gain_envelope_visibility ();
349
350 update_time_selection_display ();
351
352 if (internal_editing()) {
353
354 /* reinstate any existing MIDI note (and by extension, MIDI
355 * region) selection for internal edit mode. This allows a user
356 * to enter/exit/enter this mode without losing a selection of
357 * notes.
358 */
359
360 catch_up_on_midi_selection ();
361
362 /* ensure that the track canvas has focus, so that key events
363 will get directed to the correct place.
364 */
365 _track_canvas->grab_focus ();
366
367 /* enable MIDI editing actions, which in turns enables their
368 bindings
369 */
370 ActionManager::set_sensitive (_midi_actions, true);
371
372 /* mark "magic widget focus" so that we handle key events
373 * correctly
374 */
375 Keyboard::magic_widget_grab_focus ();
376 } else {
377 /* undo some of the above actions, since we're not in internal
378 edit mode.
379 */
380 ActionManager::set_sensitive (_midi_actions, false);
381 Keyboard::magic_widget_drop_focus ();
382 }
383
384 if (was_internal && !internal_editing()) {
385 /* drop any selected regions so that they in turn
386 * redraw any selected notes. This essentially the
387 * opposite of ::catch_up_on_midi_selection() called
388 * above.
389 */
390 get_selection().clear_regions ();
391 }
392
393 update_all_enter_cursors ();
394
395 MouseModeChanged (); /* EMIT SIGNAL */
396 }
397
398 bool
internal_editing() const399 Editor::internal_editing() const
400 {
401 return mouse_mode == Editing::MouseContent || mouse_mode == Editing::MouseDraw;
402 }
403
404 void
update_time_selection_display()405 Editor::update_time_selection_display ()
406 {
407 switch (mouse_mode) {
408 case MouseRange:
409 selection->clear_objects ();
410 selection->clear_midi_notes ();
411 break;
412 case MouseObject:
413 selection->clear_time ();
414 selection->clear_midi_notes ();
415 break;
416 case MouseDraw:
417 /* Clear regions, but not time or tracks, since that
418 would destroy the range selection rectangle, which we need to stick
419 around for AutomationRangeDrag. */
420 selection->clear_regions ();
421 selection->clear_playlists ();
422 break;
423 case MouseContent:
424 /* This handles internal edit.
425 Clear everything except points and notes.
426 */
427 selection->clear_regions();
428 selection->clear_lines();
429 selection->clear_playlists ();
430
431 selection->clear_time ();
432 selection->clear_tracks ();
433 break;
434
435 case MouseTimeFX:
436 /* We probably want to keep region selection */
437 selection->clear_points ();
438 selection->clear_lines();
439 selection->clear_playlists ();
440
441 selection->clear_time ();
442 selection->clear_tracks ();
443 break;
444
445 case MouseAudition:
446 /*Don't lose lines or points if no action in this mode */
447 selection->clear_regions ();
448 selection->clear_playlists ();
449 selection->clear_time ();
450 selection->clear_tracks ();
451 break;
452
453 default:
454 /*Clear everything */
455 selection->clear_objects();
456 selection->clear_time ();
457 selection->clear_tracks ();
458 break;
459 }
460 }
461
462 void
step_mouse_mode(bool next)463 Editor::step_mouse_mode (bool next)
464 {
465 const int n_mouse_modes = (int)MouseContent + 1;
466 int current = (int)current_mouse_mode();
467 if (next) {
468 set_mouse_mode((MouseMode)((current + 1) % n_mouse_modes));
469 } else {
470 set_mouse_mode((MouseMode)((current + n_mouse_modes - 1) % n_mouse_modes));
471 }
472 }
473
474 void
button_selection(ArdourCanvas::Item * item,GdkEvent * event,ItemType item_type)475 Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
476 {
477 /* in object/audition/timefx/gain-automation mode,
478 * any button press sets the selection if the object
479 * can be selected. this is a bit of hack, because
480 * we want to avoid this if the mouse operation is a
481 * region alignment.
482 *
483 * note: not dbl-click or triple-click
484 *
485 * Also note that there is no region selection in internal edit mode, otherwise
486 * for operations operating on the selection (e.g. cut) it is not obvious whether
487 * to cut notes or regions.
488 */
489
490 MouseMode eff_mouse_mode = effective_mouse_mode ();
491
492 if (eff_mouse_mode == MouseCut) {
493 /* never change selection in cut mode */
494 return;
495 }
496
497 if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) {
498 /* context clicks are always about object properties, even if
499 * we're in range mode within smart mode.
500 */
501 eff_mouse_mode = MouseObject;
502 }
503
504 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
505 if (get_smart_mode()) {
506 switch (item_type) {
507 case FadeInHandleItem:
508 case FadeInTrimHandleItem:
509 case FadeOutHandleItem:
510 case FadeOutTrimHandleItem:
511 eff_mouse_mode = MouseObject;
512 break;
513 default:
514 break;
515 }
516 }
517
518 if (((mouse_mode != MouseObject) &&
519 (mouse_mode != MouseAudition || item_type != RegionItem) &&
520 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
521 (mouse_mode != MouseDraw) &&
522 (mouse_mode != MouseContent || item_type == RegionItem)) ||
523 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
524 return;
525 }
526
527 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
528
529 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
530
531 /* almost no selection action on modified button-2 or button-3 events */
532
533 if ((item_type != RegionItem && event->button.button != 2)
534 /* for selection of control points prior to delete (shift-right click) */
535 && !(item_type == ControlPointItem && event->button.button == 3 && event->type == GDK_BUTTON_PRESS)) {
536 return;
537 }
538 }
539 }
540
541 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
542 bool press = (event->type == GDK_BUTTON_PRESS);
543
544 if (press) {
545 _mouse_changed_selection = false;
546 }
547
548 switch (item_type) {
549 case RegionItem:
550 if (eff_mouse_mode == MouseDraw) {
551 break;
552 }
553 if (press) {
554 if (eff_mouse_mode != MouseRange) {
555 _mouse_changed_selection = set_selected_regionview_from_click (press, op);
556 } else {
557 /* don't change the selection unless the
558 * clicked track is not currently selected. if
559 * so, "collapse" the selection to just this track
560 */
561 if (!selection->selected (clicked_axisview)) {
562 set_selected_track_as_side_effect (Selection::Set);
563 }
564 }
565 } else {
566 if (eff_mouse_mode != MouseRange) {
567 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
568 }
569 }
570 break;
571
572 case RegionViewNameHighlight:
573 case RegionViewName:
574 case LeftFrameHandle:
575 case RightFrameHandle:
576 case FadeInHandleItem:
577 case FadeInTrimHandleItem:
578 case FadeInItem:
579 case FadeOutHandleItem:
580 case FadeOutTrimHandleItem:
581 case FadeOutItem:
582 case StartCrossFadeItem:
583 case EndCrossFadeItem:
584 if (get_smart_mode() || eff_mouse_mode != MouseRange) {
585 _mouse_changed_selection |= set_selected_regionview_from_click (press, op);
586 } else if (event->type == GDK_BUTTON_PRESS) {
587 set_selected_track_as_side_effect (op);
588 }
589 break;
590
591 case ControlPointItem:
592 /* for object/track exclusivity, we don't call set_selected_track_as_side_effect (op); */
593
594 if (eff_mouse_mode != MouseRange) {
595 if (event->button.button != 3) {
596 _mouse_changed_selection |= set_selected_control_point_from_click (press, op);
597 } else {
598 _mouse_changed_selection |= set_selected_control_point_from_click (press, Selection::Set);
599 }
600 }
601 break;
602
603 case GainLineItem:
604 if (eff_mouse_mode != MouseRange) {
605 AutomationLine* argl = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
606
607 std::list<Selectable*> selectables;
608 uint32_t before, after;
609 samplecnt_t const where = (samplecnt_t) floor (event->button.x * samples_per_pixel) - clicked_regionview->region ()->position ();
610
611 if (!argl || !argl->control_points_adjacent (where, before, after)) {
612 break;
613 }
614
615 selectables.push_back (argl->nth (before));
616 selectables.push_back (argl->nth (after));
617
618 switch (op) {
619 case Selection::Set:
620 if (press) {
621 selection->set (selectables);
622 _mouse_changed_selection = true;
623 }
624 break;
625 case Selection::Add:
626 if (press) {
627 selection->add (selectables);
628 _mouse_changed_selection = true;
629 }
630 break;
631 case Selection::Toggle:
632 if (press) {
633 selection->toggle (selectables);
634 _mouse_changed_selection = true;
635 }
636 break;
637
638 case Selection::Extend:
639 /* XXX */
640 break;
641 }
642 }
643 break;
644
645 case AutomationLineItem:
646 if (eff_mouse_mode != MouseRange) {
647 AutomationLine* al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
648 std::list<Selectable*> selectables;
649 double mx = event->button.x;
650 double my = event->button.y;
651
652 al->grab_item().canvas_to_item (mx, my);
653
654 uint32_t before, after;
655 samplecnt_t const where = (samplecnt_t) floor (mx * samples_per_pixel);
656
657 if (!al || !al->control_points_adjacent (where, before, after)) {
658 break;
659 }
660
661 selectables.push_back (al->nth (before));
662 selectables.push_back (al->nth (after));
663
664 switch (op) {
665 case Selection::Set:
666 if (press) {
667 selection->set (selectables);
668 _mouse_changed_selection = true;
669 }
670 break;
671 case Selection::Add:
672 if (press) {
673 selection->add (selectables);
674 _mouse_changed_selection = true;
675 }
676 break;
677 case Selection::Toggle:
678 if (press) {
679 selection->toggle (selectables);
680 _mouse_changed_selection = true;
681 }
682 break;
683
684 case Selection::Extend:
685 /* XXX */
686 break;
687 }
688 }
689 break;
690
691 case StreamItem:
692 /* for context click, select track */
693 if (event->button.button == 3) {
694 selection->clear_tracks ();
695 set_selected_track_as_side_effect (op);
696
697 /* We won't get a release.*/
698 begin_reversible_selection_op (X_("Button 3 Menu Select"));
699 commit_reversible_selection_op ();
700 }
701 break;
702
703 case AutomationTrackItem:
704 if (eff_mouse_mode != MouseDraw && op == Selection::Set) {
705 set_selected_track_as_side_effect (op);
706 }
707 break;
708
709 case NoteItem:
710 if (press && event->button.button == 3) {
711 NoteBase* cnote = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
712 assert (cnote);
713 if (cnote->region_view().selection_size() == 0 || !cnote->selected()) {
714 selection->clear_points();
715 cnote->region_view().unique_select (cnote);
716 /* we won't get the release, so store the selection change now */
717 begin_reversible_selection_op (X_("Button 3 Note Selection"));
718 commit_reversible_selection_op ();
719 }
720 }
721 break;
722
723 default:
724 break;
725 }
726
727 if ((!press) && _mouse_changed_selection) {
728 begin_reversible_selection_op (X_("Button Selection"));
729 commit_reversible_selection_op ();
730 _mouse_changed_selection = false;
731 }
732 }
733
734 bool
button_press_handler_1(ArdourCanvas::Item * item,GdkEvent * event,ItemType item_type)735 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
736 {
737 /* single mouse clicks on any of these item types operate
738 * independent of mouse mode, mostly because they are
739 * not on the main track canvas or because we want
740 * them to be modeless.
741 */
742
743 NoteBase* note = NULL;
744
745 switch (item_type) {
746 case PlayheadCursorItem:
747 _drags->set (new CursorDrag (this, *_playhead_cursor, true), event);
748 return true;
749
750 case MarkerItem:
751 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
752 hide_marker (item, event);
753 } else {
754 ArdourMarker* marker = static_cast<ArdourMarker*> (item->get_data ("marker"));
755 if (marker->type() == ArdourMarker::RegionCue) {
756 _drags->set (new RegionMarkerDrag (this, marker->region_view(), item), event);
757 } else {
758 _drags->set (new MarkerDrag (this, item), event);
759 }
760 }
761 return true;
762
763 case TempoMarkerItem:
764 {
765 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
766 _drags->set (
767 new TempoEndDrag (
768 this,
769 item
770 ),
771 event
772 );
773 } else {
774 _drags->set (
775 new TempoMarkerDrag (
776 this,
777 item,
778 ArdourKeyboard::indicates_copy (event->button.state)
779 ),
780 event
781 );
782 }
783
784 return true;
785 }
786
787 case MeterMarkerItem:
788 {
789 _drags->set (
790 new MeterMarkerDrag (
791 this,
792 item,
793 ArdourKeyboard::indicates_copy (event->button.state)
794 ),
795 event
796 );
797 return true;
798 }
799
800 case VideoBarItem:
801 _drags->set (new VideoTimeLineDrag (this, item), event);
802 return true;
803 break;
804
805 case MarkerBarItem:
806 case TempoBarItem:
807 case TempoCurveItem:
808 case MeterBarItem:
809 case TimecodeRulerItem:
810 case SamplesRulerItem:
811 case MinsecRulerItem:
812 case BBTRulerItem:
813 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)
814 && !ArdourKeyboard::indicates_constraint (event->button.state)) {
815 _drags->set (new CursorDrag (this, *_playhead_cursor, false), event);
816 } else if (ArdourKeyboard::indicates_constraint (event->button.state)
817 && Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
818 _drags->set (new TempoTwistDrag (this, item), event);
819 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
820 _drags->set (new BBTRulerDrag (this, item), event);
821 }
822 return true;
823 break;
824
825
826 case RangeMarkerBarItem:
827 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
828 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateSkipMarker), event);
829 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
830 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
831 } else {
832 _drags->set (new CursorDrag (this, *_playhead_cursor, false), event);
833 }
834 return true;
835 break;
836
837 case CdMarkerBarItem:
838 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
839 _drags->set (new CursorDrag (this, *_playhead_cursor, false), event);
840 } else {
841 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
842 }
843 return true;
844 break;
845
846 case TransportMarkerBarItem:
847 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
848 _drags->set (new CursorDrag (this, *_playhead_cursor, false), event);
849 } else {
850 _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
851 }
852 return true;
853 break;
854
855 default:
856 break;
857 }
858
859 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
860 /* special case: allow trim of range selections in joined object mode;
861 * in theory eff should equal MouseRange in this case, but it doesn't
862 * because entering the range selection canvas item results in entered_regionview
863 * being set to 0, so update_join_object_range_location acts as if we aren't
864 * over a region.
865 */
866 if (item_type == StartSelectionTrimItem) {
867 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
868 } else if (item_type == EndSelectionTrimItem) {
869 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
870 }
871 }
872
873 Editing::MouseMode eff = effective_mouse_mode ();
874
875 /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
876 if (get_smart_mode()) {
877 switch (item_type) {
878 case FadeInHandleItem:
879 case FadeInTrimHandleItem:
880 case FadeOutHandleItem:
881 case FadeOutTrimHandleItem:
882 eff = MouseObject;
883 break;
884 default:
885 break;
886 }
887 }
888
889 switch (eff) {
890 case MouseRange:
891 switch (item_type) {
892 case StartSelectionTrimItem:
893 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
894 break;
895
896 case EndSelectionTrimItem:
897 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
898 break;
899
900 case SelectionItem:
901 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
902 start_selection_grab (item, event);
903 return true;
904 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
905 /* grab selection for moving */
906 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
907 } else {
908 /* this was debated, but decided the more common action was to make a new selection */
909 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
910 }
911 break;
912
913 case StreamItem:
914 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
915 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
916 } else {
917 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
918 }
919 return true;
920 break;
921
922 case RegionViewNameHighlight:
923 if (!clicked_regionview->region()->locked()) {
924 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
925 return true;
926 }
927 break;
928
929 default:
930 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::RangeSelectModifier)) {
931 _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionExtend), event);
932 } else {
933 _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
934 }
935 }
936 return true;
937 break;
938
939 case MouseCut:
940 switch (item_type) {
941 case RegionItem:
942 case FadeInHandleItem:
943 case FadeOutHandleItem:
944 case LeftFrameHandle:
945 case RightFrameHandle:
946 case FeatureLineItem:
947 case RegionViewNameHighlight:
948 case RegionViewName:
949 case StreamItem:
950 case AutomationTrackItem:
951 _drags->set (new RegionCutDrag (this, item, canvas_event_sample (event)), event, get_canvas_cursor());
952 return true;
953 break;
954 default:
955 break;
956 }
957 break;
958
959 case MouseContent:
960 switch (item_type) {
961 case NoteItem:
962 /* Existing note: allow trimming/motion */
963 if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
964 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
965 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
966 } else {
967 _drags->set (new NoteDrag (this, item), event);
968 }
969 }
970 return true;
971
972 case GainLineItem:
973 _drags->set (new LineDrag (this, item), event);
974 return true;
975 break;
976
977 case ControlPointItem:
978 _drags->set (new ControlPointDrag (this, item), event);
979 return true;
980 break;
981
982 case AutomationLineItem:
983 _drags->set (new LineDrag (this, item), event);
984 return true;
985 break;
986
987 case StreamItem:
988 /* in the past, we created a new midi region here, but perhaps that is best left to the Draw mode */
989 break;
990
991 case AutomationTrackItem:
992 /* rubberband drag to select automation points */
993 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
994 return true;
995 break;
996
997 case RegionItem:
998 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
999 /* rubberband drag to select automation points */
1000 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1001 return true;
1002 }
1003 break;
1004
1005 default:
1006 break;
1007 }
1008 break;
1009
1010 case MouseObject:
1011 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
1012 event->type == GDK_BUTTON_PRESS) {
1013
1014 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1015
1016 } else if (event->type == GDK_BUTTON_PRESS) {
1017
1018 switch (item_type) {
1019 case FadeInHandleItem:
1020 {
1021 _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_in);
1022 return true;
1023 }
1024
1025 case FadeOutHandleItem:
1026 {
1027 _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), selection->regions), event, _cursors->fade_out);
1028 return true;
1029 }
1030
1031 case StartCrossFadeItem:
1032 case EndCrossFadeItem:
1033 /* we might allow user to grab inside the fade to trim a region with preserve_fade_anchor.
1034 * For not this is not fully implemented */
1035 #if 0
1036 if (!clicked_regionview->region()->locked()) {
1037 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
1038 return true;
1039 }
1040 #endif
1041 break;
1042
1043 case FeatureLineItem:
1044 {
1045 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
1046 remove_transient(item);
1047 return true;
1048 }
1049
1050 _drags->set (new FeatureLineDrag (this, item), event);
1051 return true;
1052 break;
1053 }
1054
1055 case RegionItem:
1056 if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
1057 /* click on an automation region view; do nothing here and let the ARV's signal handler
1058 sort it out.
1059 */
1060 break;
1061 }
1062
1063 /* click on a normal region view */
1064 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::slip_contents_modifier ())) {
1065 if (!clicked_regionview->region()->locked() && (Config->get_edit_mode() != Lock)) {
1066 _drags->add (new RegionSlipContentsDrag (this, item, clicked_regionview, selection->regions.by_layer()));
1067 }
1068 } else if (ArdourKeyboard::indicates_copy (event->button.state)) {
1069 add_region_copy_drag (item, event, clicked_regionview);
1070 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
1071 add_region_brush_drag (item, event, clicked_regionview);
1072 } else {
1073 add_region_drag (item, event, clicked_regionview);
1074 }
1075
1076
1077 _drags->start_grab (event);
1078 return true;
1079 break;
1080
1081 case RegionViewNameHighlight:
1082 case LeftFrameHandle:
1083 case RightFrameHandle:
1084 if (!clicked_regionview->region()->locked()) {
1085 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1086 return true;
1087 }
1088 break;
1089
1090 case FadeInTrimHandleItem:
1091 case FadeOutTrimHandleItem:
1092 if (!clicked_regionview->region()->locked()) {
1093 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer(), true), event);
1094 return true;
1095 }
1096 break;
1097
1098 case RegionViewName:
1099 {
1100 /* rename happens on edit clicks */
1101 if (clicked_regionview->get_name_highlight()) {
1102 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1103 return true;
1104 }
1105 break;
1106 }
1107
1108 case ControlPointItem:
1109 _drags->set (new ControlPointDrag (this, item), event);
1110 return true;
1111 break;
1112
1113 case AutomationLineItem:
1114 _drags->set (new LineDrag (this, item), event);
1115 return true;
1116 break;
1117
1118 case StreamItem:
1119 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1120 return true;
1121
1122 case AutomationTrackItem:
1123 {
1124 TimeAxisView* parent = clicked_axisview->get_parent ();
1125 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (clicked_axisview);
1126 assert (atv);
1127 if (parent && dynamic_cast<MidiTimeAxisView*> (parent) && atv->show_regions ()) {
1128
1129 RouteTimeAxisView* p = dynamic_cast<RouteTimeAxisView*> (parent);
1130 assert (p);
1131 boost::shared_ptr<Playlist> pl = p->track()->playlist ();
1132 if (pl->n_regions() == 0) {
1133 /* Parent has no regions; create one so that we have somewhere to put automation */
1134 _drags->set (new RegionCreateDrag (this, item, parent), event);
1135 } else {
1136 /* See if there's a region before the click that we can extend, and extend it if so */
1137 samplepos_t const t = canvas_event_sample (event);
1138 boost::shared_ptr<Region> prev = pl->find_next_region (t, End, -1);
1139 if (!prev) {
1140 _drags->set (new RegionCreateDrag (this, item, parent), event);
1141 } else {
1142 prev->set_length (t - prev->position (), get_grid_music_divisions (event->button.state));
1143 }
1144 }
1145 } else {
1146 /* rubberband drag to select automation points */
1147 _drags->set (new EditorRubberbandSelectDrag (this, item), event);
1148 }
1149 break;
1150 }
1151
1152 case SelectionItem:
1153 {
1154 break;
1155 }
1156
1157 case MarkerBarItem:
1158
1159 break;
1160
1161 default:
1162 break;
1163 }
1164 }
1165 return true;
1166 break;
1167
1168 case MouseDraw:
1169 switch (item_type) {
1170 case GainLineItem:
1171 _drags->set (new LineDrag (this, item), event);
1172 return true;
1173
1174 case ControlPointItem:
1175 _drags->set (new ControlPointDrag (this, item), event);
1176 return true;
1177 break;
1178
1179 case SelectionItem:
1180 {
1181 if (selection->time.empty ()) {
1182 /* nothing to do */
1183 return true;
1184 }
1185 pair<TimeAxisView*, int> tvp = trackview_by_y_position (event->button.y, false);
1186 if (!tvp.first) {
1187 /* clicked outside of a track */
1188 return true;
1189 }
1190 /* handle automation lanes first */
1191 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
1192 if (atv) {
1193 /* smart "join" mode: drag automation */
1194 _drags->set (new AutomationRangeDrag (this, atv, selection->time), event, _cursors->up_down);
1195 return true;
1196 }
1197 if (dynamic_cast<AutomationRegionView*>(clicked_regionview)) {
1198 /* MIDI CC or similar -- TODO handle multiple? */
1199 list<RegionView*> rvl;
1200 rvl.push_back (clicked_regionview);
1201 _drags->set (new AutomationRangeDrag (this, rvl, selection->time,
1202 clicked_regionview->get_time_axis_view().y_position(),
1203 clicked_regionview->get_time_axis_view().current_height()),
1204 event, _cursors->up_down);
1205 return true;
1206 }
1207
1208 /* shift+drag: only apply to clicked_regionview (if any) */
1209 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
1210 if (dynamic_cast<AudioRegionView*>(clicked_regionview) == 0) {
1211 return true;
1212 }
1213 list<RegionView*> rvl;
1214 rvl.push_back (clicked_regionview);
1215 // TODO: handle layer_display() == Stacked
1216 _drags->set (new AutomationRangeDrag (this, rvl, selection->time,
1217 clicked_regionview->get_time_axis_view().y_position(),
1218 clicked_regionview->get_time_axis_view().current_height()),
1219 event, _cursors->up_down);
1220 return true;
1221 }
1222
1223 /* collect all audio regions-views in the given range selection */
1224 list<RegionView*> rvl;
1225 TrackViewList ts = selection->tracks.filter_to_unique_playlists ();
1226 for (TrackViewList::iterator i = ts.begin(); i != ts.end(); ++i) {
1227 RouteTimeAxisView* tatv;
1228 boost::shared_ptr<Playlist> playlist;
1229 if ((tatv = dynamic_cast<RouteTimeAxisView*> (*i)) == 0) {
1230 continue;
1231 }
1232 if ((playlist = (*i)->playlist()) == 0) {
1233 continue;
1234 }
1235 if (boost::dynamic_pointer_cast<AudioPlaylist> (playlist) == 0) {
1236 continue;
1237 }
1238 for (list<AudioRange>::const_iterator j = selection->time.begin(); j != selection->time.end(); ++j) {
1239 boost::shared_ptr<RegionList> rl = playlist->regions_touched (j->start, j->end);
1240 for (RegionList::iterator ir = rl->begin(); ir != rl->end(); ++ir) {
1241 RegionView* rv;
1242 if ((rv = tatv->view()->find_view (*ir)) != 0) {
1243 rvl.push_back (rv);
1244 }
1245 }
1246 }
1247 }
1248 /* region-gain drag */
1249 if (!rvl.empty ()) {
1250 double y_pos = tvp.first->y_position();
1251 double height = tvp.first->current_height();
1252 StreamView* cv = tvp.first->view ();
1253 if (cv->layer_display() == Stacked && cv->layers() > 1) {
1254 height /= cv->layers();
1255 double yy = event->button.y - _trackview_group->canvas_origin().y;
1256 y_pos += floor ((yy - y_pos) / height) * height;
1257 }
1258 _drags->set (new AutomationRangeDrag (this, rvl, selection->time, y_pos, height),
1259 event, _cursors->up_down);
1260 }
1261 return true;
1262 break;
1263 }
1264
1265 case AutomationLineItem:
1266 _drags->set (new LineDrag (this, item), event);
1267 break;
1268
1269 case NoteItem:
1270 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1271 if (note->big_enough_to_trim() && note->mouse_near_ends()) {
1272 /* Note is big and pointer is near the end, trim */
1273 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1274 } else {
1275 /* Drag note */
1276 _drags->set (new NoteDrag (this, item), event);
1277 }
1278 return true;
1279 }
1280 return true;
1281
1282 case StreamItem:
1283 if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
1284 _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
1285 }
1286 return true;
1287
1288 default:
1289 break;
1290 }
1291 return true;
1292 break;
1293
1294 case MouseTimeFX:
1295 if (item_type == NoteItem) {
1296 /* resize-drag notes */
1297 if ((note = reinterpret_cast<NoteBase*>(item->get_data ("notebase")))) {
1298 if (note->big_enough_to_trim()) {
1299 _drags->set (new NoteResizeDrag (this, item), event, get_canvas_cursor());
1300 }
1301 }
1302 return true;
1303 } else if (clicked_regionview) {
1304 /* do time-FX */
1305 _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1306 return true;
1307 }
1308 break;
1309
1310 case MouseAudition:
1311 _drags->set (new ScrubDrag (this, item), event, _cursors->transparent);
1312 scrub_reversals = 0;
1313 scrub_reverse_distance = 0;
1314 last_scrub_x = event->button.x;
1315 scrubbing_direction = 0;
1316 return true;
1317 break;
1318
1319 default:
1320 break;
1321 }
1322
1323 return false;
1324 }
1325
1326 bool
button_press_handler_2(ArdourCanvas::Item * item,GdkEvent * event,ItemType item_type)1327 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1328 {
1329 Editing::MouseMode const eff = effective_mouse_mode ();
1330 switch (eff) {
1331 case MouseObject:
1332 switch (item_type) {
1333 case RegionItem:
1334 if (ArdourKeyboard::indicates_copy (event->button.state)) {
1335 add_region_copy_drag (item, event, clicked_regionview);
1336 } else {
1337 add_region_drag (item, event, clicked_regionview);
1338 }
1339 _drags->start_grab (event);
1340 return true;
1341 break;
1342 case ControlPointItem:
1343 _drags->set (new ControlPointDrag (this, item), event);
1344 return true;
1345 break;
1346
1347 default:
1348 break;
1349 }
1350
1351 switch (item_type) {
1352 case RegionViewNameHighlight:
1353 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1354 return true;
1355 break;
1356
1357 case LeftFrameHandle:
1358 case RightFrameHandle:
1359 _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
1360 return true;
1361 break;
1362
1363 case RegionViewName:
1364 _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
1365 return true;
1366 break;
1367
1368 default:
1369 break;
1370 }
1371
1372 break;
1373
1374 case MouseDraw:
1375 return false;
1376
1377 case MouseRange:
1378 /* relax till release */
1379 return true;
1380 break;
1381
1382 default:
1383 break;
1384 }
1385
1386 return false;
1387 }
1388
1389 bool
button_press_handler(ArdourCanvas::Item * item,GdkEvent * event,ItemType item_type)1390 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1391 {
1392 if (event->type == GDK_2BUTTON_PRESS) {
1393 _drags->mark_double_click ();
1394 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1395 return true;
1396 }
1397
1398 if (event->type != GDK_BUTTON_PRESS) {
1399 return false;
1400 }
1401
1402 _track_canvas->grab_focus();
1403
1404 if (_session && _session->actively_recording()) {
1405 return true;
1406 }
1407
1408 button_selection (item, event, item_type);
1409
1410 if (!_drags->active () &&
1411 (Keyboard::is_delete_event (&event->button) ||
1412 Keyboard::is_context_menu_event (&event->button) ||
1413 Keyboard::is_edit_event (&event->button))) {
1414
1415 /* handled by button release */
1416 return true;
1417 }
1418
1419 /* not rolling, effectively in range mode, follow edits enabled (likely
1420 * to start range drag), not in a fade handle (since that means we are
1421 * not starting a range drag): locate the PH here
1422 */
1423
1424 if ((item_type != FadeInHandleItem) &&
1425 (item_type != FadeOutHandleItem) &&
1426 !_drags->active () &&
1427 _session &&
1428 !_session->transport_rolling() &&
1429 (effective_mouse_mode() == MouseRange) &&
1430 UIConfiguration::instance().get_follow_edits() &&
1431 !_session->config.get_external_sync()) {
1432
1433 MusicSample where (canvas_event_sample (event), 0);
1434 snap_to (where);
1435 _session->request_locate (where.sample, MustStop);
1436 }
1437
1438 switch (event->button.button) {
1439 case 1:
1440 return button_press_handler_1 (item, event, item_type);
1441 break;
1442
1443 case 2:
1444 return button_press_handler_2 (item, event, item_type);
1445 break;
1446
1447 case 3:
1448 break;
1449
1450 default:
1451 return button_press_dispatch (&event->button);
1452 break;
1453
1454 }
1455
1456 return false;
1457 }
1458
1459 bool
button_press_dispatch(GdkEventButton * ev)1460 Editor::button_press_dispatch (GdkEventButton* ev)
1461 {
1462 /* this function is intended only for buttons 4 and above. */
1463
1464 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1465 return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
1466 }
1467
1468 bool
button_release_dispatch(GdkEventButton * ev)1469 Editor::button_release_dispatch (GdkEventButton* ev)
1470 {
1471 /* this function is intended only for buttons 4 and above. */
1472
1473 Gtkmm2ext::MouseButton b (ev->state, ev->button);
1474 return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
1475 }
1476
1477 bool
button_release_handler(ArdourCanvas::Item * item,GdkEvent * event,ItemType item_type)1478 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1479 {
1480 MusicSample where (canvas_event_sample (event), 0);
1481 AutomationTimeAxisView* atv = 0;
1482
1483 _press_cursor_ctx.reset();
1484
1485 /* no action if we're recording */
1486
1487 if (_session && _session->actively_recording()) {
1488 return true;
1489 }
1490
1491 bool were_dragging = false;
1492
1493 if (!Keyboard::is_context_menu_event (&event->button)) {
1494
1495 /* see if we're finishing a drag */
1496
1497 if (_drags->active ()) {
1498 bool const r = _drags->end_grab (event);
1499 if (r) {
1500 /* grab dragged, so do nothing else */
1501 return true;
1502 }
1503
1504 were_dragging = true;
1505 }
1506
1507 update_region_layering_order_editor ();
1508 }
1509
1510 /* edit events get handled here */
1511
1512 if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
1513 switch (item_type) {
1514 case RegionItem:
1515 show_region_properties ();
1516 break;
1517 case TempoMarkerItem: {
1518 ArdourMarker* marker;
1519 TempoMarker* tempo_marker;
1520
1521 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1522 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1523 abort(); /*NOTREACHED*/
1524 }
1525
1526 if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
1527 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
1528 abort(); /*NOTREACHED*/
1529 }
1530
1531 edit_tempo_marker (*tempo_marker);
1532 break;
1533 }
1534
1535 case MeterMarkerItem: {
1536 ArdourMarker* marker;
1537 MeterMarker* meter_marker;
1538
1539 if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1540 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
1541 abort(); /*NOTREACHED*/
1542 }
1543
1544 if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
1545 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
1546 abort(); /*NOTREACHED*/
1547 }
1548 edit_meter_marker (*meter_marker);
1549 break;
1550 }
1551
1552 case RegionViewName:
1553 if (clicked_regionview->name_active()) {
1554 return mouse_rename_region (item, event);
1555 }
1556 break;
1557
1558 case ControlPointItem:
1559 edit_control_point (item);
1560 break;
1561
1562 default:
1563 break;
1564 }
1565 return true;
1566 }
1567
1568 /* context menu events get handled here */
1569 if (Keyboard::is_context_menu_event (&event->button)) {
1570
1571 context_click_event = *event;
1572
1573 if (!_drags->active ()) {
1574
1575 /* no matter which button pops up the context menu, tell the menu
1576 widget to use button 1 to drive menu selection.
1577 */
1578
1579 switch (item_type) {
1580 case FadeInItem:
1581 case FadeInHandleItem:
1582 case FadeInTrimHandleItem:
1583 case StartCrossFadeItem:
1584 popup_xfade_in_context_menu (1, event->button.time, item, item_type);
1585 break;
1586
1587 case FadeOutItem:
1588 case FadeOutHandleItem:
1589 case FadeOutTrimHandleItem:
1590 case EndCrossFadeItem:
1591 popup_xfade_out_context_menu (1, event->button.time, item, item_type);
1592 break;
1593
1594 case LeftFrameHandle:
1595 case RightFrameHandle:
1596 break;
1597
1598 case StreamItem:
1599 popup_track_context_menu (1, event->button.time, item_type, false);
1600 break;
1601
1602 case RegionItem:
1603 case RegionViewNameHighlight:
1604 case RegionViewName:
1605 popup_track_context_menu (1, event->button.time, item_type, false);
1606 break;
1607
1608 case SelectionItem:
1609 popup_track_context_menu (1, event->button.time, item_type, true);
1610 break;
1611
1612 case AutomationTrackItem:
1613 popup_track_context_menu (1, event->button.time, item_type, false);
1614 break;
1615
1616 case MarkerBarItem:
1617 case RangeMarkerBarItem:
1618 case TransportMarkerBarItem:
1619 case CdMarkerBarItem:
1620 case TempoBarItem:
1621 case TempoCurveItem:
1622 case MeterBarItem:
1623 case VideoBarItem:
1624 case TimecodeRulerItem:
1625 case SamplesRulerItem:
1626 case MinsecRulerItem:
1627 case BBTRulerItem:
1628 popup_ruler_menu (where.sample, item_type);
1629 break;
1630
1631 case MarkerItem:
1632 marker_context_menu (&event->button, item);
1633 break;
1634
1635 case TempoMarkerItem:
1636 tempo_or_meter_marker_context_menu (&event->button, item);
1637 break;
1638
1639 case MeterMarkerItem:
1640 tempo_or_meter_marker_context_menu (&event->button, item);
1641 break;
1642
1643 case CrossfadeViewItem:
1644 popup_track_context_menu (1, event->button.time, item_type, false);
1645 break;
1646
1647 case ControlPointItem:
1648 popup_control_point_context_menu (item, event);
1649 break;
1650
1651 case NoteItem:
1652 if (internal_editing()) {
1653 popup_note_context_menu (item, event);
1654 }
1655 break;
1656
1657 default:
1658 break;
1659 }
1660
1661 return true;
1662 }
1663 }
1664
1665 /* delete events get handled here */
1666
1667 Editing::MouseMode const eff = effective_mouse_mode ();
1668
1669 if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
1670
1671 switch (item_type) {
1672 case TempoMarkerItem:
1673 remove_tempo_marker (item);
1674 break;
1675
1676 case MeterMarkerItem:
1677 remove_meter_marker (item);
1678 break;
1679
1680 case MarkerItem:
1681 remove_marker (*item);
1682 break;
1683
1684 case RegionItem:
1685 if (eff == MouseObject) {
1686 remove_clicked_region ();
1687 }
1688 break;
1689
1690 case ControlPointItem:
1691 remove_control_point (item);
1692 break;
1693
1694 case NoteItem:
1695 remove_midi_note (item, event);
1696 break;
1697
1698 default:
1699 break;
1700 }
1701 return true;
1702 }
1703
1704 switch (event->button.button) {
1705 case 1:
1706
1707 switch (item_type) {
1708 /* see comments in button_press_handler */
1709 case PlayheadCursorItem:
1710 case MarkerItem:
1711 case GainLineItem:
1712 case AutomationLineItem:
1713 case StartSelectionTrimItem:
1714 case EndSelectionTrimItem:
1715 return true;
1716
1717 case MarkerBarItem:
1718 if (!_dragging_playhead) {
1719 snap_to_with_modifier (where, event, RoundNearest, SnapToGrid_Scaled);
1720 mouse_add_new_marker (where.sample);
1721 }
1722 return true;
1723
1724 case CdMarkerBarItem:
1725 if (!_dragging_playhead) {
1726 /* if we get here then a dragged range wasn't done */
1727 snap_to_with_modifier (where, event, RoundNearest, SnapToGrid_Scaled);
1728 mouse_add_new_marker (where.sample, true);
1729 }
1730 return true;
1731 case TempoBarItem:
1732 case TempoCurveItem:
1733 if (!_dragging_playhead && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1734 snap_to_with_modifier (where, event);
1735 mouse_add_new_tempo_event (where.sample);
1736 }
1737 return true;
1738
1739 case MeterBarItem:
1740 if (!_dragging_playhead && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1741 mouse_add_new_meter_event (pixel_to_sample (event->button.x));
1742 }
1743 return true;
1744 break;
1745
1746 case TimecodeRulerItem:
1747 case SamplesRulerItem:
1748 case MinsecRulerItem:
1749 case BBTRulerItem:
1750 return true;
1751 break;
1752
1753 default:
1754 break;
1755 }
1756
1757 switch (eff) {
1758 case MouseDraw:
1759 switch (item_type) {
1760 case RegionItem:
1761 {
1762 /* check that we didn't drag before releasing, since
1763 its really annoying to create new control
1764 points when doing this.
1765 */
1766 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
1767 if (!were_dragging && arv) {
1768 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1769 arv->add_gain_point_event (item, event, with_guard_points);
1770 }
1771 return true;
1772 break;
1773 }
1774
1775 case AutomationTrackItem: {
1776 bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
1777 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
1778 if (atv) {
1779 atv->add_automation_event (event, where.sample, event->button.y, with_guard_points);
1780 }
1781 return true;
1782 break;
1783 }
1784 default:
1785 break;
1786 }
1787 break;
1788
1789 case MouseAudition:
1790 if (scrubbing_direction == 0) {
1791 /* no drag, just a click */
1792 switch (item_type) {
1793 case RegionItem:
1794 play_selected_region ();
1795 break;
1796 default:
1797 break;
1798 }
1799 } else if (_session) {
1800 /* make sure we stop */
1801 _session->request_stop ();
1802 }
1803 break;
1804
1805 default:
1806 break;
1807
1808 }
1809
1810 /* do any (de)selection operations that should occur on button release */
1811 button_selection (item, event, item_type);
1812
1813 return true;
1814 break;
1815
1816
1817 case 2:
1818 switch (eff) {
1819
1820 case MouseObject:
1821 switch (item_type) {
1822 case RegionItem:
1823 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1824 raise_region ();
1825 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1826 lower_region ();
1827 } else {
1828 /* Button2 click is unused */
1829 }
1830 return true;
1831
1832 break;
1833
1834 default:
1835 break;
1836 }
1837 break;
1838
1839 case MouseDraw:
1840 return true;
1841
1842 case MouseRange:
1843 // x_style_paste (where, 1.0);
1844 return true;
1845 break;
1846
1847 default:
1848 break;
1849 }
1850
1851 break;
1852
1853 case 3:
1854 break;
1855
1856 default:
1857 break;
1858 }
1859
1860 return false;
1861 }
1862
1863 bool
enter_handler(ArdourCanvas::Item * item,GdkEvent * event,ItemType item_type)1864 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1865 {
1866 ControlPoint* cp;
1867 ArdourMarker * marker;
1868 MeterMarker* m_marker = 0;
1869 TempoMarker* t_marker = 0;
1870 double fraction;
1871 bool ret = true;
1872
1873 /* by the time we reach here, entered_regionview and entered trackview
1874 * will have already been set as appropriate. Things are done this
1875 * way because this method isn't passed a pointer to a variable type of
1876 * thing that is entered (which may or may not be canvas item).
1877 * (e.g. the actual entered regionview)
1878 */
1879
1880 choose_canvas_cursor_on_entry (item_type);
1881
1882 switch (item_type) {
1883 case ControlPointItem:
1884 if (mouse_mode == MouseDraw || mouse_mode == MouseObject || mouse_mode == MouseContent) {
1885 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1886 cp->show ();
1887
1888 fraction = 1.0 - (cp->get_y() / cp->line().height());
1889
1890 _verbose_cursor->set (cp->line().get_verbose_cursor_string (fraction));
1891 _verbose_cursor->show ();
1892 }
1893 break;
1894
1895 case GainLineItem:
1896 if (mouse_mode == MouseDraw) {
1897 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1898 if (line) {
1899 line->set_outline_color (UIConfiguration::instance().color ("entered gain line"));
1900 }
1901 }
1902 break;
1903
1904 case AutomationLineItem:
1905 if (mouse_mode == MouseDraw || mouse_mode == MouseObject) {
1906 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1907 if (line) {
1908 line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
1909 }
1910 }
1911 break;
1912
1913 case AutomationTrackItem:
1914 AutomationTimeAxisView* atv;
1915 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1916 clear_entered_track = false;
1917 set_entered_track (atv);
1918 }
1919 break;
1920
1921 case MarkerItem:
1922 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
1923 break;
1924 }
1925 entered_marker = marker;
1926 marker->set_entered (true);
1927 break;
1928
1929 case MeterMarkerItem:
1930 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
1931 break;
1932 }
1933 entered_marker = m_marker;
1934 if (m_marker->meter().position_lock_style() == MusicTime) {
1935 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
1936 } else {
1937 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
1938 }
1939 break;
1940
1941 case TempoMarkerItem:
1942 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
1943 break;
1944 }
1945 entered_marker = t_marker;
1946 if (t_marker->tempo().position_lock_style() == MusicTime) {
1947 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
1948 } else {
1949 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
1950 }
1951 break;
1952
1953 case FadeInHandleItem:
1954 case FadeInTrimHandleItem:
1955 if (mouse_mode == MouseObject) {
1956 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1957 if (rect) {
1958 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1959 rect->set_fill_color (rv->get_fill_color());
1960 }
1961 }
1962 break;
1963
1964 case FadeOutHandleItem:
1965 case FadeOutTrimHandleItem:
1966 if (mouse_mode == MouseObject) {
1967 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
1968 if (rect) {
1969 RegionView* rv = static_cast<RegionView*>(item->get_data ("regionview"));
1970 rect->set_fill_color (rv->get_fill_color ());
1971 }
1972 }
1973 break;
1974
1975 case FeatureLineItem:
1976 {
1977 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1978 line->set_outline_color (0xFF0000FF);
1979 }
1980 break;
1981
1982 case SelectionItem:
1983 break;
1984
1985 case WaveItem:
1986 {
1987 if (entered_regionview) {
1988 entered_regionview->entered();
1989 }
1990 }
1991 break;
1992
1993 default:
1994 break;
1995 }
1996
1997 /* third pass to handle entered track status in a comprehensible way.
1998 */
1999
2000 switch (item_type) {
2001 case GainLineItem:
2002 case AutomationLineItem:
2003 case ControlPointItem:
2004 /* these do not affect the current entered track state */
2005 clear_entered_track = false;
2006 break;
2007
2008 case AutomationTrackItem:
2009 /* handled above already */
2010 break;
2011
2012 default:
2013
2014 break;
2015 }
2016
2017 return ret;
2018 }
2019
2020 bool
leave_handler(ArdourCanvas::Item * item,GdkEvent *,ItemType item_type)2021 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent*, ItemType item_type)
2022 {
2023 AutomationLine* al;
2024 ArdourMarker *marker;
2025 TempoMarker *t_marker;
2026 MeterMarker *m_marker;
2027 bool ret = true;
2028
2029 if (!_enter_stack.empty()) {
2030 _enter_stack.pop_back();
2031 }
2032
2033 switch (item_type) {
2034 case ControlPointItem:
2035 _verbose_cursor->hide ();
2036 break;
2037
2038 case GainLineItem:
2039 case AutomationLineItem:
2040 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
2041 {
2042 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
2043 if (line) {
2044 line->set_outline_color (al->get_line_color());
2045 }
2046 }
2047 break;
2048
2049 case MarkerItem:
2050 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2051 break;
2052 }
2053 entered_marker = 0;
2054 marker->set_entered (false);
2055 break;
2056
2057 case MeterMarkerItem:
2058 if ((m_marker = static_cast<MeterMarker *> (item->get_data ("marker"))) == 0) {
2059 break;
2060 }
2061 entered_marker = 0;
2062 if (m_marker->meter().position_lock_style() == MusicTime) {
2063 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker music"));
2064 } else {
2065 m_marker->set_color_rgba (UIConfiguration::instance().color ("meter marker"));
2066 }
2067 break;
2068
2069 case TempoMarkerItem:
2070 if ((t_marker = static_cast<TempoMarker *> (item->get_data ("marker"))) == 0) {
2071 break;
2072 }
2073 entered_marker = 0;
2074 if (t_marker->tempo().position_lock_style() == MusicTime) {
2075 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker music"));
2076 } else {
2077 t_marker->set_color_rgba (UIConfiguration::instance().color ("tempo marker"));
2078 }
2079 break;
2080
2081 case FadeInTrimHandleItem:
2082 case FadeOutTrimHandleItem:
2083 case FadeInHandleItem:
2084 case FadeOutHandleItem:
2085 {
2086 ArdourCanvas::Rectangle *rect = dynamic_cast<ArdourCanvas::Rectangle *> (item);
2087 if (rect) {
2088 rect->set_fill_color (UIConfiguration::instance().color ("inactive fade handle"));
2089 }
2090 }
2091 break;
2092
2093 case AutomationTrackItem:
2094 break;
2095
2096 case FeatureLineItem:
2097 {
2098 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
2099 line->set_outline_color (UIConfiguration::instance().color ("zero line"));
2100 }
2101 break;
2102
2103 default:
2104 break;
2105 }
2106
2107 return ret;
2108 }
2109
2110 void
scrub(samplepos_t sample,double current_x)2111 Editor::scrub (samplepos_t sample, double current_x)
2112 {
2113 double delta;
2114
2115 if (scrubbing_direction == 0) {
2116 /* first move */
2117 _session->request_locate (sample, MustStop);
2118 _session->request_transport_speed (0.1, false);
2119 scrubbing_direction = 1;
2120
2121 } else {
2122
2123 if (last_scrub_x > current_x) {
2124
2125 /* pointer moved to the left */
2126
2127 if (scrubbing_direction > 0) {
2128
2129 /* we reversed direction to go backwards */
2130
2131 scrub_reversals++;
2132 scrub_reverse_distance += (int) (last_scrub_x - current_x);
2133
2134 } else {
2135
2136 /* still moving to the left (backwards) */
2137
2138 scrub_reversals = 0;
2139 scrub_reverse_distance = 0;
2140
2141 delta = 0.01 * (last_scrub_x - current_x);
2142 _session->request_transport_speed_nonzero (_session->actual_speed() - delta, false);
2143 }
2144
2145 } else {
2146 /* pointer moved to the right */
2147
2148 if (scrubbing_direction < 0) {
2149 /* we reversed direction to go forward */
2150
2151 scrub_reversals++;
2152 scrub_reverse_distance += (int) (current_x - last_scrub_x);
2153
2154 } else {
2155 /* still moving to the right */
2156
2157 scrub_reversals = 0;
2158 scrub_reverse_distance = 0;
2159
2160 delta = 0.01 * (current_x - last_scrub_x);
2161 _session->request_transport_speed_nonzero (_session->actual_speed() + delta, false);
2162 }
2163 }
2164
2165 /* if there have been more than 2 opposite motion moves detected, or one that moves
2166 back more than 10 pixels, reverse direction
2167 */
2168
2169 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
2170
2171 if (scrubbing_direction > 0) {
2172 /* was forwards, go backwards */
2173 _session->request_transport_speed (-0.1, false);
2174 scrubbing_direction = -1;
2175 } else {
2176 /* was backwards, go forwards */
2177 _session->request_transport_speed (0.1, false);
2178 scrubbing_direction = 1;
2179 }
2180
2181 scrub_reverse_distance = 0;
2182 scrub_reversals = 0;
2183 }
2184 }
2185
2186 last_scrub_x = current_x;
2187 }
2188
2189 bool
motion_handler(ArdourCanvas::Item *,GdkEvent * event,bool from_autoscroll)2190 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
2191 {
2192 _last_motion_y = event->motion.y;
2193
2194 if (event->motion.is_hint) {
2195 gint x, y;
2196
2197 /* We call this so that MOTION_NOTIFY events continue to be
2198 * delivered to the canvas. We need to do this because we set
2199 * Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
2200 * the density of the events, at the expense of a round-trip
2201 * to the server. Given that this will mostly occur on cases
2202 * where DISPLAY = :0.0, and given the cost of what the motion
2203 * event might do, its a good tradeoff.
2204 */
2205
2206 _track_canvas->get_pointer (x, y);
2207 }
2208
2209 if (current_stepping_trackview) {
2210 /* don't keep the persistent stepped trackview if the mouse moves */
2211 current_stepping_trackview = 0;
2212 step_timeout.disconnect ();
2213 }
2214
2215 if (_session && _session->actively_recording()) {
2216 /* Sorry. no dragging stuff around while we record */
2217 return true;
2218 }
2219
2220 update_join_object_range_location (event->motion.y);
2221
2222 if (_drags->active ()) {
2223 //drags change the snapped_cursor location, because we are snapping the thing being dragged, not the actual mouse cursor
2224 return _drags->motion_handler (event, from_autoscroll);
2225 } else {
2226 bool ignored;
2227 bool peaks_visible = false;
2228 MusicSample where (0, 0);
2229 if (mouse_sample (where.sample, ignored)) {
2230
2231 /* display peaks */
2232 if (mouse_mode == MouseContent || ArdourKeyboard::indicates_snap (event->motion.state)) {
2233 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(entered_regionview);
2234 if (arv) {
2235 _region_peak_cursor->set (arv, where.sample, samples_per_pixel);
2236 peaks_visible = true;
2237 }
2238 }
2239
2240 /* the snapped_cursor shows where an operation (like Split) is going to occur */
2241 snap_to_with_modifier (where, event);
2242 set_snapped_cursor_position (where.sample);
2243 }
2244
2245 if (!peaks_visible) {
2246 _region_peak_cursor->hide ();
2247 }
2248 }
2249
2250 return false;
2251 }
2252
2253 bool
can_remove_control_point(ArdourCanvas::Item * item)2254 Editor::can_remove_control_point (ArdourCanvas::Item* item)
2255 {
2256 ControlPoint* control_point;
2257
2258 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2259 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2260 abort(); /*NOTREACHED*/
2261 }
2262
2263 AutomationLine& line = control_point->line ();
2264 if (dynamic_cast<AudioRegionGainLine*> (&line)) {
2265 /* we shouldn't remove the first or last gain point in region gain lines */
2266 if (line.is_last_point(*control_point) || line.is_first_point(*control_point)) {
2267 return false;
2268 }
2269 }
2270
2271 return true;
2272 }
2273
2274 void
remove_control_point(ArdourCanvas::Item * item)2275 Editor::remove_control_point (ArdourCanvas::Item* item)
2276 {
2277 if (!can_remove_control_point (item)) {
2278 return;
2279 }
2280
2281 ControlPoint* control_point;
2282
2283 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2284 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2285 abort(); /*NOTREACHED*/
2286 }
2287
2288 control_point->line().remove_point (*control_point);
2289 }
2290
2291 void
edit_control_point(ArdourCanvas::Item * item)2292 Editor::edit_control_point (ArdourCanvas::Item* item)
2293 {
2294 ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
2295
2296 if (p == 0) {
2297 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2298 abort(); /*NOTREACHED*/
2299 }
2300
2301 ControlPointDialog d (p);
2302
2303 if (d.run () != RESPONSE_ACCEPT) {
2304 return;
2305 }
2306
2307 p->line().modify_point_y (*p, d.get_y_fraction ());
2308 }
2309
2310 void
edit_notes(MidiRegionView * mrv)2311 Editor::edit_notes (MidiRegionView* mrv)
2312 {
2313 MidiRegionView::Selection const & s = mrv->selection();
2314
2315 if (s.empty ()) {
2316 return;
2317 }
2318
2319 EditNoteDialog* d = new EditNoteDialog (mrv, s);
2320 d->show_all ();
2321
2322 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &Editor::note_edit_done), d));
2323 }
2324
2325 void
note_edit_done(int r,EditNoteDialog * d)2326 Editor::note_edit_done (int r, EditNoteDialog* d)
2327 {
2328 d->done (r);
2329 delete d;
2330 }
2331
2332 void
edit_region(RegionView * rv)2333 Editor::edit_region (RegionView* rv)
2334 {
2335 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
2336 temporal_zoom_selection (Both);
2337 } else {
2338 rv->show_region_editor ();
2339 }
2340 }
2341
2342 void
visible_order_range(int * low,int * high) const2343 Editor::visible_order_range (int* low, int* high) const
2344 {
2345 *low = TimeAxisView::max_order ();
2346 *high = 0;
2347
2348 for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
2349
2350 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
2351
2352 if (rtv && !rtv->hidden()) {
2353
2354 if (*high < rtv->order()) {
2355 *high = rtv->order ();
2356 }
2357
2358 if (*low > rtv->order()) {
2359 *low = rtv->order ();
2360 }
2361 }
2362 }
2363 }
2364
2365 void
region_view_item_click(AudioRegionView & rv,GdkEventButton * event)2366 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
2367 {
2368 /* Either add to or set the set the region selection, unless
2369 * this is an alignment click (control used)
2370 */
2371
2372 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
2373
2374 samplepos_t where = get_preferred_edit_position();
2375
2376 if (where >= 0) {
2377
2378 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
2379
2380 align_region (rv.region(), SyncPoint, where);
2381
2382 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
2383
2384 align_region (rv.region(), End, where);
2385
2386 } else {
2387
2388 align_region (rv.region(), Start, where);
2389 }
2390 }
2391 }
2392 }
2393
2394 void
collect_new_region_view(RegionView * rv)2395 Editor::collect_new_region_view (RegionView* rv)
2396 {
2397 latest_regionviews.push_back (rv);
2398 }
2399
2400 void
collect_and_select_new_region_view(RegionView * rv)2401 Editor::collect_and_select_new_region_view (RegionView* rv)
2402 {
2403 selection->add(rv);
2404 latest_regionviews.push_back (rv);
2405 }
2406
2407 void
cancel_selection()2408 Editor::cancel_selection ()
2409 {
2410 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2411 (*i)->hide_selection ();
2412 }
2413
2414 selection->clear ();
2415 clicked_selection = 0;
2416 }
2417
2418 void
cancel_time_selection()2419 Editor::cancel_time_selection ()
2420 {
2421 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
2422 (*i)->hide_selection ();
2423 }
2424 selection->time.clear ();
2425 clicked_selection = 0;
2426 }
2427
2428 void
point_trim(GdkEvent * event,samplepos_t new_bound)2429 Editor::point_trim (GdkEvent* event, samplepos_t new_bound)
2430 {
2431 RegionView* rv = clicked_regionview;
2432
2433 /* Choose action dependant on which button was pressed */
2434 switch (event->button.button) {
2435 case 1:
2436 begin_reversible_command (_("start point trim"));
2437
2438 if (selection->selected (rv)) {
2439 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
2440 i != selection->regions.by_layer().end(); ++i)
2441 {
2442 if (!(*i)->region()->locked()) {
2443 (*i)->region()->clear_changes ();
2444 (*i)->region()->trim_front (new_bound);
2445 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2446 }
2447 }
2448
2449 } else {
2450 if (!rv->region()->locked()) {
2451 rv->region()->clear_changes ();
2452 rv->region()->trim_front (new_bound);
2453 _session->add_command(new StatefulDiffCommand (rv->region()));
2454 }
2455 }
2456
2457 commit_reversible_command();
2458
2459 break;
2460 case 2:
2461 begin_reversible_command (_("end point trim"));
2462
2463 if (selection->selected (rv)) {
2464
2465 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
2466 {
2467 if (!(*i)->region()->locked()) {
2468 (*i)->region()->clear_changes();
2469 (*i)->region()->trim_end (new_bound);
2470 _session->add_command(new StatefulDiffCommand ((*i)->region()));
2471 }
2472 }
2473
2474 } else {
2475
2476 if (!rv->region()->locked()) {
2477 rv->region()->clear_changes ();
2478 rv->region()->trim_end (new_bound);
2479 _session->add_command (new StatefulDiffCommand (rv->region()));
2480 }
2481 }
2482
2483 commit_reversible_command();
2484
2485 break;
2486 default:
2487 break;
2488 }
2489 }
2490
2491 void
hide_marker(ArdourCanvas::Item * item,GdkEvent *)2492 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
2493 {
2494 ArdourMarker* marker;
2495 bool is_start;
2496
2497 if ((marker = static_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
2498 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2499 abort(); /*NOTREACHED*/
2500 }
2501
2502 Location* location = find_location_from_marker (marker, is_start);
2503 location->set_hidden (true, this);
2504 }
2505
2506 gint
mouse_rename_region(ArdourCanvas::Item *,GdkEvent *)2507 Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
2508 {
2509 using namespace Gtkmm2ext;
2510
2511 ArdourWidgets::Prompter prompter (false);
2512
2513 prompter.set_prompt (_("Name for region:"));
2514 prompter.set_initial_text (clicked_regionview->region()->name());
2515 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
2516 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
2517 prompter.show_all ();
2518 switch (prompter.run ()) {
2519 case Gtk::RESPONSE_ACCEPT:
2520 string str;
2521 prompter.get_result(str);
2522 if (str.length()) {
2523 clicked_regionview->region()->set_name (str);
2524 }
2525 break;
2526 }
2527 return true;
2528 }
2529
2530
2531 void
mouse_brush_insert_region(RegionView * rv,samplepos_t pos)2532 Editor::mouse_brush_insert_region (RegionView* rv, samplepos_t pos)
2533 {
2534 /* no brushing without a useful quantize setting */
2535 if (_grid_type == GridTypeNone)
2536 return;
2537
2538 /* don't brush a copy over the original */
2539
2540 if (pos == rv->region()->position()) {
2541 return;
2542 }
2543
2544 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&rv->get_time_axis_view());
2545
2546 if (!rtv || !rtv->is_track()) {
2547 return;
2548 }
2549
2550 boost::shared_ptr<Playlist> playlist = rtv->playlist();
2551
2552 playlist->clear_changes ();
2553 boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region(), true));
2554 playlist->add_region (new_region, pos);
2555 _session->add_command (new StatefulDiffCommand (playlist));
2556
2557 /* playlist is frozen, so we have to update manually XXX this is disgusting */
2558
2559 //playlist->RegionAdded (new_region); /* EMIT SIGNAL */
2560 }
2561
2562 gint
track_height_step_timeout()2563 Editor::track_height_step_timeout ()
2564 {
2565 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
2566 current_stepping_trackview = 0;
2567 return false;
2568 }
2569 return true;
2570 }
2571
2572 void
add_region_drag(ArdourCanvas::Item * item,GdkEvent *,RegionView * region_view)2573 Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2574 {
2575 assert (region_view);
2576
2577 if (!region_view->region()->playlist()) {
2578 return;
2579 }
2580
2581 switch (Config->get_edit_mode()) {
2582 case Splice:
2583 _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
2584 break;
2585 case Ripple:
2586 _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
2587 break;
2588 default:
2589 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false));
2590 break;
2591
2592 }
2593 }
2594
2595 void
add_region_copy_drag(ArdourCanvas::Item * item,GdkEvent *,RegionView * region_view)2596 Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2597 {
2598 assert (region_view);
2599
2600 if (!region_view->region()->playlist()) {
2601 return;
2602 }
2603
2604 _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), true));
2605 }
2606
2607 void
add_region_brush_drag(ArdourCanvas::Item * item,GdkEvent *,RegionView * region_view)2608 Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region_view)
2609 {
2610 assert (region_view);
2611
2612 if (!region_view->region()->playlist()) {
2613 return;
2614 }
2615
2616 if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
2617 return;
2618 }
2619
2620 std::list<RegionView*> empty;
2621 _drags->add (new RegionBrushDrag (this, item, region_view, empty));
2622 }
2623
2624 /** Start a grab where a time range is selected, track(s) are selected, and the
2625 * user clicks and drags a region with a modifier in order to create a new region containing
2626 * the section of the clicked region that lies within the time range.
2627 */
2628 void
start_selection_grab(ArdourCanvas::Item *,GdkEvent * event)2629 Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
2630 {
2631 if (clicked_regionview == 0) {
2632 return;
2633 }
2634
2635 /* lets try to create new Region for the selection */
2636
2637 vector<boost::shared_ptr<Region> > new_regions;
2638 create_region_from_selection (new_regions);
2639
2640 if (new_regions.empty()) {
2641 return;
2642 }
2643
2644 /* XXX fix me one day to use all new regions */
2645
2646 boost::shared_ptr<Region> region (new_regions.front());
2647
2648 /* add it to the current stream/playlist.
2649 *
2650 * tricky: the streamview for the track will add a new regionview. we will
2651 * catch the signal it sends when it creates the regionview to
2652 * set the regionview we want to then drag.
2653 */
2654
2655 latest_regionviews.clear();
2656 sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
2657
2658 /* A selection grab currently creates two undo/redo operations, one for
2659 * creating the new region and another for moving it.
2660 */
2661 begin_reversible_command (Operations::selection_grab);
2662
2663 boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
2664
2665 playlist->clear_changes ();
2666 clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
2667 _session->add_command(new StatefulDiffCommand (playlist));
2668
2669 c.disconnect ();
2670
2671 if (latest_regionviews.empty()) {
2672 /* something went wrong */
2673 abort_reversible_command ();
2674 return;
2675 }
2676
2677 /* we need to deselect all other regionviews, and select this one
2678 * i'm ignoring undo stuff, because the region creation will take care of it
2679 */
2680
2681 selection->set (latest_regionviews);
2682
2683 commit_reversible_command ();
2684
2685 _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false), event);
2686 }
2687
2688 void
escape()2689 Editor::escape ()
2690 {
2691 if (_drags->active ()) {
2692 _drags->abort ();
2693 } else if (_session) {
2694
2695 midi_action (&MidiRegionView::clear_note_selection);
2696
2697 selection->clear ();
2698
2699 /* if session is playing a range, cancel that */
2700 if (_session->get_play_range()) {
2701 _session->request_cancel_play_range();
2702 }
2703
2704 if (_session->solo_selection_active()) {
2705 StripableList sl;
2706 _session->solo_selection (sl, false);
2707 }
2708 }
2709
2710 ARDOUR_UI::instance()->reset_focus (&contents());
2711 }
2712
2713 /** Update _join_object_range_state which indicate whether we are over the top
2714 * or bottom half of a route view, used by the `join object/range' tool
2715 * mode. Coordinates in canvas space.
2716 */
2717 void
update_join_object_range_location(double y)2718 Editor::update_join_object_range_location (double y)
2719 {
2720 if (!get_smart_mode()) {
2721 _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
2722 return;
2723 }
2724
2725 JoinObjectRangeState const old = _join_object_range_state;
2726
2727 if (mouse_mode == MouseObject) {
2728 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2729 } else if (mouse_mode == MouseRange) {
2730 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2731 }
2732
2733 if (entered_regionview) {
2734
2735 /* TODO: there is currently a bug here(?)
2736 * when we are inside a region fade handle, it acts as though we are in range mode because it is in the top half of the region
2737 * can it be fixed here?
2738 */
2739
2740 ArdourCanvas::Duple const item_space = entered_regionview->get_canvas_group()->canvas_to_item (ArdourCanvas::Duple (0, y));
2741 double const c = item_space.y / entered_regionview->height();
2742
2743 _join_object_range_state = c <= 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
2744
2745 Editor::EnterContext* ctx = get_enter_context(RegionItem);
2746 if (_join_object_range_state != old && ctx) {
2747 ctx->cursor_ctx->change(which_track_cursor());
2748 }
2749
2750 } else if (entered_track) {
2751
2752 RouteTimeAxisView* entered_route_view = dynamic_cast<RouteTimeAxisView*> (entered_track);
2753
2754 if (entered_route_view) {
2755
2756 double cx = 0;
2757 double cy = y;
2758
2759 entered_route_view->canvas_display()->canvas_to_item (cx, cy);
2760
2761 double track_height = entered_route_view->view()->child_height();
2762 if (UIConfiguration::instance().get_show_name_highlight()) {
2763 track_height -= TimeAxisViewItem::NAME_HIGHLIGHT_SIZE;
2764 }
2765 double const c = cy / track_height;
2766
2767
2768 if (c <= 0.5) {
2769 _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
2770 } else {
2771 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2772 }
2773
2774 } else {
2775 /* Other kinds of tracks use object mode */
2776 _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
2777 }
2778
2779 Editor::EnterContext* ctx = get_enter_context(StreamItem);
2780 if (_join_object_range_state != old && ctx) {
2781 ctx->cursor_ctx->change(which_track_cursor());
2782 }
2783 }
2784 }
2785
2786 Editing::MouseMode
effective_mouse_mode() const2787 Editor::effective_mouse_mode () const
2788 {
2789 if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
2790 return MouseObject;
2791 } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
2792 return MouseRange;
2793 }
2794
2795 return mouse_mode;
2796 }
2797
2798 void
remove_midi_note(ArdourCanvas::Item * item,GdkEvent *)2799 Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
2800 {
2801 NoteBase* e = reinterpret_cast<NoteBase*> (item->get_data ("notebase"));
2802 assert (e);
2803
2804 e->region_view().delete_note (e->note ());
2805 }
2806
2807 /** Obtain the pointer position in canvas coordinates */
2808 void
get_pointer_position(double & x,double & y) const2809 Editor::get_pointer_position (double& x, double& y) const
2810 {
2811 int px, py;
2812 _track_canvas->get_pointer (px, py);
2813 _track_canvas->window_to_canvas (px, py, x, y);
2814 }
2815