audioengine branch can now load and run at least one test session.
[ardour.git] / gtk2_ardour / generic_pluginui.cc
index 3ed935275551e65b2e059b9f566d7a2ed3d277c4..971dfc0e9ba7638426afa281f1808b6607d3973f 100644 (file)
@@ -74,7 +74,7 @@ GenericPluginUI::GenericPluginUI (boost::shared_ptr<PluginInsert> pi, bool scrol
        set_border_width (10);
        //set_homogeneous (false);
 
-       pack_start (main_contents, false, false);
+       pack_start (main_contents, true, true);
        settings_box.set_homogeneous (false);
 
        HBox* constraint_hbox = manage (new HBox);
@@ -88,7 +88,8 @@ GenericPluginUI::GenericPluginUI (boost::shared_ptr<PluginInsert> pi, bool scrol
        set_latency_label ();
 
        smaller_hbox->pack_start (latency_button, false, false, 10);
-       smaller_hbox->pack_start (_preset_box, false, false);
+       smaller_hbox->pack_start (_preset_combo, false, false);
+       smaller_hbox->pack_start (_preset_modified, false, false);
        smaller_hbox->pack_start (add_button, false, false);
        smaller_hbox->pack_start (save_button, false, false);
        smaller_hbox->pack_start (delete_button, false, false);
@@ -99,7 +100,10 @@ GenericPluginUI::GenericPluginUI (boost::shared_ptr<PluginInsert> pi, bool scrol
 
        VBox* v1_box = manage (new VBox);
        VBox* v2_box = manage (new VBox);
-       pack_end (plugin_analysis_expander, true, true);
+       pack_end (plugin_analysis_expander, false, false);
+       if (!plugin->get_docs().empty()) {
+               pack_end (description_expander, false, false);
+       }
 
        v1_box->pack_start (*smaller_hbox, false, true);
        v2_box->pack_start (focus_button, false, true);
@@ -111,8 +115,8 @@ GenericPluginUI::GenericPluginUI (boost::shared_ptr<PluginInsert> pi, bool scrol
 
        main_contents.pack_start (*constraint_hbox, false, false);
 
-       if ( is_scrollable ) {
-               scroller.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+       if (is_scrollable ) {
+               scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
                scroller.set_name ("PluginEditor");
                scroller_view.set_name("PluginEditor");
                scroller_view.add (hpacker);
@@ -120,8 +124,7 @@ GenericPluginUI::GenericPluginUI (boost::shared_ptr<PluginInsert> pi, bool scrol
 
                main_contents.pack_start (scroller, true, true);
 
-       }
-       else {
+       } else {
                main_contents.pack_start (hpacker, false, false);
        }
 
@@ -129,6 +132,7 @@ GenericPluginUI::GenericPluginUI (boost::shared_ptr<PluginInsert> pi, bool scrol
 
        bypass_button.set_active (!pi->active());
 
+       prefheight = 0;
        build ();
 }
 
@@ -139,6 +143,55 @@ GenericPluginUI::~GenericPluginUI ()
        }
 }
 
+// Some functions for calculating the 'similarity' of two plugin
+// control labels.
+
+static int get_number(string label) {
+static const char *digits = "0123456789";
+int value = -1;
+
+       std::size_t first_digit_pos = label.find_first_of(digits);
+       if (first_digit_pos != string::npos) {
+               // found some digits: there's a number in there somewhere
+               stringstream s;
+               s << label.substr(first_digit_pos);
+               s >> value;
+       }
+       return value;
+}
+
+static int match_or_digit(char c1, char c2) {
+       return c1 == c2 || (isdigit(c1) && isdigit(c2));
+}      
+
+static std::size_t matching_chars_at_head(const string s1, const string s2) {
+std::size_t length, n = 0;
+
+       length = min(s1.length(), s2.length());
+       while (n < length) {
+               if (!match_or_digit(s1[n], s2[n]))
+                       break;
+               n++;
+       } 
+       return n;
+}
+
+static std::size_t matching_chars_at_tail(const string s1, const string s2) {
+std::size_t s1pos, s2pos, n = 0;
+
+       s1pos = s1.length();
+       s2pos = s2.length();
+       while (s1pos-- > 0 && s2pos-- > 0) {
+               if (!match_or_digit(s1[s1pos], s2[s2pos])       )
+                       break;
+               n++;
+       } 
+       return n;
+}
+
+static const guint32 min_controls_per_column = 17, max_controls_per_column = 24;
+static const float default_similarity_threshold = 0.3;
+
 void
 GenericPluginUI::build ()
 {
@@ -152,7 +205,6 @@ GenericPluginUI::build ()
        int output_rows, output_cols;
        int button_rows, button_cols;
 
-       prefheight = 30;
        hpacker.set_spacing (10);
 
        output_rows = initial_output_rows;
@@ -177,6 +229,7 @@ GenericPluginUI::build ()
 
        bt_frame = manage (new Frame);
        bt_frame->set_name ("BaseFrame");
+       bt_frame->set_label (_("Switches"));
        bt_frame->add (button_table);
        hpacker.pack_start(*bt_frame, true, true);
 
@@ -191,6 +244,7 @@ GenericPluginUI::build ()
        hpacker.pack_start(*frame, true, true);
 
        /* find all ports. build control elements for all appropriate control ports */
+       std::vector<ControlUI *> cui_controls_list;
 
        for (i = 0; i < plugin->parameter_count(); ++i) {
 
@@ -202,26 +256,12 @@ GenericPluginUI::build ()
                                continue;
                        }
 
-                       ControlUI* cui;
-
-                       /* if we are scrollable, just use one long column */
-
-                       if (!is_scrollable) {
-                               if (x++ > 20){
-                                       frame = manage (new Frame);
-                                       frame->set_name ("BaseFrame");
-                                       box = manage (new VBox);
-
-                                       box->set_border_width (5);
-                                       box->set_spacing (1);
-
-                                       frame->add (*box);
-                                       hpacker.pack_start(*frame,true,true);
-
-                                       x = 1;
-                               }
+                       if (plugin->describe_parameter (Evoral::Parameter(PluginAutomation, 0, i)) == X_("hidden")) {
+                               continue;
                        }
 
+                       ControlUI* cui;
+
                        boost::shared_ptr<ARDOUR::AutomationControl> c
                                = boost::dynamic_pointer_cast<ARDOUR::AutomationControl>(
                                        insert->control(Evoral::Parameter(PluginAutomation, 0, i)));
@@ -231,13 +271,18 @@ GenericPluginUI::build ()
                                continue;
                        }
 
-                       if (cui->controller || cui->clickbox || cui->combo) {
-
-                               box->pack_start (*cui, false, false);
+                       const std::string param_docs = plugin->get_parameter_docs(i);
+                       if (!param_docs.empty()) {
+                               ARDOUR_UI::instance()->set_tip(cui, param_docs.c_str());
+                       }
 
+                       if (cui->controller || cui->clickbox || cui->combo) {
+                               // Get all of the controls into a list, so that
+                               // we can lay them out a bit more nicely later.
+                               cui_controls_list.push_back(cui);
                        } else if (cui->button) {
 
-                               if (button_row == button_rows) {
+                               if (!is_scrollable && button_row == button_rows) {
                                        button_row = 0;
                                        if (++button_col == button_cols) {
                                                button_cols += 2;
@@ -260,33 +305,93 @@ GenericPluginUI::build ()
                                        output_cols ++;
                                        output_table.resize (output_rows, output_cols);
                                }
+                       }
+               } 
+       }
 
-                               /* old code, which divides meters into
-                                * columns first, rows later. New code divides into one row
+       // Iterate over the list of controls to find which adjacent controls
+       // are similar enough to be grouped together.
+       
+       string label, previous_label = "";
+       int numbers_in_labels[cui_controls_list.size()];
+       
+       float similarity_scores[cui_controls_list.size()];
+       float most_similar = 0.0, least_similar = 1.0;
+       
+       i = 0;
+       for (vector<ControlUI*>::iterator cuip = cui_controls_list.begin(); cuip != cui_controls_list.end(); ++cuip, ++i) {
+               label = (*cuip)->label.get_text();
+               numbers_in_labels[i] = get_number(label);
+
+               if (i > 0) {
+                       // A hand-wavy calculation of how similar this control's
+                       // label is to the previous.
+                       similarity_scores[i] = 
+                               (float) ( 
+                                       ( matching_chars_at_head(label, previous_label) + 
+                                         matching_chars_at_tail(label, previous_label) +
+                                         1 
+                                       ) 
+                               ) / (label.length() + previous_label.length());
+                       if (numbers_in_labels[i] >= 0) {
+                               similarity_scores[i] += (numbers_in_labels[i] == numbers_in_labels[i-1]);
+                       }
+                       least_similar = min(least_similar, similarity_scores[i]);
+                       most_similar  = max(most_similar, similarity_scores[i]);
+               } else {
+                       similarity_scores[0] = 1.0;
+               }
 
-                               if (output_row == output_rows) {
-                                       output_row = 0;
-                                       if (++output_col == output_cols) {
-                                               output_cols += 2;
-                                               output_table.resize (output_rows, output_cols);
-                                       }
-                               }
+               // cerr << "label: " << label << " sim: " << fixed << setprecision(3) << similarity_scores[i] << " num: " << numbers_in_labels[i] << endl;
+               previous_label = label;                                
+       }
 
-                               output_table.attach (*cui, output_col, output_col + 1, output_row, output_row+1,
-                                                    FILL|EXPAND, FILL);
+       
+       // cerr << "most similar: " << most_similar << ", least similar: " << least_similar << endl;
+       float similarity_threshold;
+       
+       if (most_similar > 1.0) {
+               similarity_threshold = default_similarity_threshold;
+       } else {
+               similarity_threshold = most_similar - (1 - default_similarity_threshold);
+       }
+       
+       // Now iterate over the list of controls to display them, placing an
+       // HSeparator between controls of less than a certain similarity, and 
+       // starting a new column when necessary.
+       
+       i = 0;
+       for (vector<ControlUI*>::iterator cuip = cui_controls_list.begin(); cuip != cui_controls_list.end(); ++cuip, ++i) {
 
-                               output_row++;
-                               */
+               ControlUI* cui = *cuip;
+               
+               if (!is_scrollable) {
+                       x++;
+               }
+               
+               if (x > max_controls_per_column || similarity_scores[i] <= similarity_threshold) {
+                       if (x > min_controls_per_column) {
+                               frame = manage (new Frame);
+                               frame->set_name ("BaseFrame");
+                               frame->set_label (_("Controls"));
+                               box = manage (new VBox);
+                               box->set_border_width (5);
+                               box->set_spacing (1);
+                               frame->add (*box);
+                               hpacker.pack_start(*frame, true, true);
+                               x = 0;
+                       } else {
+                               HSeparator *split = new HSeparator();
+                               split->set_size_request(-1, 5);
+                               box->pack_start(*split, false, false, 0);
                        }
 
-                       /* HACK: ideally the preferred height would be queried from
-                          the complete hpacker, but I can't seem to get that
-                          information in time, so this is an estimation
-                       */
-
-                       prefheight += 30;
-
                }
+               box->pack_start (*cui, false, false);
+       }
+
+       if (is_scrollable) {
+               prefheight = 30 * i;
        }
 
        if (box->children().empty()) {
@@ -300,6 +405,7 @@ GenericPluginUI::build ()
        if (!output_table.children().empty()) {
                frame = manage (new Frame);
                frame->set_name ("BaseFrame");
+               frame->set_label(_("Meters"));
                frame->add (output_table);
                hpacker.pack_end (*frame, true, true);
        }
@@ -345,10 +451,9 @@ GenericPluginUI::automation_state_changed (ControlUI* cui)
 
        // don't lock to avoid deadlock because we're triggered by
        // AutomationControl::Changed() while the automation lock is taken
-       switch (insert->get_parameter_automation_state (cui->parameter())
-                       & (Off|Play|Touch|Write)) {
-       case Off:
-               cui->automate_button.set_label (_("Manual"));
+       switch (insert->get_parameter_automation_state (cui->parameter()) & (ARDOUR::Off|Play|Touch|Write)) {
+       case ARDOUR::Off:
+               cui->automate_button.set_label (S_("Automation|Manual"));
                break;
        case Play:
                cui->automate_button.set_label (_("Play"));
@@ -366,9 +471,25 @@ GenericPluginUI::automation_state_changed (ControlUI* cui)
 }
 
 
-static void integer_printer (char buf[32], Adjustment &adj, void */*arg*/)
+bool
+GenericPluginUI::integer_printer (char buf[32], Adjustment &adj, ControlUI* cui)
 {
-       snprintf (buf, 32, "%.0f", adj.get_value());
+       float const v = adj.get_value ();
+       
+       if (cui->scale_points) {
+               Plugin::ScalePoints::const_iterator i = cui->scale_points->begin ();
+               while (i != cui->scale_points->end() && i->second != v) {
+                       ++i;
+               }
+
+               if (i != cui->scale_points->end ()) {
+                       snprintf (buf, 32, "%s", i->first.c_str());
+                       return true;
+               }
+       }
+               
+       snprintf (buf, 32, "%.0f", v);
+       return true;
 }
 
 void
@@ -388,7 +509,6 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat
 
        control_ui = manage (new ControlUI ());
        control_ui->combo = 0;
-       control_ui->combo_map = 0;
        control_ui->control = mcontrol;
        control_ui->update_pending = false;
        control_ui->label.set_text (desc.label);
@@ -402,15 +522,27 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat
 
        if (plugin->parameter_is_input(port_index)) {
 
-               /* Build a combo box */
+               /* See if there any named values for our input value */
+               control_ui->scale_points = plugin->get_scale_points (port_index);
 
-               boost::shared_ptr<ARDOUR::Plugin::ScalePoints> points
-                       = plugin->get_scale_points(port_index);
+               /* If this parameter is an integer, work out the number of distinct values
+                  it can take on (assuming that lower and upper values are allowed).
+               */
+               int const steps = desc.integer_step ? (desc.upper - desc.lower + 1) / desc.step : 0;
+
+               if (control_ui->scale_points && ((steps && int (control_ui->scale_points->size()) == steps) || desc.enumeration)) {
+                       
+                       /* Either:
+                        *   a) There is a label for each possible value of this input, or
+                        *   b) This port is marked as being an enumeration.
+                        */
 
-               if (points) {
                        std::vector<std::string> labels;
-                       for (ARDOUR::Plugin::ScalePoints::const_iterator i = points->begin();
-                            i != points->end(); ++i) {
+                       for (
+                               ARDOUR::Plugin::ScalePoints::const_iterator i = control_ui->scale_points->begin();
+                               i != control_ui->scale_points->end();
+                               ++i) {
+                               
                                labels.push_back(i->first);
                        }
 
@@ -448,7 +580,7 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat
 
                        mcontrol->Changed.connect (control_connections, invalidator (*this), boost::bind (&GenericPluginUI::toggle_parameter_changed, this, control_ui), gui_context());
                        mcontrol->alist()->automation_state_changed.connect (control_connections, invalidator (*this), boost::bind (&GenericPluginUI::automation_state_changed, this, control_ui), gui_context());
-       
+
                        if (plugin->get_parameter (port_index) > 0.5){
                                control_ui->button->set_active(true);
                        }
@@ -471,8 +603,8 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat
                Adjustment* adj = control_ui->controller->adjustment();
                boost::shared_ptr<PluginInsert::PluginControl> pc = boost::dynamic_pointer_cast<PluginInsert::PluginControl> (control_ui->control);
 
-               adj->set_lower (pc->user_to_ui (desc.lower));
-               adj->set_upper (pc->user_to_ui (desc.upper));
+               adj->set_lower (pc->internal_to_interface (desc.lower));
+               adj->set_upper (pc->internal_to_interface (desc.upper));
 
                adj->set_step_increment (desc.step);
                adj->set_page_increment (desc.largestep);
@@ -480,7 +612,7 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat
                if (desc.integer_step) {
                        control_ui->clickbox = new ClickBox (adj, "PluginUIClickBox");
                        Gtkmm2ext::set_size_request_to_display_given_text (*control_ui->clickbox, "g9999999", 2, 2);
-                       control_ui->clickbox->set_print_func (integer_printer, 0);
+                       control_ui->clickbox->set_printer (sigc::bind (sigc::mem_fun (*this, &GenericPluginUI::integer_printer), control_ui));
                } else {
                        //sigc::slot<void,char*,uint32_t> pslot = sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::print_parameter), (uint32_t) port_index);
 
@@ -495,7 +627,7 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat
 
                }
 
-               adj->set_value (pc->plugin_to_ui (plugin->get_parameter (port_index)));
+               adj->set_value (pc->internal_to_interface (plugin->get_parameter (port_index)));
 
                /* XXX memory leak: SliderController not destroyed by ControlUI
                   destructor, and manage() reports object hierarchy
@@ -529,7 +661,7 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat
                control_ui->display_label->set_name ("ParameterValueDisplay");
 
                control_ui->display->add (*control_ui->display_label);
-               Gtkmm2ext::set_size_request_to_display_given_text (*control_ui->display, "-99,99", 2, 2);
+               Gtkmm2ext::set_size_request_to_display_given_text (*control_ui->display, "-888.8g", 2, 6);
 
                control_ui->display->show_all ();
 
@@ -539,7 +671,17 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat
                MeterInfo * info = new MeterInfo(port_index);
                control_ui->meterinfo = info;
 
-               info->meter = new FastMeter (5, 5, FastMeter::Vertical);
+               info->meter = new FastMeter (
+                               5, 5, FastMeter::Vertical, 0,
+                               0x0000aaff,
+                               0x008800ff, 0x008800ff,
+                               0x00ff00ff, 0x00ff00ff,
+                               0xcccc00ff, 0xcccc00ff,
+                               0xffaa00ff, 0xffaa00ff,
+                               0xff0000ff,
+                               ARDOUR_UI::config()->canvasvar_MeterBackgroundBot.get(),
+                               ARDOUR_UI::config()->canvasvar_MeterBackgroundTop.get()
+                               );
 
                info->min_unbound = desc.min_unbound;
                info->max_unbound = desc.max_unbound;
@@ -550,6 +692,9 @@ GenericPluginUI::build_control_ui (guint32 port_index, boost::shared_ptr<Automat
                control_ui->vbox = manage (new VBox);
                control_ui->hbox = manage (new HBox);
 
+               control_ui->hbox->set_spacing(1);
+               control_ui->vbox->set_spacing(3);
+
                control_ui->label.set_angle(90);
                control_ui->hbox->pack_start (control_ui->label, false, false);
                control_ui->hbox->pack_start (*info->meter, false, false);
@@ -598,8 +743,8 @@ GenericPluginUI::astate_clicked (ControlUI* cui, uint32_t /*port*/)
        MenuList& items (automation_menu->items());
 
        items.clear ();
-       items.push_back (MenuElem (_("Manual"),
-                                  sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::set_automation_state), (AutoState) Off, cui)));
+       items.push_back (MenuElem (S_("Automation|Manual"),
+                                  sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::set_automation_state), (AutoState) ARDOUR::Off, cui)));
        items.push_back (MenuElem (_("Play"),
                                   sigc::bind (sigc::mem_fun(*this, &GenericPluginUI::set_automation_state), (AutoState) Play, cui)));
        items.push_back (MenuElem (_("Write"),
@@ -650,9 +795,8 @@ GenericPluginUI::update_control_display (ControlUI* cui)
 
        cui->ignore_change++;
 
-       if (cui->combo) {
-               std::map<string,float>::iterator it;
-               for (it = cui->combo_map->begin(); it != cui->combo_map->end(); ++it) {
+       if (cui->combo && cui->scale_points) {
+               for (ARDOUR::Plugin::ScalePoints::iterator it = cui->scale_points->begin(); it != cui->scale_points->end(); ++it) {
                        if (it->second == val) {
                                cui->combo->set_active_text(it->first);
                                break;
@@ -694,29 +838,18 @@ GenericPluginUI::control_port_toggled (ControlUI* cui)
 void
 GenericPluginUI::control_combo_changed (ControlUI* cui)
 {
-       if (!cui->ignore_change) {
+       if (!cui->ignore_change && cui->scale_points) {
                string value = cui->combo->get_active_text();
-               std::map<string,float> mapping = *cui->combo_map;
-               insert->automation_control(cui->parameter())->set_value(mapping[value]);
+               insert->automation_control (cui->parameter())->set_value ((*cui->scale_points)[value]);
        }
 }
 
-void
-GenericPluginUI::processor_active_changed (boost::weak_ptr<Processor> weak_processor)
-{
-       ENSURE_GUI_THREAD (*this, &GenericPluginUI::processor_active_changed, weak_processor)
-
-       boost::shared_ptr<Processor> processor = weak_processor.lock();
-
-       bypass_button.set_active (!processor || !processor->active());
-}
-
 bool
 GenericPluginUI::start_updating (GdkEventAny*)
 {
        if (output_controls.size() > 0 ) {
                screen_update_connection.disconnect();
-               screen_update_connection = ARDOUR_UI::instance()->RapidScreenUpdate.connect
+               screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect
                        (sigc::mem_fun(*this, &GenericPluginUI::output_update));
        }
        return false;
@@ -728,11 +861,11 @@ GenericPluginUI::stop_updating (GdkEventAny*)
        for (vector<ControlUI*>::iterator i = input_controls.begin(); i != input_controls.end(); ++i) {
                (*i)->controller->stop_updating ();
        }
-       
+
        if (output_controls.size() > 0 ) {
                screen_update_connection.disconnect();
        }
-       
+
        return false;
 }