Index: gtk2_ardour/generic_pluginui.cc
===================================================================
--- gtk2_ardour/generic_pluginui.cc	(revision 6960)
+++ gtk2_ardour/generic_pluginui.cc	(working copy)
@@ -121,6 +121,61 @@
 	}
 }
 
+// Some functions useful for calculation the 'similarity' of two plugin control
+// labels. Maybe there are standard library functions that do these things, but
+// I don't know what they are...
+
+static int get_number(string label) {
+static const char *digits = "0123456789";
+int value = -1;
+
+	unsigned int 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 int matching_chars_at_head(const string s1, const string s2) {
+int n = 0;
+int length;
+
+	length = min(s1.length(), s2.length());
+	while (n < length) {
+		if (!match_or_digit(s1[n], s2[n]))
+			break;
+		n++;
+	} 
+	return n;
+}
+
+static int matching_chars_at_tail(const string s1, const string s2) {
+int n = 0;
+int s1pos, s2pos;
+
+	s1pos = s1.length();
+	s2pos = s2.length();
+	while (s1pos-- > 0 && s2pos-- > 0) {
+		if (!match_or_digit(s1[s1pos], s2[s2pos])	)
+			break;
+		n++;
+	} 
+	return n;
+}
+
+// #include <iomanip>
+
+static const guint32 min_controls_per_column = 17, max_controls_per_column = 24;
+static const float default_similarity_threshold = 0.3;
+
 void
 GenericPluginUI::build ()
 
@@ -135,6 +190,7 @@
 	int output_rows, output_cols;
 	int button_rows, button_cols;
 	guint32 n_ins=0, n_outs = 0;
+	int tallest_column = 0;	
 
 	prefheight = 30;
 	hpacker.set_spacing (10);
@@ -161,6 +217,7 @@
 
 	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);
 
@@ -175,6 +232,7 @@
 	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) {
 
@@ -187,34 +245,15 @@
 			}
 
 			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 ((cui = build_control_ui (i, plugin->get_nth_control (i))) == 0) {
 				error << string_compose(_("Plugin Editor: could not build control element for port %1"), i) << endmsg;
 				continue;
 			}
 				
 			if (cui->control || cui->clickbox || cui->combo) {
-
-				box->pack_start (*cui, false, false);
-
+				// get all of the controls into a list so we can lay them out
+				// a bit more nicely
+				cui_controls_list.push_back(cui);
 			} else if (cui->button) {
 
 				if (button_row == button_rows) {
@@ -240,35 +279,99 @@
 					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
-				 
-				if (output_row == output_rows) {
-					output_row = 0;
-					if (++output_col == output_cols) {
-						output_cols += 2;
-						output_table.resize (output_rows, output_cols);
+			}
+		} 
+	}
+
+	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;
+		}
+
+		// cerr << "label: " << label << " sim: " << fixed << setprecision(3) << similarity_scores[i] << " num: " << numbers_in_labels[i] << endl;
+		previous_label = label;		                       
+	}
+
+	
+	// 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);
+	}
+	
+
+	i = 0;
+	for (vector<ControlUI*>::iterator cuip = cui_controls_list.begin(); cuip != cui_controls_list.end(); ++cuip, ++i) {
+
+		ControlUI* cui = *cuip;
+		/* if we are scrollable, just use one long column */
+		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);
+					
+					if (tallest_column < x) {
+						tallest_column = x;
 					}
+					x = 0;
+				} else {
+					HSeparator *split = new HSeparator();
+					split->set_size_request(-1, 5);
+					box->pack_start(*split, false, false, 0);
 				}
-				
-				output_table.attach (*cui, output_col, output_col + 1, output_row, output_row+1, 
-						     FILL|EXPAND, FILL);
- 
-				output_row++;
-				*/
 			}
-				
-			/* 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);
+		previous_label = cui->label.get_text();
 
-		} 
 	}
+	
+	/* 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 = (tallest_column + 1) * 20;
+
 	n_ins = plugin->get_info()->n_inputs;
 	n_outs = plugin->get_info()->n_outputs;
 
@@ -283,6 +386,7 @@
 	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);
 	}
