b038f3dafce99493b0ca00305538c6a3dd6f041a
jcasper
  Tue Sep 26 15:58:44 2023 -0700
Initial commit of track decorators, refs #30237

diff --git src/hg/hgTracks/simpleTracks.c src/hg/hgTracks/simpleTracks.c
index 686dd57..baa6a87 100644
--- src/hg/hgTracks/simpleTracks.c
+++ src/hg/hgTracks/simpleTracks.c
@@ -26,30 +26,32 @@
 #include "wiggle.h"
 #include "lfs.h"
 #include "grp.h"
 #include "chromColors.h"
 #include "hgTracks.h"
 #include "subText.h"
 #include "cds.h"
 #include "mafTrack.h"
 #include "wigCommon.h"
 #include "hui.h"
 #include "imageV2.h"
 #include "bigBed.h"
 #include "htmshell.h"
 #include "kxTok.h"
 #include "hash.h"
+#include "decorator.h"
+#include "decoratorUi.h"
 
 #ifndef GBROWSE
 #include "encode.h"
 #include "expRatioTracks.h"
 #include "hapmapTrack.h"
 #include "retroGene.h"
 #include "switchGear.h"
 #include "variation.h"
 #include "wiki.h"
 #include "wormdna.h"
 #include "aliType.h"
 #include "agpGap.h"
 #include "cgh.h"
 #include "bactigPos.h"
 #include "genePred.h"
@@ -456,30 +458,37 @@
 	}
     struct spaceSaver *ss = findSpaceSaver(tg, vis);
     if (ss)
 	return ss->rowCount;
     // Falls thru here if a new visibility is needed, such as full changing to pack after limitVisibility.
     // This will usually be when it is the first window and it is requesting a new vis.
     }
 
 if (currentWindow != windows)  // if not first window
     {
     errAbort("unexpected current window %lu, expected first window %lu", (unsigned long) currentWindow, (unsigned long) windows);
     }
 
 // If we get here, currentWindow should be first window i.e. windows var.
 
+if (hasDecorators(tg))
+{
+    packDecorators(tg);  // be nice to avoid doing this for every pass through packCountRowsOverflow
+            // reason being we only ever pack decorations one way, which only depends on the decoration style
+            // (and user selections), but not the vis mode - vis just changes height.
+}
+
 struct slList *item;
 MgFont *font = tl.font;
 int extraWidth = tl.mWidth * 2;
 long long start, end;  // TODO usually screen pixels, so plain long should work.
 struct window *w;
 struct track *tgSave = tg;
 
 // find out which items are the same,
 // and belong on the same line with (probably one) label:
 struct hash *sameItem = newHash(10);
 // TODO estimate this as log base 2 # items in all windows
 //struct hash *itemDone = newHash(10);  // TODO I do not need this yet in this version if no dupes.
 for(w=windows,tg=tgSave; w; w=w->next,tg=tg->nextWindow)
     {
     // Have to init these here
@@ -614,56 +623,123 @@
 		sin->done = TRUE;
 		foundWork = TRUE;
 		lastWin = sin->window;
 
 		int winOffset = sin->window->insideX - fullInsideX;
 		struct slList *item = sin->item;
 		int baseStart = tg->itemStart(tg, item);
 		int baseEnd = tg->itemEnd(tg, item);
 		struct window *w = sin->window;
 
 		// convert bases to pixels
 		if (baseStart <= w->winStart)
 		    start = 0;
 		else
 		    start = round((double)(baseStart - w->winStart)*scale);
+
+int expandPack = 0;  // deactivating item boundary extention for now - working on that next
+                if (hasDecorators(tg) && expandPack)
+                // extend item start with overlays to put the label in the right place
+                    {
+                    int overlayStart;
+                    char decoratorKey[2048];
+                    safef(decoratorKey, sizeof decoratorKey, "%s:%d-%d:%s", chrom, baseStart, baseEnd, mapItemName);
+                    if (decoratorGroupGetOverlayExtent(tg, decoratorKey, &overlayStart, NULL))
+                        {
+                        if (overlayStart < start)
+                            start = overlayStart;
+                        }
+                    }
+
 		if (!tg->drawLabelInBox && !tg->drawName && withLabels && (!noLabel))
+                // add item label
 		    {
 		    leftLabelSize = mgFontStringWidth(font,
 					       tg->itemName(tg, item)) + extraWidth;
 		    if (start - leftLabelSize + winOffset < 0)
 			leftLabelSize = start + winOffset;
 		    start -= leftLabelSize;
 		    }
 
+                if (hasDecorators(tg) && expandPack)
+                // expand item start for packing if other decorations push it past the left label edge, for packing
+                    {
+                    int decoratedStart;
+                    char decoratorKey[2048];
+                    safef(decoratorKey, sizeof decoratorKey, "%s:%d-%d:%s", chrom, baseStart, baseEnd, mapItemName);
+                    if (decoratorGroupGetExtent(tg, decoratorKey, &decoratedStart, NULL))
+                        {
+                        if (decoratedStart < start)
+                            start = decoratedStart;
+                        }
+                    }
+
 		if (baseEnd >= w->winEnd)
 		    end = w->insideWidth;
 		else
 		    end = round((baseEnd - w->winStart)*scale);
+
+                if (hasDecorators(tg) && expandPack)
+                // extend item ends via overlay here
+                    {
+                    int overlayEnd;
+                    char decoratorKey[2048];
+                    safef(decoratorKey, sizeof decoratorKey, "%s:%d-%d:%s", chrom, baseStart, baseEnd, mapItemName);
+                    if (decoratorGroupGetOverlayExtent(tg, decoratorKey, NULL, &overlayEnd))
+                        {
+                        if (overlayEnd > end)
+                            end = overlayEnd;
+                        }
+                    }
+
 		if (tg->itemRightPixels && withLabels)
+                // draw anything that's supposed to be off the right edge (like right labels)
 		    {
 		    end += tg->itemRightPixels(tg, item);
 		    if (end > w->insideWidth)
 			end = w->insideWidth;
 		    }
 
+                if (hasDecorators(tg) && expandPack)
+                // and extend item end via general decoration extension here, for packing
+                    {
+                    int decoratedEnd;
+                    char decoratorKey[2048];
+                    safef(decoratorKey, sizeof decoratorKey, "%s:%d-%d:%s", chrom, baseStart, baseEnd, mapItemName);
+                    if (decoratorGroupGetExtent(tg, decoratorKey, NULL, &decoratedEnd))
+                        {
+                        if (decoratedEnd > end)
+                            end = decoratedEnd;
+                        }
+                    }
+
+
 		AllocVar(range);
 		range->start = start + winOffset;
 		range->end = end + winOffset;
 		slAddHead(&rangeList, range);
                 rangeWidth += (range->end - range->start);
+
                 range->height = 1;
+                if (hasDecorators(tg))
+                    {
+                    char itemString[2048];
+                    struct linkedFeatures *lf = (struct linkedFeatures*) item;
+                    safef(itemString, sizeof(itemString), "%s:%d-%d:%s", chrom, baseStart,
+                            baseEnd, lf->name);
+                    range->height += decoratorGroupHeight(tg, itemString);
+                    }
 
 		AllocVar(node);
 		node->val = item;
 		node->parentSs = sin->ss;
 		node->noLabel = noLabel;
 		slAddHead(&nodeList, node);
 
 		noLabel = TRUE; // turns off labels for all following windows - for now.
 
 		}
 
 	    if (!foundWork)
 		continue;
 
 
@@ -4330,153 +4406,207 @@
     hvGfxSetClip(hvgSide, leftLabelX, yOff, insideWidth, tg->height);
     char nameBuff[SMALLBUF];
     safef(nameBuff, sizeof(nameBuff), "Last Row: %d", overflowCount);
     mgFontStringWidth(font, nameBuff);
     hvGfxTextRight(hvgSide, leftLabelX, y, leftLabelWidth-1, tg->lineHeight,
                    color, font, nameBuff);
     hvGfxUnclip(hvgSide);
     hvGfxSetClip(hvgSide, insideX, yOff, insideWidth, tg->height);
     }
 /* restore state */
 tg->limitedVis = origVis;
 tg->heightPer = origHeightPer;
 tg->lineHeight = origLineHeight;
 }
 
-static void genericDrawItem(struct track *tg, struct spaceNode *sn,
-                            struct hvGfx *hvg, int xOff, int yOff, int width,
+
+static void genericDrawItemLabel(struct track *tg, struct spaceNode *sn,
+                                 struct hvGfx *hvg, int xOff, int y, int width,
                                  MgFont *font, Color color, Color labelColor, enum trackVisibility vis,
                                  double scale, boolean withLeftLabels)
-/* draw one non-overflow item */
 {
 struct slList *item = sn->val;
 int s = tg->itemStart(tg, item);
-int e = tg->itemEnd(tg, item);
+//int e = tg->itemEnd(tg, item);
 int sClp = (s < winStart) ? winStart : s;
-int eClp = (e > winEnd)   ? winEnd   : e;
+//int eClp = (e > winEnd)   ? winEnd   : e;
 int x1 = round((sClp - winStart)*scale) + xOff;
-int x2 = round((eClp - winStart)*scale) + xOff;
+//int x2 = round((eClp - winStart)*scale) + xOff;
 int textX = x1;
 
 if (tg->drawLabelInBox)
     withLeftLabels = FALSE;
 
 if (tg->itemNameColor != NULL)
     {
     color = tg->itemNameColor(tg, item, hvg);
     labelColor = color;
     if (withLeftLabels && isTooLightForTextOnWhite(hvg, color))
 	labelColor = somewhatDarkerColor(hvg, color);
     }
 
-// get y offset for item in pack mode
-int yRow = 0;
-if (tg->ss && tg->ss->rowSizes != NULL)
-    {
-    int i;
-    for (i=0; i < sn->row; i++)
-        yRow += tg->ss->rowSizes[i];
-    int itemHeight = tg->itemHeight(tg, item);
-    yRow += (tg->ss->rowSizes[sn->row] - itemHeight + 1);
-    tg->heightPer = itemHeight;
-    }
-else
-    yRow = tg->lineHeight * sn->row;
-int y = yOff + yRow;
-
-tg->drawItemAt(tg, item, hvg, xOff, y, scale, font, color, vis);
-
-// GALT non-proportional track like gtexGene
-handleNonPropDrawItemAt(tg, sn, item, hvg, xOff, y, scale, font, color, vis);
-
 /* pgSnpDrawAt may change withIndividualLabels between items */
 boolean withLabels = (withLeftLabels && withIndividualLabels && ((vis == tvPack) || (vis == tvFull && isTypeBedLike(tg))) && (!sn->noLabel) && !tg->drawName);
 if (withLabels)
     {
     char *name = tg->itemName(tg, item);
     int nameWidth = mgFontStringWidth(font, name);
     int dotWidth = tl.nWidth/2;
     boolean snapLeft = FALSE;
     boolean drawNameInverted = highlightItem(tg, item);
     textX -= nameWidth + dotWidth;
-
     snapLeft = (textX < fullInsideX);
     snapLeft |= (vis == tvFull && isTypeBedLike(tg));
 
     /* Special tweak for expRatio in pack mode: force all labels
      * left to prevent only a subset from being placed right: */
     snapLeft |= (startsWith("expRatio", tg->tdb->type));
 
 #ifdef IMAGEv2_NO_LEFTLABEL_ON_FULL
     if (theImgBox == NULL && snapLeft)
 #else///ifndef IMAGEv2_NO_LEFTLABEL_ON_FULL
     if (snapLeft)        /* Snap label to the left. */
 #endif ///ndef IMAGEv2_NO_LEFTLABEL_ON_FULL
         {
         textX = leftLabelX;
         assert(hvgSide != NULL);
+        int prevX, prevY, prevWidth, prevHeight;
+        hvGfxGetClip(hvgSide, &prevX, &prevY, &prevWidth, &prevHeight);
         hvGfxUnclip(hvgSide);
-        hvGfxSetClip(hvgSide, leftLabelX, yOff, fullInsideX - leftLabelX, tg->height);
+        hvGfxSetClip(hvgSide, leftLabelX, y, fullInsideX - leftLabelX, tg->lineHeight);
         if(drawNameInverted)
             {
             int boxStart = leftLabelX + leftLabelWidth - 2 - nameWidth;
             hvGfxBox(hvgSide, boxStart, y, nameWidth+1, tg->heightPer - 1, color);
             hvGfxTextRight(hvgSide, leftLabelX, y, leftLabelWidth-1, tg->heightPer,
                         MG_WHITE, font, name);
             }
         else
             hvGfxTextRight(hvgSide, leftLabelX, y, leftLabelWidth-1, tg->heightPer,
                         labelColor, font, name);
         hvGfxUnclip(hvgSide);
-        hvGfxSetClip(hvgSide, insideX, yOff, insideWidth, tg->height);
+        hvGfxSetClip(hvgSide, prevX, prevY, prevWidth, prevHeight);
         }
     else
         {
 	// shift clipping to allow drawing label to left of currentWindow
 	int pdfSlop=nameWidth/5;
+        int prevX, prevY, prevWidth, prevHeight;
+        hvGfxGetClip(hvgSide, &prevX, &prevY, &prevWidth, &prevHeight);
         hvGfxUnclip(hvg);
         hvGfxSetClip(hvg, textX-1-pdfSlop, y, nameWidth+1+pdfSlop, tg->heightPer);
         if(drawNameInverted)
             {
             hvGfxBox(hvg, textX - 1, y, nameWidth+1, tg->heightPer-1, color);
             hvGfxTextRight(hvg, textX, y, nameWidth, tg->heightPer, MG_WHITE, font, name);
             }
         else
             // usual labeling
             hvGfxTextRight(hvg, textX, y, nameWidth, tg->heightPer, labelColor, font, name);
         hvGfxUnclip(hvg);
-        hvGfxSetClip(hvg, insideX, yOff, insideWidth, tg->height);
+        hvGfxSetClip(hvgSide, prevX, prevY, prevWidth, prevHeight);
         }
     }
+withIndividualLabels = TRUE; /* reset in case done with pgSnp */
+}
+
+static void genericItemMapAndArrows(struct track *tg, struct spaceNode *sn,
+                                    struct hvGfx *hvg, int xOff, int y, int width,
+                                    MgFont *font, Color color, Color labelColor, enum trackVisibility vis,
+                                    double scale, boolean withLeftLabels)
+{
+struct slList *item = sn->val;
+int s = tg->itemStart(tg, item);
+int e = tg->itemEnd(tg, item);
+int sClp = (s < winStart) ? winStart : s;
+int eClp = (e > winEnd)   ? winEnd   : e;
+int x1 = round((sClp - winStart)*scale) + xOff;
+int x2 = round((eClp - winStart)*scale) + xOff;
+int textX = x1;
+
+
 if (!tg->mapsSelf)
     {
     int w = x2-textX;
     /* Arrows? */
     if (w > 0)
         {
         if (nextItemCompatible(tg))
             genericDrawNextItemStuff(tg, hvg, vis, item, scale, x2, x1, textX, y, tg->heightPer, color, TRUE);
         else if (exonNumberMapsCompatible(tg, vis))
             genericDrawNextItemStuff(tg, hvg, vis, item, scale, x2, x1, textX, y, tg->heightPer, color, FALSE);
         else
             {
             tg->mapItem(tg, hvg, item, tg->itemName(tg, item),
                         tg->mapItemName(tg, item), s, e, textX, y, w, tg->heightPer);
             }
         }
     }
-    withIndividualLabels = TRUE; /* reset in case done with pgSnp */
+}
+
+
+static void genericDrawItem(struct track *tg, struct spaceNode *sn,
+                            struct hvGfx *hvg, int xOff, int yOff, int width,
+                            MgFont *font, Color color, Color labelColor, enum trackVisibility vis,
+                            double scale, boolean withLeftLabels)
+/* draw one non-overflow item */
+{
+struct slList *item = sn->val;
+//int s = tg->itemStart(tg, item);
+//int e = tg->itemEnd(tg, item);
+//int sClp = (s < winStart) ? winStart : s;
+//int eClp = (e > winEnd)   ? winEnd   : e;
+//int x1 = round((sClp - winStart)*scale) + xOff;
+//int x2 = round((eClp - winStart)*scale) + xOff;
+//int textX = x1;
+
+if (tg->drawLabelInBox)
+    withLeftLabels = FALSE;
+
+if (tg->itemNameColor != NULL)
+    {
+    color = tg->itemNameColor(tg, item, hvg);
+    labelColor = color;
+    if (withLeftLabels && isTooLightForTextOnWhite(hvg, color))
+	labelColor = somewhatDarkerColor(hvg, color);
+    }
+
+// get y offset for item in pack mode
+int yRow = 0;
+if (tg->ss && tg->ss->rowSizes != NULL)
+    {
+    int i;
+    for (i=0; i < sn->row; i++)
+        yRow += tg->ss->rowSizes[i];
+    int itemHeight = tg->itemHeight(tg, item);
+    yRow += (tg->ss->rowSizes[sn->row] - itemHeight + 1);
+    tg->heightPer = itemHeight;
+    }
+else
+    yRow = tg->lineHeight * sn->row;
+int y = yOff + yRow;
+
+tg->drawItemAt(tg, item, hvg, xOff, y, scale, font, color, vis);
+
+// GALT non-proportional track like gtexGene
+handleNonPropDrawItemAt(tg, sn, item, hvg, xOff, y, scale, font, color, vis);
+
+tg->drawItemLabel(tg, sn, hvg, xOff, y, width, font, color, labelColor, vis, scale, withLeftLabels);
+
+// do mapping and arrows
+
+tg->doItemMapAndArrows(tg, sn, hvg, xOff, y, width, font, color, labelColor, vis, scale, withLeftLabels);
 }
 
 int normalizeCount(struct preDrawElement *el, double countFactor,
     double minVal, double maxVal, double sumData, double sumSquares)
 /* Normalize statistics to be based on an integer number of valid bases.
  * Integer value is the smallest integer not less than countFactor. */
 {
 bits32 validCount = ceil(countFactor);
 double normFactor = (double)validCount/countFactor;
 
 el->count = validCount;
 el->min = minVal;
 el->max = maxVal;
 el->sumData = sumData * normFactor;
 el->sumSquares = sumSquares * normFactor;
@@ -5074,40 +5204,47 @@
     return;
 
 struct linkedFeatures *lf = item;
 char *newItemName   = (isEmpty(lf->mouseOver)) ? itemName: lf->mouseOver;
 
 // copied from genericMapItem
 char *directUrl = trackDbSetting(tg->tdb, "directUrl");
 boolean withHgsid = (trackDbSetting(tg->tdb, "hgsid") != NULL);
 char *trackName = tg->track;
 if (tg->originalTrack != NULL)
     trackName = tg->originalTrack;
 mapBoxHgcOrHgGene(hvg, start, end, x, y, width, height, trackName,
                   mapItemName, newItemName, directUrl, withHgsid, NULL);
 }
 
+int linkedFeaturesFixedTotalHeightNoOverflow(struct track *tg, enum trackVisibility vis)
+{
+return tgFixedTotalHeightOptionalOverflow(tg,vis, tl.fontHeight+1, tl.fontHeight, FALSE);
+}
+
+
 void linkedFeaturesMethods(struct track *tg)
 /* Fill in track methods for linked features. */
 {
 tg->freeItems = linkedFeaturesFreeItems;
 tg->drawItems = linkedFeaturesDraw;
 tg->drawItemAt = linkedFeaturesDrawAt;
 tg->itemName = linkedFeaturesName;
 tg->mapItemName = linkedFeaturesName;
 tg->mapItem = linkedFeaturesMapItem;
-tg->totalHeight = tgFixedTotalHeightNoOverflow;
+//tg->totalHeight = tgFixedTotalHeightNoOverflow;
+tg->totalHeight = linkedFeaturesFixedTotalHeightNoOverflow;
 tg->itemHeight = tgFixedItemHeight;
 tg->itemStart = linkedFeaturesItemStart;
 tg->itemEnd = linkedFeaturesItemEnd;
 tg->itemNameColor = linkedFeaturesNameColor;
 tg->nextPrevExon = linkedFeaturesNextPrevItem;
 tg->nextPrevItem = linkedFeaturesLabelNextPrevItem;
 
 if (trackDbSettingClosestToHomeOn(tg->tdb, "linkIdInName"))
     {
     tg->mapItemName = linkedFeaturesNameField1;
     tg->itemName = linkedFeaturesNameNotField1;
     }
 }
 
 int linkedFeaturesSeriesItemStart(struct track *tg, void *item)
@@ -14099,30 +14236,33 @@
 track->nextPrevExon = NULL;
 }
 
 void fillInFromType(struct track *track, struct trackDb *tdb)
 /* Fill in various function pointers in track from type field of tdb. */
 {
 char *typeLine = tdb->type, *words[8], *type;
 int wordCount;
 if (typeLine == NULL)
     return;
 wordCount = chopLine(cloneString(typeLine), words);
 if (wordCount <= 0)
     return;
 type = words[0];
 
+track->drawItemLabel = genericDrawItemLabel;
+track->doItemMapAndArrows = genericItemMapAndArrows;
+
 #ifndef GBROWSE
 if (sameWord(type, "bed"))
     {
     complexBedMethods(track, tdb, FALSE, wordCount, words);
     /* bed.h includes genePred.h so should be able to use these trackDb
        settings. */
     if (trackDbSetting(track->tdb, GENEPRED_CLASS_TBL) !=NULL)
         track->itemColor = genePredItemClassColor;
 
     // FIXME: as long as registerTrackHandler doesn't accept wildcards,
     // this probably needs to stay here (it's in the wrong function)
     if (startsWith("pubs", track->track) && stringIn("Marker", track->track))
         pubsMarkerMethods(track);
     if (startsWith("pubs", track->track) && stringIn("Blat", track->track))
         pubsBlatMethods(track);