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