Re-work layering in possibly debatable ways. Sketchy docs in doc/layering.
[ardour.git] / gtk2_ardour / midi_time_axis.cc
index 5979e315a8f8c07184ad55025a7352a26c211b26..c3df55a43985a6e1d611674d4bdafd991df75ff3 100644 (file)
 #include "gtkmm2ext/utils.h"
 
 #include "ardour/file_source.h"
-#include "ardour/midi_playlist.h"
+#include "ardour/ladspa_plugin.h"
+#include "ardour/location.h"
 #include "ardour/midi_diskstream.h"
 #include "ardour/midi_patch_manager.h"
+#include "ardour/midi_playlist.h"
+#include "ardour/midi_region.h"
 #include "ardour/midi_source.h"
-#include "ardour/processor.h"
-#include "ardour/ladspa_plugin.h"
-#include "ardour/location.h"
+#include "ardour/operations.h"
 #include "ardour/playlist.h"
+#include "ardour/processor.h"
 #include "ardour/region_factory.h"
 #include "ardour/session.h"
 #include "ardour/session_playlist.h"
@@ -57,6 +59,7 @@
 
 #include "add_midi_cc_track_dialog.h"
 #include "ardour_ui.h"
+#include "ardour_button.h"
 #include "automation_line.h"
 #include "automation_time_axis.h"
 #include "canvas-note-event.h"
@@ -98,10 +101,9 @@ using namespace Editing;
 static const uint32_t MIDI_CONTROLS_BOX_MIN_HEIGHT = 162;
 static const uint32_t KEYBOARD_MIN_HEIGHT = 140;
 
-MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess,
-               boost::shared_ptr<Route> rt, Canvas& canvas)
+MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess, Canvas& canvas)
        : AxisView(sess) // virtually inherited
-       , RouteTimeAxisView(ed, sess, rt, canvas)
+       , RouteTimeAxisView(ed, sess, canvas)
        , _ignore_signals(false)
        , _range_scroomer(0)
        , _piano_roll_header(0)
@@ -114,18 +116,39 @@ MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess,
        , _track_color_mode_item(0)
        , _step_edit_item (0)
        , _midi_thru_item (0)
-       , default_channel_menu (0)
        , controller_menu (0)
         , _step_editor (0)
 {
-       subplugin_menu.set_name ("ArdourContextMenu");
+}
 
+void
+MidiTimeAxisView::set_route (boost::shared_ptr<Route> rt)
+{
+       _route = rt;
+       
        _view = new MidiStreamView (*this);
+       
+       if (is_track ()) {
+               _piano_roll_header = new PianoRollHeader(*midi_view());
+               _range_scroomer = new MidiScroomer(midi_view()->note_range_adjustment);
+       }
+
+       /* This next call will result in our height being set up, so it must come after
+          the creation of the piano roll / range scroomer as their visibility is set up
+          when our height is.
+       */
+       RouteTimeAxisView::set_route (rt);
+
+       _view->apply_color (_color, StreamView::RegionColor);
+
+       subplugin_menu.set_name ("ArdourContextMenu");
+
+       if (!gui_property ("note-range-min").empty ()) {
+               midi_view()->apply_note_range (atoi (gui_property ("note-range-min").c_str()), atoi (gui_property ("note-range-max").c_str()), true);
+       }
+       midi_view()->NoteRangeChanged.connect (sigc::mem_fun (*this, &MidiTimeAxisView::note_range_changed));
 
        ignore_toggle = false;
-       
-       mute_button->set_active (false);
-       solo_button->set_active (false);
 
        if (is_midi_track()) {
                controls_ebox.set_name ("MidiTimeAxisViewControlsBaseUnselected");
@@ -138,20 +161,16 @@ MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess,
 
        processors_changed (RouteProcessorChange ());
 
-       ensure_xml_node ();
-
-       set_state (*xml_node, Stateful::loading_state_version);
-
        _route->processors_changed.connect (*this, invalidator (*this), ui_bind (&MidiTimeAxisView::processors_changed, this, _1), gui_context());
 
        if (is_track()) {
-               _piano_roll_header = new PianoRollHeader(*midi_view());
-
                _piano_roll_header->AddNoteSelection.connect (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection));
                _piano_roll_header->ExtendNoteSelection.connect (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection));
                _piano_roll_header->ToggleNoteSelection.connect (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection));
 
-               _range_scroomer = new MidiScroomer(midi_view()->note_range_adjustment);
+               /* Suspend updates of the StreamView during scroomer drags to speed things up */
+               _range_scroomer->DragStarting.connect (sigc::mem_fun (*midi_view(), &MidiStreamView::suspend_updates));
+               _range_scroomer->DragFinishing.connect (sigc::mem_fun (*midi_view(), &MidiStreamView::resume_updates));
 
                controls_hbox.pack_start(*_range_scroomer);
                controls_hbox.pack_start(*_piano_roll_header);
@@ -164,8 +183,12 @@ MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess,
 
                /* ask for notifications of any new RegionViews */
                _view->RegionViewAdded.connect (sigc::mem_fun(*this, &MidiTimeAxisView::region_view_added));
-               _view->attach ();
-                
+
+               if (!_editor.have_idled()) {
+                       /* first idle will do what we need */
+               } else {
+                       first_idle ();
+               }
        }
 
        HBox* midi_controls_hbox = manage(new HBox());
@@ -203,20 +226,47 @@ MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess,
        _channel_selector.mode_changed.connect(
                sigc::mem_fun(*this, &MidiTimeAxisView::set_channel_mode));
 
-       XMLProperty *prop;
-       if ((prop = xml_node->property ("color-mode")) != 0) {
-               _color_mode = ColorMode (string_2_enum(prop->value(), _color_mode));
+       string prop = gui_property ("color-mode");
+       if (!prop.empty()) {
+               _color_mode = ColorMode (string_2_enum(prop, _color_mode));
                if (_color_mode == ChannelColors) {
                        _channel_selector.set_channel_colors(CanvasNoteEvent::midi_channel_colors);
                }
        }
 
-       if ((prop = xml_node->property ("note-mode")) != 0) {
-               _note_mode = NoteMode (string_2_enum(prop->value(), _note_mode));
+       set_color_mode (_color_mode, true, false);
+
+       prop = gui_property ("note-mode");
+       if (!prop.empty()) {
+               _note_mode = NoteMode (string_2_enum (prop, _note_mode));
                if (_percussion_mode_item) {
                        _percussion_mode_item->set_active (_note_mode == Percussive);
                }
        }
+
+       /* Look for any GUI object state nodes that represent automation children that should exist, and create
+        * the children.
+        */
+       
+       GUIObjectState& gui_state = gui_object_state ();
+       for (GUIObjectState::StringPropertyMap::const_iterator i = gui_state.begin(); i != gui_state.end(); ++i) {
+               PBD::ID route_id;
+               bool has_parameter;
+               Evoral::Parameter parameter (0, 0, 0);
+
+               bool const p = AutomationTimeAxisView::parse_state_id (i->first, route_id, has_parameter, parameter);
+               if (p && route_id == _route->id () && has_parameter) {
+                       create_automation_child (parameter, string_is_affirmative (gui_object_state().get_string (i->first, X_("visible"))));
+               }
+       }
+}
+
+void
+MidiTimeAxisView::first_idle ()
+{
+       if (is_track ()) {
+               _view->attach ();
+       }
 }
 
 MidiTimeAxisView::~MidiTimeAxisView ()
@@ -231,13 +281,30 @@ MidiTimeAxisView::~MidiTimeAxisView ()
         delete _step_editor;
 }
 
+void
+MidiTimeAxisView::enter_internal_edit_mode ()
+{
+        if (midi_view()) {
+                midi_view()->enter_internal_edit_mode ();
+        }
+}
+
+void
+MidiTimeAxisView::leave_internal_edit_mode ()
+{
+        if (midi_view()) {
+                midi_view()->leave_internal_edit_mode ();
+        }
+}
+
 void
 MidiTimeAxisView::check_step_edit ()
 {
+       ensure_step_editor ();
         _step_editor->check_step_edit ();
 }
 
-void 
+void
 MidiTimeAxisView::model_changed()
 {
        std::list<std::string> device_modes = MIDI::Name::MidiPatchManager::instance()
@@ -266,47 +333,37 @@ MidiTimeAxisView::midi_view()
        return dynamic_cast<MidiStreamView*>(_view);
 }
 
-guint32
-MidiTimeAxisView::show_at (double y, int& nth, Gtk::VBox *parent)
-{
-       ensure_xml_node ();
-       xml_node->add_property ("shown-editor", "yes");
-
-       guint32 ret = TimeAxisView::show_at (y, nth, parent);
-       return ret;
-}
-
-void
-MidiTimeAxisView::hide ()
-{
-       ensure_xml_node ();
-       xml_node->add_property ("shown-editor", "no");
-
-       TimeAxisView::hide ();
-}
-
 void
 MidiTimeAxisView::set_height (uint32_t h)
 {
-       RouteTimeAxisView::set_height (h);
-
-       if (height >= MIDI_CONTROLS_BOX_MIN_HEIGHT) {
+       if (h >= MIDI_CONTROLS_BOX_MIN_HEIGHT) {
                _midi_controls_box.show_all ();
        } else {
                _midi_controls_box.hide();
        }
-
-       if (height >= KEYBOARD_MIN_HEIGHT) {
-               if (is_track() && _range_scroomer)
+       
+       if (h >= KEYBOARD_MIN_HEIGHT) {
+               if (is_track() && _range_scroomer) {
                        _range_scroomer->show();
-               if (is_track() && _piano_roll_header)
+               }
+               if (is_track() && _piano_roll_header) {
                        _piano_roll_header->show();
+               }
        } else {
-               if (is_track() && _range_scroomer)
+               if (is_track() && _range_scroomer) {
                        _range_scroomer->hide();
-               if (is_track() && _piano_roll_header)
+               }
+               if (is_track() && _piano_roll_header) {
                        _piano_roll_header->hide();
+               }
        }
+
+       /* We need to do this after changing visibility of our stuff, as it will
+          eventually trigger a call to Editor::reset_controls_layout_width(),
+          which needs to know if we have just shown or hidden a scroomer /
+          piano roll.
+       */
+       RouteTimeAxisView::set_height (h);
 }
 
 void
@@ -329,43 +386,13 @@ MidiTimeAxisView::append_extra_display_menu_items ()
                        sigc::mem_fun(*this, &MidiTimeAxisView::set_note_range),
                        MidiStreamView::ContentsRange)));
 
-       items.push_back (MenuElem (_("Note range"), *range_menu));
-       items.push_back (MenuElem (_("Note mode"), *build_note_mode_menu()));
-       items.push_back (MenuElem (_("Default Channel"), *build_def_channel_menu()));
+       items.push_back (MenuElem (_("Note Range"), *range_menu));
+       items.push_back (MenuElem (_("Note Mode"), *build_note_mode_menu()));
 
        items.push_back (CheckMenuElem (_("MIDI Thru"), sigc::mem_fun(*this, &MidiTimeAxisView::toggle_midi_thru)));
        _midi_thru_item = dynamic_cast<CheckMenuItem*>(&items.back());
-}
 
-Gtk::Menu*
-MidiTimeAxisView::build_def_channel_menu ()
-{
-       using namespace Menu_Helpers;
-
-       default_channel_menu = manage (new Menu ());
-
-       uint8_t defchn = midi_track()->default_channel();
-       MenuList& def_channel_items = default_channel_menu->items();
-       RadioMenuItem* item;
-       RadioMenuItem::Group dc_group;
-
-       for (int i = 0; i < 16; ++i) {
-               char buf[4];
-               snprintf (buf, sizeof (buf), "%d", i+1);
-
-               def_channel_items.push_back (RadioMenuElem (dc_group, buf,
-                                                           sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_default_channel), i)));
-               item = dynamic_cast<RadioMenuItem*>(&def_channel_items.back());
-               item->set_active ((i == defchn));
-       }
-
-       return default_channel_menu;
-}
-
-void
-MidiTimeAxisView::set_default_channel (int chn)
-{
-       midi_track()->set_default_channel (chn);
+       items.push_back (SeparatorElem ());
 }
 
 void
@@ -382,7 +409,7 @@ MidiTimeAxisView::toggle_midi_thru ()
 }
 
 void
-MidiTimeAxisView::build_automation_action_menu ()
+MidiTimeAxisView::build_automation_action_menu (bool for_selection)
 {
        using namespace Menu_Helpers;
 
@@ -392,55 +419,58 @@ MidiTimeAxisView::build_automation_action_menu ()
           controller_menu's gobj, meaning that it can't be reattached
           below.  See bug #3134.
        */
-          
+
        if (controller_menu) {
                detach_menu (*controller_menu);
        }
 
        _channel_command_menu_map.clear ();
-       RouteTimeAxisView::build_automation_action_menu ();
+       RouteTimeAxisView::build_automation_action_menu (for_selection);
 
        MenuList& automation_items = automation_action_menu->items();
-       
+
        uint16_t selected_channels = _channel_selector.get_selected_channels();
 
        if (selected_channels !=  0) {
 
                automation_items.push_back (SeparatorElem());
 
-               /* these 3 MIDI "command" types are semantically more like automation than note data,
+               /* these 2 MIDI "command" types are semantically more like automation than note data,
                   but they are not MIDI controllers. We give them special status in this menu, since
                   they will not show up in the controller list and anyone who actually knows
                   something about MIDI (!) would not expect to find them there.
                */
 
-               add_channel_command_menu_item (automation_items, _("Program Change"), MidiPgmChangeAutomation, 0);
                add_channel_command_menu_item (automation_items, _("Bender"), MidiPitchBenderAutomation, 0);
+               automation_items.back().set_sensitive (!for_selection || _editor.get_selection().tracks.size() == 1);
                add_channel_command_menu_item (automation_items, _("Pressure"), MidiChannelPressureAutomation, 0);
-               
+               automation_items.back().set_sensitive (!for_selection || _editor.get_selection().tracks.size() == 1);
+
                /* now all MIDI controllers. Always offer the possibility that we will rebuild the controllers menu
                   since it might need to be updated after a channel mode change or other change. Also detach it
                   first in case it has been used anywhere else.
                */
-               
+
                build_controller_menu ();
-               
+
                automation_items.push_back (SeparatorElem());
                automation_items.push_back (MenuElem (_("Controllers"), *controller_menu));
+               automation_items.back().set_sensitive (!for_selection || _editor.get_selection().tracks.size() == 1);
        } else {
                automation_items.push_back (MenuElem (string_compose ("<i>%1</i>", _("No MIDI Channels selected"))));
+               dynamic_cast<Label*> (automation_items.back().get_child())->set_use_markup (true);
        }
-               
+
 }
 
 void
 MidiTimeAxisView::change_all_channel_tracks_visibility (bool yn, Evoral::Parameter param)
 {
        uint16_t selected_channels = _channel_selector.get_selected_channels();
-       
+
        for (uint8_t chn = 0; chn < 16; chn++) {
                if (selected_channels & (0x0001 << chn)) {
-                       
+
                        Evoral::Parameter fully_qualified_param (param.type(), chn, param.id());
                        Gtk::CheckMenuItem* menu = automation_child_menu_item (fully_qualified_param);
 
@@ -461,7 +491,7 @@ MidiTimeAxisView::add_channel_command_menu_item (Menu_Helpers::MenuList& items,
 
        uint16_t selected_channels = _channel_selector.get_selected_channels();
        int chn_cnt = 0;
-       
+
        for (uint8_t chn = 0; chn < 16; chn++) {
                if (selected_channels & (0x0001 << chn)) {
                        if (++chn_cnt > 1) {
@@ -469,11 +499,11 @@ MidiTimeAxisView::add_channel_command_menu_item (Menu_Helpers::MenuList& items,
                        }
                }
        }
-       
+
        if (chn_cnt > 1) {
-               
+
                /* multiple channels - create a submenu, with 1 item per channel */
-               
+
                Menu* chn_menu = manage (new Menu);
                MenuList& chn_items (chn_menu->items());
                Evoral::Parameter param_without_channel (auto_type, 0, cmd);
@@ -481,25 +511,25 @@ MidiTimeAxisView::add_channel_command_menu_item (Menu_Helpers::MenuList& items,
                /* add a couple of items to hide/show all of them */
 
                chn_items.push_back (MenuElem (_("Hide all channels"),
-                                                   sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility), 
+                                                   sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility),
                                                                false, param_without_channel)));
                chn_items.push_back (MenuElem (_("Show all channels"),
-                                                   sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility), 
+                                                   sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility),
                                                                true, param_without_channel)));
-               
+
                for (uint8_t chn = 0; chn < 16; chn++) {
                        if (selected_channels & (0x0001 << chn)) {
-                               
+
                                /* for each selected channel, add a menu item for this controller */
-                               
+
                                Evoral::Parameter fully_qualified_param (auto_type, chn, cmd);
                                chn_items.push_back (CheckMenuElem (string_compose (_("Channel %1"), chn+1),
                                                                    sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
                                                                                fully_qualified_param)));
-                               
+
                                boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
                                bool visible = false;
-                               
+
                                if (track) {
                                        if (track->marked_for_display()) {
                                                visible = true;
@@ -511,36 +541,36 @@ MidiTimeAxisView::add_channel_command_menu_item (Menu_Helpers::MenuList& items,
                                cmi->set_active (visible);
                        }
                }
-               
+
                /* now create an item in the parent menu that has the per-channel list as a submenu */
-                       
+
                items.push_back (MenuElem (label, *chn_menu));
-               
+
        } else {
-               
+
                /* just one channel - create a single menu item for this command+channel combination*/
-               
+
                for (uint8_t chn = 0; chn < 16; chn++) {
                        if (selected_channels & (0x0001 << chn)) {
-                               
+
                                Evoral::Parameter fully_qualified_param (auto_type, chn, cmd);
                                items.push_back (CheckMenuElem (label,
                                                                sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
                                                                            fully_qualified_param)));
-                               
+
                                boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
                                bool visible = false;
-                               
+
                                if (track) {
                                        if (track->marked_for_display()) {
                                                visible = true;
                                        }
                                }
-                               
+
                                CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&items.back());
                                _channel_command_menu_map[fully_qualified_param] = cmi;
                                cmi->set_active (visible);
-                               
+
                                /* one channel only */
                                break;
                        }
@@ -561,7 +591,7 @@ MidiTimeAxisView::build_controller_menu ()
        controller_menu = new Menu; // explicitly managed by us
        MenuList& items (controller_menu->items());
 
-       /* create several "top level" menu items for sets of controllers (16 at a time), and populate each one with a submenu 
+       /* create several "top level" menu items for sets of controllers (16 at a time), and populate each one with a submenu
           for each controller+channel combination covering the currently selected channels for this track
        */
 
@@ -571,7 +601,7 @@ MidiTimeAxisView::build_controller_menu ()
         */
 
        int chn_cnt = 0;
-       
+
        for (uint8_t chn = 0; chn < 16; chn++) {
                if (selected_channels & (0x0001 << chn)) {
                        if (++chn_cnt > 1) {
@@ -579,8 +609,9 @@ MidiTimeAxisView::build_controller_menu ()
                        }
                }
        }
-       
-       /* loop over all 127 MIDI controllers, in groups of 16 */
+
+       /* loop over all 127 MIDI controllers, in groups of 16; except don't offer
+          bank select controllers, as they are handled by the `patch' code */
 
        for (int i = 0; i < 127; i += 16) {
 
@@ -592,6 +623,10 @@ MidiTimeAxisView::build_controller_menu ()
 
                for (int ctl = i; ctl < i+16; ++ctl) {
 
+                       if (ctl == MIDI_CTL_MSB_BANK || ctl == MIDI_CTL_LSB_BANK) {
+                               continue;
+                       }
+
                        if (chn_cnt > 1) {
 
                                /* multiple channels - create a submenu, with 1 item per channel */
@@ -600,20 +635,20 @@ MidiTimeAxisView::build_controller_menu ()
                                MenuList& chn_items (chn_menu->items());
 
                                /* add a couple of items to hide/show this controller on all channels */
-                               
+
                                Evoral::Parameter param_without_channel (MidiCCAutomation, 0, ctl);
                                chn_items.push_back (MenuElem (_("Hide all channels"),
-                                                                   sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility), 
+                                                                   sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility),
                                                                                false, param_without_channel)));
                                chn_items.push_back (MenuElem (_("Show all channels"),
-                                                                   sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility), 
+                                                                   sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility),
                                                                                true, param_without_channel)));
-               
+
                                for (uint8_t chn = 0; chn < 16; chn++) {
                                        if (selected_channels & (0x0001 << chn)) {
-                                               
+
                                                /* for each selected channel, add a menu item for this controller */
-                                               
+
                                                Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl);
                                                chn_items.push_back (CheckMenuElem (string_compose (_("Channel %1"), chn+1),
                                                                                    sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
@@ -621,55 +656,60 @@ MidiTimeAxisView::build_controller_menu ()
 
                                                boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
                                                bool visible = false;
-                                               
+
                                                if (track) {
                                                        if (track->marked_for_display()) {
                                                                visible = true;
                                                        }
                                                }
-                                               
+
                                                CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&chn_items.back());
                                                _controller_menu_map[fully_qualified_param] = cmi;
                                                cmi->set_active (visible);
                                        }
                                }
-                               
+
                                /* add the per-channel menu to the list of controllers, with the name of the controller */
                                ctl_items.push_back (MenuElem (string_compose ("<b>%1</b>: %2", ctl, midi_name (ctl)), *chn_menu));
                                dynamic_cast<Label*> (ctl_items.back().get_child())->set_use_markup (true);
-                                     
+
                        } else {
 
                                /* just one channel - create a single menu item for this ctl+channel combination*/
 
                                for (uint8_t chn = 0; chn < 16; chn++) {
                                        if (selected_channels & (0x0001 << chn)) {
-                                               
+
                                                Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl);
-                                               ctl_items.push_back (CheckMenuElem (_route->describe_parameter (fully_qualified_param),
-                                                                                   sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
-                                                                                               fully_qualified_param)));
-                                               
+                                               ctl_items.push_back (
+                                                       CheckMenuElem (
+                                                               string_compose ("<b>%1</b>: %2 [%3]", ctl, midi_name (ctl), int (chn)),
+                                                               sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
+                                                                           fully_qualified_param)
+                                                               )
+                                                       );
+                                               dynamic_cast<Label*> (ctl_items.back().get_child())->set_use_markup (true);
+
                                                boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
                                                bool visible = false;
-                                               
+
                                                if (track) {
                                                        if (track->marked_for_display()) {
                                                                visible = true;
                                                        }
                                                }
-                                               
+
                                                CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&ctl_items.back());
                                                _controller_menu_map[fully_qualified_param] = cmi;
                                                cmi->set_active (visible);
-                                               
+
                                                /* one channel only */
                                                break;
                                        }
                                }
                        }
                }
-                       
+
                /* add the menu for this block of controllers to the overall controller menu */
 
                items.push_back (MenuElem (string_compose (_("Controllers %1-%2"), i, i+15), *ctl_menu));
@@ -710,17 +750,20 @@ MidiTimeAxisView::build_color_mode_menu()
 
        RadioMenuItem::Group mode_group;
        items.push_back (RadioMenuElem (mode_group, _("Meter Colors"),
-                               sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_color_mode), MeterColors)));
+                                       sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_color_mode),
+                                                   MeterColors, false, true)));
        _meter_color_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
        _meter_color_mode_item->set_active(_color_mode == MeterColors);
 
        items.push_back (RadioMenuElem (mode_group, _("Channel Colors"),
-                               sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_color_mode), ChannelColors)));
+                                       sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_color_mode),
+                                                   ChannelColors, false, true)));
        _channel_color_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
        _channel_color_mode_item->set_active(_color_mode == ChannelColors);
 
        items.push_back (RadioMenuElem (mode_group, _("Track Color"),
-                               sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_color_mode), TrackColor)));
+                                       sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_color_mode),
+                                                   TrackColor, false, true)));
        _channel_color_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
        _channel_color_mode_item->set_active(_color_mode == TrackColor);
 
@@ -733,23 +776,27 @@ MidiTimeAxisView::set_note_mode(NoteMode mode)
        if (_note_mode != mode || midi_track()->note_mode() != mode) {
                _note_mode = mode;
                midi_track()->set_note_mode(mode);
-               xml_node->add_property ("note-mode", enum_2_string(_note_mode));
+               set_gui_property ("note-mode", enum_2_string(_note_mode));
                _view->redisplay_track();
        }
 }
 
 void
-MidiTimeAxisView::set_color_mode(ColorMode mode)
+MidiTimeAxisView::set_color_mode (ColorMode mode, bool force, bool redisplay)
 {
-       if (_color_mode != mode) {
-               if (mode == ChannelColors) {
-                       _channel_selector.set_channel_colors(CanvasNoteEvent::midi_channel_colors);
-               } else {
-                       _channel_selector.set_default_channel_color();
-               }
+       if (_color_mode == mode && !force) {
+               return;
+       }
+
+       if (mode == ChannelColors) {
+               _channel_selector.set_channel_colors(CanvasNoteEvent::midi_channel_colors);
+       } else {
+               _channel_selector.set_default_channel_color();
+       }
 
-               _color_mode = mode;
-               xml_node->add_property ("color-mode", enum_2_string(_color_mode));
+       _color_mode = mode;
+       set_gui_property ("color-mode", enum_2_string(_color_mode));
+       if (redisplay) {
                _view->redisplay_track();
        }
 }
@@ -775,31 +822,39 @@ MidiTimeAxisView::update_range()
 }
 
 void
-MidiTimeAxisView::show_all_automation ()
+MidiTimeAxisView::show_all_automation (bool apply_to_selection)
 {
-       if (midi_track()) {
-               const set<Evoral::Parameter> params = midi_track()->midi_playlist()->contained_automation();
+       if (apply_to_selection) {
+               _editor.get_selection().tracks.foreach_midi_time_axis (boost::bind (&MidiTimeAxisView::show_all_automation, _1, false));
+       } else {
+               if (midi_track()) {
+                       const set<Evoral::Parameter> params = midi_track()->midi_playlist()->contained_automation();
 
-               for (set<Evoral::Parameter>::const_iterator i = params.begin(); i != params.end(); ++i) {
-                       create_automation_child(*i, true);
+                       for (set<Evoral::Parameter>::const_iterator i = params.begin(); i != params.end(); ++i) {
+                               create_automation_child(*i, true);
+                       }
                }
-       }
 
-       RouteTimeAxisView::show_all_automation ();
+               RouteTimeAxisView::show_all_automation ();
+       }
 }
 
 void
-MidiTimeAxisView::show_existing_automation ()
+MidiTimeAxisView::show_existing_automation (bool apply_to_selection)
 {
-       if (midi_track()) {
-               const set<Evoral::Parameter> params = midi_track()->midi_playlist()->contained_automation();
+       if (apply_to_selection) {
+               _editor.get_selection().tracks.foreach_midi_time_axis (boost::bind (&MidiTimeAxisView::show_existing_automation, _1, false));
+       } else {
+               if (midi_track()) {
+                       const set<Evoral::Parameter> params = midi_track()->midi_playlist()->contained_automation();
 
-               for (set<Evoral::Parameter>::const_iterator i = params.begin(); i != params.end(); ++i) {
-                       create_automation_child(*i, true);
+                       for (set<Evoral::Parameter>::const_iterator i = params.begin(); i != params.end(); ++i) {
+                               create_automation_child (*i, true);
+                       }
                }
-       }
 
-       RouteTimeAxisView::show_existing_automation ();
+               RouteTimeAxisView::show_existing_automation ();
+       }
 }
 
 /** Create an automation track for the given parameter (pitch bend, channel pressure).
@@ -807,35 +862,77 @@ MidiTimeAxisView::show_existing_automation ()
 void
 MidiTimeAxisView::create_automation_child (const Evoral::Parameter& param, bool show)
 {
-       /* These controllers are region "automation", so we do not create
-        * an AutomationList/Line for the track */
-
        if (param.type() == NullAutomation) {
                cerr << "WARNING: Attempt to create NullAutomation child, ignoring" << endl;
                return;
        }
 
        AutomationTracks::iterator existing = _automation_tracks.find (param);
+
        if (existing != _automation_tracks.end()) {
+
+               /* automation track created because we had existing data for
+                * the processor, but visibility may need to be controlled
+                * since it will have been set visible by default.
+                */
+
+               cerr << "show existing auto track: " << show << " noredraw " << no_redraw << endl;
+
+               if (existing->second->set_marked_for_display (show) && !no_redraw) {
+                       request_redraw ();
+               }
+
                return;
        }
 
-       boost::shared_ptr<AutomationControl> c = _route->get_control (param);
-
-       assert(c);
+       boost::shared_ptr<AutomationTimeAxisView> track;
+
+       switch (param.type()) {
+
+       case GainAutomation:
+               create_gain_automation_child (param, show);
+               break;
+
+       case PluginAutomation:
+               /* handled elsewhere */
+               break;
+
+       case MidiCCAutomation:
+       case MidiPgmChangeAutomation:
+       case MidiPitchBenderAutomation:
+       case MidiChannelPressureAutomation:
+       case MidiSystemExclusiveAutomation:
+               /* These controllers are region "automation" - they are owned
+                * by regions (and their MidiModels), not by the track. As a
+                * result we do not create an AutomationList/Line for the track
+                * ... except here we are doing something!! XXX 
+                */
+
+               track.reset (new AutomationTimeAxisView (
+                                    _session,
+                                    _route,
+                                    boost::shared_ptr<Automatable> (),
+                                    boost::shared_ptr<AutomationControl> (),
+                                    param,
+                                    _editor,
+                                    *this,
+                                    true,
+                                    parent_canvas,
+                                    _route->describe_parameter(param)
+                                    ));
+
+               if (_view) {
+                       _view->foreach_regionview (sigc::mem_fun (*track.get(), &TimeAxisView::add_ghost));
+               }
 
-       boost::shared_ptr<AutomationTimeAxisView> track(new AutomationTimeAxisView (_session,
-                       _route, boost::shared_ptr<ARDOUR::Automatable>(), c,
-                       _editor,
-                       *this,
-                       true,
-                       parent_canvas,
-                       _route->describe_parameter(param)));
+               add_automation_child (param, track, show);
+               break;
 
-       add_automation_child (param, track, show);
+       default:
+               error << "MidiTimeAxisView: unknown automation child " << EventTypeMap::instance().to_symbol(param) << endmsg;
+       }
 }
 
-
 void
 MidiTimeAxisView::route_active_changed ()
 {
@@ -956,29 +1053,29 @@ MidiTimeAxisView::set_channel_mode (ChannelMode, uint16_t)
                        if (!track) {
                                continue;
                        }
-                       
+
                        if ((selected_channels & (0x0001 << chn)) == 0) {
-                               /* channel not in use. hiding it will trigger RouteTimeAxisView::automation_track_hidden() 
+                               /* channel not in use. hiding it will trigger RouteTimeAxisView::automation_track_hidden()
                                   which will cause a redraw. We don't want one per channel, so block that with no_redraw.
                                 */
-                               changed = track->set_visibility (false) || changed;
+                               changed = track->set_marked_for_display (false) || changed;
                        } else {
-                               changed = track->set_visibility (true) || changed;
+                               changed = track->set_marked_for_display (true) || changed;
                        }
                }
        }
 
        no_redraw = false;
 
-       /* TODO: Bender, PgmChange, Pressure */
+       /* TODO: Bender, Pressure */
 
-       /* invalidate the controller menu, so that we rebuilt it next time */
+       /* invalidate the controller menu, so that we rebuild it next time */
        _controller_menu_map.clear ();
        delete controller_menu;
        controller_menu = 0;
 
        if (changed) {
-               _route->gui_changed ("track_height", this);
+               request_redraw ();
        }
 }
 
@@ -1008,21 +1105,21 @@ MidiTimeAxisView::add_region (framepos_t pos, framecnt_t length, bool commit)
 {
        Editor* real_editor = dynamic_cast<Editor*> (&_editor);
 
-       real_editor->begin_reversible_command (_("create region"));
-        playlist()->clear_history ();
+       real_editor->begin_reversible_command (Operations::create_region);
+        playlist()->clear_changes ();
 
        real_editor->snap_to (pos, 0);
-       
+
        boost::shared_ptr<Source> src = _session->create_midi_source_for_session (view()->trackview().track().get(),
                                                                                   view()->trackview().track()->name());
-       PropertyList plist; 
-       
+       PropertyList plist;
+
        plist.add (ARDOUR::Properties::start, 0);
        plist.add (ARDOUR::Properties::length, length);
        plist.add (ARDOUR::Properties::name, PBD::basename_nosuffix(src->name()));
-       
+
        boost::shared_ptr<Region> region = (RegionFactory::create (src, plist));
-        
+
        playlist()->add_region (region, pos);
        _session->add_command (new StatefulDiffCommand (playlist()));
 
@@ -1033,20 +1130,61 @@ MidiTimeAxisView::add_region (framepos_t pos, framecnt_t length, bool commit)
        return boost::dynamic_pointer_cast<MidiRegion>(region);
 }
 
-void 
-MidiTimeAxisView::start_step_editing ()
+void
+MidiTimeAxisView::ensure_step_editor ()
 {
-        if (!_step_editor) {
-                _step_editor = new StepEditor (_editor, midi_track(), *this);
-        }
+       if (!_step_editor) {
+               _step_editor = new StepEditor (_editor, midi_track(), *this);
+       }
+}
 
+void
+MidiTimeAxisView::start_step_editing ()
+{
+       ensure_step_editor ();
         _step_editor->start_step_editing ();
 
 }
-void 
+void
 MidiTimeAxisView::stop_step_editing ()
 {
         if (_step_editor) {
                 _step_editor->stop_step_editing ();
         }
 }
+
+
+/** @return channel (counted from 0) to add an event to, based on the current setting
+ *  of the channel selector.
+ */
+uint8_t
+MidiTimeAxisView::get_channel_for_add () const
+{
+       uint16_t const chn_mask = _channel_selector.get_selected_channels ();
+       int chn_cnt = 0;
+       uint8_t channel = 0;
+
+       /* pick the highest selected channel, unless all channels are selected,
+          which is interpreted to mean channel 1 (zero)
+       */
+
+       for (uint16_t i = 0; i < 16; ++i) {
+               if (chn_mask & (1<<i)) {
+                       channel = i;
+                       chn_cnt++;
+               }
+       }
+
+       if (chn_cnt == 16) {
+               channel = 0;
+       }
+
+       return channel;
+}
+
+void
+MidiTimeAxisView::note_range_changed ()
+{
+       set_gui_property ("note-range-min", (int) midi_view()->lowest_note ());
+       set_gui_property ("note-range-max", (int) midi_view()->highest_note ());
+}