Index: src/hg/lib/hui.c
===================================================================
RCS file: /projects/compbio/cvsroot/kent/src/hg/lib/hui.c,v
retrieving revision 1.214
retrieving revision 1.215
diff -b -B -U 1000000 -r1.214 -r1.215
--- src/hg/lib/hui.c	24 Jun 2009 23:23:50 -0000	1.214
+++ src/hg/lib/hui.c	25 Jun 2009 00:05:51 -0000	1.215
@@ -1,5315 +1,5316 @@
 /* hui - human genome user interface common controls. */
 
 #include "common.h"
 #include "hash.h"
 #include "cheapcgi.h"
 #include "htmshell.h"
 #include "jksql.h"
 #include "jsHelper.h"
 #include "sqlNum.h"
 #include "cart.h"
 #include "hdb.h"
 #include "hui.h"
 #include "hCommon.h"
 #include "hgConfig.h"
 #include "chainCart.h"
 #include "chainDb.h"
 #include "netCart.h"
 #include "obscure.h"
 #include "wiggle.h"
 #include "phyloTree.h"
 #include "hgMaf.h"
 #include "udc.h"
 #include "customTrack.h"
 #include "encode/encodePeak.h"
 
 static char const rcsid[] = "$Id$";
 
 #define SMALLBUF 128
 #define MAX_SUBGROUP 9
 #define ADD_BUTTON_LABEL        "add"
 #define CLEAR_BUTTON_LABEL      "clear"
 #define JBUFSIZE 2048
 
 #define PM_BUTTON "<A NAME=\"%s\"></A><A HREF=\"#%s\"><IMG height=18 width=18 onclick=\"return (setCheckBoxesThatContain('%s',%s,true,'%s','','%s') == false);\" id=\"btn_%s\" src=\"../images/%s\" alt=\"%s\"></A>\n"
 #define DEF_BUTTON "<A NAME=\"%s\"></A><A HREF=\"#%s\"><IMG onclick=\"setCheckBoxesThatContain('%s',true,false,'%s','','%s'); return (setCheckBoxesThatContain('%s',false,false,'%s','_defOff','%s') == false);\" id=\"btn_%s\" src=\"../images/%s\" alt=\"%s\"></A>\n"
 #define DEFAULT_BUTTON(nameOrId,anc,beg,contains) printf(DEF_BUTTON,(anc),(anc),(nameOrId),        (beg),(contains),(nameOrId),(beg),(contains),(anc),"defaults_sm.png","default")
 #define    PLUS_BUTTON(nameOrId,anc,beg,contains) printf(PM_BUTTON, (anc),(anc),(nameOrId),"true", (beg),(contains),(anc),"add_sm.gif",   "+")
 #define   MINUS_BUTTON(nameOrId,anc,beg,contains) printf(PM_BUTTON, (anc),(anc),(nameOrId),"false",(beg),(contains),(anc),"remove_sm.gif","-")
 
 #define ENCODE_DCC_DOWNLOADS "encodeDCC"
 
 static boolean makeNamedDownloadsLink(struct trackDb *tdb,char *name)
 // Make a downloads link (if appropriate and then returns TRUE)
 {
 // Downloads directory if this is ENCODE
 if(trackDbSetting(tdb, "wgEncode") != NULL)
     {
     printf("<A HREF=\"http://%s/goldenPath/%s/%s/%s/\" title='Open dowloads directory in a new window' TARGET=ucscDownloads>%s</A>",
             cfgOptionDefault("downloads.server", "hgdownload.cse.ucsc.edu"),
             trackDbSettingOrDefault(tdb, "origAssembly","hg18"),
             ENCODE_DCC_DOWNLOADS,
             tdb->tableName,name);
     return TRUE;
     }
 return FALSE;
 }
 
 boolean makeDownloadsLink(struct trackDb *tdb)
 // Make a downloads link (if appropriate and then returns TRUE)
 {
 return makeNamedDownloadsLink(tdb,"Downloads");
 }
 
 boolean makeSchemaLink(char *db,struct trackDb *tdb,char *label)
 // Make a table schema link (if appropriate and then returns TRUE)
 {
 #define SCHEMA_LINKED "<A HREF=\"../cgi-bin/hgTables?db=%s&hgta_group=%s&hgta_track=%s&hgta_table=%s&hgta_doSchema=describe+table+schema\" TARGET=ucscSchema%s>%s</A>"
 if (hTableOrSplitExists(db, tdb->tableName))
     {
 	char *tableName  = tdb->tableName;
 	if (sameString(tableName, "mrna"))
 	    tableName = "all_mrna";
     char *hint = " title='Open table schema in new window'";
     if( label == NULL)
         label = " View table schema";
 
     if(tdbIsCompositeChild(tdb))
         printf(SCHEMA_LINKED, db, tdb->parent->grp, tdb->parent->tableName,tableName,hint,label);
     else
         printf(SCHEMA_LINKED, db, tdb->grp, tdb->tableName,tableName,hint,label);
 
     return TRUE;
     }
 return FALSE;
 }
 
 boolean metadataToggle(struct trackDb *tdb,char *title,boolean embeddedInText,boolean showLongLabel)
 /* If metadata exists, create a link that will allow toggling it's display */
 {
 metadata_t *metadata = metadataSettingGet(tdb);
 if(metadata != NULL)
     {
     printf("%s<A HREF='#a_meta_%s' onclick='return metadataShowHide(\"%s\");' title='Show metadata details...'>%s</A>",
            (embeddedInText?"&nbsp;":"<P>"),tdb->tableName,tdb->tableName, title);
     printf("<DIV id='div_%s_meta' style='display:none;'><!--<table>",tdb->tableName);
     if(showLongLabel)
         printf("<tr onmouseover=\"this.style.cursor='text';\"><td colspan=2>%s</td></tr>",tdb->longLabel);
     printf("<tr onmouseover=\"this.style.cursor='text';\"><td align=right><i>shortLabel:</i></td><td nowrap>%s</td></tr>",tdb->shortLabel);
     int ix = (sameString(metadata->values[0],"wgEncode")?1:0); // first should be project.
     for(;ix<metadata->count;ix++)
         {
         if(sameString(metadata->tags[ix],"fileName"))
             {
             printf("<tr onmouseover=\"this.style.cursor='text';\"><td align=right><i>%s:</i></td><td nowrap>",metadata->tags[ix]);
             makeNamedDownloadsLink(tdb->parent != NULL? tdb->parent :tdb ,metadata->values[ix]);
             printf("</td></tr>");
             }
         else
             if(!sameString(metadata->tags[ix],"subId")
                 && !sameString(metadata->tags[ix],"composite"))
             printf("<tr onmouseover=\"this.style.cursor='text';\"><td align=right><i>%s:</i></td><td nowrap>%s</td></tr>",metadata->tags[ix],metadata->values[ix]);
         }
     printf("</table>--></div>");
     metadataFree(&metadata);
     return TRUE;
     }
 return FALSE;
 }
 
 void extraUiLinks(char *db,struct trackDb *tdb)
 /* Show downlaods, schema and metadata links where appropriate */
 {
 boolean schemaLink = (isCustomTrack(tdb->tableName) == FALSE)
                   && (hTableOrSplitExists(db, tdb->tableName));
 boolean metadataLink = (!tdbIsComposite(tdb))
                   && trackDbSetting(tdb, "metadata");
 boolean downloadLink = (trackDbSetting(tdb, "wgEncode") != NULL);
 boolean moreThanOne = (schemaLink && metadataLink)
                    || (schemaLink && downloadLink)
                    || (downloadLink && metadataLink);
 
 printf("<P>");
 if(moreThanOne)
     printf("<table><tr><td nowrap>View table: ");
 
 if(schemaLink)
     {
     makeSchemaLink(db,tdb,(moreThanOne ? "schema":"View table schema"));
     if(downloadLink || metadataLink)
         printf(", ");
     }
 if(downloadLink)
     {
     struct trackDb *trueTdb = tdbIsCompositeChild(tdb)? tdb->parent: tdb;
     makeNamedDownloadsLink(trueTdb,(moreThanOne ? "downloads":"Downloads"));
     if(metadataLink)
         printf(",");
     }
 if (metadataLink)
     metadataToggle(tdb,"metadata", TRUE, TRUE);
 
 if(moreThanOne)
     printf("</td></tr></table>");
 puts("</P>");
 }
 
 
 char *hUserCookie()
 /* Return our cookie name. */
 {
 if (hIsMgcServer())
     return "mgcuid";
 else
     return cfgOptionDefault("central.cookie", "hguid");
 }
 
 void setUdcCacheDir()
 /* set the path to the udc cache dir */
 {
 udcSetDefaultDir(cfgOptionDefault("udc.cacheDir", udcDefaultDir()));
 }
 
 
 char *wrapWhiteFont(char *s)
 /* Write white font around s */
 {
 static char buf[256];
 safef(buf, sizeof(buf), "<FONT COLOR=\"#FFFFFF\">%s</FONT>", s);
 return buf;
 }
 
 char *hDocumentRoot()
 /* get the path to the DocumentRoot, or the default */
 {
 return cfgOptionDefault("browser.documentRoot", DOCUMENT_ROOT);
 }
 
 char *hHelpFile(char *fileRoot)
 /* Given a help file root name (e.g. "hgPcrResult" or "cutters"),
  * prepend the complete help directory path and add .html suffix.
  * Do not free the statically allocated result. */
 {
 static char helpName[PATH_LEN];
 /* This cfgOption comes from Todd Lowe's hgTrackUi.c addition (r1.230): */
 char *helpDir = cfgOption("help.html");
 if (helpDir != NULL)
     safef(helpName, sizeof(helpName), "%s/%s.html", helpDir, fileRoot);
 else
     safef(helpName, sizeof(helpName), "%s%s/%s.html", hDocumentRoot(),
 	  HELP_DIR, fileRoot);
 return helpName;
 }
 
 char *hFileContentsOrWarning(char *file)
 /* Return the contents of the html file, or a warning message.
  * The file path may begin with hDocumentRoot(); if it doesn't, it is
  * assumed to be relative and hDocumentRoot() will be prepended. */
 {
 if (isEmpty(file))
     return cloneString("<BR>Program Error: Empty file name for include file"
 		       "<BR>\n");
 char path[PATH_LEN];
 char *docRoot = hDocumentRoot();
 if (startsWith(docRoot, file))
     safecpy(path, sizeof path, file);
 else
     safef(path, sizeof path, "%s/%s", docRoot, file);
 if (! fileExists(path))
     {
     char message[1024];
     safef(message, sizeof(message), "<BR>Program Error: Missing file %s</BR>",
 	  path);
     return cloneString(message);
     }
 /* If the file is there but not readable, readInGulp will errAbort,
  * but I think that is serious enough that errAbort is OK. */
 char *result;
 readInGulp(path, &result, NULL);
 return result;
 }
 
 char *hCgiRoot()
 /* get the path to the CGI directory.
  * Returns NULL when not running as a CGI (unless specified by browser.cgiRoot) */
 {
 static char defaultDir[PATH_LEN];
 char *scriptFilename = getenv("SCRIPT_FILENAME");
 if(scriptFilename)
     {
     char dir[PATH_LEN], name[FILENAME_LEN], extension[FILEEXT_LEN];
     dir[0] = 0;
     splitPath(scriptFilename, dir, name, extension);
     safef(defaultDir, sizeof(defaultDir), "%s", dir);
     int len = strlen(defaultDir);
     // Get rid of trailing slash to be consistent with hDocumentRoot
     if(defaultDir[len-1] == '/')
         defaultDir[len-1] = 0;
     }
 else
     {
     defaultDir[0] = 0;
     }
 return cfgOptionDefault("browser.cgiRoot", defaultDir);
 }
 
 char *hBackgroundImage()
 /* get the path to the configured background image to use, or the default */
 {
 return cfgOptionDefault("browser.background", DEFAULT_BACKGROUND);
 }
 
 /******  Some stuff for tables of controls ******/
 
 struct controlGrid *startControlGrid(int columns, char *align)
 /* Start up a control grid. */
 {
 struct controlGrid *cg;
 AllocVar(cg);
 cg->columns = columns;
 cg->align = cloneString(align);
 cg->rowOpen = FALSE;
 return cg;
 }
 
 void controlGridEndRow(struct controlGrid *cg)
 /* Force end of row. */
 {
 printf("</tr>");
 cg->rowOpen = FALSE;
 cg->columnIx = 0;
 }
 
 void controlGridStartCell(struct controlGrid *cg)
 /* Start a new cell in control grid. */
 {
 if (cg->columnIx == cg->columns)
     controlGridEndRow(cg);
 if (!cg->rowOpen)
     {
     printf("<tr>");
     cg->rowOpen = TRUE;
     }
 if (cg->align)
     printf("<td align=%s>", cg->align);
 else
     printf("<td>");
 }
 
 void controlGridEndCell(struct controlGrid *cg)
 /* End cell in control grid. */
 {
 printf("</td>");
 ++cg->columnIx;
 }
 
 void endControlGrid(struct controlGrid **pCg)
 /* Finish up a control grid. */
 {
 struct controlGrid *cg = *pCg;
 if (cg != NULL)
     {
     int i;
     if (cg->columnIx != 0 && cg->columnIx < cg->columns)
 	for( i = cg->columnIx; i <= cg->columns; i++)
 	    printf("<td>&nbsp;</td>\n");
     if (cg->rowOpen)
 	printf("</tr>\n");
     printf("</table>\n");
     freeMem(cg->align);
     freez(pCg);
     }
 }
 
 /******  Some stuff for hide/dense/full controls ******/
 
 static char *hTvStrings[] =
 /* User interface strings for track visibility controls. */
     {
     "hide",
     "dense",
     "full",
     "pack",
     "squish"
     };
 
 enum trackVisibility hTvFromStringNoAbort(char *s)
 /* Given a string representation of track visibility, return as
  * equivalent enum. */
 {
 int vis = stringArrayIx(s, hTvStrings, ArraySize(hTvStrings));
 if (vis < 0)
     vis = 0;  // don't generate bogus value on invalid input
 return vis;
 }
 
 enum trackVisibility hTvFromString(char *s)
 /* Given a string representation of track visibility, return as
  * equivalent enum. */
 {
 enum trackVisibility vis = hTvFromStringNoAbort(s);
 if ((int)vis < 0)
    errAbort("Unknown visibility %s", s);
 return vis;
 }
 
 char *hStringFromTv(enum trackVisibility vis)
 /* Given enum representation convert to string. */
 {
 return hTvStrings[vis];
 }
 
 void hTvDropDownClassWithJavascript(char *varName, enum trackVisibility vis, boolean canPack, char *class,char * javascript)
 /* Make track visibility drop down for varName with style class */
 {
 static char *noPack[] =
     {
     "hide",
     "dense",
     "full",
     };
 static char *pack[] =
     {
     "hide",
     "dense",
     "squish",
     "pack",
     "full",
     };
 static int packIx[] = {tvHide,tvDense,tvSquish,tvPack,tvFull};
 if (canPack)
     cgiMakeDropListClassWithStyleAndJavascript(varName, pack, ArraySize(pack),
                             pack[packIx[vis]], class, TV_DROPDOWN_STYLE,javascript);
 else
     cgiMakeDropListClassWithStyleAndJavascript(varName, noPack, ArraySize(noPack),
                             noPack[vis], class, TV_DROPDOWN_STYLE,javascript);
 }
 
 void hTvDropDownClassVisOnly(char *varName, enum trackVisibility vis,
 	boolean canPack, char *class, char *visOnly)
 /* Make track visibility drop down for varName with style class,
 	and potentially limited to visOnly */
 {
 static char *denseOnly[] =
     {
     "hide",
     "dense",
     };
 static char *squishOnly[] =
     {
     "hide",
     "squish",
     };
 static char *packOnly[] =
     {
     "hide",
     "pack",
     };
 static char *fullOnly[] =
     {
     "hide",
     "full",
     };
 static char *noPack[] =
     {
     "hide",
     "dense",
     "full",
     };
 static char *pack[] =
     {
     "hide",
     "dense",
     "squish",
     "pack",
     "full",
     };
 static int packIx[] = {tvHide,tvDense,tvSquish,tvPack,tvFull};
 if (visOnly != NULL)
     {
     int visIx = (vis > 0) ? 1 : 0;
     if (sameWord(visOnly,"dense"))
 	cgiMakeDropListClassWithStyle(varName, denseOnly, ArraySize(denseOnly),
 		denseOnly[visIx], class, TV_DROPDOWN_STYLE);
     else if (sameWord(visOnly,"squish"))
 	cgiMakeDropListClassWithStyle(varName, squishOnly,
                 ArraySize(squishOnly), squishOnly[visIx],
                 class, TV_DROPDOWN_STYLE);
     else if (sameWord(visOnly,"pack"))
 	cgiMakeDropListClassWithStyle(varName, packOnly, ArraySize(packOnly),
 		packOnly[visIx], class, TV_DROPDOWN_STYLE);
     else if (sameWord(visOnly,"full"))
 	cgiMakeDropListClassWithStyle(varName, fullOnly, ArraySize(fullOnly),
 		fullOnly[visIx], class, TV_DROPDOWN_STYLE);
     else			/* default when not recognized */
 	cgiMakeDropListClassWithStyle(varName, denseOnly, ArraySize(denseOnly),
 		denseOnly[visIx], class, TV_DROPDOWN_STYLE);
     }
     else
     {
     if (canPack)
 	cgiMakeDropListClassWithStyle(varName, pack, ArraySize(pack),
                             pack[packIx[vis]], class, TV_DROPDOWN_STYLE);
     else
 	cgiMakeDropListClassWithStyle(varName, noPack, ArraySize(noPack),
                             noPack[vis], class, TV_DROPDOWN_STYLE);
     }
 }
 
 void hideShowDropDown(char *varName, boolean show, char *class)
 /* Make hide/show dropdown for varName */
 {
 static char *hideShow[] =
     {
     "hide",
     "show"
     };
 cgiMakeDropListClassWithStyle(varName, hideShow, ArraySize(hideShow),
                     hideShow[show], class, TV_DROPDOWN_STYLE);
 }
 
 
 /****** Some stuff for stsMap related controls *******/
 
 static char *stsMapOptions[] = {
     "All Genetic",
     "Genethon",
     "Marshfield",
     "deCODE",
     "GeneMap 99",
     "Whitehead YAC",
     "Whitehead RH",
     "Stanford TNG",
 };
 
 enum stsMapOptEnum smoeStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, stsMapOptions);
 if (x < 0)
    errAbort("Unknown option %s", string);
 return x;
 }
 
 char *smoeEnumToString(enum stsMapOptEnum x)
 /* Convert from enum to string representation. */
 {
 return stsMapOptions[x];
 }
 
 void smoeDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, stsMapOptions, ArraySize(stsMapOptions),
 	curVal);
 }
 
 /****** Some stuff for stsMapMouseNew related controls *******/
 
 static char *stsMapMouseOptions[] = {
     "All Genetic",
     "WICGR Genetic Map",
     "MGD Genetic Map",
     "RH",
 };
 
 enum stsMapMouseOptEnum smmoeStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, stsMapMouseOptions);
 if (x < 0)
    errAbort("Unknown option %s", string);
 return x;
 }
 
 char *smmoeEnumToString(enum stsMapMouseOptEnum x)
 /* Convert from enum to string representation. */
 {
 return stsMapMouseOptions[x];
 }
 
 void smmoeDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, stsMapMouseOptions, ArraySize(stsMapMouseOptions),
 	curVal);
 }
 
 /****** Some stuff for stsMapRat related controls *******/
 
 static char *stsMapRatOptions[] = {
     "All Genetic",
     "FHHxACI",
     "SHRSPxBN",
     "RH",
 };
 
 enum stsMapRatOptEnum smroeStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, stsMapRatOptions);
 if (x < 0)
    errAbort("Unknown option %s", string);
 return x;
 }
 
 char *smroeEnumToString(enum stsMapRatOptEnum x)
 /* Convert from enum to string representation. */
 {
 return stsMapRatOptions[x];
 }
 
 void smroeDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, stsMapRatOptions, ArraySize(stsMapRatOptions),
 	curVal);
 }
 
 /****** Some stuff for fishClones related controls *******/
 
 static char *fishClonesOptions[] = {
     "Fred Hutchinson CRC",
     "National Cancer Institute",
     "Sanger Centre",
     "Roswell Park Cancer Institute",
     "Cedars-Sinai Medical Center",
     "Los Alamos National Lab",
     "UC San Francisco",
 };
 
 enum fishClonesOptEnum fcoeStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, fishClonesOptions);
 if (x < 0)
    errAbort("Unknown option %s", string);
 return x;
 }
 
 char *fcoeEnumToString(enum fishClonesOptEnum x)
 /* Convert from enum to string representation. */
 {
 return fishClonesOptions[x];
 }
 
 void fcoeDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, fishClonesOptions, ArraySize(fishClonesOptions),
 	curVal);
 }
 
 /****** Some stuff for recombRate related controls *******/
 
 static char *recombRateOptions[] = {
     "deCODE Sex Averaged Distances",
     "deCODE Female Distances",
     "deCODE Male Distances",
     "Marshfield Sex Averaged Distances",
     "Marshfield Female Distances",
     "Marshfield Male Distances",
     "Genethon Sex Averaged Distances",
     "Genethon Female Distances",
     "Genethon Male Distances",
 };
 
 enum recombRateOptEnum rroeStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, recombRateOptions);
 if (x < 0)
    errAbort("Unknown option %s", string);
 return x;
 }
 
 char *rroeEnumToString(enum recombRateOptEnum x)
 /* Convert from enum to string representation. */
 {
 return recombRateOptions[x];
 }
 
 void rroeDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, recombRateOptions, ArraySize(recombRateOptions),
 	curVal);
 }
 
 /****** Some stuff for recombRateRat related controls *******/
 
 static char *recombRateRatOptions[] = {
     "SHRSPxBN Sex Averaged Distances",
     "FHHxACI Sex Averaged Distances",
 };
 
 enum recombRateRatOptEnum rrroeStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, recombRateRatOptions);
 if (x < 0)
    errAbort("Unknown option %s", string);
 return x;
 }
 
 char *rrroeEnumToString(enum recombRateRatOptEnum x)
 /* Convert from enum to string representation. */
 {
 return recombRateRatOptions[x];
 }
 
 void rrroeDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, recombRateRatOptions, ArraySize(recombRateRatOptions),
 	curVal);
 }
 
 /****** Some stuff for recombRateMouse related controls *******/
 
 static char *recombRateMouseOptions[] = {
     "WI Genetic Map Sex Averaged Distances",
     "MGD Genetic Map Sex Averaged Distances",
 };
 
 enum recombRateMouseOptEnum rrmoeStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, recombRateMouseOptions);
 if (x < 0)
    errAbort("Unknown option %s", string);
 return x;
 }
 
 char *rrmoeEnumToString(enum recombRateMouseOptEnum x)
 /* Convert from enum to string representation. */
 {
 return recombRateMouseOptions[x];
 }
 
 void rrmoeDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, recombRateMouseOptions, ArraySize(recombRateMouseOptions),
 	curVal);
 }
 
 /****** Some stuff for CGH NCI60 related controls *******/
 
 static char *cghNci60Options[] = {
     "Tissue Averages",
     "BREAST",
     "CNS",
     "COLON",
     "LEUKEMIA",
     "LUNG",
     "MELANOMA",
     "OVARY",
     "PROSTATE",
     "RENAL",
     "All Cell Lines",
 };
 
 enum cghNci60OptEnum cghoeStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, cghNci60Options);
 if (x < 0)
    errAbort("Unknown option %s", string);
 return x;
 }
 
 char *cghoeEnumToString(enum cghNci60OptEnum x)
 /* Convert from enum to string representation. */
 {
 return cghNci60Options[x];
 }
 
 void cghoeDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, cghNci60Options, ArraySize(cghNci60Options),
 	curVal);
 }
 
 /****** Some stuff for nci60 related controls *******/
 
 static char *nci60Options[] = {
     "Tissue Averages",
     "All Cell Lines",
     "BREAST",
     "CNS",
     "COLON",
     "LEUKEMIA",
     "MELANOMA",
     "OVARIAN",
     "PROSTATE",
     "RENAL",
     "NSCLC",
     "DUPLICATE",
     "UNKNOWN"
     };
 
 enum nci60OptEnum nci60StringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, nci60Options);
 if (x < 0)
    errAbort("hui::nci60StringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *nci60EnumToString(enum nci60OptEnum x)
 /* Convert from enum to string representation. */
 {
 return nci60Options[x];
 }
 
 void nci60DropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, nci60Options, ArraySize(nci60Options),
 	curVal);
 }
 
 
 /*** Control of base/codon coloring code: ***/
 
 /* All options (parallel to enum baseColorDrawOpt): */
 static char *baseColorDrawAllOptionLabels[] =
     {
     BASE_COLOR_DRAW_OFF_LABEL,
     BASE_COLOR_DRAW_GENOMIC_CODONS_LABEL,
     BASE_COLOR_DRAW_ITEM_CODONS_LABEL,
     BASE_COLOR_DRAW_DIFF_CODONS_LABEL,
     BASE_COLOR_DRAW_ITEM_BASES_LABEL,
     BASE_COLOR_DRAW_DIFF_BASES_LABEL,
     };
 static char *baseColorDrawAllOptionValues[] =
     {
     BASE_COLOR_DRAW_OFF,
     BASE_COLOR_DRAW_GENOMIC_CODONS,
     BASE_COLOR_DRAW_ITEM_CODONS,
     BASE_COLOR_DRAW_DIFF_CODONS,
     BASE_COLOR_DRAW_ITEM_BASES,
     BASE_COLOR_DRAW_DIFF_BASES,
     };
 
 /* Subset of options for tracks with CDS info but not item sequence: */
 static char *baseColorDrawGenomicOptionLabels[] =
     {
     BASE_COLOR_DRAW_OFF_LABEL,
     BASE_COLOR_DRAW_GENOMIC_CODONS_LABEL,
     };
 static char *baseColorDrawGenomicOptionValues[] =
     {
     BASE_COLOR_DRAW_OFF,
     BASE_COLOR_DRAW_GENOMIC_CODONS,
     };
 
 /* Subset of options for tracks with aligned item sequence but not CDS: */
 static char *baseColorDrawItemOptionLabels[] =
     {
     BASE_COLOR_DRAW_OFF_LABEL,
     BASE_COLOR_DRAW_ITEM_BASES_LABEL,
     BASE_COLOR_DRAW_DIFF_BASES_LABEL,
     };
 static char *baseColorDrawItemOptionValues[] =
     {
     BASE_COLOR_DRAW_OFF,
     BASE_COLOR_DRAW_ITEM_BASES,
     BASE_COLOR_DRAW_DIFF_BASES,
     };
 
 enum baseColorDrawOpt baseColorDrawOptStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, baseColorDrawAllOptionValues);
 if (x < 0)
    errAbort("hui::baseColorDrawOptStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 static boolean baseColorGotCds(struct trackDb *tdb)
 /* Return true if this track has CDS info according to tdb (or is genePred). */
 {
 boolean gotIt = FALSE;
 char *setting = trackDbSetting(tdb, BASE_COLOR_USE_CDS);
 if (isNotEmpty(setting))
     {
     if (sameString(setting, "all") || sameString(setting, "given") ||
 	sameString(setting, "genbank") || startsWith("table", setting))
 	gotIt = TRUE;
     else if (! sameString(setting, "none"))
 	errAbort("trackDb for %s, setting %s: unrecognized value \"%s\".  "
 		 "must be one of {none,all,given,genbank,table}.",
 		 tdb->tableName, BASE_COLOR_USE_CDS, setting);
     }
 else if (startsWith("genePred", tdb->type))
     gotIt = TRUE;
 return gotIt;
 }
 
 static boolean baseColorGotSequence(struct trackDb *tdb)
 /* Return true if this track has aligned sequence according to tdb. */
 {
 boolean gotIt = FALSE;
 char *setting = trackDbSetting(tdb, BASE_COLOR_USE_SEQUENCE);
 if (isNotEmpty(setting))
     {
     if (sameString(setting, "genbank") || sameString(setting, "seq") ||
 	sameString(setting, "ss") || startsWith("extFile", setting) ||
 	sameString(setting, "hgPcrResult") || sameString(setting, "nameIsSequence") ||
-	sameString(setting, "seq1Seq2"))
+	sameString(setting, "seq1Seq2") || sameString(setting, "lfExtra"))
 	gotIt = TRUE;
     else if (differentString(setting, "none"))
 	errAbort("trackDb for %s, setting %s: unrecognized value \"%s\".  "
-		 "must be one of {none,genbank,seq,ss,extFile,nameIsSequence,seq1Seq2,hgPcrResult}.",
+		 "must be one of {none,genbank,seq,ss,extFile,nameIsSequence,seq1Seq2,"
+		 "hgPcrResult,lfExtra}.",
 		 tdb->tableName, BASE_COLOR_USE_SEQUENCE, setting);
     }
 return gotIt;
 }
 
 void baseColorDrawOptDropDown(struct cart *cart, struct trackDb *tdb)
 /* Make appropriately labeled drop down of options if any are applicable.*/
 {
 enum baseColorDrawOpt curOpt = baseColorDrawOptEnabled(cart, tdb);
 char *curValue = baseColorDrawAllOptionValues[curOpt];
 char var[512];
 boolean gotCds = baseColorGotCds(tdb);
 boolean gotSeq = baseColorGotSequence(tdb);
 
 safef(var, sizeof(var), "%s." BASE_COLOR_VAR_SUFFIX, tdb->tableName);
 if (gotCds && gotSeq)
     {
     puts("<P><B>Color track by codons or bases:</B>");
     cgiMakeDropListFull(var, baseColorDrawAllOptionLabels,
 			baseColorDrawAllOptionValues,
 			ArraySize(baseColorDrawAllOptionLabels),
 			curValue, NULL);
     printf("<BR><A HREF=\"%s\">Help on mRNA coloring</A><BR>",
 	   CDS_MRNA_HELP_PAGE);
     }
 else if (gotCds)
     {
     puts("<P><B>Color track by codons:</B>");
     cgiMakeDropListFull(var, baseColorDrawGenomicOptionLabels,
 			baseColorDrawGenomicOptionValues,
 			ArraySize(baseColorDrawGenomicOptionLabels),
 			curValue, NULL);
     printf("<BR><A HREF=\"%s\">Help on codon coloring</A><BR>",
 	   CDS_HELP_PAGE);
     }
 else if (gotSeq)
     {
     puts("<P><B>Color track by bases:</B>");
     cgiMakeDropListFull(var, baseColorDrawItemOptionLabels,
 			baseColorDrawItemOptionValues,
 			ArraySize(baseColorDrawItemOptionLabels),
 			curValue, NULL);
     printf("<BR><A HREF=\"%s\">Help on mRNA coloring</A><BR>",
 	   CDS_MRNA_HELP_PAGE);
     }
 }
 
 enum baseColorDrawOpt baseColorDrawOptEnabled(struct cart *cart,
 					      struct trackDb *tdb)
 /* Query cart & trackDb to determine what drawing mode (if any) is enabled. */
 {
 char *stringVal = NULL;
 assert(cart);
 assert(tdb);
 
 /* trackDb can override default of OFF; cart can override trackDb. */
 stringVal = trackDbSettingOrDefault(tdb, BASE_COLOR_DEFAULT,
 				    BASE_COLOR_DRAW_OFF);
 stringVal = cartUsualStringClosestToHome(cart, tdb, FALSE, BASE_COLOR_VAR_SUFFIX,stringVal);
 
 return baseColorDrawOptStringToEnum(stringVal);
 }
 
 
 /*** Control of fancy indel display code: ***/
 
 static boolean indelAppropriate(struct trackDb *tdb)
 /* Return true if it makes sense to offer indel display options for tdb. */
 {
 return (tdb && startsWith("psl", tdb->type) &&
 	(cfgOptionDefault("browser.indelOptions", NULL) != NULL));
 }
 
 void indelShowOptions(struct cart *cart, struct trackDb *tdb)
 /* Make HTML inputs for indel display options if any are applicable. */
 {
 if (indelAppropriate(tdb))
     {
     boolean showDoubleInsert, showQueryInsert, showPolyA;
     char var[512];
     indelEnabled(cart, tdb, 0.0, &showDoubleInsert, &showQueryInsert, &showPolyA);
     printf("<P><B>Alignment Gap/Insertion Display Options</B><BR>\n");
     safef(var, sizeof(var), "%s_%s", INDEL_DOUBLE_INSERT, tdb->tableName);
     cgiMakeCheckBox(var, showDoubleInsert);
     printf("Draw double horizontal lines when both genome and query have "
 	   "an insertion "
 	   "<BR>\n");
     safef(var, sizeof(var), "%s_%s", INDEL_QUERY_INSERT, tdb->tableName);
     cgiMakeCheckBox(var, showQueryInsert);
     printf("Draw a vertical blue line for an insertion at the beginning or "
 	   "end of the query, orange for insertion in the middle of the query"
 	   "<BR>\n");
     safef(var, sizeof(var), "%s_%s", INDEL_POLY_A, tdb->tableName);
     /* We can highlight valid polyA's only if we have query sequence --
      * so indelPolyA code piggiebacks on baseColor code: */
     if (baseColorGotSequence(tdb))
 	{
 	cgiMakeCheckBox(var, showPolyA);
 	printf("Draw a vertical green line where query has a polyA tail "
 	       "insertion"
 	       "<BR>\n");
 	}
 
     printf("<A HREF=\"%s\">Help on alignment gap/insertion display options</A>"
 	   "<BR>\n",
 	   INDEL_HELP_PAGE);
     }
 }
 
 static boolean tdbOrCartBoolean(struct cart *cart, struct trackDb *tdb,
 				char *settingName, char *defaultOnOff)
 /* Query cart & trackDb to determine if a boolean variable is set. */
 {
 boolean alreadySet;
 char optionStr[512];
 alreadySet = !sameString("off",
 		trackDbSettingOrDefault(tdb, settingName, defaultOnOff));
 safef(optionStr, sizeof(optionStr), "%s_%s",
       settingName, tdb->tableName);
 alreadySet = cartUsualBoolean(cart, optionStr, alreadySet);
 return alreadySet;
 }
 
 void indelEnabled(struct cart *cart, struct trackDb *tdb, float basesPerPixel,
 		  boolean *retDoubleInsert, boolean *retQueryInsert,
 		  boolean *retPolyA)
 /* Query cart & trackDb to determine what indel display (if any) is enabled. Set
  * basesPerPixel to 0.0 to disable check for zoom level.  */
 {
 boolean apropos = indelAppropriate(tdb);
 if (apropos && (basesPerPixel > 0.0))
     {
     // check indel max zoom
     float showIndelMaxZoom = trackDbFloatSettingOrDefault(tdb, "showIndelMaxZoom", -1.0);
     if ((showIndelMaxZoom >= 0)
         && ((basesPerPixel > showIndelMaxZoom) || (showIndelMaxZoom == 0.0)))
         apropos = FALSE;
     }
 
 if (retDoubleInsert)
     *retDoubleInsert = apropos &&
 	tdbOrCartBoolean(cart, tdb, INDEL_DOUBLE_INSERT, "off");
 if (retQueryInsert)
     *retQueryInsert = apropos &&
 	tdbOrCartBoolean(cart, tdb, INDEL_QUERY_INSERT, "off");
 if (retPolyA)
     *retPolyA = apropos &&
 	tdbOrCartBoolean(cart, tdb, INDEL_POLY_A, "off");
 }
 
 
 /****** base position (ruler) controls *******/
 
 static char *zoomOptions[] = {
     ZOOM_1PT5X,
     ZOOM_3X,
     ZOOM_10X,
     ZOOM_BASE
     };
 
 void zoomRadioButtons(char *var, char *curVal)
 /* Make a list of radio buttons for all zoom options */
 {
 int i;
 int size = ArraySize(zoomOptions);
 for (i = 0; i < size; i++)
     {
     char *s = zoomOptions[i];
     cgiMakeRadioButton(var, s, sameString(s, curVal));
     printf(" %s &nbsp;&nbsp;", s);
     }
 }
 
 /****** Some stuff for affy related controls *******/
 
 static char *affyOptions[] = {
     "Chip Type",
     "Chip ID",
     "Tissue Averages"
     };
 
 enum affyOptEnum affyStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, affyOptions);
 if (x < 0)
    errAbort("hui::affyStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *affyEnumToString(enum affyOptEnum x)
 /* Convert from enum to string representation. */
 {
 return affyOptions[x];
 }
 
 void affyDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, affyOptions, ArraySize(affyOptions),
 	curVal);
 }
 
 /****** Some stuff for affy all exon related controls *******/
 
 static char *affyAllExonOptions[] = {
     "Chip",
     "Tissue Averages"
     };
 
 enum affyAllExonOptEnum affyAllExonStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, affyAllExonOptions);
 if (x < 0)
    errAbort("hui::affyAllExonStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *affyAllExonEnumToString(enum affyAllExonOptEnum x)
 /* Convert from enum to string representation. */
 {
 return affyAllExonOptions[x];
 }
 
 void affyAllExonDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, affyAllExonOptions, ArraySize(affyAllExonOptions),
 	curVal);
 }
 
 /****** Some stuff for Rosetta related controls *******/
 
 static char *rosettaOptions[] = {
     "All Experiments",
     "Common Reference and Other",
     "Common Reference",
     "Other Exps"
 };
 
 static char *rosettaExonOptions[] = {
     "Confirmed Only",
     "Predicted Only",
     "All",
 };
 
 enum rosettaOptEnum rosettaStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, rosettaOptions);
 if (x < 0)
    errAbort("hui::rosettaStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *rosettaEnumToString(enum rosettaOptEnum x)
 /* Convert from enum to string representation. */
 {
 return rosettaOptions[x];
 }
 
 void rosettaDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, rosettaOptions, ArraySize(rosettaOptions),
 	curVal);
 }
 
 enum rosettaExonOptEnum rosettaStringToExonEnum(char *string)
 /* Convert from string to enum representation of exon types. */
 {
 int x = stringIx(string, rosettaExonOptions);
 if (x < 0)
    errAbort("hui::rosettaStringToExonEnum() - Unknown option %s", string);
 return x;
 }
 
 char *rosettaExonEnumToString(enum rosettaExonOptEnum x)
 /* Convert from enum to string representation of exon types. */
 {
 return rosettaExonOptions[x];
 }
 
 void rosettaExonDropDown(char *var, char *curVal)
 /* Make drop down of exon type options. */
 {
 cgiMakeDropList(var, rosettaExonOptions, ArraySize(rosettaExonOptions), curVal);
 }
 
 /****** Options for the net track level display options *******/
 static char *netLevelOptions[] = {
     NET_LEVEL_0,
     NET_LEVEL_1,
     NET_LEVEL_2,
     NET_LEVEL_3,
     NET_LEVEL_4,
     NET_LEVEL_5,
     NET_LEVEL_6
     };
 
 enum netLevelEnum netLevelStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, netLevelOptions);
 if (x < 0)
    errAbort("hui::netLevelStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *netLevelEnumToString(enum netLevelEnum x)
 /* Convert from enum to string representation. */
 {
 return netLevelOptions[x];
 }
 
 void netLevelDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, netLevelOptions, ArraySize(netLevelOptions), curVal);
 }
 
 /****** Options for the net track color options *******/
 static char *netColorOptions[] = {
     CHROM_COLORS,
     GRAY_SCALE
     };
 
 enum netColorEnum netColorStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, netColorOptions);
 if (x < 0)
    errAbort("hui::netColorStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *netColorEnumToString(enum netColorEnum x)
 /* Convert from enum to string representation. */
 {
 return netColorOptions[x];
 }
 
 void netColorDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, netColorOptions, ArraySize(netColorOptions), curVal);
 }
 
 /****** Options for the chain track color options *******/
 static char *chainColorOptions[] = {
     CHROM_COLORS,
     SCORE_COLORS,
     NO_COLORS
     };
 
 enum chainColorEnum chainColorStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, chainColorOptions);
 if (x < 0)
    errAbort("hui::chainColorStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *chainColorEnumToString(enum chainColorEnum x)
 /* Convert from enum to string representation. */
 {
 return chainColorOptions[x];
 }
 
 void chainColorDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, chainColorOptions, ArraySize(chainColorOptions), curVal);
 }
 
 /****** Options for the wiggle track Windowing *******/
 
 static char *wiggleWindowingOptions[] = {
     "maximum",
     "mean",
     "minimum"
     };
 
 enum wiggleWindowingEnum wiggleWindowingStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, wiggleWindowingOptions);
 if (x < 0)
    errAbort("hui::wiggleWindowingStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *wiggleWindowingEnumToString(enum wiggleWindowingEnum x)
 /* Convert from enum to string representation. */
 {
 return wiggleWindowingOptions[x];
 }
 
 void wiggleWindowingDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, wiggleWindowingOptions, ArraySize(wiggleWindowingOptions),
 	curVal);
 }
 
 /****** Options for the wiggle track Smoothing *******/
 
 static char *wiggleSmoothingOptions[] = {
     "OFF", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11",
     "12", "13", "14", "15", "16"
     };
 
 enum wiggleSmoothingEnum wiggleSmoothingStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, wiggleSmoothingOptions);
 if (x < 0)
    errAbort("hui::wiggleSmoothingStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *wiggleSmoothingEnumToString(enum wiggleSmoothingEnum x)
 /* Convert from enum to string representation. */
 {
 return wiggleSmoothingOptions[x];
 }
 
 void wiggleSmoothingDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, wiggleSmoothingOptions, ArraySize(wiggleSmoothingOptions),
 	curVal);
 }
 
 /****** Options for the wiggle track y Line Mark On/Off *******/
 
 static char *wiggleYLineMarkOptions[] = {
     "OFF",
     "ON"
     };
 
 enum wiggleYLineMarkEnum wiggleYLineMarkStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, wiggleYLineMarkOptions);
 if (x < 0)
    errAbort("hui::wiggleYLineMarkStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *wiggleYLineMarkEnumToString(enum wiggleYLineMarkEnum x)
 /* Convert from enum to string representation. */
 {
 return wiggleYLineMarkOptions[x];
 }
 
 void wiggleYLineMarkDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, wiggleYLineMarkOptions, ArraySize(wiggleYLineMarkOptions),
 	curVal);
 }
 
 /****** Options for the wiggle track AutoScale *******/
 
 static char *wiggleScaleOptions[] = {
     "use vertical viewing range setting",
     "auto-scale to data view"
     };
 
 enum wiggleScaleOptEnum wiggleScaleStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, wiggleScaleOptions);
 if (x < 0)
    errAbort("hui::wiggleScaleStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *wiggleScaleEnumToString(enum wiggleScaleOptEnum x)
 /* Convert from enum to string representation. */
 {
 return wiggleScaleOptions[x];
 }
 
 void wiggleScaleDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, wiggleScaleOptions, ArraySize(wiggleScaleOptions),
 	curVal);
 }
 
 /****** Options for the wiggle track type of graph *******/
 
 static char *wiggleGraphOptions[] = {
     "points",
     "bar"
     };
 
 enum wiggleGraphOptEnum wiggleGraphStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, wiggleGraphOptions);
 if (x < 0)
    errAbort("hui::wiggleGraphStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *wiggleGraphEnumToString(enum wiggleGraphOptEnum x)
 /* Convert from enum to string representation. */
 {
 return wiggleGraphOptions[x];
 }
 
 void wiggleGraphDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, wiggleGraphOptions, ArraySize(wiggleGraphOptions),
 	curVal);
 }
 
 /****** Options for the wiggle track horizontal grid lines *******/
 
 static char *wiggleGridOptions[] = {
     "ON",
     "OFF"
     };
 
 enum wiggleGridOptEnum wiggleGridStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, wiggleGridOptions);
 if (x < 0)
    errAbort("hui::wiggleGridStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *wiggleGridEnumToString(enum wiggleGridOptEnum x)
 /* Convert from enum to string representation. */
 {
 return wiggleGridOptions[x];
 }
 
 void wiggleGridDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, wiggleGridOptions, ArraySize(wiggleGridOptions),
 	curVal);
 }
 
 /****** Some stuff for wiggle track related controls *******/
 
 static char *wiggleOptions[] = {
     "samples only",
     "linear interpolation"
     };
 
 enum wiggleOptEnum wiggleStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, wiggleOptions);
 if (x < 0)
    errAbort("hui::wiggleStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *wiggleEnumToString(enum wiggleOptEnum x)
 /* Convert from enum to string representation. */
 {
 return wiggleOptions[x];
 }
 
 void wiggleDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, wiggleOptions, ArraySize(wiggleOptions),
 	curVal);
 }
 
 
 /****** Some stuff for GCwiggle track related controls *******/
 
 static char *GCwiggleOptions[] = {
     "samples only",
     "linear interpolation"
     };
 
 enum GCwiggleOptEnum GCwiggleStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, GCwiggleOptions);
 if (x < 0)
    errAbort("hui::GCwiggleStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *GCwiggleEnumToString(enum GCwiggleOptEnum x)
 /* Convert from enum to string representation. */
 {
 return GCwiggleOptions[x];
 }
 
 void GCwiggleDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, GCwiggleOptions, ArraySize(GCwiggleOptions),
 	curVal);
 }
 
 /****** Some stuff for chimp track related controls *******/
 
 static char *chimpOptions[] = {
     "samples only",
     "linear interpolation"
     };
 
 enum chimpOptEnum chimpStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, chimpOptions);
 if (x < 0)
    errAbort("hui::chimpStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *chimpEnumToString(enum chimpOptEnum x)
 /* Convert from enum to string representation. */
 {
 return chimpOptions[x];
 }
 
 void chimpDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, chimpOptions, ArraySize(chimpOptions),
 	curVal);
 }
 
 
 
 /****** Some stuff for mRNA and EST related controls *******/
 
 static void addMrnaFilter(struct mrnaUiData *mud, char *track, char *label, char *key, char *table)
 /* Add an mrna filter */
 {
 struct mrnaFilter *fil;
 char buf[128];
 AllocVar(fil);
 fil->label = label;
 safef(buf, sizeof(buf), "%s_%s", track, key);
 fil->key = cloneString(buf);
 fil->table = table;
 slAddTail(&mud->filterList, fil);
 }
 
 struct mrnaUiData *newBedUiData(char *track)
 /* Make a new  in extra-ui data structure for a bed. */
 {
 struct mrnaUiData *mud;
 char buf[128];  /* Expand me here */
 AllocVar(mud);
 safef(buf, sizeof(buf), "%sFt", track);
 mud->filterTypeVar = cloneString(buf);
 safef(buf, sizeof(buf), "%sLt", track);
 mud->logicTypeVar = cloneString(buf);
 addMrnaFilter(mud, track, "name", "name",track);
 return mud;
 }
 
 struct mrnaUiData *newMrnaUiData(char *track, boolean isXeno)
 /* Make a new  in extra-ui data structure for mRNA. */
 {
 struct mrnaUiData *mud;
 char buf[128];
 AllocVar(mud);
 safef(buf, sizeof(buf), "%sFt", track);
 mud->filterTypeVar = cloneString(buf);
 safef(buf, sizeof(buf), "%sLt", track);
 mud->logicTypeVar = cloneString(buf);
 if (isXeno)
     addMrnaFilter(mud, track, "organism", "org", "organism");
 addMrnaFilter(mud, track, "accession", "acc", "acc");
 addMrnaFilter(mud, track, "author", "aut", "author");
 addMrnaFilter(mud, track, "library", "lib", "library");
 addMrnaFilter(mud, track, "tissue", "tis", "tissue");
 addMrnaFilter(mud, track, "cell", "cel", "cell");
 addMrnaFilter(mud, track, "keyword", "key", "keyword");
 addMrnaFilter(mud, track, "gene", "gen", "geneName");
 addMrnaFilter(mud, track, "product", "pro", "productName");
 addMrnaFilter(mud, track, "description", "des", "description");
 return mud;
 }
 
 int trackNameAndLabelCmp(const void *va, const void *vb)
 /* Compare to sort on label. */
 {
 const struct trackNameAndLabel *a = *((struct trackNameAndLabel **)va);
 const struct trackNameAndLabel *b = *((struct trackNameAndLabel **)vb);
 return strcmp(a->label, b->label);
 }
 
 char *trackFindLabel(struct trackNameAndLabel *list, char *label)
 /* Try to find label in list. Return NULL if it's
  * not there. */
 {
 struct trackNameAndLabel *el;
 for (el = list; el != NULL; el = el->next)
     {
     if (sameString(el->label, label))
         return label;
     }
 return NULL;
 }
 
 char *genePredDropDown(struct cart *cart, struct hash *trackHash,
                                         char *formName, char *varName)
 /* Make gene-prediction drop-down().  Return track name of
  * currently selected one.  Return NULL if no gene tracks.
  * If formName isn't NULL, it's the form for auto submit (onchange attr).
  * If formName is NULL, no submit occurs when menu is changed */
 {
 char *cartTrack = cartOptionalString(cart, varName);
 struct hashEl *trackList, *trackEl;
 char *selectedName = NULL;
 struct trackNameAndLabel *nameList = NULL, *name;
 char *trackName = NULL;
 
 /* Make alphabetized list of all genePred track names. */
 trackList = hashElListHash(trackHash);
 for (trackEl = trackList; trackEl != NULL; trackEl = trackEl->next)
     {
     struct trackDb *tdb = trackEl->val;
     char *dupe = cloneString(tdb->type);
     char *type = firstWordInLine(dupe);
     if ((sameString(type, "genePred")) && (!sameString(tdb->tableName, "tigrGeneIndex") && !tdbIsComposite(tdb)))
 	{
 	AllocVar(name);
 	name->name = tdb->tableName;
 	name->label = tdb->shortLabel;
 	slAddHead(&nameList, name);
 	}
     freez(&dupe);
     }
 slSort(&nameList, trackNameAndLabelCmp);
 
 /* No gene tracks - not much we can do. */
 if (nameList == NULL)
     {
     slFreeList(&trackList);
     return NULL;
     }
 
 /* Try to find current track - from cart first, then
  * knownGenes, then refGenes. */
 if (cartTrack != NULL)
     selectedName = trackFindLabel(nameList, cartTrack);
 if (selectedName == NULL)
     selectedName = trackFindLabel(nameList, "Known Genes");
 if (selectedName == NULL)
     selectedName = trackFindLabel(nameList, "SGD Genes");
 if (selectedName == NULL)
     selectedName = trackFindLabel(nameList, "BDGP Genes");
 if (selectedName == NULL)
     selectedName = trackFindLabel(nameList, "WormBase Genes");
 if (selectedName == NULL)
     selectedName = trackFindLabel(nameList, "RefSeq Genes");
 if (selectedName == NULL)
     selectedName = nameList->name;
 
 /* Make drop-down list. */
     {
     char javascript[SMALLBUF], *autoSubmit;
     int nameCount = slCount(nameList);
     char **menu;
     int i;
 
     AllocArray(menu, nameCount);
     for (name = nameList, i=0; name != NULL; name = name->next, ++i)
 	{
 	menu[i] = name->label;
 	}
     if (formName == NULL)
         autoSubmit = NULL;
     else
         {
         safef(javascript, sizeof(javascript),
                 "onchange=\"document.%s.submit();\"", formName);
         autoSubmit = javascript;
         }
     cgiMakeDropListFull(varName, menu, menu,
     	                        nameCount, selectedName, autoSubmit);
     freez(&menu);
     }
 
 /* Convert to track name */
 for (name = nameList; name != NULL; name = name->next)
     {
     if (sameString(selectedName, name->label))
         trackName = name->name;
     }
 
 /* Clean up and return. */
 slFreeList(&nameList);
 slFreeList(&trackList);
 return trackName;
 }
 
 struct hash *makeTrackHashWithComposites(char *database, char *chrom,
                                         bool withComposites)
 /* Make hash of trackDb items for this chromosome, including composites,
    not just the subtracks. */
 {
 struct trackDb *tdbs = hTrackDb(database, chrom);
 struct hash *trackHash = newHash(7);
 
 while (tdbs != NULL)
     {
     struct trackDb *tdb = slPopHead(&tdbs);
     if (hTrackOnChrom(tdb, chrom))
         {
         if (tdb->subtracks)
             {
             struct trackDb *subtrack;
             for (subtrack = tdb->subtracks; subtrack != NULL;
                         subtrack = subtrack->next)
                 {
                 /* need track description for hgc */
                 subtrack->html = cloneString(tdb->html);
                 hashAdd(trackHash, subtrack->tableName, subtrack);
                 }
             if (withComposites)
                 hashAdd(trackHash, tdb->tableName, tdb);
             }
         else
             hashAdd(trackHash, tdb->tableName, tdb);
         }
     else
         trackDbFree(&tdb);
     }
 
 return trackHash;
 }
 
 struct hash *makeTrackHash(char *database, char *chrom)
 /* Make hash of trackDb items for this chromosome. */
 {
     return makeTrackHashWithComposites(database, chrom, FALSE);
 }
 
 /****** Stuff for acembly related options *******/
 
 static char *acemblyOptions[] = {
     "all genes",
     "main",
     "putative",
 };
 
 enum acemblyOptEnum acemblyStringToEnum(char *string)
 /* Convert from string to enum representation. */
 {
 int x = stringIx(string, acemblyOptions);
 if (x < 0)
    errAbort("hui::acemblyStringToEnum() - Unknown option %s", string);
 return x;
 }
 
 char *acemblyEnumToString(enum acemblyOptEnum x)
 /* Convert from enum to string representation. */
 {
 return acemblyOptions[x];
 }
 
 void acemblyDropDown(char *var, char *curVal)
 /* Make drop down of options. */
 {
 cgiMakeDropList(var, acemblyOptions, ArraySize(acemblyOptions),
 	curVal);
 }
 
 boolean parseAssignment(char *words, char **name, char **value)
 /* parse <name>=<value>, destroying input words in the process */
 {
 char *p;
 if ((p = index(words, '=')) == NULL)
     return FALSE;
 *p++ = 0;
 if (name)
     *name = words;
 if (value)
     *value = p;
 return TRUE;
 }
 
 static char *getPrimaryType(char *primarySubtrack, struct trackDb *tdb)
 /* Do not free when done. */
 {
 struct trackDb *subtrack = NULL;
 char *type = NULL;
 if (primarySubtrack)
     for (subtrack = tdb->subtracks; subtrack != NULL; subtrack = subtrack->next)
 	{
 	if (sameString(subtrack->tableName, primarySubtrack))
 	    {
 	    type = subtrack->type;
 	    break;
 	    }
 	}
 return type;
 }
 
 boolean hSameTrackDbType(char *type1, char *type2)
 /* Compare type strings: require same string unless both are wig tracks. */
 {
 return (sameString(type1, type2) ||
 	(startsWith("wig ", type1) && startsWith("wig ", type2)));
 }
 
 typedef struct _dividers {
     int count;
     char**subgroups;
     char* setting;
 } dividers_t;
 
 static dividers_t *dividersSettingGet(struct trackDb *parentTdb)
 /* Parses any dividers setting in parent of subtracks */
 {
 dividers_t *dividers = needMem(sizeof(dividers_t));
 dividers->setting    = cloneString(trackDbSetting(parentTdb, "dividers"));
 if(dividers->setting == NULL)
     {
     freeMem(dividers);
     return NULL;
     }
 dividers->subgroups  = needMem(24*sizeof(char*));
 dividers->count      = chopByWhite(dividers->setting, dividers->subgroups,24);
 return dividers;
 }
 static void dividersFree(dividers_t **dividers)
 /* frees any previously obtained dividers setting */
 {
 if(dividers && *dividers)
     {
     freeMem((*dividers)->subgroups);
     freeMem((*dividers)->setting);
     freez(dividers);
     }
 }
 
 typedef struct _hierarchy {
     int count;
     char* subgroup;
     char**membership;
     int*  indents;
     char* setting;
 } hierarchy_t;
 
 static hierarchy_t *hierarchySettingGet(struct trackDb *parentTdb)
 /* Parses any list hierachy instructions setting in parent of subtracks */
 {
 hierarchy_t *hierarchy = needMem(sizeof(hierarchy_t));
 hierarchy->setting     = cloneString(trackDbSetting(parentTdb, "hierarchy"));  // To be freed later
 if(hierarchy->setting == NULL)
     {
     freeMem(hierarchy);
     return NULL;
     }
 int cnt,ix;
 char *words[SMALLBUF];
 cnt = chopLine(hierarchy->setting, words);
 assert(cnt<=ArraySize(words));
 if(cnt <= 1)
     {
     freeMem(hierarchy->setting);
     freeMem(hierarchy);
     return NULL;
     }
 
 hierarchy->membership  = needMem(cnt*sizeof(char*));
 hierarchy->indents     = needMem(cnt*sizeof(int));
 hierarchy->subgroup    = words[0];
 char *name,*value;
 for (ix = 1,hierarchy->count=0; ix < cnt; ix++)
     {
     if (parseAssignment(words[ix], &name, &value))
         {
         hierarchy->membership[hierarchy->count]  = name;
         hierarchy->indents[hierarchy->count] = sqlUnsigned(value);
         hierarchy->count++;
         }
     }
 return hierarchy;
 }
 static void hierarchyFree(hierarchy_t **hierarchy)
 /* frees any previously obtained hierachy settings */
 {
 if(hierarchy && *hierarchy)
     {
     freeMem((*hierarchy)->setting);
     freeMem((*hierarchy)->membership);
     freeMem((*hierarchy)->indents);
     freez(hierarchy);
     }
 }
 
 typedef struct _dimensions {
     int count;
     char**names;
     char**subgroups;
     char* setting;
 } dimensions_t;
 
 boolean dimensionsExist(struct trackDb *parentTdb)
 /* Does this parent track contain dimensions? */
 {
     return (trackDbSetting(parentTdb, "dimensions") != NULL);
 }
 //static boolean dimensionsSubtrackOf(struct trackDb *childTdb)
 ///* Does this child belong to a parent  with dimensions? */
 //{
 //    return (tdbIsCompositeChild(childTdb) && dimensionsExist(childTdb->parent));
 //}
 
 static dimensions_t *dimensionSettingsGet(struct trackDb *parentTdb)
 /* Parses any dimemnions setting in parent of subtracks */
 {
 dimensions_t *dimensions = needMem(sizeof(dimensions_t));
 dimensions->setting = cloneString(trackDbSetting(parentTdb, "dimensions"));  // To be freed later
 if(dimensions->setting == NULL)
     {
     freeMem(dimensions);
     return NULL;
     }
 int cnt,ix;
 char *words[SMALLBUF];
 cnt = chopLine(dimensions->setting,words);
 assert(cnt<=ArraySize(words));
 if(cnt <= 0)
     {
     freeMem(dimensions->setting);
     freeMem(dimensions);
     return NULL;
     }
 
 dimensions->names     = needMem(cnt*sizeof(char*));
 dimensions->subgroups = needMem(cnt*sizeof(char*));
 char *name,*value;
 for (ix = 0,dimensions->count=0; ix < cnt; ix++)
     {
     if (parseAssignment(words[ix], &name, &value))
         {
         dimensions->names[dimensions->count]     = name;
         dimensions->subgroups[dimensions->count] = value;
         dimensions->count++;
         }
     }
 return dimensions;
 }
 static void dimensionsFree(dimensions_t **dimensions)
 /* frees any previously obtained dividers setting */
 {
 if(dimensions && *dimensions)
     {
     freeMem((*dimensions)->setting);
     freeMem((*dimensions)->names);
     freeMem((*dimensions)->subgroups);
     freez(dimensions);
     }
 }
 
 #define SUBGROUP_MAX 9
 
 typedef struct _members {
     int count;
     char * tag;
     char * title;
     char **names;
     char **values;
     char * setting;
 } members_t;
 
 int subgroupCount(struct trackDb *parentTdb)
 /* How many subGroup setting does this parent have? */
 {
 int ix;
 int count = 0;
 for(ix=1;ix<=SUBGROUP_MAX;ix++)
     {
     char subGrp[16];
     safef(subGrp, ArraySize(subGrp), "subGroup%d",ix);
     if(trackDbSetting(parentTdb, subGrp) != NULL)
         count++;
     }
 return count;
 }
 
 char * subgroupSettingByTagOrName(struct trackDb *parentTdb, char *groupNameOrTag)
 /* look for a subGroup by name (ie subGroup1) or tag (ie view) and return an unallocated char* */
 {
 int ix;
 char *setting = NULL;
 if(startsWith("subGroup",groupNameOrTag))
     {
     setting = trackDbSetting(parentTdb, groupNameOrTag);
     if(setting != NULL)
         return setting;
     }
 for(ix=1;ix<=SUBGROUP_MAX;ix++) // How many do we support?
     {
     char subGrp[16];
     safef(subGrp, ArraySize(subGrp), "subGroup%d",ix);
     setting = trackDbSetting(parentTdb, subGrp);
     if(setting != NULL)  // Doesn't require consecutive subgroups
         {
         if(startsWithWord(groupNameOrTag,setting))
             return setting;
         }
     }
 return NULL;
 }
 
 boolean subgroupingExists(struct trackDb *parentTdb, char *groupNameOrTag)
 /* Does this parent track contain a particular subgrouping? */
 {
     return (subgroupSettingByTagOrName(parentTdb,groupNameOrTag) != NULL);
 }
 
 static members_t *subgroupMembersGet(struct trackDb *parentTdb, char *groupNameOrTag)
 /* Parse a subGroup setting line into tag,title, names(optional) and values(optional), returning the count of members or 0 */
 {
 int ix,cnt;
 char *setting = subgroupSettingByTagOrName(parentTdb, groupNameOrTag);
 if(setting == NULL)
     return NULL;
 members_t *members = needMem(sizeof(members_t));
 members->setting = cloneString(setting);
 char *words[SMALLBUF];
 cnt = chopLine(members->setting, words);
 assert(cnt <= ArraySize(words));
 if(cnt <= 1)
     {
     freeMem(members->setting);
     freeMem(members);
     return NULL;
     }
 members->tag   = words[0];
 members->title = strSwapChar(words[1],'_',' '); // Titles replace '_' with space
 members->names = needMem(cnt*sizeof(char*));
 members->values = needMem(cnt*sizeof(char*));
 for (ix = 2,members->count=0; ix < cnt; ix++)
     {
     char *name,*value;
     if (parseAssignment(words[ix], &name, &value))
         {
         members->names[members->count]  = name;
         members->values[members->count] = strSwapChar(value,'_',' ');
         members->count++;
         }
     }
 return members;
 }
 static members_t *subgroupMembersGetByDimension(struct trackDb *parentTdb, char dimension)
 /* Finds the dimension requested and return its associated
  * subgroupMembership, returning the count of members or 0 */
 {
 int ix;
 dimensions_t *dimensions = dimensionSettingsGet(parentTdb);
 if(dimensions!=NULL)
     {
     for(ix=0;ix<dimensions->count;ix++)
         {
         if(startsWith("dimension",dimensions->names[ix])
         && strlen(dimensions->names[ix]) == 10
         && lastChar(dimensions->names[ix]) == dimension)
             {
             members_t *members = subgroupMembersGet(parentTdb, dimensions->subgroups[ix]);
             dimensionsFree(&dimensions);
             return members;
             }
         }
     dimensionsFree(&dimensions);
     }
 return NULL;
 }
 
 static void subgroupMembersFree(members_t **members)
 /* frees memory for subgroupMembers lists */
 {
 if(members && *members)
     {
     freeMem((*members)->setting);
     freeMem((*members)->names);
     freeMem((*members)->values);
     freez(members);
     }
 }
 
 int multViewCount(struct trackDb *parentTdb)
 /* returns the number of multiView views declared */
 {
 char *setting = subgroupSettingByTagOrName(parentTdb,"view");
 if(setting == NULL)
     return 0;
 
 setting = cloneString(setting);
 int cnt;
 char *words[32];
 cnt = chopLine(setting, words);
 freeMem(setting);
 return (cnt - 1);
 }
 
 #ifdef ADD_MULT_SELECT_DIMENSIONS
 // This is the beginning of work on allowing subtrack selection by multi-select drop downs
 typedef struct _selectables {
     int count;
     members_t **subgroups;
     boolean**multiple;
 } selectables_t;
 
 boolean selectablesExist(struct trackDb *parentTdb)
 /* Does this parent track contain selectables option? */
 {
     return (trackDbSetting(parentTdb, "selectableBy") != NULL);
 }
 #endif//def ADD_MULT_SELECT_DIMENSIONS
 
 typedef struct _membership {
     int count;
     char **subgroups;    // Ary of Tags in parentTdb->subGroupN and in childTdb->subGroups (ie view)
     char **membership;   // Ary of Tags of subGroups that child belongs to (ie PK)
     char **titles;       // Ary of Titles of subGroups a child belongs to (ie Peak)
     char * setting;
 } membership_t;
 
 static membership_t *subgroupMembershipGet(struct trackDb *childTdb)
 /* gets all the subgroup membership for a child track */
 {
 membership_t *membership = needMem(sizeof(membership_t));
 membership->setting = cloneString(trackDbSetting(childTdb, "subGroups"));
 if(membership->setting == NULL)
     {
     freeMem(membership);
     return NULL;
     }
 
 int ix,cnt;
 char *words[SMALLBUF];
 cnt = chopLine(membership->setting, words);
 assert(cnt <= ArraySize(words));
 if(cnt <= 0)
     {
     freeMem(membership->setting);
     freeMem(membership);
     return NULL;
     }
 membership->subgroups  = needMem(cnt*sizeof(char*));
 membership->membership = needMem(cnt*sizeof(char*));
 membership->titles     = needMem(cnt*sizeof(char*));
 for (ix = 0,membership->count=0; ix < cnt; ix++)
     {
     char *name,*value;
     if (parseAssignment(words[ix], &name, &value))
         {
         membership->subgroups[membership->count]  = name;
         membership->membership[membership->count] = value;//strSwapChar(value,'_',' ');
         members_t* members = subgroupMembersGet(childTdb->parent, name);
         membership->titles[membership->count] = NULL; // default
         if(members != NULL)
             {
             int ix2 = stringArrayIx(value,members->names,members->count);
             if(ix2 != -1)
                 membership->titles[membership->count] = strSwapChar(cloneString(members->values[ix2]),'_',' ');
             subgroupMembersFree(&members);
             }
         membership->count++;
         }
     }
 return membership;
 }
 static void subgroupMembershipFree(membership_t **membership)
 /* frees subgroupMembership memory */
 {
 if(membership && *membership)
     {
     int ix;
     for(ix=0;ix<(*membership)->count;ix++) { freeMem((*membership)->titles[ix]); }
     freeMem((*membership)->titles);
     freeMem((*membership)->setting);
     freeMem((*membership)->subgroups);
     freeMem((*membership)->membership);
     freez(membership);
     }
 }
 
 boolean subgroupFind(struct trackDb *childTdb, char *name,char **value)
 /* looks for a single tag in a child track's subGroups setting */
 {
 if(value != NULL)
     *value = NULL;
 char *subGroups = trackDbSetting(childTdb, "subGroups");
 if(subGroups == (void*)NULL)
     return FALSE;
 char *found = stringIn(name, subGroups);
 if(found == (void*)NULL)
     return FALSE;
 if(found[strlen(name)] != '=')
     return FALSE;
 if(value != (void*)NULL)
     {
     *value = cloneFirstWordInLine(found+strlen(name)+1);
     if(*value == NULL)
         return FALSE;
     }
 return TRUE;
 }
 boolean subgroupFindTitle(struct trackDb *parentTdb, char *name,char **value)
 /* looks for a a subgroup matching the name and returns the title if found */
 {
 if(value != (void*)NULL)
     *value = NULL;
 members_t*members=subgroupMembersGet(parentTdb, name);
 if(members==NULL)
     return FALSE;
 *value = cloneString(members->title);
 subgroupMembersFree(&members);
 return TRUE;
 }
 void subgroupFree(char **value)
 /* frees subgroup memory */
 {
 if(value && *value)
     freez(value);
 }
 
 sortOrder_t *sortOrderGet(struct cart *cart,struct trackDb *parentTdb)
 /* Parses any list sort order instructions for parent of subtracks (from cart or trackDb)
    Some trickiness here.  sortOrder->sortOrder is from cart (changed by user action), as is sortOrder->order,
    But columns are in original tdb order (unchanging)!  However, if cart is null, all is from trackDb.ra */
 {
 int ix;
 char *setting = trackDbSetting(parentTdb, "sortOrder");
 if(setting == NULL) // Must be in trackDb or not a sortable table
     return NULL;
 
 sortOrder_t *sortOrder = needMem(sizeof(sortOrder_t));
 sortOrder->htmlId = needMem(strlen(parentTdb->tableName)+15);
 safef(sortOrder->htmlId, (strlen(parentTdb->tableName)+15), "%s.sortOrder", parentTdb->tableName);
 char *cartSetting = NULL;
 if(cart != NULL)
     cartSetting = cartCgiUsualString(cart, sortOrder->htmlId, setting);
 if(cart != NULL && strlen(cartSetting) == strlen(setting))
     sortOrder->sortOrder = cloneString(cartSetting);  // cart order
 else
     sortOrder->sortOrder = cloneString(setting);      // old cart value is abandoned!
 sortOrder->column  = needMem(12*sizeof(char*)); // There aren't going to be more than 3 or 4!
 sortOrder->setting = cloneString(setting);
 sortOrder->count   = chopByWhite(sortOrder->setting, sortOrder->column,12);
 sortOrder->title   = needMem(sortOrder->count*sizeof(char*));
 sortOrder->forward = needMem(sortOrder->count*sizeof(boolean));
 sortOrder->order   = needMem(sortOrder->count*sizeof(int));
 for (ix = 0; ix<sortOrder->count; ix++)
     {
     strSwapChar(sortOrder->column[ix],'=',0);  // Don't want 'CEL=+' but 'CEL' and '+'
     char *pos = stringIn(sortOrder->column[ix], sortOrder->sortOrder);// find tdb substr in cart current order string
     //assert(pos != NULL && pos[strlen(sortOrder->column[ix])] == '=');
     if(pos != NULL && pos[strlen(sortOrder->column[ix])] == '=')
         {
         int ord=1;
         char* pos2 = sortOrder->sortOrder;
         for(;*pos2 && pos2 < pos;pos2++)
             {
             if(*pos2 == '=') // Discovering sort order in cart
                 ord++;
             }
         sortOrder->forward[ix] = (pos[strlen(sortOrder->column[ix]) + 1] == '+');
         sortOrder->order[ix] = ord;
         }
     else  // give up on cartSetting
         {
         sortOrder->forward[ix] = TRUE;
         sortOrder->order[ix] = ix+1;
         }
     subgroupFindTitle(parentTdb,sortOrder->column[ix],&(sortOrder->title[ix]));
     }
 return sortOrder;  // NOTE cloneString:words[0]==*sortOrder->column[0] and will be freed when sortOrder is freed
 }
 void sortOrderFree(sortOrder_t **sortOrder)
 /* frees any previously obtained sortOrder settings */
 {
 if(sortOrder && *sortOrder)
     {
     int ix;
     for(ix=0;ix<(*sortOrder)->count;ix++) { subgroupFree(&((*sortOrder)->title[ix])); }
     freeMem((*sortOrder)->sortOrder);
     freeMem((*sortOrder)->htmlId);
     freeMem((*sortOrder)->column);
     freeMem((*sortOrder)->forward);
     freeMem((*sortOrder)->order);
     freeMem((*sortOrder)->setting);
     freez(sortOrder);
     }
 }
 
 sortableTdbItem *sortableTdbItemCreate(struct trackDb *tdbChild,sortOrder_t *sortOrder)
 // creates a sortable tdb item struct, given a child tdb and its parent's sort table
 // Errors in interpreting a passed in sortOrder will return NULL
 {
 sortableTdbItem *item = NULL;
 if(tdbChild == NULL || tdbChild->shortLabel == NULL)
     return NULL;
 AllocVar(item);
 item->tdb = tdbChild;
 if(sortOrder != NULL)   // Add some sort buttons
     {
     int sIx=0;
     for(sIx=sortOrder->count - 1;sIx>=0;sIx--) // walk backwards to ensure sort order in columns
         {
         sortColumn *column = NULL;
         AllocVar(column);
         column->fwd = sortOrder->forward[sIx];
         if(!subgroupFind(item->tdb,sortOrder->column[sIx],&(column->value)))
             {
             char *setting = trackDbSetting(item->tdb,sortOrder->column[sIx]);
             if(setting != NULL)
                 column->value = cloneString(setting);
             // No subgroup, assume there is a matching setting (eg longLabel)
             }
         if(column->value != NULL)
             slAddHead(&(item->columns), column);
         else
             {
             freez(&column);
             if(item->columns != NULL)
                 slFreeList(&(item->columns));
             freeMem(item);
             return NULL; // sortOrder setting doesn't match items to be sorted.
             }
         }
     }
 return item;
 }
 
 static int sortableTdbItemsCmp(const void *va, const void *vb)
 // Compare two sortable tdb items based upon sort columns.
 {
 const sortableTdbItem *a = *((sortableTdbItem **)va);
 const sortableTdbItem *b = *((sortableTdbItem **)vb);
 sortColumn *colA = a->columns;
 sortColumn *colB = b->columns;
 int compared = 0;
 for (;compared==0 && colA!=NULL && colB!=NULL;colA=colA->next,colB=colB->next)
     {
     if(colA->value != NULL && colB->value != NULL)
         compared = strcmp(colA->value, colB->value) * (colA->fwd? 1: -1);
     }
 if(compared != 0)
     return compared;
 
 return strcmp(a->tdb->shortLabel, b->tdb->shortLabel); // Last chance
 }
 
 void sortTdbItemsAndUpdatePriorities(sortableTdbItem **items)
 // sort items in list and then update priorities of item tdbs
 {
 if(items != NULL && *items != NULL)
     {
     slSort(items, sortableTdbItemsCmp);
     int priority=10001; // Setting priorities high allows new subtracks without cart entries to fall after existing subtracks
     sortableTdbItem *item;
     for (item = *items; item != NULL; item = item->next)
         item->tdb->priority = (float)priority++;
     }
 }
 
 void sortableTdbItemsFree(sortableTdbItem **items)
 // Frees all memory associated with a list of sortable tdb items
 {
 if(items != NULL && *items != NULL)
     {
     sortableTdbItem *item;
     for (item = *items; item != NULL; item = item->next)
         {
         sortColumn *column;
         for (column = item->columns; column != NULL; column = column->next)
             freeMem(column->value);
         slFreeList(&(item->columns));
         }
     slFreeList(items);
     }
 }
 
 static boolean colonPairToStrings(char * colonPair,char **first,char **second)
 { // Will set *first and *second to NULL.  Must free any string returned!  No colon: value goes to *first
 if(first)
     *first =NULL; // default to NULL !
 if(second)
     *second=NULL;
 if(colonPair != NULL)
     {
     if(strchr(colonPair,':'))
         {
         if(second)
             *second = cloneString(strchr(colonPair,':') + 1);
         if(first)
             *first = strSwapChar(cloneString(colonPair),':',0);
         }
     else if(first)
         *first = cloneString(colonPair);
     return (*first != NULL || *second != NULL);
     }
 return FALSE;
 }
 static boolean colonPairToInts(char * colonPair,int *first,int *second)
 { // Non-destructive. Only sets values if found. No colon: value goes to *first
 char *a=NULL;
 char *b=NULL;
 if(colonPairToStrings(colonPair,&a,&b))
     {
     if(a!=NULL)
         {
         if(first)
             *first = atoi(a);
         freeMem(a);
         }
     if(b!=NULL)
         {
         if(second)
             *second = atoi(b);
         freeMem(b);
         }
     return TRUE;
     }
 return FALSE;
 }
 static boolean colonPairToDoubles(char * colonPair,double *first,double *second)
 { // Non-destructive. Only sets values if found. No colon: value goes to *first
 char *a=NULL;
 char *b=NULL;
 if(colonPairToStrings(colonPair,&a,&b))
     {
     if(a!=NULL)
         {
         if(first)
             *first = strtod(a,NULL);
         freeMem(a);
         }
     if(b!=NULL)
         {
         if(second)
             *second = strtod(b,NULL);
         freeMem(b);
         }
     return TRUE;
     }
 return FALSE;
 }
 
 filterBy_t *filterBySetGet(struct trackDb *tdb, struct cart *cart, char *name)
 /* Gets one or more "filterBy" settings (ClosestToHome).  returns NULL if not found */
 {
 filterBy_t *filterBySet = NULL;
 
 char *setting = trackDbSettingClosestToHome(tdb, FILTER_BY);
 if(setting == NULL)
     return NULL;
 if( name == NULL )
     name = tdb->tableName;
 
 setting = cloneString(setting);
 char *filters[10];
 int filterCount = chopLine(setting, filters);
 int ix;
 for(ix=0;ix<filterCount;ix++)
     {
     char *filter = cloneString(filters[ix]);
     filterBy_t *filterBy;
     AllocVar(filterBy);
     strSwapChar(filter,':',0);
     filterBy->column = filter;
     filter += strlen(filter) + 1;
     strSwapChar(filter,'=',0);
     filterBy->title = strSwapChar(filter,'_',' '); // Title does not have underscores
     filter += strlen(filter) + 1;
     if(filter[0] == '+') // values are indexes to the string titles
         {
         filter += 1;
         filterBy->useIndex = TRUE;
         }
     filterBy->slValues = slNameListFromComma(filter);
     slAddTail(&filterBySet,filterBy); // Keep them in order (only a few)
 
     if(cart != NULL)
         {
         char suffix[64];
         safef(suffix, sizeof(suffix), "filterBy.%s", filterBy->column);
         boolean compositeLevel = isNameAtCompositeLevel(tdb,name);
         if(cartLookUpVariableClosestToHome(cart,tdb,compositeLevel,suffix,&(filterBy->htmlName)))
             filterBy->slChoices = cartOptionalSlNameList(cart,filterBy->htmlName);
         }
     if(filterBy->htmlName == NULL)
         {
         int len = strlen(name) + strlen(filterBy->column) + 15;
         filterBy->htmlName = needMem(len);
         safef(filterBy->htmlName, len, "%s.filterBy.%s", name,filterBy->column);
         }
     }
 freeMem(setting);
 
 return filterBySet;
 }
 
 void filterBySetFree(filterBy_t **filterBySet)
 /* Free a set of filterBy structs */
 {
 if(filterBySet != NULL)
     {
     while(*filterBySet != NULL)
         {
         filterBy_t *filterBy = slPopHead(filterBySet);
         if(filterBy->slValues != NULL)
             slNameFreeList(filterBy->slValues);
         if(filterBy->slChoices != NULL)
             slNameFreeList(filterBy->slChoices);
         if(filterBy->htmlName != NULL)
             freeMem(filterBy->htmlName);
         freeMem(filterBy->column);
         freeMem(filterBy);
         }
     }
 }
 
 static char *filterByClause(filterBy_t *filterBy)
 /* returns the "column in (...)" clause for a single filterBy struct */
 {
 int count = slCount(filterBy->slChoices);
 if(count == 0)
     return NULL;
 if(slNameInList(filterBy->slChoices,"All"))
     return NULL;
 
 struct dyString *dyClause = newDyString(256);
 dyStringPrintf(dyClause, filterBy->column);
 if(count == 1)
     dyStringPrintf(dyClause, " = ");
 else
     dyStringPrintf(dyClause, " in (");
 
 struct slName *slChoice = NULL;
 boolean notFirst = FALSE;
 for(slChoice = filterBy->slChoices;slChoice != NULL;slChoice=slChoice->next)
     {
     if(notFirst)
         dyStringPrintf(dyClause, ",");
     notFirst = TRUE;
     if(filterBy->useIndex)
         dyStringPrintf(dyClause, slChoice->name);
     else
         dyStringPrintf(dyClause, "\"%s\"",slChoice->name);
     }
 if(dyStringLen(dyClause) == 0)
     {
     dyStringFree(&dyClause);
     return NULL;
     }
 if(count > 1)
     dyStringPrintf(dyClause, ")");
 
 return dyStringCannibalize(&dyClause);
 }
 
 char *filterBySetClause(filterBy_t *filterBySet)
 /* returns the "column1 in (...) and column2 in (...)" clause for a set of filterBy structs */
 {
 struct dyString *dyClause = newDyString(256);
 boolean notFirst = FALSE;
 filterBy_t *filterBy = NULL;
 
 for(filterBy = filterBySet;filterBy != NULL; filterBy = filterBy->next)
     {
     char *clause = filterByClause(filterBy);
     if(clause != NULL)
         {
         if(notFirst)
             dyStringPrintf(dyClause, " AND ");
         dyStringPrintf(dyClause, clause);
         freeMem(clause);
         notFirst = TRUE;
         }
     }
 if(dyStringLen(dyClause) == 0)
     {
     dyStringFree(&dyClause);
     return NULL;
     }
 return dyStringCannibalize(&dyClause);
 }
 
 void filterBySetCfgUi(struct trackDb *tdb, filterBy_t *filterBySet)
 /* Does the UI for a list of filterBy structure */
 {
 if(filterBySet == NULL)
     return;
 
 #define FILTERBY_HELP_LINK  "<A HREF=\"../../goldenPath/help/multiView.html\" TARGET=ucscHelp>help</A>"
 int count = slCount(filterBySet);
 if(count == 1)
     puts("<BR><TABLE cellpadding=3><TR valign='top'>");
 else
     printf("<BR><B>Filter by</B> (select multiple categories and items - %s)<TABLE cellpadding=3><TR valign='top'>\n",FILTERBY_HELP_LINK);
 filterBy_t *filterBy = NULL;
 for(filterBy = filterBySet;filterBy != NULL; filterBy = filterBy->next)
     {
     puts("<TD>");
     if(count == 1)
         printf("<B>Filter by %s</B> (select multiple items - %s)<BR>\n",filterBy->title,FILTERBY_HELP_LINK);
     else
         printf("<B>%s</B><BR>\n",filterBy->title);
     int fullSize = slCount(filterBy->slValues)+1;
     int openSize = min(20,fullSize);
     int closedSize = (filterBy->slChoices == NULL || slCount(filterBy->slChoices) == 1 ? 1 : openSize);  //slCount(filterBy->slValues)+1);   // slChoice ??
 //#define MULTI_SELECT_WITH_JS "<div class='multiSelectContainer'><SELECT name='%s.filterBy.%s' multiple=true size=%d openSize=%d style='display: none' onclick='multiSelectClick(this,%d);' onblur='multiSelectBlur(this,%d);' class='normalText filterBy'></div><BR>\n"
 //    printf(MULTI_SELECT_WITH_JS,tdb->tableName,filterBy->column,closedSize,openSize,openSize,openSize);
 #define MULTI_SELECT_WITH_JS "<SELECT name='%s.filterBy.%s' multiple=true size=%d onkeydown='this.size=%d' onclick='multiSelectClick(this,%d);' onblur='multiSelectBlur(this);' class='filterBy'><BR>\n"
     printf(MULTI_SELECT_WITH_JS,tdb->tableName,filterBy->column,closedSize,openSize,openSize);
     printf("<OPTION%s>All</OPTION>\n",(filterBy->slChoices == NULL || slNameInList(filterBy->slChoices,"All")?" SELECTED":"") );
     struct slName *slValue;
     if(filterBy->useIndex)
         {
         int ix=1;
         for(slValue=filterBy->slValues;slValue!=NULL;slValue=slValue->next,ix++)
             {
             char varName[32];
             safef(varName, sizeof(varName), "%d",ix);
             char *name = strSwapChar(cloneString(slValue->name),'_',' ');
                 printf("<OPTION%s value=%s>%s</OPTION>\n",(filterBy->slChoices != NULL && slNameInList(filterBy->slChoices,varName)?" SELECTED":""),varName,name);
             freeMem(name);
             }
         }
     else
         {
         for(slValue=filterBy->slValues;slValue!=NULL;slValue=slValue->next)
             printf("<OPTION%s>%s</OPTION>\n",(filterBy->slChoices != NULL && slNameInList(filterBy->slChoices,slValue->name)?" SELECTED":""),slValue->name);
         }
     }
     // The following is needed to make msie scroll to selected option.
     printf("<script type='text/javascript'>onload=function(){ $( 'select[name^=%s.filterBy.]' ).children('option[selected]').each( function(i) { this.selected=true; }); }</script>\n",tdb->tableName);
 puts("</TR></TABLE>");
 
 return;
 }
 
 #define COLOR_BG_DEFAULT_IX     0
 #define COLOR_BG_ALTDEFAULT_IX  1
 #define DIVIDING_LINE "<TR valign=\"CENTER\" line-height=\"1\" BGCOLOR=\"%s\"><TH colspan=\"5\" align=\"CENTER\"><hr noshade color=\"%s\" width=\"100%%\"></TD></TR>\n"
 #define DIVIDER_PRINT(color) printf(DIVIDING_LINE,COLOR_BG_DEFAULT,(color))
 
 static char *checkBoxIdMakeForTrack(struct trackDb *tdb,char *tagX,char *tagY,char *tagZ,membership_t *membership)
 /* Creates an 'id' string for subtrack checkbox in style that matrix understand: "cb_dimX_dimY_view_cb" */
 {
 int ix;
 #define CHECKBOX_ID_SZ 128
 char *id = needMem(CHECKBOX_ID_SZ);
 // What is wanted: id="cb_ES_K4_SIG_cb"
 safef(id, CHECKBOX_ID_SZ, "cb_%s_", tdb->tableName);
 //safecpy(id,CHECKBOX_ID_SZ,"cb_");
 if(tagX != NULL)
     {
     ix = stringArrayIx(tagX, membership->subgroups, membership->count);
     if(ix >= 0)
         safef(id+strlen(id), CHECKBOX_ID_SZ-strlen(id), "%s_", membership->membership[ix]);
     }
 if(tagY != NULL)
     {
     ix = stringArrayIx(tagY, membership->subgroups, membership->count);
     if(ix >= 0)
         safef(id+strlen(id), CHECKBOX_ID_SZ-strlen(id), "%s_", membership->membership[ix]);
     }
 if(tagZ != NULL)
     {
     ix = stringArrayIx(tagZ, membership->subgroups, membership->count);
     if(ix >= 0)
         safef(id+strlen(id), CHECKBOX_ID_SZ-strlen(id), "%s_", membership->membership[ix]);
     }
 if(membership != NULL)
     {
     ix = stringArrayIx("view", membership->subgroups, membership->count);   // view is a known tagname
     if(ix >= 0)
         safef(id+strlen(id), CHECKBOX_ID_SZ-strlen(id), "%s_", membership->membership[ix]);
     }
 safecat(id+strlen(id), CHECKBOX_ID_SZ-strlen(id), "cb");
 // If all else fails:
 //if(strlen(id) <= 5)
 //    safef(id, CHECKBOX_ID_SZ, "cb_%s", tdb->tableName);
 return id;
 }
 static void checkBoxIdFree(char**id)
 /* Frees 'id' string */
 {
 if(id && *id)
     freez(id);
 }
 
 static boolean divisionIfNeeded(char **lastDivide,dividers_t *dividers,membership_t *membership)
 /* Keeps track of location within subtracks in order to provide requested division lines */
 {
 boolean division = FALSE;
 if(dividers)
     {
     if(lastDivide != NULL)
         {
         int ix;
         for(ix=0;ix<dividers->count;ix++)
             {
             int sIx = stringArrayIx(dividers->subgroups[ix],membership->subgroups, membership->count);
             if((lastDivide[ix] == (void*)NULL && sIx >= 0)
             || (lastDivide[ix] != (void*)NULL && sIx <  0)
             || (strcmp(lastDivide[ix],membership->membership[sIx]) != 0) )
                 {
                 division = TRUE;
                 if(lastDivide[ix] != (void*)NULL)
                     freeMem(lastDivide[ix]);
                 lastDivide[ix] = ( sIx<0 ? (void*)NULL : cloneString(membership->membership[sIx]) );
                 }
             }
         }
     //if(division)
     //    DIVIDER_PRINT(COLOR_DARKGREEN);
     }
 return division;
 }
 
 static void indentIfNeeded(hierarchy_t*hierarchy,membership_t *membership)
 /* inserts any needed indents */
 {
 int indent = 0;
 if(hierarchy && hierarchy->count>0)
     {
     int ix;
     for(ix=0;ix<membership->count;ix++)
         {
         int iIx = stringArrayIx(membership->membership[ix], hierarchy->membership, hierarchy->count);
         if(iIx >= 0)
             {
             indent = hierarchy->indents[iIx];
             break;  // Only one
             }
         }
     }
 for(;indent>0;indent--)
     puts ("&nbsp;&nbsp;&nbsp;");
 }
 
 static int daysOfMonth(struct tm *tp)
 {
 int days=0;
 switch(tp->tm_mon)
     {
     case 3:
     case 5:
     case 8:
     case 10:    days = 30;   break;
     case 1:     days = 28;
                 if( (tp->tm_year % 4) == 0
                 && ((tp->tm_year % 20) != 0 || (tp->tm_year % 100) == 0) )
                     days = 29;
                 break;
     default:    days = 31;   break;
     }
 return days;
 }
 
 static void dateAdd(struct tm *tp,int addYears,int addMonths,int addDays)
 /* Add years,months,days to a date */
 {
 tp->tm_mday  += addDays;
 tp->tm_mon   += addMonths;
 tp->tm_year  += addYears;
 int dom=28;
 while( (tp->tm_mon >11  || tp->tm_mon <0)
     || (tp->tm_mday>dom || tp->tm_mday<1) )
     {
     if(tp->tm_mon>11)   // First month: tm.tm_mon is 0-11 range
         {
         tp->tm_year += (tp->tm_mon / 12);
         tp->tm_mon  = (tp->tm_mon % 12);
         }
     else if(tp->tm_mon<0)
         {
         tp->tm_year += (tp->tm_mon / 12) - 1;
         tp->tm_mon  =  (tp->tm_mon % 12) + 12;
         }
     else
         {
         dom = daysOfMonth(tp);
         if(tp->tm_mday>dom)
             {
             tp->tm_mday -= dom;
             tp->tm_mon  += 1;
             dom = daysOfMonth(tp);
             }
         else if(tp->tm_mday < 1)
             {
             tp->tm_mon  -= 1;
             dom = daysOfMonth(tp);
             tp->tm_mday += dom;
             }
         }
     }
 }
 static char *dateAddToAndFormat(char *date,char *format,int addYears,int addMonths,int addDays)
 /* Add years,months,days to a formatted date and returns the new date as a string on the stack
 *  format is a strptime/strftime format: %F = yyyy-mm-dd */
 {
 char *newDate = needMem(12);
 struct tm tp;
 if(strptime (date,format, &tp))
     {
     dateAdd(&tp,addYears,addMonths,addDays); // tp.tm_year only contains years since 1900
     strftime(newDate,12,format,&tp);
     }
 return newDate;  // newDate is never freed!
 }
 
 boolean tdbSortPrioritiesFromCart(struct cart *cart, struct trackDb **tdbList)
 /* Updates the tdb->priority from cart then sorts the list anew.
    Returns TRUE if priorities obtained from cart */
 {
 char htmlIdentifier[128];
 struct trackDb *tdb;
 boolean cartPriorities = FALSE;
 for (tdb = *tdbList; tdb != NULL; tdb = tdb->next)
     {
         safef(htmlIdentifier, sizeof(htmlIdentifier), "%s.priority", tdb->tableName);
         char *cartHas = cartOptionalString(cart,htmlIdentifier);
         if(cartHas != NULL)
             {
             tdb->priority = atof(cartHas);
             cartPriorities = TRUE;
             }
     }
     slSort(tdbList, trackDbCmp);
 return cartPriorities;
 }
 
 static void cfgByCfgType(eCfgType cType,char *db, struct cart *cart, struct trackDb *tdb,char *prefix, char *title, boolean boxed)
 {
 switch(cType)
     {
     case cfgBedScore:
 	{
 	char *scoreMax = trackDbSettingClosestToHome(tdb, SCORE_FILTER _MAX);
 	int maxScore = (scoreMax ? sqlUnsigned(scoreMax):1000);
 	scoreCfgUi(db, cart,tdb,prefix,title,maxScore,boxed);
 	}
 	break;
     case cfgPeak:
                         encodePeakCfgUi(cart,tdb,prefix,title,boxed);
                         break;
     case cfgWig:        wigCfgUi(cart,tdb,prefix,title,boxed);
                         break;
     case cfgWigMaf:     wigMafCfgUi(cart,tdb,prefix,title,boxed, db);
                         break;
     case cfgGenePred:   genePredCfgUi(cart,tdb,prefix,title,boxed);
                         break;
     case cfgChain:      chainCfgUi(db,cart,tdb,prefix,title,boxed, NULL);
                         break;
     case cfgNetAlign:	netAlignCfgUi(db,cart,tdb,prefix,title,boxed);
                         break;
     default:            warn("Track type is not known to multi-view composites.");
                         break;
     }
 }
 
 char *encodeRestrictionDateDisplay(struct trackDb *trackDb)
 /* Create a string for ENCODE restriction date of this track
    if return is not null, then free it after use */
 {
 if (!trackDb)
     return NULL;
 boolean addMonths = FALSE;
 char *date = metadataSettingFind(trackDb,"dateUnrestricted");
 if(date == NULL)
     {
     date = metadataSettingFind(trackDb,"dateSubmitted");
     addMonths = TRUE;
     }
 if(date == NULL)
     {
     addMonths = FALSE;
     date = trackDbSetting(trackDb, "dateUnrestricted");
     if(date)
         date = cloneString(date); // all returns should be freeable memory
     }
 if(date == NULL)
     {
     date = trackDbSetting(trackDb, "dateSubmitted");
     if(date)
         {
         addMonths = TRUE;
         date = cloneString(date); // all returns should be freeable memory
         }
     }
 if (date != NULL)
     {
     date = strSwapChar(date, ' ', 0);   // Truncate time
     if(addMonths)
         date = dateAddToAndFormat(date, "%F", 0, 9, 0);
     }
 return date;
 }
 
 static void compositeUiSubtracks(char *db, struct cart *cart, struct trackDb *parentTdb,
                  boolean selectedOnly, char *primarySubtrack)
 /* Show checkboxes for subtracks. */
 {
 struct trackDb *subtrack;
 char *primaryType = getPrimaryType(primarySubtrack, parentTdb);
 char htmlIdentifier[SMALLBUF];
 struct dyString *dyHtml = newDyString(SMALLBUF);
 char *words[5];
 char *colors[2]   = { COLOR_BG_DEFAULT,
                       COLOR_BG_ALTDEFAULT };
 int colorIx = COLOR_BG_DEFAULT_IX; // Start with non-default allows alternation
 
 // Look for dividers, heirarchy, dimensions, sort and dragAndDrop!
 char **lastDivide = NULL;
 dividers_t *dividers = dividersSettingGet(parentTdb);
 if(dividers)
     lastDivide = needMem(sizeof(char*)*dividers->count);
 hierarchy_t *hierarchy = hierarchySettingGet(parentTdb);
 
 enum
 {
     dimX=0,
     dimY=1,
     dimZ=2,
     dimV=3,
     dimMax=4
 };
 members_t* dimensions[dimMax];
 dimensions[dimX]=subgroupMembersGetByDimension(parentTdb,'X');
 dimensions[dimY]=subgroupMembersGetByDimension(parentTdb,'Y');
 dimensions[dimZ]=subgroupMembersGetByDimension(parentTdb,'Z');
 dimensions[dimV]=subgroupMembersGet(parentTdb,"view");
 int dimCount=0,di;
 for(di=0;di<dimMax;di++) { if(dimensions[di]) dimCount++; }
 sortOrder_t* sortOrder = sortOrderGet(cart,parentTdb);
 boolean preSorted = FALSE;
 boolean useDragAndDrop = sameOk("subTracks",trackDbSetting(parentTdb, "dragAndDrop"));
 
 // Now we can start in on the table of subtracks
 printf("\n<TABLE CELLSPACING='2' CELLPADDING='0' border='0'");
 if(sortOrder != NULL)
     {
     printf(" id='subtracks.%s.sortable'",parentTdb->tableName);
     dyStringClear(dyHtml);
     dyStringPrintf(dyHtml, "tableSortable");
     colorIx = COLOR_BG_ALTDEFAULT_IX;
     }
 else
     printf(" id='subtracks.%s'",parentTdb->tableName);
 if(useDragAndDrop)
     {
     if(dyStringLen(dyHtml) > 0)
         dyStringAppendC(dyHtml,' ');
     dyStringPrintf(dyHtml, "tableWithDragAndDrop");
     printf(" class='%s'",dyStringContents(dyHtml));
     dyStringClear(dyHtml);
     colorIx = COLOR_BG_ALTDEFAULT_IX;
     }
 puts("><THEAD>");
 
 if (!primarySubtrack)
     {
     char javascript[JBUFSIZE];
     boolean displayAll = sameString(cartUsualString(cart, "displaySubtracks", "all"), "all");
     boolean restrictions = FALSE;
     for (subtrack = parentTdb->subtracks; subtrack != NULL; subtrack = subtrack->next)
         {
         char *date = metadataSettingFind(subtrack,"dateUnrestricted");
         if(date != NULL)
             {
             freeMem(date);
             restrictions = TRUE;
             break;
             }
         }
 
     printf("<TR");
     if(useDragAndDrop)
         printf(" id=\"noDrag\" class='nodrop nodrag'");
     printf("><TD colspan='%d'><B>List subtracks:&nbsp;",(sortOrder != NULL?sortOrder->count+2:3));
     safef(javascript, sizeof(javascript), "onclick=\"showOrHideSelectedSubtracks(true);\"");
     cgiMakeOnClickRadioButton("displaySubtracks", "selected", !displayAll,javascript);
     puts("only selected/visible &nbsp;&nbsp;");
     safef(javascript, sizeof(javascript), "onclick=\"showOrHideSelectedSubtracks(false);\"");
     cgiMakeOnClickRadioButton("displaySubtracks", "all", displayAll,javascript);
     puts("all</B></TD>");
 
     if(sortOrder != NULL)   // Add some sort buttons
         {
         puts("<TD colspan=5>&nbsp;</TD></TR>");
         printf("<TR id=\"%s.sortTr\" class='nodrop nodrag'>\n",parentTdb->tableName);     // class='nodrop nodrag'
         printf("<TD>&nbsp;<INPUT TYPE=HIDDEN NAME='%s' id='%s' VALUE=\"%s\"></TD>", sortOrder->htmlId, sortOrder->htmlId,sortOrder->sortOrder); // keeing track of priority
         // Columns in tdb order (unchanging), sort in cart order (changed by user action)
         int sIx=0;
         for(sIx=0;sIx<sortOrder->count;sIx++)
             {
             printf ("<TH id='%s.%s.sortTh' abbr='%c' onMouseOver=\"hintOverSortableColumnHeader(this)\" nowrap><A HREF='#nowhere' onclick=\"tableSortAtButtonPress(this,'%s');return false;\">%s</A><sup>%s",
                 parentTdb->tableName,sortOrder->column[sIx],(sortOrder->forward[sIx]?'-':'+'),sortOrder->column[sIx],sortOrder->title[sIx],(sortOrder->forward[sIx]?"&darr;":"&uarr;"));
             if (sortOrder->count > 1)
                 printf ("%d",sortOrder->order[sIx]);
             puts ("</sup></TH>");
             }
         puts("<TD>&nbsp;</TD>");
         }
 
     puts("<TH>&nbsp;</TH>");
     //puts("<TH align=\"center\" nowrap>&nbsp;Table&nbsp;</TH>");
     if(restrictions)
         {
         printf("<TH align=\"center\" nowrap>&nbsp;");
         printf("<A HREF=\'%s\' TARGET=BLANK>Restricted Until</A>", ENCODE_DATA_RELEASE_POLICY);
         puts("&nbsp;</TH>");
         }
     }
 
 if(sortOrder != NULL || useDragAndDrop)
     {
     preSorted = tdbSortPrioritiesFromCart(cart, &(parentTdb->subtracks)); // preserves user's prev sort/drags
     puts("</TR></THEAD><TBODY id='sortable_tbody'>");
     }
 else
     {
     slSort(&(parentTdb->subtracks), trackDbCmp);  // straight from trackDb.ra
     preSorted = TRUE;
     puts("</TR></THEAD><TBODY>");
     }
 
 for (subtrack = parentTdb->subtracks; subtrack != NULL; subtrack = subtrack->next)
     {
     boolean checkedCB = TRUE;
     boolean enabledCB = TRUE;
     boolean isPrimary = FALSE;
     char *setting;
     int ix;
 
     if ((setting = trackDbSetting(subtrack, "subTrack")) != NULL)
         {
         if (chopLine(cloneString(setting), words) >= 2)
             checkedCB = differentString(words[1], "off");
         }
     safef(htmlIdentifier, sizeof(htmlIdentifier), "%s_sel", subtrack->tableName);
     setting = cartOptionalString(cart, htmlIdentifier);
     if(setting != NULL)
         {
         int state = atoi(setting);
         checkedCB = (state == 1 || state == -1);  // checked/eanbled:1 unchecked/enabled:0 checked/disabled:-1 unchecked/disabled:-2
         enabledCB = (state >= 0);
         }
     isPrimary = (primarySubtrack &&
          sameString(subtrack->tableName, primarySubtrack));
 
     if (primarySubtrack)
         {
         if (isPrimary)
             {
             puts("<TR><TD>");
             cgiMakeHiddenBoolean(htmlIdentifier, TRUE);
             puts("[on] ");
             printf ("</TD><TD>%s [selected on main page]</TD></TR>\n",
                 subtrack->longLabel);
             }
         else if (hSameTrackDbType(primaryType, subtrack->type) &&
                      hTableExists(db, subtrack->tableName))
             {
             puts("<TR><TD>");
             cgiMakeCheckBox(htmlIdentifier, checkedCB && enabledCB);
             printf ("</TD><TD>%s</TD></TR>\n", subtrack->longLabel);
             }
         }
     else
         {
         eCfgType cType = cfgTypeFromTdb(subtrack,FALSE);
         if(trackDbSettingClosestToHomeOn(subtrack, "configurable") == FALSE)
             cType = cfgNone;
         membership_t *membership = subgroupMembershipGet(subtrack);
         if (hTableExists(db, subtrack->tableName))
             {
             if(sortOrder == NULL && !useDragAndDrop)
                 {
                 if( divisionIfNeeded(lastDivide,dividers,membership) )
                     colorIx = (colorIx == COLOR_BG_DEFAULT_IX ? COLOR_BG_ALTDEFAULT_IX : COLOR_BG_DEFAULT_IX);
                 }
 
             char *id = checkBoxIdMakeForTrack(subtrack,
                         (dimensions[dimX]?dimensions[dimX]->tag:NULL),
                         (dimensions[dimY]?dimensions[dimY]->tag:NULL),
                         (dimensions[dimZ]?dimensions[dimZ]->tag:NULL),membership); // view is known tag
             printf("<TR valign='top' BGCOLOR=\"%s\"",colors[colorIx]);
             if(useDragAndDrop)
                 printf(" class='trDraggable' title='Drag to Reorder'");
 
             printf(" id=\"tr_%s\" nowrap%s>\n<TD>",id,(selectedOnly?" style='display:none'":""));
             dyStringClear(dyHtml);
             dyStringPrintf(dyHtml, "onclick='matSubtrackCbClick(this);' onmouseover=\"this.style.cursor='default';\" class=\"subtrackCB");
             for(di=0;di<dimMax;di++)
                 {
                 if(dimensions[di] && -1 != (ix = stringArrayIx(dimensions[di]->tag, membership->subgroups, membership->count)))
                     dyStringPrintf(dyHtml, " %s",membership->membership[ix]);
                 }
             dyStringAppendC(dyHtml,'"');
             cgiMakeCheckBox2BoolWithIdAndJS(htmlIdentifier,checkedCB,enabledCB,id,dyStringContents(dyHtml));
             if(sortOrder != NULL || useDragAndDrop)
                 {
                 safef(htmlIdentifier, sizeof(htmlIdentifier), "%s.priority", subtrack->tableName);
                 float priority = (float)cartUsualDouble(cart, htmlIdentifier, subtrack->priority);
                 printf("<INPUT TYPE=HIDDEN NAME='%s' id='%s' VALUE=\"%.0f\">", htmlIdentifier, htmlIdentifier, priority); // keeing track of priority
                 }
 
             if(sortOrder != NULL)
                 {
                 int sIx=0;
                 for(sIx=0;sIx<sortOrder->count;sIx++)
                     {
                     ix = stringArrayIx(sortOrder->column[sIx], membership->subgroups, membership->count);                        // TODO: Sort needs to expand from subGroups to labels as well
                     if(ix >= 0)
                         {
 #define CFG_SUBTRACK_LINK  "<A HREF='#a_cfg_%s' onclick='return subtrackCfgShow(\"%s\");' title='Subtrack Configuration'>%s</A>\n"
 #define MAKE_CFG_SUBTRACK_LINK(table,title) printf(CFG_SUBTRACK_LINK, (table),(table),(title))
                         printf ("<TD id='%s' nowrap abbr='%s' align='left'>&nbsp;",sortOrder->column[sIx],membership->membership[ix]);
                         if(cType != cfgNone && sameString("view",sortOrder->column[sIx]))
                             MAKE_CFG_SUBTRACK_LINK(subtrack->tableName,membership->titles[ix]);  // FIXME: Currently configurable under sort only supported when multiview
                         else
                             printf("%s\n",membership->titles[ix]);
                         puts ("</TD>");
                         }
                     }
                 }
             else
                 {
                 printf ("<TD nowrap='true'>&nbsp;");
                 indentIfNeeded(hierarchy,membership);
                 if(cType != cfgNone)
                     MAKE_CFG_SUBTRACK_LINK(subtrack->tableName,subtrack->shortLabel);
                 else
                     printf("%s\n",subtrack->shortLabel);
                 puts ("</TD>");
                 }
             printf ("<TD nowrap='true' title='select to copy' onmouseover=\"this.style.cursor='text';\"><div>&nbsp;%s", subtrack->longLabel);
             if(trackDbSetting(parentTdb, "wgEncode") && trackDbSetting(subtrack, "accession"))
                 printf (" [GEO:%s]", trackDbSetting(subtrack, "accession"));
             metadataToggle(subtrack,"...",TRUE,FALSE);
             printf("</div>");
 
             if(cType != cfgNone)
                 {
                 ix = stringArrayIx("view", membership->subgroups, membership->count);
 #define CFG_SUBTRACK_DIV "<DIV id='div_%s_cfg'%s><INPUT TYPE=HIDDEN NAME='%s' value='%s'>\n"
 #define MAKE_CFG_SUBTRACK_DIV(table,cfgVar,open) printf(CFG_SUBTRACK_DIV,(table),((open)?"":" style='display:none'"),(cfgVar),((open)?"on":"off"))
                 safef(htmlIdentifier,sizeof(htmlIdentifier),"%s.childShowCfg",subtrack->tableName);
                 boolean open = cartUsualBoolean(cart, htmlIdentifier,FALSE);
                 MAKE_CFG_SUBTRACK_DIV(subtrack->tableName,htmlIdentifier,open);
                 safef(htmlIdentifier,sizeof(htmlIdentifier),"%s.%s",subtrack->tableName,membership->membership[ix]);
                 cfgByCfgType(cType,db,cart,subtrack,htmlIdentifier,"Subtrack",TRUE);
                 puts("</DIV>\n");
                 }
             printf("<TD nowrap>&nbsp;");
             makeSchemaLink(db,subtrack,"schema");
             puts("&nbsp;");
 
             char *dateDisplay = encodeRestrictionDateDisplay(subtrack);
             if (dateDisplay)
                 printf("</TD><TD align=\"CENTER\">&nbsp;%s&nbsp;", dateDisplay);
 
             puts("</TD></TR>");
             checkBoxIdFree(&id);
             }
         subgroupMembershipFree(&membership);
         }
     }
 puts("</TBODY><TFOOT></TFOOT>");
 puts("</TABLE>");
 puts("<P>");
 //if (!preSorted && sortOrder != NULL)  // No longer need to do this since hgTrackDb should sort composites with sortOrder and set priorities
 //    puts("<script type='text/javascript'>tableSortAtStartup();</script>");
 if (!primarySubtrack)
     puts("<script type='text/javascript'>matInitializeMatrix();</script>");
 for(di=dimX;di<dimMax;di++)
     subgroupMembersFree(&dimensions[di]);
 dyStringFree(&dyHtml)
 sortOrderFree(&sortOrder);
 dividersFree(&dividers);
 hierarchyFree(&hierarchy);
 }
 
 static void compositeUiAllSubtracks(char *db, struct cart *cart, struct trackDb *tdb,
 				    char *primarySubtrack)
 /* Show checkboxes for all subtracks, not just selected ones. */
 {
 compositeUiSubtracks(db, cart, tdb, FALSE, primarySubtrack);
 }
 
 static void compositeUiSelectedSubtracks(char *db, struct cart *cart, struct trackDb *tdb,
 					 char *primarySubtrack)
 /* Show checkboxes only for selected subtracks. */
 {
 compositeUiSubtracks(db, cart, tdb, TRUE, primarySubtrack);
 }
 
 static void makeAddClearSubmitTweak(char javascript[JBUFSIZE], char *formName,
 				    char *buttonVar, char *label)
 /* safef into javascript a sequence of commands that will force a refresh
  * of this same form, updating the values of whatever variables are necessary
  * to say what we want to do. */
 {
 safef(javascript, JBUFSIZE*sizeof(char),
       "document.%s.action = '%s'; document.%s.%s.value='%s'; "
       "document.%s.submit();",
       formName, cgiScriptName(), formName, buttonVar, label,
       formName);
 }
 
 #define MANY_SUBTRACKS  8
 
 #define WIGGLE_HELP_PAGE  "../goldenPath/help/hgWiggleTrackHelp.html"
 
 static boolean cfgBeginBoxAndTitle(struct trackDb *tdb, boolean boxed, char *title)
 /* Handle start of box and title for individual track type settings */
 {
 if(!boxed)
     {
     boxed = trackDbSettingOn(tdb,"boxedCfg");
     if(boxed)
         printf("<BR>");
     }
 if (boxed)
     {
     printf("<TABLE class='blueBox' bgcolor=\"%s\" borderColor=\"%s\"><TR><TD align='RIGHT'>", COLOR_BG_ALTDEFAULT, COLOR_BG_ALTDEFAULT);
     if (title)
         printf("<CENTER><B>%s Configuration</B></CENTER>\n", title);
     }
 else if (title)
     printf("<p><B>%s &nbsp;</b>", title );
 else
     printf("<p>");
 return boxed;
 }
 
 static void cfgLinkToDependentCfgs(struct trackDb *tdb,char *prefix)
 /* Link composite or view level controls to all associateled lower level controls */
 {
 if(tdbIsComposite(tdb))
     printf("<script type='text/javascript'>compositeCfgRegisterOnchangeAction(\"%s\")</script>\n",prefix);
 }
 
 static void cfgEndBox(boolean boxed)
 /* Handle end of box and title for individual track type settings */
 {
 if (boxed)
     puts("</td></tr></table>");
 }
 
 void wigCfgUi(struct cart *cart, struct trackDb *tdb,char *name,char *title,boolean boxed)
 /* UI for the wiggle track */
 {
 char *typeLine = NULL;  /*  to parse the trackDb type line  */
 char *words[8];     /*  to parse the trackDb type line  */
 int wordCount = 0;  /*  to parse the trackDb type line  */
 char option[256];
 double minY;        /*  from trackDb or cart    */
 double maxY;        /*  from trackDb or cart    */
 double tDbMinY;     /*  data range limits from trackDb type line */
 double tDbMaxY;     /*  data range limits from trackDb type line */
 int defaultHeight;  /*  pixels per item */
 char *horizontalGrid = NULL;    /*  Grid lines, off by default */
 char *lineBar;  /*  Line or Bar graph */
 char *autoScale;    /*  Auto scaling on or off */
 char *windowingFunction;    /*  Maximum, Mean, or Minimum */
 char *smoothingWindow;  /*  OFF or [2:16] */
 char *yLineMarkOnOff;   /*  user defined Y marker line to draw */
 double yLineMark;       /*  from trackDb or cart    */
 int maxHeightPixels = atoi(DEFAULT_HEIGHT_PER);
 int minHeightPixels = MIN_HEIGHT_PER;
 
 boxed = cfgBeginBoxAndTitle(tdb, boxed, title);
 
 wigFetchMinMaxPixelsWithCart(cart,tdb,name,&minHeightPixels, &maxHeightPixels, &defaultHeight);
 typeLine = cloneString(tdb->type);
 wordCount = chopLine(typeLine,words);
 
 wigFetchMinMaxYWithCart(cart,tdb,name, &minY, &maxY, &tDbMinY, &tDbMaxY, wordCount, words);
 freeMem(typeLine);
 
 (void) wigFetchHorizontalGridWithCart(cart,tdb,name, &horizontalGrid);
 (void) wigFetchAutoScaleWithCart(cart,tdb,name, &autoScale);
 (void) wigFetchGraphTypeWithCart(cart,tdb,name, &lineBar);
 (void) wigFetchWindowingFunctionWithCart(cart,tdb,name, &windowingFunction);
 (void) wigFetchSmoothingWindowWithCart(cart,tdb,name, &smoothingWindow);
 (void) wigFetchYLineMarkWithCart(cart,tdb,name, &yLineMarkOnOff);
 wigFetchYLineMarkValueWithCart(cart,tdb,name, &yLineMark);
 
 printf("<TABLE BORDER=0>");
 
 printf("<TR valign=center><th align=right>Type of graph:</th><td align=left>");
 snprintf( option, sizeof(option), "%s.%s", name, LINEBAR );
 wiggleGraphDropDown(option, lineBar);
 if(boxed)
     {
     printf("</td><td align=right colspan=2>");
     printf("<A HREF=\"%s\" TARGET=_blank>Graph configuration help</A>",WIGGLE_HELP_PAGE);
     }
 puts("</td></TR>");
 
 printf("<TR valign=center><th align=right>Track height:</th><td align=left colspan=3>");
 snprintf(option, sizeof(option), "%s.%s", name, HEIGHTPER );
 cgiMakeIntVarWithLimits(option, defaultHeight, "Track height",0, minHeightPixels, maxHeightPixels);
 printf("pixels&nbsp;(range: %d to %d)",
     minHeightPixels, maxHeightPixels);
 puts("</TD></TR>");
 
 printf("<TR valign=center><th align=right>Vertical viewing range:</th><td align=left>&nbsp;min:&nbsp;");
 snprintf(option, sizeof(option), "%s.%s", name, MIN_Y );
 cgiMakeDoubleVarWithLimits(option, minY, "Range min", 0, tDbMinY, tDbMaxY);
 printf("</td><td align=leftv colspan=2>max:&nbsp;");
 snprintf(option, sizeof(option), "%s.%s", name, MAX_Y );
 cgiMakeDoubleVarWithLimits(option, maxY, "Range max", 0, tDbMinY, tDbMaxY);
 printf("&nbsp;(range: %g to %g)",
     tDbMinY, tDbMaxY);
 puts("</TD></TR>");
 
 printf("<TR valign=center><th align=right>Data view scaling:</th><td align=left colspan=3>");
 snprintf(option, sizeof(option), "%s.%s", name, AUTOSCALE );
 wiggleScaleDropDown(option, autoScale);
 puts("</TD></TR>");
 
 printf("<TR valign=center><th align=right>Windowing function:</th><td align=left>");
 snprintf(option, sizeof(option), "%s.%s", name, WINDOWINGFUNCTION );
 wiggleWindowingDropDown(option, windowingFunction);
 
 printf("<th align=right>Smoothing window:</th><td align=left>");
 snprintf(option, sizeof(option), "%s.%s", name, SMOOTHINGWINDOW );
 wiggleSmoothingDropDown(option, smoothingWindow);
 puts("&nbsp;pixels</TD></TR>");
 
 printf("<TR valign=center><td align=right><b>Draw y indicator lines:</b><td align=left colspan=2>");
 printf("at y = 0.0:");
 snprintf(option, sizeof(option), "%s.%s", name, HORIZGRID );
 wiggleGridDropDown(option, horizontalGrid);
 printf("&nbsp;&nbsp;&nbsp;at y =");
 snprintf(option, sizeof(option), "%s.%s", name, YLINEMARK );
 cgiMakeDoubleVarWithLimits(option, yLineMark, "Indicator at Y", 0, tDbMinY, tDbMaxY);
 printf("</td><td align=left>");
 snprintf(option, sizeof(option), "%s.%s", name, YLINEONOFF );
 wiggleYLineMarkDropDown(option, yLineMarkOnOff);
 if(boxed)
     puts("</TD></TR></TABLE>");
 else
     {
     puts("</TD></TR></TABLE>");
     printf("<A HREF=\"%s\" TARGET=_blank>Graph configuration help</A>",WIGGLE_HELP_PAGE);
     }
 
 cfgEndBox(boxed);
 }
 
 
 void scoreGrayLevelCfgUi(struct cart *cart, struct trackDb *tdb, char *prefix, int scoreMax)
 /* If scoreMin has been set, let user select the shade of gray for that score, in case
  * the default is too light to see or darker than necessary. */
 {
 boolean compositeLevel = isNameAtCompositeLevel(tdb,prefix);
 char *scoreMinStr = trackDbSettingClosestToHome(tdb, GRAY_LEVEL_SCORE_MIN);
 if (scoreMinStr != NULL)
     {
     int scoreMin = atoi(scoreMinStr);
     // maxShade=9 taken from hgTracks/simpleTracks.c.  Ignore the 10 in shadesOfGray[10+1] --
     // maxShade is used to access the array.
     int maxShade = 9;
     int scoreMinGrayLevel = scoreMin * maxShade/scoreMax;
     if (scoreMinGrayLevel <= 0) scoreMinGrayLevel = 1;
     int minGrayLevel = cartUsualIntClosestToHome(cart, tdb, compositeLevel, MIN_GRAY_LEVEL, scoreMinGrayLevel);
     if (minGrayLevel <= 0) minGrayLevel = 1;
     if (minGrayLevel > maxShade) minGrayLevel = maxShade;
     puts("\n<P><B>Shade of lowest-scoring items: </B>");
     // Add javascript to select so that its color is consistent with option colors:
     int level = 255 - (255*minGrayLevel / maxShade);
     printf("<SELECT NAME=\"%s.%s\" STYLE='color: #%02x%02x%02x' class='normalText'",
 	   prefix, MIN_GRAY_LEVEL, level, level, level);
     int i;
 #ifdef OMIT
     // IE works without this code and FF doesn't work with it.
     printf(" onchange=\"switch(this.value) {");
     for (i = 1;  i < maxShade;  i++)
         {
         level = 255 - (255*i / maxShade);
         printf ("case '%d': $(this).css('color','#%02x%02x%02x'); break; ",i, level, level, level);
         }
     level = 255 - (255*i / maxShade);
     printf("default: $(this).css('color','#%02x%02x%02x'); }\"", level, level, level);
 #endif//def OMIT
     puts(">\n");
     // Use class to set color of each option:
     for (i = 1;  i <= maxShade;  i++)
         {
         level = 255 - (255*i / maxShade);
         printf("<OPTION%s STYLE='color: #%02x%02x%02x' VALUE=%d>",
             (minGrayLevel == i ? " SELECTED" : ""), level, level, level, i);
         if (i == maxShade)
             printf("&bull; black</OPTION>\n");
         else
             printf("&bull; gray (%d%%)</OPTION>\n", i * (100/maxShade));
         }
     printf("</SELECT></P>\n");
     }
 }
 
 static boolean getScoreDefaultsFromTdb(struct trackDb *tdb, char *scoreName,char *defaults,char**min,char**max)
 /* returns TRUE if defaults exist and sets the string pointer (because they may be float or int)
    if min or max are set, then they should be freed */
 {
 if(min)
     *min = NULL; // default these outs!
 if(max)
     *max = NULL;
 char *setting = trackDbSettingClosestToHome(tdb, scoreName);
 if(setting)
     {
     if(strchr(setting,':') != NULL)
         return colonPairToStrings(setting,min,max);
     else if(min)
         *min = cloneString(setting);
     return TRUE;
     }
 return FALSE;
 }
 
 static boolean getScoreLimitsFromTdb(struct trackDb *tdb, char *scoreName,char *defaults,char**min,char**max)
 /* returns TRUE if limits exist and sets the string pointer (because they may be float or int)
    if min or max are set, then they should be freed */
 {
 if(min)
     *min = NULL; // default these outs!
 if(max)
     *max = NULL;
 char scoreLimitName[128];
 safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _LIMITS);
 char *setting = trackDbSettingClosestToHome(tdb, scoreLimitName);
 if(setting)
     {
     return colonPairToStrings(setting,min,max);
     }
 else
     {
     if(min)
         {
         safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MIN);
         setting = trackDbSettingClosestToHome(tdb, scoreLimitName);
         if(setting)
             *min = cloneString(setting);
         }
     if(max)
         {
         safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MAX);
         setting = trackDbSettingClosestToHome(tdb, scoreLimitName);
         if(setting)
             *max = cloneString(setting);
         }
     return TRUE;
     }
 if(defaults != NULL && ((min && *min == NULL) || (max && *max == NULL)))
     {
     char *minLoc=NULL;
     char *maxLoc=NULL;
     if(colonPairToStrings(defaults,&minLoc,&maxLoc))
         {
         if(min && *min == NULL && minLoc != NULL)
             *min=minLoc;
         else
             freeMem(minLoc);
         if(max && *max == NULL && maxLoc != NULL)
             *max=maxLoc;
         else
             freeMem(maxLoc);
         return TRUE;
         }
     }
 return FALSE;
 }
 
 static void getScoreIntRangeFromCart(struct cart *cart, struct trackDb *tdb, char *scoreName,
                                  int *limitMin, int *limitMax,int *min,int *max)
 /* gets an integer score range from the cart, but the limits from trackDb
    for any of the pointers provided, will return a value found, if found, else it's contents
    are undisturbed (use NO_VALUE to recognize unavaliable values) */
 {
 char scoreLimitName[128];
 char *deMin=NULL,*deMax=NULL;
 if((limitMin || limitMax) && getScoreLimitsFromTdb(tdb,scoreName,NULL,&deMin,&deMax))
     {
     if(deMin != NULL && limitMin)
         *limitMin = atoi(deMin);
     if(deMax != NULL && limitMax)
         *limitMax = atoi(deMax);
     freeMem(deMin);
     freeMem(deMax);
     }
 if((min || max) && getScoreDefaultsFromTdb(tdb,scoreName,NULL,&deMin,&deMax))
     {
     if(deMin != NULL && min)
         *min = atoi(deMin);
     if(deMax != NULL && max)
         *max =atoi(deMax);
     freeMem(deMin);
     freeMem(deMax);
     }
 if(max)
     {
     safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MAX);
     deMax = cartOptionalStringClosestToHome(cart, tdb,FALSE,scoreLimitName);
     if(deMax != NULL)
         *max = atoi(deMax);
     }
 if(min)
     {
     safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, (max && deMax? _MIN:"")); // Warning: name changes if max!
     deMin = cartOptionalStringClosestToHome(cart, tdb,FALSE,scoreLimitName);
     if(deMin != NULL)
         *min = atoi(deMin);
     }
 if(min && limitMin && *min != NO_VALUE && *min < *limitMin) *min = *limitMin; // defaults within range
 if(min && limitMax && *min != NO_VALUE && *min > *limitMax) *min = *limitMax;
 if(max && limitMax && *max != NO_VALUE && *max > *limitMax) *max = *limitMax;
 if(max && limitMin && *max != NO_VALUE && *max < *limitMin) *max = *limitMin;
 }
 
 static void getScoreFloatRangeFromCart(struct cart *cart, struct trackDb *tdb, char *scoreName,
                                    double *limitMin,double *limitMax,double*min,double*max)
 /* gets an double score range from the cart, but the limits from trackDb
    for any of the pointers provided, will return a value found, if found, else it's contents
    are undisturbed (use NO_VALUE to recognize unavaliable values) */
 {
 char scoreLimitName[128];
 char *deMin=NULL,*deMax=NULL;
 if((limitMin || limitMax) && getScoreLimitsFromTdb(tdb,scoreName,NULL,&deMin,&deMax))
     {
     if(deMin != NULL && limitMin)
         *limitMin = strtod(deMin,NULL);
     if(deMax != NULL && limitMax)
         *limitMax =strtod(deMax,NULL);
     freeMem(deMin);
     freeMem(deMax);
     }
 if((min || max) && getScoreDefaultsFromTdb(tdb,scoreName,NULL,&deMin,&deMax))
     {
     if(deMin != NULL && min)
         *min = strtod(deMin,NULL);
     if(deMax != NULL && max)
         *max =strtod(deMax,NULL);
     freeMem(deMin);
     freeMem(deMax);
     }
 if(max)
     {
     safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MAX);
     deMax = cartOptionalStringClosestToHome(cart, tdb,FALSE,scoreLimitName);
     if(deMax != NULL)
         *max = strtod(deMax,NULL);
     }
 if(min)
     {
     safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MIN); // name is always {filterName}Min
     deMin = cartOptionalStringClosestToHome(cart, tdb,FALSE,scoreLimitName);
     if(deMin != NULL)
         *min = strtod(deMin,NULL);
     }
 if(min && limitMin && (int)(*min) != NO_VALUE && *min < *limitMin) *min = *limitMin; // defaults within range
 if(min && limitMax && (int)(*min) != NO_VALUE && *min > *limitMax) *min = *limitMax;
 if(max && limitMax && (int)(*max) != NO_VALUE && *max > *limitMax) *max = *limitMax;
 if(max && limitMin && (int)(*max) != NO_VALUE && *max < *limitMin) *max = *limitMin;
 }
 
 void scoreCfgUi(char *db, struct cart *cart, struct trackDb *tdb, char *name, char *title,  int maxScore, boolean boxed)
 /* Put up UI for filtering bed track based on a score */
 {
 char option[256];
 boolean compositeLevel = isNameAtCompositeLevel(tdb,name);
 
 boolean scoreFilterOk = (trackDbSettingClosestToHome(tdb, NO_SCORE_FILTER) == NULL);
 boolean glvlScoreMin = (trackDbSettingClosestToHome(tdb, GRAY_LEVEL_SCORE_MIN) != NULL);
 if (! (scoreFilterOk || glvlScoreMin))
     return;
 
 boxed = cfgBeginBoxAndTitle(tdb, boxed, title);
 
 if (scoreFilterOk)
     {
     int minLimit=0,maxLimit=maxScore,minVal=0,maxVal=maxScore;
     getScoreIntRangeFromCart(cart,tdb,SCORE_FILTER,&minLimit,&maxLimit,&minVal,&maxVal);
 
     boolean filterByRange = trackDbSettingClosestToHomeOn(tdb, SCORE_FILTER _BY_RANGE);
     if (filterByRange)
         {
         puts("<B>Filter score range:  min:</B>");
         snprintf(option, sizeof(option), "%s.%s", name,SCORE_FILTER _MIN);
         cgiMakeIntVarWithLimits(option, minVal, "Minimum score",0, minLimit,maxLimit);
         puts("<B>max:</B>");
         snprintf(option, sizeof(option), "%s.%s", name,SCORE_FILTER _MAX);
         cgiMakeIntVarWithLimits(option, maxVal, "Maximum score",0,minLimit,maxLimit);
         printf("(%d to %d)\n",minLimit,maxLimit);
         }
     else
         {
         printf("<b>Show only items with score at or above:</b> ");
         snprintf(option, sizeof(option), "%s.%s", name,SCORE_FILTER);
         cgiMakeIntVarWithLimits(option, minVal, "Minimum score",0, minLimit,maxLimit);
         printf("&nbsp;&nbsp;(range: %d to %d)", minLimit, maxLimit);
         }
     }
 
 if (glvlScoreMin)
     scoreGrayLevelCfgUi(cart, tdb, name, maxScore);
 
 /* filter top-scoring N items in track */
 char *scoreCtString = trackDbSettingClosestToHome(tdb, "filterTopScorers");
 if (scoreCtString != NULL)
     {
     /* show only top-scoring items. This option only displayed if trackDb
      * setting exists.  Format:  filterTopScorers <on|off> <count> <table> */
     char *words[2];
     char *scoreFilterCt = NULL;
     chopLine(cloneString(scoreCtString), words);
     safef(option, sizeof(option), "%s.filterTopScorersOn", name);
     bool doScoreCtFilter =
         cartUsualBooleanClosestToHome(cart, tdb, compositeLevel, "filterTopScorersOn", sameString(words[0], "on"));
     puts("<P>");
     cgiMakeCheckBox(option, doScoreCtFilter);
     safef(option, sizeof(option), "%s.filterTopScorersCt", name);
     scoreFilterCt = cartUsualStringClosestToHome(cart, tdb, compositeLevel, "filterTopScorersCt", words[1]);
 
     puts("&nbsp; <B> Show only items in top-scoring </B>");
     cgiMakeIntVarWithLimits(option,atoi(scoreFilterCt),"Top-scoring count",0,1,100000);
     /* Only check size of table if track does not have subtracks */
     if ( !compositeLevel && hTableExists(db, tdb->tableName))
         printf("&nbsp; (range: 1 to 100,000 total items: %d)\n",getTableSize(db, tdb->tableName));
     else
         printf("&nbsp; (range: 1 to 100,000)\n");
     }
 cfgEndBox(boxed);
 }
 
 void netAlignCfgUi(char *db, struct cart *cart, struct trackDb *tdb, char *prefix, char *title, boolean boxed)
 /* Put up UI for net tracks */
 {
 boxed = cfgBeginBoxAndTitle(tdb, boxed, title);
 
 boolean compositeLevel = isNameAtCompositeLevel(tdb,prefix);
 
 enum netColorEnum netColor = netFetchColorOption(cart, tdb, compositeLevel);
 
 char optString[256];	/*	our option strings here	*/
 safef(optString, ArraySize(optString), "%s.%s", prefix, NET_COLOR );
 printf("<p><b>Color nets by:&nbsp;</b>");
 netColorDropDown(optString, netColorEnumToString(netColor));
 
 #ifdef NOT_YET
 enum netLevelEnum netLevel = netFetchLevelOption(cart, tdb, compositeLevel);
 
 safef( optString, ArraySize(optString), "%s.%s", prefix, NET_LEVEL );
 printf("<p><b>Limit display of nets to:&nbsp;</b>");
 netLevelDropDown(optString, netLevelEnumToString(netLevel));
 #endif
 
 cfgEndBox(boxed);
 }
 
 void chainCfgUi(char *db, struct cart *cart, struct trackDb *tdb, char *prefix, char *title, boolean boxed, char *chromosome)
 /* Put up UI for chain tracks */
 {
 boxed = cfgBeginBoxAndTitle(tdb, boxed, title);
 
 boolean compositeLevel = isNameAtCompositeLevel(tdb,prefix);
 
 enum chainColorEnum chainColor =
 	chainFetchColorOption(cart, tdb, compositeLevel);
 
 /* check if we have normalized scores available */
 boolean normScoreAvailable = FALSE;
 
 if (! compositeLevel)
     {
     if (chromosome)
 	{
 	if (chainDbNormScoreAvailable(db, chromosome, tdb->tableName, NULL))
 	    normScoreAvailable = TRUE;
 	}
     else
 	{
 	// This will not work if tableName is a split table, we don't know
 	//	the chromosome at this point here
 	struct sqlConnection *conn = hAllocConn(db);
 	int tblIx = sqlFieldIndex(conn, tdb->tableName, "normScore");
 	normScoreAvailable = (tblIx > -1) ? TRUE : FALSE;
 	hFreeConn(&conn);
 	}
     }
 else
     {
     char * colorOptionType =
      trackDbSettingClosestToHomeOrDefault(tdb, "chainNormScoreAvailable", "no");
     if (differentWord(colorOptionType, "no"))
 	normScoreAvailable = TRUE;
     }
 
 char optString[256];
 if (normScoreAvailable)
     {
     safef(optString, ArraySize(optString), "%s.%s", prefix, OPT_CHROM_COLORS );
     printf("<p><b>Color chains by:&nbsp;</b>");
     chainColorDropDown(optString, chainColorEnumToString(chainColor));
     }
 else
     {
     printf("<p><b>Color track based on chromosome:</b>&nbsp;");
 
     char optString[256];
     /* initial value of chromosome coloring option is "on", unless
      * overridden by the colorChromDefault setting in the track */
     char *binaryColorDefault =
 	    trackDbSettingClosestToHomeOrDefault(tdb, "colorChromDefault", "on");
     /* allow cart to override trackDb setting */
     safef(optString, sizeof(optString), "%s.color", prefix);
     char * colorSetting = cartUsualStringClosestToHome(cart, tdb,
 	compositeLevel, "color", binaryColorDefault);
     cgiMakeRadioButton(optString, "on", sameString(colorSetting, "on"));
     printf(" on ");
     cgiMakeRadioButton(optString, "off", sameString(colorSetting, "off"));
     printf(" off ");
     printf("<br>\n");
     }
 
 printf("<p><b>Filter by chromosome (e.g. chr10):</b> ");
 safef(optString, ArraySize(optString), "%s.%s", prefix, OPT_CHROM_FILTER);
 cgiMakeTextVar(optString,
     cartUsualStringClosestToHome(cart, tdb, compositeLevel,
 	OPT_CHROM_FILTER, ""), 15);
 
 if (normScoreAvailable)
     scoreCfgUi(db, cart,tdb,prefix,NULL,CHAIN_SCORE_MAXIMUM,FALSE);
 
 cfgEndBox(boxed);
 }
 
 static boolean showScoreFilter(struct cart *cart, struct trackDb *tdb, boolean *opened, boolean boxed,
                                boolean compositeLevel,char *name, char *title, char *label,
                                char *scoreName,char *defaults,char *limitsDefault)
 /* Shows a score filter control with minimum value and optional range */
 {
 char *setting = trackDbSettingClosestToHomeOrDefault(tdb, scoreName,defaults);//"0.0");
 if(setting)
     {
     if(*opened == FALSE)
         {
         boxed = cfgBeginBoxAndTitle(tdb, boxed, title);
         puts("<TABLE>");
         *opened = TRUE;
         }
     printf("<TR><TD align='right'><B>%s:</B><TD align='left'>",label);
     char varName[256];
     char altLabel[256];
     safef(varName, sizeof(varName), "%s%s", scoreName, _BY_RANGE);
     boolean filterByRange = trackDbSettingClosestToHomeOn(tdb, varName);
     double minLimit=NO_VALUE,maxLimit=NO_VALUE;
     colonPairToDoubles(limitsDefault,&minLimit,&maxLimit);
     double minVal=minLimit,maxVal=maxLimit;
     colonPairToDoubles(setting,&minVal,&maxVal);
     getScoreFloatRangeFromCart(cart,tdb,scoreName,&minLimit,&maxLimit,&minVal,&maxVal);
     safef(varName, sizeof(varName), "%s.%s%s", name, scoreName, _MIN);
     safef(altLabel, sizeof(altLabel), "%s%s", (filterByRange?"Minimum ":""), label);
     cgiMakeDoubleVarWithLimits(varName,minVal, altLabel, 0,minLimit, maxLimit);
     if(filterByRange) // TODO: Test this range stuff which is not yet used
         {
         printf("<TD align='left'>to<TD align='left'>");
         safef(varName, sizeof(varName), "%s.%s%s", name, scoreName, _MAX);
         safef(altLabel, sizeof(altLabel), "%s%s", (filterByRange?"Maximum ":""), label);
         cgiMakeDoubleVarWithLimits(varName,maxVal, altLabel, 0,minLimit, maxLimit);
         }
     safef(altLabel, sizeof(altLabel), "%s", (filterByRange?"": "colspan=3"));
     if(minLimit != NO_VALUE && maxLimit != NO_VALUE)
         printf("<TD align='left'%s> (%g to %g)",altLabel,minLimit, maxLimit);
     else if(minLimit != NO_VALUE)
         printf("<TD align='left'%s> (minimum %g)",altLabel,minLimit);
     else if(maxLimit != NO_VALUE)
         printf("<TD align='left'%s> (maximum %g)",altLabel,maxLimit);
     else
         printf("<TD align='left'%s",altLabel);
     puts("</TR>");
     return TRUE;
     }
 return FALSE;
 }
 struct dyString *dyAddFilterAsInt(struct cart *cart, struct trackDb *tdb,
        struct dyString *extraWhere,char *filter,char *defaultLimits, char*field, boolean *and)
 /* creates the where clause condition to support numeric int filter range.
    Filters are expected to follow
         {fiterName}: trackDb min or min:max - default value(s);
         {filterName}Min or {filterName}: min (user supplied) cart variable;
         {filterName}Max: max (user supplied) cart variable;
         {filterName}Limits: trackDb allowed range "0:1000" Optional
            uses:{filterName}Min: old trackDb value if {filterName}Limits not found
                 {filterName}Max: old trackDb value if {filterName}Limits not found
                 defaultLimits: function param if no tdb limits settings found)
    The 'and' param and dyString in/out allows stringing multiple where clauses together */
 {
 char filterLimitName[64];
 if(sameWord(filter,NO_SCORE_FILTER))
     safef(filterLimitName, sizeof(filterLimitName), "%s", NO_SCORE_FILTER);
 else
     safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter,_NO);
 if(trackDbSettingClosestToHome(tdb, filterLimitName) != NULL)
     return extraWhere;
 
 char *setting = NULL;
 if(differentWord(filter,SCORE_FILTER))
     setting = trackDbSettingClosestToHome(tdb, filter);
 else
     setting = trackDbSettingClosestToHomeOrDefault(tdb, filter,"0:1000");
 if(setting || sameWord(filter,NO_SCORE_FILTER))
     {
     boolean invalid = FALSE;
     int minValueTdb = 0,maxValueTdb = NO_VALUE;
     colonPairToInts(setting,&minValueTdb,&maxValueTdb);
     int minLimit=NO_VALUE,maxLimit=NO_VALUE,min=minValueTdb,max=maxValueTdb;
     colonPairToInts(defaultLimits,&minLimit,&maxLimit);
     getScoreIntRangeFromCart(cart,tdb,filter,&minLimit,&maxLimit,&min,&max);
     if(minLimit != NO_VALUE || maxLimit != NO_VALUE)
         {
         // assume tdb default values within range! (don't give user errors that have no consequence)
         if((min != minValueTdb && ((minLimit != NO_VALUE && min < minLimit)
                                 || (maxLimit != NO_VALUE && min > maxLimit)))
         || (max != maxValueTdb && ((minLimit != NO_VALUE && max < minLimit)
                                 || (maxLimit != NO_VALUE && max > maxLimit))))
             {
             invalid = TRUE;
             char value[64];
             if(max == NO_VALUE) // min only is allowed, but max only is not
                 safef(value, sizeof(value), "entered minimum (%d)", min);
             else
                 safef(value, sizeof(value), "entered range (min:%d and max:%d)", min, max);
             char limits[64];
             if(minLimit != NO_VALUE && maxLimit != NO_VALUE)
                 safef(limits, sizeof(limits), "violates limits (%d to %d)", minLimit, maxLimit);
             else if(minLimit != NO_VALUE)
                 safef(limits, sizeof(limits), "violates lower limit (%d)", minLimit);
             else //if(maxLimit != NO_VALUE)
                 safef(limits, sizeof(limits), "violates uppper limit (%d)", maxLimit);
             warn("invalid filter by %s: %s %s for track %s", field, value, limits, tdb->tableName);
             }
         }
     // else no default limits!
     if(invalid)
         {
         safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, (max!=NO_VALUE?_MIN:""));
         cartRemoveVariableClosestToHome(cart,tdb,FALSE,filterLimitName);
         safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _MAX);
         cartRemoveVariableClosestToHome(cart,tdb,FALSE,filterLimitName);
         }
 //#define FILTER_ASSUMES_RANGE_AT_LIMITS_IS_VALID_FILTER
 #ifdef FILTER_ASSUMES_RANGE_AT_LIMITS_IS_VALID_FILTER
     else if((min != 0 && (int)min != NO_VALUE) || (int)max != NO_VALUE) // Assumes min==0 is no filter!
         {
         if((min != 0 && min != NO_VALUE) && max != NO_VALUE)
             dyStringPrintf(extraWhere, "%s(%s BETWEEN %d and %d)", (*and?" and ":""),field,min,max); // both min and max
         else if(min != 0 && min != NO_VALUE)
             dyStringPrintf(extraWhere, "%s(%s >= %d)", (*and?" and ":""),field,min);  // min only
         else //if(max != NO_VALUE)
             dyStringPrintf(extraWhere, "%s(%s <= %d)", (*and?" and ":""),field,max);  // max only
 #else//ifndef FILTER_ASSUMES_RANGE_AT_LIMITS_IS_VALID_FILTER
     else if((min != NO_VALUE && (minLimit == NO_VALUE || minLimit != min))  // Assumes min==NO_VALUE or min==minLimit is no filter
          || (max != NO_VALUE && (maxLimit == NO_VALUE || maxLimit != max))) // Assumes max==NO_VALUE or max==maxLimit is no filter!
         {
         if(max == NO_VALUE || (maxLimit != NO_VALUE && maxLimit == max))
             dyStringPrintf(extraWhere, "%s(%s >= %d)", (*and?" and ":""),field,min);  // min only
         else if(min == NO_VALUE || (minLimit != NO_VALUE && minLimit == min))
             dyStringPrintf(extraWhere, "%s(%s <= %d)", (*and?" and ":""),field,max);  // max only
         else
             dyStringPrintf(extraWhere, "%s(%s BETWEEN %d and %d)", (*and?" and ":""),field,min,max); // both min and max
 #endif//ndef FILTER_ASSUMES_RANGE_AT_LIMITS_IS_VALID_FILTER
         *and=TRUE;
         //warn("%s: %s",tdb->tableName,extraWhere->string);
         }
     }
 return extraWhere;
 }
 
 struct dyString *dyAddFilterAsDouble(struct cart *cart, struct trackDb *tdb,
        struct dyString *extraWhere,char *filter,char *defaultLimits, char*field, boolean *and)
 /* creates the where clause condition to support numeric double filters.
    Filters are expected to follow
         {fiterName}: trackDb min or min:max - default value(s);
         {filterName}Min or {filterName}: min (user supplied) cart variable;
         {filterName}Max: max (user supplied) cart variable;
         {filterName}Limits: trackDb allowed range "0.0:10.0" Optional
             uses:  defaultLimits: function param if no tdb limits settings found)
    The 'and' param and dyString in/out allows stringing multiple where clauses together */
 {
 char *setting = trackDbSettingClosestToHome(tdb, filter);
 if(setting)
     {
     boolean invalid = FALSE;
     double minValueTdb = 0,maxValueTdb = NO_VALUE;
     colonPairToDoubles(setting,&minValueTdb,&maxValueTdb);
     double minLimit=NO_VALUE,maxLimit=NO_VALUE,min=minValueTdb,max=maxValueTdb;
     colonPairToDoubles(defaultLimits,&minLimit,&maxLimit);
     getScoreFloatRangeFromCart(cart,tdb,filter,&minLimit,&maxLimit,&min,&max);
     if((int)minLimit != NO_VALUE || (int)maxLimit != NO_VALUE)
         {
         // assume tdb default values within range! (don't give user errors that have no consequence)
         if((min != minValueTdb && (((int)minLimit != NO_VALUE && min < minLimit)
                                 || ((int)maxLimit != NO_VALUE && min > maxLimit)))
         || (max != maxValueTdb && (((int)minLimit != NO_VALUE && max < minLimit)
                                 || ((int)maxLimit != NO_VALUE && max > maxLimit))))
             {
             invalid = TRUE;
             char value[64];
             if((int)max == NO_VALUE) // min only is allowed, but max only is not
                 safef(value, sizeof(value), "entered minimum (%g)", min);
             else
                 safef(value, sizeof(value), "entered range (min:%g and max:%g)", min, max);
             char limits[64];
             if((int)minLimit != NO_VALUE && (int)maxLimit != NO_VALUE)
                 safef(limits, sizeof(limits), "violates limits (%g to %g)", minLimit, maxLimit);
             else if((int)minLimit != NO_VALUE)
                 safef(limits, sizeof(limits), "violates lower limit (%g)", minLimit);
             else //if((int)maxLimit != NO_VALUE)
                 safef(limits, sizeof(limits), "violates uppper limit (%g)", maxLimit);
             warn("invalid filter by %s: %s %s for track %s", field, value, limits, tdb->tableName);
             }
         }
     if(invalid)
         {
         char filterLimitName[64];
         safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _MIN);
         cartRemoveVariableClosestToHome(cart,tdb,FALSE,filterLimitName);
         safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _MAX);
         cartRemoveVariableClosestToHome(cart,tdb,FALSE,filterLimitName);
         }
 #ifdef FILTER_ASSUMES_RANGE_AT_LIMITS_IS_VALID_FILTER
     else if((min != 0 && (int)min != NO_VALUE) || (int)max != NO_VALUE) // Assumes min==0 is no filter!
         {
         if((min != 0 && (int)min != NO_VALUE) && (int)max != NO_VALUE)
             dyStringPrintf(extraWhere, "%s(%s BETWEEN %g and %g)", (*and?" and ":""),field,min,max); // both min and max
         else if(min != 0 && (int)min != NO_VALUE)
             dyStringPrintf(extraWhere, "%s(%s >= %g)", (*and?" and ":""),field,min);  // min only
         else //if((int)max != NO_VALUE)
             dyStringPrintf(extraWhere, "%s(%s <= %g)", (*and?" and ":""),field,max);  // max only
 #else//ifndef FILTER_ASSUMES_RANGE_AT_LIMITS_IS_VALID_FILTER
     else if(((int)min != NO_VALUE && ((int)minLimit == NO_VALUE || minLimit != min))  // Assumes min==NO_VALUE or min==minLimit is no filter
          || ((int)max != NO_VALUE && ((int)maxLimit == NO_VALUE || maxLimit != max))) // Assumes max==NO_VALUE or max==maxLimit is no filter!
         {
         if((int)max == NO_VALUE || ((int)maxLimit != NO_VALUE && maxLimit == max))
             dyStringPrintf(extraWhere, "%s(%s >= %g)", (*and?" and ":""),field,min);  // min only
         else if((int)min == NO_VALUE || ((int)minLimit != NO_VALUE && minLimit == min))
             dyStringPrintf(extraWhere, "%s(%s <= %g)", (*and?" and ":""),field,max);  // max only
         else
             dyStringPrintf(extraWhere, "%s(%s BETWEEN %g and %g)", (*and?" and ":""),field,min,max); // both min and max
 #endif//ndef FILTER_ASSUMES_RANGE_AT_LIMITS_IS_VALID_FILTER
         *and=TRUE;
         }
     }
 return extraWhere;
 }
 
 void encodePeakCfgUi(struct cart *cart, struct trackDb *tdb, char *name, char *title, boolean boxed)
 /* Put up UI for filtering wgEnocde peaks based on score, Pval and Qval */
 {
 boolean compositeLevel = isNameAtCompositeLevel(tdb,name);
 boolean opened = FALSE;
 showScoreFilter(cart,tdb,&opened,boxed,compositeLevel,name,title,"Minimum Q-Value (-log 10)",QVALUE_FILTER,NULL,NULL);//,"0.0",NULL);
 showScoreFilter(cart,tdb,&opened,boxed,compositeLevel,name,title,"Minimum P-Value (-log 10)",PVALUE_FILTER,NULL,NULL);//,"0.0",NULL);
 showScoreFilter(cart,tdb,&opened,boxed,compositeLevel,name,title,"Minimum Signal value",     SIGNAL_FILTER,NULL,NULL);//,"0.0",NULL);
 
 char *setting = trackDbSettingClosestToHomeOrDefault(tdb, SCORE_FILTER,NULL);//"0:1000");
 if(setting)
     {
     if(!opened)
         {
         boxed = cfgBeginBoxAndTitle(tdb, boxed, title);
         puts("<TABLE>");
         opened = TRUE;
         }
     char varName[256];
     int minLimit=0,maxLimit=1000,minVal=0,maxVal=NO_VALUE;
     colonPairToInts(setting,&minVal,&maxVal);
     getScoreIntRangeFromCart(cart,tdb,SCORE_FILTER,&minLimit,&maxLimit,&minVal,&maxVal);
     if(maxVal != NO_VALUE)
         puts("<TR><TD align='right'><B>Score range: min:</B><TD align='left'>");
     else
         puts("<TR><TD align='right'><B>Minimum score:</B><TD align='left'>");
     safef(varName, sizeof(varName), "%s%s", SCORE_FILTER, _BY_RANGE);
     boolean filterByRange = trackDbSettingClosestToHomeOn(tdb, varName);
     safef(varName, sizeof(varName), "%s.%s%s", name, SCORE_FILTER, (filterByRange?_MIN:""));
     cgiMakeIntVarWithLimits(varName, minVal, "Minimum score", 0, minLimit, maxLimit);
     if(filterByRange)
         {
         if(maxVal == NO_VALUE)
             maxVal = maxLimit;
         puts("<TD align='right'>to<TD align='left'>");
         safef(varName, sizeof(varName), "%s.%s%s", name, SCORE_FILTER,_MAX);
         cgiMakeIntVarWithLimits(varName, maxVal, "Maximum score", 0, minLimit, maxLimit);
         }
     printf("<TD align='left'%s> (%d to %d)",(filterByRange?"":" colspan=3"),minLimit, maxLimit);
     if(trackDbSettingClosestToHome(tdb, GRAY_LEVEL_SCORE_MIN) != NULL)
         {
         printf("<TR><TD align='right'colspan=5>");
         scoreGrayLevelCfgUi(cart, tdb, name, 1000);
         puts("</TR>");
         }
     }
 if(opened)
     {
     puts("</TABLE>");
     cfgEndBox(boxed);
     }
 }
 
 void genePredCfgUi(struct cart *cart, struct trackDb *tdb, char *name, char *title, boolean boxed)
 /* Put up gencode-specific controls */
 {
 char varName[64];
 boolean compositeLevel = isNameAtCompositeLevel(tdb,name);
 char *geneLabel = cartUsualStringClosestToHome(cart, tdb,compositeLevel, "label", "gene");
 
 boxed = cfgBeginBoxAndTitle(tdb, boxed, title);
 
 if (sameString(name, "acembly"))
     {
     char *acemblyClass = cartUsualStringClosestToHome(cart,tdb,compositeLevel,"type", acemblyEnumToString(0));
     printf("<p><b>Gene Class: </b>");
     acemblyDropDown("acembly.type", acemblyClass);
     printf("  ");
     }
 else if(sameString("wgEncodeSangerGencode", name)
 || (startsWith("encodeGencode", name) && !sameString("encodeGencodeRaceFrags", name)))
     {
     printf("<B>Label:</B> ");
     safef(varName, sizeof(varName), "%s.label", name);
     cgiMakeRadioButton(varName, "gene", sameString("gene", geneLabel));
     printf("%s ", "gene");
     cgiMakeRadioButton(varName, "accession", sameString("accession", geneLabel));
     printf("%s ", "accession");
     cgiMakeRadioButton(varName, "both", sameString("both", geneLabel));
     printf("%s ", "both");
     cgiMakeRadioButton(varName, "none", sameString("none", geneLabel));
     printf("%s ", "none");
     }
 
 if(trackDbSettingClosestToHomeOn(tdb, "nmdFilter"))
     {
     boolean nmdDefault = FALSE;
     safef(varName, sizeof(varName), "hgt.%s.nmdFilter", name);
     nmdDefault = cartUsualBoolean(cart,varName, FALSE);  // TODO: var name (hgt prefix) needs changing before ClosesToHome can be used
     printf("<p><b>Filter out NMD targets.</b>");
     cgiMakeCheckBox(varName, nmdDefault);
     }
 
 if(!sameString(tdb->tableName, "tigrGeneIndex")
 && !sameString(tdb->tableName, "ensGeneNonCoding")
 && !sameString(tdb->tableName, "encodeGencodeRaceFrags"))
     baseColorDrawOptDropDown(cart, tdb);
 
 filterBy_t *filterBySet = filterBySetGet(tdb,cart,name);
 if(filterBySet != NULL)
     {
     filterBySetCfgUi(tdb,filterBySet);
     filterBySetFree(&filterBySet);
     }
 cfgEndBox(boxed);
 }
 
 char **wigMafGetSpecies(struct cart *cart, struct trackDb *tdb, char *db, struct wigMafSpecies **list, int *groupCt)
 {
 int speciesCt = 0;
 char *speciesGroup = trackDbSetting(tdb, SPECIES_GROUP_VAR);
 char *speciesUseFile = trackDbSetting(tdb, SPECIES_USE_FILE);
 char *speciesOrder = trackDbSetting(tdb, SPECIES_ORDER_VAR);
 char sGroup[24];
 //Ochar *groups[20];
 struct wigMafSpecies *wmSpecies, *wmSpeciesList = NULL;
 int group;
 int i;
 #define MAX_SP_SIZE 2000
 #define MAX_GROUPS 20
 char *species[MAX_SP_SIZE];
 char option[MAX_SP_SIZE];
 
 /* determine species and groups for pairwise -- create checkboxes */
 if (speciesOrder == NULL && speciesGroup == NULL && speciesUseFile == NULL)
     {
     if (isCustomTrack(tdb->tableName))
 	return NULL;
     errAbort("Track %s missing required trackDb setting: speciesOrder, speciesGroups, or speciesUseFile", tdb->tableName);
     }
 
 char **groups = needMem(MAX_GROUPS * sizeof (char *));
 *groupCt = 1;
 if (speciesGroup)
     *groupCt = chopByWhite(speciesGroup, groups, MAX_GROUPS);
 
 if (speciesUseFile)
     {
     if ((speciesGroup != NULL) || (speciesOrder != NULL))
 	errAbort("Can't specify speciesUseFile and speciesGroup or speciesOrder");
     speciesOrder = cartGetOrderFromFile(db, cart, speciesUseFile);
     }
 
 for (group = 0; group < *groupCt; group++)
     {
     if (*groupCt != 1 || !speciesOrder)
         {
         safef(sGroup, sizeof sGroup, "%s%s",
                                 SPECIES_GROUP_PREFIX, groups[group]);
         speciesOrder = trackDbRequiredSetting(tdb, sGroup);
         }
     speciesCt = chopLine(speciesOrder, species);
     for (i = 0; i < speciesCt; i++)
         {
         AllocVar(wmSpecies);
         wmSpecies->name = cloneString(species[i]);
     	safef(option, sizeof(option), "%s.%s", tdb->tableName, wmSpecies->name);
 	wmSpecies->on = cartUsualBoolean(cart, option, TRUE);
 	//printf("checking %s and is %d\n",option,wmSpecies->on);
         wmSpecies->group = group;
         slAddHead(&wmSpeciesList, wmSpecies);
         }
     }
 slReverse(&wmSpeciesList);
 *list = wmSpeciesList;
 
 return groups;
 }
 
 struct wigMafSpecies * wigMafSpeciesTable(struct cart *cart,
     struct trackDb *tdb, char *name, char *db)
 {
 int groupCt;
 #define MAX_SP_SIZE 2000
 char option[MAX_SP_SIZE];
 int group, prevGroup;
 int i,j;
 
 bool lowerFirstChar = TRUE;
 char *speciesTarget = trackDbSetting(tdb, SPECIES_TARGET_VAR);
 char *speciesTree = trackDbSetting(tdb, SPECIES_TREE_VAR);
 
 struct wigMafSpecies *wmSpeciesList;
 char **groups = wigMafGetSpecies(cart, tdb, db, &wmSpeciesList, &groupCt);
 struct wigMafSpecies *wmSpecies = wmSpeciesList;
 struct slName *speciesList = NULL;
 
 for(; wmSpecies; wmSpecies = wmSpecies->next)
     {
     struct slName *newName = slNameNew(wmSpecies->name);
     slAddHead(&speciesList, newName);
     //printf("%s<BR>\n",speciesList->name);
     }
 slReverse(&speciesList);
 
 int numberPerRow;
 struct phyloTree *tree;
 boolean lineBreakJustPrinted;
 char trackName[255];
 char query[256];
 char **row;
 struct sqlConnection *conn;
 struct sqlResult *sr;
 char *words[MAX_SP_SIZE];
 int defaultOffSpeciesCnt = 0;
 
 jsIncludeFile("utils.js",NULL);
 //jsInit();
 puts("\n<P><B>Species selection:</B>&nbsp;");
 
 PLUS_BUTTON( "id", "plus_pw","cb_maf_","_maf_");
 MINUS_BUTTON("id","minus_pw","cb_maf_","_maf_");
 cgiContinueHiddenVar("g");
 
 char prefix[512];
 safef(prefix, sizeof prefix, "%s.", name);
 char *defaultOffSpecies = trackDbSetting(tdb, "speciesDefaultOff");
 if (defaultOffSpecies)
     {
     DEFAULT_BUTTON( "id", "default_pw","cb_maf_","_maf_");
     int wordCt = chopLine(defaultOffSpecies, words);
     defaultOffSpeciesCnt = wordCt;
     }
 
 if ((speciesTree != NULL) && ((tree = phyloParseString(speciesTree)) != NULL))
     {
     char buffer[128];
     char *nodeNames[512];
     int numNodes = 0;
     char *path, *orgName;
     int ii;
 
     safef(buffer, sizeof(buffer), "%s.vis",name);
     cartMakeRadioButton(cart, buffer,"useTarg", "useTarg");
     printf("Show shortest path to target species:  ");
     path = phyloNodeNames(tree);
     numNodes = chopLine(path, nodeNames);
     for(ii=0; ii < numNodes; ii++)
         {
         if ((orgName = hOrganism(nodeNames[ii])) != NULL)
             nodeNames[ii] = orgName;
         nodeNames[ii][0] = toupper(nodeNames[ii][0]);
         }
 
     cgiMakeDropList(SPECIES_HTML_TARGET, nodeNames, numNodes,
 	cartUsualString(cart, SPECIES_HTML_TARGET, speciesTarget));
     puts("<br>");
     cartMakeRadioButton(cart,buffer,"useCheck", "useTarg");
     printf("Show all species checked : ");
     }
 
 if (groupCt == 1)
     puts("\n<TABLE><TR>");
 group = -1;
 lineBreakJustPrinted = FALSE;
 for (wmSpecies = wmSpeciesList, i = 0, j = 0; wmSpecies != NULL;
 		    wmSpecies = wmSpecies->next, i++)
     {
     char *label;
     prevGroup = group;
     group = wmSpecies->group;
     if (groupCt != 1 && group != prevGroup)
 	{
 	i = 0;
 	j = 0;
 	if (group != 0)
 	    puts("</TR></TABLE>\n");
         /* replace underscores in group names */
         subChar(groups[group], '_', ' ');
 	printf("<P>&nbsp;&nbsp;<B><EM>%s</EM></B>", groups[group]);
     printf("&nbsp;&nbsp;");
     safef(option, sizeof(option), "plus_%s", groups[group]);
     PLUS_BUTTON( "id",option,"cb_maf_",groups[group]);
     safef(option, sizeof(option),"minus_%s", groups[group]);
     MINUS_BUTTON("id",option,"cb_maf_",groups[group]);
 
 	puts("\n<TABLE><TR>");
 	}
     if (hIsGsidServer())
 	numberPerRow = 6;
     else
 	numberPerRow = 5;
 
     /* new logic to decide if line break should be displayed here */
     if ((j != 0 && (j % numberPerRow) == 0) && (lineBreakJustPrinted == FALSE))
         {
         puts("</TR><TR>");
         lineBreakJustPrinted = TRUE;
         }
 
     char id[MAX_SP_SIZE];
     boolean checked = TRUE;
     if(defaultOffSpeciesCnt > 0)
         {
         if(stringArrayIx(wmSpecies->name,words,defaultOffSpeciesCnt) == -1)
             safef(id, sizeof(id), "cb_maf_%s_%s", groups[group], wmSpecies->name);
         else
             {
             safef(id, sizeof(id), "cb_maf_%s_%s_defOff", groups[group], wmSpecies->name);
             checked = FALSE;
             }
         }
     else
         safef(id, sizeof(id), "cb_maf_%s_%s", groups[group], wmSpecies->name);
 
     if (hIsGsidServer())
         {
         char *chp;
         /* for GSID maf, display only entries belong to the specific MSA selected */
     	safef(option, sizeof(option), "%s.%s", name, wmSpecies->name);
     	label = hOrganism(wmSpecies->name);
     	if (label == NULL)
             label = wmSpecies->name;
         strcpy(trackName, tdb->tableName);
 
         /* try AaMaf first */
         chp = strstr(trackName, "AaMaf");
         /* if it is not a AaMaf track, try Maf next */
         if (chp == NULL) chp = strstr(trackName, "Maf");
 
         /* test if the entry actually is part of the specific maf track data */
         if (chp != NULL)
             {
             *chp = '\0';
             safef(query, sizeof(query),
             "select id from %sMsa where id = 'ss.%s'", trackName, label);
 
             conn = hAllocConn(db);
             sr = sqlGetResult(conn, query);
             row = sqlNextRow(sr);
 
             /* offer it only if the entry is found in current maf data set */
             if (row != NULL)
                 {
                 puts("<TD>");
                 cgiMakeCheckBoxWithId(option, cartUsualBoolean(cart, option, checked),id);
                 printf ("%s", label);
                 puts("</TD>");
                 fflush(stdout);
                 lineBreakJustPrinted = FALSE;
                 j++;
                 }
             sqlFreeResult(&sr);
             hFreeConn(&conn);
             }
         }
     else
     	{
     	puts("<TD>");
     	safef(option, sizeof(option), "%s.%s", name, wmSpecies->name);
         wmSpecies->on = cartUsualBoolean(cart, option, checked);
         cgiMakeCheckBoxWithId(option, wmSpecies->on,id);
     	label = hOrganism(wmSpecies->name);
     	if (label == NULL)
 		label = wmSpecies->name;
         if (lowerFirstChar)
             *label = tolower(*label);
     	printf ("%s<BR>", label);
     	puts("</TD>");
         lineBreakJustPrinted = FALSE;
         j++;
         }
     }
 puts("</TR></TABLE><BR>\n");
 return wmSpeciesList;
 }
 
 void wigMafCfgUi(struct cart *cart, struct trackDb *tdb,char *name, char *title, boolean boxed, char *db)
 /* UI for maf/wiggle track
  * NOTE: calls wigCfgUi */
 {
 bool lowerFirstChar = TRUE;
 int i;
 char option[MAX_SP_SIZE];
 
 boxed = cfgBeginBoxAndTitle(tdb, boxed, title);
 
 char *defaultCodonSpecies = trackDbSetting(tdb, SPECIES_CODON_DEFAULT);
 char *framesTable = trackDbSetting(tdb, "frames");
 char *firstCase = trackDbSetting(tdb, ITEM_FIRST_CHAR_CASE);
 if (firstCase != NULL)
     {
     if (sameWord(firstCase, "noChange")) lowerFirstChar = FALSE;
     }
 char *treeImage = NULL;
 struct consWiggle *consWig, *consWiggles = wigMafWiggles(db, tdb);
 
 
 boolean isWigMafProt = FALSE;
 
 if (strstr(tdb->type, "wigMafProt")) isWigMafProt = TRUE;
 
 puts("<TABLE><TR><TD VALIGN=\"TOP\">");
 
 if (consWiggles && consWiggles->next)
     {
     /* check for alternate conservation wiggles -- create checkboxes */
     puts("<P STYLE=\"margin-top:10;\"><B>Conservation:</B>" );
     boolean first = TRUE;
     for (consWig = consWiggles; consWig != NULL; consWig = consWig->next)
         {
         char *wigVar = wigMafWiggleVar(tdb, consWig);
         cgiMakeCheckBox(wigVar, cartUsualBoolean(cart, wigVar, first));
         first = FALSE;
         subChar(consWig->uiLabel, '_', ' ');
         printf ("%s&nbsp;", consWig->uiLabel);
         }
     }
 
 struct wigMafSpecies *wmSpeciesList = wigMafSpeciesTable(cart, tdb, name, db);
 struct wigMafSpecies *wmSpecies;
 
 if (isWigMafProt)
     puts("<B>Multiple alignment amino acid-level:</B><BR>" );
 else
     puts("<B>Multiple alignment base-level:</B><BR>" );
 safef(option, sizeof option, "%s.%s", name, MAF_DOT_VAR);
 cgiMakeCheckBox(option, cartCgiUsualBoolean(cart, option, FALSE));
 
 if (isWigMafProt)
     puts("Display amino acids identical to reference as dots<BR>" );
 else
     puts("Display bases identical to reference as dots<BR>" );
 
 safef(option, sizeof option, "%s.%s", name, MAF_CHAIN_VAR);
 cgiMakeCheckBox(option, cartCgiUsualBoolean(cart, option, TRUE));
 
 char *irowStr = trackDbSetting(tdb, "irows");
 boolean doIrows = (irowStr == NULL) || !sameString(irowStr, "off");
 if (isCustomTrack(tdb->tableName) || doIrows)
     puts("Display chains between alignments<BR>");
 else
     {
     if (isWigMafProt)
 	puts("Display unaligned amino acids with spanning chain as 'o's<BR>");
     else
 	puts("Display unaligned bases with spanning chain as 'o's<BR>");
     }
 safef(option, sizeof option, "%s.%s", name, "codons");
 if (framesTable)
     {
     char *nodeNames[512];
     char buffer[128];
 
     printf("<BR><B>Codon Translation:</B><BR>");
     printf("Default species to establish reading frame: ");
     nodeNames[0] = db;
     for (wmSpecies = wmSpeciesList, i = 1; wmSpecies != NULL;
 			wmSpecies = wmSpecies->next, i++)
 	{
 	nodeNames[i] = wmSpecies->name;
 	}
     cgiMakeDropList(SPECIES_CODON_DEFAULT, nodeNames, i,
 	cartUsualString(cart, SPECIES_CODON_DEFAULT, defaultCodonSpecies));
     puts("<br>");
     safef(buffer, sizeof(buffer), "%s.codons",name);
     cartMakeRadioButton(cart, buffer,"codonNone", "codonDefault");
     printf("No codon translation<BR>");
     cartMakeRadioButton(cart, buffer,"codonDefault", "codonDefault");
     printf("Use default species reading frames for translation<BR>");
     cartMakeRadioButton(cart, buffer,"codonFrameNone", "codonDefault");
     printf("Use reading frames for species if available, otherwise no translation<BR>");
     cartMakeRadioButton(cart, buffer,"codonFrameDef", "codonDefault");
     printf("Use reading frames for species if available, otherwise use default species<BR>");
     }
 else
     {
     /* Codon highlighting does not apply to wigMafProt type */
     if (!strstr(tdb->type, "wigMafProt"))
 	{
     	puts("<P><B>Codon highlighting:</B><BR>" );
 
 #ifdef GENE_FRAMING
 
     	safef(option, sizeof(option), "%s.%s", name, MAF_FRAME_VAR);
     	char *currentCodonMode = cartCgiUsualString(cart, option, MAF_FRAME_GENE);
 
     	/* Disable codon highlighting */
    	 cgiMakeRadioButton(option, MAF_FRAME_NONE,
 		    sameString(MAF_FRAME_NONE, currentCodonMode));
     	puts("None &nbsp;");
 
     	/* Use gene pred */
     	cgiMakeRadioButton(option, MAF_FRAME_GENE,
 			    sameString(MAF_FRAME_GENE, currentCodonMode));
     	puts("CDS-annotated frame based on");
     	safef(option, sizeof(option), "%s.%s", name, MAF_GENEPRED_VAR);
     	genePredDropDown(cart, makeTrackHash(db, chromosome), NULL, option);
 
 #else
     	snprintf(option, sizeof(option), "%s.%s", name, BASE_COLORS_VAR);
     	puts ("&nbsp; Alternate colors every");
     	cgiMakeIntVar(option, cartCgiUsualInt(cart, option, 0), 1);
     	puts ("bases<BR>");
     	snprintf(option, sizeof(option), "%s.%s", name,
 			    BASE_COLORS_OFFSET_VAR);
     	puts ("&nbsp; Offset alternate colors by");
     	cgiMakeIntVar(option, cartCgiUsualInt(cart, option, 0), 1);
     	puts ("bases<BR>");
 #endif
 	}
     }
 
 treeImage = trackDbSetting(tdb, "treeImage");
 if (treeImage)
     printf("</TD><TD VALIGN=\"TOP\"><IMG SRC=\"../images/%s\"></TD></TR></TABLE>", treeImage);
 else
     puts("</TD></TR></TABLE>");
 
 if (trackDbSetting(tdb, CONS_WIGGLE) != NULL)
     {
     wigCfgUi(cart,tdb,name,"Conservation graph:",FALSE);
     }
 cfgEndBox(boxed);
 }
 
 static boolean compositeViewCfgExpandedByDefault(struct trackDb *parentTdb,char *view,char **visibility)
 /* returns true if the view cfg is expanded by default.  Optionally allocates string of view setting (eg 'dense') */
 {
 int cnt,ix;
 boolean expanded = FALSE;
 if ( visibility != NULL )
     *visibility = cloneString(hStringFromTv(parentTdb->visibility));
 char *setting = trackDbSetting(parentTdb, "visibilityViewDefaults");
 if(setting == NULL)
     return FALSE;
 
 char *target = cloneString(setting);
 char *words[SMALLBUF];
 cnt = chopLine(target, words);
 for(ix=0;ix<cnt;ix++)
     {
     if(startsWith(view,words[ix]) && words[ix][strlen(view)] == '=')
         {
         if (words[ix][strlen(words[ix]) - 1] == '+')
             {
             expanded = TRUE;
             if ( visibility != NULL )
                 words[ix][strlen(words[ix]) - 1] = 0;
             }
         if ( visibility != NULL )
             *visibility = cloneString(words[ix] + strlen(view) + 1);
         break;
         }
     }
 // At this point we need to search the cart to see if any others are already expanded.
 // cart var of style "wgEncodeYaleChIPseq.Peaks.showCfg" {parentTable}.{view}.showCfg value='on'
 freeMem(target);
 return expanded;
 }
 
 enum trackVisibility visCompositeViewDefault(struct trackDb *parentTdb,char *view)
 /* returns the default track visibility of particular view within a composite track */
 {
 char *visibility = NULL;
 (void)compositeViewCfgExpandedByDefault(parentTdb,view,&visibility);
 enum trackVisibility vis = hTvFromString(visibility);
 freeMem(visibility);
 return vis;
 }
 
 static boolean hCompositeDisplayViewDropDowns(char *db, struct cart *cart, struct trackDb *parentTdb)
 /* UI for composite view drop down selections. */
 {
 int ix;
 struct trackDb *subtrack;
 char objName[SMALLBUF];
 char javascript[JBUFSIZE];
 #define CFG_LINK  "<B><A NAME=\"a_cfg_%s\"></A><A id='a_cfg_%s' HREF=\"#a_cfg_%s\" onclick=\"return (showConfigControls('%s') == false);\" title=\"%s Configuration\">%s</A><INPUT TYPE=HIDDEN NAME='%s.%s.showCfg' value='%s'></B>\n"
 #define MAKE_CFG_LINK(name,title,tbl,open) printf(CFG_LINK, (name),(name),(name),(name),(title),(title),(tbl),(name),((open)?"on":"off"))
 
 members_t *membersOfView = subgroupMembersGet(parentTdb,"view");
 if(membersOfView == NULL)
     return FALSE;
 
 char configurable[membersOfView->count];
 memset(configurable,cfgNone,sizeof(configurable));
 int firstOpened = -1;
 boolean makeCfgRows = FALSE;
 struct trackDb **matchedSubtracks = needMem(sizeof(struct trackDb *)*membersOfView->count);
 char *setting = trackDbSetting(parentTdb,"settingsByView");
 boolean blockCfgs = (setting != NULL && (sameWord(setting,"off") || sameWord(setting,"disabled") || sameWord(setting,"false")));
 
 for (ix = 0; ix < membersOfView->count; ix++)
     {
     for (subtrack = parentTdb->subtracks; subtrack != NULL; subtrack = subtrack->next)
         {
         char *stView;
         if(!subgroupFind(subtrack,"view",&stView))
             continue;
         if(differentString(stView,membersOfView->names[ix]))
             continue;
         matchedSubtracks[ix] = subtrack;
         if(!blockCfgs)
             {
             configurable[ix] = (char)cfgTypeFromTdb(subtrack,TRUE); // Warns if not multi-view compatible
             if(configurable[ix] != cfgNone)
                 {
                 if(firstOpened == -1)
                     {
                     safef(objName, sizeof(objName), "%s.%s.showCfg", parentTdb->tableName,membersOfView->names[ix]);
                     if(cartUsualBoolean(cart,objName,FALSE))
                         firstOpened = ix;
                     }
                 makeCfgRows = TRUE;
                 }
             }
         break;
         }
     }
 
 toLowerN(membersOfView->title, 1);
 printf("<B>Select %s </B>(<A HREF=\"../../goldenPath/help/multiView.html\" title='Help on views' TARGET=_BLANK>help</A>):<BR>\n", membersOfView->title);
 puts("<TABLE><TR align=\"LEFT\">");
 for (ix = 0; ix < membersOfView->count; ix++)
     {
     if(matchedSubtracks[ix] != NULL)
         {
         printf("<TD>");
         if(configurable[ix] != cfgNone)
             {
             MAKE_CFG_LINK(membersOfView->names[ix],membersOfView->values[ix],parentTdb->tableName,(firstOpened == ix));
             }
         else
             printf("<B>%s</B>\n",membersOfView->values[ix]);
         puts("</TD>");
 
         safef(objName, sizeof(objName), "%s.%s.vis", parentTdb->tableName,membersOfView->names[ix]);
         enum trackVisibility tv =
             hTvFromString(cartUsualString(cart, objName,hStringFromTv(visCompositeViewDefault(parentTdb,membersOfView->names[ix]))));
 
         safef(javascript, sizeof(javascript), "onchange=\"matSelectViewForSubTracks(this,'%s');\"", membersOfView->names[ix]);
 
         printf("<TD>");
         hTvDropDownClassWithJavascript(objName, tv, parentTdb->canPack,"viewDd normalText",javascript);
         puts(" &nbsp; &nbsp; &nbsp;</TD>");
         // Until the cfg boxes are inserted here, this divorces the relationship
         //if(membersOfView->count > 6 && ix == ((membersOfView->count+1)/2)-1)  // An attempt at 2 rows of cfg's No good!
         //    puts("</tr><tr><td>&nbsp;</td></tr><tr>");
         }
     }
 // Need to do the same for ENCODE Gencode 'filterBy's
 puts("</TR>");
 if(makeCfgRows)
     {
     puts("</TABLE><TABLE>");
     for (ix = 0; ix < membersOfView->count; ix++)
         {
         if(matchedSubtracks[ix] != NULL)
             {
             printf("<TR id=\"tr_cfg_%s\"",membersOfView->names[ix]);
             if((firstOpened == -1 && !compositeViewCfgExpandedByDefault(parentTdb,membersOfView->names[ix],NULL))
             ||  firstOpened != ix)
                 printf(" style=\"display:none\"");
             printf("><TD width=10>&nbsp;</TD>");
             int ix2=ix;
             while(0 < ix2--)
                 printf("<TD width=100>&nbsp;</TD>");
             printf("<TD colspan=%d>",membersOfView->count+1);
             safef(objName, sizeof(objName), "%s.%s", parentTdb->tableName,membersOfView->names[ix]);
             if(configurable[ix] != cfgNone)
                 {
                 cfgByCfgType(configurable[ix],db,cart,matchedSubtracks[ix],objName,membersOfView->values[ix],TRUE);
                 cfgLinkToDependentCfgs(parentTdb,objName);
                 }
             }
         }
     }
 puts("</TABLE><BR>");
 subgroupMembersFree(&membersOfView);
 freeMem(matchedSubtracks);
 return TRUE;
 }
 
 static char *labelWithVocabLink(struct trackDb *parentTdb, struct trackDb *childTdb, char *vocabType, char *label)
 /* If the parentTdb has a controlledVocabulary setting and the vocabType is found,
    then label will be wrapped with the link to display it.  Return string is cloned. */
 {
 char *vocab = trackDbSetting(parentTdb, "controlledVocabulary");
 if(vocab == NULL)
     return cloneString(label); // No wrapping!
 
 char *words[15];
 int count,ix;
 boolean found=FALSE;
 if((count = chopByWhite(cloneString(vocab), words,15)) <= 1)
     return cloneString(label);
 for(ix=1;ix<count && !found;ix++)
     {
 #define VOCAB_LINK "<A HREF='hgEncodeVocab?ra=/usr/local/apache/cgi-bin/%s&term=\"%s\"' title='%s details' TARGET=ucscVocab>%s</A>"
     if(sameString(vocabType,words[ix])) // controlledVocabulary setting matches tag so all labels are linked
         {
         int sz=strlen(VOCAB_LINK)+strlen(words[0])+strlen(words[ix])+2*strlen(label) + 2;
         char *link=needMem(sz);
         safef(link,sz,VOCAB_LINK,words[0],words[ix],label,label);
         freeMem(words[0]);
         return link;
         }
     else if(countChars(words[ix],'=') == 1 && childTdb != NULL) // The name of a trackDb setting follows and will be the controlled vocab term
         {
         strSwapChar(words[ix],'=',0);
         if(sameString(vocabType,words[ix]))  // tags match, but search for term
             {
             char * cvSetting = words[ix] + strlen(words[ix]) + 1;
             char * cvTerm = metadataSettingFind(childTdb, cvSetting);
             if(cvTerm != NULL)
                 {
                 int sz=strlen(VOCAB_LINK)+strlen(words[0])+strlen(cvTerm)+2*strlen(label) + 2;
                 char *link=needMem(sz);
                 safef(link,sz,VOCAB_LINK,words[0],cvTerm,label,label);
                 freeMem(words[0]);
                 freeMem(cvTerm);
                 return link;
                 }
             }
         }
     }
 freeMem(words[0]);
 return cloneString(label);
 }
 
 #define PM_BUTTON_UC "<A NAME='%s'></A><A HREF='#%s'><IMG height=18 width=18 onclick=\"return (matSetMatrixCheckBoxes(%s%s%s%s%s%s) == false);\" id='btn_%s' src='../images/%s'></A>"
 static void buttonsForAll()
 {
 printf(PM_BUTTON_UC, "plus_all", "plus_all", "true",  "", "", "", "", "",  "plus_all",    "add_sm.gif");
 printf(PM_BUTTON_UC,"minus_all","minus_all","false",  "", "", "", "", "", "minus_all", "remove_sm.gif");
 }
 static void buttonsForOne(char *name,char *class)
 {
 printf(PM_BUTTON_UC, name, name,  "true", ",'", class, "'", "", "", name,    "add_sm.gif");
 printf(PM_BUTTON_UC, name, name, "false", ",'", class, "'", "", "", name, "remove_sm.gif");
 }
 
 #define MATRIX_RIGHT_BUTTONS_AFTER 8
 #define MATRIX_BOTTOM_BUTTONS_AFTER 20
 static void matrixXheadingsRow1(struct trackDb *parentTdb, members_t *dimensionX,members_t *dimensionY,struct trackDb **tdbsX,boolean top)
 /* prints the top row of a matrix: 'All' buttons; X titles; buttons 'All' */
 {
 printf("<TR ALIGN=CENTER BGCOLOR='%s' valign=%s>\n",COLOR_BG_ALTDEFAULT,top?"BOTTOM":"TOP");
 if(dimensionX && dimensionY)
     {
     printf("<TH ALIGN=LEFT valign=%s>",top?"TOP":"BOTTOM");
     buttonsForAll();
     puts("&nbsp;All</TH>");
     }
 
 // If there is an X dimension, then titles go across the top
 if(dimensionX)
     {
     int ixX,cntX=0;
     if(dimensionY)
         printf("<TH align=RIGHT><EM><B>%s</EM></B></TH>", dimensionX->title);
     else
         printf("<TH ALIGN=RIGHT valign=%s>&nbsp;&nbsp;<EM><B>%s</EM></B></TH>",top?"TOP":"BOTTOM", dimensionX->title);
     for (ixX = 0; ixX < dimensionX->count; ixX++)
         {
         if(tdbsX[ixX] != NULL)
             {
             char *label = replaceChars(dimensionX->values[ixX]," (","<BR>(");//
             printf("<TH WIDTH='60'>&nbsp;%s&nbsp;</TH>",labelWithVocabLink(parentTdb,tdbsX[ixX],dimensionX->tag,label));
             freeMem(label);
             cntX++;
             }
         }
     // If dimension is big enough, then add Y buttons to righ as well
     if(cntX>MATRIX_RIGHT_BUTTONS_AFTER)
         {
         if(dimensionY)
             {
             printf("<TH align=LEFT><EM><B>%s</EM></B></TH>", dimensionX->title);
             printf("<TH ALIGN=RIGHT valign=%s>All&nbsp;",top?"TOP":"BOTTOM");
             buttonsForAll();
             puts("</TH>");
             }
         else
             printf("<TH ALIGN=LEFT valign=%s><EM><B>%s</EM></B>&nbsp;&nbsp;</TH>",top?"TOP":"BOTTOM", dimensionX->title);
         }
     }
 else if(dimensionY)
     {
     printf("<TH ALIGN=RIGHT WIDTH=100 nowrap>");
     printf("<EM><B>%s</EM></B>", dimensionY->title);
     printf("</TH><TH ALIGN=CENTER WIDTH=60>");
     buttonsForAll();
     puts("</TH>");
     }
 puts("</TR>\n");
 }
 
 static void matrixXheadingsRow2(struct trackDb *parentTdb, members_t *dimensionX,members_t *dimensionY,struct trackDb **tdbsX)
 /* prints the 2nd row of a matrix: Y title; X buttons; title Y */
 {
 // If there are both X and Y dimensions, then there is a row of buttons in X
 if(dimensionX && dimensionY)
     {
     int ixX,cntX=0;
     printf("<TR ALIGN=CENTER BGCOLOR=\"%s\"><TH ALIGN=CENTER colspan=2><EM><B>%s</EM></B></TH>",COLOR_BG_ALTDEFAULT, dimensionY->title);
     for (ixX = 0; ixX < dimensionX->count; ixX++)    // Special row of +- +- +-
         {
         if(tdbsX[ixX] != NULL)
             {
             char objName[SMALLBUF];
             puts("<TD>");
             safef(objName, sizeof(objName), "plus_%s_all", dimensionX->names[ixX]);
             buttonsForOne( objName, dimensionX->names[ixX] );
             puts("</TD>");
             cntX++;
             }
         }
     // If dimension is big enough, then add Y buttons to righ as well
     if(cntX>MATRIX_RIGHT_BUTTONS_AFTER)
         printf("<TH ALIGN=CENTER colspan=2><EM><B>%s</EM></B></TH>", dimensionY->title);
     puts("</TR>\n");
     }
 }
 
 static void matrixXheadings(struct trackDb *parentTdb, members_t *dimensionX,members_t *dimensionY,struct trackDb **tdbsX,boolean top)
 /* UI for X headings in matrix */
 {
 if(top)
     matrixXheadingsRow1(parentTdb,dimensionX,dimensionY,tdbsX,top);
 
     matrixXheadingsRow2(parentTdb,dimensionX,dimensionY,tdbsX);
 
 if(!top)
     matrixXheadingsRow1(parentTdb,dimensionX,dimensionY,tdbsX,top);
 }
 
 static void matrixYheadings(struct trackDb *parentTdb, members_t *dimensionX,members_t *dimensionY,int ixY,struct trackDb *childTdb,boolean left)
 /* prints the top row of a matrix: 'All' buttons; X titles; buttons 'All' */
 {
 if(dimensionX && dimensionY && childTdb != NULL) // Both X and Y, then column of buttons
     {
     char objName[SMALLBUF];
     printf("<TH ALIGN=%s nowrap colspan=2>",left?"RIGHT":"LEFT");
     if(left)
         printf("%s&nbsp;",labelWithVocabLink(parentTdb,childTdb,dimensionY->tag,dimensionY->values[ixY]));
     safef(objName, sizeof(objName), "plus_all_%s", dimensionY->names[ixY]);
     buttonsForOne( objName, dimensionY->names[ixY] );
     if(!left)
         printf("&nbsp;%s",labelWithVocabLink(parentTdb,childTdb,dimensionY->tag,dimensionY->values[ixY]));
     puts("</TH>");
     }
 else if (dimensionX)
     {
     printf("<TH ALIGN=%s>",left?"RIGHT":"LEFT");
     buttonsForAll();
     puts("</TH>");
     }
 else if (left && dimensionY && childTdb != NULL)
     printf("<TH ALIGN=RIGHT nowrap>%s</TH>\n",labelWithVocabLink(parentTdb,childTdb,dimensionY->tag,dimensionY->values[ixY]));
 }
 
 
 static boolean hCompositeUiByMatrix(char *db, struct cart *cart, struct trackDb *parentTdb, char *formName)
 /* UI for composite tracks: matrix of checkboxes. */
 {
 //int ix;
 char objName[SMALLBUF];
 char javascript[JBUFSIZE];
 boolean alreadySet = TRUE;
 struct trackDb *subtrack;
 
 if(!dimensionsExist(parentTdb))
     return FALSE;
 
 int ixX,ixY,ixZ;
 members_t *dimensionX = subgroupMembersGetByDimension(parentTdb,'X');
 members_t *dimensionY = subgroupMembersGetByDimension(parentTdb,'Y');
 members_t *dimensionZ = subgroupMembersGetByDimension(parentTdb,'Z');
 if(dimensionX == NULL && dimensionY == NULL && dimensionZ == NULL) // Must be an X, Y or Z dimension
     return FALSE;
 
 // use array of char determine all the cells (in X,Y,Z dimensions) that are actually populated
 char *value;
 int sizeOfX = dimensionX?dimensionX->count:1;
 int sizeOfY = dimensionY?dimensionY->count:1;
 int sizeOfZ = dimensionZ?dimensionZ->count:1;
 int cells[sizeOfX][sizeOfY]; // There needs to be atleast one element in dimension
 int cellsZ[sizeOfX];         // The Z dimension is a separate 1D matrix
 struct trackDb *tdbsX[sizeOfX]; // Representative subtracks
 struct trackDb *tdbsY[sizeOfY];
 struct trackDb *tdbsZ[sizeOfZ];
 memset(cells, 0, sizeof(cells));
 memset(cellsZ, 0, sizeof(cellsZ));
 memset(tdbsX, 0, sizeof(tdbsX));
 memset(tdbsY, 0, sizeof(tdbsY));
 memset(tdbsZ, 0, sizeof(tdbsZ));
 for (subtrack = parentTdb->subtracks; subtrack != NULL; subtrack = subtrack->next)
     {
     ixX = (dimensionX ? -1 : 0 );
     ixY = (dimensionY ? -1 : 0 );
     ixZ = (dimensionZ ? -1 : 0 );
     if(dimensionX && subgroupFind(subtrack,dimensionX->tag,&value))
         {
         ixX = stringArrayIx(value,dimensionX->names,dimensionX->count);
         tdbsX[ixX] = subtrack;
         subgroupFree(&value);
         }
     if(dimensionY && subgroupFind(subtrack,dimensionY->tag,&value))
         {
         ixY = stringArrayIx(value,dimensionY->names,dimensionY->count);
         tdbsY[ixY] = subtrack;
         subgroupFree(&value);
         }
     if(dimensionZ && subgroupFind(subtrack,dimensionZ->tag,&value))
         {
         ixZ = stringArrayIx(value,dimensionZ->names,dimensionZ->count);
         tdbsZ[ixZ] = subtrack;
         subgroupFree(&value);
         }
     if(ixX > -1 && ixY > -1)
         cells[ixX][ixY]++;
     if(ixZ > -1)
         cellsZ[ixZ]++;
     }
 
 //puts("<B>Select subtracks by characterization:</B><BR>");
 printf("<B>Select subtracks by ");
 if(dimensionX && !dimensionY)
     safef(javascript, sizeof(javascript), "%s:</B><BR>",dimensionX->title);
 else if(!dimensionX && dimensionY)
     safef(javascript, sizeof(javascript), "%s:</B><BR>",dimensionY->title);
 else if(dimensionX && dimensionY && !dimensionZ)
     safef(javascript, sizeof(javascript), "%s and %s:</B><BR>",dimensionX->title,dimensionY->title);
 else //if(dimensionX && dimensionY && dimensionZ)
     safef(javascript, sizeof(javascript), "%s, %s and %s:</B><BR>",dimensionX->title,dimensionY->title,dimensionZ->title);
 puts(strLower(javascript));
 
 printf("<TABLE class='greenBox' bgcolor='%s' borderColor='%s'}>\n",COLOR_BG_DEFAULT,COLOR_BG_DEFAULT);
 
 matrixXheadings(parentTdb,dimensionX,dimensionY,tdbsX,TRUE);
 
 // Now the Y by X matrix
 int cntX=0,cntY=0;
 for (ixY = 0; ixY < sizeOfY; ixY++)
     {
     if(tdbsY[ixY] != NULL || dimensionY == NULL)
         {
         cntY++;
         assert(!dimensionY || ixY < dimensionY->count);
         printf("<TR ALIGN=CENTER BGCOLOR=\"#FFF9D2\">");
 
         matrixYheadings(parentTdb, dimensionX,dimensionY,ixY,tdbsY[ixY],TRUE);
 
         for (ixX = 0; ixX < sizeOfX; ixX++)
             {
             if(tdbsX[ixX] != NULL || dimensionX == NULL)
                 {
                 assert(!dimensionX || ixX < dimensionX->count);
                 if(cntY==1) // Only do this on the first good Y
                     cntX++;
 
                 if(dimensionX && ixX == dimensionX->count)
                     break;
                 if(cells[ixX][ixY] > 0)
                     {
                     if(dimensionX && dimensionY)
                         {
                         safef(objName, sizeof(objName), "mat_%s_%s_cb", dimensionX->names[ixX],dimensionY->names[ixY]);
                         safef(javascript, sizeof(javascript), "onclick='matSetSubtrackCheckBoxes(this.checked,\"%s\",\"%s\");'",
                             dimensionX->names[ixX],dimensionY->names[ixY]);
                         }
                     else
                         {
                         safef(objName, sizeof(objName), "mat_%s_cb", (dimensionX ? dimensionX->names[ixX] : dimensionY->names[ixY]));
                         safef(javascript, sizeof(javascript), "onclick='matSetSubtrackCheckBoxes(this.checked,\"%s\");'",
                             (dimensionX ? dimensionX->names[ixX] : dimensionY->names[ixY]));
                         }
                     alreadySet = cartUsualBoolean(cart, objName, FALSE);
                     puts("<TD>");
                     struct dyString *dyJS = newDyString(100);
                     dyStringPrintf(dyJS, javascript);
                     dyStringPrintf(dyJS, " class=\"matrixCB");
                     if(dimensionX)
                         dyStringPrintf(dyJS, " %s",dimensionX->names[ixX]);
                     if(dimensionY)
                         dyStringPrintf(dyJS, " %s",dimensionY->names[ixY]);
                     dyStringAppendC(dyJS,'"');
                     cgiMakeCheckBoxJS(objName,alreadySet,dyStringCannibalize(&dyJS));
                     puts("</TD>");
                     }
                 else
                     puts("<TD>&nbsp;</TD>");
                 }
             }
         if(dimensionX && cntX>MATRIX_RIGHT_BUTTONS_AFTER)
             matrixYheadings(parentTdb, dimensionX,dimensionY,ixY,tdbsY[ixY],FALSE);
         puts("</TR>\n");
         }
     }
 if(dimensionY && cntY>MATRIX_BOTTOM_BUTTONS_AFTER)
     matrixXheadings(parentTdb,dimensionX,dimensionY,tdbsX,FALSE);
 
 if(dimensionZ)
     {
     printf("<TR align='center' valign='bottom' BGCOLOR='%s''>",COLOR_BG_ALTDEFAULT);
     printf("<TH class='greenRoof' STYLE='font-size: 2' colspan=50>&nbsp;</TH>");
     printf("<TR BGCOLOR='%s'><TH valign=top align=left colspan=2 rowspan=20><B><EM>%s</EM></B>:",
             COLOR_BG_ALTDEFAULT,dimensionZ->title);
     int cntZ=0;
     for(ixZ=0;ixZ<sizeOfZ;ixZ++)
         {
         if(tdbsZ[ixZ] != NULL && cellsZ[ixZ]>0)
             {
             if(cntZ > 0 && (cntZ % sizeOfX) == 0)
                 printf("</TR><TR BGCOLOR='%s'>",COLOR_BG_ALTDEFAULT);
             printf("<TH align=left nowrap>");
             safef(objName, sizeof(objName), "mat_%s_dimZ_cb",dimensionZ->names[ixZ]);
             safef(javascript, sizeof(javascript), "onclick='matSetSubtrackCheckBoxes(this.checked,\"%s\");'",dimensionZ->names[ixZ]);
             alreadySet = cartUsualBoolean(cart, objName, FALSE);
             struct dyString *dyJS = newDyString(100);
             dyStringPrintf(dyJS, javascript);
             dyStringPrintf(dyJS, " class=\"matrixCB dimZ %s\"",dimensionZ->names[ixZ]);
             cgiMakeCheckBoxJS(objName,alreadySet,dyStringCannibalize(&dyJS));
             printf("%s",labelWithVocabLink(parentTdb,tdbsZ[ixZ],dimensionZ->tag,dimensionZ->values[ixZ]));
             puts("</TH>");
             cntZ++;
             }
         }
     if((cntZ % sizeOfX) > 0)
         printf("<TH colspan=%d>&nbsp;</TH>",sizeOfX);
     }
 puts("</TD></TR></TABLE>");
 
 subgroupMembersFree(&dimensionX);
 subgroupMembersFree(&dimensionY);
 subgroupMembersFree(&dimensionZ);
 puts("<BR>\n");
 
 return TRUE;
 }
 
 static boolean hCompositeUiAllButtons(char *db, struct cart *cart, struct trackDb *parentTdb, char *formName)
 /* UI for composite tracks: all/none buttons only (as opposed to matrix or lots of buttons */
 {
 if(slCount(parentTdb->subtracks) <= 1)
     return FALSE;
 
 if(dimensionsExist(parentTdb))
     return FALSE;
 
 #define PM_BUTTON_GLOBAL "<A NAME='%s'></A><A HREF='#%s'><IMG height=18 width=18 onclick=\"return (subtrackCBsSetAll(%s) == false);\" id='btn_%s' src='../images/%s'></A>"
 #define    BUTTON_PLUS_ALL_GLOBAL()  printf(PM_BUTTON_GLOBAL, "plus_all", "plus_all", "true", "plus_all",   "add_sm.gif")
 #define    BUTTON_MINUS_ALL_GLOBAL() printf(PM_BUTTON_GLOBAL,"minus_all","minus_all","false","minus_all","remove_sm.gif")
 printf("<P><B>Select subtracks:</B><P>All:&nbsp;");
 BUTTON_PLUS_ALL_GLOBAL();
 BUTTON_MINUS_ALL_GLOBAL();
 puts("</P>");
 return TRUE;
 }
 
 static boolean hCompositeUiNoMatrix(char *db, struct cart *cart, struct trackDb *parentTdb,
           char *primarySubtrack, char *formName)
 /* UI for composite tracks: subtrack selection.  This is the default UI
 without matrix controls. */
 {
 int i, j, k;
 char *words[SMALLBUF];
 char option[SMALLBUF];
 int wordCnt;
 char javascript[JBUFSIZE];
 char *primaryType = getPrimaryType(primarySubtrack, parentTdb);
 char *name, *value;
 char buttonVar[32];
 char setting[] = "subGroupN";
 char *button;
 struct trackDb *subtrack;
 bool hasSubgroups = (trackDbSetting(parentTdb, "subGroup1") != NULL);
 
 if(dimensionsExist(parentTdb))
     return FALSE;
 
 puts ("<TABLE>");
 if (hasSubgroups)
     {
     puts("<TR><B>Select subtracks:</B></TR>");
     puts("<TR><TD><EM><B>&nbsp; &nbsp; All</B></EM>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </TD><TD>");
     }
 else
     {
     puts("<TR><TD><B>All subtracks:</B></TD><TD>");
     }
 safef(buttonVar, sizeof buttonVar, "%s", "button_all");
 if (formName)
     {
     cgiMakeHiddenVar(buttonVar, "");
     makeAddClearSubmitTweak(javascript, formName, buttonVar,
                 ADD_BUTTON_LABEL);
     cgiMakeOnClickButton(javascript, ADD_BUTTON_LABEL);
     puts("</TD><TD>");
     makeAddClearSubmitTweak(javascript, formName, buttonVar,
                 CLEAR_BUTTON_LABEL);
     cgiMakeOnClickButton(javascript, CLEAR_BUTTON_LABEL);
     }
 else
     {
     cgiMakeButton(buttonVar, ADD_BUTTON_LABEL);
     puts("</TD><TD>");
     cgiMakeButton(buttonVar, CLEAR_BUTTON_LABEL);
     }
 button = cgiOptionalString(buttonVar);
 if (isNotEmpty(button))
     {
     for (subtrack = parentTdb->subtracks; subtrack != NULL; subtrack = subtrack->next)
         {
         boolean newVal = FALSE;
         safef(option, sizeof(option), "%s_sel", subtrack->tableName);
         newVal = sameString(button, ADD_BUTTON_LABEL);
         if (primarySubtrack)
             {
             if (sameString(subtrack->tableName, primarySubtrack))
                 newVal = TRUE;
             if (hSameTrackDbType(primaryType, subtrack->type))
                 cartSetBoolean(cart, option, newVal);
             }
         else
             cartSetBoolean(cart, option, newVal);
         }
     }
 puts("</TD></TR>");
 puts ("</TABLE>");
 /* generate set & clear buttons for subgroups */
 for (i = 0; i < MAX_SUBGROUP; i++)
     {
     char *subGroup;
     safef(setting, sizeof setting, "subGroup%d", i+1);
     if (trackDbSetting(parentTdb, setting) == NULL)
         break;
     wordCnt = chopLine(cloneString(trackDbSetting(parentTdb, setting)), words);
     if (wordCnt < 2)
         continue;
     subGroup = cloneString(words[0]);
     if(sameWord(subGroup,"view"))
         continue;  // Multi-view should have taken care of "view" subgroup already
     puts ("<TABLE>");
     printf("<TR><TD><EM><B>&nbsp; &nbsp; %s</EM></B></TD></TR>", words[1]);
     for (j = 2; j < wordCnt; j++)
         {
         if (!parseAssignment(words[j], &name, &value))
             continue;
         printf("<TR><TD>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; %s</TD><TD>",
            value);
         safef(buttonVar, sizeof buttonVar, "%s_%s", subGroup, name);
         if (formName)
             {
             cgiMakeHiddenVar(buttonVar, "");
             makeAddClearSubmitTweak(javascript, formName, buttonVar,
                         ADD_BUTTON_LABEL);
             cgiMakeOnClickButton(javascript, ADD_BUTTON_LABEL);
             puts("</TD><TD>");
             makeAddClearSubmitTweak(javascript, formName, buttonVar,
                         CLEAR_BUTTON_LABEL);
             cgiMakeOnClickButton(javascript, CLEAR_BUTTON_LABEL);
             }
         else
             {
             cgiMakeButton(buttonVar, ADD_BUTTON_LABEL);
             puts("</TD><TD>");
             cgiMakeButton(buttonVar, CLEAR_BUTTON_LABEL);
             }
         puts("</TD></TR>");
         button = cgiOptionalString(buttonVar);
         if (isEmpty(button))
             continue;
         for (subtrack = parentTdb->subtracks; subtrack != NULL;
                 subtrack = subtrack->next)
             {
             char *p;
             int n;
             if ((p = trackDbSetting(subtrack, "subGroups")) == NULL)
                 continue;
             n = chopLine(cloneString(p), words);
             for (k = 0; k < n; k++)
                 {
                 char *subName, *subValue;
                 if (!parseAssignment(words[k], &subName, &subValue))
                     continue;
                 if (sameString(subName, subGroup) && sameString(subValue, name))
                     {
                     boolean newVal = FALSE;
                     safef(option, sizeof(option),"%s_sel", subtrack->tableName);
                     newVal = sameString(button, ADD_BUTTON_LABEL);
                     if (primarySubtrack)
                         {
                         if (sameString(subtrack->tableName, primarySubtrack))
                             newVal = TRUE;
                         if (hSameTrackDbType(primaryType, subtrack->type))
                             cartSetBoolean(cart, option, newVal);
                         }
                     else
                         cartSetBoolean(cart, option, newVal);
                     }
                 }
             }
         }
     puts ("</TABLE>");
     }
     return TRUE;
 }
 
 void hCompositeUi(char *db, struct cart *cart, struct trackDb *tdb,
 		  char *primarySubtrack, char *fakeSubmit, char *formName)
 /* UI for composite tracks: subtrack selection.  If primarySubtrack is
  * non-NULL, don't allow it to be cleared and only offer subtracks
  * that have the same type.  If fakeSubmit is non-NULL, add a hidden
  * var with that name so it looks like it was pressed. */
 {
 bool hasSubgroups = (trackDbSetting(tdb, "subGroup1") != NULL);
 boolean displayAll =
     sameString(cartUsualString(cart, "displaySubtracks", "all"), "all");
 boolean isMatrix = dimensionsExist(tdb);
 boolean viewsOnly = FALSE;
 
 if(trackDbSetting(tdb, "dragAndDrop") != NULL)
     jsIncludeFile("jquery.tablednd.js", NULL);
 jsIncludeFile("hui.js",NULL);
 
 puts("<P>");
 if (slCount(tdb->subtracks) < MANY_SUBTRACKS && !hasSubgroups)
     {
     compositeUiAllSubtracks(db, cart, tdb, primarySubtrack);
     return;
     }
 if (fakeSubmit)
     cgiMakeHiddenVar(fakeSubmit, "submit");
 
 if(subgroupingExists(tdb,"view"))
     {
     hCompositeDisplayViewDropDowns(db, cart,tdb);
     if(subgroupCount(tdb) <= 1)
         viewsOnly = TRUE;
     }
 if(!viewsOnly)
     {
     if(trackDbSettingOn(tdb, "allButtonPair"))
         hCompositeUiAllButtons(db, cart, tdb, formName);
     else if (!hasSubgroups || !isMatrix || primarySubtrack)
         hCompositeUiNoMatrix(db, cart,tdb,primarySubtrack,formName);
     else
         hCompositeUiByMatrix(db, cart, tdb, formName);
     }
 cartSaveSession(cart);
 cgiContinueHiddenVar("g");
 if (displayAll)
     compositeUiAllSubtracks(db, cart, tdb, primarySubtrack);
 else
     compositeUiSelectedSubtracks(db, cart, tdb, primarySubtrack);
 
 cfgLinkToDependentCfgs(tdb,tdb->tableName);
 
 if (primarySubtrack == NULL)  // This is set for tableBrowser but not hgTrackUi
     {
         if (slCount(tdb->subtracks) > 5)
         {
         cgiMakeButton("Submit", "Submit");
         puts("<P>");
         }
     }
 }
 
 boolean superTrackDropDown(struct cart *cart, struct trackDb *tdb,
                                 int visibleChild)
 /* Displays hide/show dropdown for supertrack.
  * Set visibleChild to indicate whether 'show' should be grayed
  * out to indicate that no supertrack members are visible:
  *    0 to gray out (no visible children)
  *    1 don't gray out (there are visible children)
  *   -1 don't know (this function should determine)
  * If -1,i the subtracks field must be populated with the child trackDbs.
  * Returns false if not a supertrack */
 {
 if (!tdbIsSuperTrack(tdb))
     return FALSE;
 
 /* determine if supertrack is show/hide */
 boolean show = FALSE;
 char *setting =
         cartUsualString(cart, tdb->tableName, tdb->isShow ? "show" : "hide");
 if (sameString("show", setting))
     show = TRUE;
 
 /* Determine if any tracks in supertrack are visible; if not,
  * the 'show' is grayed out */
 if (show && (visibleChild == -1))
     {
     visibleChild = 0;
     struct trackDb *cTdb;
     for (cTdb = tdb->subtracks; cTdb != NULL; cTdb = tdb->next)
         {
         cTdb->visibility =
                 hTvFromString(cartUsualString(cart, cTdb->tableName,
                                       hStringFromTv(cTdb->visibility)));
         if (cTdb->visibility != tvHide)
             visibleChild = 1;
         }
     }
 hideShowDropDown(tdb->tableName, show, (show && visibleChild) ?
                             "normalText" : "hiddenText");
 return TRUE;
 }
 
 int tvConvertToNumericOrder(enum trackVisibility v)
 {
 return ((v) == tvFull   ? 4 : \
         (v) == tvPack   ? 3 : \
         (v) == tvSquish ? 2 : \
         (v) == tvDense  ? 1 : 0);
 }
 int tvCompare(enum trackVisibility a, enum trackVisibility b)
 /* enum trackVis isn't in numeric order by visibility, so compare
  * symbolically: */
 {
 return (tvConvertToNumericOrder(b) - tvConvertToNumericOrder(a));
 }
 
 enum trackVisibility tvMin(enum trackVisibility a, enum trackVisibility b)
 /* Return the less visible of a and b. */
 {
 if (tvCompare(a, b) >= 0)
     return a;
 else
     return b;
 }
 
 char *compositeViewControlNameFromTdb(struct trackDb *tdb)
 /* Returns a string with the composite view control name if one exists */
 {
 char *stView   = NULL;
 char *name     = NULL;
 char *rootName = NULL;
 // This routine should give these results: compositeName.viewName or else subtrackName.viewName or else compositeName or else subtrackName
 if(tdbIsCompositeChild(tdb) == TRUE && trackDbSetting(tdb, "subTrack") != NULL)
     {
     if(trackDbSettingClosestToHomeOn(tdb, "configurable"))
         rootName = tdb->tableName;  // subtrackName
     else
         rootName = firstWordInLine(cloneString(trackDbSetting(tdb, "subTrack"))); // compositeName
     }
 if(rootName != NULL)
     {
     if(subgroupFind(tdb,"view",&stView))
         {
         int len = strlen(rootName) + strlen(stView) + 3;
         name = needMem(len);
         safef(name,len,"%s.%s",rootName,stView);
         subgroupFree(&stView);
         }
     else
         name = cloneString(rootName);
     }
 else
     name = cloneString(tdb->tableName);
 return name;
 }
 void compositeViewControlNameFree(char **name)
 /* frees a string allocated by compositeViewControlNameFromTdb */
 {
 if(name && *name)
     freez(name);
 }
 
 boolean isNameAtCompositeLevel(struct trackDb *tdb,char *name)
 /* cfgUi controls are passed a prefix name that may be at the composite or at the subtrack level
    returns TRUE for composite level name */
 {
 if(tdbIsCompositeChild(tdb)
 && startsWith(tdb->parent->tableName,name)
 && name[strlen(tdb->parent->tableName)] == '.')  // Cfg name at composite view level
     return TRUE;
 return FALSE;
 }
 

