b8e7a2261f97338a614b7eed2aa6131da27510e1
chmalee
  Tue Sep 1 10:47:36 2020 -0700
Add bigBed support for trackDb constructed mouseOvers, refs #26160

diff --git src/hg/hgTracks/bigBedTrack.c src/hg/hgTracks/bigBedTrack.c
index e45ad57..a5fa699 100644
--- src/hg/hgTracks/bigBedTrack.c
+++ src/hg/hgTracks/bigBedTrack.c
@@ -494,62 +494,76 @@
 
 int seqTypeField =  0;
 if (sameString(track->tdb->type, "bigPsl"))
     {
     seqTypeField =  bbExtraFieldIndex(bbi, "seqType");
     }
 
 int mouseOverIdx = bbExtraFieldIndex(bbi, mouseOverField);
 
 track->bbiFile = NULL;
 
 struct bigBedFilter *filters = bigBedBuildFilters(cart, bbi, track->tdb) ;
 if (compositeChildHideEmptySubtracks(cart, track->tdb, NULL, NULL))
    labelTrackAsHideEmpty(track);
 
+// mouseOvers can be built constructed via trackDb settings instead
+// of embedded directly in bigBed
+char *mouseOverPattern = NULL;
+char **fieldNames = NULL;
+if (!mouseOverIdx)
+    {
+    mouseOverPattern = cartOrTdbString(cart, track->tdb, "mouseOver", NULL);
+    AllocArray(fieldNames, bbi->fieldCount);
+    struct slName *field = NULL, *fields = bbFieldNames(bbi);
+    int i =  0;
+    for (field = fields; field != NULL; field = field->next)
+        fieldNames[i++] = field->name;
+    }
+
 // a fake item that is the union of the items that span the current  window
 struct linkedFeatures *spannedLf = NULL;
 unsigned filtered = 0;
 for (bb = bbList; bb != NULL; bb = bb->next)
     {
     struct linkedFeatures *lf = NULL;
+    char *bedRow[bbi->fieldCount];
     if (sameString(track->tdb->type, "bigPsl"))
 	{
 	char *seq, *cds;
 	struct psl *psl = pslFromBigPsl(chromName, bb, seqTypeField,  &seq, &cds); 
 	int sizeMul =  pslIsProtein(psl) ? 3 : 1;
 	boolean isXeno = 0;  // just affects grayIx
 	boolean nameGetsPos = FALSE; // we want the name to stay the name
 
 	lf = lfFromPslx(psl, sizeMul, isXeno, nameGetsPos, track);
 	lf->original = psl;
 	if ((seq != NULL) && (lf->orientation == -1))
 	    reverseComplement(seq, strlen(seq));
 	lf->extra = seq;
 	lf->cds = cds;
 	}
     else if (sameString(tdb->type, "bigDbSnp"))
         {
         // bigDbSnp does not have a score field, but I want to compute the freqSourceIx from
         // trackDb and settings one time instead of for each item, so I'm overloading scoreMin.
         int freqSourceIx = scoreMin;
         lf = lfFromBigDbSnp(tdb, bb, filters, freqSourceIx);
         }
     else
 	{
         char startBuf[16], endBuf[16];
-        char *bedRow[bbi->fieldCount];
         bigBedIntervalToRow(bb, chromName, startBuf, endBuf, bedRow, ArraySize(bedRow));
         if (bigBedFilterInterval(bedRow, filters))
             {
             struct bed *bed = bedLoadN(bedRow, fieldCount);
             lf = bedMungToLinkedFeatures(&bed, tdb, fieldCount,
                 scoreMin, scoreMax, useItemRgb);
             }
         if (track->visibility != tvDense && lf && doWindowSizeFilter && bb->start < winStart && bb->end > winEnd)
             {
             mergeCount++;
             struct bed *bed = bedLoadN(bedRow, fieldCount);
             struct linkedFeatures *tmp = bedMungToLinkedFeatures(&bed, tdb, fieldCount,
                 scoreMin, scoreMax, useItemRgb);
             if (spannedLf)
                 {
@@ -563,54 +577,59 @@
                     struct rgbColor currColor = colorIxToRgb(spannedLf->filterColor);
                     int r = currColor.r + round((itemColor.r - currColor.r) / mergeCount);
                     int g = currColor.g + round((itemColor.g - currColor.g) / mergeCount);
                     int b = currColor.b + round((itemColor.b - currColor.b) / mergeCount);
                     spannedLf->filterColor = MAKECOLOR_32(r,g,b);
                     }
                 }
             else
                 {
                 // setting the label here protects against the case when only one item would
                 // have been merged. When this happens we warn in the track longLabel that
                 // nothing happened and essentially make the spanned item be what the actual
                 // item would be. If multiple items are merged then the labels and mouseOvers
                 // will get fixed up later
                 tmp->label = bigBedMakeLabel(track->tdb, track->labelColumns,  bb, chromName);
+                if (mouseOverIdx > 0)
                     tmp->mouseOver = restField(bb, mouseOverIdx);
+                else if (mouseOverPattern)
+                    tmp->mouseOver = replaceFieldInPattern(mouseOverPattern, bbi->fieldCount, fieldNames, bedRow);
                 slAddHead(&spannedLf, tmp);
                 }
             continue; // lf will be NULL, but these items aren't "filtered", they're merged
             }
 	}
 
     if (lf == NULL)
         {
         filtered++;
         continue;
         }
 
     if (lf->label == NULL)
         lf->label = bigBedMakeLabel(track->tdb, track->labelColumns,  bb, chromName);
     if (sameString(track->tdb->type, "bigGenePred") || startsWith("genePred", track->tdb->type))
         {
         lf->original = genePredFromBigGenePred(chromName, bb); 
         }
 
     if (lf->mouseOver == NULL)
         {
-        char* mouseOver = restField(bb, mouseOverIdx);
-        lf->mouseOver   = mouseOver; // leaks some memory, cloneString handles NULL ifself 
+        if (mouseOverIdx > 0)
+            lf->mouseOver = restField(bb, mouseOverIdx);
+        else if (mouseOverPattern)
+            lf->mouseOver = replaceFieldInPattern(mouseOverPattern, bbi->fieldCount, fieldNames, bedRow);
         }
     slAddHead(pLfList, lf);
     }
 
 if (filtered)
    labelTrackAsFilteredNumber(track, filtered);
 
 if (doWindowSizeFilter)
     // add the number of merged items to the track longLabel
     {
     char labelBuf[256];
     if (mergeCount > 1)
         safef(labelBuf, sizeof(labelBuf), " (Merged %d items)", mergeCount);
     else
         safef(labelBuf, sizeof(labelBuf), " (No Items Merged in window)");