45c9532b9711e24298ca3900a6d4b45e4b51983c
galt
  Tue Jan 27 14:43:53 2015 -0800
Fixes #7474 - mouseover now shows exon and intron numbers for linkedFeatures tracks.

diff --git src/hg/hgTracks/simpleTracks.c src/hg/hgTracks/simpleTracks.c
index 9cb31ff..d55397c 100644
--- src/hg/hgTracks/simpleTracks.c
+++ src/hg/hgTracks/simpleTracks.c
@@ -187,30 +187,31 @@
 int leftLabelWidthChars = 17;   /* number of characters allowed for left label */
 int insideX;			/* Start of area to draw track in in pixels. */
 int insideWidth;		/* Width of area to draw tracks in in pixels. */
 int leftLabelX;                 /* Start of area to draw left labels on. */
 int leftLabelWidth;             /* Width of area to draw left labels on. */
 float basesPerPixel = 0;       /* bases covered by a pixel; a measure of zoom */
 boolean zoomedToBaseLevel;      /* TRUE if zoomed so we can draw bases. */
 boolean zoomedToCodonLevel; /* TRUE if zoomed so we can print codons text in genePreds*/
 boolean zoomedToCdsColorLevel; /* TRUE if zoomed so we can color each codon*/
 
 boolean withLeftLabels = TRUE;		/* Display left labels? */
 boolean withIndividualLabels = TRUE;    /* print labels on item-by-item basis (false to skip) */
 boolean withCenterLabels = TRUE;	/* Display center labels? */
 boolean withGuidelines = TRUE;		/* Display guidelines? */
 boolean withNextExonArrows = FALSE;	/* Display next exon navigation buttons near center labels? */
+boolean withExonNumbers = FALSE;	/* Display exon and intron numbers in mouseOver instead of item name */
 boolean revCmplDisp = FALSE;          /* reverse-complement display */
 
 boolean measureTiming = FALSE;	/* DON'T EDIT THIS -- use CGI param "&measureTiming=." . */
 struct track *trackList = NULL;    /* List of all tracks. */
 struct cart *cart;	/* The cart where we keep persistent variables. */
 
 int seqBaseCount;	/* Number of bases in sequence. */
 int winBaseCount;	/* Number of bases in window. */
 
 int maxShade = 9;	/* Highest shade in a color gradient. */
 Color shadesOfGray[10+1];	/* 10 shades of gray from white to black
                                  * Red is put at end to alert overflow. */
 Color shadesOfBrown[10+1];	/* 10 shades of brown from tan to tar. */
 struct rgbColor brownColor = {100, 50, 0};
 struct rgbColor tanColor = {255, 240, 200};
@@ -1907,30 +1908,157 @@
 	else if (bigExon)
 	    linkedFeaturesMoveWinEnd(exon->end, bufferToEdge, newWinSize, &newWinStart, &newWinEnd);
 	else
 	    linkedFeaturesMoveWinStart(exon->start, bufferToEdge, newWinSize, &newWinStart, &newWinEnd);
 	if (!revStrand)
 	    safef(mouseOverText, sizeof(mouseOverText), "%s (%d/%d)", prevExonText, numExons-exonIx, numExons);
 	else
 	    safef(mouseOverText, sizeof(mouseOverText), "%s (%d/%d)", nextExonText, exonIx+1, numExons);
 	mapBoxJumpTo(hvg, x, y, w, h, tg, chromName, newWinStart, newWinEnd, mouseOverText);
 	break;
 	}
     }
 slFreeList(&exonList);
 }
 
+void linkedFeaturesItemExonMaps(struct track *tg, struct hvGfx *hvg, void *item, double scale,
+    int y, int heightPer, int sItem, int eItem,
+    boolean lButton, boolean rButton, int buttonW)
+/* Draw mapBoxes over exons and introns labeled with exon/intron numbers */
+{
+struct linkedFeatures *lf = item;
+struct simpleFeature *exons = lf->components;
+struct simpleFeature *exon = exons;
+char *exonText, *intronText;
+int numExons = 0;
+int exonIx = 1;
+struct slRef *exonList = NULL, *ref;
+// TODO this exonText (and intronText) setting is just a made-up placeholder.
+// could add a real setting name. Maybe someday extend to exon names (LRG?) instead of just exon numbers
+if (startsWith("chain", tg->tdb->type) || startsWith("lrg", tg->tdb->track))
+    {
+    exonText   = trackDbSettingClosestToHomeOrDefault(tg->tdb, "exonText"  , "Block");
+    intronText = trackDbSettingClosestToHomeOrDefault(tg->tdb, "intronText", "Gap"  ); // what really goes here for chain type?
+    }
+else
+    {
+    exonText   = trackDbSettingClosestToHomeOrDefault(tg->tdb, "exonText"  , "Exon"  );
+    intronText = trackDbSettingClosestToHomeOrDefault(tg->tdb, "intronText", "Intron");
+    }
+while (exon != NULL)
+/* Make a stupid list of exons separate from what's given. */
+/* It seems like lf->components isn't necessarily sorted. */
+    {
+    refAdd(&exonList, exon);
+    exon = exon->next;
+    }
+/* Now sort it. */
+slSort(&exonList, exonSlRefCmp);
+numExons = slCount(exonList);
+boolean revStrand = (lf->orientation == -1);
+int eLast = -1;
+int s = -1;
+int e = -1;
+char mouseOverText[256];
+boolean isExon = TRUE;
+int picStart = insideX;
+int picEnd = picStart + insideWidth;
+if (lButton)
+    picStart += buttonW;
+if (rButton)
+    picEnd -= buttonW;
+for (ref = exonList; TRUE; )
+    {
+    exon = ref->val;
+    if (isExon)
+	{
+	s = exon->start;
+	e = exon->end;
+	}
+    else
+	{
+	s = eLast;
+	e = exon->start;
+	}
+    // skip exons and introns that are completely outside the window
+    if (!(s > winEnd) || (e < winStart))
+	{
+	int sClp = (s < winStart) ? winStart : s;
+	int eClp = (e > winEnd)   ? winEnd   : e;
+
+	int sx = round((sClp - winStart)*scale) + insideX;
+	int ex = round((eClp - winStart)*scale) + insideX;
+
+        // skip regions entirely outside available picture 
+        // (accounts for space taken by exon arrows buttons)
+	if (!(sx > picEnd) || (ex < picStart))
+	    {
+	    // clip it to avail pic 
+	    sx = (sx < picStart) ? picStart : sx;
+    	    ex = (ex > picEnd)   ? picEnd   : ex;
+
+	    int w = ex - sx;
+
+	    int exonIntronNumber;
+	    char *exonIntronText;
+	    int numExonIntrons = numExons;
+	    if (isExon)
+		{
+		exonIntronText = exonText;
+		}
+	    else
+		{
+		exonIntronText = intronText;
+		--numExonIntrons;  // introns are one fewer than exons
+		}
+
+	    if (!revStrand)
+		exonIntronNumber = exonIx;
+	    else
+		exonIntronNumber = numExonIntrons-exonIx+1;
+
+	    safef(mouseOverText, sizeof(mouseOverText), "%s (%d/%d)", exonIntronText, exonIntronNumber, numExonIntrons);
+
+	    if (w > 0) // draw exon or intron if width is greater than 0
+		{
+		tg->mapItem(tg, hvg, item, mouseOverText, tg->mapItemName(tg, item),
+		    sItem, eItem, sx, y, w, heightPer);
+		picStart = ex;  // prevent pileups. is this right? add 1? does it work?
+		}
+	    }
+	}
+
+    if (isExon)
+	{
+    	eLast = e;
+	ref = ref->next;
+	if (!ref)
+	    break;
+	}
+    else
+	{
+	exonIx++;
+	}
+    isExon = !isExon;
+
+    if (s > winEnd) // since the rest will also be outside the window
+	break;
+
+    }
+slFreeList(&exonList);
+}
+
 void linkedFeaturesLabelNextPrevItem(struct track *tg, boolean next)
 /* Default next-gene function for linkedFeatures.  Changes winStart/winEnd
  * and updates position in cart. */
 {
 int start = winStart;
 int end = winEnd;
 int size = winBaseCount;
 int sizeWanted = size;
 int bufferToEdge;
 struct bed *items = NULL;
 
 /* If there's stuff on the screen, skip past it. */
 /* If not, skip to the edge of the window. */
 if (next)
     {
@@ -2978,140 +3106,184 @@
 return highlight;
 }
 
 double scaleForWindow(double width, int seqStart, int seqEnd)
 /* Return the scale for the window. */
 {
 return width / (seqEnd - seqStart);
 }
 
 boolean nextItemCompatible(struct track *tg)
 /* Check to see if we draw nextPrev item buttons on a track. */
 {
 return (withNextExonArrows && tg->nextExonButtonable && tg->nextPrevExon);
 }
 
+boolean exonNumberMapsCompatible(struct track *tg, enum trackVisibility vis)
+/* Check to see if we draw exon and intron maps labeling their number. */
+{
+boolean exonNumbers = sameString(trackDbSettingOrDefault(tg->tdb, "exonNumbers", "on"), "on");
+return (withExonNumbers && exonNumbers && (vis==tvFull || vis==tvPack) && (winEnd - winStart < 400000) 
+ && (tg->nextPrevExon==linkedFeaturesNextPrevItem));
+}
+
 void genericMapItem(struct track *tg, struct hvGfx *hvg, void *item,
 		    char *itemName, char *mapItemName, int start, int end,
 		    int x, int y, int width, int height)
 /* This is meant to be used by genericDrawItems to set to tg->mapItem in */
 /* case tg->mapItem isn't set to anything already. */
 {
 // Don't bother if we are imageV2 and a dense child.
 if (!theImgBox || tg->limitedVis != tvDense || !tdbIsCompositeChild(tg->tdb))
     {
     char *directUrl = trackDbSetting(tg->tdb, "directUrl");
     boolean withHgsid = (trackDbSetting(tg->tdb, "hgsid") != NULL);
     mapBoxHgcOrHgGene(hvg, start, end, x, y, width, height, tg->track,
                       mapItemName, itemName, directUrl, withHgsid, NULL);
     }
 }
 
 void genericDrawNextItemStuff(struct track *tg, struct hvGfx *hvg, enum trackVisibility vis,
-                              struct slList *item, int x2, int textX, int y, int heightPer,
-                              Color color)
+                              struct slList *item, double scale, int x2, int x1, int textX, int y, int heightPer,
+                              Color color, boolean doButtons)
 /* After the item is drawn in genericDrawItems, draw next/prev item related */
 /* buttons and the corresponding mapboxes. */
 {
 int buttonW = heightPer-1 + 2*NEXT_ITEM_ARROW_BUFFER;
 int s = tg->itemStart(tg, item);
 int e = tg->itemEnd(tg, item);
 boolean rButton = FALSE;
 boolean lButton = FALSE;
 /* Draw the actual triangles.  These are always at the edge of the window. */
-if (s < winStart)
+if (doButtons && (s < winStart))
     {
     lButton = TRUE;
     hvGfxNextItemButton(hvg, insideX + NEXT_ITEM_ARROW_BUFFER, y,
                         heightPer-1, heightPer-1, color, MG_WHITE, FALSE);
     }
-if (e > winEnd)
+if (doButtons && (e > winEnd))
     {
     rButton = TRUE;
     hvGfxNextItemButton(hvg, insideX + insideWidth - NEXT_ITEM_ARROW_BUFFER - heightPer,
                         y, heightPer-1, heightPer-1, color, MG_WHITE, TRUE);
     }
+
+if ((vis == tvFull) || (vis == tvPack))
+    {
+    /* Make the button mapboxes. */
+    if (lButton)
+        tg->nextPrevExon(tg, hvg, item, insideX, y, buttonW, heightPer, FALSE);
+    if (rButton)
+        tg->nextPrevExon(tg, hvg, item, insideX + insideWidth - buttonW, y, buttonW, heightPer, TRUE);
+    }
+
+boolean compat = exonNumberMapsCompatible(tg, vis);
 if (vis == tvPack)
     {
     int w = x2-textX;
     if (lButton)
         { // if left-button, the label will be on far left, split out a map just for that label.
 	tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item),
                     s, e, textX, y, insideX-textX, heightPer);
-	tg->nextPrevExon(tg, hvg, item, insideX, y, buttonW, heightPer, FALSE);
 	textX = insideX + buttonW; // continue on the right side of the left exon button
 	w = x2-textX;
 	}
     if (rButton)
         {
-	tg->nextPrevExon(tg, hvg, item, x2-buttonW, y, buttonW, heightPer, TRUE);
 	w -= buttonW;
         }
+
+    if (compat)
+	{  // draw labeled exon/intron maps with exon/intron numbers
+	linkedFeaturesItemExonMaps(tg, hvg, item, scale, y, heightPer, s, e, lButton, rButton, buttonW);
+	x2 = x1;
+	w = x2-textX;
+	}
+    // if not already mapped, pick up the label
+    if (!(lButton && compat))
+	{
         tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item),
 		s, e, textX, y, w, heightPer);
 	}
+    }
+
 else if (vis == tvFull)
     {
     int geneMapBoxX = insideX;
     int geneMapBoxW = insideWidth;
-    /* Draw the first gene mapbox, in the left margin. */
+    /* Draw the first gene label mapbox, in the left margin. */
 #ifndef IMAGEv2_NO_LEFTLABEL_ON_FULL
     int trackPastTabX = (withLeftLabels ? trackTabWidth : 0);
 #ifdef IMAGEv2_SHORT_MAPITEMS
     char *name = tg->itemName(tg, item);
     if (*name != '\0')
         tg->mapItem(tg, hvg, item, name, tg->mapItemName(tg, item),
                     s, e, trackPastTabX, y, insideX - trackPastTabX, heightPer);
 #else///ndef IMAGEv2_SHORT_MAPITEMS
     tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item),
                 s, e, trackPastTabX, y, insideX - trackPastTabX, heightPer);
 #endif///ndef IMAGEv2_SHORT_MAPITEMS
 #endif///ndef IMAGEv2_NO_LEFTLABEL_ON_FULL
-    /* Make the button mapboxes. */
-    if (lButton)
-        tg->nextPrevExon(tg, hvg, item, insideX, y, buttonW, heightPer, FALSE);
-    if (rButton)
-        tg->nextPrevExon(tg, hvg, item, insideX + insideWidth - buttonW, y, buttonW, heightPer, TRUE);
     /* Depending on which button mapboxes we drew, draw the remaining mapbox. */
-    if (lButton && rButton)
+    if (lButton)
         {
         geneMapBoxX += buttonW;
-        geneMapBoxW -= 2 * buttonW;
+        geneMapBoxW -= buttonW;
         }
-    else if (lButton)
+    if (rButton)
 	{
-        geneMapBoxX += buttonW;
         geneMapBoxW -= buttonW;
 	}
-    else if (rButton)
-        geneMapBoxW -= buttonW;
 #ifdef IMAGEv2_SHORT_MAPITEMS
     if (x2 > 0)
         {
         geneMapBoxX = textX;
         geneMapBoxW = x2-geneMapBoxX;
         if (geneMapBoxW < 5) // Full with short labels but don't make tiny map items
             {
             geneMapBoxX -= (5 - geneMapBoxW)/2;
             geneMapBoxW = 5;
             }
         }
 #endif//def IMAGEv2_SHORT_MAPITEMS
+    if (compat)
+	{  // draw labeled exon/intron maps with exon/intron numbers
+	linkedFeaturesItemExonMaps(tg, hvg, item, scale, y, heightPer, s, e, lButton, rButton, buttonW);
+	if (!lButton)
+	    {
+	    int w = x1 - geneMapBoxX;
+	    if (w > 0)
+		tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item),
+		    s, e, geneMapBoxX, y, w, heightPer);
+	    }
+	if (!rButton)
+	    {
+	    int w = geneMapBoxX + geneMapBoxW - x2;
+	    if (w > 0)
+		tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item),
+		    s, e, x2, y, w, heightPer);
+	    }
+	}
+    else
+	{
 	tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item),
 		    s, e, geneMapBoxX, y, geneMapBoxW, heightPer);
 	}
     }
+}
+
+
 
 static void genericDrawOverflowItem(struct track *tg, struct spaceNode *sn,
                                     struct hvGfx *hvg, int xOff, int yOff, int width,
                                     MgFont *font, Color color, double scale,
                                     int overflowRow, boolean firstOverflow)
 /* genericDrawItems logic for drawing an overflow row */
 {
 /* change some state to paint it in the "overflow" row. */
 enum trackVisibility origVis = tg->limitedVis;
 int origLineHeight = tg->lineHeight;
 int origHeightPer = tg->heightPer;
 tg->limitedVis = tvDense;
 tg->heightPer = tl.fontHeight;
 tg->lineHeight = tl.fontHeight+1;
 struct slList *item = sn->val;
@@ -3218,31 +3390,33 @@
             {
             hvGfxBox(hvg, textX - 1, y, nameWidth+1, tg->heightPer-1, color);
             hvGfxTextRight(hvg, textX, y, nameWidth, tg->heightPer, MG_WHITE, font, name);
             }
         else
             hvGfxTextRight(hvg, textX, y, nameWidth, tg->heightPer, labelColor, font, name);
         }
     }
 if (!tg->mapsSelf)
     {
     int w = x2-textX;
     /* Arrows? */
     if (w > 0)
         {
         if (nextItemCompatible(tg))
-            genericDrawNextItemStuff(tg, hvg, vis, item, x2, textX, y, tg->heightPer, color);
+            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 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. */
 {
@@ -3459,34 +3633,54 @@
         #ifdef IMAGEv2_NO_LEFTLABEL_ON_FULL
             if (theImgBox != NULL && vis == tvFull)  // dragScroll >1x has no bed full leftlabels,
                 {                                    // but in image labels like pack.
                 char *name = tg->itemName(tg, item);
                 int nameWidth = mgFontStringWidth(font, name);
                 int textX = round((s - winStart)*scale) + xOff;
                 textX -= (nameWidth + 2);
                 if (textX >= insideX && nameWidth > 0)
                     {
                     x1 = textX; // extends the map item to cover this label
                     Color itemNameColor = tg->itemNameColor(tg, item, hvg);
                     hvGfxTextRight(hvg,textX,y,nameWidth,tg->heightPer,itemNameColor,font,name);
                     }
                 }
         #endif ///def IMAGEv2_NO_LEFTLABEL_ON_FULL
-            genericDrawNextItemStuff(tg, hvg, vis, item, x2, x1, y, tg->heightPer, color);
+            genericDrawNextItemStuff(tg, hvg, vis, item, scale, x2, x1, x1, y, tg->heightPer, color);
             }
 #else//ifndef IMAGEv2_SHORT_MAPITEMS
-            genericDrawNextItemStuff(tg, hvg, vis, item, -1, -1, y, tg->heightPer, color);
+            {
+            // Convert start/end coordinates to pix
+            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;
+            genericDrawNextItemStuff(tg, hvg, vis, item, scale, x2, x1, -1, y, tg->heightPer, color, TRUE); // was -1, -1, -1
+	    }
+        else if (exonNumberMapsCompatible(tg, vis))
+            {
+            // Convert start/end coordinates to pix
+            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;
+            genericDrawNextItemStuff(tg, hvg, vis, item, scale, x2, x1, -1, y, tg->heightPer, color, FALSE); // was -1, -1, -1
+	    }
 #endif//ndef IMAGEv2_SHORT_MAPITEMS
         y += tg->lineHeight;
         }
     }
 }
 
 void genericDrawItems(struct track *tg, int seqStart, int seqEnd,
                       struct hvGfx *hvg, int xOff, int yOff, int width,
                       MgFont *font, Color color, enum trackVisibility vis)
 /* Draw generic item list.  Features must be fixed height
  * and tg->drawItemAt has to be filled in. */
 {
 if (tg->mapItem == NULL)
     tg->mapItem = genericMapItem;
 if (vis != tvDense && (! bedItemRgb(tg->tdb)) && baseColorCanDraw(tg))
@@ -12793,41 +12987,41 @@
     else
 	track->colorShades = shadesOfGray;
     }
 track->tdb = tdb;
 
 /* Handle remote database settings - just a JK experiment at the moment. */
 track->remoteSqlHost = trackDbSetting(tdb, "sqlHost");
 track->remoteSqlUser = trackDbSetting(tdb, "sqlUser");
 track->remoteSqlPassword = trackDbSetting(tdb, "sqlPassword");
 track->remoteSqlDatabase = trackDbSetting(tdb, "sqlDatabase");
 track->remoteSqlTable = trackDbSetting(tdb, "sqlTable");
 track->isRemoteSql =  (track->remoteSqlHost != NULL && track->remoteSqlUser != NULL
 			&& track->remoteSqlDatabase != NULL && track->remoteSqlTable !=NULL);
 
 exonArrows = trackDbSetting(tdb, "exonArrows");
-nextItem = trackDbSetting(tdb, "nextItemButton");
 /* default exonArrows to on, except for tracks in regulation/expression group */
 if (exonArrows == NULL)
     {
     if (sameString(tdb->grp, "regulation"))
        exonArrows = "off";
     else
        exonArrows = "on";
     }
 track->exonArrows = sameString(exonArrows, "on");
 track->nextExonButtonable = TRUE;
+nextItem = trackDbSetting(tdb, "nextItemButton");
 if (nextItem && sameString(nextItem, "off"))
     track->nextExonButtonable = FALSE;
 track->nextItemButtonable = track->nextExonButtonable;
 #ifndef GBROWSE
 char *iatName = trackDbSetting(tdb, "itemAttrTbl");
 if (iatName != NULL)
     track->itemAttrTbl = itemAttrTblNew(iatName);
 #endif /* GBROWSE */
 fillInFromType(track, tdb);
 return track;
 }
 
 struct hash *handlerHash;
 
 void registerTrackHandler(char *name, TrackHandler handler)