d3752edc12da1bf08427946150f564dbdd5d2254
angie
  Thu Oct 24 13:55:51 2019 -0700
bigDbSnp track handler code - initial commit.  refs #23283
* dnautil: Added trimRefAltLeft to get left-justified trimming (a la VCF not HGVS).
* bigBedClick: do hReplaceGbdb up front in parseDetailsTablUrls instead of waiting until endpoint.
* trackDbCustom.c: consolidating type-handling for wig/bigWig vs. bigBed-based big*.

diff --git src/hg/lib/hui.c src/hg/lib/hui.c
index 19b1548..2798893 100644
--- src/hg/lib/hui.c
+++ src/hg/lib/hui.c
@@ -38,30 +38,31 @@
 #include "errCatch.h"
 #include "samAlignment.h"
 #include "makeItemsItem.h"
 #include "bedDetail.h"
 #include "pgSnp.h"
 #include "memgfx.h"
 #include "trackHub.h"
 #include "gtexUi.h"
 #include "genbank.h"
 #include "htmlPage.h"
 #include "longRange.h"
 #include "barChartUi.h"
 #include "interactUi.h"
 #include "interact.h"
 #include "hicUi.h"
+#include "bigDbSnp.h"
 #include "customComposite.h"
 #include "trackVersion.h"
 #include "hubConnect.h"
 #include "bigBedFilter.h"
 
 #define SMALLBUF 256
 #define MAX_SUBGROUP 9
 #define ADD_BUTTON_LABEL        "add"
 #define CLEAR_BUTTON_LABEL      "clear"
 #define JBUFSIZE 2048
 
 
 #define DEF_BUTTON "<IMG id=\"btn_%s\" src=\"../images/%s\" alt=\"%s\">\n"
 #define DEF_BUTTON_JS "setCheckBoxesThatContain('%s',true,false,'%s','','%s');" \
 	       "setCheckBoxesThatContain('%s',false,false,'%s','_defOff','%s');" 
@@ -3938,106 +3939,123 @@
     printf(" value='%s'",name);
 if (filterBy->styleFollows)
     {
     char *styler = label + strlen(label)+1;
     if (*styler != '\0')
 	{
 	if (*styler == '#') // Legacy: just the color that follows
 	    printf(" style='color: %s;'",styler);
 	else
 	    printf(" style='%s'",styler);
 	}
     }
 printf(">%s</OPTION>\n",label);
 }
 
+static boolean filterByColumnIsMultiple(struct cart *cart, struct trackDb *tdb, char *column)
+{
+char settingString[4096];
+safef(settingString, sizeof settingString, "%s%s", column, FILTER_TYPE_NAME);
+char *setting = cartOrTdbString(cart, tdb, settingString, NULL);
+if (setting == NULL)
+    {
+    safef(settingString, sizeof settingString, "%s.%s", column, FILTER_TYPE_NAME);
+    setting = cartOrTdbString(cart, tdb, settingString, FILTERBY_MULTIPLE_LIST_AND);
+    }
+return (sameString(setting, FILTERBY_MULTIPLE) ||
+        sameString(setting, FILTERBY_MULTIPLE_LIST_OR) ||
+        sameString(setting, FILTERBY_MULTIPLE_LIST_AND));
+}
+
 void filterBySetCfgUiGuts(struct cart *cart, struct trackDb *tdb,
 		      filterBy_t *filterBySet, boolean onOneLine,
 		      char *filterTypeTitle, char *selectIdPrefix, char *allLabel, char *prefix)
 // Does the UI for a list of filterBy structure for either filterBy or highlightBy controls
 {
 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("<TABLE cellpadding=3><TR valign='top'>");
 else
     printf("<B>%s items by:</B> (select multiple categories and items - %s)"
-	   "<TABLE cellpadding=3><TR valign='top'>\n",filterTypeTitle,FILTERBY_HELP_LINK);
+	   "<TABLE cellpadding=3><TR valign='bottom'>\n",filterTypeTitle,FILTERBY_HELP_LINK);
 
 if (tdbIsBigBed(tdb))
     {
     char varName[1024];
     safef(varName, sizeof(varName), "%s.doAdvanced", tdb->track);
     puts("&nbsp;&nbsp;&nbsp;");
     printf("<a id='%s' style='text-decoration: underline; color: #121E9A' title='Show advanced options..'>%s<img src='../images/downBlue.png'/></a>" ,varName,"Advanced ");
     printf("<BR>");
     jsInlineF("$(function () { advancedSearchOnChange('%s'); });\n", varName);
     }
 
 
 filterBy_t *filterBy = NULL;
 if (cartOptionalString(cart, "ajax") == NULL)
     {
     webIncludeResourceFile("ui.dropdownchecklist.css");
     jsIncludeFile("ui.dropdownchecklist.js",NULL);
     jsIncludeFile("ddcl.js",NULL);
     }
 
-int ix=0;
-for(filterBy = filterBySet;filterBy != NULL; filterBy = filterBy->next, ix++)
-    {
-    char settingString[4096];
-    safef(settingString, sizeof settingString, "%s%s", filterBy->column, FILTER_TYPE_NAME);
-    char *setting = cartOrTdbString(cart, tdb, settingString, NULL);
-    if (setting == NULL)
-        {
-        safef(settingString, sizeof settingString, "%s.%s", filterBy->column, FILTER_TYPE_NAME);
-        setting = cartOrTdbString(cart, tdb, settingString, FILTERBY_MULTIPLE_LIST_AND);
-        }
-
-    boolean isMultiple = sameString(setting, FILTERBY_MULTIPLE) ||sameString(setting, FILTERBY_MULTIPLE_LIST_OR) ||sameString(setting, FILTERBY_MULTIPLE_LIST_AND);
+// TODO: columnCount (Number of filterBoxes per row) should be configurable through tdb setting
 
+for (filterBy = filterBySet;  filterBy != NULL;  filterBy = filterBy->next)
+    {
     puts("<TD>");
     char selectStatement[4096];
-    if (isMultiple)
+    if (filterByColumnIsMultiple(cart, tdb, filterBy->column))
         safef(selectStatement, sizeof selectStatement, " (select multiple items - %s)", FILTERBY_HELP_LINK);
     else
         selectStatement[0] = 0;
     if(count == 1)
 	printf("<B>%s by %s</B>%s",filterTypeTitle,filterBy->title,selectStatement);
     else
 	printf("<B>%s</B>",filterBy->title);
-    printf("<BR>\n");
-
-    if (isMultiple && tdbIsBigBed(tdb))
+    puts("</TD>");
+    }
+puts("</tr><tr>");
+for (filterBy = filterBySet;  filterBy != NULL;  filterBy = filterBy->next)
     {
+    puts("<td>");
+    if (filterByColumnIsMultiple(cart, tdb, filterBy->column) && tdbIsBigBed(tdb))
+        {
+        char settingString[4096];
+        safef(settingString, sizeof settingString, "%s%s", filterBy->column, FILTER_TYPE_NAME);
+        char *setting = cartOrTdbString(cart, tdb, settingString, FILTERBY_MULTIPLE_LIST_AND);
         char cartSettingString[4096];
         safef(cartSettingString, sizeof cartSettingString, "%s.%s", prefix, settingString);
         printf("<div class='advanced' style='display:none'><b>Match if  ");
         cgiMakeRadioButton(cartSettingString, FILTERBY_MULTIPLE_LIST_AND, sameString(setting, FILTERBY_MULTIPLE_LIST_AND));
         printf(" all ");
         cgiMakeRadioButton(cartSettingString, FILTERBY_MULTIPLE_LIST_OR, sameString(setting, FILTERBY_MULTIPLE_LIST_OR));
         printf(" one or more match</b></div> ");
         }
-    // TODO: columnCount (Number of filterBoxes per row) should be configurable through tdb setting
-
+    puts("</td>");
+    }
+puts("</tr><tr>");
+int ix=0;
+for (filterBy = filterBySet;  filterBy != NULL;  filterBy = filterBy->next, ix++)
+    {
+    puts("<td>");
     // value is always "All", even if label is different, to simplify javascript code
     int valIx = 0;
-    if (isMultiple)
+    if (filterByColumnIsMultiple(cart, tdb, filterBy->column))
         {
         printf( "<SELECT id='%s%d' name='%s' multiple style='display: none; font-size:.9em;' class='filterBy'><BR>\n", selectIdPrefix,ix,filterBy->htmlName);
         printf("<OPTION%s value=\"All\">%s</OPTION>\n", (filterByAllChosen(filterBy)?" SELECTED":""), allLabel);
         valIx = 1;
         }
     else
         {
         printf( "<SELECT id='%s%d' name='%s' style='font-size:.9em;'<BR>\n", selectIdPrefix,ix,filterBy->htmlName);
         valIx = 0;
         }
     struct slName *slValue;
 
     for (slValue=filterBy->slValues;slValue!=NULL;slValue=slValue->next,valIx++)
 	{
 	char varName[32];
@@ -4061,32 +4079,33 @@
 	if (filterBy->useIndex || filterBy->valueAndLabel)
 	    printf(" value='%s'",name);
 	if (filterBy->styleFollows)
 	    {
 	    char *styler = label + strlen(label)+1;
 	    if (*styler != '\0')
 		{
 		if (*styler == '#') // Legacy: just the color that follows
 		    printf(" style='color: %s;'",styler);
 		else
 		    printf(" style='%s'",styler);
 		}
 	    }
 	printf(">%s</OPTION>\n",label);
 	}
-    }
     printf("</SELECT>\n");
+    puts("</td>");
+    }
 
 puts("</TR></TABLE>");
 }
 
 void filterBySetCfgUi(struct cart *cart, struct trackDb *tdb,
 		  filterBy_t *filterBySet, boolean onOneLine, char *prefix)
 /* Does the filter UI for a list of filterBy structure */
 {
 filterBySetCfgUiGuts(cart, tdb, filterBySet, onOneLine, "Filter", "fbc", "All", prefix);
 }
 
 void highlightBySetCfgUi(struct cart *cart, struct trackDb *tdb,
 		     filterBy_t *filterBySet, boolean onOneLine, char *prefix)
 /* Does the highlight UI for a list of filterBy structure */
 {
@@ -4273,30 +4292,112 @@
 /*
 printf("<TR valign=center><th align=right>Drawing method:</th><td align=left>");
 safef(option, sizeof(option), "%s.%s", name, POPMETHOD);
 char *popMethodVal = cartOrTdbString(cart, tdb, "popMethod", NULL);
         
 cgiMakeDropListFull(option, popMethodLabels, popMethodValues,
     ArraySize(popMethodValues), popMethodVal, NULL, NULL);
     */
 
 puts("</td></TR>");
 
 printf("</TABLE>");
 cfgEndBox(boxed);
 }
 
+void labelMakeCheckBox(struct cart *cart, struct trackDb *tdb, char *sym, char *desc,
+                       boolean defaultOn)
+/* add a checkbox for the user to select a component of a label (e.g. ID, name, other info).
+ * NOTE: This does not have a track name argument, so the correct tdb must be passed in:
+ * if setting is at composite level, then pass in composite tdb, likewise for view. */
+{
+char suffix[512];
+safef(suffix, sizeof(suffix), "label.%s", sym);
+boolean option = cartUsualBooleanClosestToHome(cart, tdb, FALSE, suffix, defaultOn);
+char cartVar[1024];
+safef(cartVar, sizeof cartVar, "%s.%s", tdb->track, suffix);
+cgiMakeCheckBox(cartVar, option);
+printf(" %s&nbsp;&nbsp;&nbsp;", desc);
+}
+
+static void freqSourceSelect(struct cart *cart, struct trackDb *tdb, char *name)
+/* Make a select input for preferred source of allele frequencies from
+ * trackDb setting freqSourceOrder. */
+{
+char *freqSourceOrder = cloneString(trackDbSetting(tdb, "freqSourceOrder"));
+if (isEmpty(freqSourceOrder))
+    return;
+int fsCount = countSeparatedItems(freqSourceOrder, ',');
+char *menu[fsCount];
+chopCommas(freqSourceOrder, menu);
+boolean parentLevel = isNameAtParentLevel(tdb, name);
+char *freqProj = cartOptionalStringClosestToHome(cart, tdb, parentLevel, "freqProj");
+puts("<b>Frequency source/project to use for Minor Allele Frequency (MAF):</b>");
+char cartVar[1024];
+safef(cartVar, sizeof cartVar, "%s.freqProj", name);
+cgiMakeDropList(cartVar, menu, ArraySize(menu), freqProj);
+puts("<br>");
+}
+
+static struct trackDb *tdbOrAncestorByName(struct trackDb *tdb, char *name)
+/* For reasons Angie cannot fathom, if a composite or view is passed to cfgByCfgType then
+ * cfgByCfgType passes a leaf subtrack to its callees like bigDbSnpCfgUi.  That is why we
+ * see so many calls to isNameAtParentLevel, which returns true if the tdb was originally
+ * at the composite or view level, which we can only tell by comparing with the original track name.
+ * labelMakeCheckBox, called by many handlers in hgTrackUi that must be always top-level
+ * (or have a special handler that bypasses cfgByCfgType like refSeqComposite),
+ * is blissfully unaware of this.  It uses the same tdb for looking in cart ClosestToHome
+ * and for making the HTML element's cart var name, trusting that the correct tdb has been
+ * handed to it.
+ * So in order for a callee of cfgByCfgType to call labelMakeCheckBox with the correct tdb,
+ * we need to walk back up comparing name like isNameAtParentLevel does.
+ * If name doesn't match tdb or any of its ancestors then this returns NULL. */
+{
+struct trackDb *correctTdb;
+for (correctTdb = tdb;  correctTdb != NULL;  correctTdb = correctTdb->parent)
+    if (startsWithWordByDelimiter(correctTdb->track, '.', name))
+        return correctTdb;
+return NULL;
+}
+
+void bigDbSnpCfgUi(char *db, struct cart *cart, struct trackDb *leafTdb, char *name, char *title,
+                   boolean boxed)
+/* UI for bigDbSnp a.k.a. "dbSNP 2.0". */
+{
+boxed = cfgBeginBoxAndTitle(leafTdb, boxed, title);
+freqSourceSelect(cart, leafTdb, name);
+puts("<br>");
+puts("<b>Label:</b>");
+struct trackDb *correctTdb = tdbOrAncestorByName(leafTdb, name);
+labelMakeCheckBox(cart, correctTdb, "rsId", "rs# identifier", TRUE);
+labelMakeCheckBox(cart, correctTdb, "refAlt", "reference/alternate allele", TRUE);
+labelMakeCheckBox(cart, correctTdb, "majMin", "major/minor allele", FALSE);
+labelMakeCheckBox(cart, correctTdb, "maf", "MAF if available", FALSE);
+labelMakeCheckBox(cart, correctTdb, "func", "Most severe functional impact on gene if any", FALSE);
+puts("<br>");
+scoreCfgUi(db, cart, leafTdb, name, "", 0, FALSE);
+puts("<br>");
+puts("<b>Minimum MAF:</b>");
+boolean parentLevel = isNameAtParentLevel(leafTdb, name);
+double minMaf = cartUsualDoubleClosestToHome(cart, leafTdb, parentLevel, "minMaf", 0.0);
+char cartVar[1024];
+safef(cartVar, sizeof cartVar, "%s.minMaf", name);
+cgiMakeDoubleVarWithLimits(cartVar, minMaf, "MAF", 0, 0.0, 0.5);
+puts("range: 0.0 - 0.5");
+cfgEndBox(boxed);
+}
+
 void cfgByCfgType(eCfgType cType,char *db, struct cart *cart, struct trackDb *tdb,char *prefix,
 	      char *title, boolean boxed)
 // Methods for putting up type specific cfgs used by composites/subtracks in hui.c
 {
 // When only one subtrack, then show it's cfg settings instead of composite/view level settings
 // This simplifies the UI where hgTrackUi won't have 2 levels of cfg,
 // while hgTracks still supports rightClick cfg of the subtrack.
 
 if (configurableByAjax(tdb,cType) > 0) // Only if subtrack's configurable by ajax do we
     {                                  // consider this option
     if (tdbIsComposite(tdb)                       // called for the composite
         && !isCustomComposite(tdb)
         && !tdbIsCompositeView(tdb->subtracks)        // and there is no view level
         && slCount(tdb->subtracks) == 1)              // and there is only one subtrack
 	{
@@ -4354,30 +4455,32 @@
     case cfgLong:       longRangeCfgUi(cart, tdb, prefix, title, boxed);
 			break;
     case cfgSnake:      snakeCfgUi(cart, tdb, prefix, title, boxed);
 			break;
     case cfgPsl:        pslCfgUi(db,cart,tdb,prefix,title,boxed);
                         break;
     case cfgBarChart:   barChartCfgUi(db,cart,tdb,prefix,title,boxed);
                         break;
     case cfgInteract:   interactCfgUi(db,cart,tdb,prefix,title,boxed);
                         break;
     case cfgLollipop:   lollyCfgUi(db,cart,tdb,prefix,title,boxed);
 			scoreCfgUi(db, cart,tdb,prefix,title,1000,boxed);
                         break;
     case cfgHic:        hicCfgUi(db,cart,tdb,prefix,title,boxed);
                         break;
+    case cfgBigDbSnp:   bigDbSnpCfgUi(db, cart, tdb, prefix, title, boxed);
+                        break;
     default:            warn("Track type is not known to multi-view composites. type is: %d ",
 			     cType);
 			break;
     }
 }
 
 char *encodeRestrictionDate(char *db,struct trackDb *trackDb,boolean excludePast)
 // Create a string for ENCODE restriction date of this track
 // if return is not null, then free it after use
 {
 if (!trackDb)
     return NULL;
 
 char *date = NULL;
 
@@ -8893,38 +8996,34 @@
 if (isCustomTrack(tdb->track))
     {
     if (ct)
 	{
 	conn =  hAllocConn(CUSTOM_TRASH);
 	tableName = ct->dbTableName;
 	}
     }
 else if (startsWith("big", tdb->type))
     {
     char *tableName = hTableForTrack(database, tdb->table);
     struct sqlConnection *conn =  hAllocConnTrack(database, tdb);
     char *bbiFileName = bbiNameFromSettingOrTable(tdb, conn, tableName);
     hFreeConn(&conn);
     struct bbiFile *bbi = NULL;
-    if (startsWith("bigBed", tdb->type) || sameString("bigBarChart", tdb->type) 
-        || sameString("bigMaf", tdb->type) || sameString("bigPsl", tdb->type)
-        || sameString("bigChain", tdb->type) || sameString("bigGenePred", tdb->type)
-        || startsWith("bigLolly", tdb->type)
-        || sameString("bigInteract", tdb->type))
-	bbi = bigBedFileOpen(bbiFileName);
-    else if (startsWith("bigWig", tdb->type))
+    if (startsWith("bigWig", tdb->type))
 	bbi = bigWigFileOpen(bbiFileName);
+    else
+	bbi = bigBedFileOpen(bbiFileName);
     time_t timep = 0;
     if (bbi)
 	{
 	timep = bbiUpdateTime(bbi);
 	bbiFileClose(&bbi);
 	}
     printBbiUpdateTime(&timep);
     }
 else
     {
     tableName = hTableForTrack(database, tdb->table);
     conn = hAllocConnTrack(database, tdb);
     }
 if (tableName)
     {