Add missing files.
[ardour.git] / gtk2_ardour / midi_region_view.cc
index f82f309c819a17f2a3461fb9d906b6921008674f..fcab48328711f3ad127de3f199dc51c668c2883d 100644 (file)
@@ -80,6 +80,8 @@ using namespace Editing;
 using namespace ArdourCanvas;
 using Gtkmm2ext::Keyboard;
 
+PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
+
 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
 
 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
@@ -116,6 +118,8 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &
 
        Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
        connect_to_diskstream ();
+
+       SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
 }
 
 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
@@ -149,6 +153,8 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &
        PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
 
        connect_to_diskstream ();
+
+       SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
 }
 
 void
@@ -284,6 +290,14 @@ MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
 
        Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
        connect_to_diskstream ();
+
+       SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
+}
+
+const boost::shared_ptr<ARDOUR::MidiRegion>
+MidiRegionView::midi_region() const
+{
+       return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
 }
 
 void
@@ -363,15 +377,19 @@ MidiRegionView::enter_notify (GdkEventCrossing* ev)
 {
        trackview.editor().MouseModeChanged.connect (
                _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
-                                                    );
-
-       Keyboard::magic_widget_grab_focus();
-       group->grab_focus();
+               );
 
        if (trackview.editor().current_mouse_mode() == MouseRange) {
                create_ghost_note (ev->x, ev->y);
        }
 
+       if (!trackview.editor().internal_editing()) {
+               Keyboard::magic_widget_drop_focus();
+       } else {
+               Keyboard::magic_widget_grab_focus();
+               group->grab_focus();
+       }
+
        return false;
 }
 
@@ -382,6 +400,7 @@ MidiRegionView::leave_notify (GdkEventCrossing*)
 
        trackview.editor().verbose_cursor()->hide ();
        remove_ghost_note ();
+
        return false;
 }
 
@@ -394,6 +413,13 @@ MidiRegionView::mouse_mode_changed ()
                remove_ghost_note ();
                trackview.editor().verbose_cursor()->hide ();
        }
+
+       if (!trackview.editor().internal_editing()) {
+               Keyboard::magic_widget_drop_focus();
+       } else {
+               Keyboard::magic_widget_grab_focus();
+               group->grab_focus();
+       }
 }
 
 bool
@@ -460,7 +486,7 @@ MidiRegionView::button_release (GdkEventButton* ev)
                                                beats = 1;
                                        }
 
-                                       create_note_at (event_x, event_y, beats, true);
+                                       create_note_at (event_x, event_y, beats, true, true);
                                }
 
                                break;
@@ -474,7 +500,7 @@ MidiRegionView::button_release (GdkEventButton* ev)
                                        beats = 1;
                                }
 
-                               create_note_at (event_x, event_y, beats, true);
+                               create_note_at (event_x, event_y, beats, true, true);
 
                                break;
                        }
@@ -503,7 +529,7 @@ MidiRegionView::button_release (GdkEventButton* ev)
                                const double x  = _drag_rect->property_x1();
                                const double length = trackview.editor().pixel_to_frame (_drag_rect->property_x2() - _drag_rect->property_x1());
 
-                               create_note_at (x, _drag_rect->property_y1(), region_frames_to_region_beats(length), true);
+                               create_note_at (x, _drag_rect->property_y1(), region_frames_to_region_beats(length), true, false);
                        }
                }
 
@@ -529,25 +555,28 @@ MidiRegionView::motion (GdkEventMotion* ev)
        event_y = ev->y;
        group->w2i(event_x, event_y);
 
+       PublicEditor& editor = trackview.editor ();
+       
        // convert event_x to global frame
-       event_frame = snap_pixel_to_frame (event_x);
+       framecnt_t grid_frames;
+       event_frame = snap_frame_to_grid_underneath (editor.pixel_to_frame (event_x), grid_frames);
 
-       if (!_ghost_note && trackview.editor().current_mouse_mode() != MouseRange
+       if (!_ghost_note && editor.current_mouse_mode() != MouseRange
            && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
            && _mouse_state != AddDragging) {
 
                create_ghost_note (ev->x, ev->y);
-       } else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange
+       } else if (_ghost_note && editor.current_mouse_mode() != MouseRange
                   && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
 
                update_ghost_note (ev->x, ev->y);
-       } else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange) {
+       } else if (_ghost_note && editor.current_mouse_mode() != MouseRange) {
 
                delete _ghost_note;
                _ghost_note = 0;
 
-               trackview.editor().verbose_cursor()->hide ();
-       } else if (_ghost_note && trackview.editor().current_mouse_mode() == MouseRange) {
+               editor.verbose_cursor()->hide ();
+       } else if (_ghost_note && editor.current_mouse_mode() == MouseRange) {
                update_ghost_note (ev->x, ev->y);
        }
 
@@ -565,7 +594,7 @@ MidiRegionView::motion (GdkEventMotion* ev)
                        return false;
                }
 
-               if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject
+               if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject
                    && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
                        // Select drag start
 
@@ -591,7 +620,7 @@ MidiRegionView::motion (GdkEventMotion* ev)
                        _mouse_state = SelectRectDragging;
                        return true;
 
-               } else if (trackview.editor().internal_editing()) {
+               } else if (editor.internal_editing()) {
                        // Add note drag start
 
                        group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
@@ -603,11 +632,12 @@ MidiRegionView::motion (GdkEventMotion* ev)
                        _drag_start_y = event_y;
 
                        _drag_rect = new ArdourCanvas::SimpleRect(*group);
-                       _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
+                       _drag_rect->property_x1() = editor.frame_to_pixel(event_frame);
 
                        _drag_rect->property_y1() = midi_stream_view()->note_to_y(
                                midi_stream_view()->y_to_note(event_y));
-                       _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
+                       _drag_rect->property_x2() = editor.frame_to_pixel(event_frame);
+                       
                        _drag_rect->property_y2() = _drag_rect->property_y1()
                                + floor(midi_stream_view()->note_height());
                        _drag_rect->property_outline_what() = 0xFF;
@@ -619,7 +649,7 @@ MidiRegionView::motion (GdkEventMotion* ev)
                        delete _ghost_note;
                        _ghost_note = 0;
 
-                       trackview.editor().verbose_cursor()->hide ();
+                       editor.verbose_cursor()->hide ();
 
                        return true;
                }
@@ -639,7 +669,17 @@ MidiRegionView::motion (GdkEventMotion* ev)
                }
 
                if (_mouse_state == AddDragging) {
-                       event_x = trackview.editor().frame_to_pixel(event_frame);
+                       event_x = editor.frame_to_pixel(event_frame);
+
+                       if (editor.snap_mode() == SnapNormal) {
+                               /* event_frame will have been snapped to the start of the note we are under;
+                                  it's more intuitive if we use the end of that note here
+                               */
+                               event_x = editor.frame_to_pixel (event_frame + grid_frames);
+                       } else {
+                               event_x = editor.frame_to_pixel (event_frame);
+                       }
+                       
                }
 
                if (_drag_rect) {
@@ -661,7 +701,7 @@ MidiRegionView::motion (GdkEventMotion* ev)
                                _drag_rect->property_y1() = event_y;
                        }
 
-                       update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
+                       update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y, Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
                }
 
                _last_x = event_x;
@@ -685,6 +725,13 @@ MidiRegionView::scroll (GdkEventScroll* ev)
                return false;
        }
 
+       if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
+               /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
+                  it still works for zoom.
+               */
+               return false;
+       }
+
        trackview.editor().verbose_cursor()->hide ();
 
        bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
@@ -705,15 +752,17 @@ MidiRegionView::key_press (GdkEventKey* ev)
           repeated presses, carry out key actions at key press, not release.
        */
 
-       if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
+       bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
+       
+       if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
                _mouse_state = SelectTouchDragging;
                return true;
 
-       } else if (ev->keyval == GDK_Escape) {
+       } else if (ev->keyval == GDK_Escape && unmodified) {
                clear_selection();
                _mouse_state = None;
 
-       } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
+       } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
 
                bool start = (ev->keyval == GDK_comma);
                bool end = (ev->keyval == GDK_period);
@@ -724,7 +773,7 @@ MidiRegionView::key_press (GdkEventKey* ev)
 
                return true;
 
-       } else if (ev->keyval == GDK_Delete) {
+       } else if (ev->keyval == GDK_Delete && unmodified) {
 
                delete_selection();
                return true;
@@ -775,20 +824,17 @@ MidiRegionView::key_press (GdkEventKey* ev)
                }
                return true;
 
-       } else if (ev->keyval == GDK_Left) {
+       } else if (ev->keyval == GDK_Left && unmodified) {
 
                nudge_notes (false);
                return true;
 
-       } else if (ev->keyval == GDK_Right) {
+       } else if (ev->keyval == GDK_Right && unmodified) {
 
                nudge_notes (true);
                return true;
 
-       } else if (ev->keyval == GDK_Control_L) {
-               return true;
-
-       } else if (ev->keyval == GDK_c) {
+       } else if (ev->keyval == GDK_c && unmodified) {
                channel_edit ();
                return true;
        }
@@ -799,7 +845,7 @@ MidiRegionView::key_press (GdkEventKey* ev)
 bool
 MidiRegionView::key_release (GdkEventKey* ev)
 {
-       if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
+       if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
                _mouse_state = None;
                return true;
        }
@@ -810,7 +856,7 @@ void
 MidiRegionView::channel_edit ()
 {
        bool first = true;
-       uint8_t current_channel;
+       uint8_t current_channel = 0;
 
        if (_selection.empty()) {
                return;
@@ -861,9 +907,10 @@ MidiRegionView::show_list_editor ()
  * \param y vertical position in pixels
  * \param length duration of the note in beats, which will be snapped to the grid
  * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
+ * \param snap_x true to snap x to the grid, otherwise false.
  */
 void
-MidiRegionView::create_note_at(double x, double y, double length, bool sh)
+MidiRegionView::create_note_at (double x, double y, double length, bool sh, bool snap_x)
 {
        MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
        MidiStreamView* const view = mtv->midi_view();
@@ -874,7 +921,11 @@ MidiRegionView::create_note_at(double x, double y, double length, bool sh)
        assert(note <= 127.0);
 
        // Start of note in frames relative to region start
-       framepos_t const start_frames = snap_pixel_to_frame (x);
+       framepos_t start_frames = trackview.editor().pixel_to_frame (x);
+       if (snap_x) {
+               framecnt_t grid_frames;
+               start_frames = snap_frame_to_grid_underneath (start_frames, grid_frames);
+       }
        assert(start_frames >= 0);
 
        // Snap length
@@ -1930,40 +1981,41 @@ MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
 }
 
 void
-MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
-{
-       for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
-               if ((*i)->selected() && (*i) != ev) {
-                       (*i)->set_selected(false);
-                       (*i)->hide_velocity();
-               }
-       }
-
-       _selection.clear();
-}
-
-void
-MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
+MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
 {
        for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
                if ((*i) != ev) {
-
                        Selection::iterator tmp = i;
                        ++tmp;
 
                        (*i)->set_selected (false);
+                       (*i)->hide_velocity ();
                        _selection.erase (i);
-
+                       
                        i = tmp;
-
                } else {
                        ++i;
                }
        }
 
-       /* don't bother with removing this regionview from the editor selection,
-          since we're about to add another note, and thus put/keep this
-          regionview in the editor selection.
+       /* this does not change the status of this regionview w.r.t the editor
+          selection.
+       */
+
+       if (signal) {
+               SelectionCleared (this); /* EMIT SIGNAL */
+       }
+}
+
+void
+MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
+{
+       clear_selection_except (ev);
+
+       /* don't bother with checking to see if we should remove this
+          regionview from the editor selection, since we're about to add
+          another note, and thus put/keep this regionview in the editor
+          selection anyway.
        */
 
        if (!ev->selected()) {
@@ -1981,6 +2033,31 @@ MidiRegionView::select_all_notes ()
        }
 }
 
+void
+MidiRegionView::select_range (framepos_t start, framepos_t end)
+{
+       clear_selection ();
+
+       for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+               framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
+               if (t >= start && t <= end) {
+                       add_to_selection (*i);
+               }
+       }
+}
+
+void
+MidiRegionView::invert_selection ()
+{
+       for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+               if ((*i)->selected()) {
+                       remove_from_selection(*i);
+               } else {
+                       add_to_selection (*i);
+               }
+       }
+}
+
 void
 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
 {
@@ -2071,10 +2148,14 @@ MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
 }
 
 void
-MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
+MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
 {
        if (!add) {
-               clear_selection_except(ev);
+               clear_selection_except (ev);
+               if (!_selection.empty()) {
+                       PublicEditor& editor (trackview.editor());
+                       editor.get_selection().add (this);
+               }
        }
 
        if (!extend) {
@@ -2126,7 +2207,7 @@ MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
 }
 
 void
-MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
+MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
 {
        if (x1 > x2) {
                swap (x1, x2);
@@ -2163,7 +2244,7 @@ MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2
                        if (!(*i)->selected()) {
                                add_to_selection (*i);
                        }
-               } else if ((*i)->selected()) {
+               } else if ((*i)->selected() && !extend) {
                        // Not inside rectangle
                        remove_from_selection (*i);
                }
@@ -2301,12 +2382,6 @@ MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
                // keep notes in standard midi range
                clamp_to_0_127(new_pitch);
 
-               // keep original pitch if note is dragged outside valid midi range
-               if ((original_pitch != 0 && new_pitch == 0)
-                   || (original_pitch != 127 && new_pitch == 127)) {
-                       new_pitch = original_pitch;
-               }
-
                lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
                highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
 
@@ -2322,6 +2397,9 @@ MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
        }
 }
 
+/** @param x Pixel relative to the region position.
+ *  @return Snapped frame relative to the region position.
+ */
 framepos_t
 MidiRegionView::snap_pixel_to_frame(double x)
 {
@@ -2329,32 +2407,9 @@ MidiRegionView::snap_pixel_to_frame(double x)
        return snap_frame_to_frame (editor.pixel_to_frame (x));
 }
 
-/** Snap a frame offset within our region using the current snap settings.
- *  @param x Frame offset from this region's position.
- *  @return Snapped frame offset from this region's position.
+/** @param x Pixel relative to the region position.
+ *  @return Snapped pixel relative to the region position.
  */
-frameoffset_t
-MidiRegionView::snap_frame_to_frame (frameoffset_t x)
-{
-       PublicEditor& editor = trackview.editor();
-
-       /* x is region relative, convert it to global absolute frames */
-       framepos_t const session_frame = x + _region->position();
-
-       /* try a snap in either direction */
-       framepos_t frame = session_frame;
-       editor.snap_to (frame, 0);
-
-       /* if we went off the beginning of the region, snap forwards */
-       if (frame < _region->position ()) {
-               frame = session_frame;
-               editor.snap_to (frame, 1);
-       }
-
-       /* back to region relative */
-       return frame - _region->position();
-}
-
 double
 MidiRegionView::snap_to_pixel(double x)
 {
@@ -2494,9 +2549,6 @@ MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at
                        double beats;
 
                        beats = snap_pixel_to_frame (current_x);
-                       /* XXX not sure this is correct - snap_pixel_to_frame()
-                          returns an absolute frame.
-                       */
                        beats = region_frames_to_region_beats (beats);
 
                        double len;
@@ -2559,13 +2611,10 @@ MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at
                        }
                }
 
-               /* Convert that to a frame within the region */
+               /* Convert that to a frame within the source */
                current_x = snap_pixel_to_frame (current_x) + _region->start ();
 
                /* and then to beats */
-               /* XXX not sure this is correct - snap_pixel_to_frame()
-                  returns an absolute frame.
-               */
                current_x = region_frames_to_region_beats (current_x);
 
                if (at_front && current_x < canvas_note->note()->end_time()) {
@@ -3316,24 +3365,18 @@ MidiRegionView::update_ghost_note (double x, double y)
        PublicEditor& editor = trackview.editor ();
        
        framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
-       bool success;
-       Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, unsnapped_frame);
-
-       if (!success) {
-               grid_beats = 1;
-       }
-
-       framecnt_t const grid_frames = region_beats_to_region_frames (grid_beats);
-       framepos_t f = snap_frame_to_frame (unsnapped_frame);
+       framecnt_t grid_frames;
+       framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
+       
        /* use region_frames... because we are converting a delta within the region
        */
         
        double length = region_frames_to_region_beats (snap_frame_to_frame (f + grid_frames) - f);
 
        /* note that this sets the time of the ghost note in beats relative to
-          the start of the region.
+          the start of the source; that is how all note times are stored.
        */
-       _ghost_note->note()->set_time (region_frames_to_region_beats (f - _region->position()));
+       _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
        _ghost_note->note()->set_length (length);
        _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
        _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
@@ -3606,3 +3649,45 @@ MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double
        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);
+}