prevent everything except the name column from changing selection in EditorRoutes
[ardour.git] / gtk2_ardour / midi_streamview.cc
index 55f4732a5c846affe4b4b579138f3bcc76c7b50a..76fd813078ac4dbf0448994abb9f6d76f0f4041e 100644 (file)
@@ -1,20 +1,26 @@
 /*
-    Copyright (C) 2001-2007 Paul Davis
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-*/
+ * Copyright (C) 2006-2014 David Robillard <d@drobilla.net>
+ * Copyright (C) 2007 Doug McLain <doug@nostar.net>
+ * Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
+ * Copyright (C) 2016 Nick Mainsbridge <mainsbridge@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
 
 #include <cmath>
 #include <utility>
@@ -33,9 +39,8 @@
 #include "ardour/region_factory.h"
 #include "ardour/session.h"
 #include "ardour/smf_source.h"
+#include "ardour/evoral_types_convert.h"
 
-#include "ardour_ui.h"
-#include "global_signals.h"
 #include "gui_thread.h"
 #include "midi_region_view.h"
 #include "midi_streamview.h"
 #include "region_view.h"
 #include "rgb_macros.h"
 #include "selection.h"
+#include "ui_config.h"
 #include "utils.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace std;
 using namespace ARDOUR;
@@ -76,7 +82,7 @@ MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
           can be put below ghost regions from MIDI underlays
        */
        _note_lines = new ArdourCanvas::LineSet (_canvas_group, ArdourCanvas::LineSet::Horizontal);
-       
+
        _note_lines->Event.connect(
                sigc::bind(sigc::mem_fun(_trackview.editor(),
                                         &PublicEditor::canvas_stream_view_event),
@@ -86,7 +92,7 @@ MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
 
        color_handler ();
 
-       ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
+       UIConfiguration::instance().ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
 
        note_range_adjustment.set_page_size(_highest_note - _lowest_note);
        note_range_adjustment.set_value(_lowest_note);
@@ -100,7 +106,7 @@ MidiStreamView::~MidiStreamView ()
 }
 
 RegionView*
-MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool)
+MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool recording)
 {
        boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
 
@@ -108,8 +114,16 @@ MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, b
                return 0;
        }
 
-       RegionView* region_view = new MidiRegionView (_canvas_group, _trackview, region,
-                                                     _samples_per_pixel, region_color);
+       RegionView* region_view = NULL;
+       if (recording) {
+               region_view = new MidiRegionView (
+                       _canvas_group, _trackview, region,
+                       _samples_per_pixel, region_color, recording,
+                       TimeAxisViewItem::Visibility(TimeAxisViewItem::ShowFrame));
+       } else {
+               region_view = new MidiRegionView (_canvas_group, _trackview, region,
+                                                 _samples_per_pixel, region_color);
+       }
 
        region_view->init (false);
 
@@ -145,12 +159,6 @@ MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wait
 
        region_views.push_front (region_view);
 
-       if (_trackview.editor().internal_editing()) {
-               region_view->hide_rect ();
-       } else {
-               region_view->show_rect ();
-       }
-
        /* display events and find note range */
        display_region (region_view, wait_for_data);
 
@@ -176,11 +184,22 @@ MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
        }
 
        region_view->enable_display (true);
+       region_view->set_height (child_height());
 
        boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
+       if (!source) {
+               error << _("attempt to display MIDI region with no source") << endmsg;
+               return;
+       }
 
        if (load_model) {
-               source->load_model();
+               Glib::Threads::Mutex::Lock lm(source->mutex());
+               source->load_model(lm);
+       }
+
+       if (!source->model()) {
+               error << _("attempt to display MIDI region with no model") << endmsg;
+               return;
        }
 
        _range_dirty = update_data_note_range(
@@ -188,7 +207,6 @@ MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
                source->model()->highest_note());
 
        // Display region contents
-       region_view->set_height (child_height());
        region_view->display_model(source->model());
 }
 
@@ -200,7 +218,7 @@ MidiStreamView::display_track (boost::shared_ptr<Track> tr)
 
        draw_note_lines();
 
-       NoteRangeChanged();
+       NoteRangeChanged(); /* EMIT SIGNAL*/
 }
 
 void
@@ -208,7 +226,8 @@ MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
 {
        boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
        if (mr) {
-               mr->midi_source(0)->load_model();
+               Glib::Threads::Mutex::Lock lm(mr->midi_source(0)->mutex());
+               mr->midi_source(0)->load_model(lm);
                _range_dirty = update_data_note_range(
                        mr->model()->lowest_note(),
                        mr->model()->highest_note());
@@ -230,6 +249,18 @@ MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
        return dirty;
 }
 
+void
+MidiStreamView::set_layer_display (LayerDisplay d)
+{
+
+//revert this change for now.  Although stacked view is weirdly implemented wrt the "scroomer", it is still necessary to manage layered midi regions.
+//     if (d != Overlaid) {
+//             return;
+//     }
+
+       StreamView::set_layer_display (d);
+}
+
 void
 MidiStreamView::redisplay_track ()
 {
@@ -311,7 +342,7 @@ MidiStreamView::draw_note_lines()
                 */
 
                if (i <= highest_note()) {
-                       _note_lines->add (y, 1.0, ARDOUR_UI::config()->get_canvasvar_PianoRollBlackOutline());
+                       _note_lines->add (y, 1.0, UIConfiguration::instance().color ("piano roll black outline"));
                }
 
                /* now add a thicker line/bar which covers the entire vertical
@@ -324,17 +355,17 @@ MidiStreamView::draw_note_lines()
                case 6:
                case 8:
                case 10:
-                       color = ARDOUR_UI::config()->get_canvasvar_PianoRollBlack();
+                       color = UIConfiguration::instance().color_mod ("piano roll black", "piano roll black");
                        break;
                default:
-                       color = ARDOUR_UI::config()->get_canvasvar_PianoRollWhite();
+                       color = UIConfiguration::instance().color_mod ("piano roll white", "piano roll white");
                        break;
                }
 
                double h = y - prev_y;
                double mid = y + (h/2.0);
-                               
-               if (height > 1.0) { // XXX ? should that not be h >= 1 ?
+
+               if (mid >= 0 && h > 1.0) {
                        _note_lines->add (mid, h, color);
                }
 
@@ -399,7 +430,7 @@ MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region
                apply_note_range_to_regions ();
        }
 
-       NoteRangeChanged();
+       NoteRangeChanged(); /* EMIT SIGNAL*/
 }
 
 void
@@ -424,13 +455,13 @@ MidiStreamView::setup_rec_box ()
 {
        // cerr << _trackview.name() << " streamview SRB\n";
 
-       if (_trackview.session()->transport_rolling()) {
+       if (!_trackview.session()->transport_stopped()) {
 
                if (!rec_active &&
                    _trackview.session()->record_status() == Session::Recording &&
-                   _trackview.track()->record_enabled()) {
+                   _trackview.track()->rec_enable_control()->get_value()) {
 
-                       if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
+                       if (UIConfiguration::instance().get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
 
                                /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
 
@@ -442,10 +473,10 @@ MidiStreamView::setup_rec_box ()
 
                                // handle multi
 
-                               framepos_t start = 0;
+                               samplepos_t start = 0;
                                if (rec_regions.size() > 0) {
                                        start = rec_regions.back().first->start()
-                                               + _trackview.track()->get_captured_frames(rec_regions.size()-1);
+                                               + _trackview.track()->get_captured_samples (rec_regions.size() - 1);
                                }
 
                                if (!rec_regions.empty()) {
@@ -461,10 +492,10 @@ MidiStreamView::setup_rec_box ()
                                   is so that the RegionView gets created with a non-zero width, as apparently
                                   creating a RegionView with a zero width causes it never to be displayed
                                   (there is a warning in TimeAxisViewItem::init about this).  However, we
-                                  must also set length_beats to something non-zero, otherwise the frame length
+                                  must also set length_beats to something non-zero, otherwise the sample length
                                   of 1 causes length_beats to be set to some small quantity << 1.  Then
                                   when the position is set up below, this length_beats is used to recompute
-                                  length using BeatsFramesConverter::to, which is slightly innacurate for small
+                                  length using BeatsSamplesConverter::to, which is slightly innacurate for small
                                   beats values because it converts floating point beats to bars, beats and
                                   integer ticks.  The upshot of which being that length gets set back to 0,
                                   meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
@@ -477,13 +508,13 @@ MidiStreamView::setup_rec_box ()
                                                                      (RegionFactory::create (sources, plist, false)));
                                if (region) {
                                        region->set_start (_trackview.track()->current_capture_start()
-                                                          - _trackview.track()->get_capture_start_frame (0));
-                                       region->set_position (_trackview.track()->current_capture_start());
-                                       RegionView* rv = add_region_view_internal (region, false);
+                                                          - _trackview.track()->get_capture_start_sample (0));
+                                       region->set_position (_trackview.session()->transport_sample());
+
+                                       RegionView* rv = add_region_view_internal (region, false, true);
                                        MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
                                        mrv->begin_write ();
 
-                               
                                        /* rec region will be destroyed in setup_rec_box */
                                        rec_regions.push_back (make_pair (region, rv));
 
@@ -496,36 +527,11 @@ MidiStreamView::setup_rec_box ()
 
                        /* start a new rec box */
 
-                       boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
-                       framepos_t const frame_pos = mt->current_capture_start ();
-                       gdouble const xstart = _trackview.editor().sample_to_pixel (frame_pos);
-                       gdouble const xend = xstart;
-                       uint32_t fill_color;
-
-                       fill_color = ARDOUR_UI::config()->get_canvasvar_RecordingRect();
-
-                       ArdourCanvas::Rectangle * rec_rect = new ArdourCanvas::Rectangle (_canvas_group);
-                       rec_rect->set (ArdourCanvas::Rect (xstart, 1, xend, _trackview.current_height() - 1));
-                       rec_rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_RecordingRect());
-                       rec_rect->set_fill_color (fill_color);
-                       rec_rect->lower_to_bottom();
-
-                       RecBoxInfo recbox;
-                       recbox.rectangle = rec_rect;
-                       recbox.start = _trackview.session()->transport_frame();
-                       recbox.length = 0;
-
-                       rec_rects.push_back (recbox);
-
-                       screen_update_connection.disconnect();
-                       screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
-                               sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
-                       rec_updating = true;
-                       rec_active = true;
+                       create_rec_box (_trackview.midi_track()->current_capture_start(), 0);
 
                } else if (rec_active &&
                           (_trackview.session()->record_status() != Session::Recording ||
-                           !_trackview.track()->record_enabled())) {
+                           !_trackview.track()->rec_enable_control()->get_value())) {
                        screen_update_connection.disconnect();
                        rec_active = false;
                        rec_updating = false;
@@ -540,7 +546,6 @@ MidiStreamView::setup_rec_box ()
                        /* disconnect rapid update */
                        screen_update_connection.disconnect();
                        rec_data_ready_connections.drop_connections ();
-
                        rec_updating = false;
                        rec_active = false;
 
@@ -579,9 +584,9 @@ MidiStreamView::color_handler ()
        draw_note_lines ();
 
        if (_trackview.is_midi_track()) {
-               canvas_rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_MidiTrackBase());
+               canvas_rect->set_fill_color (UIConfiguration::instance().color_mod ("midi track base", "midi track base"));
        } else {
-               canvas_rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_MidiBusBase());
+               canvas_rect->set_fill_color (UIConfiguration::instance().color ("midi bus base"));
        }
 }
 
@@ -624,7 +629,7 @@ MidiStreamView::update_rec_box ()
 
        /* Update the region being recorded to reflect where we currently are */
        boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
-       region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
+       region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start(), 0);
 
        MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
        mrv->extend_active_notes ();
@@ -633,7 +638,7 @@ MidiStreamView::update_rec_box ()
 uint8_t
 MidiStreamView::y_to_note (double y) const
 {
-       int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
+       int const n = ((contents_height() - y) / contents_height() * (double)contents_note_range())
                + lowest_note();
 
        if (n < 0) {
@@ -642,7 +647,8 @@ MidiStreamView::y_to_note (double y) const
                return 127;
        }
 
-       return n;
+       /* min due to rounding and/or off-by-one errors */
+       return min ((uint8_t) n, highest_note());
 }
 
 /** Suspend updates to the regions' note ranges and our
@@ -661,21 +667,47 @@ void
 MidiStreamView::resume_updates ()
 {
        _updates_suspended = false;
-       
+
        draw_note_lines ();
        apply_note_range_to_regions ();
 
        _canvas_group->redraw ();
 }
 
-void
-MidiStreamView::leave_internal_edit_mode ()
+struct RegionPositionSorter {
+       bool operator() (RegionView* a, RegionView* b) {
+               return a->region()->position() < b->region()->position();
+       }
+};
+
+bool
+MidiStreamView::paste (ARDOUR::samplepos_t pos, const Selection& selection, PasteContext& ctx, const int32_t sub_num)
 {
-       StreamView::leave_internal_edit_mode ();
-       for (RegionViewList::iterator i = region_views.begin(); i != region_views.end(); ++i) {
-               MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*i);
-               if (mrv) {
-                       mrv->clear_selection ();
+       /* Paste into the first region which starts on or before pos.  Only called when
+          using an internal editing tool. */
+
+       if (region_views.empty()) {
+               return false;
+       }
+
+       region_views.sort (RegionView::PositionOrder());
+
+       list<RegionView*>::const_iterator prev = region_views.begin ();
+
+       for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
+               if ((*i)->region()->position() > pos) {
+                       break;
                }
+               prev = i;
        }
+
+       boost::shared_ptr<Region> r = (*prev)->region ();
+
+       /* If *prev doesn't cover pos, it's no good */
+       if (r->position() > pos || ((r->position() + r->length()) < pos)) {
+               return false;
+       }
+
+       MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*prev);
+       return mrv ? mrv->paste(pos, selection, ctx, sub_num) : false;
 }