582e9ba06d359ee167ca2a60b495cd6d4fe9d69f
angie
  Wed Feb 12 10:56:52 2014 -0800
In snp125+ track controls, don't show options for class and validationcodes that don't appear in the table.  For example, validation codes
by-hapmap and by-1000genomes are not applicable for non-human genomes;
and only a subset of the class codes are used, sometimes a small subset.
refs #12490

diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c
index fb77ce3..6e09033 100644
--- src/hg/hgTrackUi/hgTrackUi.c
+++ src/hg/hgTrackUi/hgTrackUi.c
@@ -230,82 +230,183 @@
 safef(cartVar, sizeof(cartVar), "%s.include_%s", track, attributeVar);
 jsMakeCheckboxGroupSetClearButton(cartVar, TRUE);
 puts(" ");
 jsMakeCheckboxGroupSetClearButton(cartVar, FALSE);
 printf("</TD></TR>\n<TR><TD>\n");
 boolean foundInCart = FALSE;
 struct slName *selectedAttributes = snp125FilterFromCart(cart, track, attributeVar, &foundInCart);
 // Include all by default:
 if (! foundInCart)
     selectedAttributes = slNameListFromStringArray(values, menuSize);
 cgiMakeCheckboxGroupWithVals(cartVar, labels, values, menuSize, selectedAttributes,
 			     SNP125_FILTER_COLUMNS);
 printf("</TD></TR>\n");
 }
 
-static char *commaSepFromSqlSetTypeDecl(struct sqlFieldInfo *fi, char *table)
-/* Destructively prepare fi->type for chopCommas: strip initial "set(" and final ")",
- * informatively errAborting if not found, and strip the single-quote characters
- * that mysql puts around each field. */
+static char *commaSepFromSqlEnumOrSetTypeDecl(struct sqlFieldInfo *fi, char *type, char *table)
+/* Destructively prepare fi->type for chopCommas. type is either "enum" or "set".
+ * Strip the initial "set(" or "enum(" and final ")", informatively errAborting if not found,
+ * and strip the single-quote characters that mysql puts around each field. */
+{
+if (sameString(type, "enum"))
+    {
+    if (!startsWith("enum(", fi->type))
+	errAbort("Expected %s.%s's type to begin with 'enum(' but got '%s'",
+		 table, fi->field, fi->type);
+    }
+else if (sameString(type, "set"))
     {
     if (!startsWith("set(", fi->type))
 	errAbort("Expected %s.%s's type to begin with 'set(' but got '%s'",
 		 table, fi->field, fi->type);
-char *vals = fi->type + strlen("set(");
+    }
+char *vals = fi->type + strlen(type) + 1;
 char *rightParen = strrchr(vals, ')');
 if (rightParen == NULL || rightParen[1] != '\0')
     errAbort("Expected %s.%s's type to end with ')' but got '%s'",
 	     table, fi->field, fi->type);
 else
     *rightParen = '\0';
 stripChar(vals, '\'');
 return vals;
 }
 
+static struct slName *snp125FixClassGlobals(struct trackDb *tdb)
+/* Fix snp125Class* global variables to contain only the classes that are present
+ * in the SQL enum type definition. Return a list of classes that are not present. */
+{
+struct sqlConnection *conn = hAllocConn(database);
+struct sqlFieldInfo *fi, *fiList = sqlFieldInfoGet(conn, tdb->table);
+hFreeConn(&conn);
+struct slName *unusedList = NULL;
+boolean foundClass = FALSE;
+for (fi = fiList;  fi != NULL;  fi = fi->next)
+    {
+    if (sameString(fi->field, "class"))
+	{
+	foundClass = TRUE;
+	char *vals = commaSepFromSqlEnumOrSetTypeDecl(fi, "enum", tdb->table);
+	char *values[64]; // max 11 in older tables
+	int valCount = chopCommas(vals, values);
+	char *labels[valCount];
+	char *defaults[valCount];
+	char *oldVars[valCount];
+	// Use labels from old static array
+	int i;
+	for (i = 0;  i < valCount;  i++)
+	    {
+	    int oldIx = stringArrayIx(values[i], snp125ClassDataName, snp125ClassArraySize);
+	    labels[i] = snp125ClassLabels[oldIx];
+	    defaults[i] = snp125ClassDefault[oldIx];
+	    oldVars[i] = snp125ClassOldColorVars[oldIx];
+	    }
+	// Make a list of unused values;
+	for (i = 0;  i < snp125ClassArraySize;  i++)
+	    {
+	    if (stringArrayIx(snp125ClassDataName[i], values, valCount) < 0)
+		slAddHead(&unusedList, slNameNew(snp125ClassDataName[i]));
+	    }
+	// Now overwrite old globals with the correct contents.
+	snp125ClassArraySize = valCount;
+	for (i = 0;  i < valCount;  i++)
+	    {
+	    snp125ClassDataName[i] = cloneString(values[i]);
+	    snp125ClassLabels[i] = cloneString(labels[i]);
+	    snp125ClassDefault[i] = cloneString(defaults[i]);
+	    snp125ClassOldColorVars[i] = cloneString(oldVars[i]);
+	    }
+	}
+    }
+if (! foundClass)
+    errAbort("Didn't find definition of func field in %s", tdb->table);
+return unusedList;
+}
+
+static void snp125MakeHiddenInputsForUnused(char *cartVar, struct slName *unusedList)
+/* If this db's snpNNN table uses only a small subset of the global arrays, but some
+ * other db's snpNNN table uses a larger subset, we don't want to have the effect of
+ * turning off the checkboxes that aren't used in this db's snpNNN.  So make hidden
+ * inputs to pretend that all unused values' checkboxes are checked. */
+{
+struct slName *unused;
+for (unused = unusedList;  unused != NULL;  unused = unused->next)
+    cgiMakeHiddenVar(cartVar, unused->name);
+}
+
 static void snp137PrintFunctionFilterControls(struct trackDb *tdb)
 /* As of snp137, show func filter choices based on sql field set
  * values and Sequence Ontology (SO) terms so we won't have to
  * hardcode menus as new functional categories are added. */
 {
 struct sqlConnection *conn = hAllocConn(database);
 struct sqlFieldInfo *fi, *fiList = sqlFieldInfoGet(conn, tdb->table);
 hFreeConn(&conn);
 for (fi = fiList;  fi != NULL;  fi = fi->next)
     {
     if (sameString(fi->field, "func"))
 	{
-	char *vals = commaSepFromSqlSetTypeDecl(fi, tdb->table);
+	char *vals = commaSepFromSqlEnumOrSetTypeDecl(fi, "set", tdb->table);
 	char *values[128]; // 22 values as of snp137
 	int valCount = chopCommas(vals, values);
 	char *labels[valCount];
 	int i;
 	for (i = 0;  i < valCount;  i++)
 	    {
 	    if (sameString(values[i], "unknown"))
 		labels[i] = "Unknown";
 	    else
 		labels[i] = snpMisoLinkFromFunc(values[i]);
 	    }
 	snp125PrintFilterControls(tdb->track, "Function", "func", labels, values, valCount);
-	return;;
+	return;
 	}
     }
 errAbort("Didn't find definition of func field in %s", tdb->table);
 }
 
+int snp125ValidArraySize(int version)
+/* Figure out how many validation options are applicable to this database and version. */
+{
+// Cache result since it costs a mysql query and won't change
+static int size = 0;
+if (size == 0)
+    {
+    size = snp125ValidArraySizeNonHuman;
+    if (sameString(hOrganism(database), "Human"))
+	{
+	size = snp125ValidArraySizeHuman;
+	if (version < 130)
+	    size--; // no by-1000genomes
+	}
+    }
+return size;
+}
+
+static void snp125MakeHiddenInputsForValid(char *cartVar, int version)
+/* Non-human dbs' snpNNN tables use only a subset of the validation codes, but human dbs'
+ * snpNNN tables use all of them.  When making options for non-human dbs, we don't want
+ * to have the effect of turning off the checkboxes that aren't used (but would be for human).
+ * So make hidden inputs to pretend that all unused values' checkboxes are checked. */
+{
+int i;
+for (i = snp125ValidArraySize(version);  i < snp125ValidArraySizeHuman;  i++)
+    cgiMakeHiddenVar(cartVar, snp125ValidDataName[i]);
+}
+
 static void snp125PrintFilterControlSection(struct trackDb *tdb, int version,
-					    boolean molTypeHasMito)
+					    boolean molTypeHasMito,
+					    struct slName *snp125UnusedClasses)
 /* Print a collapsible section of filtering controls on SNP properties, first numeric
  * and then enum/set. */
 {
 char cartVar[512];
 printf("<TR><TD colspan=2><A name=\"filterControls\"></TD></TR>\n");
 jsBeginCollapsibleSection(cart, tdb->track, "filterByAttribute", "Filtering Options", FALSE);
 
 printf("<BR>\n");
 safef(cartVar, sizeof(cartVar), "%s.minAvHet", tdb->track);
 double minAvHet = cartUsualDouble(cart, cartVar,
 			     // Check old cart var name:
 			     cartUsualDouble(cart, "snp125AvHetCutoff", SNP125_DEFAULT_MIN_AVHET));
 printf("<B>Minimum <A HREF=\"#AvHet\">Average Heterozygosity</A>:</B>&nbsp;");
 cgiMakeDoubleVar(cartVar, minAvHet, 6);
 printf("<BR>\n");
@@ -350,32 +451,36 @@
 
 printf("<B>Filter by attribute:</B><BR>\n");
 printf("Check the boxes below to include SNPs with those attributes.  "
        "In order to be displayed, a SNP must pass the filter for each "
        "category.  \n"
        "Some assemblies may not contain any SNPs that have some of the "
        "listed attributes.\n"
        "<BR><BR>\n");
 
 printf("<TABLE border=0 cellspacing=0 cellpadding=0>\n");
 if (version <= 127)
     snp125PrintFilterControls(tdb->track, "Location Type", "locType", snp125LocTypeLabels,
 			 snp125LocTypeDataName, snp125LocTypeArraySize);
 snp125PrintFilterControls(tdb->track, "Class", "class", snp125ClassLabels,
 			  snp125ClassDataName, snp125ClassArraySize);
+safef(cartVar, sizeof(cartVar), "%s.include_%s", tdb->track, "class");
+snp125MakeHiddenInputsForUnused(cartVar, snp125UnusedClasses);
 snp125PrintFilterControls(tdb->track, "Validation", "valid", snp125ValidLabels,
-			  snp125ValidDataName, snp125ValidArraySize);
+			  snp125ValidDataName, snp125ValidArraySize(version));
+safef(cartVar, sizeof(cartVar), "%s.include_%s", tdb->track, "valid");
+snp125MakeHiddenInputsForValid(cartVar, version);
 if (version < 137)
     {
     int funcArraySize = (version < 130) ? snp125FuncArraySize : (snp125FuncArraySize - 1);
     snp125PrintFilterControls(tdb->track, "Function", "func", snp125FuncLabels,
 			      snp125FuncDataName, funcArraySize);
     }
 else
     snp137PrintFunctionFilterControls(tdb);
 int molTypeArraySize = snp125MolTypeArraySize;
 if (! molTypeHasMito)
     molTypeArraySize--;
 snp125PrintFilterControls(tdb->track, "Molecule Type", "molType", snp125MolTypeLabels,
 			  snp125MolTypeDataName, molTypeArraySize);
 if (version >= 132)
     {
@@ -443,31 +548,31 @@
 
 static void snp125ResetColorVarsIfNecessary(struct trackDb *tdb, char *buttonVar, int version)
 /* If the 'Set defaults' button has been clicked, remove all color-control cart variables. */
 {
 // Note we use CGI, not cart, to detect a click:
 if (isNotEmpty(cgiOptionalString(buttonVar)))
     {
     char cartVar[512];
     safef(cartVar, sizeof(cartVar), "%s.colorSource", tdb->track);
     cartRemove(cart, cartVar);
     cartRemove(cart, snp125ColorSourceOldVar);
     snp125RemoveColorVars(cart, snp125LocTypeOldColorVars,  TRUE, snp125LocTypeArraySize,
 			  tdb->track, "locType");
     snp125RemoveColorVars(cart, snp125ClassOldColorVars, TRUE, snp125ClassArraySize,
 			  tdb->track, "class");
-    snp125RemoveColorVars(cart, snp125ValidOldColorVars, TRUE, snp125ValidArraySize,
+    snp125RemoveColorVars(cart, snp125ValidOldColorVars, TRUE, snp125ValidArraySizeHuman,
 			  tdb->track, "valid");
     int funcArraySize = (version < 130) ? snp125FuncArraySize : (snp125FuncArraySize - 1);
     snp125RemoveColorVars(cart, snp125FuncOldColorVars, TRUE, funcArraySize,
 			  tdb->track, "func");
     snp125RemoveColorVars(cart, snp125MolTypeOldColorVars, TRUE, snp125MolTypeArraySize,
 			  tdb->track, "molType");
     snp125RemoveColorVars(cart, snp132ExceptionVarName, FALSE, snp132ExceptionArraySize,
 			  tdb->track, "exceptions");
     snp125RemoveColorVars(cart, snp132BitfieldVarName, FALSE, snp132BitfieldArraySize,
 			  tdb->track, "bitfields");
     }
 }
 
 void snp125PrintColorControlSection(struct trackDb *tdb, int version, boolean molTypeHasMito)
 /* Print a collapsible section of color controls: user selects an attribute to color by,
@@ -523,35 +628,37 @@
        "options for the feature selected above will be used to color items;\n"
        "color options for other features will not be shown.\n");
 
 if (version > 127 && colorSourceCart == snp125ColorSourceLocType)
     colorSourceCart = SNP125_DEFAULT_COLOR_SOURCE;
 switch (colorSourceCart)
     {
     int funcArraySize, excArraySize, molTypeArraySize;
     case snp125ColorSourceLocType:
                 snp125PrintColorSpec(tdb->track, "locType", snp125LocTypeOldColorVars, TRUE,
                                      snp125LocTypeLabels, snp125LocTypeDefault,
                                      snp125LocTypeArraySize);
                 break;
     case snp125ColorSourceClass:
                 snp125PrintColorSpec(tdb->track, "class", snp125ClassOldColorVars, TRUE,
-                                     snp125ClassLabels, snp125ClassDefault, snp125ClassArraySize);
+                                     snp125ClassLabels, snp125ClassDefault,
+				     snp125ClassArraySize);
                 break;
     case snp125ColorSourceValid:
                 snp125PrintColorSpec(tdb->track, "valid", snp125ValidOldColorVars, TRUE,
-                                     snp125ValidLabels, snp125ValidDefault, snp125ValidArraySize);
+                                     snp125ValidLabels, snp125ValidDefault,
+				     snp125ValidArraySize(version));
                 break;
     case snp125ColorSourceFunc:
                 funcArraySize = (version < 130) ? snp125FuncArraySize : (snp125FuncArraySize - 1);
                 snp125PrintColorSpec(tdb->track, "func", snp125FuncOldColorVars, TRUE,
                                      snp125FuncLabels, snp125FuncDefault, funcArraySize);
                 break;
     case snp125ColorSourceMolType:
 		molTypeArraySize = snp125MolTypeArraySize;
 		if (! molTypeHasMito)
 		    molTypeArraySize--;
                 snp125PrintColorSpec(tdb->track, "molType", snp125MolTypeOldColorVars, TRUE,
 				     snp125MolTypeLabels, snp125MolTypeDefault, molTypeArraySize);
                 break;
     case snp125ColorSourceExceptions:
 		excArraySize = snp132ExceptionArraySize;
@@ -589,57 +696,55 @@
     if (sameString(*enumVals, "mito"))
 	gotMito = TRUE;
     enumVals++;
     }
 hFreeConn(&conn);
 return gotMito;
 }
 
 void snp125Ui(struct trackDb *tdb)
 /* UI for dbSNP version 125 and later. */
 {
 char *orthoTable = snp125OrthoTable(tdb, NULL);
 int version = snpVersion(tdb->track);
 char cartVar[512];
 jsInit();
-
-if (version < 130)
-    snp125ValidArraySize--; // no by-1000genomes
+struct slName *snp125UnusedClasses = snp125FixClassGlobals(tdb);
 
 if (isNotEmpty(orthoTable) && hTableExists(database, orthoTable))
     {
     printf("<BR><B>Include Chimp state and observed human alleles in name: </B>&nbsp;");
     safef(cartVar, sizeof(cartVar), "%s.extendedNames", tdb->track);
     snp125ExtendedNames = cartUsualBoolean(cart, cartVar,
 			  // Check old cart var name for backwards compatibility w/ old sessions:
 					   cartUsualBoolean(cart, "snp125ExtendedNames", FALSE));
     cgiMakeCheckBox(cartVar, snp125ExtendedNames);
     printf("<BR>(If enabled, chimp allele is displayed first, then '>', then human alleles).");
     printf("<BR><BR>\n");
     }
 else
     puts("<BR>");
 
 // Make wrapper table for collapsible sections:
 puts("<TABLE border=0 cellspacing=0 cellpadding=0>");
 
 snp125OfferGeneTracksForFunction(tdb);
 
 boolean molTypeHasMito = snp125CheckMolTypeForMito(tdb);
 
 puts("<TR><TD colspan=2><BR></TD></TR>");
-snp125PrintFilterControlSection(tdb, version, molTypeHasMito);
+snp125PrintFilterControlSection(tdb, version, molTypeHasMito, snp125UnusedClasses);
 puts("<TR><TD colspan=2><BR></TD></TR>");
 
 snp125PrintColorControlSection(tdb, version, molTypeHasMito);
 // End wrapper table for collapsible sections:
 puts("</TABLE>");
 }
 
 void snpUi(struct trackDb *tdb)
 /* Put up UI snp data. */
 {
 int   snpSource  = 0;
 int   snpMolType = 0;
 int   snpClass   = 0;
 int   snpValid   = 0;
 int   snpFunc    = 0;