son't try to select non-existent notes after editing (and thus crash).
[ardour.git] / gtk2_ardour / mixer_strip.cc
index 0153ac5f1f1dbe1260926c06a1273797b81a876e..ca1b104e0fdf3528edb098b2af7881005489061b 100644 (file)
@@ -38,6 +38,7 @@
 #include "ardour/audio_track.h"
 #include "ardour/audioengine.h"
 #include "ardour/internal_send.h"
+#include "ardour/io.h"
 #include "ardour/meter.h"
 #include "ardour/midi_track.h"
 #include "ardour/pannable.h"
@@ -70,7 +71,7 @@
 #include "tooltips.h"
 #include "ui_config.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace ARDOUR;
 using namespace ARDOUR_UI_UTILS;
@@ -263,7 +264,9 @@ MixerStrip::init ()
        group_button.set_name ("mixer strip button");
 
        _comment_button.set_name (X_("mixer strip button"));
+       _comment_button.set_text_ellipsize (Pango::ELLIPSIZE_END);
        _comment_button.signal_clicked.connect (sigc::mem_fun (*this, &RouteUI::toggle_comment_editor));
+       _comment_button.signal_size_allocate().connect (sigc::mem_fun (*this, &MixerStrip::comment_button_resized));
 
        // TODO implement ArdourKnob::on_size_request properly
 #define PX_SCALE(px) std::max((float)px, rintf((float)px * UIConfiguration::instance().get_ui_scale()))
@@ -283,7 +286,6 @@ MixerStrip::init ()
        width_button.signal_button_press_event().connect (sigc::mem_fun(*this, &MixerStrip::width_button_pressed), false);
        hide_button.signal_clicked.connect (sigc::mem_fun(*this, &MixerStrip::hide_clicked));
 
-//     width_hide_box.set_border_width (1);
        width_hide_box.set_spacing (2);
        width_hide_box.pack_start (width_button, false, true);
        width_hide_box.pack_start (number_label, true, true);
@@ -296,6 +298,7 @@ MixerStrip::init ()
        number_label.set_fixed_colors (0x80808080, 0x80808080);
        number_label.set_alignment (.5, .5);
        number_label.set_fallthrough_to_parent (true);
+       number_label.set_tweaks (ArdourButton::OccasionalText);
 
        global_vpacker.set_spacing (2);
        if (!ARDOUR::Profile->get_trx()) {
@@ -350,7 +353,6 @@ MixerStrip::init ()
        number_label.signal_button_press_event().connect (sigc::mem_fun(*this, &MixerStrip::number_button_button_press), false);
 
        name_button.signal_button_press_event().connect (sigc::mem_fun(*this, &MixerStrip::name_button_button_press), false);
-       name_button.signal_button_release_event().connect (sigc::mem_fun(*this, &MixerStrip::name_button_button_release), false);
 
        group_button.signal_button_press_event().connect (sigc::mem_fun(*this, &MixerStrip::select_route_group), false);
 
@@ -454,6 +456,20 @@ MixerStrip::name() const
        return string();
 }
 
+void
+MixerStrip::update_trim_control ()
+{
+       if (route()->trim() && route()->trim()->active() &&
+           route()->n_inputs().n_audio() > 0) {
+               trim_control.show ();
+               trim_control.set_controllable (route()->trim()->gain_control());
+       } else {
+               trim_control.hide ();
+               boost::shared_ptr<Controllable> none;
+               trim_control.set_controllable (none);
+       }
+}
+
 void
 MixerStrip::set_route (boost::shared_ptr<Route> rt)
 {
@@ -560,14 +576,7 @@ MixerStrip::set_route (boost::shared_ptr<Route> rt)
                monitor_disk_button->hide ();
        }
 
-       if (route()->trim() && route()->trim()->active()) {
-               trim_control.show ();
-               trim_control.set_controllable (route()->trim()->gain_control());
-       } else {
-               trim_control.hide ();
-               boost::shared_ptr<Controllable> none;
-               trim_control.set_controllable (none);
-       }
+       update_trim_control();
 
        if (is_midi_track()) {
                if (midi_input_enable_button == 0) {
@@ -908,7 +917,9 @@ MixerStrip::output_press (GdkEventButton *ev)
                citems.push_back (SeparatorElem());
                citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*(static_cast<RouteUI*>(this)), &RouteUI::edit_output_configuration)));
 
-               output_menu.popup (1, ev->time);
+               Gtkmm2ext::anchored_menu_popup(&output_menu, &output_button, "",
+                                              1, ev->time);
+
                break;
        }
 
@@ -1010,7 +1021,8 @@ MixerStrip::input_press (GdkEventButton *ev)
                citems.push_back (SeparatorElem());
                citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*(static_cast<RouteUI*>(this)), &RouteUI::edit_input_configuration)));
 
-               input_menu.popup (1, ev->time);
+               Gtkmm2ext::anchored_menu_popup(&input_menu, &input_button, "",
+                                              1, ev->time);
 
                break;
        }
@@ -1207,6 +1219,7 @@ MixerStrip::update_io_button (boost::shared_ptr<ARDOUR::Route> route, Width widt
 {
        uint32_t io_count;
        uint32_t io_index;
+       boost::shared_ptr<IO> io;
        boost::shared_ptr<Port> port;
        vector<string> port_connections;
 
@@ -1231,30 +1244,70 @@ MixerStrip::update_io_button (boost::shared_ptr<ARDOUR::Route> route, Width widt
        ostringstream tooltip;
        char * tooltip_cstr;
 
-       //to avoid confusion, the button caption should only show connections that match the datatype of the track
+       /* To avoid confusion, the button caption only shows connections that match the expected datatype
+        *
+        * First of all, if the user made only connections to a given type, we should use that one since
+        * it is very probably what the user expects. If there are several connections types, then show
+        * audio ones as primary, which matches expectations for both audio tracks with midi control and
+        * synthesisers. This first heuristic can be expressed with these two rules:
+        * A) If there are connected audio ports, consider audio as primary type.
+        * B) Else, if there are connected midi ports, consider midi as primary type.
+        *
+        * If there are no connected ports, then we choose the primary type based on the type of existing
+        * but unconnected ports. Again:
+        * C) If there are audio ports, consider audio as primary type.
+        * D) Else, if there are midi ports, consider midi as primary type. */
+
        DataType dt = DataType::AUDIO;
-       if ( boost::dynamic_pointer_cast<MidiTrack>(route) != 0 ) {
-               dt = DataType::MIDI;
-               // avoid further confusion with Midi-tracks that have a synth.
-               // Audio-ports may be connected, but button says "Disconnected"
+       bool match = false;
+
+       if (for_input) {
+               io = route->input();
+       } else {
+               io = route->output();
+       }
+
+       io_count = io->n_ports().n_total();
+       for (io_index = 0; io_index < io_count; ++io_index) {
+               port = io->nth (io_index);
+               if (port->connected()) {
+                       match = true;
+                       if (port->type() == DataType::AUDIO) {
+                               /* Rule A) applies no matter the remaining ports */
+                               dt = DataType::AUDIO;
+                               break;
+                       }
+                       if (port->type() == DataType::MIDI) {
+                               /* Rule B) is a good candidate... */
+                               dt = DataType::MIDI;
+                               /* ...but continue the loop to check remaining ports for rule A) */
+                       }
+               }
+       }
+
+       if (!match) {
+               /* Neither rule A) nor rule B) matched */
+               if ( io->n_ports().n_audio() > 0 ) {
+                       /* Rule C */
+                       dt = DataType::AUDIO;
+               } else if ( io->n_ports().n_midi() > 0 ) {
+                       /* Rule D */
+                       dt = DataType::MIDI;
+               }
+       }
+
+       if ( dt == DataType::MIDI ) {
                tooltip << _("MIDI ");
        }
 
        if (for_input) {
-               io_count = route->n_inputs().n_total();
                tooltip << string_compose (_("<b>INPUT</b> to %1"), Gtkmm2ext::markup_escape_text (route->name()));
        } else {
-               io_count = route->n_outputs().n_total();
                tooltip << string_compose (_("<b>OUTPUT</b> from %1"), Gtkmm2ext::markup_escape_text (route->name()));
        }
 
-
        for (io_index = 0; io_index < io_count; ++io_index) {
-               if (for_input) {
-                       port = route->input()->nth (io_index);
-               } else {
-                       port = route->output()->nth (io_index);
-               }
+               port = io->nth (io_index);
 
                port_connections.clear ();
                port->get_connections(port_connections);
@@ -1465,6 +1518,7 @@ void
 MixerStrip::io_changed_proxy ()
 {
        Glib::signal_idle().connect_once (sigc::mem_fun (*this, &MixerStrip::update_panner_choices));
+       Glib::signal_idle().connect_once (sigc::mem_fun (*this, &MixerStrip::update_trim_control));
 }
 
 void
@@ -1487,33 +1541,27 @@ MixerStrip::port_connected_or_disconnected (boost::weak_ptr<Port> wa, boost::wea
 void
 MixerStrip::setup_comment_button ()
 {
-       switch (_width) {
+       std::string comment = _route->comment();
 
-       case Wide:
-               if (_route->comment().empty ()) {
-                       _comment_button.unset_bg (STATE_NORMAL);
-                       _comment_button.set_text (_("Comments"));
-               } else {
-                       _comment_button.modify_bg (STATE_NORMAL, color ());
-                       _comment_button.set_text (_("*Comments*"));
-               }
-               break;
+       set_tooltip (_comment_button, comment.empty() ? _("Click to add/edit comments") : _route->comment());
 
-       case Narrow:
-               if (_route->comment().empty ()) {
-                       _comment_button.unset_bg (STATE_NORMAL);
-                       _comment_button.set_text (_("Cmt"));
-               } else {
-                       _comment_button.modify_bg (STATE_NORMAL, color ());
-                       _comment_button.set_text (_("*Cmt*"));
-               }
-               break;
+       if (comment.empty ()) {
+               _comment_button.set_name ("generic button");
+               _comment_button.set_text (_width  == Wide ? _("Comments") : _("Cmt"));
+               return;
        }
 
-       set_tooltip (
-               _comment_button, _route->comment().empty() ? _("Click to add/edit comments") : _route->comment()
-               );
+       _comment_button.set_name ("comment button");
 
+       string::size_type pos = comment.find_first_of (" \t\n");
+       if (pos != string::npos) {
+               comment = comment.substr (0, pos);
+       }
+       if (comment.empty()) {
+               _comment_button.set_text (_width  == Wide ? _("Comments") : _("Cmt"));
+       } else {
+               _comment_button.set_text (comment);
+       }
 }
 
 bool
@@ -1537,7 +1585,12 @@ MixerStrip::select_route_group (GdkEventButton *ev)
                WeakRouteList r;
                r.push_back (route ());
                group_menu->build (r);
-               group_menu->menu()->popup (1, ev->time);
+
+               RouteGroup *rg = _route->route_group();
+
+               Gtkmm2ext::anchored_menu_popup(group_menu->menu(), &group_button,
+                                              rg ? rg->name() : _("No Group"),
+                                              1, ev->time);
        }
 
        return true;
@@ -1586,7 +1639,13 @@ MixerStrip::help_count_plugins (boost::weak_ptr<Processor> p)
        if (!processor || !processor->display_to_user()) {
                return;
        }
-       if (boost::dynamic_pointer_cast<PluginInsert> (processor)) {
+       boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (processor);
+#ifdef MIXBUS
+       if (pi && pi->is_channelstrip ()) {
+               return;
+       }
+#endif
+       if (pi) {
                ++_plugin_insert_cnt;
        }
 }
@@ -1637,6 +1696,12 @@ MixerStrip::build_route_ops_menu ()
                items.push_back (MenuElem (_("Pin Connections..."), sigc::mem_fun (*this, &RouteUI::manage_pins)));
        }
 
+       if (_route->the_instrument () && _route->the_instrument ()->output_streams().n_audio() > 2) {
+               // TODO ..->n_audio() > 1 && separate_output_groups) hard to check here every time.
+               items.push_back (MenuElem (_("Fan out to Busses"), sigc::bind (sigc::mem_fun (*this, &RouteUI::fan_out), true, true)));
+               items.push_back (MenuElem (_("Fan out to Tracks"), sigc::bind (sigc::mem_fun (*this, &RouteUI::fan_out), false, true)));
+       }
+
        items.push_back (SeparatorElem());
        items.push_back (MenuElem (_("Adjust Latency..."), sigc::mem_fun (*this, &RouteUI::adjust_latency)));
 
@@ -1672,12 +1737,17 @@ MixerStrip::build_route_ops_menu ()
 gboolean
 MixerStrip::name_button_button_press (GdkEventButton* ev)
 {
-       if (ev->button == 3) {
+       if (ev->button == 1 || ev->button == 3) {
                list_route_operations ();
 
                /* do not allow rename if the track is record-enabled */
                rename_menu_item->set_sensitive (!is_track() || !track()->rec_enable_control()->get_value());
-               route_ops_menu->popup (1, ev->time);
+               if (ev->button == 1) {
+                       Gtkmm2ext::anchored_menu_popup(route_ops_menu, &name_button, "",
+                                                      1, ev->time);
+               } else {
+                       route_ops_menu->popup (3, ev->time);
+               }
 
                return true;
        }
@@ -1685,20 +1755,6 @@ MixerStrip::name_button_button_press (GdkEventButton* ev)
        return false;
 }
 
-gboolean
-MixerStrip::name_button_button_release (GdkEventButton* ev)
-{
-       if (ev->button == 1) {
-               list_route_operations ();
-
-               /* do not allow rename if the track is record-enabled */
-               rename_menu_item->set_sensitive (!is_track() || !track()->rec_enable_control()->get_value());
-               route_ops_menu->popup (1, ev->time);
-       }
-
-       return false;
-}
-
 gboolean
 MixerStrip::number_button_button_press (GdkEventButton* ev)
 {
@@ -1723,8 +1779,10 @@ MixerStrip::list_route_operations ()
 }
 
 void
-MixerStrip::show_selected ()
+MixerStrip::set_selected (bool yn)
 {
+       AxisView::set_selected (yn);
+
        if (selected()) {
                global_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
                global_frame.set_name ("MixerStripSelectedFrame");
@@ -1791,6 +1849,12 @@ MixerStrip::name_button_resized (Gtk::Allocation& alloc)
        name_button.set_layout_ellipsize_width (alloc.get_width() * PANGO_SCALE);
 }
 
+void
+MixerStrip::comment_button_resized (Gtk::Allocation& alloc)
+{
+       _comment_button.set_layout_ellipsize_width (alloc.get_width() * PANGO_SCALE);
+}
+
 bool
 MixerStrip::width_button_pressed (GdkEventButton* ev)
 {