Use a Drag class for midi note selection, so that it
[ardour.git] / gtk2_ardour / midi_region_view.cc
1 /*
2     Copyright (C) 2001-2011 Paul Davis
3     Author: David Robillard
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include <cmath>
21 #include <cassert>
22 #include <algorithm>
23 #include <ostream>
24
25 #include <gtkmm.h>
26
27 #include <gtkmm2ext/gtk_ui.h>
28
29 #include <sigc++/signal.h>
30
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
33
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
41
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIParameters.hpp"
44 #include "evoral/Control.hpp"
45 #include "evoral/midi_util.h"
46
47 #include "automation_region_view.h"
48 #include "automation_time_axis.h"
49 #include "canvas-hit.h"
50 #include "canvas-note.h"
51 #include "canvas_patch_change.h"
52 #include "debug.h"
53 #include "editor.h"
54 #include "editor_drag.h"
55 #include "ghostregion.h"
56 #include "gui_thread.h"
57 #include "keyboard.h"
58 #include "midi_channel_dialog.h"
59 #include "midi_cut_buffer.h"
60 #include "midi_list_editor.h"
61 #include "midi_region_view.h"
62 #include "midi_streamview.h"
63 #include "midi_time_axis.h"
64 #include "midi_util.h"
65 #include "note_player.h"
66 #include "public_editor.h"
67 #include "rgb_macros.h"
68 #include "selection.h"
69 #include "simpleline.h"
70 #include "streamview.h"
71 #include "utils.h"
72 #include "mouse_cursors.h"
73 #include "patch_change_dialog.h"
74 #include "verbose_cursor.h"
75
76 #include "i18n.h"
77
78 using namespace ARDOUR;
79 using namespace PBD;
80 using namespace Editing;
81 using namespace ArdourCanvas;
82 using Gtkmm2ext::Keyboard;
83
84 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
85
86 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
87
88 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
89                                 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
90         : RegionView (parent, tv, r, spu, basic_color)
91         , _force_channel(-1)
92         , _last_channel_selection(0xFFFF)
93         , _current_range_min(0)
94         , _current_range_max(0)
95         , _model_name(string())
96         , _custom_device_mode(string())
97         , _active_notes(0)
98         , _note_group(new ArdourCanvas::Group(*group))
99         , _note_diff_command (0)
100         , _ghost_note(0)
101         , _drag_rect (0)
102         , _step_edit_cursor (0)
103         , _step_edit_cursor_width (1.0)
104         , _step_edit_cursor_position (0.0)
105         , _channel_selection_scoped_note (0)
106         , _temporary_note_group (0)
107         , _mouse_state(None)
108         , _pressed_button(0)
109         , _sort_needed (true)
110         , _optimization_iterator (_events.end())
111         , _list_editor (0)
112         , _no_sound_notes (false)
113         , _last_event_x (0)
114         , _last_event_y (0)
115         , _pre_enter_cursor (0)
116 {
117         _note_group->raise_to_top();
118         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
119
120         Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
121         connect_to_diskstream ();
122
123         SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
124 }
125
126 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
127                                 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
128                                 TimeAxisViewItem::Visibility visibility)
129         : RegionView (parent, tv, r, spu, basic_color, false, visibility)
130         , _force_channel(-1)
131         , _last_channel_selection(0xFFFF)
132         , _model_name(string())
133         , _custom_device_mode(string())
134         , _active_notes(0)
135         , _note_group(new ArdourCanvas::Group(*parent))
136         , _note_diff_command (0)
137         , _ghost_note(0)
138         , _drag_rect (0)
139         , _step_edit_cursor (0)
140         , _step_edit_cursor_width (1.0)
141         , _step_edit_cursor_position (0.0)
142         , _channel_selection_scoped_note (0)
143         , _temporary_note_group (0)
144         , _mouse_state(None)
145         , _pressed_button(0)
146         , _sort_needed (true)
147         , _optimization_iterator (_events.end())
148         , _list_editor (0)
149         , _no_sound_notes (false)
150         , _last_event_x (0)
151         , _last_event_y (0)
152 {
153         _note_group->raise_to_top();
154         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
155
156         connect_to_diskstream ();
157
158         SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
159 }
160
161 void
162 MidiRegionView::parameter_changed (std::string const & p)
163 {
164         if (p == "diplay-first-midi-bank-as-zero") {
165                 if (_enable_display) {
166                         redisplay_model();
167                 }
168         }
169 }
170
171 MidiRegionView::MidiRegionView (const MidiRegionView& other)
172         : sigc::trackable(other)
173         , RegionView (other)
174         , _force_channel(-1)
175         , _last_channel_selection(0xFFFF)
176         , _model_name(string())
177         , _custom_device_mode(string())
178         , _active_notes(0)
179         , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
180         , _note_diff_command (0)
181         , _ghost_note(0)
182         , _drag_rect (0)
183         , _step_edit_cursor (0)
184         , _step_edit_cursor_width (1.0)
185         , _step_edit_cursor_position (0.0)
186         , _channel_selection_scoped_note (0)
187         , _temporary_note_group (0)
188         , _mouse_state(None)
189         , _pressed_button(0)
190         , _sort_needed (true)
191         , _optimization_iterator (_events.end())
192         , _list_editor (0)
193         , _no_sound_notes (false)
194         , _last_event_x (0)
195         , _last_event_y (0)
196 {
197         Gdk::Color c;
198         int r,g,b,a;
199
200         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
201         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
202
203         init (c, false);
204 }
205
206 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
207         : RegionView (other, boost::shared_ptr<Region> (region))
208         , _force_channel(-1)
209         , _last_channel_selection(0xFFFF)
210         , _model_name(string())
211         , _custom_device_mode(string())
212         , _active_notes(0)
213         , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
214         , _note_diff_command (0)
215         , _ghost_note(0)
216         , _drag_rect (0)
217         , _step_edit_cursor (0)
218         , _step_edit_cursor_width (1.0)
219         , _step_edit_cursor_position (0.0)
220         , _channel_selection_scoped_note (0)
221         , _temporary_note_group (0)
222         , _mouse_state(None)
223         , _pressed_button(0)
224         , _sort_needed (true)
225         , _optimization_iterator (_events.end())
226         , _list_editor (0)
227         , _no_sound_notes (false)
228         , _last_event_x (0)
229         , _last_event_y (0)
230 {
231         Gdk::Color c;
232         int r,g,b,a;
233
234         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
235         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
236
237         init (c, true);
238 }
239
240 void
241 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
242 {
243         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
244
245         CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
246                                                          ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
247                                                          gui_context());
248
249         if (wfd) {
250                 midi_region()->midi_source(0)->load_model();
251         }
252
253         _model = midi_region()->midi_source(0)->model();
254         _enable_display = false;
255
256         RegionView::init (basic_color, false);
257
258         compute_colors (basic_color);
259
260         set_height (trackview.current_height());
261
262         region_muted ();
263         region_sync_changed ();
264         region_resized (ARDOUR::bounds_change);
265         region_locked ();
266
267         reset_width_dependent_items (_pixel_width);
268
269         set_colors ();
270
271         _enable_display = true;
272         if (_model) {
273                 if (wfd) {
274                         display_model (_model);
275                 }
276         }
277
278         group->raise_to_top();
279         group->signal_event().connect(
280                 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
281
282         midi_view()->signal_channel_mode_changed().connect(
283                 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
284
285         midi_view()->signal_midi_patch_settings_changed().connect(
286                 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
287
288         trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
289                                                ui_bind(&MidiRegionView::snap_changed, this),
290                                                gui_context());
291
292         Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
293         connect_to_diskstream ();
294
295         SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
296 }
297
298 const boost::shared_ptr<ARDOUR::MidiRegion>
299 MidiRegionView::midi_region() const
300 {
301         return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
302 }
303
304 void
305 MidiRegionView::connect_to_diskstream ()
306 {
307         midi_view()->midi_track()->DataRecorded.connect(
308                 *this, invalidator(*this),
309                 ui_bind(&MidiRegionView::data_recorded, this, _1),
310                 gui_context());
311 }
312
313 bool
314 MidiRegionView::canvas_event(GdkEvent* ev)
315 {
316         switch (ev->type) {
317         case GDK_ENTER_NOTIFY:
318         case GDK_LEAVE_NOTIFY:
319                 _last_event_x = ev->crossing.x;
320                 _last_event_y = ev->crossing.y;
321                 break;
322         case GDK_MOTION_NOTIFY:
323                 _last_event_x = ev->motion.x;
324                 _last_event_y = ev->motion.y;
325                 break;
326         default:
327                 break;
328         }
329
330         if (!trackview.editor().internal_editing()) {
331                 return false;
332         }
333
334         switch (ev->type) {
335         case GDK_SCROLL:
336                 return scroll (&ev->scroll);
337
338         case GDK_KEY_PRESS:
339                 return key_press (&ev->key);
340
341         case GDK_KEY_RELEASE:
342                 return key_release (&ev->key);
343
344         case GDK_BUTTON_PRESS:
345                 return button_press (&ev->button);
346
347         case GDK_2BUTTON_PRESS:
348                 return true;
349
350         case GDK_BUTTON_RELEASE:
351                 return button_release (&ev->button);
352
353         case GDK_ENTER_NOTIFY:
354                 return enter_notify (&ev->crossing);
355
356         case GDK_LEAVE_NOTIFY:
357                 return leave_notify (&ev->crossing);
358
359         case GDK_MOTION_NOTIFY:
360                 return motion (&ev->motion);
361
362         default:
363                 break;
364         }
365
366         return false;
367 }
368
369 void
370 MidiRegionView::remove_ghost_note ()
371 {
372         delete _ghost_note;
373         _ghost_note = 0;
374 }
375
376 bool
377 MidiRegionView::enter_notify (GdkEventCrossing* ev)
378 {
379         trackview.editor().MouseModeChanged.connect (
380                 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
381                 );
382
383         if (trackview.editor().current_mouse_mode() == MouseRange) {
384                 create_ghost_note (ev->x, ev->y);
385         }
386
387         if (!trackview.editor().internal_editing()) {
388                 Keyboard::magic_widget_drop_focus();
389         } else {
390                 Keyboard::magic_widget_grab_focus();
391                 group->grab_focus();
392         }
393
394         return false;
395 }
396
397 bool
398 MidiRegionView::leave_notify (GdkEventCrossing*)
399 {
400         _mouse_mode_connection.disconnect ();
401
402         trackview.editor().verbose_cursor()->hide ();
403         remove_ghost_note ();
404
405         return false;
406 }
407
408 void
409 MidiRegionView::mouse_mode_changed ()
410 {
411         if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
412                 create_ghost_note (_last_event_x, _last_event_y);
413         } else {
414                 remove_ghost_note ();
415                 trackview.editor().verbose_cursor()->hide ();
416         }
417
418         if (!trackview.editor().internal_editing()) {
419                 Keyboard::magic_widget_drop_focus();
420         } else {
421                 Keyboard::magic_widget_grab_focus();
422                 group->grab_focus();
423         }
424 }
425
426 bool
427 MidiRegionView::button_press (GdkEventButton* ev)
428 {
429         if (ev->button != 1) {
430                 return false;
431         }
432
433         _last_x = ev->x;
434         _last_y = ev->y;
435
436         group->w2i (_last_x, _last_y);
437
438         if (_mouse_state != SelectTouchDragging) {
439
440                 _pressed_button = ev->button;
441                 _mouse_state = Pressed;
442
443                 return true;
444         }
445
446         _pressed_button = ev->button;
447
448         return true;
449 }
450
451 bool
452 MidiRegionView::button_release (GdkEventButton* ev)
453 {
454         double event_x, event_y;
455
456         if (ev->button != 1) {
457                 return false;
458         }
459
460         event_x = ev->x;
461         event_y = ev->y;
462
463         group->w2i(event_x, event_y);
464         group->ungrab(ev->time);
465
466         PublicEditor& editor = trackview.editor ();
467
468         switch (_mouse_state) {
469         case Pressed: // Clicked
470
471                 switch (editor.current_mouse_mode()) {
472                 case MouseObject:
473                 case MouseTimeFX:
474                         {
475                                 clear_selection();
476
477                                 if (Keyboard::is_insert_note_event(ev)) {
478
479                                         double event_x, event_y;
480
481                                         event_x = ev->x;
482                                         event_y = ev->y;
483                                         group->w2i(event_x, event_y);
484
485                                         bool success;
486                                         Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
487
488                                         if (!success) {
489                                                 beats = 1;
490                                         }
491
492                                         create_note_at (event_x, event_y, beats, true, true);
493                                 }
494
495                                 break;
496                         }
497                 case MouseRange:
498                         {
499                                 bool success;
500                                 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
501
502                                 if (!success) {
503                                         beats = 1;
504                                 }
505
506                                 create_note_at (event_x, event_y, beats, true, true);
507
508                                 break;
509                         }
510                 default:
511                         break;
512                 }
513
514                 _mouse_state = None;
515                 break;
516
517         case SelectRectDragging: // Select drag done
518                 editor.drags()->end_grab ((GdkEvent *) ev);
519                 _mouse_state = None;
520                 break;
521
522         case AddDragging: // Add drag done
523
524                 _mouse_state = None;
525
526                 if (Keyboard::is_insert_note_event(ev) || trackview.editor().current_mouse_mode() == MouseRange) {
527
528                         if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
529
530                                 const double x  = _drag_rect->property_x1();
531                                 const double length = trackview.editor().pixel_to_frame (_drag_rect->property_x2() - _drag_rect->property_x1());
532
533                                 create_note_at (x, _drag_rect->property_y1(), region_frames_to_region_beats(length), true, false);
534                         }
535                 }
536
537                 delete _drag_rect;
538                 _drag_rect = 0;
539
540                 create_ghost_note (ev->x, ev->y);
541
542         default:
543                 break;
544         }
545
546         return false;
547 }
548
549 bool
550 MidiRegionView::motion (GdkEventMotion* ev)
551 {
552         double event_x, event_y;
553         framepos_t event_frame = 0;
554
555         event_x = ev->x;
556         event_y = ev->y;
557         group->w2i(event_x, event_y);
558
559         PublicEditor& editor = trackview.editor ();
560         
561         // convert event_x to global frame
562         framecnt_t grid_frames;
563         event_frame = snap_frame_to_grid_underneath (editor.pixel_to_frame (event_x), grid_frames);
564
565         if (!_ghost_note && editor.current_mouse_mode() != MouseRange
566             && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
567             && _mouse_state != AddDragging) {
568
569                 create_ghost_note (ev->x, ev->y);
570         } else if (_ghost_note && editor.current_mouse_mode() != MouseRange
571                    && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
572
573                 update_ghost_note (ev->x, ev->y);
574         } else if (_ghost_note && editor.current_mouse_mode() != MouseRange) {
575
576                 delete _ghost_note;
577                 _ghost_note = 0;
578
579                 editor.verbose_cursor()->hide ();
580         } else if (_ghost_note && editor.current_mouse_mode() == MouseRange) {
581                 update_ghost_note (ev->x, ev->y);
582         }
583
584         /* any motion immediately hides velocity text that may have been visible */
585
586         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
587                 (*i)->hide_velocity ();
588         }
589
590         switch (_mouse_state) {
591         case Pressed: // Maybe start a drag, if we've moved a bit
592
593                 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
594                         /* no appreciable movement since the button was pressed */
595                         return false;
596                 }
597
598                 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject
599                     && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
600
601                         editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
602                         _mouse_state = SelectRectDragging;
603                         return true;
604
605                 } else if (editor.internal_editing()) {
606                         // Add note drag start
607
608                         group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
609                                     Gdk::Cursor(Gdk::FLEUR), ev->time);
610
611                         _last_x = event_x;
612                         _last_y = event_y;
613                         _drag_start_x = event_x;
614                         _drag_start_y = event_y;
615
616                         _drag_rect = new ArdourCanvas::SimpleRect(*group);
617                         _drag_rect->property_x1() = editor.frame_to_pixel(event_frame);
618
619                         _drag_rect->property_y1() = midi_stream_view()->note_to_y(
620                                 midi_stream_view()->y_to_note(event_y));
621                         _drag_rect->property_x2() = editor.frame_to_pixel(event_frame);
622                         
623                         _drag_rect->property_y2() = _drag_rect->property_y1()
624                                 + floor(midi_stream_view()->note_height());
625                         _drag_rect->property_outline_what() = 0xFF;
626                         _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
627                         _drag_rect->property_fill_color_rgba()    = 0xFFFFFF66;
628
629                         _mouse_state = AddDragging;
630                         
631                         delete _ghost_note;
632                         _ghost_note = 0;
633
634                         editor.verbose_cursor()->hide ();
635
636                         return true;
637                 }
638
639                 return false;
640
641         case SelectRectDragging:
642                 editor.drags()->motion_handler ((GdkEvent *) ev, false);
643                 break;
644                 
645         case AddDragging: // Add note drag motion
646
647                 if (ev->is_hint) {
648                         int t_x;
649                         int t_y;
650                         GdkModifierType state;
651                         gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
652                         event_x = t_x;
653                         event_y = t_y;
654                 }
655
656                 if (_mouse_state == AddDragging) {
657                         event_x = editor.frame_to_pixel(event_frame);
658
659                         if (editor.snap_mode() == SnapNormal) {
660                                 /* event_frame will have been snapped to the start of the note we are under;
661                                    it's more intuitive if we use the end of that note here
662                                 */
663                                 event_x = editor.frame_to_pixel (event_frame + grid_frames);
664                         } else {
665                                 event_x = editor.frame_to_pixel (event_frame);
666                         }
667                         
668                 }
669
670                 if (_drag_rect) {
671
672                         if (event_x > _drag_start_x) {
673                                 _drag_rect->property_x2() = event_x;
674                         }
675                         else {
676                                 _drag_rect->property_x1() = event_x;
677                         }
678                 }
679
680                 _last_x = event_x;
681                 _last_y = event_y;
682
683         case SelectTouchDragging:
684                 return false;
685
686         default:
687                 break;
688         }
689
690         return false;
691 }
692
693
694 bool
695 MidiRegionView::scroll (GdkEventScroll* ev)
696 {
697         if (_selection.empty()) {
698                 return false;
699         }
700
701         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
702                 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
703                    it still works for zoom.
704                 */
705                 return false;
706         }
707
708         trackview.editor().verbose_cursor()->hide ();
709
710         bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
711
712         if (ev->direction == GDK_SCROLL_UP) {
713                 change_velocities (true, fine, false);
714         } else if (ev->direction == GDK_SCROLL_DOWN) {
715                 change_velocities (false, fine, false);
716         }
717         return true;
718 }
719
720 bool
721 MidiRegionView::key_press (GdkEventKey* ev)
722 {
723         /* since GTK bindings are generally activated on press, and since
724            detectable auto-repeat is the name of the game and only sends
725            repeated presses, carry out key actions at key press, not release.
726         */
727
728         bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
729         
730         if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
731                 _mouse_state = SelectTouchDragging;
732                 return true;
733
734         } else if (ev->keyval == GDK_Escape && unmodified) {
735                 clear_selection();
736                 _mouse_state = None;
737
738         } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
739
740                 bool start = (ev->keyval == GDK_comma);
741                 bool end = (ev->keyval == GDK_period);
742                 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
743                 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
744
745                 change_note_lengths (fine, shorter, 0.0, start, end);
746
747                 return true;
748
749         } else if (ev->keyval == GDK_Delete && unmodified) {
750
751                 delete_selection();
752                 return true;
753
754         } else if (ev->keyval == GDK_Tab) {
755
756                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
757                         goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
758                 } else {
759                         goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
760                 }
761                 return true;
762
763         } else if (ev->keyval == GDK_ISO_Left_Tab) {
764
765                 /* Shift-TAB generates ISO Left Tab, for some reason */
766
767                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
768                         goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
769                 } else {
770                         goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
771                 }
772                 return true;
773
774
775
776         } else if (ev->keyval == GDK_Up) {
777
778                 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
779                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
780
781                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
782                         change_velocities (true, fine, allow_smush);
783                 } else {
784                         transpose (true, fine, allow_smush);
785                 }
786                 return true;
787
788         } else if (ev->keyval == GDK_Down) {
789
790                 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
791                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
792
793                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
794                         change_velocities (false, fine, allow_smush);
795                 } else {
796                         transpose (false, fine, allow_smush);
797                 }
798                 return true;
799
800         } else if (ev->keyval == GDK_Left && unmodified) {
801
802                 nudge_notes (false);
803                 return true;
804
805         } else if (ev->keyval == GDK_Right && unmodified) {
806
807                 nudge_notes (true);
808                 return true;
809
810         } else if (ev->keyval == GDK_c && unmodified) {
811                 channel_edit ();
812                 return true;
813         }
814
815         return false;
816 }
817
818 bool
819 MidiRegionView::key_release (GdkEventKey* ev)
820 {
821         if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
822                 _mouse_state = None;
823                 return true;
824         }
825         return false;
826 }
827
828 void
829 MidiRegionView::channel_edit ()
830 {
831         bool first = true;
832         uint8_t current_channel = 0;
833
834         if (_selection.empty()) {
835                 return;
836         }
837         
838         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
839                 if (first) {
840                         current_channel = (*i)->note()->channel ();
841                         first = false;
842                 }
843         }
844
845         MidiChannelDialog channel_dialog (current_channel);
846         int ret = channel_dialog.run ();
847
848         switch (ret) {
849         case Gtk::RESPONSE_OK:
850                 break;
851         default:
852                 return;
853         }
854
855         uint8_t new_channel = channel_dialog.active_channel ();
856
857         start_note_diff_command (_("channel edit"));
858
859         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
860                 Selection::iterator next = i;
861                 ++next;
862                 change_note_channel (*i, new_channel);
863                 i = next;
864         }
865
866         apply_diff ();
867 }
868
869 void
870 MidiRegionView::show_list_editor ()
871 {
872         if (!_list_editor) {
873                 _list_editor = new MidiListEditor (trackview.session(), midi_region());
874         }
875         _list_editor->present ();
876 }
877
878 /** Add a note to the model, and the view, at a canvas (click) coordinate.
879  * \param x horizontal position in pixels
880  * \param y vertical position in pixels
881  * \param length duration of the note in beats, which will be snapped to the grid
882  * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
883  * \param snap_x true to snap x to the grid, otherwise false.
884  */
885 void
886 MidiRegionView::create_note_at (double x, double y, double length, bool sh, bool snap_x)
887 {
888         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
889         MidiStreamView* const view = mtv->midi_view();
890
891         double note = view->y_to_note(y);
892
893         assert(note >= 0.0);
894         assert(note <= 127.0);
895
896         // Start of note in frames relative to region start
897         framepos_t start_frames = trackview.editor().pixel_to_frame (x);
898         if (snap_x) {
899                 framecnt_t grid_frames;
900                 start_frames = snap_frame_to_grid_underneath (start_frames, grid_frames);
901         }
902         assert(start_frames >= 0);
903
904         // Snap length
905         length = region_frames_to_region_beats(
906                 snap_frame_to_frame (start_frames + region_beats_to_region_frames(length)) - start_frames);
907
908         assert (length != 0);
909
910         if (sh) {
911                 length = region_frames_to_region_beats (region_beats_to_region_frames (length) - 1);
912         }
913
914         const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
915                                                                   region_frames_to_region_beats(start_frames + _region->start()), length,
916                                                                   (uint8_t)note, 0x40));
917
918         if (_model->contains (new_note)) {
919                 return;
920         }
921
922         view->update_note_range(new_note->note());
923
924         MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
925         cmd->add (new_note);
926         _model->apply_command(*trackview.session(), cmd);
927
928         play_midi_note (new_note);
929 }
930
931 void
932 MidiRegionView::clear_events()
933 {
934         clear_selection();
935
936         MidiGhostRegion* gr;
937         for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
938                 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
939                         gr->clear_events();
940                 }
941         }
942
943         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
944                 delete *i;
945         }
946
947         _events.clear();
948         _patch_changes.clear();
949         _sys_exes.clear();
950         _optimization_iterator = _events.end();
951 }
952
953 void
954 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
955 {
956         _model = model;
957
958         content_connection.disconnect ();
959         _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
960
961         clear_events ();
962
963         if (_enable_display) {
964                 redisplay_model();
965         }
966 }
967
968 void
969 MidiRegionView::start_note_diff_command (string name)
970 {
971         if (!_note_diff_command) {
972                 _note_diff_command = _model->new_note_diff_command (name);
973         }
974 }
975
976 void
977 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
978 {
979         if (_note_diff_command) {
980                 _note_diff_command->add (note);
981         }
982         if (selected) {
983                 _marked_for_selection.insert(note);
984         }
985         if (show_velocity) {
986                 _marked_for_velocity.insert(note);
987         }
988 }
989
990 void
991 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
992 {
993         if (_note_diff_command && ev->note()) {
994                 _note_diff_command->remove(ev->note());
995         }
996 }
997
998 void
999 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1000                                       MidiModel::NoteDiffCommand::Property property,
1001                                       uint8_t val)
1002 {
1003         if (_note_diff_command) {
1004                 _note_diff_command->change (ev->note(), property, val);
1005         }
1006 }
1007
1008 void
1009 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1010                                       MidiModel::NoteDiffCommand::Property property,
1011                                       Evoral::MusicalTime val)
1012 {
1013         if (_note_diff_command) {
1014                 _note_diff_command->change (ev->note(), property, val);
1015         }
1016 }
1017
1018 void
1019 MidiRegionView::apply_diff (bool as_subcommand)
1020 {
1021         bool add_or_remove;
1022
1023         if (!_note_diff_command) {
1024                 return;
1025         }
1026
1027         if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1028                 // Mark all selected notes for selection when model reloads
1029                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1030                         _marked_for_selection.insert((*i)->note());
1031                 }
1032         }
1033
1034         if (as_subcommand) {
1035                 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1036         } else {
1037                 _model->apply_command (*trackview.session(), _note_diff_command);
1038         }
1039
1040         _note_diff_command = 0;
1041         midi_view()->midi_track()->playlist_modified();
1042
1043         if (add_or_remove) {
1044                 _marked_for_selection.clear();
1045         }
1046
1047         _marked_for_velocity.clear();
1048 }
1049
1050 void
1051 MidiRegionView::abort_command()
1052 {
1053         delete _note_diff_command;
1054         _note_diff_command = 0;
1055         clear_selection();
1056 }
1057
1058 CanvasNoteEvent*
1059 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1060 {
1061         if (_optimization_iterator != _events.end()) {
1062                 ++_optimization_iterator;
1063         }
1064
1065         if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1066                 return *_optimization_iterator;
1067         }
1068
1069         for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1070                 if ((*_optimization_iterator)->note() == note) {
1071                         return *_optimization_iterator;
1072                 }
1073         }
1074
1075         return 0;
1076 }
1077
1078 void
1079 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1080 {
1081         MidiModel::Notes notes;
1082         _model->get_notes (notes, op, val, chan_mask);
1083
1084         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1085                 CanvasNoteEvent* cne = find_canvas_note (*n);
1086                 if (cne) {
1087                         e.push_back (cne);
1088                 }
1089         }
1090 }
1091
1092 void
1093 MidiRegionView::redisplay_model()
1094 {
1095         // Don't redisplay the model if we're currently recording and displaying that
1096         if (_active_notes) {
1097                 return;
1098         }
1099
1100         if (!_model) {
1101                 return;
1102         }
1103
1104         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1105                 (*i)->invalidate ();
1106         }
1107
1108         MidiModel::ReadLock lock(_model->read_lock());
1109
1110         MidiModel::Notes& notes (_model->notes());
1111         _optimization_iterator = _events.begin();
1112
1113         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1114
1115                 boost::shared_ptr<NoteType> note (*n);
1116                 CanvasNoteEvent* cne;
1117                 bool visible;
1118
1119                 if (note_in_region_range (note, visible)) {
1120
1121                         if ((cne = find_canvas_note (note)) != 0) {
1122
1123                                 cne->validate ();
1124
1125                                 CanvasNote* cn;
1126                                 CanvasHit* ch;
1127
1128                                 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1129                                         update_note (cn);
1130                                 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1131                                         update_hit (ch);
1132                                 }
1133
1134                                 if (visible) {
1135                                         cne->show ();
1136                                 } else {
1137                                         cne->hide ();
1138                                 }
1139
1140                         } else {
1141
1142                                 add_note (note, visible);
1143                         }
1144
1145                 } else {
1146
1147                         if ((cne = find_canvas_note (note)) != 0) {
1148                                 cne->validate ();
1149                                 cne->hide ();
1150                         }
1151                 }
1152         }
1153
1154
1155         /* remove note items that are no longer valid */
1156
1157         for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1158                 if (!(*i)->valid ()) {
1159                         delete *i;
1160                         i = _events.erase (i);
1161                 } else {
1162                         ++i;
1163                 }
1164         }
1165
1166         _patch_changes.clear();
1167         _sys_exes.clear();
1168
1169         display_sysexes();
1170         display_patch_changes ();
1171
1172         _marked_for_selection.clear ();
1173         _marked_for_velocity.clear ();
1174
1175         /* we may have caused _events to contain things out of order (e.g. if a note
1176            moved earlier or later). we don't generally need them in time order, but
1177            make a note that a sort is required for those cases that require it.
1178         */
1179
1180         _sort_needed = true;
1181 }
1182
1183 void
1184 MidiRegionView::display_patch_changes ()
1185 {
1186         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1187         uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1188
1189         for (uint8_t i = 0; i < 16; ++i) {
1190                 if (chn_mask & (1<<i)) {
1191                         display_patch_changes_on_channel (i);
1192                 }
1193                 /* TODO gray-out patch instad of not displaying it */
1194         }
1195 }
1196
1197 void
1198 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1199 {
1200         for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1201
1202                 if ((*i)->channel() != channel) {
1203                         continue;
1204                 }
1205
1206                 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1207
1208                 boost::shared_ptr<MIDI::Name::Patch> patch =
1209                         MIDI::Name::MidiPatchManager::instance().find_patch(
1210                                 _model_name, _custom_device_mode, channel, patch_key);
1211
1212                 if (patch != 0) {
1213                         add_canvas_patch_change (*i, patch->name());
1214                 } else {
1215                         char buf[16];
1216                         /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1217                         snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1218                         add_canvas_patch_change (*i, buf);
1219                 }
1220         }
1221 }
1222
1223 void
1224 MidiRegionView::display_sysexes()
1225 {
1226         for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1227                 Evoral::MusicalTime time = (*i)->time();
1228                 assert(time >= 0);
1229
1230                 ostringstream str;
1231                 str << hex;
1232                 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1233                         str << int((*i)->buffer()[b]);
1234                         if (b != (*i)->size() -1) {
1235                                 str << " ";
1236                         }
1237                 }
1238                 string text = str.str();
1239
1240                 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1241
1242                 double height = midi_stream_view()->contents_height();
1243
1244                 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1245                         new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1246
1247                 // Show unless patch change is beyond the region bounds
1248                 if (time - _region->start() >= _region->length() || time < _region->start()) {
1249                         sysex->hide();
1250                 } else {
1251                         sysex->show();
1252                 }
1253
1254                 _sys_exes.push_back(sysex);
1255         }
1256 }
1257
1258
1259 MidiRegionView::~MidiRegionView ()
1260 {
1261         in_destructor = true;
1262
1263         trackview.editor().verbose_cursor()->hide ();
1264
1265         note_delete_connection.disconnect ();
1266
1267         delete _list_editor;
1268
1269         RegionViewGoingAway (this); /* EMIT_SIGNAL */
1270
1271         if (_active_notes) {
1272                 end_write();
1273         }
1274
1275         _selection.clear();
1276         clear_events();
1277
1278         delete _note_group;
1279         delete _note_diff_command;
1280         delete _step_edit_cursor;
1281         delete _temporary_note_group;
1282 }
1283
1284 void
1285 MidiRegionView::region_resized (const PropertyChange& what_changed)
1286 {
1287         RegionView::region_resized(what_changed);
1288
1289         if (what_changed.contains (ARDOUR::Properties::position)) {
1290                 set_duration(_region->length(), 0);
1291                 if (_enable_display) {
1292                         redisplay_model();
1293                 }
1294         }
1295 }
1296
1297 void
1298 MidiRegionView::reset_width_dependent_items (double pixel_width)
1299 {
1300         RegionView::reset_width_dependent_items(pixel_width);
1301         assert(_pixel_width == pixel_width);
1302
1303         if (_enable_display) {
1304                 redisplay_model();
1305         }
1306
1307         move_step_edit_cursor (_step_edit_cursor_position);
1308         set_step_edit_cursor_width (_step_edit_cursor_width);
1309 }
1310
1311 void
1312 MidiRegionView::set_height (double height)
1313 {
1314         static const double FUDGE = 2.0;
1315         const double old_height = _height;
1316         RegionView::set_height(height);
1317         _height = height - FUDGE;
1318
1319         apply_note_range(midi_stream_view()->lowest_note(),
1320                          midi_stream_view()->highest_note(),
1321                          height != old_height + FUDGE);
1322
1323         if (name_pixbuf) {
1324                 name_pixbuf->raise_to_top();
1325         }
1326
1327         for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1328                 (*x)->set_height (midi_stream_view()->contents_height());
1329         }
1330
1331         if (_step_edit_cursor) {
1332                 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1333         }
1334 }
1335
1336
1337 /** Apply the current note range from the stream view
1338  * by repositioning/hiding notes as necessary
1339  */
1340 void
1341 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1342 {
1343         if (!_enable_display) {
1344                 return;
1345         }
1346
1347         if (!force && _current_range_min == min && _current_range_max == max) {
1348                 return;
1349         }
1350
1351         _current_range_min = min;
1352         _current_range_max = max;
1353
1354         for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1355                 CanvasNoteEvent* event = *i;
1356                 boost::shared_ptr<NoteType> note (event->note());
1357
1358                 if (note->note() < _current_range_min ||
1359                     note->note() > _current_range_max) {
1360                         event->hide();
1361                 } else {
1362                         event->show();
1363                 }
1364
1365                 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1366
1367                         const double y1 = midi_stream_view()->note_to_y(note->note());
1368                         const double y2 = y1 + floor(midi_stream_view()->note_height());
1369
1370                         cnote->property_y1() = y1;
1371                         cnote->property_y2() = y2;
1372
1373                 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1374
1375                         const double diamond_size = update_hit (chit);
1376
1377                         chit->set_height (diamond_size);
1378                 }
1379         }
1380 }
1381
1382 GhostRegion*
1383 MidiRegionView::add_ghost (TimeAxisView& tv)
1384 {
1385         CanvasNote* note;
1386
1387         double unit_position = _region->position () / samples_per_unit;
1388         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1389         MidiGhostRegion* ghost;
1390
1391         if (mtv && mtv->midi_view()) {
1392                 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1393                    to allow having midi notes on top of note lines and waveforms.
1394                 */
1395                 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1396         } else {
1397                 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1398         }
1399
1400         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1401                 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1402                         ghost->add_note(note);
1403                 }
1404         }
1405
1406         ghost->set_height ();
1407         ghost->set_duration (_region->length() / samples_per_unit);
1408         ghosts.push_back (ghost);
1409
1410         GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1411
1412         return ghost;
1413 }
1414
1415
1416 /** Begin tracking note state for successive calls to add_event
1417  */
1418 void
1419 MidiRegionView::begin_write()
1420 {
1421         assert(!_active_notes);
1422         _active_notes = new CanvasNote*[128];
1423         for (unsigned i=0; i < 128; ++i) {
1424                 _active_notes[i] = 0;
1425         }
1426 }
1427
1428
1429 /** Destroy note state for add_event
1430  */
1431 void
1432 MidiRegionView::end_write()
1433 {
1434         delete[] _active_notes;
1435         _active_notes = 0;
1436         _marked_for_selection.clear();
1437         _marked_for_velocity.clear();
1438 }
1439
1440
1441 /** Resolve an active MIDI note (while recording).
1442  */
1443 void
1444 MidiRegionView::resolve_note(uint8_t note, double end_time)
1445 {
1446         if (midi_view()->note_mode() != Sustained) {
1447                 return;
1448         }
1449
1450         if (_active_notes && _active_notes[note]) {
1451
1452                 /* XXX is end_time really region-centric? I think so, because
1453                    this is a new region that we're recording, so source zero is
1454                    the same as region zero
1455                 */
1456                 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1457
1458                 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1459                 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1460                 _active_notes[note] = 0;
1461         }
1462 }
1463
1464
1465 /** Extend active notes to rightmost edge of region (if length is changed)
1466  */
1467 void
1468 MidiRegionView::extend_active_notes()
1469 {
1470         if (!_active_notes) {
1471                 return;
1472         }
1473
1474         for (unsigned i=0; i < 128; ++i) {
1475                 if (_active_notes[i]) {
1476                         _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1477                 }
1478         }
1479 }
1480
1481
1482 void
1483 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1484 {
1485         if (_no_sound_notes || !trackview.editor().sound_notes()) {
1486                 return;
1487         }
1488
1489         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1490
1491         if (!route_ui || !route_ui->midi_track()) {
1492                 return;
1493         }
1494
1495         NotePlayer* np = new NotePlayer (route_ui->midi_track());
1496         np->add (note);
1497         np->play ();
1498 }
1499
1500 void
1501 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1502 {
1503         if (_no_sound_notes || !trackview.editor().sound_notes()) {
1504                 return;
1505         }
1506
1507         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1508
1509         if (!route_ui || !route_ui->midi_track()) {
1510                 return;
1511         }
1512
1513         NotePlayer* np = new NotePlayer (route_ui->midi_track());
1514
1515         for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1516                 np->add (*n);
1517         }
1518
1519         np->play ();
1520 }
1521
1522
1523 bool
1524 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1525 {
1526         const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1527         bool outside = (note_start_frames  < 0) || (note_start_frames > _region->last_frame());
1528
1529         visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1530                 (note->note() <= midi_stream_view()->highest_note());
1531
1532         return !outside;
1533 }
1534
1535 /** Update a canvas note's size from its model note.
1536  *  @param ev Canvas note to update.
1537  *  @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1538  */
1539 void
1540 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1541 {
1542         boost::shared_ptr<NoteType> note = ev->note();
1543
1544         const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1545         const double y1 = midi_stream_view()->note_to_y(note->note());
1546
1547         ev->property_x1() = x;
1548         ev->property_y1() = y1;
1549
1550         /* trim note display to not overlap the end of its region */
1551
1552         if (note->length() > 0) {
1553                 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1554                 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1555         } else {
1556                 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1557         }
1558
1559         ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1560
1561         if (note->length() == 0) {
1562                 if (_active_notes) {
1563                         assert(note->note() < 128);
1564                         // If this note is already active there's a stuck note,
1565                         // finish the old note rectangle
1566                         if (_active_notes[note->note()]) {
1567                                 CanvasNote* const old_rect = _active_notes[note->note()];
1568                                 boost::shared_ptr<NoteType> old_note = old_rect->note();
1569                                 old_rect->property_x2() = x;
1570                                 old_rect->property_outline_what() = (guint32) 0xF;
1571                         }
1572                         _active_notes[note->note()] = ev;
1573                 }
1574                 /* outline all but right edge */
1575                 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1576         } else {
1577                 /* outline all edges */
1578                 ev->property_outline_what() = (guint32) 0xF;
1579         }
1580
1581         if (update_ghost_regions) {
1582                 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1583                         MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1584                         if (gr) {
1585                                 gr->update_note (ev);
1586                         }
1587                 }
1588         }
1589 }
1590
1591 double
1592 MidiRegionView::update_hit (CanvasHit* ev)
1593 {
1594         boost::shared_ptr<NoteType> note = ev->note();
1595
1596         const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1597         const double x = trackview.editor().frame_to_pixel(note_start_frames);
1598         const double diamond_size = midi_stream_view()->note_height() / 2.0;
1599         const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1600
1601         ev->move_to (x, y);
1602
1603         return diamond_size;
1604 }
1605
1606 /** Add a MIDI note to the view (with length).
1607  *
1608  * If in sustained mode, notes with length 0 will be considered active
1609  * notes, and resolve_note should be called when the corresponding note off
1610  * event arrives, to properly display the note.
1611  */
1612 void
1613 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1614 {
1615         CanvasNoteEvent* event = 0;
1616
1617         assert(note->time() >= 0);
1618         assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1619
1620         //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1621
1622         if (midi_view()->note_mode() == Sustained) {
1623
1624                 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1625
1626                 update_note (ev_rect);
1627
1628                 event = ev_rect;
1629
1630                 MidiGhostRegion* gr;
1631
1632                 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1633                         if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1634                                 gr->add_note(ev_rect);
1635                         }
1636                 }
1637
1638         } else if (midi_view()->note_mode() == Percussive) {
1639
1640                 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1641
1642                 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1643
1644                 update_hit (ev_diamond);
1645
1646                 event = ev_diamond;
1647
1648         } else {
1649                 event = 0;
1650         }
1651
1652         if (event) {
1653                 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1654                         note_selected(event, true);
1655                 }
1656
1657                 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1658                         event->show_velocity();
1659                 }
1660
1661                 event->on_channel_selection_change(_last_channel_selection);
1662                 _events.push_back(event);
1663
1664                 if (visible) {
1665                         event->show();
1666                 } else {
1667                         event->hide ();
1668                 }
1669         }
1670
1671         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1672         MidiStreamView* const view = mtv->midi_view();
1673
1674         view->update_note_range (note->note());
1675 }
1676
1677 void
1678 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1679                                Evoral::MusicalTime pos, Evoral::MusicalTime len)
1680 {
1681         boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1682
1683         /* potentially extend region to hold new note */
1684
1685         framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1686         framepos_t region_end = _region->last_frame();
1687
1688         if (end_frame > region_end) {
1689                 _region->set_length (end_frame - _region->position());
1690         }
1691
1692         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1693         MidiStreamView* const view = mtv->midi_view();
1694
1695         view->update_note_range(new_note->note());
1696
1697         _marked_for_selection.clear ();
1698         clear_selection ();
1699
1700         start_note_diff_command (_("step add"));
1701         note_diff_add_note (new_note, true, false);
1702         apply_diff();
1703
1704         // last_step_edit_note = new_note;
1705 }
1706
1707 void
1708 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1709 {
1710         change_note_lengths (false, false, beats, false, true);
1711 }
1712
1713 void
1714 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1715 {
1716         assert (patch->time() >= 0);
1717
1718         const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (patch->time()));
1719
1720         double const height = midi_stream_view()->contents_height();
1721
1722         boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1723                 new CanvasPatchChange(*this, *_note_group,
1724                                       displaytext,
1725                                       height,
1726                                       x, 1.0,
1727                                       _model_name,
1728                                       _custom_device_mode,
1729                                       patch)
1730                           );
1731
1732         // Show unless patch change is beyond the region bounds
1733         if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) {
1734                 patch_change->hide();
1735         } else {
1736                 patch_change->show();
1737         }
1738
1739         _patch_changes.push_back (patch_change);
1740 }
1741
1742 void
1743 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1744 {
1745         MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1746         while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1747                 ++i;
1748         }
1749
1750         if (i != _model->patch_changes().end()) {
1751                 key.msb = (*i)->bank_msb ();
1752                 key.lsb = (*i)->bank_lsb ();
1753                 key.program_number = (*i)->program ();
1754         } else {
1755                 key.msb = key.lsb = key.program_number = 0;
1756         }
1757
1758         assert (key.is_sane());
1759 }
1760
1761
1762 void
1763 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1764 {
1765         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1766
1767         if (pc.patch()->program() != new_patch.program_number) {
1768                 c->change_program (pc.patch (), new_patch.program_number);
1769         }
1770
1771         int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1772         if (pc.patch()->bank() != new_bank) {
1773                 c->change_bank (pc.patch (), new_bank);
1774         }
1775
1776         _model->apply_command (*trackview.session(), c);
1777
1778         _patch_changes.clear ();
1779         display_patch_changes ();
1780 }
1781
1782 void
1783 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1784 {
1785         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1786
1787         if (old_change->time() != new_change.time()) {
1788                 c->change_time (old_change, new_change.time());
1789         }
1790
1791         if (old_change->channel() != new_change.channel()) {
1792                 c->change_channel (old_change, new_change.channel());
1793         }
1794
1795         if (old_change->program() != new_change.program()) {
1796                 c->change_program (old_change, new_change.program());
1797         }
1798
1799         if (old_change->bank() != new_change.bank()) {
1800                 c->change_bank (old_change, new_change.bank());
1801         }
1802
1803         _model->apply_command (*trackview.session(), c);
1804
1805         _patch_changes.clear ();
1806         display_patch_changes ();
1807 }
1808
1809 /** Add a patch change to the region.
1810  *  @param t Time in frames relative to region position
1811  *  @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1812  *  MidiTimeAxisView::get_channel_for_add())
1813  */
1814 void
1815 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1816 {
1817         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1818
1819         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1820         c->add (MidiModel::PatchChangePtr (
1821                         new Evoral::PatchChange<Evoral::MusicalTime> (
1822                                 absolute_frames_to_source_beats (_region->position() + t),
1823                                 mtv->get_channel_for_add(), patch.program(), patch.bank()
1824                                 )
1825                         )
1826                 );
1827
1828         _model->apply_command (*trackview.session(), c);
1829
1830         _patch_changes.clear ();
1831         display_patch_changes ();
1832 }
1833
1834 void
1835 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1836 {
1837         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1838         c->change_time (pc.patch (), t);
1839         _model->apply_command (*trackview.session(), c);
1840
1841         _patch_changes.clear ();
1842         display_patch_changes ();
1843 }
1844
1845 void
1846 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1847 {
1848         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1849         c->remove (pc->patch ());
1850         _model->apply_command (*trackview.session(), c);
1851
1852         _patch_changes.clear ();
1853         display_patch_changes ();
1854 }
1855
1856 void
1857 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1858 {
1859         if (patch.patch()->program() < 127) {
1860                 MIDI::Name::PatchPrimaryKey key;
1861                 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1862                 key.program_number++;
1863                 change_patch_change (patch, key);
1864         }
1865 }
1866
1867 void
1868 MidiRegionView::next_patch (CanvasPatchChange& patch)
1869 {
1870         if (patch.patch()->program() > 0) {
1871                 MIDI::Name::PatchPrimaryKey key;
1872                 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1873                 key.program_number--;
1874                 change_patch_change (patch, key);
1875         }
1876 }
1877
1878 void
1879 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1880 {
1881         if (patch.patch()->program() < 127) {
1882                 MIDI::Name::PatchPrimaryKey key;
1883                 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1884                 if (key.lsb > 0) {
1885                         key.lsb--;
1886                         change_patch_change (patch, key);
1887                 } else {
1888                         if (key.msb > 0) {
1889                                 key.lsb = 127;
1890                                 key.msb--;
1891                                 change_patch_change (patch, key);
1892                         }
1893                 }
1894         }
1895 }
1896
1897 void
1898 MidiRegionView::next_bank (CanvasPatchChange& patch)
1899 {
1900         if (patch.patch()->program() > 0) {
1901                 MIDI::Name::PatchPrimaryKey key;
1902                 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1903                 if (key.lsb < 127) {
1904                         key.lsb++;
1905                         change_patch_change (patch, key);
1906                 } else {
1907                         if (key.msb < 127) {
1908                                 key.lsb = 0;
1909                                 key.msb++;
1910                                 change_patch_change (patch, key);
1911                         }
1912                 }
1913         }
1914 }
1915
1916 void
1917 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1918 {
1919         if (_selection.empty()) {
1920                 return;
1921         }
1922
1923         _selection.erase (cne);
1924 }
1925
1926 void
1927 MidiRegionView::delete_selection()
1928 {
1929         if (_selection.empty()) {
1930                 return;
1931         }
1932
1933         start_note_diff_command (_("delete selection"));
1934
1935         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1936                 if ((*i)->selected()) {
1937                         _note_diff_command->remove((*i)->note());
1938                 }
1939         }
1940
1941         _selection.clear();
1942
1943         apply_diff ();
1944 }
1945
1946 void
1947 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1948 {
1949         start_note_diff_command (_("delete note"));
1950         _note_diff_command->remove (n);
1951         apply_diff ();
1952
1953         trackview.editor().verbose_cursor()->hide ();
1954 }
1955
1956 void
1957 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
1958 {
1959         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1960                 if ((*i) != ev) {
1961                         Selection::iterator tmp = i;
1962                         ++tmp;
1963
1964                         (*i)->set_selected (false);
1965                         (*i)->hide_velocity ();
1966                         _selection.erase (i);
1967                         
1968                         i = tmp;
1969                 } else {
1970                         ++i;
1971                 }
1972         }
1973
1974         /* this does not change the status of this regionview w.r.t the editor
1975            selection.
1976         */
1977
1978         if (signal) {
1979                 SelectionCleared (this); /* EMIT SIGNAL */
1980         }
1981 }
1982
1983 void
1984 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1985 {
1986         clear_selection_except (ev);
1987
1988         /* don't bother with checking to see if we should remove this
1989            regionview from the editor selection, since we're about to add
1990            another note, and thus put/keep this regionview in the editor
1991            selection anyway.
1992         */
1993
1994         if (!ev->selected()) {
1995                 add_to_selection (ev);
1996         }
1997 }
1998
1999 void
2000 MidiRegionView::select_all_notes ()
2001 {
2002         clear_selection ();
2003
2004         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2005                 add_to_selection (*i);
2006         }
2007 }
2008
2009 void
2010 MidiRegionView::select_range (framepos_t start, framepos_t end)
2011 {
2012         clear_selection ();
2013
2014         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2015                 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2016                 if (t >= start && t <= end) {
2017                         add_to_selection (*i);
2018                 }
2019         }
2020 }
2021
2022 void
2023 MidiRegionView::invert_selection ()
2024 {
2025         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2026                 if ((*i)->selected()) {
2027                         remove_from_selection(*i);
2028                 } else {
2029                         add_to_selection (*i);
2030                 }
2031         }
2032 }
2033
2034 void
2035 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2036 {
2037         uint8_t low_note = 127;
2038         uint8_t high_note = 0;
2039         MidiModel::Notes& notes (_model->notes());
2040         _optimization_iterator = _events.begin();
2041
2042         if (!add) {
2043                 clear_selection ();
2044         }
2045
2046         if (extend && _selection.empty()) {
2047                 extend = false;
2048         }
2049
2050         if (extend) {
2051
2052                 /* scan existing selection to get note range */
2053
2054                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2055                         if ((*i)->note()->note() < low_note) {
2056                                 low_note = (*i)->note()->note();
2057                         }
2058                         if ((*i)->note()->note() > high_note) {
2059                                 high_note = (*i)->note()->note();
2060                         }
2061                 }
2062
2063                 low_note = min (low_note, notenum);
2064                 high_note = max (high_note, notenum);
2065         }
2066
2067         _no_sound_notes = true;
2068
2069         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2070
2071                 boost::shared_ptr<NoteType> note (*n);
2072                 CanvasNoteEvent* cne;
2073                 bool select = false;
2074
2075                 if (((1 << note->channel()) & channel_mask) != 0) {
2076                         if (extend) {
2077                                 if ((note->note() >= low_note && note->note() <= high_note)) {
2078                                         select = true;
2079                                 }
2080                         } else if (note->note() == notenum) {
2081                                 select = true;
2082                         }
2083                 }
2084
2085                 if (select) {
2086                         if ((cne = find_canvas_note (note)) != 0) {
2087                                 // extend is false because we've taken care of it,
2088                                 // since it extends by time range, not pitch.
2089                                 note_selected (cne, add, false);
2090                         }
2091                 }
2092
2093                 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2094
2095         }
2096
2097         _no_sound_notes = false;
2098 }
2099
2100 void
2101 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2102 {
2103         MidiModel::Notes& notes (_model->notes());
2104         _optimization_iterator = _events.begin();
2105
2106         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2107
2108                 boost::shared_ptr<NoteType> note (*n);
2109                 CanvasNoteEvent* cne;
2110
2111                 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2112                         if ((cne = find_canvas_note (note)) != 0) {
2113                                 if (cne->selected()) {
2114                                         note_deselected (cne);
2115                                 } else {
2116                                         note_selected (cne, true, false);
2117                                 }
2118                         }
2119                 }
2120         }
2121 }
2122
2123 void
2124 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2125 {
2126         if (!add) {
2127                 clear_selection_except (ev);
2128                 if (!_selection.empty()) {
2129                         PublicEditor& editor (trackview.editor());
2130                         editor.get_selection().add (this);
2131                 }
2132         }
2133
2134         if (!extend) {
2135
2136                 if (!ev->selected()) {
2137                         add_to_selection (ev);
2138                 }
2139
2140         } else {
2141                 /* find end of latest note selected, select all between that and the start of "ev" */
2142
2143                 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2144                 Evoral::MusicalTime latest = 0;
2145
2146                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2147                         if ((*i)->note()->end_time() > latest) {
2148                                 latest = (*i)->note()->end_time();
2149                         }
2150                         if ((*i)->note()->time() < earliest) {
2151                                 earliest = (*i)->note()->time();
2152                         }
2153                 }
2154
2155                 if (ev->note()->end_time() > latest) {
2156                         latest = ev->note()->end_time();
2157                 }
2158
2159                 if (ev->note()->time() < earliest) {
2160                         earliest = ev->note()->time();
2161                 }
2162
2163                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2164
2165                         /* find notes entirely within OR spanning the earliest..latest range */
2166
2167                         if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2168                             ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2169                                 add_to_selection (*i);
2170                         }
2171
2172                 }
2173         }
2174 }
2175
2176 void
2177 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2178 {
2179         remove_from_selection (ev);
2180 }
2181
2182 void
2183 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2184 {
2185         if (x1 > x2) {
2186                 swap (x1, x2);
2187         }
2188
2189         if (y1 > y2) {
2190                 swap (y1, y2);
2191         }
2192
2193         // TODO: Make this faster by storing the last updated selection rect, and only
2194         // adjusting things that are in the area that appears/disappeared.
2195         // We probably need a tree to be able to find events in O(log(n)) time.
2196
2197         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2198
2199                 /* check if any corner of the note is inside the rect
2200
2201                    Notes:
2202                    1) this is computing "touched by", not "contained by" the rect.
2203                    2) this does not require that events be sorted in time.
2204                 */
2205
2206                 const double ix1 = (*i)->x1();
2207                 const double ix2 = (*i)->x2();
2208                 const double iy1 = (*i)->y1();
2209                 const double iy2 = (*i)->y2();
2210
2211                 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2212                     (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2213                     (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2214                     (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2215
2216                         // Inside rectangle
2217                         if (!(*i)->selected()) {
2218                                 add_to_selection (*i);
2219                         }
2220                 } else if ((*i)->selected() && !extend) {
2221                         // Not inside rectangle
2222                         remove_from_selection (*i);
2223                 }
2224         }
2225 }
2226
2227 void
2228 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2229 {
2230         Selection::iterator i = _selection.find (ev);
2231
2232         if (i != _selection.end()) {
2233                 _selection.erase (i);
2234         }
2235
2236         ev->set_selected (false);
2237         ev->hide_velocity ();
2238
2239         if (_selection.empty()) {
2240                 PublicEditor& editor (trackview.editor());
2241                 editor.get_selection().remove (this);
2242         }
2243 }
2244
2245 void
2246 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2247 {
2248         bool add_mrv_selection = false;
2249
2250         if (_selection.empty()) {
2251                 add_mrv_selection = true;
2252         }
2253
2254         if (_selection.insert (ev).second) {
2255                 ev->set_selected (true);
2256                 play_midi_note ((ev)->note());
2257         }
2258
2259         if (add_mrv_selection) {
2260                 PublicEditor& editor (trackview.editor());
2261                 editor.get_selection().add (this);
2262         }
2263 }
2264
2265 void
2266 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2267 {
2268         typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2269         PossibleChord to_play;
2270         Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2271
2272         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2273                 if ((*i)->note()->time() < earliest) {
2274                         earliest = (*i)->note()->time();
2275                 }
2276         }
2277
2278         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2279                 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2280                         to_play.push_back ((*i)->note());
2281                 }
2282                 (*i)->move_event(dx, dy);
2283         }
2284
2285         if (dy && !_selection.empty() && !_no_sound_notes && trackview.editor().sound_notes()) {
2286
2287                 if (to_play.size() > 1) {
2288
2289                         PossibleChord shifted;
2290
2291                         for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2292                                 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2293                                 moved_note->set_note (moved_note->note() + cumulative_dy);
2294                                 shifted.push_back (moved_note);
2295                         }
2296
2297                         play_midi_chord (shifted);
2298
2299                 } else if (!to_play.empty()) {
2300
2301                         boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2302                         moved_note->set_note (moved_note->note() + cumulative_dy);
2303                         play_midi_note (moved_note);
2304                 }
2305         }
2306 }
2307
2308 void
2309 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2310 {
2311         assert (!_selection.empty());
2312
2313         uint8_t lowest_note_in_selection  = 127;
2314         uint8_t highest_note_in_selection = 0;
2315         uint8_t highest_note_difference = 0;
2316
2317         // find highest and lowest notes first
2318
2319         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2320                 uint8_t pitch = (*i)->note()->note();
2321                 lowest_note_in_selection  = std::min(lowest_note_in_selection,  pitch);
2322                 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2323         }
2324
2325         /*
2326           cerr << "dnote: " << (int) dnote << endl;
2327           cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2328           << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2329           cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2330           << int(highest_note_in_selection) << endl;
2331           cerr << "selection size: " << _selection.size() << endl;
2332           cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2333         */
2334
2335         // Make sure the note pitch does not exceed the MIDI standard range
2336         if (highest_note_in_selection + dnote > 127) {
2337                 highest_note_difference = highest_note_in_selection - 127;
2338         }
2339
2340         start_note_diff_command (_("move notes"));
2341
2342         for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2343
2344                 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (source_beats_to_absolute_frames ((*i)->note()->time()) + dt);
2345
2346                 if (new_time < 0) {
2347                         continue;
2348                 }
2349
2350                 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2351
2352                 uint8_t original_pitch = (*i)->note()->note();
2353                 uint8_t new_pitch      = original_pitch + dnote - highest_note_difference;
2354
2355                 // keep notes in standard midi range
2356                 clamp_to_0_127(new_pitch);
2357
2358                 lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
2359                 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2360
2361                 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2362         }
2363
2364         apply_diff();
2365
2366         // care about notes being moved beyond the upper/lower bounds on the canvas
2367         if (lowest_note_in_selection  < midi_stream_view()->lowest_note() ||
2368             highest_note_in_selection > midi_stream_view()->highest_note()) {
2369                 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2370         }
2371 }
2372
2373 /** @param x Pixel relative to the region position.
2374  *  @return Snapped frame relative to the region position.
2375  */
2376 framepos_t
2377 MidiRegionView::snap_pixel_to_frame(double x)
2378 {
2379         PublicEditor& editor (trackview.editor());
2380         return snap_frame_to_frame (editor.pixel_to_frame (x));
2381 }
2382
2383 /** @param x Pixel relative to the region position.
2384  *  @return Snapped pixel relative to the region position.
2385  */
2386 double
2387 MidiRegionView::snap_to_pixel(double x)
2388 {
2389         return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2390 }
2391
2392 double
2393 MidiRegionView::get_position_pixels()
2394 {
2395         framepos_t region_frame = get_position();
2396         return trackview.editor().frame_to_pixel(region_frame);
2397 }
2398
2399 double
2400 MidiRegionView::get_end_position_pixels()
2401 {
2402         framepos_t frame = get_position() + get_duration ();
2403         return trackview.editor().frame_to_pixel(frame);
2404 }
2405
2406 framepos_t
2407 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2408 {
2409         /* the time converter will return the frame corresponding to `beats'
2410            relative to the start of the source. The start of the source
2411            is an implied position given by region->position - region->start
2412         */
2413         const framepos_t source_start = _region->position() - _region->start();
2414         return  source_start +  _source_relative_time_converter.to (beats);
2415 }
2416
2417 double
2418 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2419 {
2420         /* the `frames' argument needs to be converted into a frame count
2421            relative to the start of the source before being passed in to the
2422            converter.
2423         */
2424         const framepos_t source_start = _region->position() - _region->start();
2425         return  _source_relative_time_converter.from (frames - source_start);
2426 }
2427
2428 framepos_t
2429 MidiRegionView::region_beats_to_region_frames(double beats) const
2430 {
2431         return _region_relative_time_converter.to(beats);
2432 }
2433
2434 double
2435 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2436 {
2437         return _region_relative_time_converter.from(frames);
2438 }
2439
2440 void
2441 MidiRegionView::begin_resizing (bool /*at_front*/)
2442 {
2443         _resize_data.clear();
2444
2445         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2446                 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2447
2448                 // only insert CanvasNotes into the map
2449                 if (note) {
2450                         NoteResizeData *resize_data = new NoteResizeData();
2451                         resize_data->canvas_note = note;
2452
2453                         // create a new SimpleRect from the note which will be the resize preview
2454                         SimpleRect *resize_rect = new SimpleRect(
2455                                 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2456
2457                         // calculate the colors: get the color settings
2458                         uint32_t fill_color = UINT_RGBA_CHANGE_A(
2459                                 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2460                                 128);
2461
2462                         // make the resize preview notes more transparent and bright
2463                         fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2464
2465                         // calculate color based on note velocity
2466                         resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2467                                 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2468                                 fill_color,
2469                                 0.85);
2470
2471                         resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2472                                 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2473
2474                         resize_data->resize_rect = resize_rect;
2475                         _resize_data.push_back(resize_data);
2476                 }
2477         }
2478 }
2479
2480 /** Update resizing notes while user drags.
2481  * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2482  * @param at_front which end of the note (true == note on, false == note off)
2483  * @param delta_x change in mouse position since the start of the drag
2484  * @param relative true if relative resizing is taking place, false if absolute resizing.  This only makes
2485  * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2486  * amount of the drag.  In non-relative mode, all selected notes are set to have the same start or end point
2487  * as the \a primary note.
2488  */
2489 void
2490 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2491 {
2492         bool cursor_set = false;
2493
2494         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2495                 SimpleRect* resize_rect = (*i)->resize_rect;
2496                 CanvasNote* canvas_note = (*i)->canvas_note;
2497                 double current_x;
2498
2499                 if (at_front) {
2500                         if (relative) {
2501                                 current_x = canvas_note->x1() + delta_x;
2502                         } else {
2503                                 current_x = primary->x1() + delta_x;
2504                         }
2505                 } else {
2506                         if (relative) {
2507                                 current_x = canvas_note->x2() + delta_x;
2508                         } else {
2509                                 current_x = primary->x2() + delta_x;
2510                         }
2511                 }
2512
2513                 if (at_front) {
2514                         resize_rect->property_x1() = snap_to_pixel(current_x);
2515                         resize_rect->property_x2() = canvas_note->x2();
2516                 } else {
2517                         resize_rect->property_x2() = snap_to_pixel(current_x);
2518                         resize_rect->property_x1() = canvas_note->x1();
2519                 }
2520
2521                 if (!cursor_set) {
2522                         double beats;
2523
2524                         beats = snap_pixel_to_frame (current_x);
2525                         beats = region_frames_to_region_beats (beats);
2526
2527                         double len;
2528
2529                         if (at_front) {
2530                                 if (beats < canvas_note->note()->end_time()) {
2531                                         len = canvas_note->note()->time() - beats;
2532                                         len += canvas_note->note()->length();
2533                                 } else {
2534                                         len = 0;
2535                                 }
2536                         } else {
2537                                 if (beats >= canvas_note->note()->time()) {
2538                                         len = beats - canvas_note->note()->time();
2539                                 } else {
2540                                         len = 0;
2541                                 }
2542                         }
2543
2544                         char buf[16];
2545                         snprintf (buf, sizeof (buf), "%.3g beats", len);
2546                         show_verbose_cursor (buf, 0, 0);
2547
2548                         cursor_set = true;
2549                 }
2550
2551         }
2552 }
2553
2554
2555 /** Finish resizing notes when the user releases the mouse button.
2556  *  Parameters the same as for \a update_resizing().
2557  */
2558 void
2559 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2560 {
2561         start_note_diff_command (_("resize notes"));
2562
2563         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2564                 CanvasNote*  canvas_note = (*i)->canvas_note;
2565                 SimpleRect*  resize_rect = (*i)->resize_rect;
2566
2567                 /* Get the new x position for this resize, which is in pixels relative
2568                  * to the region position.
2569                  */
2570                 
2571                 double current_x;
2572
2573                 if (at_front) {
2574                         if (relative) {
2575                                 current_x = canvas_note->x1() + delta_x;
2576                         } else {
2577                                 current_x = primary->x1() + delta_x;
2578                         }
2579                 } else {
2580                         if (relative) {
2581                                 current_x = canvas_note->x2() + delta_x;
2582                         } else {
2583                                 current_x = primary->x2() + delta_x;
2584                         }
2585                 }
2586
2587                 /* Convert that to a frame within the source */
2588                 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2589
2590                 /* and then to beats */
2591                 current_x = region_frames_to_region_beats (current_x);
2592
2593                 if (at_front && current_x < canvas_note->note()->end_time()) {
2594                         note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2595
2596                         double len = canvas_note->note()->time() - current_x;
2597                         len += canvas_note->note()->length();
2598
2599                         if (len > 0) {
2600                                 /* XXX convert to beats */
2601                                 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2602                         }
2603                 }
2604
2605                 if (!at_front) {
2606                         double len = current_x - canvas_note->note()->time();
2607
2608                         if (len > 0) {
2609                                 /* XXX convert to beats */
2610                                 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2611                         }
2612                 }
2613
2614                 delete resize_rect;
2615                 delete (*i);
2616         }
2617
2618         _resize_data.clear();
2619         apply_diff();
2620 }
2621
2622 void
2623 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2624 {
2625         uint8_t new_velocity;
2626
2627         if (relative) {
2628                 new_velocity = event->note()->velocity() + velocity;
2629                 clamp_to_0_127(new_velocity);
2630         } else {
2631                 new_velocity = velocity;
2632         }
2633
2634         event->set_selected (event->selected()); // change color
2635
2636         note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2637 }
2638
2639 void
2640 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2641 {
2642         uint8_t new_note;
2643
2644         if (relative) {
2645                 new_note = event->note()->note() + note;
2646         } else {
2647                 new_note = note;
2648         }
2649
2650         clamp_to_0_127 (new_note);
2651         note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2652 }
2653
2654 void
2655 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2656 {
2657         bool change_start = false;
2658         bool change_length = false;
2659         Evoral::MusicalTime new_start = 0;
2660         Evoral::MusicalTime new_length = 0;
2661
2662         /* NOTE: the semantics of the two delta arguments are slightly subtle:
2663
2664            front_delta: if positive - move the start of the note later in time (shortening it)
2665            if negative - move the start of the note earlier in time (lengthening it)
2666
2667            end_delta:   if positive - move the end of the note later in time (lengthening it)
2668            if negative - move the end of the note earlier in time (shortening it)
2669         */
2670
2671         if (front_delta) {
2672                 if (front_delta < 0) {
2673
2674                         if (event->note()->time() < -front_delta) {
2675                                 new_start = 0;
2676                         } else {
2677                                 new_start = event->note()->time() + front_delta; // moves earlier
2678                         }
2679
2680                         /* start moved toward zero, so move the end point out to where it used to be.
2681                            Note that front_delta is negative, so this increases the length.
2682                         */
2683
2684                         new_length = event->note()->length() - front_delta;
2685                         change_start = true;
2686                         change_length = true;
2687
2688                 } else {
2689
2690                         Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2691
2692                         if (new_pos < event->note()->end_time()) {
2693                                 new_start = event->note()->time() + front_delta;
2694                                 /* start moved toward the end, so move the end point back to where it used to be */
2695                                 new_length = event->note()->length() - front_delta;
2696                                 change_start = true;
2697                                 change_length = true;
2698                         }
2699                 }
2700
2701         }
2702
2703         if (end_delta) {
2704                 bool can_change = true;
2705                 if (end_delta < 0) {
2706                         if (event->note()->length() < -end_delta) {
2707                                 can_change = false;
2708                         }
2709                 }
2710
2711                 if (can_change) {
2712                         new_length = event->note()->length() + end_delta;
2713                         change_length = true;
2714                 }
2715         }
2716
2717         if (change_start) {
2718                 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2719         }
2720
2721         if (change_length) {
2722                 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2723         }
2724 }
2725
2726 void
2727 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2728 {
2729         uint8_t new_channel;
2730
2731         if (relative) {
2732                 if (chn < 0.0) {
2733                         if (event->note()->channel() < -chn) {
2734                                 new_channel = 0;
2735                         } else {
2736                                 new_channel = event->note()->channel() + chn;
2737                         }
2738                 } else {
2739                         new_channel = event->note()->channel() + chn;
2740                 }
2741         } else {
2742                 new_channel = (uint8_t) chn;
2743         }
2744
2745         note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2746 }
2747
2748 void
2749 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2750 {
2751         Evoral::MusicalTime new_time;
2752
2753         if (relative) {
2754                 if (delta < 0.0) {
2755                         if (event->note()->time() < -delta) {
2756                                 new_time = 0;
2757                         } else {
2758                                 new_time = event->note()->time() + delta;
2759                         }
2760                 } else {
2761                         new_time = event->note()->time() + delta;
2762                 }
2763         } else {
2764                 new_time = delta;
2765         }
2766
2767         note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2768 }
2769
2770 void
2771 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2772 {
2773         note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2774 }
2775
2776 void
2777 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2778 {
2779         int8_t delta;
2780
2781         if (_selection.empty()) {
2782                 return;
2783         }
2784
2785         if (fine) {
2786                 delta = 1;
2787         } else {
2788                 delta = 10;
2789         }
2790
2791         if (!up) {
2792                 delta = -delta;
2793         }
2794
2795         if (!allow_smush) {
2796                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2797                         if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2798                                 return;
2799                         }
2800                 }
2801         }
2802
2803         start_note_diff_command (_("change velocities"));
2804
2805         for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2806                 Selection::iterator next = i;
2807                 ++next;
2808                 change_note_velocity (*i, delta, true);
2809                 i = next;
2810         }
2811
2812         apply_diff();
2813
2814         if (!_selection.empty()) {
2815                 char buf[24];
2816                 snprintf (buf, sizeof (buf), "Vel %d",
2817                           (int) (*_selection.begin())->note()->velocity());
2818                 show_verbose_cursor (buf, 10, 10);
2819         }
2820 }
2821
2822
2823 void
2824 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2825 {
2826         if (_selection.empty()) {
2827                 return;
2828         }
2829
2830         int8_t delta;
2831
2832         if (fine) {
2833                 delta = 1;
2834         } else {
2835                 delta = 12;
2836         }
2837
2838         if (!up) {
2839                 delta = -delta;
2840         }
2841
2842         if (!allow_smush) {
2843                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2844                         if (!up) {
2845                                 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2846                                         return;
2847                                 }
2848                         } else {
2849                                 if ((int8_t) (*i)->note()->note() + delta > 127) {
2850                                         return;
2851                                 }
2852                         }
2853                 }
2854         }
2855
2856         start_note_diff_command (_("transpose"));
2857
2858         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2859                 Selection::iterator next = i;
2860                 ++next;
2861                 change_note_note (*i, delta, true);
2862                 i = next;
2863         }
2864
2865         apply_diff ();
2866 }
2867
2868 void
2869 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2870 {
2871         if (delta == 0.0) {
2872                 if (fine) {
2873                         delta = 1.0/128.0;
2874                 } else {
2875                         /* grab the current grid distance */
2876                         bool success;
2877                         delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2878                         if (!success) {
2879                                 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2880                                 cerr << "Grid type not available as beats - TO BE FIXED\n";
2881                                 return;
2882                         }
2883                 }
2884         }
2885
2886         if (shorter) {
2887                 delta = -delta;
2888         }
2889
2890         start_note_diff_command (_("change note lengths"));
2891
2892         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2893                 Selection::iterator next = i;
2894                 ++next;
2895
2896                 /* note the negation of the delta for start */
2897
2898                 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2899                 i = next;
2900         }
2901
2902         apply_diff ();
2903
2904 }
2905
2906 void
2907 MidiRegionView::nudge_notes (bool forward)
2908 {
2909         if (_selection.empty()) {
2910                 return;
2911         }
2912
2913         /* pick a note as the point along the timeline to get the nudge distance.
2914            its not necessarily the earliest note, so we may want to pull the notes out
2915            into a vector and sort before using the first one.
2916         */
2917
2918         framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2919         framepos_t unused;
2920         framepos_t distance;
2921
2922         if (trackview.editor().snap_mode() == Editing::SnapOff) {
2923
2924                 /* grid is off - use nudge distance */
2925
2926                 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2927
2928         } else {
2929
2930                 /* use grid */
2931
2932                 framepos_t next_pos = ref_point;
2933
2934                 if (forward) {
2935                         if (max_framepos - 1 < next_pos) {
2936                                 next_pos += 1;
2937                         }
2938                 } else {
2939                         if (next_pos == 0) {
2940                                 return;
2941                         }
2942                         next_pos -= 1;
2943                 }
2944
2945                 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2946                 distance = ref_point - next_pos;
2947         }
2948
2949         if (distance == 0) {
2950                 return;
2951         }
2952
2953         Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2954
2955         if (!forward) {
2956                 delta = -delta;
2957         }
2958
2959         start_note_diff_command (_("nudge"));
2960
2961         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2962                 Selection::iterator next = i;
2963                 ++next;
2964                 change_note_time (*i, delta, true);
2965                 i = next;
2966         }
2967
2968         apply_diff ();
2969 }
2970
2971 void
2972 MidiRegionView::change_channel(uint8_t channel)
2973 {
2974         start_note_diff_command(_("change channel"));
2975         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2976                 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2977         }
2978
2979         apply_diff();
2980 }
2981
2982
2983 void
2984 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2985 {
2986         Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2987
2988         _pre_enter_cursor = editor->get_canvas_cursor ();
2989
2990         if (_mouse_state == SelectTouchDragging) {
2991                 note_selected (ev, true);
2992         }
2993
2994         show_verbose_cursor (ev->note ());
2995 }
2996
2997 void
2998 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2999 {
3000         Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3001
3002         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3003                 (*i)->hide_velocity ();
3004         }
3005
3006         editor->verbose_cursor()->hide ();
3007
3008         if (_pre_enter_cursor) {
3009                 editor->set_canvas_cursor (_pre_enter_cursor);
3010                 _pre_enter_cursor = 0;
3011         }
3012 }
3013
3014 void
3015 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3016 {
3017         ostringstream s;
3018         /* XXX should get patch name if we can */
3019         s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3020         show_verbose_cursor (s.str(), 10, 20);
3021 }
3022
3023 void
3024 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3025 {
3026         trackview.editor().verbose_cursor()->hide ();
3027 }
3028
3029 void
3030 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3031 {
3032         Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3033
3034         if (x_fraction > 0.0 && x_fraction < 0.25) {
3035                 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3036         } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
3037                 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3038         } else {
3039                 if (_pre_enter_cursor && can_set_cursor) {
3040                         editor->set_canvas_cursor (_pre_enter_cursor);
3041                 }
3042         }
3043 }
3044
3045 void
3046 MidiRegionView::set_frame_color()
3047 {
3048         uint32_t f;
3049
3050         TimeAxisViewItem::set_frame_color ();
3051
3052         if (!frame) {
3053                 return;
3054         }
3055
3056         if (_selected) {
3057                 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3058         } else if (high_enough_for_name) {
3059                 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3060         } else {
3061                 f = fill_color;
3062         }
3063
3064         if (!rect_visible) {
3065                 f = UINT_RGBA_CHANGE_A (f, 0);
3066         }
3067
3068         frame->property_fill_color_rgba() = f;
3069 }
3070
3071 void
3072 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3073 {
3074         switch (mode) {
3075         case AllChannels:
3076         case FilterChannels:
3077                 _force_channel = -1;
3078                 break;
3079         case ForceChannel:
3080                 _force_channel = mask;
3081                 mask = 0xFFFF; // Show all notes as active (below)
3082         };
3083
3084         // Update notes for selection
3085         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3086                 (*i)->on_channel_selection_change(mask);
3087         }
3088
3089         _last_channel_selection = mask;
3090
3091         _patch_changes.clear ();
3092         display_patch_changes ();
3093 }
3094
3095 void
3096 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3097 {
3098         _model_name         = model;
3099         _custom_device_mode = custom_device_mode;
3100         redisplay_model();
3101 }
3102
3103 void
3104 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3105 {
3106         if (_selection.empty()) {
3107                 return;
3108         }
3109
3110         PublicEditor& editor (trackview.editor());
3111
3112         switch (op) {
3113         case Delete:
3114                 /* XXX what to do ? */
3115                 break;
3116         case Cut:
3117         case Copy:
3118                 editor.get_cut_buffer().add (selection_as_cut_buffer());
3119                 break;
3120         default:
3121                 break;
3122         }
3123
3124         if (op != Copy) {
3125
3126                 start_note_diff_command();
3127
3128                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3129                         switch (op) {
3130                         case Copy:
3131                                 break;
3132                         case Delete:
3133                         case Cut:
3134                         case Clear:
3135                                 note_diff_remove_note (*i);
3136                                 break;
3137                         }
3138                 }
3139
3140                 apply_diff();
3141         }
3142 }
3143
3144 MidiCutBuffer*
3145 MidiRegionView::selection_as_cut_buffer () const
3146 {
3147         Notes notes;
3148
3149         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3150                 NoteType* n = (*i)->note().get();
3151                 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3152         }
3153
3154         MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3155         cb->set (notes);
3156
3157         return cb;
3158 }
3159
3160 /** This method handles undo */
3161 void
3162 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3163 {
3164         if (mcb.empty()) {
3165                 return;
3166         }
3167
3168         DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3169
3170         trackview.session()->begin_reversible_command (_("paste"));
3171
3172         start_note_diff_command (_("paste"));
3173
3174         Evoral::MusicalTime beat_delta;
3175         Evoral::MusicalTime paste_pos_beats;
3176         Evoral::MusicalTime duration;
3177         Evoral::MusicalTime end_point = 0;
3178
3179         duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3180         paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3181         beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3182         paste_pos_beats = 0;
3183
3184         DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6 ; beat delta = %7\n",
3185                                                        (*mcb.notes().begin())->time(),
3186                                                        (*mcb.notes().rbegin())->end_time(),
3187                                                        duration, pos, _region->position(),
3188                                                        paste_pos_beats, beat_delta));
3189
3190         clear_selection ();
3191
3192         for (int n = 0; n < (int) times; ++n) {
3193
3194                 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3195
3196                         boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3197                         copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3198
3199                         /* make all newly added notes selected */
3200
3201                         note_diff_add_note (copied_note, true);
3202                         end_point = copied_note->end_time();
3203                 }
3204
3205                 paste_pos_beats += duration;
3206         }
3207
3208         /* if we pasted past the current end of the region, extend the region */
3209
3210         framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3211         framepos_t region_end = _region->position() + _region->length() - 1;
3212
3213         if (end_frame > region_end) {
3214
3215                 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3216
3217                 _region->clear_changes ();
3218                 _region->set_length (end_frame);
3219                 trackview.session()->add_command (new StatefulDiffCommand (_region));
3220         }
3221
3222         apply_diff (true);
3223
3224         trackview.session()->commit_reversible_command ();
3225 }
3226
3227 struct EventNoteTimeEarlyFirstComparator {
3228         bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3229                 return a->note()->time() < b->note()->time();
3230         }
3231 };
3232
3233 void
3234 MidiRegionView::time_sort_events ()
3235 {
3236         if (!_sort_needed) {
3237                 return;
3238         }
3239
3240         EventNoteTimeEarlyFirstComparator cmp;
3241         _events.sort (cmp);
3242
3243         _sort_needed = false;
3244 }
3245
3246 void
3247 MidiRegionView::goto_next_note (bool add_to_selection)
3248 {
3249         bool use_next = false;
3250
3251         if (_events.back()->selected()) {
3252                 return;
3253         }
3254
3255         time_sort_events ();
3256
3257         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3258         uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3259
3260         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3261                 if ((*i)->selected()) {
3262                         use_next = true;
3263                         continue;
3264                 } else if (use_next) {
3265                         if (channel_mask & (1 << (*i)->note()->channel())) {
3266                                 if (!add_to_selection) {
3267                                         unique_select (*i);
3268                                 } else {
3269                                         note_selected (*i, true, false);
3270                                 }
3271                                 return;
3272                         }
3273                 }
3274         }
3275
3276         /* use the first one */
3277
3278         if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3279                 unique_select (_events.front());
3280         }
3281 }
3282
3283 void
3284 MidiRegionView::goto_previous_note (bool add_to_selection)
3285 {
3286         bool use_next = false;
3287
3288         if (_events.front()->selected()) {
3289                 return;
3290         }
3291
3292         time_sort_events ();
3293
3294         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3295         uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3296
3297         for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3298                 if ((*i)->selected()) {
3299                         use_next = true;
3300                         continue;
3301                 } else if (use_next) {
3302                         if (channel_mask & (1 << (*i)->note()->channel())) {
3303                                 if (!add_to_selection) {
3304                                         unique_select (*i);
3305                                 } else {
3306                                         note_selected (*i, true, false);
3307                                 }
3308                                 return;
3309                         }
3310                 }
3311         }
3312
3313         /* use the last one */
3314
3315         if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3316                 unique_select (*(_events.rbegin()));
3317         }
3318 }
3319
3320 void
3321 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3322 {
3323         bool had_selected = false;
3324
3325         time_sort_events ();
3326
3327         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3328                 if ((*i)->selected()) {
3329                         selected.insert ((*i)->note());
3330                         had_selected = true;
3331                 }
3332         }
3333
3334         if (allow_all_if_none_selected && !had_selected) {
3335                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3336                         selected.insert ((*i)->note());
3337                 }
3338         }
3339 }
3340
3341 void
3342 MidiRegionView::update_ghost_note (double x, double y)
3343 {
3344         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3345
3346         _last_ghost_x = x;
3347         _last_ghost_y = y;
3348
3349         _note_group->w2i (x, y);
3350
3351         PublicEditor& editor = trackview.editor ();
3352         
3353         framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3354         framecnt_t grid_frames;
3355         framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3356         
3357         /* use region_frames... because we are converting a delta within the region
3358         */
3359          
3360         double length = region_frames_to_region_beats (snap_frame_to_frame (f + grid_frames) - f);
3361
3362         /* note that this sets the time of the ghost note in beats relative to
3363            the start of the source; that is how all note times are stored.
3364         */
3365         _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3366         _ghost_note->note()->set_length (length);
3367         _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3368         _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3369
3370         /* the ghost note does not appear in ghost regions, so pass false in here */
3371         update_note (_ghost_note, false);
3372
3373         show_verbose_cursor (_ghost_note->note ());
3374 }
3375
3376 void
3377 MidiRegionView::create_ghost_note (double x, double y)
3378 {
3379         delete _ghost_note;
3380         _ghost_note = 0;
3381
3382         boost::shared_ptr<NoteType> g (new NoteType);
3383         _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3384         _ghost_note->property_outline_color_rgba() = 0x000000aa;
3385         update_ghost_note (x, y);
3386         _ghost_note->show ();
3387
3388         _last_ghost_x = x;
3389         _last_ghost_y = y;
3390
3391         show_verbose_cursor (_ghost_note->note ());
3392 }
3393
3394 void
3395 MidiRegionView::snap_changed ()
3396 {
3397         if (!_ghost_note) {
3398                 return;
3399         }
3400
3401         create_ghost_note (_last_ghost_x, _last_ghost_y);
3402 }
3403
3404 void
3405 MidiRegionView::drop_down_keys ()
3406 {
3407         _mouse_state = None;
3408 }
3409
3410 void
3411 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3412 {
3413         double note = midi_stream_view()->y_to_note(y);
3414         Events e;
3415         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3416
3417         uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3418
3419         if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3420                 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3421         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3422                 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3423         } else {
3424                 return;
3425         }
3426
3427         bool add_mrv_selection = false;
3428
3429         if (_selection.empty()) {
3430                 add_mrv_selection = true;
3431         }
3432
3433         for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3434                 if (_selection.insert (*i).second) {
3435                         (*i)->set_selected (true);
3436                 }
3437         }
3438
3439         if (add_mrv_selection) {
3440                 PublicEditor& editor (trackview.editor());
3441                 editor.get_selection().add (this);
3442         }
3443 }
3444
3445 void
3446 MidiRegionView::color_handler ()
3447 {
3448         RegionView::color_handler ();
3449
3450         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3451                 (*i)->set_selected ((*i)->selected()); // will change color
3452         }
3453
3454         /* XXX probably more to do here */
3455 }
3456
3457 void
3458 MidiRegionView::enable_display (bool yn)
3459 {
3460         RegionView::enable_display (yn);
3461         if (yn) {
3462                 redisplay_model ();
3463         }
3464 }
3465
3466 void
3467 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3468 {
3469         if (_step_edit_cursor == 0) {
3470                 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3471
3472                 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3473                 _step_edit_cursor->property_y1() = 0;
3474                 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3475                 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3476                 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3477         }
3478
3479         move_step_edit_cursor (pos);
3480         _step_edit_cursor->show ();
3481 }
3482
3483 void
3484 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3485 {
3486         _step_edit_cursor_position = pos;
3487
3488         if (_step_edit_cursor) {
3489                 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3490                 _step_edit_cursor->property_x1() = pixel;
3491                 set_step_edit_cursor_width (_step_edit_cursor_width);
3492         }
3493 }
3494
3495 void
3496 MidiRegionView::hide_step_edit_cursor ()
3497 {
3498         if (_step_edit_cursor) {
3499                 _step_edit_cursor->hide ();
3500         }
3501 }
3502
3503 void
3504 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3505 {
3506         _step_edit_cursor_width = beats;
3507
3508         if (_step_edit_cursor) {
3509                 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3510         }
3511 }
3512
3513 /** Called when a diskstream on our track has received some data.  Update the view, if applicable.
3514  *  @param w Source that the data will end up in.
3515  */
3516 void
3517 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3518 {
3519         if (!_active_notes) {
3520                 /* we aren't actively being recorded to */
3521                 return;
3522         }
3523
3524         boost::shared_ptr<MidiSource> src = w.lock ();
3525         if (!src || src != midi_region()->midi_source()) {
3526                 /* recorded data was not destined for our source */
3527                 return;
3528         }
3529
3530         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3531
3532         boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3533
3534         BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3535
3536         framepos_t back = max_framepos;
3537
3538         for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3539                 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3540                 assert (ev.buffer ());
3541
3542                 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3543
3544                 if (ev.type() == MIDI_CMD_NOTE_ON) {
3545
3546                         boost::shared_ptr<NoteType> note (
3547                                 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3548                                                           );
3549
3550                         add_note (note, true);
3551
3552                         /* fix up our note range */
3553                         if (ev.note() < _current_range_min) {
3554                                 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3555                         } else if (ev.note() > _current_range_max) {
3556                                 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3557                         }
3558
3559                 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3560                         resolve_note (ev.note (), time_beats);
3561                 }
3562
3563                 back = ev.time ();
3564         }
3565
3566         midi_stream_view()->check_record_layers (region(), back);
3567 }
3568
3569 void
3570 MidiRegionView::trim_front_starting ()
3571 {
3572         /* Reparent the note group to the region view's parent, so that it doesn't change
3573            when the region view is trimmed.
3574         */
3575         _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3576         _temporary_note_group->move (group->property_x(), group->property_y());
3577         _note_group->reparent (*_temporary_note_group);
3578 }
3579
3580 void
3581 MidiRegionView::trim_front_ending ()
3582 {
3583         _note_group->reparent (*group);
3584         delete _temporary_note_group;
3585         _temporary_note_group = 0;
3586
3587         if (_region->start() < 0) {
3588                 /* Trim drag made start time -ve; fix this */
3589                 midi_region()->fix_negative_start ();
3590         }
3591 }
3592
3593 void
3594 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3595 {
3596         PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3597         if (d.run () != Gtk::RESPONSE_ACCEPT) {
3598                 return;
3599         }
3600
3601         change_patch_change (pc->patch(), d.patch ());
3602 }
3603
3604
3605 void
3606 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3607 {
3608         char buf[24];
3609         snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3610                   Evoral::midi_note_name (n->note()).c_str(),
3611                   (int) n->note (),
3612                   (int) n->channel() + 1,
3613                   (int) n->velocity());
3614
3615         show_verbose_cursor (buf, 10, 20);
3616 }
3617
3618 void
3619 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3620 {
3621         double wx, wy;
3622
3623         trackview.editor().get_pointer_position (wx, wy);
3624
3625         wx += xoffset;
3626         wy += yoffset;
3627
3628         /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3629
3630         double x1, y1, x2, y2;
3631         trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3632
3633         if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3634                 wy -= (y2 - y1) + 2 * yoffset;
3635         }
3636
3637         trackview.editor().verbose_cursor()->set (text, wx, wy);
3638         trackview.editor().verbose_cursor()->show ();
3639 }
3640
3641 /** @param p A session framepos.
3642  *  @param grid_frames Filled in with the number of frames that a grid interval is at p.
3643  *  @return p snapped to the grid subdivision underneath it.
3644  */
3645 framepos_t
3646 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3647 {
3648         PublicEditor& editor = trackview.editor ();
3649         
3650         bool success;
3651         Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3652
3653         if (!success) {
3654                 grid_beats = 1;
3655         }
3656         
3657         grid_frames = region_beats_to_region_frames (grid_beats);
3658
3659         /* Hack so that we always snap to the note that we are over, instead of snapping
3660            to the next one if we're more than halfway through the one we're over.
3661         */
3662         if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3663                 p -= grid_frames / 2;
3664         }
3665
3666         return snap_frame_to_frame (p);
3667 }
3668
3669 /** Called when the selection has been cleared in any MidiRegionView.
3670  *  @param rv MidiRegionView that the selection was cleared in.
3671  */
3672 void
3673 MidiRegionView::selection_cleared (MidiRegionView* rv)
3674 {
3675         if (rv == this) {
3676                 return;
3677         }
3678
3679         /* Clear our selection in sympathy; but don't signal the fact */
3680         clear_selection (false);
3681 }