8c908f948b09826c6cb4452ee5b282aca41be85e
galt
  Tue Dec 8 21:52:59 2015 -0800
Multi-region (exonMostly). This work allows people to look at virtual chromosomes from a list of regions and then navigate and perform all of the usual functions on it.

diff --git src/hg/hgTracks/rmskJoinedTrack.c src/hg/hgTracks/rmskJoinedTrack.c
index dce1ba6..7c9a231 100644
--- src/hg/hgTracks/rmskJoinedTrack.c
+++ src/hg/hgTracks/rmskJoinedTrack.c
@@ -39,44 +39,101 @@
 /* MAX_UNALIGNED_PIXEL_LEN: The maximum size of unaligned sequence
  * to draw before switching to the unscaled view: ie. "----/###/---"
  * This is measured in pixels *not* base pairs due to the
  * mixture of scaled ( lines ) and unscaled ( text ) glyph
  * components.
  */
 #define MAX_UNALIGNED_PIXEL_LEN 150
 
 // TODO: Document
 #define LABEL_PADDING 5
 #define MINHEIGHT 24
 
 // TODO: Document
 static float pixelsPerBase = 1.0;
 
+
+struct windowGlobals
+{
+struct windowGlobals *next;
+struct window *window;
+/* These are the static globals needed for each window: */
+struct hash *classHash;
+struct repeatItem *otherRepeatItem;
+struct hash *subTracksHash;
+};
+
+static struct windowGlobals *wgWindows = NULL;
+static struct windowGlobals *wgWindow = NULL;
+static struct windowGlobals *wgWindowOld = NULL;
+
+static bool setWgWindow()
+/* scan genoCache windows. create new one if not found */
+{
+if (wgWindow && wgWindow->window == currentWindow)
+    return FALSE;
+wgWindowOld = wgWindow;
+for (wgWindow = wgWindows; wgWindow; wgWindow =  wgWindow->next)
+    {
+    if (wgWindow->window == currentWindow)
+	{
+	return TRUE;
+	}
+    }
+AllocVar(wgWindow);
+wgWindow->window = currentWindow;
+slAddTail(&wgWindows, wgWindow);
+return TRUE;
+}
+
+// per window static vars act like local global vars, need one per window
+
 /* Hash of the repeatItems ( tg->items ) for this track.
  *   This is used to hold display names for the lines of
  *   the track, the line heights, and class colors.
  */
 struct hash *classHash = NULL;
 static struct repeatItem *otherRepeatItem = NULL;
 
 /* Hash of subtracks
  *   The joinedRmskTrack is designed to be used as a composite track.
  *   When rmskJoinedLoadItems is called this hash is populated with the
  *   results of one or more table queries
  */
 struct hash *subTracksHash = NULL;
 
+
+static void setWg()
+/* set up per-window globals for the current window */
+{
+if (setWgWindow())
+    {
+    if (wgWindowOld)
+	{
+	wgWindowOld->classHash       = classHash;
+	wgWindowOld->otherRepeatItem = otherRepeatItem;
+    	wgWindowOld->subTracksHash   = subTracksHash;
+	}
+    classHash       = wgWindow->classHash;
+    otherRepeatItem = wgWindow->otherRepeatItem;
+    subTracksHash   = wgWindow->subTracksHash;
+    }
+
+}
+
+
+
 struct subTrack
 {
   // The number of display levels used in levels[]
 int levelCount;
   // The rmskJoined records from table query
 struct rmskJoined **levels;
 };
 
 
 // Repeat Classes: Display names
 static char *rptClassNames[] = {
 "SINE", "LINE", "LTR", "DNA", "Simple", "Low Complexity", "Satellite",
 "RNA", "Other", "Unknown",
 };
 
@@ -94,115 +151,109 @@
  *        help table in joinedRmskTrack.html
  */
 static Color rmskJoinedClassColors[] = {
 0xff1f77b4,			// SINE - red
 0xffff7f0e,			// LINE - lime
 0xff2ca02c,			// LTR - maroon
 0xffd62728,			// DNA - fuchsia
 0xff9467bd,			// Simple - yellow
 0xff8c564b,			// LowComplex - olive
 0xffe377c2,			// Satellite - blue
 0xff7f7f7f,			// RNA - green
 0xffbcbd22,			// Other - teal
 0xff17becf,			// Unknown - aqua
 };
 
-// Basic range type
-struct Extents
-{
-int start;
-int end;
-};
 
-static struct Extents * getExtents(struct rmskJoined *rm)
+static void getExtents(struct rmskJoined *rm, int *pStart, int *pEnd)
 /* Calculate the glyph extents in genome coordinate
  * space ( ie. bp )
  *
  * It is not straightforward to determine the extent
  * of this track's glyph.  This is due to the mixture
  * of graphic and text elements within the glyph.
  *
  */
 {
-static struct Extents ex;
+int start;
+int end;
 char lenLabel[20];
 
-if (rm == NULL)
-    return NULL;
-
 // Start position is anchored by the alignment start
 // coordinates.  Then we subtract from that space for
 // the label.
-ex.start = rm->alignStart -
+start = rm->alignStart -
     (int) ((mgFontStringWidth(tl.font, rm->name) +
 	    LABEL_PADDING) / pixelsPerBase);
 
 // Now subtract out space for the unaligned sequence
 // bar starting off the graphics portion of the glyph.
 if ((rm->blockSizes[0] * pixelsPerBase) > MAX_UNALIGNED_PIXEL_LEN)
     {
     // Unaligned sequence bar needs length label -- account for that.
     safef(lenLabel, sizeof(lenLabel), "%d", rm->blockSizes[0]);
-    ex.start -= (int) (MAX_UNALIGNED_PIXEL_LEN / pixelsPerBase) +
+    start -= (int) (MAX_UNALIGNED_PIXEL_LEN / pixelsPerBase) +
 	(int) ((mgFontStringWidth(tl.font, lenLabel) +
 		LABEL_PADDING) / pixelsPerBase);
     }
 else
     {
-    ex.start -= rm->blockSizes[0];
+    start -= rm->blockSizes[0];
     }
 
-ex.end = rm->alignEnd;
+end = rm->alignEnd;
 
 if ((rm->blockSizes[rm->blockCount - 1] * pixelsPerBase) >
     MAX_UNALIGNED_PIXEL_LEN)
     {
     safef(lenLabel, sizeof(lenLabel), "%d",
 	   rm->blockSizes[rm->blockCount - 1]);
-    ex.end +=
+    end +=
 	(int) (MAX_UNALIGNED_PIXEL_LEN / pixelsPerBase) +
 	(int) ((mgFontStringWidth(tl.font, lenLabel) +
 		LABEL_PADDING) / pixelsPerBase);
     }
 else
     {
-    ex.end += rm->blockSizes[rm->blockCount - 1];
+    end += rm->blockSizes[rm->blockCount - 1];
     }
 
-return &ex;
+*pStart = start;
+*pEnd = end;
 }
 
 // A better way to organize the display
 static int cmpRepeatDiv(const void *va, const void *vb)
 /* Sort repeats by divergence. TODO: Another way to do this
    would be to sort by containment.  How many annotations
    are contained within the repeat.  This would require
    a "contained_count" be precalculated for each annotation.
  */
 {
 struct rmskJoined *a = *((struct rmskJoined **) va);
 struct rmskJoined *b = *((struct rmskJoined **) vb);
 
 return (b->score - a->score);
 }
 
 struct repeatItem *classRIList = NULL;
 struct repeatItem *fullRIList = NULL;
 
 static struct repeatItem * makeJRepeatItems()
 /* Initialize the track */
 {
+setWg();
 int i;
 struct repeatItem *ri = NULL;
 
 // Build a permanent hash for mapping
 // class names to colors.  Used by glyph
 // drawing routine.  Also build the
 // classRIList primarily for use in tvDense
 // mode.
 if (!classHash)
     {
     classHash = newHash(6);
     int numClasses = ArraySize(rptClasses);
     for (i = 0; i < numClasses; ++i)
         {
         AllocVar(ri);
@@ -221,208 +272,210 @@
 return classRIList;
 }
 
 
 
 static void rmskJoinedLoadItems(struct track *tg)
 /* We do the query(ies) here so we can report how deep the track(s)
  * will be when rmskJoinedTotalHeight() is called later on.
  */
 {
   /*
    * Initialize the subtracks hash - This will eventually contain
    * all the repeat data for each displayed subtrack.
    */
 
+setWg();
+
 if (!subTracksHash)
     subTracksHash = newHash(20);
 
 //tg->items = makeJRepeatItems();
  makeJRepeatItems();
 
 int baseWidth = winEnd - winStart;
 pixelsPerBase = (float) insideWidth / (float) baseWidth;
 //Disabled
 //if ((tg->visibility == tvFull || tg->visibility == tvSquish ||
 //     tg->visibility == tvPack) && baseWidth <= DETAIL_VIEW_MAX_SCALE)
 if (tg->visibility == tvFull && baseWidth <= DETAIL_VIEW_MAX_SCALE)
     {
     struct repeatItem *ri = NULL;
     struct subTrack *st = NULL;
     AllocVar(st);
     st->levelCount = 0;
     struct rmskJoined *rm = NULL;
     char **row;
     int rowOffset;
     struct sqlConnection *conn = hAllocConn(database);
     struct sqlResult *sr = hRangeQuery(conn, tg->table, chromName,
                                         winStart, winEnd, NULL,
                                         &rowOffset);
     struct rmskJoined *detailList = NULL;
     while ((row = sqlNextRow(sr)) != NULL)
         {
         rm = rmskJoinedLoad(row + rowOffset);
         slAddHead(&detailList, rm);
         }
+    //warn("rmskJoinedLoadItems tg->table=%s chromName=%s winStart=%d winEnd=%d, rowOffset=%d slCount(detailList)=%d", 
+	//tg->table, chromName, winStart, winEnd, rowOffset, slCount(detailList)); // DEBUG REMOVE
     slSort(&detailList, cmpRepeatDiv);
 
     sqlFreeResult(&sr);
     hFreeConn(&conn);
 
-    AllocArray( st->levels, slCount(&detailList));
+    if (detailList)
+	AllocArray( st->levels, slCount(detailList));
 
-    int crChromEnd;
     while (detailList)
         {
         st->levels[st->levelCount++] = detailList;
 
         struct rmskJoined *cr = detailList;
         detailList = detailList->next;
         cr->next = NULL;
+        int crChromStart, crChromEnd;
         int rmChromStart, rmChromEnd;
         struct rmskJoined *prev = NULL;
         rm = detailList;
 
-        struct Extents *ext = NULL;
-        ext = getExtents(cr);
-        crChromEnd = ext->end;
-
+        getExtents(cr, &crChromStart, &crChromEnd);
 
         if ( tg->visibility == tvFull )
 	    {
 	    AllocVar(ri);
 	    ri->className = cr->name;
 	    slAddHead(&fullRIList, ri);
 	    }
 
         //Disabled
         //if ( tg->visibility == tvSquish || tg->visibility == tvPack )
         if ( tg->visibility == tvFull )
              {
              while (rm)
                  {
-                 ext = getExtents(rm);
-                 rmChromStart = ext->start;
-                 rmChromEnd = ext->end;
+                 getExtents(rm, &rmChromStart, &rmChromEnd);
 
                  if (rmChromStart > crChromEnd)
                      {
                      cr->next = rm;
                      cr = rm;
                      crChromEnd = rmChromEnd;
                      if (prev)
                          prev->next = rm->next;
                      else
                          detailList = rm->next;
 
                      rm = rm->next;
                      cr->next = NULL;
                      }
                  else
                      {
                      // Save for a lower level
                      prev = rm;
                      rm = rm->next;
                      }
                  } // while ( rm )
              } // else if ( tg->visibility == tvSquish ...
         } // while ( detailList )
     // Create Hash Entry
+    //warn("rmskJoinedLoadItems tg->table=%s st->levelCount=%d", tg->table, st->levelCount); // DEBUG REMOVE
     hashReplace(subTracksHash, tg->table, st);
     slReverse(&fullRIList);
     tg->items = fullRIList;
     } // if ((tg->visibility == tvFull || ...
 else
    tg->items = classRIList;
+//warn("rmskJoinedLoadItems track=%s vis=%d slCount(tg->items)=%d", tg->track, tg->visibility, slCount(tg->items)); // DEBUG REMOVE
 }
 
 static void rmskJoinedFreeItems(struct track *tg)
 /* Free up rmskJoinedMasker items. */
 {
 slFreeList(&tg->items);
 }
 
 static char * rmskJoinedName(struct track *tg, void *item)
 /* Return name of rmskJoined item track. */
 {
-static char empty = '\0';
 struct repeatItem *ri = item;
   /*
    * In detail view mode the items represent different packing
    * levels.  No need to display a label at each level.  Instead
    * Just return a label for the first level.
    */
 //Disabled
 //if ((tg->visibility == tvSquish ||
 //     tg->visibility == tvPack) && winBaseCount <= DETAIL_VIEW_MAX_SCALE)
 if (tg->visibility == tvFull && winBaseCount <= DETAIL_VIEW_MAX_SCALE)
     {
     if (strcmp(ri->className, "SINE") == 0)
-	return("Repeats");
+	return "Repeats";
     else
-	return &empty;
+	return "";
     }
 return ri->className;
 }
 
 
 int rmskJoinedItemHeight(struct track *tg, void *item)
 {
   // Are we in full view mode and at the scale needed to display
   // the detail view?
 //Disabled
 //if (tg->limitedVis == tvSquish && winBaseCount <= DETAIL_VIEW_MAX_SCALE )
 //    {
 //    if ( tg->heightPer < (MINHEIGHT/2) )
 //      return (MINHEIGHT/2);
 //    else
 //      return tg->heightPer;
 //    }
 //else if ((tg->limitedVis == tvFull || tg->limitedVis == tvPack) && winBaseCount <= DETAIL_VIEW_MAX_SCALE)
 if (tg->limitedVis == tvFull && winBaseCount <= DETAIL_VIEW_MAX_SCALE)
     {
-    if ( tg->heightPer < MINHEIGHT )
-      return MINHEIGHT;
-    else
-      return tg->heightPer;
+    return max(tg->heightPer, MINHEIGHT);
     }
 else
     {
       return tgFixedItemHeight(tg, item);
     }
 }
 
 int rmskJoinedTotalHeight(struct track *tg, enum trackVisibility vis)
 {
+setWg();
+//warn("rmskJoinedTotalHeight track=%s vis=%d winBaseCount=%d", tg->track, vis, winBaseCount); // DEBUG REMOVE
   // Are we in full view mode and at the scale needed to display
   // the detail view?
 //Disabled
 //if ((tg->limitedVis == tvFull || tg->limitedVis == tvSquish ||
 //     tg->limitedVis == tvPack) && winBaseCount <= DETAIL_VIEW_MAX_SCALE)
 if (tg->limitedVis == tvFull && winBaseCount <= DETAIL_VIEW_MAX_SCALE)
     {
     // Lookup the depth of this subTrack and report it
     struct subTrack *st = hashFindVal(subTracksHash, tg->table);
     if (st)
         {
-        tg->height = ((st->levelCount ) * rmskJoinedItemHeight(tg, NULL) );
-        return ((st->levelCount ) * rmskJoinedItemHeight(tg, NULL));
+        tg->height = (st->levelCount * rmskJoinedItemHeight(tg, NULL));
+	//warn("rmskJoinedTotalHeight track=%s st->levelCount=%d tg->height=%d", tg->track, st->levelCount, tg->height); // DEBUG REMOVE
+        return tg->height;
         }
     else
         {
         tg->height = rmskJoinedItemHeight(tg, NULL);
-        return (rmskJoinedItemHeight(tg, NULL));	// Just display one line
+	//warn("rmskJoinedTotalHeight track=%s st is NULL tg->height=%d", tg->track, tg->height); // DEBUG REMOVE
+        return tg->height;	// Just display one line
         }
     }
 else
     {
     if ( vis == tvDense )
       {
       tg->height = tgFixedTotalHeightNoOverflow(tg, tvDense );
       return tgFixedTotalHeightNoOverflow(tg, tvDense );
       }
     else
       {
       tg->height = tgFixedTotalHeightNoOverflow(tg, tvFull );
       return tgFixedTotalHeightNoOverflow(tg, tvFull );
       }
     }
@@ -617,30 +670,31 @@
  *  blockSizes:   As you would expect.
  *
  *  Here is an example:
  *                ie.
  *                A forward strand RM annotation from chr1:186-196
  *                which is aligned to a 100 bp repeat from 75-84
  *                in the consensus would be represented as:
  *
  *                chromStart: 111
  *                chromEnd: 212
  *                blockRelStarts: -1, 75, -1
  *                blockSizes: 75, 10, 16
  *
  */
 {
+setWg();
 int idx;
 int lx1, lx2;
 int cx1, cx2;
 int w;
 struct repeatItem *ri;
   /*
    * heightPer is the God given height of a single
    * track item...respect your overlord.
    */
 int alignedBlockHeight = heightPer * 0.5;
 int alignedBlockOffset = heightPer - alignedBlockHeight;
 int unalignedBlockHeight = heightPer * 0.75;
 int unalignedBlockOffset = heightPer - unalignedBlockHeight;
 
 Color black = hvGfxFindColorIx(hvg, 0, 0, 0);
@@ -1010,30 +1064,31 @@
 		hvGfxLine(hvg, cx2, y + alignedBlockOffset, lx2,
 			   y + unalignedBlockOffset, black);
 		}
 	    }
 	}
     }
 }
 
 static void origRepeatDraw(struct track *tg, int seqStart, int seqEnd,
 		struct hvGfx *hvg, int xOff, int yOff, int width,
 		MgFont * font, Color color, enum trackVisibility vis)
 /* This is the original repeat drawing routine, modified
  * to handle the new rmskJoined table structure.
  */
 {
+setWg();
 int baseWidth = seqEnd - seqStart;
 struct repeatItem *ri;
 int y = yOff;
 int heightPer = tg->heightPer;
 int lineHeight = tg->lineHeight;
 int x1, x2, w;
 Color col;
 struct sqlConnection *conn = hAllocConn(database);
 struct sqlResult *sr = NULL;
 char **row;
 int rowOffset;
 
 //Disabled
 //if (vis == tvFull || vis == tvSquish || vis == tvPack)
 if (vis == tvFull)
@@ -1168,45 +1223,45 @@
 	    }
 	}
     dyStringFree(&query);
     }
 sqlFreeResult(&sr);
 hFreeConn(&conn);
 }
 
 /* Main callback for displaying this track in the viewport
  * of the browser.
  */
 static void rmskJoinedDrawItems(struct track *tg, int seqStart, int seqEnd,
 	     struct hvGfx *hvg, int xOff, int yOff, int width,
 	     MgFont * font, Color color, enum trackVisibility vis)
 {
+setWg();
 int baseWidth = seqEnd - seqStart;
 
 // TODO: Document
 pixelsPerBase = (float) width / (float) baseWidth;
   /*
    * Its unclear to me why heightPer is not updated to the
    * value set in rmskJoinedItemHeight() at the time this callback
    * is invoked.  Getting the correct value myself.
    * was:
    * int heightPer = tg->heightPer;
    */
 int heightPer = rmskJoinedItemHeight(tg, NULL);
 //boolean isFull = (vis == tvFull);
 struct rmskJoined *rm;
-// DEBUG REMOVE
 
   // If we are in full view mode and the scale is sufficient,
   // display the new visualization.
 //Disabled
 //if ((vis == tvFull || vis == tvSquish || vis == tvPack) && baseWidth <= DETAIL_VIEW_MAX_SCALE)
 if (vis == tvFull && baseWidth <= DETAIL_VIEW_MAX_SCALE)
     {
     int level = yOff;
     struct subTrack *st = hashFindVal(subTracksHash, tg->table);
     if (!st)
 	return;
 
     int lidx = st->levelCount;
     int currLevel = 0;
     for (currLevel = 0; currLevel < lidx; currLevel++)