+MidiRegionView::drop_down_keys ()
+{
+ _mouse_state = None;
+}
+
+void
+MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
+{
+ double note = midi_stream_view()->y_to_note(y);
+ Events e;
+ MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+
+ uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
+
+ if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
+ get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
+ } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
+ get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
+ } else {
+ return;
+ }
+
+ bool add_mrv_selection = false;
+
+ if (_selection.empty()) {
+ add_mrv_selection = true;
+ }
+
+ for (Events::iterator i = e.begin(); i != e.end(); ++i) {
+ if (_selection.insert (*i).second) {
+ (*i)->set_selected (true);
+ }
+ }
+
+ if (add_mrv_selection) {
+ PublicEditor& editor (trackview.editor());
+ editor.get_selection().add (this);
+ }
+}
+
+void
+MidiRegionView::color_handler ()
+{
+ RegionView::color_handler ();
+
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ (*i)->set_selected ((*i)->selected()); // will change color
+ }
+
+ /* XXX probably more to do here */
+}
+
+void
+MidiRegionView::enable_display (bool yn)
+{
+ RegionView::enable_display (yn);
+ if (yn) {
+ redisplay_model ();
+ }
+}
+
+void
+MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
+{
+ if (_step_edit_cursor == 0) {
+ ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
+
+ _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
+ _step_edit_cursor->property_y1() = 0;
+ _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
+ _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
+ _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
+ }
+
+ move_step_edit_cursor (pos);
+ _step_edit_cursor->show ();
+}
+
+void
+MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
+{
+ _step_edit_cursor_position = pos;
+
+ if (_step_edit_cursor) {
+ double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
+ _step_edit_cursor->property_x1() = pixel;
+ set_step_edit_cursor_width (_step_edit_cursor_width);
+ }
+}
+
+void
+MidiRegionView::hide_step_edit_cursor ()
+{
+ if (_step_edit_cursor) {
+ _step_edit_cursor->hide ();
+ }
+}
+
+void
+MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
+{
+ _step_edit_cursor_width = beats;
+
+ if (_step_edit_cursor) {
+ _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
+ }
+}
+
+/** Called when a diskstream on our track has received some data. Update the view, if applicable.
+ * @param w Source that the data will end up in.
+ */
+void
+MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
+{
+ if (!_active_notes) {
+ /* we aren't actively being recorded to */
+ return;
+ }
+
+ boost::shared_ptr<MidiSource> src = w.lock ();
+ if (!src || src != midi_region()->midi_source()) {
+ /* recorded data was not destined for our source */
+ return;
+ }
+
+ MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
+
+ boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
+
+ BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
+
+ framepos_t back = max_framepos;
+
+ for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
+ Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
+ assert (ev.buffer ());
+
+ /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
+ frames from the start of the source, and so time_beats is in terms of the
+ source.
+ */
+
+ Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
+
+ if (ev.type() == MIDI_CMD_NOTE_ON) {
+
+ boost::shared_ptr<NoteType> note (
+ new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
+ );
+
+ add_note (note, true);
+
+ /* fix up our note range */
+ if (ev.note() < _current_range_min) {
+ midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
+ } else if (ev.note() > _current_range_max) {
+ midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
+ }
+
+ } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
+ resolve_note (ev.note (), time_beats);
+ }
+
+ back = ev.time ();
+ }
+
+ midi_stream_view()->check_record_layers (region(), back);
+}
+
+void
+MidiRegionView::trim_front_starting ()
+{
+ /* Reparent the note group to the region view's parent, so that it doesn't change
+ when the region view is trimmed.
+ */
+ _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
+ _temporary_note_group->move (group->property_x(), group->property_y());
+ _note_group->reparent (*_temporary_note_group);
+}
+
+void
+MidiRegionView::trim_front_ending ()
+{
+ _note_group->reparent (*group);
+ delete _temporary_note_group;
+ _temporary_note_group = 0;
+
+ if (_region->start() < 0) {
+ /* Trim drag made start time -ve; fix this */
+ midi_region()->fix_negative_start ();
+ }
+}
+
+void
+MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
+{
+ PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY);
+ if (d.run () != Gtk::RESPONSE_ACCEPT) {
+ return;
+ }
+
+ change_patch_change (pc->patch(), d.patch ());
+}
+
+
+void
+MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
+{
+ char buf[24];
+ snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
+ Evoral::midi_note_name (n->note()).c_str(),
+ (int) n->note (),
+ (int) n->channel() + 1,
+ (int) n->velocity());
+
+ show_verbose_cursor (buf, 10, 20);
+}
+
+void
+MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
+{
+ double wx, wy;
+
+ trackview.editor().get_pointer_position (wx, wy);
+
+ wx += xoffset;
+ wy += yoffset;
+
+ /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
+
+ double x1, y1, x2, y2;
+ trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
+
+ if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
+ wy -= (y2 - y1) + 2 * yoffset;
+ }
+
+ trackview.editor().verbose_cursor()->set (text, wx, wy);
+ trackview.editor().verbose_cursor()->show ();
+}
+
+/** @param p A session framepos.
+ * @param grid_frames Filled in with the number of frames that a grid interval is at p.
+ * @return p snapped to the grid subdivision underneath it.
+ */
+framepos_t
+MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
+{
+ PublicEditor& editor = trackview.editor ();
+
+ bool success;
+ Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
+
+ if (!success) {
+ grid_beats = 1;
+ }
+
+ grid_frames = region_beats_to_region_frames (grid_beats);
+
+ /* Hack so that we always snap to the note that we are over, instead of snapping
+ to the next one if we're more than halfway through the one we're over.
+ */
+ if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
+ p -= grid_frames / 2;
+ }
+
+ return snap_frame_to_frame (p);
+}
+
+/** Called when the selection has been cleared in any MidiRegionView.
+ * @param rv MidiRegionView that the selection was cleared in.
+ */
+void
+MidiRegionView::selection_cleared (MidiRegionView* rv)
+{
+ if (rv == this) {
+ return;
+ }
+
+ /* Clear our selection in sympathy; but don't signal the fact */
+ clear_selection (false);
+}
+
+void
+MidiRegionView::note_button_release ()