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/hgTracks.c src/hg/hgTracks/hgTracks.c
index 35272f5..370e92c 100644
--- src/hg/hgTracks/hgTracks.c
+++ src/hg/hgTracks/hgTracks.c
@@ -1,16 +1,16 @@
-/* hgTracks - the original, and still the largest module for the UCSC Human Genome
+/* hgTracks - the /riginal, and still the largest module for the UCSC Human Genome
  * Browser main cgi script.  Currently contains most of the track framework, though
  * there's quite a bit of other framework type code in simpleTracks.c.  The main
  * routine got moved to create a new entry point to the bulk of the code for the
  * hgRenderTracks web service.  See mainMain.c for the main used by the hgTracks CGI. */
 
 /* Copyright (C) 2014 The Regents of the University of California 
  * See README in this or parent directory for licensing information. */
 
 #include <pthread.h>
 #include "common.h"
 #include "hCommon.h"
 #include "linefile.h"
 #include "portable.h"
 #include "memalloc.h"
 #include "localmem.h"
@@ -51,30 +51,31 @@
 #include "liftOver.h"
 #include "pcrResult.h"
 #include "jsHelper.h"
 #include "mafTrack.h"
 #include "hgConfig.h"
 #include "encode.h"
 #include "agpFrag.h"
 #include "imageV2.h"
 #include "suggest.h"
 #include "search.h"
 #include "errCatch.h"
 #include "iupac.h"
 #include "botDelay.h"
 #include "chromInfo.h"
 #include "extTools.h"
+#include "basicBed.h"
 #include "customFactory.h"
 
 /* Other than submit and Submit all these vars should start with hgt.
  * to avoid weeding things out of other program's namespaces.
  * Because the browser is a central program, most of its cart
  * variables are not hgt. qualified.  It's a good idea if other
  * program's unique variables be qualified with a prefix though. */
 char *excludeVars[] = { "submit", "Submit", "dirty", "hgt.reset",
             "hgt.in1", "hgt.in2", "hgt.in3", "hgt.inBase",
             "hgt.out1", "hgt.out2", "hgt.out3", "hgt.out4",
             "hgt.left1", "hgt.left2", "hgt.left3",
             "hgt.right1", "hgt.right2", "hgt.right3",
             "hgt.dinkLL", "hgt.dinkLR", "hgt.dinkRL", "hgt.dinkRR",
             "hgt.tui", "hgt.hideAll", "hgt.visAllFromCt",
 	    "hgt.psOutput", "hideControls", "hgt.toggleRevCmplDisp",
@@ -596,134 +597,157 @@
     &&  winStart <= cb->chromEnd)
         {
         safef(startBand, buffSize, "%s", cb->name);
         }
     /* End is > rather than >= due to odditiy in the
        cytoband track where the starts and ends of two
        bands overlaps by one. */
     if (winEnd >  cb->chromStart
     &&  winEnd <= cb->chromEnd)
         {
         safef(endBand, buffSize, "%s", cb->name);
         }
     }
 }
 
-void makeChromIdeoImage(struct track **pTrackList, char *psOutput,
+boolean makeChromIdeoImage(struct track **pTrackList, char *psOutput,
                         struct tempName *ideoTn)
 /* Make an ideogram image of the chromsome and our position in it.  If the
  * ideoTn parameter is not NULL, it is filled in if the ideogram is created. */
 {
 struct track *ideoTrack = NULL;
 MgFont *font = tl.font;
 char *mapName = "ideoMap";
 struct hvGfx *hvg;
 boolean doIdeo = TRUE;
 int ideoWidth = round(.8 *tl.picWidth);
 int ideoHeight = 0;
 int textWidth = 0;
 struct tempName pngTn;
+boolean nukeIdeoFromList = FALSE;
 if (ideoTn == NULL)
     ideoTn = &pngTn;   // not returning value
 
 ideoTrack = chromIdeoTrack(*pTrackList);
 
+//warn("makeChromIdeoImage ideoTrack=%lu ideoTrack->track=%s", (unsigned long) ideoTrack, ideoTrack ? ideoTrack->track : ""); // DEBUG REMOVE
+
 /* If no ideogram don't draw. */
 if(ideoTrack == NULL)
     doIdeo = FALSE;
 else if(trackImgOnly && !ideogramToo)
     {
     doIdeo = FALSE;
     }
 else
     {
+    //warn("makeChromIdeoImage about to remove track from group and tracklist"); // DEBUG REMOVE
     /* Remove the track from the group and track list. */
     removeTrackFromGroup(ideoTrack);
     slRemoveEl(pTrackList, ideoTrack);
+    nukeIdeoFromList = TRUE;
 
     /* Fix for hide all button hiding the ideogram as well. */
     if(withIdeogram && ideoTrack->items == NULL)
 	{
 	ideoTrack->visibility = tvDense;
 	ideoTrack->loadItems(ideoTrack);
 	}
     limitVisibility(ideoTrack);
 
     /* If hidden don't draw. */
     if(ideoTrack->limitedVis == tvHide || !withIdeogram)
         doIdeo = FALSE;
     }
 if(doIdeo)
     {
+    //warn("makeChromIdeoImage doIdeo = TRUE"); // DEBUG REMOVE
     char startBand[16];
     char endBand[16];
-    char title[32];
+    char title[64];  // was 32
     startBand[0] = endBand[0] = '\0';
     fillInStartEndBands(ideoTrack, startBand, endBand, sizeof(startBand));
     /* Start up client side map. */
     if (!psOutput)
         hPrintf("<MAP Name=%s>\n", mapName);
     /* Draw the ideogram. */
     ideoHeight = gfxBorder + ideoTrack->height;
     if (psOutput)
         {
         trashDirFile(ideoTn, "hgtIdeo", "hgtIdeo", ".ps");
         hvg = hvGfxOpenPostScript(ideoWidth, ideoHeight, ideoTn->forCgi);
         }
     else
         {
         trashDirFile(ideoTn, "hgtIdeo", "hgtIdeo", ".png");
         hvg = hvGfxOpenPng(ideoWidth, ideoHeight, ideoTn->forCgi, FALSE);
         }
     hvg->rc = revCmplDisp;
     initColors(hvg);
     ideoTrack->ixColor = hvGfxFindRgb(hvg, &ideoTrack->color);
     ideoTrack->ixAltColor = hvGfxFindRgb(hvg, &ideoTrack->altColor);
     hvGfxSetClip(hvg, 0, gfxBorder, ideoWidth, ideoTrack->height);
-    if (isEmpty(startBand))
+    if (virtMode)
+	{
+    	if (!sameString(virtModeShortDescr,""))
+    	    safef(title, sizeof(title), "%s (%s)", chromName, virtModeShortDescr);
+	else
+    	    safef(title, sizeof(title), "%s (%s)", chromName, virtModeType);
+	}
+    else if (isEmpty(startBand))
         safef(title, sizeof(title), "%s", chromName);
     else if (sameString(startBand, endBand))
         safef(title, sizeof(title), "%s (%s)", chromName, startBand);
     else
         safef(title, sizeof(title), "%s (%s-%s)", chromName, startBand, endBand);
     textWidth = mgFontStringWidth(font, title);
     hvGfxTextCentered(hvg, 2, gfxBorder, textWidth, ideoTrack->height, MG_BLACK, font, title);
-    ideoTrack->drawItems(ideoTrack, winStart, winEnd, hvg, textWidth+4, gfxBorder,
-                         ideoWidth-textWidth-4, font, ideoTrack->ixColor, ideoTrack->limitedVis);
+    // cytoBandDrawAt() clips x based on insideX+insideWidth, 
+    // but in virtMode we may be in a window that is smaller than the ideo width
+    // so temporarily set them to the actual ideo graphic offset and width
+    int saveInsideX = insideX;
+    int saveInsideWidth = insideWidth;
+    insideX = textWidth+4;
+    insideWidth = ideoWidth-insideX;
+    ideoTrack->drawItems(ideoTrack, winStart, winEnd, hvg, insideX, gfxBorder,
+                         insideWidth, font, ideoTrack->ixColor, ideoTrack->limitedVis);
+    insideX = saveInsideX;
+    insideWidth = saveInsideWidth;
     hvGfxUnclip(hvg);
     /* Save out picture and tell html file about it. */
     hvGfxClose(&hvg);
     /* Finish map. */
     if (!psOutput)
         hPrintf("</MAP>\n");
     }
 hPrintf("<TABLE BORDER=0 CELLPADDING=0>");
 if (doIdeo && !psOutput)
     {
     hPrintf("<TR><TD HEIGHT=5></TD></TR>");
     hPrintf("<TR><TD><IMG SRC = \"%s\" BORDER=1 WIDTH=%d HEIGHT=%d USEMAP=#%s id='chrom'>",
             ideoTn->forHtml, ideoWidth, ideoHeight, mapName);
     hPrintf("</TD></TR>");
     hPrintf("<TR><TD HEIGHT=5></TD></TR></TABLE>\n");
     }
 else
     hPrintf("<TR><TD HEIGHT=10></TD></TR></TABLE>\n");
 if(ideoTrack != NULL)
     {
     ideoTrack->limitedVisSet = TRUE;
     ideoTrack->limitedVis = tvHide; /* Don't draw in main gif. */
     }
+return nukeIdeoFromList;
 }
 
 char *pcrResultMapItemName(struct track *tg, void *item)
 /* Stitch accession and display name back together (if necessary). */
 {
 struct linkedFeatures *lf = item;
 return pcrResultItemAccName(lf->name, lf->extra);
 }
 
 void pcrResultLoad(struct track *tg)
 /* Load locations of primer matches into linkedFeatures items. */
 {
 char *pslFileName, *primerFileName;
 struct targetDb *target;
 if (! pcrResultParseCart(database, cart, &pslFileName, &primerFileName, &target))
@@ -1145,31 +1169,30 @@
 struct slList *prev = NULL;
 
 /* for sample tracks */
 double minRangeCutoff, maxRangeCutoff;
 double minRange, maxRange;
 double min0, max0;
 char minRangeStr[32];
 char maxRangeStr[32];
 
 int ymin, ymax;
 int newy;
 char o4[256];
 char o5[256];
 struct slList *item;
 enum trackVisibility vis = track->limitedVis;
-enum trackVisibility savedVis = vis;
 Color labelColor = (track->labelColor ?
                         track->labelColor : track->ixColor);
 int fontHeight = mgFontLineHeight(font);
 int tHeight = trackPlusLabelHeight(track, fontHeight);
 if (vis == tvHide)
     return y;
 
 /*  if a track can do its own left labels, do them after drawItems */
 if (track->drawLeftLabels != NULL)
     return y + tHeight;
 
 /*  Wiggle tracks depend upon clipping.  They are reporting
  *  totalHeight artifically high by 1 so this will leave a
  *  blank area one pixel high below the track.
  */
@@ -1209,30 +1232,33 @@
         samplePrintYAxisLabel( hvg, y+5, track, "5.0", min0, max0 );
         samplePrintYAxisLabel( hvg, y+5, track, "6.0", min0, max0 );
         }
     }
 else
     {
     sprintf( minRangeStr, "%d", (int)round(minRangeCutoff));
     sprintf( maxRangeStr, "%d", (int)round(maxRangeCutoff));
     }
 /* special label handling for wigMaf type tracks -- they
    display a left label in pack mode.  To use the full mode
    labeling, temporarily set visibility to full.
    Restore savedVis later */
 if (startsWith("bigMaf", track->tdb->type) || startsWith("wigMaf", track->tdb->type) || startsWith("maf", track->tdb->type))
     vis = tvFull;
+/* behave temporarily like pack for these */
+if (track->limitedVis == tvFull && isTypeBedLike(track))
+    vis = tvPack;
 
 switch (vis)
     {
     case tvHide:
         break;  /* Do nothing; */
     case tvPack:
     case tvSquish:
 	y += tHeight;
         break;
     case tvFull:
         if (isCenterLabelIncluded(track))
             y += fontHeight;
 
         if( track->subType == lfSubSample && track->items == NULL )
             y += track->height;
@@ -1323,124 +1349,140 @@
             ymax = y - (track->heightPer / 2) + (fontHeight / 2);
             ymin = y + (track->heightPer / 2) - (fontHeight / 2);
             hvGfxTextRight(hvg, leftLabelX, ymin,
                         leftLabelWidth-1, track->lineHeight,
                         track->ixAltColor, font, minRangeStr );
             hvGfxTextRight(hvg, leftLabelX, ymax,
                         leftLabelWidth-1, track->lineHeight,
                         track->ixAltColor, font, maxRangeStr );
             }
         hvGfxTextRight(hvg, leftLabelX, y, leftLabelWidth-1,
                     track->lineHeight, labelColor, font,
                     track->shortLabel);
         y += track->height;
         break;
     }
-/* NOTE: might want to just restore savedVis here for all track types,
-   but I'm being cautious... */
-if (sameString(track->tdb->type, "wigMaf"))
-    vis = savedVis;
 hvGfxUnclip(hvg);
 return y;
 }
 
+void setGlobalsFromWindow(struct window *window);  // FORWARD DECLARATION
+
 static void doLabelNextItemButtons(struct track *track, struct track *parentTrack,
                                    struct hvGfx *hvg, MgFont *font, int y,
                                    int trackPastTabX, int trackPastTabWidth, int fontHeight,
                                    int insideHeight, Color labelColor)
 /* If the track allows label next-item buttons (next gene), draw them. */
 /* The button will cause hgTracks to run again with the additional CGI */
 /* vars nextItem=trackName or prevItem=trackName, which will then  */
 /* signal the browser to find the next thing on the track before it */
 /* does anything else. */
 {
-int portWidth = insideWidth;
-int portX = insideX;
+int portWidth = fullInsideWidth;
+int portX = fullInsideX;
 // If a portal was established, then set the portal dimensions
-int portalStart,chromStart;
+long portalStart,chromStart;
 double basesPerPixel;
+// TODO GALT need to tweak it still for virtchrom stuff, e.g. maybe change some var names or types to long
 if (theImgBox
 && imgBoxPortalDimensions(theImgBox,&chromStart,NULL,NULL,NULL,&portalStart,NULL,
                           &portWidth,&basesPerPixel))
     {
     portX = (int)((portalStart - chromStart) / basesPerPixel);
     portX += gfxBorder;
     if (withLeftLabels)
         portX += tl.leftLabelWidth + gfxBorder;
     portWidth = portWidth-gfxBorder-insideX;
     }
 int arrowWidth = insideHeight;
 int arrowButtonWidth = arrowWidth + 2 * NEXT_ITEM_ARROW_BUFFER;
 int rightButtonX = portX + portWidth - arrowButtonWidth - 1;
 char buttonText[256];
 Color fillColor = lightGrayIndex();
 labelColor = blackIndex();
 hvGfxNextItemButton(hvg, rightButtonX + NEXT_ITEM_ARROW_BUFFER, y, arrowWidth, arrowWidth,
                     labelColor, fillColor, TRUE);
 hvGfxNextItemButton(hvg, portX + NEXT_ITEM_ARROW_BUFFER, y, arrowWidth, arrowWidth,
                     labelColor, fillColor, FALSE);
+
 safef(buttonText, ArraySize(buttonText), "hgt.prevItem=%s", track->track);
+
+// warn("portX=%d y+1=%d portWidth=%d arrowButtonWidth=%d track=%s", portX, y+1, portWidth, arrowButtonWidth, track->track); // DEBUG REMOVE
+
 mapBoxReinvoke(hvg, portX, y + 1, arrowButtonWidth, insideHeight, track, FALSE,
                NULL, 0, 0, (revCmplDisp ? "Next item" : "Prev item"), buttonText);
+
 #ifdef IMAGEv2_SHORT_TOGGLE
 char *label = (theImgBox ? track->longLabel : parentTrack->longLabel);
 int width = portWidth - (2 * arrowButtonWidth);
 int x = portX + arrowButtonWidth;
 // make toggle cover only actual label
 int size = mgFontStringWidth(font,label) + 12;  // get close enough to the label
 if (width > size)
     {
     x += width/2 - size/2;
     width = size;
     }
 mapBoxToggleVis(hvg, x, y + 1, width, insideHeight, (theImgBox ? track : parentTrack));
 #else///ifndef IMAGEv2_SHORT_TOGGLE
+//warn("portX+arrowButtonWidth=%d y+1=%d portWidth-(2*arrowButtonWidth)=%d arrowButtonWidth=%d track=%s", 
+  //portX + arrowButtonWidth, y+1, portWidth - (2 * arrowButtonWidth), arrowButtonWidth, track->track); // DEBUG REMOVE
 mapBoxToggleVis(hvg, portX + arrowButtonWidth, y + 1, portWidth - (2 * arrowButtonWidth),
                 insideHeight, (theImgBox ? track : parentTrack));
 #endif///ndef IMAGEv2_SHORT_TOGGLE
+
+// use the last window globals instead of the first 
+struct window *w=windows;
+while(w->next)
+    w = w->next;
+setGlobalsFromWindow(w); // use last window
+
 safef(buttonText, ArraySize(buttonText), "hgt.nextItem=%s", track->track);
 mapBoxReinvoke(hvg, portX + portWidth - arrowButtonWidth, y + 1, arrowButtonWidth, insideHeight,
                track, FALSE, NULL, 0, 0, (revCmplDisp ? "Prev item" : "Next item"), buttonText);
+
+setGlobalsFromWindow(windows); // restore first window
+
 }
 
 static int doCenterLabels(struct track *track, struct track *parentTrack,
-                                struct hvGfx *hvg, MgFont *font, int y)
+                                struct hvGfx *hvg, MgFont *font, int y, int fullInsideWidth )
 /* Draw center labels.  Return y coord */
 {
 if (track->limitedVis != tvHide)
     {
     if (isCenterLabelIncluded(track))
         {
         int trackPastTabX = (withLeftLabels ? trackTabWidth : 0);
         int trackPastTabWidth = tl.picWidth - trackPastTabX;
         int fontHeight = mgFontLineHeight(font);
         int insideHeight = fontHeight-1;
 	boolean toggleDone = FALSE;
         char *label = track->longLabel;
         Color labelColor = (track->labelColor ?
                             track->labelColor : track->ixColor);
         if (isCenterLabelConditional(track))
             {
             struct trackDb* tdbComposite = tdbGetComposite(track->tdb);
             if (tdbComposite != NULL)
                 {
                 label = tdbComposite->longLabel;
                 labelColor = hvGfxFindColorIx(hvg, tdbComposite->colorR, 
                                                 tdbComposite->colorG, tdbComposite->colorB);
                 }
             }
-        hvGfxTextCentered(hvg, insideX, y+1, insideWidth, insideHeight,
+        hvGfxTextCentered(hvg, insideX, y+1, fullInsideWidth, insideHeight,
                           labelColor, font, label);
         if (track->nextItemButtonable && track->nextPrevItem && !tdbIsComposite(track->tdb))
             {
             if (withNextItemArrows || trackDbSettingOn(track->tdb, "nextItemButton"))
                 {
                 doLabelNextItemButtons(track, parentTrack, hvg, font, y, trackPastTabX,
                                        trackPastTabWidth, fontHeight, insideHeight, labelColor);
                 toggleDone = TRUE;
                 }
             }
         if (!toggleDone)
             {
         #ifdef IMAGEv2_SHORT_TOGGLE
             // make toggle cover only actual label
             int size = mgFontStringWidth(font,label) + 12;  // get close enough to the label
@@ -1630,211 +1672,230 @@
             mapHeight = track->height;
         else
             mapHeight = track->lineHeight;
         int maxWinToDraw = getMaxWindowToDraw(track->tdb);
         if (maxWinToDraw <= 1 || (winEnd - winStart) <= maxWinToDraw)
             mapBoxToggleVis(hvg, trackPastTabX, y, trackPastTabWidth, mapHeight, track);
         y += mapHeight;
         break;
     case tvHide:
     default:
         break;  /* Do nothing; */
     }
 return y;
 }
 
-int computeScaleBar(int numBases, char scaleText[], int scaleTextSize)
+long computeScaleBar(long numBases, char scaleText[], int scaleTextSize)
 /* Do some scalebar calculations and return the number of bases the scalebar will span. */
 {
 char *baseWord = "bases";
-int scaleBases = 0;
+long scaleBases = 0;
 int scaleBasesTextNum = 0;
 int numFigs = (int)log10(numBases);
 int frontNum = (int)(numBases/pow(10,numFigs));
 if (frontNum == 1)
     {
     numFigs--;
     scaleBases = 5 * pow(10, numFigs);
     scaleBasesTextNum = 5 * pow(10, numFigs % 3);
     }
 else if ((frontNum > 1) && (frontNum <= 4))
     {
     scaleBases = pow(10, numFigs);
     scaleBasesTextNum = pow(10, numFigs % 3);
     }
 else if (frontNum > 4)
     {
     scaleBases = 2 * pow(10, numFigs);
     scaleBasesTextNum = 2 * pow(10, numFigs % 3);
     }
 if ((numFigs >= 3) && (numFigs < 6))
     baseWord = "kb";
 else if ((numFigs >= 6) && (numFigs < 9))
     baseWord = "Mb";
+else if ((numFigs >= 9) && (numFigs < 12))
+    baseWord = "Gb";
 safef(scaleText, scaleTextSize, "%d %s", scaleBasesTextNum, baseWord);
 return scaleBases;
 }
 
 enum trackVisibility limitedVisFromComposite(struct track *subtrack)
 /* returns the subtrack visibility which may be limited by composite with multi-view dropdowns. */
 {
 if (tdbIsCompositeChild(subtrack->tdb))
     {
     if (!subtrack->limitedVisSet)
         {
         subtrack->visibility = tdbVisLimitedByAncestors(cart, subtrack->tdb, TRUE, TRUE);
         limitVisibility(subtrack);
         }
     }
 else
     limitVisibility(subtrack);
 return subtrack->limitedVis;
 }
 
-static int makeRulerZoomBoxes(struct hvGfx *hvg, struct cart *cart, int winStart,int winEnd,
-                              int insideWidth,int seqBaseCount,int rulerClickY,
-                              int rulerClickHeight)
-/* Make hit boxes that will zoom program around ruler. */
+static int calcNewWinWidth(struct cart *cart, int winStart, int winEnd, int insideWidth)
+/* Calc width of hit boxes that will zoom program around ruler. */
+// TODO GALT
+// probably should change this to use full and virt vars and make it
+// go across the entire full image width. Especially since this is about
+// zooming on the virt chrom when user clicks on scalebar or chrom-ruler
+// Default is to just zoom current window in by 3x to 1/3 of current width.
 {
-int boxes = 30;
 int winWidth = winEnd - winStart;
 int newWinWidth = winWidth;
-int i, ws, we = 0;
-int mid, ns, ne;
-double wScale = (double)winWidth/boxes;
 char message[32];
 char *zoomType = cartCgiUsualString(cart, RULER_BASE_ZOOM_VAR, ZOOM_3X);
 
 safef(message, sizeof(message), "%s zoom", zoomType);
 if (sameString(zoomType, ZOOM_1PT5X))
     newWinWidth = winWidth/1.5;
 else if (sameString(zoomType, ZOOM_3X))
     newWinWidth = winWidth/3;
 else if (sameString(zoomType, ZOOM_10X))
     newWinWidth = winWidth/10;
 else if (sameString(zoomType, ZOOM_100X))
     newWinWidth = winWidth/100;
 else if (sameString(zoomType, ZOOM_BASE))
     newWinWidth = insideWidth/tl.mWidth;
 else
     errAbort("invalid zoom type %s", zoomType);
 
 if (newWinWidth < 1)
     newWinWidth = 1;
 
-for (i=1; i<=boxes; ++i)
-    {
-    ws = we;
-    we = round(wScale*i);
-    mid = (ws + we)/2 + winStart;
-    ns = mid-newWinWidth/2;
-    ne = ns + newWinWidth;
-    if (ns < 0)
-        {
-        ns = 0;
-        ne -= ns;
+return newWinWidth;
 }
-    if (ne > seqBaseCount)
+
+static void drawScaleBar(
+    struct hvGfx *hvg, 
+    MgFont *font,
+    int fontHeight,
+    int yAfterRuler,
+    int y,
+    int scaleBarTotalHeight
+    )
+/* draws the scale bar */
 {
-        ns -= (ne - seqBaseCount);
-        ne = seqBaseCount;
-        }
+int scaleBarPad = 2;
+int scaleBarHeight = fontHeight;
+
+// can have one for entire multi-window image
+char scaleText[32];
+long numBases = 0;
+struct window *w;
+for (w=windows; w; w=w->next)
+    numBases += (w->winEnd - w->winStart);
+long scaleBases = computeScaleBar(numBases, scaleText, sizeof(scaleText));
+int scalePixels = (int)((double)fullInsideWidth*scaleBases/numBases);
+int scaleBarX = fullInsideX + (int)(((double)fullInsideWidth-scalePixels)/2);
+int scaleBarEndX = scaleBarX + scalePixels;
+int scaleBarY = y + 0.5 * scaleBarTotalHeight;
+hvGfxTextRight(hvg, fullInsideX, y + scaleBarPad,
+	       (scaleBarX-2)-fullInsideX, scaleBarHeight, MG_BLACK, font, scaleText);
+hvGfxLine(hvg, scaleBarX, scaleBarY, scaleBarEndX, scaleBarY, MG_BLACK);
+hvGfxLine(hvg, scaleBarX, y+scaleBarPad, scaleBarX,
+	  y+scaleBarTotalHeight-scaleBarPad, MG_BLACK);
+hvGfxLine(hvg, scaleBarEndX, y+scaleBarPad, scaleBarEndX,
+	  y+scaleBarTotalHeight-scaleBarPad, MG_BLACK);
+if(cartUsualBoolean(cart, BASE_SHOWASM_SCALEBAR, TRUE))
+    {
+    int fHeight = vgGetFontPixelHeight(hvg->vg, font);
+    hvGfxText(hvg, scaleBarEndX + 10,
+	      y + (scaleBarTotalHeight - fHeight)/2 + ((font == mgSmallFont()) ?  1 : 0),
+	      MG_BLACK, font, trackHubSkipHubName(database));
     }
-return newWinWidth;
 }
 
-static int doDrawRuler(struct hvGfx *hvg,int *newWinWidth,int *rulerClickHeight,
+static int doDrawRuler(struct hvGfx *hvg, int *rulerClickHeight,
                        int rulerHeight, int yAfterRuler, int yAfterBases, MgFont *font,
-                       int fontHeight,boolean rulerCds)
+                       int fontHeight, boolean rulerCds, int scaleBarTotalHeight, struct window *window)
 /* draws the ruler. */
 {
-int scaleBarPad = 2;
-int scaleBarHeight = fontHeight;
-int scaleBarTotalHeight = fontHeight + 2 * scaleBarPad;
 int titleHeight = fontHeight;
 int baseHeight = fontHeight;
 //int yAfterBases = yAfterRuler;
 int showPosHeight = fontHeight;
 int codonHeight = fontHeight;
 struct dnaSeq *seq = NULL;
 int rulerClickY = 0;
 *rulerClickHeight = rulerHeight;
 
 int y = rulerClickY;
 hvGfxSetClip(hvg, insideX, y, insideWidth, yAfterRuler-y+1);
 int relNumOff = winStart;
 
 if (baseTitle)
     {
-    hvGfxTextCentered(hvg, insideX, y, insideWidth, titleHeight,MG_BLACK, font, baseTitle);
+    if (window == windows) // first window, only need to do once
+	{
+	hvGfxUnclip(hvg);
+	hvGfxSetClip(hvg, fullInsideX, y, fullInsideWidth, yAfterRuler-y+1);
+	hvGfxTextCentered(hvg, fullInsideX, y, fullInsideWidth, titleHeight,MG_BLACK, font, baseTitle);
+	hvGfxUnclip(hvg);
+	hvGfxSetClip(hvg, insideX, y, insideWidth, yAfterRuler-y+1);
+	}
     *rulerClickHeight += titleHeight;
     y += titleHeight;
     }
 if (baseShowPos||baseShowAsm)
     {
+    if (window == windows) // first window, only need to do once
+	{
+	hvGfxUnclip(hvg);
+	hvGfxSetClip(hvg, fullInsideX, y, fullInsideWidth, yAfterRuler-y+1);
 	char txt[256];
 	char numBuf[SMALLBUF];
 	char *freezeName = NULL;
 	freezeName = hFreezeFromDb(database);
-    sprintLongWithCommas(numBuf, winEnd-winStart);
+	sprintLongWithCommas(numBuf, virtWinEnd-virtWinStart);
 	if (freezeName == NULL)
 	    freezeName = cloneString("Unknown");
 	if (baseShowPos&&baseShowAsm)
 	    safef(txt,sizeof(txt),"%s %s   %s (%s bp)",trackHubSkipHubName(organism),
 		    freezeName, addCommasToPos(database, position), numBuf);
 	else if (baseShowPos)
 	    safef(txt,sizeof(txt),"%s (%s bp)",addCommasToPos(database, position),numBuf);
 	else
 	    safef(txt,sizeof(txt),"%s %s",trackHubSkipHubName(organism),freezeName);
-    hvGfxTextCentered(hvg, insideX, y, insideWidth, showPosHeight,MG_BLACK, font, txt);
-    *rulerClickHeight += showPosHeight;
 	freez(&freezeName);
+	hvGfxTextCentered(hvg, fullInsideX, y, fullInsideWidth, showPosHeight,MG_BLACK, font, txt);
+	hvGfxUnclip(hvg);
+	hvGfxSetClip(hvg, insideX, y, insideWidth, yAfterRuler-y+1);
+	}
+    *rulerClickHeight += showPosHeight;
     y += showPosHeight;
     }
 if (baseShowScaleBar)
     {
-    char scaleText[32];
-    int numBases = winEnd-winStart;
-    int scaleBases = computeScaleBar(numBases, scaleText, sizeof(scaleText));
-    int scalePixels = (int)((double)insideWidth*scaleBases/numBases);
-    int scaleBarX = insideX + (int)(((double)insideWidth-scalePixels)/2);
-    int scaleBarEndX = scaleBarX + scalePixels;
-    int scaleBarY = y + 0.5 * scaleBarTotalHeight;
-    *rulerClickHeight += scaleBarTotalHeight;
-    hvGfxTextRight(hvg, insideX, y + scaleBarPad,
-                   (scaleBarX-2)-insideX, scaleBarHeight, MG_BLACK, font, scaleText);
-    hvGfxLine(hvg, scaleBarX, scaleBarY, scaleBarEndX, scaleBarY, MG_BLACK);
-    hvGfxLine(hvg, scaleBarX, y+scaleBarPad, scaleBarX,
-              y+scaleBarTotalHeight-scaleBarPad, MG_BLACK);
-    hvGfxLine(hvg, scaleBarEndX, y+scaleBarPad, scaleBarEndX,
-              y+scaleBarTotalHeight-scaleBarPad, MG_BLACK);
-    if(cartUsualBoolean(cart, BASE_SHOWASM_SCALEBAR, TRUE))
+    if (window == windows) // first window
 	{
-        int fHeight = vgGetFontPixelHeight(hvg->vg, font);
-        hvGfxText(hvg, scaleBarEndX + 10,
-                  y + (scaleBarTotalHeight - fHeight)/2 + ((font == mgSmallFont()) ?  1 : 0),
-                  MG_BLACK, font, trackHubSkipHubName(database));
+	hvGfxUnclip(hvg);
+	hvGfxSetClip(hvg, fullInsideX, y, fullInsideWidth, yAfterRuler-y+1);
+	drawScaleBar(hvg, font, fontHeight, yAfterRuler, y, scaleBarTotalHeight);
+	hvGfxUnclip(hvg);
+	hvGfxSetClip(hvg, insideX, y, insideWidth, yAfterRuler-y+1);
 	}
     y += scaleBarTotalHeight;
+    *rulerClickHeight += scaleBarTotalHeight;
     }
-if (baseShowRuler)
+if (baseShowRuler && (insideWidth >=36))
     {
     hvGfxDrawRulerBumpText(hvg, insideX, y, rulerHeight, insideWidth, MG_BLACK,
                            font, relNumOff, winBaseCount, 0, 1);
     }
-*newWinWidth = makeRulerZoomBoxes(hvg, cart,winStart,winEnd,insideWidth,seqBaseCount,
-                                  rulerClickY,*rulerClickHeight);
 
 if (zoomedToBaseLevel || rulerCds)
     {
     Color baseColor = MG_BLACK;
     int start, end, chromSize;
     struct dnaSeq *extraSeq;
     /* extraSeq has extra leading & trailing bases
     * for translation in to amino acids */
     boolean complementRulerBases = cartUsualBooleanDb(cart, database, COMPLEMENT_BASES_VAR, FALSE);
     // gray bases if not matching the direction of display
     if (complementRulerBases != revCmplDisp)
 	baseColor = MG_GRAY;
 
     /* get sequence, with leading & trailing 3 bases
      * used for amino acid translation */
@@ -1856,30 +1917,31 @@
 	}
 
     /* for drawing bases, must clip off leading and trailing 3 bases */
     seq = cloneDnaSeq(extraSeq);
     seq = newDnaSeq(seq->dna+3, seq->size-6, seq->name);
 
     if (zoomedToBaseLevel)
         drawBases(hvg, insideX, y+rulerHeight, insideWidth, baseHeight,
                   baseColor, font, complementRulerBases, seq);
 
     /* set up clickable area to toggle ruler visibility */
         {
         char newRulerVis[100];
 	safef(newRulerVis, 100, "%s=%s", RULER_TRACK_NAME,
               rulerMode == tvFull ? rulerMenu[tvDense] : rulerMenu[tvFull]);
+	// GALT TODO should this be fullInsideX and fullInsideWidth?
 	mapBoxReinvoke(hvg, insideX, y+rulerHeight, insideWidth,baseHeight, NULL,
 	    FALSE, NULL, 0, 0, "", newRulerVis);
 	}
     if (rulerCds)
 	{
 	/* display codons */
 	int frame;
 	int firstFrame = 0;
 	int mod;            // for determining frame ordering on display
 	struct simpleFeature *sfList;
 	double scale = scaleForWindow(insideWidth, winStart, winEnd);
 
 	/* WARNING: tricky code to assure that an amino acid
 	 * stays in the same frame line on the browser during panning.
 	 * There may be a simpler way... */
@@ -1962,68 +2024,112 @@
 
 dyStringFree(&dy);
 }
 
 static void rAddToTrackHash(struct hash *trackHash, struct track *trackList)
 /* Add list and any children of list to hash. */
 {
 struct track *track;
 for (track = trackList; track != NULL; track = track->next)
     {
     hashAddUnique(trackHash, track->track, track);
     rAddToTrackHash(trackHash, track->subtracks);
     }
 }
 
-void highlightRegion(struct cart *cart, struct hvGfx *hvg, int insideX, int imagePixelHeight,
-                       int winStart, int winEnd)
+struct highlightVar
+// store highlight information
+{
+char *db;
+char *chrom;
+long chromStart;
+long chromEnd;
+char *hexColor;
+};
+
+struct highlightVar *parseHighlightInfo()
+// Parse highlight info from cart var
+// db.chrom:start-end#hexColor
+{
+struct highlightVar *h = NULL;
+char *highlightDef = cartOptionalString(cart, "highlight");
+//warn("parseHighlightInfo highlight=%s", highlightDef); // DEBUG REMOVE
+if(highlightDef)
+    {
+    AllocVar(h);
+    h->db     = cloneNextWordByDelimiter(&highlightDef,'.');
+    h->chrom  = cloneNextWordByDelimiter(&highlightDef,':');
+    // long to handle virt chrom coordinates
+    h->chromStart = atol(cloneNextWordByDelimiter(&highlightDef,'-'));
+    h->chromEnd   = atol(cloneNextWordByDelimiter(&highlightDef,'#'));
+    h->chromStart--; // Not zero based
+    if (highlightDef && *highlightDef != '\0')
+	h->hexColor = cloneString(highlightDef);
+    //verbose(1, "DEBUG GALT highlightRegion() db=%s chrom=%s chromStart=%ld chromEnd=%ld virtChromName=%s winStart=%ld winEnd=%ld\n",
+	//h->db, h->chrom, h->chromStart, h->chromEnd, virtChromName, virtWinStart, virtWinEnd); // DEBUG REMOVE
+    }
+return h;
+}
+
+void highlightRegion(struct cart *cart, struct hvGfx *hvg, int imagePixelHeight)
 // Highlights a region in the image.  Only done if theImgBox is not defined.
 // Thus it is done for ps/pdf and view image but the hgTracks image is highlighted via js
 {
-char *highlightDef = cartOptionalString(cart, "highlight");
-if(highlightDef && theImgBox == NULL) // Only highlight region when imgBox is not used.
-    {
-    // expect db.chrom:start-end#hexColor
-    char *db     = cloneNextWordByDelimiter(&highlightDef,'.');
-    char *chrom  = cloneNextWordByDelimiter(&highlightDef,':');
-    int chromStart = atoi(cloneNextWordByDelimiter(&highlightDef,'-'));
-    int chromEnd   = atoi(cloneNextWordByDelimiter(&highlightDef,'#'));
-    if ((db    != NULL && sameString(db,   database ))
-    &&  (chrom != NULL && sameString(chrom,chromName))
-    &&  (chromEnd != 0)
-    &&  (chromStart <= winEnd && chromEnd >= winStart))
-        {
-        chromStart--; // Not zero based
-        chromStart = max(chromStart, winStart);
-        chromEnd = min(chromEnd, winEnd);
-        double pixelsPerBase = scaleForPixels(insideWidth + 1);
-        int startPixels = pixelsPerBase * (chromStart - winStart); // floor
+struct highlightVar *h = parseHighlightInfo();
+
+if(h && theImgBox == NULL) // Only highlight region when imgBox is not used. (pdf and show-image)
+    {
+    if (virtualSingleChrom()) // DISGUISE VMODE
+	{
+	if ((h->db && sameString(h->db, database))
+	&&  (h->chrom && sameString(h->chrom,chromName)))
+	    {
+	    char position[1024];
+	    safef(position, sizeof position, "%s:%ld-%ld", h->chrom, h->chromStart, h->chromEnd);
+	    char *newPosition = undisguisePosition(position); // UN-DISGUISE VMODE
+	    if (startsWith("virt:", newPosition))
+		{
+		parseVPosition(newPosition, &h->chrom, &h->chromStart, &h->chromEnd);
+		}
+	    }	    
+	}
+
+    if ((h->db && sameString(h->db, database))
+    &&  (h->chrom && sameString(h->chrom,virtChromName))
+    &&  (h->chromEnd != 0)
+    &&  (h->chromStart <= virtWinEnd && h->chromEnd >= virtWinStart))
+        {
+
+        h->chromStart = max(h->chromStart, virtWinStart);
+        h->chromEnd = min(h->chromEnd, virtWinEnd);
+        double pixelsPerBase = (double)fullInsideWidth/(virtWinEnd - virtWinStart);
+        int startPixels = pixelsPerBase * (h->chromStart - virtWinStart); // floor
         if (startPixels < 0)
             startPixels *= -1;  // reverse complement
-        int width = pixelsPerBase * (double)(chromEnd - chromStart) + 0.5; // round up
+        int width = pixelsPerBase * (double)(h->chromEnd - h->chromStart) + 0.5; // round up
         if (width < 2)
             width = 2;
 
         // Default color to light blue, but if setting has color, use it.
         unsigned int hexColor = MAKECOLOR_32(170, 255, 255);
-        if (highlightDef != NULL && *highlightDef != '\0')
+        if (h->hexColor)
             {
-            long rgb = strtol(highlightDef,NULL,16); // Big and little Endians
+            long rgb = strtol(h->hexColor,NULL,16); // Big and little Endians
             hexColor = MAKECOLOR_32( ((rgb>>16)&0xff), ((rgb>>8)&0xff), (rgb&0xff) );
             }
 
-        hvGfxBox(hvg, insideX + startPixels, 0, width, imagePixelHeight,hexColor);
+        hvGfxBox(hvg, fullInsideX + startPixels, 0, width, imagePixelHeight, hexColor);
         }
     }
 }
 
 struct hash *makeGlobalTrackHash(struct track *trackList)
 /* Create a global track hash and returns a pointer to it. */
 {
 trackHash = newHash(8);
 rAddToTrackHash(trackHash, trackList);
 return trackHash;
 }
 
 //void domAddMenu(char *afterMenuId, char *newMenuId, char *label) 
 ///* Append a new drop down menu after a given menu, by changing the DOM with jquery  */
 //{
@@ -2041,300 +2147,2698 @@
 
 //void menuBarAppendExtTools() 
 ///* printf a little javascript that adds entries to a menu */
 //{
 //    char url[SMALLBUF];
 //    safef(url,ArraySize(url),"hgTracks?%s=%s&hgt.redirectTool=crispor",cartSessionVarName(), cartSessionId(cart));
 //    printf("<script>\n");
 //    printf("jQuery(document).ready( function() {\n");
 //    domAddMenu("view", "sendto", "Send to");
 //    domAppendToMenu("sendto", url, "Tefor CRISPR sites");
 //    printf("});\n");
 //    printf("</script>\n");
 //}
 
 
-void makeActiveImage(struct track *trackList, char *psOutput)
-/* Make image and image map. */
-{
-struct track *track;
-MgFont *font = tl.font;
-struct hvGfx *hvg;
-struct tempName pngTn;
-char *mapName = "map";
-int fontHeight = mgFontLineHeight(font);
-int trackPastTabX = (withLeftLabels ? trackTabWidth : 0);
-int trackTabX = gfxBorder;
-int trackPastTabWidth = tl.picWidth - trackPastTabX;
-int pixWidth, pixHeight;
-int y=0;
-int titleHeight = fontHeight;
-int scaleBarPad = 2;
-int scaleBarHeight = fontHeight;
-int scaleBarTotalHeight = fontHeight + 2 * scaleBarPad;
-int showPosHeight = fontHeight;
-int rulerHeight = fontHeight;
-int baseHeight = fontHeight;
-int basePositionHeight = rulerHeight;
-int codonHeight = fontHeight;
-int rulerTranslationHeight = codonHeight * 3;        // 3 frames
-int yAfterRuler = gfxBorder;
-int yAfterBases = yAfterRuler;  // differs if base-level translation shown
-boolean rulerCds = zoomedToCdsColorLevel;
-int rulerClickHeight = 0;
-int newWinWidth = 0;
-
-/* Figure out dimensions and allocate drawing space. */
-pixWidth = tl.picWidth;
-
-leftLabelX = gfxBorder;
-leftLabelWidth = insideX - gfxBorder*3;
-
-static struct image *theOneImg  = NULL; // No need to be global, only the map needs to be global
-static struct image *theSideImg = NULL; // Because dragScroll drags off end of image,
-                                 //    the side label gets seen. Therefore we need 2 images!!
-static struct imgSlice *curSlice    = NULL; // No need to be global, only the map needs to be global
+//--- GALT
 
-// Set up imgBox dimensions
-int sliceWidth[stMaxSliceTypes]; // Just being explicit
-int sliceOffsetX[stMaxSliceTypes];
-int sliceHeight        = 0;
-int sliceOffsetY       = 0;
-char *rulerTtl = NULL;
-if (theImgBox)
-// theImgBox is a global for now to avoid huge rewrite of hgTracks.  It is started
-// prior to this in doTrackForm()
-    {
-    hPrintf("<input type='hidden' name='db' value='%s'>\n", database);
-    hPrintf("<input type='hidden' name='c' value='%s'>\n", chromName);
-    hPrintf("<input type='hidden' name='l' value='%d'>\n", winStart);
-    hPrintf("<input type='hidden' name='r' value='%d'>\n", winEnd);
-    hPrintf("<input type='hidden' name='pix' value='%d'>\n", tl.picWidth);
-    // If a portal was established, then set the global dimensions to the entire image size
-    if (imgBoxPortalDimensions(theImgBox,&winStart,&winEnd,&(tl.picWidth),NULL,NULL,NULL,NULL,NULL))
+char *rgbColorToString(struct rgbColor color)
+/* make rgbColor into printable string */
 {
-        pixWidth = tl.picWidth;
-        winBaseCount = winEnd - winStart;
-        insideWidth = tl.picWidth-gfxBorder-insideX;
+char buf[256];
+safef(buf, sizeof buf, "rgbColor r:%d g:%d b:%d", color.r, color.g, color.b);
+return cloneString(buf);
 }
-    memset((char *)sliceWidth,  0,sizeof(sliceWidth));
-    memset((char *)sliceOffsetX,0,sizeof(sliceOffsetX));
-    if (withLeftLabels)
+
+char *trackDumpString(struct track *track)
+/* Write out info on track to string */
 {
-        sliceWidth[stButton]   = trackTabWidth + 1;
-        sliceWidth[stSide]     = leftLabelWidth - sliceWidth[stButton] + 1;
-        sliceOffsetX[stSide]   =
-                          (revCmplDisp ? (tl.picWidth - sliceWidth[stSide] - sliceWidth[stButton])
-                                       : sliceWidth[stButton]);
-        sliceOffsetX[stButton] = (revCmplDisp ? (tl.picWidth - sliceWidth[stButton]) : 0);
-        }
-    sliceOffsetX[stData] = (revCmplDisp ? 0 : sliceWidth[stSide] + sliceWidth[stButton]);
-    sliceWidth[stData]   = tl.picWidth - (sliceWidth[stSide] + sliceWidth[stButton]);
+struct dyString *dy = dyStringNew(256);
+
+dyStringPrintf(dy, "next: %lu\n", (unsigned long)track->next);
+//    struct track *next;   /* Next on list. */
+//
+dyStringPrintf(dy, "track: %s\n", track->track);
+    //char *track;             /* Track symbolic name. Name on image map etc. Same as tdb->track. */
+dyStringPrintf(dy, "table: %s\n", track->table);
+    //char *table;             /* Table symbolic name. Name of database table. Same as tdb->table.*/
+dyStringPrintf(dy, "visibility: %s\n", hStringFromTv(track->visibility));
+    //enum trackVisibility visibility; /* How much of this want to see. */
+dyStringPrintf(dy, "limitedVis: %s\n", hStringFromTv(track->limitedVis));
+    //enum trackVisibility limitedVis; /* How much of this actually see. */
+dyStringPrintf(dy, "limitedVisSet: %d\n", track->limitedVisSet);
+    //boolean limitedVisSet;	     /* Is limited visibility set? */
+
+dyStringPrintf(dy, "longLabel: %s\n", track->longLabel);
+    //char *longLabel;           /* Long label to put in center. */
+dyStringPrintf(dy, "shortLabel: %s\n", track->shortLabel);
+    //char *shortLabel;          /* Short label to put on side. */
+
+dyStringPrintf(dy, "mapsSelf: %d\n", track->mapsSelf);
+    //bool mapsSelf;          /* True if system doesn't need to do map box. */
+dyStringPrintf(dy, "drawName: %d\n", track->drawName);
+    //bool drawName;          /* True if BED wants name drawn in box. */
+
+dyStringPrintf(dy, "colorShades: %lu\n", (unsigned long)track->colorShades);
+    //Color *colorShades;	       /* Color scale (if any) to use. */
+dyStringPrintf(dy, "color: %s\n", rgbColorToString(track->color));
+    //struct rgbColor color;     /* Main color. */
+dyStringPrintf(dy, "ixColor: %u\n", track->ixColor);
+    //Color ixColor;             /* Index of main color. */
+dyStringPrintf(dy, "altColorShades: %lu\n", (unsigned long)track->altColorShades);
+    //Color *altColorShades;     /* optional alternate color scale */
+dyStringPrintf(dy, "altColor: %s\n", rgbColorToString(track->altColor));
+    //struct rgbColor altColor;  /* Secondary color. */
+dyStringPrintf(dy, "ixAltColor: %u\n", track->ixAltColor);
+    //Color ixAltColor;
+
+    //void (*loadItems)(struct track *tg);
+    /* loadItems loads up items for the chromosome range indicated.   */
+
+dyStringPrintf(dy, "items: %lu\n", (unsigned long) track->items);
+    //void *items;               /* Some type of slList of items. */
+
+    //char *(*itemName)(struct track *tg, void *item);
+    /* Return name of one of an item to display on left side. */
+
+    //char *(*mapItemName)(struct track *tg, void *item);
+    /* Return name to associate on map. */
+
+    //int (*totalHeight)(struct track *tg, enum trackVisibility vis);
+	/* Return total height. Called before and after drawItems.
+	 * Must set the following variables. */
+dyStringPrintf(dy, "height: %d\n", track->height);
+    //int height;                /* Total height - must be set by above call. */
+dyStringPrintf(dy, "lineHeight: %d\n", track->lineHeight);
+    //int lineHeight;            /* Height per item line including border. */
+dyStringPrintf(dy, "heightPer: %d\n", track->heightPer);
+    //int heightPer;             /* Height per item line minus border. */
+
+    //int (*itemHeight)(struct track *tg, void *item);
+    /* Return height of one item. */
+
+    //int (*itemRightPixels)(struct track *tg, void *item);
+    /* Return number of pixels needed to right of item for additional labeling. (Optional) */
+
+    //void (*drawItems)(struct track *tg, int seqStart, int seqEnd,
+	//struct hvGfx *hvg, int xOff, int yOff, int width,
+	//MgFont *font, Color color, enum trackVisibility vis);
+    /* Draw item list, one per track. */
+
+    //void (*drawItemAt)(struct track *tg, void *item, struct hvGfx *hvg,
+        //int xOff, int yOff, double scale,
+	//MgFont *font, Color color, enum trackVisibility vis);
+    /* Draw a single option.  This is optional, but if it's here
+     * then you can plug in genericDrawItems into the drawItems,
+     * which takes care of all sorts of things including packing. */
+
+    //int (*itemStart)(struct track *tg, void *item);
+    /* Return start of item in base pairs. */
+
+    //int (*itemEnd)(struct track *tg, void *item);
+    /* Return end of item in base pairs. */
+
+    //void (*freeItems)(struct track *tg);
+    /* Free item list. */
+
+    //Color (*itemColor)(struct track *tg, void *item, struct hvGfx *hvg);
+    /* Get color of item (optional). */
+
+    //Color (*itemNameColor)(struct track *tg, void *item, struct hvGfx *hvg);
+    /* Get color for the item's name (optional). */
+
+    //Color (*itemLabelColor)(struct track *tg, void *item, struct hvGfx *hvg);
+    /* Get color for the item's label (optional). */
+
+    //void (*mapItem)(struct track *tg, struct hvGfx *hvg, void *item,
+                    //char *itemName, char *mapItemName, int start, int end,
+                    //int x, int y, int width, int height);
+    /* Write out image mapping for a given item */
+
+dyStringPrintf(dy, "hasUi: %d\n", track->hasUi);
+    //boolean hasUi;	/* True if has an extended UI page. */
+dyStringPrintf(dy, "wigCartData: %lu\n", (unsigned long)track->wigCartData);
+    //void *wigCartData;  /* pointer to wigCart */
+dyStringPrintf(dy, "extraUiData: %lu\n", (unsigned long)track->extraUiData);
+    //void *extraUiData;	/* Pointer for track specific filter etc. data. */
+
+    //void (*trackFilter)(struct track *tg);
+    /* Stuff to handle user interface parts. */
+
+dyStringPrintf(dy, "customPt: %lu\n", (unsigned long)track->customPt);
+    //void *customPt;  /* Misc pointer variable unique to track. */
+dyStringPrintf(dy, "customInt: %d\n", track->customInt);
+    //int customInt;   /* Misc int variable unique to track. */
+dyStringPrintf(dy, "subType: %d\n", track->subType);
+    //int subType;     /* Variable to say what subtype this is for similar tracks to share code. */
+
+    /* Stuff for the various wig incarnations - sample, wig, bigWig */
+dyStringPrintf(dy, "minRange: %.2f, maxRange: %.2f\n", track->minRange, track->maxRange);
+    //float minRange, maxRange;	  /*min and max range for sample tracks 0.0 to 1000.0*/
+dyStringPrintf(dy, "scaleRange: %.2f\n", track->scaleRange);
+    //float scaleRange;             /* What to scale samples by to get logical 0-1 */
+dyStringPrintf(dy, "graphUpperLimit: %e, grapLowerLimit: %e\n", track->graphUpperLimit, track->graphLowerLimit);
+    //double graphUpperLimit, graphLowerLimit;	/* Limits of actual data in window for wigs. */
+dyStringPrintf(dy, "preDrawContainer: %lu\n", (unsigned long)track->preDrawContainer);
+    //struct preDrawContainer *preDrawContainer;  /* Numbers to graph in wig, one per pixel */
+    //struct preDrawContainer *(*loadPreDraw)(struct track *tg, int seqStart, int seqEnd, int width);
+dyStringPrintf(dy, "wigGraphOutput: %lu\n", (unsigned long)track->wigGraphOutput);
+    //struct wigGraphOutput *wigGraphOutput;  /* Where to draw wig - different for transparency */
+    /* Do bits that load the predraw buffer.  Called to set preDrawContainer */
+
+dyStringPrintf(dy, "bbiFile: %lu\n", (unsigned long)track->bbiFile);
+    //struct bbiFile *bbiFile;	/* Associated bbiFile for bigWig or bigBed. */
+
+dyStringPrintf(dy, "bedSize: %d\n", track->bedSize);
+    //int bedSize;		/* Number of fields if a bed file. */
+dyStringPrintf(dy, "isBigBed: %d\n", track->isBigBed);
+    //boolean isBigBed;		/* If a bed, is it a bigBed? */
+
+dyStringPrintf(dy, "isRemoteSql: %d\n", track->isRemoteSql);
+    //boolean isRemoteSql;	/* Is using a remote mySQL connection. */
+dyStringPrintf(dy, "remoteSqlHost: %s\n", track->remoteSqlHost);
+    //char *remoteSqlHost;	/* Host machine name for remote DB. */
+dyStringPrintf(dy, "remoteSqlUser: %s\n", track->remoteSqlUser);
+    //char *remoteSqlUser;	/* User name for remote DB. */
+dyStringPrintf(dy, "remoteSqlPassword: %s\n", track->remoteSqlPassword);
+    //char *remoteSqlPassword;	/* Password for remote DB. */
+dyStringPrintf(dy, "remoteSqlDatabase: %s\n", track->remoteSqlDatabase);
+    //char *remoteSqlDatabase;	/* Database in remote DB. */
+dyStringPrintf(dy, "remoteSqlTable: %s\n", track->remoteSqlTable);
+    //char *remoteSqlTable;	/* Table name in remote DB. */
+
+dyStringPrintf(dy, "otherDb: %s\n", track->otherDb);
+    //char *otherDb;		/* Other database for an axt track. */
+
+dyStringPrintf(dy, "private: %d\n", track->private);
+    //unsigned short private;	/* True(1) if private, false(0) otherwise. */
+dyStringPrintf(dy, "priority: %.2f\n", track->priority);
+    //float priority;   /* Tracks are drawn in priority order. */
+dyStringPrintf(dy, "defaultPriority: %.2f\n", track->defaultPriority);
+    //float defaultPriority;   /* Tracks are drawn in priority order. */
+dyStringPrintf(dy, "groupName: %s\n", track->groupName);
+    //char *groupName;	/* Name of group if any. */
+dyStringPrintf(dy, "group: %lu\n", (unsigned long)track->group);
+    //struct group *group;  /* Group this track is associated with. */
+dyStringPrintf(dy, "defaultGroupName: %s\n", track->defaultGroupName);
+    //char *defaultGroupName;  /* default Group this track is associated with. */
+dyStringPrintf(dy, "canPack: %d\n", track->canPack);
+    //boolean canPack;	/* Can we pack the display for this track? */
+dyStringPrintf(dy, "spaceSaver: %lu\n", (unsigned long)track->ss);
+    //struct spaceSaver *ss;  /* Layout when packed. */
+
+dyStringPrintf(dy, "tdb: %lu\n", (unsigned long)track->tdb);
+    //struct trackDb *tdb; /*todo:change visibility, etc. to use this */
+// ADDED by GALT
+if (track->tdb)
+    dyStringPrintf(dy, "tdb settings:\n%s\n", track->tdb->settings);
+
+dyStringPrintf(dy, "expScale: %.2f\n", track->expScale);
+    //float expScale;	/* What to scale expression tracks by. */
+dyStringPrintf(dy, "expTable: %s\n", track->expTable);
+    //char *expTable;	/* Expression table in hgFixed. */
+
+    // factorSource
+dyStringPrintf(dy, "sourceCount: %d\n", track->sourceCount);
+    //int sourceCount;	/* Number of sources for factorSource tracks. */
+dyStringPrintf(dy, "sources: %lu\n", (unsigned long)track->sources);
+    //struct expRecord **sources;  /* Array of sources */
+dyStringPrintf(dy, "sourceRightPixels: %d\n", track->sourceRightPixels);
+    //int sourceRightPixels;	/* Number of pixels to right we'll need. */
+
+    // exon/Next
+dyStringPrintf(dy, "exonArrows: %d\n", track->exonArrows);
+    //boolean exonArrows;	/* Draw arrows on exons? */
+dyStringPrintf(dy, "exonArrowsAlways: %d\n", track->exonArrowsAlways);
+    //boolean exonArrowsAlways;	/* Draw arrows on exons even with introns showing? */
+dyStringPrintf(dy, "nextExonButtonable: %d\n", track->nextExonButtonable);
+    //boolean nextExonButtonable; /* Use the next-exon buttons? */
+dyStringPrintf(dy, "nextItemButtonable: %d\n", track->nextItemButtonable);
+    //boolean nextItemButtonable; /* Use the next-gene buttons? */
+    
+dyStringPrintf(dy, "itemAttrTbl: %lu\n", (unsigned long)track->itemAttrTbl);
+    //struct itemAttrTbl *itemAttrTbl;  /* relational attributes for specific items (color) */
+
+    /* fill in left label drawing area */
+dyStringPrintf(dy, "labelColor: %u\n", track->labelColor);
+    //Color labelColor;   /* Fixed color for the track label (optional) */
+    
+    //void (*drawLeftLabels)(struct track *tg, int seqStart, int seqEnd,
+    //                       struct hvGfx *hvg, int xOff, int yOff, int width, int height,
+    //                       boolean withCenterLabels, MgFont *font,
+    //                       Color color, enum trackVisibility vis);
+
+dyStringPrintf(dy, "subtracks: %lu\n", (unsigned long)track->subtracks);
+    //struct track *subtracks;   /* list of subsidiary tracks that are
+                                //loaded and drawn by this track.  This
+                                //is used for "composite" tracks, such
+                                //as "mafWiggle */
+dyStringPrintf(dy, "parent: %lu\n", (unsigned long)track->parent);
+    //struct track *parent;	/* Parent track if any */
+dyStringPrintf(dy, "prevTrack: %lu\n", (unsigned long)track->prevTrack);
+    //struct track *prevTrack;    // if not NULL, points to track immediately above in the image.
+                                //    Needed by ConditionalCenterLabel logic
+
+    //void (*nextPrevExon)(struct track *tg, struct hvGfx *hvg, void *item, int x, int y, int w, int h, boolean next);
+    /* Function will draw the button on a track item and assign a map */
+    /* box to it as well, so that a click will move the browser window */
+    /* to the next (or previous if next==FALSE) item. This is meant to */
+    /* jump to parts of an item already partially in the window but is */
+    /* hanging off the edge... e.g. the next exon in a gene. */
+
+    //void (*nextPrevItem)(struct track *tg, boolean next);
+    /* If this function is given, it can dictate where the browser loads */
+    /* up based on whether a next-item button on the longLabel line of */
+    /* the track was pressed (as opposed to the next-item buttons on the */
+    /* track items themselves... see nextPrevExon() ). This is meant for */
+    /* going to the next/previous item currently unseen in the browser, */
+    /* e.g. the next gene. SO FAR THIS IS UNIMPLEMENTED. */
+
+    //char *(*itemDataName)(struct track *tg, char *itemName);
+    /* If not NULL, function to translated an itemName into a data name.
+     * This is can be used for looking up sequence, CDS, etc. It is used
+     * to support item names that have uniqueness identifiers added to deal
+     * with multiple alignments.  The resulting value should *not* be freed,
+     * and it should be assumed that it might only remain valid for a short
+     * period of time.*/
+
+dyStringPrintf(dy, "loadTime: %d\n", track->loadTime);
+    //int loadTime;	/* Time it takes to load (for performance tuning) */
+dyStringPrintf(dy, "drawTime: %d\n", track->drawTime);
+    //int drawTime;	/* Time it takes to draw (for performance tuning) */
+
+dyStringPrintf(dy, "remoteDataSource: %d\n", track->remoteDataSource);
+    //enum enumBool remoteDataSource; /* The data for this track is from a remote source */
+                   /* Slow retrieval means image can be rendered via an AJAX callback. */
+dyStringPrintf(dy, "customTrack: %d\n", track->customTrack);
+    //boolean customTrack; /* Need to explicitly declare this is a custom track */
+dyStringPrintf(dy, "syncChildVisToSelf: %d\n", track->syncChildVisToSelf);
+    //boolean syncChildVisToSelf;	/* If TRUE sync visibility to of children to self. */
+dyStringPrintf(dy, "networkErrMsg: %s\n", track->networkErrMsg);
+    //char *networkErrMsg;        /* Network layer error message */
+dyStringPrintf(dy, "parallelLoading: %d\n", track->parallelLoading);
+    //boolean parallelLoading;    /* If loading in parallel, usually network resources. */
+dyStringPrintf(dy, "summary: %lu\n", (unsigned long) track->summary);
+    //struct bbiSummaryElement *summary;  /* for bigBed */
+dyStringPrintf(dy, "summAll: %lu\n", (unsigned long) track->sumAll);
+    //struct bbiSummaryElement *sumAll;   /* for bigBed */
+dyStringPrintf(dy, "drawLabelInBox: %d\n", track->drawLabelInBox);
+    //boolean drawLabelInBox;     /* draw labels into the features instead of next to them */
+    //};
+
+return dyStringCannibalize(&dy);
+}
+
+char *makeDumpURL(char *text)
+/* Make a temp file to hold big dump. Return URL to it. */
+{
+// trackDump output is quite large
+struct tempName trkDmp;
+trashDirFile(&trkDmp, "hgt", "hgt", ".txt");
+FILE *f = mustOpen(trkDmp.forCgi, "w");
+fprintf(f, "%s", text);
+carefulClose(&f);
+return cloneString(trkDmp.forHtml);
+}
+
+char *makeTrackDumpLink(struct track *track)
+/* Make a track dump to trash, and return html link to it */
+{
+char *tds = trackDumpString(track);
+char *url = makeDumpURL(tds);
+char buf[1024];
+safef(buf, sizeof buf, "<A HREF=%s>URL</A>", url);
+freeMem(tds);
+freeMem(url);
+return cloneString(buf);
 }
-struct flatTracks *flatTracks = NULL;
-struct flatTracks *flatTrack = NULL;
 
-if (rulerMode != tvFull)
+boolean regionsAreInOrder(struct virtRegion *virtRegion1, struct virtRegion *virtRegion2)
+/* Return TRUE if the regions are on the same chrom and non-overlapping
+ * and are in-order, i.e. region 1 appears before region2. */
 {
-    rulerCds = FALSE;
+if (sameString(virtRegion1->chrom, virtRegion2->chrom) && virtRegion1->end <= virtRegion2->start)
+    return TRUE;
+return FALSE;
 }
 
-/* Figure out height of each visible track. */
-pixHeight = gfxBorder;
-if (rulerMode != tvHide)
+/* --- Virtual Chromosome Functions --- */
+
+boolean virtualSingleChrom()
+/* Return TRUE if using virtual single chromosome mode */
+{
+return (sameString(virtModeType,"exonMostly") || sameString(virtModeType,"geneMostly"));
+}
+
+void parseVPosition(char *position, char **pChrom, long *pStart, long *pEnd)
+/* parse Virt position */
+{
+if (!position)
+    {
+    errAbort("position NULL");
+    }
+char *vPos = cloneString(position);
+char *colon = strchr(vPos, ':');
+if (!colon)
+    errAbort("position has no colon");
+char *dash = strchr(vPos, '-');
+if (!dash)
+    errAbort("position has no dash");
+*colon = 0;
+*dash = 0;
+*pChrom = cloneString(vPos);
+*pStart = atol(colon+1) - 1;
+*pEnd = atol(dash+1);
+}
+
+void parseNVPosition(char *position, char **pChrom, int *pStart, int *pEnd)
+/* parse NonVirt position */
+{
+if (!position)
+    {
+    errAbort("position NULL");
+    }
+char *vPos = cloneString(position);
+char *colon = strchr(vPos, ':');
+if (!colon)
+    errAbort("position has no colon");
+char *dash = strchr(vPos, '-');
+if (!dash)
+    errAbort("position has no dash");
+*colon = 0;
+*dash = 0;
+*pChrom = cloneString(vPos);
+*pStart = atoi(colon+1) - 1;
+*pEnd = atoi(dash+1);
+}
+
+char *disguisePositionVirtSingleChrom(char *position) // DISGUISE VMODE
+/* Hide the virt position, convert to real single chrom span.
+ * position should be virt chrom span. 
+ * Can handle anything in the virt single chrom. */
+{   
+//warn("disguisePosition position="+position); // DEBUG REMOVE
+/* parse Virt position */
+char *chrom = NULL;
+long start = 0;
+long end = 0;
+parseVPosition(position, &chrom, &start, &end);
+if (!sameString(chrom, "virt"))
+    return position; // return original
+//warn("start="+start+" end="+end); // DEBUG REMOVE
+struct window *windows = makeWindowListFromVirtChrom(start, end);
+char *nonVirtChromName = windows->chromName;
+if (!sameString(nonVirtChromName, chromName))
+    return position; // return original
+int nonVirtWinStart  = windows->winStart;
+int nonVirtWinEnd    = windows->winEnd;
+struct window *w;
+for (w=windows->next; w; w=w->next)
+    {
+    //warn("w->chromName=%s w->winStart=%d w->winEnd=%d",w->chromName, w->winStart, w->winEnd); // DEBUG REMOVE
+    if (!sameString(w->chromName, nonVirtChromName))
+	return position; // return original
+    if (w->winEnd < nonVirtWinEnd)
+	return position; // return original
+    nonVirtWinEnd = w->winEnd;
+    }
+char nvPos[256];
+safef(nvPos, sizeof nvPos, "%s:%d-%d", nonVirtChromName, nonVirtWinStart+1, nonVirtWinEnd);
+slFreeList(&windows);
+return cloneString(nvPos);
+}
+
+
+
+char *undisguisePosition(char *position) // UN-DISGUISE VMODE
+/* Find the virt position
+ * position should be real chrom span. 
+ * Limitation: can only convert things in the current windows set. */
+{   
+//warn("undisguisePosition position="+position); // DEBUG REMOVE
+/* parse NonVirt position */
+char *chrom = NULL;
+int start = 0;
+int end = 0;
+parseNVPosition(position, &chrom, &start, &end);
+if (!sameString(chrom, chromName))
+    return position; // return original
+long newStart = -1;
+long newEnd = -1;
+struct window *lastW = NULL;
+struct window *w = NULL;
+//warn("start="+start+" end="+end); // DEBUG REMOVE
+for (w = windows; w; w=w->next)
+    {
+    //  double check chrom is same thoughout all windows, otherwise warning, return original value
+    if (!sameString(w->chromName, chromName))
+	{
+	return position; // return original
+	}
+    // check that the regions are ascending and non-overlapping
+    if (lastW && w->winStart < lastW->winEnd)
+	{
+	return position; // return original
+	}
+    // overlap with position?
+    //  if intersection, 
+    if (w->winEnd > start && end > w->winStart) 
+	{
+	int s = max(start, w->winStart);
+	int e = min(end, w->winEnd);
+	long cs = s - w->winStart + w->virtStart;
+	long ce = e - w->winStart + w->virtStart;
+	//warn("cs="+cs+" ce="+ce); // DEBUG REMOVE
+	if (newStart == -1)
+	    newStart = cs;
+	newEnd = ce;
+	}
+    lastW = w;
+   }
+if (newStart == -1) // none of the windows intersected with the position
+    return position; // return original  
+//  return new virt undisguised position as a string
+char newPos[1024];
+safef (newPos, sizeof newPos, "virt:%ld-%ld", (newStart+1), newEnd);
+//warn("undisguisePosition newPos="+newPos); // DEBUG REMOVE
+return cloneString(newPos);
+}
+
+
+char *windowsSpanPosition()
+/* Return a position string that spans all the windows.
+ * Windows should be on same chrom and ascending and non-overlapping.*/
 {
-    if (!baseShowRuler && !baseTitle && !baseShowPos && !baseShowAsm && !baseShowScaleBar && !zoomedToBaseLevel && !rulerCds)
+char buf[256];
+char *chromName = windows->chromName;
+int start = windows->winStart;
+struct window *w = windows, *last = NULL;
+while(w->next) // find last window
     {
-        warn("Can't turn everything off in base position track.  Turning ruler back on");
-        baseShowRuler = TRUE;
-        cartSetBoolean(cart, BASE_SHOWRULER, TRUE);
+    last = w;
+    w = w->next;
+    if (!sameString(chromName, w->chromName))
+	errAbort("windowsSpanPosition: expected all windows to be on the same chrom but found %s and %s", chromName, w->chromName);
+    if (w->winStart < last->winEnd)
+	errAbort("windowsSpanPosition: expected all windows to be ascending non-overlapping, found %d < %d", w->winStart, last->winEnd);
+    }
+int end = w->winEnd;
+safef(buf, sizeof buf, "%s:%d-%d", chromName, start+1, end);
+return cloneString(buf);
 }
 
-    if (baseTitle)
-        basePositionHeight += titleHeight;
+void padVirtRegions(int windowPadding)
+/* Pad virt regions with windowPadding bases
+ *
+ * NOTE a simple padding would not worry about merging or order. Just expand the beginning and end of each region.
 
-    if (baseShowPos||baseShowAsm)
-        basePositionHeight += showPosHeight;
+ * NOTE this assumes that the regions are in order, but tolerates hiccups in order.
 
-    if (baseShowScaleBar)
-        basePositionHeight += scaleBarTotalHeight;
+ * DONE make it handle multiple chromsomes
+ *
+ * TODO what about just modifying the original list directly? 
 
-    if (!baseShowRuler)
+ * I do not know if this is handling merging correctly. 
+ * DONE Maybe I should just add the padding directly into the exon-fetch-merge code.
+ * I have looked at that earlier, and it should work easily.
+ * It might also have the advantage of not having to create a duplicate list?
+ *
+ * TODO how do I test that the output is correct. 
+ *  if the input has ordered non-duplicate regions, then the output should be likewise.
+ * */
 {
-        basePositionHeight -= rulerHeight;
-        rulerHeight = 0;
+int regionCount = 0;
+long regionBases=0;
+struct virtRegion *virtRegion, *lastVirtRegion = NULL;
+int  leftWindowPadding = 0;
+int rightWindowPadding = 0;
+struct virtRegion *v, *newList = NULL;
+char *lastChrom = NULL;
+int chromSize = -1;
+for(virtRegion=virtRegionList; virtRegion; virtRegion = virtRegion->next)
+    {
+    AllocVar(v);
+    if (!sameOk(virtRegion->chrom, lastChrom))
+	{
+	chromSize = hChromSize(database, virtRegion->chrom);
 	}
-
-    if (zoomedToBaseLevel)
-        basePositionHeight += baseHeight;
-
-    yAfterRuler += basePositionHeight;
-    yAfterBases = yAfterRuler;
-    pixHeight += basePositionHeight;
-    if (rulerCds)
+    v->chrom = virtRegion->chrom; // TODO is cloning the string needed?
+    leftWindowPadding = windowPadding;
+    rightWindowPadding = windowPadding;
+    if (lastVirtRegion && regionsAreInOrder(lastVirtRegion, virtRegion))
 	{
-        yAfterRuler += rulerTranslationHeight;
-        pixHeight += rulerTranslationHeight;
+	int distToPrevRegion = virtRegion->start - lastVirtRegion->end;
+	if (distToPrevRegion < (2*windowPadding))
+	    {
+	    leftWindowPadding = distToPrevRegion/2;
 	    }
 	}
-
-boolean safeHeight = TRUE;
-/* firefox on Linux worked almost up to 34,000 at the default 620 width */
-#define maxSafeHeight   32000
-/* Hash tracks/subtracks, limit visibility and calculate total image height: */
-for (track = trackList; track != NULL; track = track->next)
+    if (virtRegion->next && regionsAreInOrder(virtRegion, virtRegion->next))
 	{
-    if (tdbIsCompositeChild(track->tdb)) // When single track is requested via AJAX,
-        limitedVisFromComposite(track);  // it could be a subtrack
-    else
-        limitVisibility(track);
+	int distToNextRegion = virtRegion->next->start - virtRegion->end;
+	if (distToNextRegion < (2*windowPadding))
+	    {
+	    rightWindowPadding = (distToNextRegion+1)/2; 
+	    // +1 to balance for odd number of bases between, arbitrarily adding it to right side
+	    }
+	}
+    v->start = virtRegion->start - leftWindowPadding;
+    v->end = virtRegion->end + rightWindowPadding;
+    if (v->start < 0)
+	v->start = 0;
+    if (v->end >= chromSize)
+	v->end = chromSize;
+    regionBases += (v->end - v->start);
+    slAddHead(&newList, v);
+
+    lastVirtRegion = virtRegion;
+    lastChrom = virtRegion->chrom;
+    ++regionCount;
+    }
+warn("After padding, %d regions", regionCount);
+slReverse(&newList);
+virtRegionList = newList; // update new list -- TODO should the old one be freed? if so, the chrom name should use cloneString
+}
+
 
-    if (!safeHeight)
+void makeVirtChrom()
+/* build virtual chrom array from virt region list */
 {
-        track->limitedVis = tvHide;
-        track->limitedVisSet = TRUE;
-        continue;
+virtRegionCount = slCount(virtRegionList);
+AllocArray(virtChrom, virtRegionCount);
+struct virtRegion *v;
+int i = 0;
+long totalBases = 0;
+for(v=virtRegionList;v;v=v->next,++i)
+    {
+    virtChrom[i].virtPos = totalBases;
+    virtChrom[i].virtRegion = v;
+    totalBases += (v->end - v->start);
+    //warn("virtChrom[%d] .virtPos=%ld", i, virtChrom[i].virtPos);  // DEBUG REMOVE
+    }
+virtSeqBaseCount = totalBases;
 }
 
-    if (tdbIsComposite(track->tdb))
+void virtChromBinarySearch(long target, long *resultIndex, int *resultOffset)
+/* Do a binary search for the target position in the virtual chrom.
+ * Return virtChrom Index and Offset if found.
+ * Return -1 if target out of range. TODO maybe change to errAbort*/
 {
-        struct track *subtrack;
-        for (subtrack = track->subtracks; subtrack != NULL; subtrack = subtrack->next)
+//The binary search will either return match or out-of-range.
+long index = -1;
+int offset = -1;
+long a = 0; // start of the array
+long b = virtRegionCount - 1; // end of array where N = count of regions
+if (target >=0 && target < virtSeqBaseCount)
     {
-            if (!isSubtrackVisible(subtrack))
-                continue;
+    while(1)
+	{
+	long c = (a + b) / 2;
 
-            // subtrack vis can be explicit or inherited from composite/view.
-            // Then it could be limited because of pixel height
-            limitedVisFromComposite(subtrack);
+	//warn("target=%ld a=%ld b=%ld c=%ld virtRegionCount=%d", target, a, b, c, virtRegionCount); // DEBUG REMOVE
 
-            if (subtrack->limitedVis != tvHide)
+	if (a > b)
 	    {
-                subtrack->hasUi = track->hasUi;
-                flatTracksAdd(&flatTracks,subtrack,cart);
+	    index = b;
+	    break;
 	    }
+	else if (target == virtChrom[c].virtPos)
+	    {
+	    // (exact match)
+	    index = c;
+	    break;
 	    }
+	else if (target > virtChrom[c].virtPos)
+	    {
+	    a = c + 1;
 	    }
-    else if (track->limitedVis != tvHide)
-        flatTracksAdd(&flatTracks,track,cart);
+	else if (target < virtChrom[c].virtPos)
+	    {
+	    b = c - 1;
 	    }
-flatTracksSort(&flatTracks); // Now we should have a perfectly good flat track list!
-struct track *prevTrack = NULL;
-for (flatTrack = flatTracks,prevTrack=NULL; flatTrack != NULL; flatTrack = flatTrack->next)
+	else
 	    {
-    track = flatTrack->track;
-    assert(track->limitedVis != tvHide);
-    int totalHeight = pixHeight+trackPlusLabelHeight(track,fontHeight);
-    if (maxSafeHeight < totalHeight)
+	    // probably should not happen
+	    errAbort("Unexpected outcome in virtChromBinarySearch.");
+	    }
+	}
+    }
+else if (target == virtSeqBaseCount)
+    { // tolerate this special case
+    index = virtRegionCount - 1;
+    }
+if (index >= 0)
+    offset = target - virtChrom[index].virtPos;
+*resultIndex = index;
+*resultOffset = offset;
+}
+
+void testVirtChromBinarySearch()
+/* test virt chrom binary search */
 {
-        char numBuf[SMALLBUF];
-        sprintLongWithCommas(numBuf, maxSafeHeight);
+warn("about to test virt chrom binary search: virtSeqBaseCount=%ld virtRegionCount=%d", virtSeqBaseCount, virtRegionCount);
+long i;
+int o;
+long target = -1;
+virtChromBinarySearch(target, &i, &o);
+if (i != -1)
+    errAbort("target %ld result %ld %d ", target, i, o);
+for(target=0; target < virtSeqBaseCount; ++target)
+    {
+    virtChromBinarySearch(target, &i, &o);
+    if (virtChrom[i].virtPos + o != target)
+	errAbort("target = %ld result %ld %d ", target, i, o);
+    struct virtRegion *v = virtChrom[i].virtRegion;
+    int size = v->end - v->start;
+    if (o > size)
+	errAbort("target = %ld result %ld %d > size=%d", target, i, o, size);
+    }
+target = virtSeqBaseCount;
+virtChromBinarySearch(target, &i, &o);
+if (i != virtRegionCount - 1)
+    errAbort("target = virtSeqBaseCount = %ld result %ld %d ", target, i, o);
+target = virtSeqBaseCount+1;
+virtChromBinarySearch(target, &i, &o);
+if (i != -1)
+    errAbort("target = virtSeqBaseCount = %ld result %ld %d ", target, i, o);
+warn("Got past test virt chrom binary search");
+}
+
+struct window *makeWindowListFromVirtChrom(long virtWinStart, long virtWinEnd)
+/* make list of windows from virtual position on virtualChrom */
+{
+// quick check of virt win start and end
+if (virtWinEnd == virtWinStart)
+    return NULL;
+if (virtWinStart < 0 || virtWinStart >= virtSeqBaseCount)
+    errAbort("unexpected error. virtWinStart=%ld out of range. virtSeqBaseCount=%ld", virtWinStart, virtSeqBaseCount);
+if (virtWinEnd < 0 || virtWinEnd > virtSeqBaseCount)
+    errAbort("unexpected error. virtWinEnd=%ld out of range. virtSeqBaseCount=%ld", virtWinEnd, virtSeqBaseCount);
+if (virtWinEnd - virtWinStart < 1)
+    errAbort("unexpected error. virtual window size < 1 base. virtWinStart=%ld virtWinEnd=%ld virtSeqBaseCount=%ld", virtWinStart, virtWinEnd, virtSeqBaseCount);
+long virtIndexStart;
+int  virtOffsetStart;
+virtChromBinarySearch(virtWinStart, &virtIndexStart, &virtOffsetStart);
+if (virtIndexStart == -1)
+    errAbort("unexpected failure to find target virtWinStart %ld in virtChrom Array", virtWinStart);
+
+long virtIndexEnd;
+int  virtOffsetEnd;
+virtChromBinarySearch(virtWinEnd, &virtIndexEnd, &virtOffsetEnd);
+if (virtIndexEnd == -1)
+    errAbort("unexpected failure to find target virtWinEnd %ld in virtChrom Array", virtWinEnd);
+
+//warn("after binary searches, virtIndexStart=%ld virtOffsetStart=%d , virtIndexEnd=%ld virtOffsetEnd=%d", virtIndexStart, virtOffsetStart, virtIndexEnd, virtOffsetEnd);  // DEBUG  REMOVE
+
+// create new windows list from virt chrom
+struct window *windows = NULL;
+long i = virtIndexStart;
+int winCount = 0;
+long basesInWindows = 0; // TODO not actually using this variable 
+for(i=virtIndexStart; i <= virtIndexEnd; ++i)
+    {
+    struct window *w;
+    AllocVar(w);
+    w->organism = organism; // obsolete
+    w->database = database; // obsolete
+    struct virtRegion *v = virtChrom[i].virtRegion;
+    long virtPos = virtChrom[i].virtPos;
+    w->chromName = v->chrom;
+    if (i == virtIndexStart)
+	{
+	w->winStart = v->start + virtOffsetStart;
+	w->virtStart = virtPos + virtOffsetStart;
+	}
+    else
+	{
+	w->winStart = v->start;
+	w->virtStart = virtPos;
+	}
+    if (i == virtIndexEnd)
+	{
+	if (virtOffsetEnd == 0)
+	    continue;
+	w->winEnd = v->start + virtOffsetEnd;
+	}
+    else
+	{
+	w->winEnd = v->end;
+	}
+    w->virtEnd = w->virtStart + (w->winEnd - w->winStart);
+    w->regionOdd = i % 2;
+    basesInWindows += (w->winEnd - w->winStart);
+    slAddHead(&windows, w);
+    ++winCount;
+    }
+slReverse(&windows);
+if (basesInWindows != (virtWinEnd-virtWinStart)) // was virtWinBaseCount but now I call this routine for highlight pos as well as virt pos.
+    errAbort("makeWindowListFromVirtChrom: unexpected error basesInWindows(%ld) != virtWinBaseCount(%ld)", basesInWindows, virtWinEnd-virtWinStart);
+return windows;
+}
+
+char *nonVirtPositionFromWindows()
+/* Must have created the virtual chrom first.
+ * Currently just a hack to use windows,
+ * use the (first) window(s) from that to get real chrom name start end */
+{
+// assumes makeWindowListFromVirtChrom() has already been called.
+if (!windows)
+    errAbort("nonVirtPositionFromWindows() unexpected error, windows list not initialized yet");
+char *nonVirtChromName = windows->chromName;
+int nonVirtWinStart  = windows->winStart;
+int nonVirtWinEnd    = windows->winEnd;
+// Extending this through more of the windows,
+// if it is on the same chrom and maybe not too far separated.
+struct window *w;
+for (w=windows->next; w; w=w->next)
+    {
+    if (sameString(w->chromName, nonVirtChromName) && w->winEnd > nonVirtWinEnd)
+	nonVirtWinEnd = w->winEnd;
+    }
+// TODO Also consider preserving the original bases in windows width (with clipping).
+char nvPos[256];
+safef(nvPos, sizeof nvPos, "%s:%d-%d", nonVirtChromName, nonVirtWinStart+1, nonVirtWinEnd);
+return cloneString(nvPos);
+}
+
+char *nonVirtPositionFromHighlightPos()
+/* Must have created the virtual chrom first.
+ * Currently just a hack to use windows,
+ * use the (first) window(s) from that to get real chrom name start end */
+{
+struct highlightVar *h = parseHighlightInfo();
+
+//if (h)
+//    warn("remapHighlightPos h-> chrom=%s chromStart=%ld chromEnd=%ld", h->chrom, h->chromStart, h->chromEnd); // DEBUG REMOVE
+
+if (!(h && h->db && sameString(h->db, database) && sameString(h->chrom,"virt")))
+    return NULL;
+
+struct window *windows = makeWindowListFromVirtChrom(h->chromStart, h->chromEnd);
+
+char *nonVirtChromName = windows->chromName;
+int nonVirtWinStart  = windows->winStart;
+int nonVirtWinEnd    = windows->winEnd;
+// Extending this through more of the windows,
+// if it is on the same chrom and maybe not too far separated.
+struct window *w;
+for (w=windows->next; w; w=w->next)
+    {
+    //warn("w->chromName=%s w->winStart=%d w->winEnd=%d",w->chromName, w->winStart, w->winEnd); // DEBUG REMOVE
+    if (sameString(w->chromName, nonVirtChromName) && w->winEnd > nonVirtWinEnd)
+	nonVirtWinEnd = w->winEnd;
+    }
+// TODO Also consider preserving the original bases in windows width (with clipping).
+char nvPos[256];
+safef(nvPos, sizeof nvPos, "%s.%s:%d-%d#%s", h->db, nonVirtChromName, nonVirtWinStart+1, nonVirtWinEnd, h->hexColor);
+return cloneString(nvPos);
+}
+
+void allocPixelsToWindows()
+/* Allocate pixels to windows, sets insideWidth and insideX 
+ *
+ * TODO currently uses a strategy that places a window at a pixel location 
+ * directly, because of round-off and missing small windows this can occasionally
+ * lead to gaps not covered by any pixel.  Consider replacing it with something 
+ * that tries not to leave many gaps -- but how to do it without distortion of some window sizes? 
+ * */
+{
+double pixelsPerBase = (double)fullInsideWidth / virtWinBaseCount; 
+long basesUsed = 0;
+int windowsTooSmall = 0;
+struct window **pWindows = &windows;
+struct window *window;
+int winCount = slCount(windows);
+for(window=windows;window;window=window->next)
+    {
+    int basesInWindow = window->winEnd - window->winStart; 
+    int pixelsInWindow = 0.5 + (double)basesInWindow * pixelsPerBase; // should this round up ? + 0.5?
+    window->insideWidth = pixelsInWindow;
+    window->insideX = fullInsideX + basesUsed * pixelsPerBase;
+    basesUsed += basesInWindow;
+
+    //warn("window x = %d  width = %d x + width=%d basesInWindow=%d", 
+	//window->insideX, window->insideWidth, window->insideX + window->insideWidth, basesInWindow); // DEBUG REMOVE
+
+    if (pixelsInWindow < 1) // remove windows less than one pixel from the list
+	{
+	//if (windowsTooSmall < 5)
+	//    warn("window x = %d  width = %d x + width=%d", window->insideX, window->insideWidth, window->insideX + window->insideWidth);
+	*pWindows = window->next;
+	--winCount;
+	++windowsTooSmall;
+	}
+    else
+	{
+	pWindows = &window->next;
+	}
+    }
+//if (windowsTooSmall > 0)
+//    warn("Summary: %d windowsTooSmall: (pixelsInWindow < 1)", windowsTooSmall);
+
+//warn("winCount=%d slCount(windows)=%d #LessThan1Pixel=%d fullInsideWidth=%d virtWinBaseCount=%ld basesUsed=%ld", 
+    //winCount, slCount(windows), windowsTooSmall, fullInsideWidth, virtWinBaseCount, basesUsed);
+
+// DEBUG REMOVE:
+// nextExonArrows are working now 
+//if (winCount >= 2)
+//    withNextExonArrows = FALSE;	/* Display next exon navigation buttons near center labels? */
+}
+
+struct positionMatch *virtChromSearchForPosition(char *chrom, int start, int end, boolean findNearest)
+/* Search the virtual chrom for the query chrom, start, end position
+ *
+ * TODO GALT: Intially this can be a simple brute-force search of the entire virtChrom array.
+ * 
+ * However, this will need to be upgraded to using a rangeTree or similar structure
+ * to rapidly return multiple regions that overlap the query position.
+ * */
+{
+struct positionMatch *list=NULL, *p;
+int nearestRegion = -1;
+boolean nearestAfter = TRUE;
+int nearestDistance = INT_MAX;
+int i;
+struct virtRegion *v = NULL;
+long virtPos = 0;
+for(i=0; i < virtRegionCount; ++i)
+    {
+    v = virtChrom[i].virtRegion;
+    virtPos = virtChrom[i].virtPos;
+    if (sameString(v->chrom,chrom))
+	{ // TODO will we need to support finding closest misses too?
+	if (v->end > start && end > v->start) // overlap
+	    {
+	    int s = max(start, v->start);
+	    int e = min(end  , v->end  );
+	    AllocVar(p);
+	    p->virtStart = virtPos + (s - v->start);
+	    p->virtEnd   = virtPos + (e - v->start);
+	    slAddHead(&list, p);
+	    }
+	else if (findNearest && (!list))
+	    {
+	    int thisDist = v->start - start;
+	    boolean thisAfter = TRUE;
+	    if (thisDist < 0) // absolute value
+		{
+		thisDist = -thisDist;
+		thisAfter = FALSE;
+		}
+	    if (thisDist < nearestDistance)
+		{
+		nearestRegion = i;
+		nearestDistance = thisDist;
+		nearestAfter = thisAfter;
+		}
+	    }
+	}
+    }
+if (findNearest && (!list) && nearestRegion != -1)
+    {
+    i = nearestRegion;
+    v = virtChrom[i].virtRegion;
+    virtPos = virtChrom[i].virtPos;
+    AllocVar(p);
+    if (nearestAfter)
+	{
+    	p->virtStart = virtPos;
+	p->virtEnd   = p->virtStart + (end - start);
+	}
+    else
+	{
+    	p->virtEnd = virtPos + (v->end - v->start);
+	p->virtStart = p->virtEnd - (end - start);
+	}
+    if (p->virtEnd > virtSeqBaseCount)
+	p->virtEnd = virtSeqBaseCount;
+    //warn("findNearest: nearestRegion=%d nearestDistance=%d p->virtStart=%ld p->virtEnd=%ld ", 
+	//nearestRegion, nearestDistance, p->virtStart, p->virtEnd); // DEBUG REMOVE
+    slAddHead(&list, p);
+    }
+slReverse(&list);
+return list;
+}
+
+int matchVPosCompare(const void *elem1, const void *elem2)
+/* compare elements based on vPos */
+{
+const struct positionMatch *a = *((struct positionMatch **)elem1);
+const struct positionMatch *b = *((struct positionMatch **)elem2);
+if (a->virtStart == b->virtStart)
+    errAbort("matchVPosCompare error should not happen: 2 elements being sorted have the same virtStart=%ld", a->virtStart);
+else if (a->virtStart > b->virtStart)
+    return 1;
+return -1;
+}
+
+void matchSortOnVPos(struct positionMatch **pList)
+/* Sort positions by virtPos 
+ * pList will be sorted by chrom, start, end but we want it ordered by vPos
+ */
+{
+slSort(pList, matchVPosCompare); 
+}
+
+
+struct positionMatch *matchMergeContiguousVPos(struct positionMatch *list)
+/* Merge contiguous matches spanning multiple touching windows */
+{
+struct positionMatch *newList = NULL;
+struct positionMatch *m;
+long lastStart = -1;
+long lastEnd = -1;
+struct positionMatch *lastM = NULL;
+boolean inMerge = FALSE;
+long mergeStart = -1;
+long mergeEnd = -1;
+if (!list)
+    return NULL;
+for(m=list; 1; m=m->next) 
+// special loop condition allows it to stop AFTER it goes thru the loop once as m=NULL.
+// this flushes out the last value.
+    {
+    if (inMerge)
+	{
+	if (m && m->virtStart == lastEnd)
+	    {
+	    // continue merging, do nothing. 
+	    // maybe could be freeing a skipped node here.
+	    }
+	else  
+	    {
+	    inMerge = FALSE;
+	    mergeEnd = lastEnd;
+	    // create new merged node and add it to new list
+	    struct positionMatch *n;
+	    AllocVar(n);
+	    n->virtStart = mergeStart;
+	    n->virtEnd   = mergeEnd;
+	    slAddHead(&newList, n);
+	    }
+	}
+    else
+	{
+	if (m && m->virtStart == lastEnd)
+	    {
+	    inMerge = TRUE;
+	    mergeStart = lastStart;
+	    }
+	else if (lastM) // just transfer the last node unmodified to the new list.
+	    {
+	    slAddHead(&newList, lastM);
+	    }
+	}
+    if (!m)
+	break;
+    lastStart = m->virtStart;
+    lastEnd = m->virtEnd;
+    lastM = m;
+    }
+slReverse(&newList);
+return newList;
+}
+
+
+// ----------------------------------
+
+
+static void checkMaxWindowToDraw(struct track *tg); // forward declaration  // DEBUG REMOVE?
+
+
+void initVirtRegionsFromSOD1Hardwired()
+/* create a testing regionlist from SOD1 (uc002ypa.3) hardwired */
+{
+virtRegionList = NULL;
+char *chrom="chr21";
+//int exonStarts[] = {33031934,33036102,33038761,33039570,33040783};
+//int exonEnds[]   = {33032154,33036199,33038831,33039688,33041243};
+// BASE-level zoom in two 20 bp exons and their on-screen junction
+int exonStarts[] = {33032134,33036102};
+int exonEnds[]   = {33032154,33036122};
+int i;
+for(i=0; i<ArraySize(exonStarts); ++i)
+    {
+    struct virtRegion *v;
+    AllocVar(v);
+    v->chrom = cloneString(chrom);
+    v->start = exonStarts[i];
+    v->end   = exonEnds[i];
+    v->strand[0] = '+';
+    v->strand[1] = 0;
+    slAddHead(&virtRegionList, v);
+    }
+slReverse(&virtRegionList);
+}
+
+void initVirtRegionsFromEmGeneTable(char *ucscTranscriptId)
+/* create a testing regionlist from knownGene transcript with given id */
+{
+struct sqlConnection *conn = hAllocConn(database);
+virtRegionList = NULL;
+struct sqlResult *sr;
+char **row;
+int rowOffset = 0;
+if (hIsBinned(database, emGeneTable)) // skip first bin column if any
+    ++rowOffset;
+char query[256];
+sqlSafef(query, sizeof(query), "select * from %s where name='%s'", emGeneTable, ucscTranscriptId);
+sr = sqlGetResult(conn, query);
+while ((row = sqlNextRow(sr)) != NULL)
+    {
+    struct genePred *gene = genePredLoad(row+rowOffset);
+    int i;
+    for(i=0; i< gene->exonCount; ++i)
+	{
+	struct virtRegion *v;
+	AllocVar(v);
+	v->chrom = cloneString(gene->chrom);
+	v->start = gene->exonStarts[i];
+	v->end   = gene->exonEnds[i];
+	v->strand[0] = gene->strand[0];
+    	v->strand[1] = 0;
+	slAddHead(&virtRegionList, v);
+	}
+    genePredFree(&gene);
+    }
+sqlFreeResult(&sr);
+slReverse(&virtRegionList);
+hFreeConn(&conn);
+}
+
+boolean initSingleAltHaplotype(char *haplotypeId)
+/* create a testing regionlist from haplotype with given id */
+{
+struct sqlConnection *conn = hAllocConn(database);
+if (!virtRegionList) // this should already contain allchroms.
+    errAbort("unexpected error in initSingleAltHaplotype: virtRegionList is NULL, should contain all chroms");
+struct virtRegion *after = virtRegionList;
+virtRegionList = NULL; 
+struct sqlResult *sr;
+char **row;
+char *table = NULL;
+if (sameString(database,"hg17"))
+    table = "altLocations"; // was haplotypeLocations which I made with BLAT and self-chains ;  // bin+bed4
+else if (sameString(database,"hg18"))
+    table = "altLocations"; // was haplotypeLocationsEnsembl which got renamed;  // bin+bed4
+else if (sameString(database,"hg19"))
+    table = "altLocations"; // created from hapRegions, was "altSeqHaplotypes";  // was bin+bed6, now bin+bed4
+else if (sameString(database,"hg38"))
+    table = "altLocations";  // bin+bed4
+else
+    {
+    warn("initSingleAltHaplotype() was expecting database to be hg17, hg18, hg19, or hg38");
+    return FALSE;
+    }
+
+// where is the alt haplo placed?
+char query[256];
+sqlSafef(query, sizeof(query), "select chrom, chromStart, chromEnd from %s where name='%s'", table, haplotypeId);
+sr = sqlGetResult(conn, query);
+row = sqlNextRow(sr);
+if (!row)
+    {
+    warn("no haplotype found for [%s]", haplotypeId);
+    return FALSE;
+    }
+char *haploChrom = cloneString(row[0]);
+int haploStart = sqlUnsigned(row[1]);
+int haploEnd   = sqlUnsigned(row[2]);
+sqlFreeResult(&sr);
+// what is the size of the alt haplo?
+int haploSize = hChromSize(database, haplotypeId); // hopefully this will work
+//warn("database=%s, haplotypeId=%s, haploSize=%d", database, haplotypeId, haploSize);  //  DEBUG REMOVE
+
+// insert into list replacing original haploChrom record
+struct virtRegion *before = NULL;
+boolean found = FALSE;
+long offset = 0;
+struct virtRegion *v;
+while ((v=slPopHead(&after)))
+    {
+    if (sameString(haploChrom, v->chrom))
+	{
+	found = TRUE;
+	break;
+	}
+    offset += (v->end - v->start);
+    slAddHead(&before, v);
+    }
+if (!found)
+    {
+    warn("initSingleAltHaplotype: chrom %s on which alt %s is placed is not found in all chroms list!", haploChrom, haplotypeId);
+    return FALSE;
+    }
+slReverse(&before);
+
+// for now, make virtchrom with just one chrom plus its haplo in the middle
+
+defaultVirtWinStart = 0;
+defaultVirtWinEnd = 0;;
+
+AllocVar(v);
+v->chrom = haploChrom;
+v->start = 0;
+v->end   = haploStart;
+defaultVirtWinStart = v->end - haploSize;
+if (defaultVirtWinStart < 0)
+    defaultVirtWinStart = 0;
+defaultVirtWinEnd = v->end;
+slAddHead(&virtRegionList, v);
+
+
+AllocVar(v);
+v->chrom = haplotypeId;
+v->start = 0;
+v->end   = haploSize;
+defaultVirtWinEnd += haploSize;
+slAddHead(&virtRegionList, v);
+
+AllocVar(v);
+v->chrom = haploChrom;
+v->start = haploEnd;
+int chromSize = hChromSize(database, haploChrom); // size of the regular chrom
+v->end   = chromSize;
+defaultVirtWinEnd += haploSize;
+if (defaultVirtWinEnd > chromSize)
+    defaultVirtWinEnd = chromSize;
+slAddHead(&virtRegionList, v);
+
+slReverse(&virtRegionList);
+hFreeConn(&conn);
+
+defaultVirtWinStart += offset;
+defaultVirtWinEnd += offset;
+
+// concatenate the 3 lists.
+virtRegionList = slCat(before,slCat(virtRegionList,after));
+
+// DEBUG REMOVE testing
+//warn("initSingleAltHaplotype: offset=%ld", offset);
+//long testOffset = 0;
+//for (v=virtRegionList; v; v=v->next)
+//    {
+//    testOffset += (v->end - v->start);
+//    warn("%s:%d-%d cumuulative offset %ld", v->chrom, v->start, v->end, testOffset);
+//    }
+
+return TRUE;
+}
+
+
+void initVirtRegionsFromKnownCanonicalGenes(char *table)
+// OBSOLETED by initVirtRegionsFromEMGeneTableExons()
+/* Create a regionlist from knownCanonical genes (not exons) */
+// I was not expecting it, but 20% of the KC genes overlap with others.
+// Some are cDNAs, some are non-coding RNAs, some just look like junk.
+// But I have to add special logic to accomodate them.
+// Currently I just merge until there is no more overlap,
+// and then I start a new region. So 30K regions should reduce to about 24K regions or less.
+{
+struct sqlConnection *conn = hAllocConn(database);
+virtRegionList = NULL;
+struct sqlResult *sr;
+char **row;
+char query[256];
+sqlSafef(query, sizeof(query), "select chrom, chromStart, chromEnd from %s where chrom not like '%%_hap_%%' and chrom not like '%%_random'", table);
+sr = sqlGetResult(conn, query);
+char chrom[256] = "";
+int start = -1;
+int end = -1;
+char lastChrom[256] = "";
+int lastStart = -1;
+int lastEnd = -1;
+boolean firstTime = TRUE;
+boolean isEOF = FALSE;
+while (1)
+    {
+    boolean printIt = FALSE;
+    row = sqlNextRow(sr);
+    if (row)
+	{
+	safecpy(chrom, sizeof chrom, row[0]);
+	start = sqlUnsigned(row[1]);
+    	end   = sqlUnsigned(row[2]);
+	if (sameString(chrom, lastChrom))
+	    {
+	    if (start <= lastEnd)
+		{
+		// overlap detected in knownCanonical
+		// extend current region
+		if (end > lastEnd)
+		    lastEnd = end;
+		}
+	    else
+		{
+		printIt = TRUE;
+		}
+	    }
+	else
+	    {
+	    printIt = TRUE;
+	    }
+	}
+    else
+	{
+	printIt = TRUE;
+	isEOF = TRUE;
+	}
+
+
+    if (printIt)
+	{
+	if (firstTime)
+	    {
+	    firstTime = FALSE;
+	    }
+	else
+	    {
+	    struct virtRegion *v;
+	    AllocVar(v);
+	    v->chrom = cloneString(lastChrom);
+	    v->start = lastStart;
+	    v->end   = lastEnd;
+	    v->strand[0] = '.';  // TODO we should probably just remove the strand field
+	    v->strand[1] = 0;
+	    slAddHead(&virtRegionList, v);
+	    }
+	}
+
+    if (isEOF)
+	break;
+
+    if (printIt)
+	{
+	safecpy(lastChrom, sizeof lastChrom, chrom);
+	lastStart = start;
+	lastEnd = end;
+	}
+
+    
+    }
+sqlFreeResult(&sr);
+slReverse(&virtRegionList);
+hFreeConn(&conn);
+}
+
+struct kce
+// keep list of overlapping genes and their exons
+{
+struct kce *next;
+struct genePred *gene;
+int exonNumber;
+};
+
+int findBestKce(struct kce *list, struct kce **pBestKce, struct kce **pPrevKce)
+// find best kce by having minimum exon start
+// TODO could replace this with a heap or a doubly-linked list
+{
+int best = -1;
+struct kce *e, *prev = NULL;
+for(e=list; e; prev=e, e=e->next)
+    {
+    int start = e->gene->exonStarts[e->exonNumber];
+    if ((start < best) || (best == -1))
+	{
+	best = start;
+	*pBestKce = e;
+	*pPrevKce = prev;
+	}
+    }
+return best;
+}
+
+static void padExons(struct genePred *gene, int chromSize)
+/* pad all of the exons */
+{
+int i;
+for(i=0; i < gene->exonCount; ++i)
+    {
+    // padding
+    gene->exonStarts[i] -= emPadding;
+    if (gene->exonStarts[i] < 0)
+	gene->exonStarts[i] = 0;
+    gene->exonEnds[i] += emPadding;
+    if (gene->exonEnds[i] > chromSize)
+	gene->exonEnds[i] = chromSize;
+
+    }
+}
+
+static void convertGenePredGeneToExon(struct genePred *gene)
+/* convert gene into a gene with just one exon that spans the entire gene */
+{
+if (gene->exonCount < 1)
+    errAbort("unexpected input in convertGenePredGeneToExon(), gene->exonCount=%d < 1", gene->exonCount);
+gene->exonEnds[0] = gene->exonEnds[gene->exonCount - 1];
+gene->exonCount = 1;
+}
+
+void initVirtRegionsFromEMGeneTableExons(boolean showNoncoding, char *knownCanonical, char *knownToTag, boolean geneMostly)
+/* Create a regionlist from knownGene exons. */
+// Merge exon regions that overlap. 
+
+// DONE Jim indicated that he would prefer it to include all transcripts, not just knownCanonical.
+
+// DONE Jim also suggested that we might want to handle padding right here in this step.
+// After thinking about it, I do not think it would be very hard because we are merging already.
+// Basically, just take the record from the db table row, add padding to start and end, 
+// and clip for chromosome size.
+
+// TODO If we keep it at full genome level (instead of single chrom), then there is an apparent
+// sorting issue because although they are sorted on disk, they are usually sorted by chrom alphabetically
+// so that chr11 (not chr2) comes after chr1.  Instead of trying to specify the sort order in the query,
+// which is slow, or trying to read one chrom at a time in the sorted order which is also slow, we can instead
+// just fetch them in their native order, and then create a duplicate array and copy the contents
+// to it in memory, one chunk per chrom, which would be very fast, but temporarily require duplicate vchrom array mem.
+// Not sure what to do about assemblies with many scaffolds.
+//
+// Adding support for extra options from Gencode hg38 so we can filter for
+// comprehensive, splice-variants, non-coding subsets.
+
+{
+struct sqlConnection *conn = hAllocConn(database);
+virtRegionList = NULL;
+struct sqlResult *sr;
+char **row;
+int rowOffset = 0;
+char query[256];
+// knownCanonical Hash
+struct hash *kcHash = NULL;
+if (knownCanonical) // filter out alt splicing variants
+    {
+    // load up hash of canonical transcriptIds
+    sqlSafef(query, sizeof(query), "select transcript from %s"
+	//" where chrom not like '%%_hap_%%' and chrom not like '%%_random'"
+	, knownCanonical);
+    if (virtualSingleChrom())
+	sqlSafefAppend(query, sizeof(query), " where chrom='%s'", chromName);
+    //warn("query = [%s]", query); // DEBUG REMOVE
+    kcHash = newHash(10);
+    sr = sqlGetResult(conn, query);
+    while ((row = sqlNextRow(sr)) != NULL)
+	{
+	hashAdd(kcHash, row[0], NULL);
+	}
+    sqlFreeResult(&sr);
+    }
+// knownToTag basic hash
+struct hash *ktHash = NULL;
+if (knownToTag) // filter out all but Basic
+    {
+    // load up hash of canonical transcriptIds
+    sqlSafef(query, sizeof(query), "select name from %s where value='basic'", knownToTag);
+    ktHash = newHash(10);
+    sr = sqlGetResult(conn, query);
+    while ((row = sqlNextRow(sr)) != NULL)
+	{
+	hashAdd(ktHash, row[0], NULL);
+	}
+    sqlFreeResult(&sr);
+    }
+setEMGeneTrack();
+if (!emGeneTable)
+    errAbort("Unexpected error, emGeneTable=NULL in initVirtRegionsFromEMGeneTableExons");
+if (hIsBinned(database, emGeneTable)) // skip first bin column if any
+    ++rowOffset;
+sqlSafef(query, sizeof(query), "select * from %s", emGeneTable);
+if (virtualSingleChrom())
+    sqlSafefAppend(query, sizeof(query), " where chrom='%s'", chromName);
+// TODO GALT may have to change this to in-memory sorting?
+// refGene is out of order because of genbank continuous loading
+if (sameString(emGeneTable,"refGene"))
+    sqlSafefAppend(query, sizeof(query), " order by chrom, txStart");  
+//warn("query = [%s]", query); // DEBUG REMOVE
+sr = sqlGetResult(conn, query);
+
+char chrom[256] = "";
+int start = -1;
+int end = -1;
+char lastChrom[256] = "";
+int lastStart = -1;
+int lastEnd = -1;
+int chromSize = -1;
+char lastChromSizeChrom[256] = "";
+boolean firstTime = TRUE;
+boolean isEOF = FALSE;
+struct kce *kceList = NULL, *bestKce, *prevKce;
+struct genePred *gene = NULL;
+while (1)
+    {
+
+    while(1) // get input if possible
+	{
+	
+	boolean readIt = FALSE;
+	if (!gene)
+	    readIt = TRUE;
+	if (isEOF)
+	    readIt = FALSE;
+	if (readIt)
+	    {
+	    row = sqlNextRow(sr);
+	    if (row)
+		{
+		gene = genePredLoad(row+rowOffset);
+		//warn("GALT loaded gene %s\n", gene->name); // DEBUG REMOVE
+		if (geneMostly)
+		    convertGenePredGeneToExon(gene);
+		if (!sameString(lastChromSizeChrom, gene->chrom))
+		    {
+		    chromSize = hChromSize(database, gene->chrom);
+		    safecpy(lastChromSizeChrom, sizeof lastChromSizeChrom, gene->chrom);
+		    }
+		if (emPadding > 0)
+		    padExons(gene, chromSize); // handle padding
+		}
+	    else
+		{
+		isEOF = TRUE;
+		}
+	    }
+	if (gene && !showNoncoding && (gene->cdsStart == gene->cdsEnd))
+	    {
+	    //warn("GALT skip non-coding gene %s cdsStart==cdsEnd", gene->name); // DEBUG REMOVE
+	    genePredFree(&gene);
+	    }
+	if (gene && knownCanonical && !hashLookup(kcHash, gene->name))
+	    {
+	    //warn("GALT skip not in knownCanonical hash gene %s", gene->name); // DEBUG REMOVE
+	    genePredFree(&gene);
+	    }
+	if (gene && knownToTag && !hashLookup(ktHash, gene->name))
+	    {
+	    //warn("GALT skip not in knownToTag Basic hash gene %s", gene->name); // DEBUG REMOVE
+	    genePredFree(&gene);
+	    }
+	boolean transferIt = FALSE;
+	if (gene && !kceList)
+	    {
+	    transferIt = TRUE;
+	    }
+	else if (gene && kceList)
+	    {
+	    // TODO need to check the chrom equality first
+	    int best = findBestKce(kceList, &bestKce, &prevKce);
+	    //warn("GALT check chrom gene->chrom=%s, lastChrom=%s, chrom=%s", gene->chrom, lastChrom, chrom); // DEBUG REMOVE
+	    if (sameString(gene->chrom, chrom))
+		{
+		//warn("GALT best=%d gene %s exonStart[0]=%d", best, gene->name, gene->exonStarts[0]); // DEBUG REMOVE
+		if (gene->exonStarts[0] < best)
+		    transferIt = TRUE;
+		}
+	    }
+	if (transferIt)
+	    {
+	    //warn("GALT transferred gene %s to kcelist", gene->name); // DEBUG REMOVE
+	    // add gene to kce list
+	    struct kce *kce;
+	    AllocVar(kce);
+	    kce->gene = gene;
+	    kce->exonNumber = 0;
+	    slAddHead(&kceList, kce);
+	    safecpy(chrom, sizeof chrom, gene->chrom);
+	    gene = NULL; // do not free since it is still in use
+	    }
+	//warn("GALT readIt=%d transferIt=%d", readIt, transferIt); // DEBUG REMOVE
+	if (gene && kceList && !transferIt)
+	    break;
+	if (isEOF && !gene)
+	    {
+	    if (kceList) // flush out the last of the items in kcelist
+		findBestKce(kceList, &bestKce, &prevKce);
+	    break;
+	    }
+	}
+
+
+    boolean printIt = FALSE;
+
+    if (kceList)
+	{	    
+
+	safecpy(chrom, sizeof chrom, bestKce->gene->chrom);
+	start = bestKce->gene->exonStarts[bestKce->exonNumber];
+	end   = bestKce->gene->exonEnds[bestKce->exonNumber];
+
+	//warn("GALT GOT DATA chrom=%s start=%d end=%d lastChrom=%s lastStart=%d lastEnd=%d", 
+	    //chrom, start, end, lastChrom, lastStart, lastEnd); // DEBUG REMOVE
+
+	if (sameString(chrom, lastChrom))
+	    {
+	    if (start <= lastEnd)
+		{
+		//warn("GALT overlap extend start=%d <= lastEnd=%d, end=%d", start, lastEnd, end); // DEBUG REMOVE
+		// overlap detected in knownCanonical
+		// extend current region
+		if (end > lastEnd)
+		    {
+		    lastEnd = end;
+		    }
+		}
+	    else
+		{
+		printIt = TRUE;
+		}
+	    }
+	else
+	    {
+	    printIt = TRUE;
+	    }
+	}
+    else
+	{
+	printIt = TRUE;
+	isEOF = TRUE;
+	}
+
+    //warn("GALT printIt=%d", printIt); // DEBUG REMOVE
+
+    if (printIt)
+	{
+	if (firstTime)
+	    {
+	    firstTime = FALSE;
+	    }
+	else
+	    {
+	    //warn("GALT adding region %s:%d-%d", lastChrom, lastStart, lastEnd); // DEBUG REMOVE
+	    struct virtRegion *v;
+	    AllocVar(v);
+	    v->chrom = cloneString(lastChrom);
+	    v->start = lastStart;
+	    v->end   = lastEnd;
+	    v->strand[0] = '.';  // TODO we should probably just remove the strand field
+	    v->strand[1] = 0;
+	    slAddHead(&virtRegionList, v);
+	    
+	    }
+	}
+
+    if (isEOF && !kceList && !gene)
+	break;
+
+    if (printIt)
+	{
+	safecpy(lastChrom, sizeof lastChrom, chrom);
+	lastStart = start;
+	lastEnd = end;
+	}
+
+    //warn("used up  %s exon# %d", bestKce->gene->name, bestKce->exonNumber);  // DEBUG REMOVE
+    // TODO update or remove current thing from kceList
+    ++bestKce->exonNumber;
+    if (bestKce->exonNumber >= bestKce->gene->exonCount)
+	{  // remove from kceList
+	genePredFree(&bestKce->gene);
+	if (prevKce)
+	    prevKce->next = bestKce->next;
+	else
+	    kceList = bestKce->next;
+	freeMem(bestKce);
+	}
+    
+    }
+sqlFreeResult(&sr);
+slReverse(&virtRegionList);
+hashFree(&kcHash);
+hashFree(&ktHash);
+hFreeConn(&conn);
+}
+
+
+void testRegionList()
+/* check if it is ascending non-overlapping regions. 
+(this is not always a requirement in the most general case, i.e. user-regions)
+*/
+{
+char lastChrom[256];
+int lastEnd = -1;
+struct virtRegion *v;
+warn("testRegionList() started DEBUG."); // DEBUG REMOVE
+for (v=virtRegionList; v; v=v->next)
+    {
+    if (sameString(v->chrom,lastChrom))
+	{
+	if (v->end < v->start)
+	    errAbort("check of region list reveals invalid region %s:%d-%d", v->chrom, v->start, v->end);
+	if (lastEnd > v->start)
+	    errAbort("check of region list reveals overlapping regions region %s:%d-%d lastEnd=%d",  v->chrom, v->start, v->end, lastEnd);
+	}
+    else
+	{
+	safecpy(lastChrom, sizeof lastChrom, v->chrom);
+	}
+    lastEnd = v->end;
+    }
+warn("testRegionList() completed OK.");
+}
+
+
+// multi-window variables global to hgTracks
+
+
+void setGlobalsFromWindow(struct window *window)
+/* set global window values */
+{
+currentWindow = window;
+organism  = window->organism;
+database  = window->database;
+chromName = window->chromName;
+winStart  = window->winStart;
+winEnd    = window->winEnd;
+insideX   = window->insideX;
+insideWidth = window->insideWidth;
+winBaseCount = winEnd - winStart;
+}
+
+
+void initExonStep()
+/* create exon-like pattern with exonSize and stepSize */
+{
+int winCount = cartUsualInt(cart, "demo2NumWindows", demo2NumWindows);
+int i;
+//int avgWidth = fullInsideWidth/winCount ; // can shrink down to 1 ok! // DEBUG REMOVE
+//int x = fullInsideX;
+int exonSize = cartUsualInt(cart, "demo2WindowSize", demo2WindowSize);  //200; //9974; //200;
+int intronSize = cartUsualInt(cart, "demo2StepSize", demo2StepSize); //200; //15000; // really using it like stepSize as that allows overlapping windows.
+struct virtRegion *v;
+for(i=0;i<winCount;++i)
+    {
+    AllocVar(v);
+    //chr21:33,031,597-33,041,570
+    v->chrom = "chr21";
+    v->start = 33031597 - 1 + i*(intronSize);
+    v->end = v->start + exonSize; //33041570;
+    slAddHead(&virtRegionList, v);
+    }
+slReverse(&virtRegionList);
+//if (winCount >= 2)
+//    withNextExonArrows = FALSE;	/* Display next exon navigation buttons near center labels? */
+//warn("winCount=%d, exonSize=%d, intronSize=%d", winCount, exonSize, intronSize); 
+}
+
+void initAllChroms()
+/* initialize virt region list for main chromosomes */
+{
+struct sqlConnection *conn = hAllocConn(database);
+struct sqlResult *sr;
+char **row;
+int winCount = 0;
+char *query =
+"NOSQLINJ select chrom, size from chromInfo"
+" where chrom     like 'chr%'"
+" and   chrom not like '%_random'"
+" and   chrom not like 'chrUn%'";
+// allow alternate haplotypes for now
+//" and   chrom not like '%_hap%'"
+//" and   chrom not like '%_alt'"
+//warn("%s",query);
+struct virtRegion *v;
+sr = sqlGetResult(conn, query);
+while ((row = sqlNextRow(sr)) != NULL)
+    {
+    unsigned chromSize = sqlUnsigned(row[1]);
+    //warn("chrom=%s size=%d", row[0], chromSize); 
+    AllocVar(v);
+    v->chrom = cloneString(row[0]);
+    v->start = 1 - 1;
+    v->end   = chromSize;
+    slAddHead(&virtRegionList, v);
+    ++winCount;
+    //if (winCount >= 2)  // DEBUG HACK REMOVE
+	//break;  
+    }
+sqlFreeResult(&sr);
+hFreeConn(&conn);
+slReverse(&virtRegionList);
+}
+
+
+
+void initWindowsAltLoci()
+/* initialize window list showing alt (alternate haplotype)*/
+{
+
+struct virtRegion *v;
+//chr1:153520530-153700530
+AllocVar(v);
+v->chrom = "chr1";
+v->start = 153520530 - 1;
+v->end   = 153700530;
+//chr1:153,520,529-154,045,739  as a single window
+slAddHead(&virtRegionList, v);
+
+//chr1_GL383518v1_alt:1-182439
+AllocVar(v);
+v->chrom = "chr1_GL383518v1_alt";
+v->start = 1 - 1;
+v->end   = 182439;
+slAddHead(&virtRegionList, v);
+
+//chr1:153865739-154045739
+AllocVar(v);
+v->chrom = "chr1";
+v->start = 153865739 - 1;
+v->end   = 154045739;
+slAddHead(&virtRegionList, v);
+
+slReverse(&virtRegionList);
+
+}
+
+boolean initVirtRegionsFromBedUrl(time_t *bedDateTime)
+/* Read custom regions from BED URL */
+{
+multiRegionsBedUrl = cartUsualString(cart, "multiRegionsBedUrl", multiRegionsBedUrl);
+int bedPadding = 0; // default no padding
+//warn("initVirtRegionsFromBedUrl got here multiRegionsBedUrl=%s", multiRegionsBedUrl); // DEBUG REMOVE
+// TODO add some checks for db change? save in cart var?
+if (sameString(multiRegionsBedUrl,""))
+    {
+    warn("No BED URL specified.");
+    return FALSE;
+    }
+if (!strstr(multiRegionsBedUrl,"://"))
+    {
+    warn("No protocol specified in BED URL %s", multiRegionsBedUrl);
+    return FALSE;
+    }
+struct lineFile *lf = lineFileUdcMayOpen(multiRegionsBedUrl, FALSE);
+if (!lf)
+    {
+    warn("Unable to open [%s] with udc", multiRegionsBedUrl);
+    return FALSE;
+    }
+*bedDateTime = udcTimeFromCache(multiRegionsBedUrl, NULL);
+//warn("unix time = %ld on BED file %s", (long)*bedDateTime, multiRegionsBedUrl); // DEBUG REMOVE
+char *line;
+int lineSize;
+int expectedFieldCount = -1;
+struct bed *bed, *bedList = NULL;
+while (lineFileNext(lf, &line, &lineSize))
+    {
+    // Process comments for keywords like database, shortDesc, and maybe others
+    if (startsWith("#",line))
+	{
+	if (startsWith("#database ",line))
+	    {
+	    char *dbFromBed = line+strlen("#database ");
+	    //warn("got #database setting: %s,  current database %s", dbFromBed, database); // DEBUG REMOVE
+	    if (!sameString(database,dbFromBed))
+		{
+		warn("Multi-Region BED URL error: The database (%s) specified in input does not match current database %s", 
+		   dbFromBed, database);
+		return FALSE;
+		}
+	    }
+	if (startsWith("#shortDesc ",line))
+	    {
+	    virtModeShortDescr = cloneString(line+strlen("#shortDesc "));
+	    //warn("got #shortDesc setting: %s", virtModeShortDescr); // DEBUG REMOVE
+	    }
+	if (startsWith("#padding ",line))
+	    {
+	    bedPadding = sqlSigned(line+strlen("#padding "));
+	    warn("got #padding setting: %d", bedPadding); // DEBUG REMOVE
+	    }
+	continue;
+	}
+
+    char *row[15];
+    int numFields = chopByWhite(line, row, ArraySize(row));
+    if (numFields < 3)
+	{
+        warn("%s doesn't appear to be in BED format. 3 or more fields required, got %d",
+                multiRegionsBedUrl, numFields);
+	return FALSE;
+	}
+    if (expectedFieldCount == -1)
+	{
+	expectedFieldCount = numFields;
+	//warn("got expectedFieldCount=%d", expectedFieldCount); // DEBUG REMOVE
+	}
+    else
+	{
+	//warn("got numFields=%d, expectedFieldCount=%d", numFields, expectedFieldCount); // DEBUG REMOVE
+	if (numFields != expectedFieldCount)
+	    errAbort("Multi-Region BED was detected to have %d columns. But this row has %d columns. "
+	    "All rows except comment lines should have the same number of columns", numFields, expectedFieldCount);
+	}
+
+    //warn("got about to call load and validate bed"); // DEBUG REMOVE
+
+    AllocVar(bed);
+    // All fields are standard BED fields, no bedplus fields supported at this time.
+    // note: this function does not validate chrom name or end beyond chrom size
+    loadAndValidateBed(row, numFields, numFields+0, lf, bed, NULL, TRUE);
+    bed->chrom=cloneString(bed->chrom);  // loadAndValidateBed does not do it for speed. but bedFree needs it.
+    slAddHead(&bedList, bed);
+
+    //warn("got after load and validate bed"); // DEBUG REMOVE
+
+    struct virtRegion *v;
+    if (numFields < 12)
+	{
+	AllocVar(v);
+	v->chrom = cloneString(bed->chrom);
+	v->start = bed->chromStart;
+	v->end   = bed->chromEnd;
+	slAddHead(&virtRegionList, v);
+	//warn("got BED region %s %d %d", v->chrom, v->start, v->end); // DEBUG REMOVE
+	}
+    else
+	{
+	int e;
+	//warn("got BED12 blockCount=%d bed->chrom=%s", bed->blockCount, bed->chrom); // DEBUG REMOVE
+	for (e = 0; e < bed->blockCount; ++e)
+	    {
+	    //warn("got BED12 exon blockSizes[e]=%d chromStarts[e]=%d", bed->blockSizes[e], bed->chromStarts[e]); // DEBUG REMOVE
+	    AllocVar(v);
+	    v->chrom = cloneString(bed->chrom);
+	    v->start = bed->chromStart + bed->chromStarts[e];
+	    v->end   = v->start + bed->blockSizes[e];
+	    //warn("got BED12 exon region %s %d %d", v->chrom, v->start, v->end); // DEBUG REMOVE
+	    slAddHead(&virtRegionList, v);
+	    }
+	}
+
+    }
+lineFileClose(&lf);
+bedFreeList(&bedList);
+slReverse(&virtRegionList);
+if (bedPadding > 0)
+    padVirtRegions(bedPadding);
+return TRUE;
+}
+
+// TODO OBSOLETED by lastDbPosCart
+boolean restoreCartSetting(char *cartSetting)
+/* Restore cart setting from var=val setting. */
+{  
+if (!cartSetting)
+    return FALSE;
+char *eq = strchr(cartSetting,'=');
+if (!eq)  // nothing to do
+    return FALSE;
+*eq = 0;
+char *cartVar = cartSetting;
+char *cartVal = eq+1;
+if (sameString(cartVal, "(null)"))
+    cartRemove(cart, cartVar);
+else
+    cartSetString(cart, cartVar, cartVal);
+*eq = '=';
+return TRUE;
+}
+
+void restoreSavedVirtPosition()
+/* Set state from lastDbPosCart. 
+ * This involves parsing the extra state that was saved.*/
+{
+
+struct hashEl *el, *elList = hashElListHash(lastDbPosCart->hash);
+for (el = elList; el != NULL; el = el->next)
+    {
+    char *cartVar = el->name;
+    char *cartVal = cartOptionalString(lastDbPosCart, cartVar);
+    if (cartVal)
+	{
+	/* do we need this feature?
+	if (sameString(cartVal,"(null)")) 
+	    cartRemove(cart, cartVar);
+	else
+        */	
+	cartSetString(cart, cartVar, cartVal);
+	}
+    }
+hashElFreeList(&elList);
+
+}
+
+void lastDbPosSaveCartSetting(char *cartVar)
+/* Save var and value from cart into lastDbPosCart. */
+{
+cartSetString(lastDbPosCart, cartVar, cartUsualString(cart, cartVar, NULL));
+}
+
+void dySaveCartSetting(struct dyString *dy, char *cartVar, boolean saveBoth)
+/* Grab var and value from cart, save as var=val to dy string. */
+{
+if (dy->stringSize > 0)
+    dyStringAppend(dy, " ");
+dyStringPrintf(dy, "%s=%s", cartVar, cartUsualString(cart, cartVar, NULL));
+if (saveBoth)
+    lastDbPosSaveCartSetting(cartVar);
+}
+
+
+boolean initRegionList()
+/* initialize window list */
+{
+//warn("initRegionList got here 0"); 
+
+// TODO GALT
+// if we are going to support windows from any org and db,
+// then we are going to have to loosen up assumptions
+// about trackList consistencey across windows.
+// But it seems like much of the rest of the code would work.
+// Not sure about speed either -- but might be ok.
+//
+//  update, well by 2015-04-28 it seems like we are not going to support windows from other assemblies
+// due to difficulties with tracklist.
+
+
+struct virtRegion *v;
+virtRegionList = NULL;
+virtModeExtraState = "";   // This is state that determines if the virtChrom has changed
+lastDbPosCart = cartOfNothing();  // USED to store and restore cart settings related to position and virtMode
+struct dyString *dy = dyStringNew(256);  // used to build virtModeExtraState
+
+if (sameString(virtModeType, "default"))
+    { 
+    //warn("demo type single original window"); // DEBUG REMOVE
+    // Single window same as normal window
+    // mostly good to test nothing was broken with single window
+    AllocVar(v);
+
+    v->chrom = chromName;
+    v->start = 0;
+    v->end = hChromSize(database, chromName);
+
+    virtWinStart = winStart;
+    virtWinEnd   = winEnd;
+
+    slAddHead(&virtRegionList, v);
+    slReverse(&virtRegionList);
+    }
+else if (sameString(virtModeType, "exonMostly")
+      || sameString(virtModeType, "geneMostly"))
+    {
+
+    //warn("emGeneTable %s", emGeneTable); // DEBUG REMOVE
+    // Gencode settings: comprehensive, alt-splice, non-coding
+
+    char *knownCanonical = NULL;  // show splice-variants, not filtered out via knownCanonical
+    boolean showNoncoding = TRUE; // show non-coding where cdsStart==cdsEnd
+    char *knownToTag = NULL;      // show comprehensive set not filtered by knownToTag
+    char varName[SMALLBUF];
+    boolean geneMostly = FALSE;
+
+    lastDbPosSaveCartSetting("emGeneTable");
+
+    //DISGUISE makes obsolete dySaveCartSetting(dy, "emGeneTable");
+    //DISGUISE makes obsolete dySaveCartSetting(dy, "emPadding");
+    if (sameString(virtModeType, "geneMostly"))
+	geneMostly = TRUE;
+    if (sameString(emGeneTable, "knownGene"))
+	{
+	// test cart var knownGene.show.noncoding
+        // check for alternate table name.
+        // if found, set and pass to gene-table reading routine
+
+        // Some code borrowed from simpleTracks.c::loadKnownGene()
+
+	safef(varName, sizeof(varName), "%s.show.noncoding", emGeneTable);
+	showNoncoding = cartUsualBoolean(cart, varName, TRUE);
+	//DISGUISE makes obsolete dySaveCartSetting(dy, varName);
+	safef(varName, sizeof(varName), "%s.show.spliceVariants", emGeneTable);
+	boolean showSpliceVariants = cartUsualBoolean(cart, varName, TRUE);
+	//DISGUISE makes obsolete dySaveCartSetting(dy, varName);
+	if (!showSpliceVariants)
+	    {
+	    char *canonicalTable = trackDbSettingOrDefault(emGeneTrack->tdb, "canonicalTable", "knownCanonical");
+	    if (hTableExists(database, canonicalTable))
+		knownCanonical = canonicalTable;
+	    }
+	safef(varName, sizeof(varName), "%s.show.comprehensive", emGeneTable);
+	boolean showComprehensive = cartUsualBoolean(cart, varName, FALSE);
+	//DISGUISE makes obsolete dySaveCartSetting(dy, varName);
+	if (!showComprehensive)
+	    {
+	    if (hTableExists(database, "knownToTag"))
+		{
+		knownToTag = "knownToTag";
+		}
+	    }
+
+	}
+    if (sameString(emGeneTable, "refGene"))
+	{
+	char varName[SMALLBUF];
+	safef(varName, sizeof(varName), "%s.hideNoncoding", emGeneTable);
+	showNoncoding = !cartUsualBoolean(cart, varName, FALSE);
+	//DISGUISE makes obsolete dySaveCartSetting(dy, varName);
+	}
+
+    initVirtRegionsFromEMGeneTableExons(showNoncoding, knownCanonical, knownToTag, geneMostly);
+    //warn("slCount(virtRegionList)=%d", slCount(virtRegionList));  // DEBUG REMOVE
+    if (geneMostly)
+	virtModeShortDescr = "genes";
+    else
+	virtModeShortDescr = "exons";
+    // DISGUISE makes obsolete dyStringPrintf(dy," %s %s", dy->string, knownCanonical, knownToTag);
+    }
+else if (sameString(virtModeType, "kcGenes")) // TODO obsolete
+    {
+    //warn("demo KnownCanonical gene regions genome-wide."); // DEBUG REMOVE
+    initVirtRegionsFromKnownCanonicalGenes("knownCanonical"); 
+    virtModeShortDescr = "genes";
+    }
+else if (sameString(virtModeType, "customUrl"))
+    {
+    //warn("custom regions from BED URL."); // DEBUG REMOVE
+    virtModeShortDescr = "customUrl";  // can be overridden by comment in input bed file
+    time_t bedDateTime = 0;
+    if (!initVirtRegionsFromBedUrl(&bedDateTime))
+	{
+	return FALSE; // return to default mode
+	}
+    dySaveCartSetting(dy, "multiRegionsBedUrl", TRUE);
+    dyStringPrintf(dy, " %ld", (long)bedDateTime);
+    }
+else if (sameString(virtModeType, "singleTrans"))
+    {
+    //warn("Single Transcript Id"); // DEBUG REMOVE
+    singleTransId = cartUsualString(cart, "singleTransId", singleTransId); 
+    if (sameString(singleTransId, ""))
+	{
+	warn("Single transcript Id should not be blank");
+	return FALSE; // return to default mode
+	}
+    setEMGeneTrack();
+    dySaveCartSetting(dy, "singleTransId", TRUE);
+    }
+else if (sameString(virtModeType, "singleAltHaplo"))
+    {
+    //warn("Single Haplotype Id"); // DEBUG REMOVE
+    singleAltHaploId = cartUsualString(cart, "singleAltHaploId", singleAltHaploId); // currently default is chr6_cox_hap2
+    initAllChroms();  // we want to default to full genome view.
+    if (!initSingleAltHaplotype(singleAltHaploId))
+	{
+	virtRegionList = NULL;
+	return FALSE; // return to default mode
+	}
+    virtModeShortDescr = "alt haplo";  // was "single haplo" but that might confuse some users.
+    dySaveCartSetting(dy, "singleAltHaploId", TRUE);
+    }
+else if (sameString(virtModeType, "allChroms"))
+    {
+    //warn("show all regular chromosomes (not alts)\n"
+	//"Warning must turn off all tracks except big*"); // DEBUG REMOVE
+    initAllChroms();
+    }
+else if (sameString(virtModeType, "demo1"))
+    {
+    //warn("demo two windows on two chroms (default posn chr21, and same loc on chr22"); // DEBUG REMOVE
+
+    // NOTE we are losing the ability to specify org and db
+   
+    //chr21:33,031,597-33,041,570
+    AllocVar(v);
+    //chr21:33,031,597-33,041,570
+    v->chrom = "chr21";
+    v->start = 33031597 - 1;
+    v->end = 33041570;
+    slAddHead(&virtRegionList, v);
+
+    struct virtRegion *v2;
+
+    AllocVar(v2);
+    //chr22:33,031,597-33,041,570
+    //window2->organism  = "Mouse";
+    //window2->database  = "mm10";
+    //window2->database  = "hg38";
+    v2->chrom = "chr22";
+    v2->start = 33031597 - 1;
+    v2->end = 33041570;
+    slAddHead(&virtRegionList, v2);
+
+    slReverse(&virtRegionList);
+    }
+else if (sameString(virtModeType, "demo2"))
+    {
+    //warn("demo multiple (70) windows on one chrom chr21 def posn, window size and step exon-like"); // DEBUG REMOVE
+    initExonStep();
+    }
+else if (sameString(virtModeType, "demo4"))
+    {
+    //warn("demo multiple (20) windows showing exons from TITIN gene uc031rqd.1."); // DEBUG REMOVE
+    initVirtRegionsFromEmGeneTable("uc031rqd.1"); // TITIN // "uc002ypa.3"); // SOD1
+    }
+else if (sameString(virtModeType, "demo5"))
+    {
+    //warn("demo alt locus on hg38\n"
+	//"Shows alt chrom surrounded by preceding and following regions of same size from reference genome "); // DEBUG REMOVE
+    initWindowsAltLoci();
+    }
+else if (sameString(virtModeType, "demo6"))
+    {
+    //warn("demo SOD1\n"
+	//"Shows zoomed in exon-exon junction from SOD1 gene, between exon1 and exon2."); // DEBUG REMOVE
+
+    initVirtRegionsFromSOD1Hardwired();
+
+    }
+else
+    {
+    //warn("unrecognized virtModeType = %s", virtModeType);
+    return FALSE; // return to default mode
+    }
+
+virtModeExtraState = dyStringCannibalize(&dy);
+
+return TRUE;
+
+}
+
+boolean isLimitedVisHiddenForAllWindows(struct track *track)
+/* Check if track limitedVis == hidden for all windows. 
+ * Return true if all are hidden */
+{
+boolean result = TRUE;
+for(;track;track=track->nextWindow)
+    if (track->limitedVis != tvHide)
+	result = FALSE;
+return result;
+}
+
+boolean isTypeBedLike(struct track *track)
+/* Check if track type is BED-like packable thing (but not rmsk or joinedRmsk) */
+{ // TODO GALT do we have all the types needed?
+// TODO could it be as simple as whether track->items exists?
+char *typeLine = track->tdb->type, *words[8], *type;
+int wordCount;
+//warn("track %s has type %s", track->track, typeLine); 
+if (typeLine == NULL)
+    return FALSE;
+wordCount = chopLine(cloneString(typeLine), words);
+if (wordCount <= 0)
+    return FALSE;
+type = words[0];
+//warn("track %s has type word[0] = %s, canPack=%d", track->track, type, track->canPack); 
+if (
+(  sameWord(type, "bed")
+|| sameWord(type, "bed5FloatScore")
+|| sameWord(type, "bed6FloatScore")
+|| sameWord(type, "bedDetail")
+|| sameWord(type, "bigBed")
+|| sameWord(type, "bigGenePred")
+|| sameWord(type, "broadPeak")
+|| sameWord(type, "chain")
+|| sameWord(type, "factorSource")
+|| sameWord(type, "genePred")
+|| sameWord(type, "gvf")
+|| sameWord(type, "narrowPeak")
+|| sameWord(type, "psl")
+//|| track->loadItems == loadSimpleBed
+//|| track->bedSize >= 3 // should pick up several ENCODE BED-Plus types.
+) 
+&& track->canPack
+   )
+    {
+    //warn("track %s is BEDLIKE", track->track); 
+    return TRUE;
+    }
+
+//warn("track %s is NOT BEDLIKE", track->track); 
+return FALSE;
+}
+
+boolean isTypeUseItemNameAsKey(struct track *track)
+/* Check if track type is like expRatio and key is just item name. */
+{ 
+
+char *typeLine = track->tdb->type, *words[8], *type;
+int wordCount;
+//warn("track %s has type %s", track->track, typeLine); 
+if (typeLine == NULL)
+    return FALSE;
+wordCount = chopLine(cloneString(typeLine), words);
+if (wordCount <= 0)
+    return FALSE;
+type = words[0];
+//warn("track %s has type word[0] = %s", track->track, type); 
+if (sameWord(type, "expRatio"))
+    {
+    //warn("track %s is like expRatio, needs one row per item", track->track); 
+    return TRUE;
+    }
+
+//warn("track %s is NOT like expRatio, which needs only one row per item", track->track); 
+return FALSE;
+}
+
+void setFlatTrackMaxHeight(struct flatTracks *flatTrack, int fontHeight)
+/* for each flatTrack, figure out maximum height needed from all windows */
+{
+struct track *track = flatTrack->track;
+int maxHeight = 0;
+struct track *winTrack;
+struct window *window;
+for (window=windows, winTrack=track; window; window=window->next, winTrack=winTrack->nextWindow)
+    {
+    setGlobalsFromWindow(window);
+
+    int trackHeight =  trackPlusLabelHeight(winTrack, fontHeight);
+
+    //warn("track %s height=%d isCenterLabelIncluded(winTrack)=%d\n", winTrack->track, trackHeight, isCenterLabelIncluded(winTrack)); // DEBUG REMOVE
+    //fflush(stdout); // DEBUG REMOVE
+
+    if (trackHeight > maxHeight)
+	maxHeight = trackHeight;
+    }
+setGlobalsFromWindow(windows); // first window
+
+flatTrack->maxHeight = maxHeight;
+
+//warn("track %s maxHeight=%d\n", track->track, maxHeight); // DEBUG REMOVE
+//fflush(stdout); // DEBUG REMOVE
+}
+
+void makeActiveImage(struct track *trackList, char *psOutput)
+/* Make image and image map. */
+{
+
+boolean galtDebug = FALSE;
+
+if (galtDebug)
+warn("makeActiveImage begins");
+
+struct window *window = NULL;
+
+struct track *track;
+MgFont *font = tl.font;
+struct hvGfx *hvg;
+struct tempName pngTn;
+char *mapName = "map";
+int fontHeight = mgFontLineHeight(font);
+int trackPastTabX = (withLeftLabels ? trackTabWidth : 0);
+int trackTabX = gfxBorder;
+int trackPastTabWidth = tl.picWidth - trackPastTabX;
+int pixWidth, pixHeight;
+int y=0;
+int titleHeight = fontHeight;
+int scaleBarPad = 2;
+int scaleBarHeight = fontHeight;
+int scaleBarTotalHeight = fontHeight + 2 * scaleBarPad;
+int showPosHeight = fontHeight;
+int rulerHeight = fontHeight;
+int baseHeight = fontHeight;
+int basePositionHeight = rulerHeight;
+int codonHeight = fontHeight;
+int rulerTranslationHeight = codonHeight * 3;        // 3 frames
+int yAfterRuler = gfxBorder;
+int yAfterBases = yAfterRuler;  // differs if base-level translation shown
+boolean rulerCds = zoomedToCdsColorLevel;
+int rulerClickHeight = 0;
+int newWinWidth = 0;
+
+/* Figure out dimensions and allocate drawing space. */
+
+pixWidth = tl.picWidth;
+
+leftLabelX = gfxBorder;
+leftLabelWidth = insideX - gfxBorder*3;
+
+struct image *theOneImg  = NULL; // No need to be global, only the map needs to be global
+struct image *theSideImg = NULL; // Because dragScroll drags off end of image,
+                                 //    the side label gets seen. Therefore we need 2 images!!
+//struct imgTrack *curImgTrack = NULL; // Make this global for now to avoid huge rewrite
+struct imgSlice *curSlice    = NULL; // No need to be global, only the map needs to be global
+//struct mapSet   *curMap      = NULL; // Make this global for now to avoid huge rewrite
+// Set up imgBox dimensions
+int sliceWidth[stMaxSliceTypes]; // Just being explicit
+int sliceOffsetX[stMaxSliceTypes];
+int sliceHeight        = 0;
+int sliceOffsetY       = 0;
+char *rulerTtl = NULL;
+
+//warn("theImgBox=%lu", (unsigned long)theImgBox); // DEBUG REMOVE
+
+if (theImgBox)
+// if theImgBox == NULL then we are rendering a simple single image such as right-click View image.
+// theImgBox is a global for now to avoid huge rewrite of hgTracks.  It is started
+// prior to this in doTrackForm()
+    {
+    rulerTtl = "drag select or click to zoom";
+    hPrintf("<input type='hidden' name='db' value='%s'>\n", database);
+    hPrintf("<input type='hidden' name='c' value='%s'>\n", chromName);
+    hPrintf("<input type='hidden' name='l' value='%d'>\n", winStart);
+    hPrintf("<input type='hidden' name='r' value='%d'>\n", winEnd);
+    hPrintf("<input type='hidden' name='pix' value='%d'>\n", tl.picWidth);
+    // If a portal was established, then set the global dimensions to the entire expanded image size
+    if (imgBoxPortalDimensions(theImgBox,&virtWinStart,&virtWinEnd,&(tl.picWidth),NULL,NULL,NULL,NULL,NULL))
+        {
+        pixWidth = tl.picWidth;
+        virtWinBaseCount = virtWinEnd - virtWinStart;
+        fullInsideWidth = tl.picWidth - gfxBorder - fullInsideX;
+        }
+    memset((char *)sliceWidth,  0,sizeof(sliceWidth));
+    memset((char *)sliceOffsetX,0,sizeof(sliceOffsetX));
+    if (withLeftLabels)
+        {
+        sliceWidth[stButton]   = trackTabWidth + 1;
+        sliceWidth[stSide]     = leftLabelWidth - sliceWidth[stButton] + 1;
+        sliceOffsetX[stSide]   =
+                          (revCmplDisp ? (tl.picWidth - sliceWidth[stSide] - sliceWidth[stButton])
+                                       : sliceWidth[stButton]);
+        sliceOffsetX[stButton] = (revCmplDisp ? (tl.picWidth - sliceWidth[stButton]) : 0);
+        }
+    sliceOffsetX[stData] = (revCmplDisp ? 0 : sliceWidth[stSide] + sliceWidth[stButton]);
+    sliceWidth[stData]   = tl.picWidth - (sliceWidth[stSide] + sliceWidth[stButton]);
+    }
+struct flatTracks *flatTracks = NULL;
+struct flatTracks *flatTrack = NULL;
+
+if (rulerMode != tvFull)
+    {
+    rulerCds = FALSE;
+    }
+
+/* Figure out height of each visible track. */
+pixHeight = gfxBorder;
+
+// figure out height of ruler
+if (rulerMode != tvHide)
+    {
+    if (!baseShowRuler && !baseTitle && !baseShowPos && !baseShowAsm && !baseShowScaleBar && !zoomedToBaseLevel && !rulerCds)
+        {
+        warn("Can't turn everything off in base position track.  Turning ruler back on");
+        baseShowRuler = TRUE;
+        cartSetBoolean(cart, BASE_SHOWRULER, TRUE);
+        }
+
+    if (baseTitle)
+        basePositionHeight += titleHeight;
+
+    if (baseShowPos||baseShowAsm)
+        basePositionHeight += showPosHeight;
+
+    if (baseShowScaleBar)
+        basePositionHeight += scaleBarTotalHeight;
+
+    if (!baseShowRuler)
+        {
+        basePositionHeight -= rulerHeight;
+        rulerHeight = 0;
+        }
+
+    if (zoomedToBaseLevel)
+        basePositionHeight += baseHeight;
+
+    yAfterRuler += basePositionHeight;
+    yAfterBases = yAfterRuler;
+    pixHeight += basePositionHeight;
+    if (rulerCds)
+        {
+        yAfterRuler += rulerTranslationHeight;
+        pixHeight += rulerTranslationHeight;
+        }
+    }
+
+
+/* Hash tracks/subtracks, limit visibility and calculate total image height: */
+
+// For multiple windows, sets height and visibility
+//       as well as making the flatTrack list.
+
+if (galtDebug)
+warn("organism=%s database=%s", organism, database); // DEBUG REMOVE
+
+// TODO there is a chance that for pack and squish
+// I might need to trigger a track-height check here
+// since it is after all items are loaded for all windows
+// but before things are checked for overflow or limitedVis?
+// The fixed non-overflow tracks like knownGene used to initialize 
+// ss and track height during loadItems(). That was delayed
+// because we now need all windows to be fully loaded before
+// calculating their joint ss layout and height.
+
+// set heights and visibilities.
+if (galtDebug)
+warn("set heights and visibilities"); // DEBUG REMOVE
+for(window=windows;window;window=window->next)
+    {
+    //warn("**** window=%lu insideX=%d insideWidth=%d", (unsigned long) window, window->insideX, window->insideWidth); // DEBUG REMOVE
+    setGlobalsFromWindow(window);
+    trackList = window->trackList;
+    for (track = trackList; track != NULL; track = track->next)
+	{
+	if (tdbIsCompositeChild(track->tdb)) // When single track is requested via AJAX,
+	    limitedVisFromComposite(track);  // it could be a subtrack
+	else
+	    {
+	    //warn("DEBUG REMOVE regular track %s", track->track);  // DEBUG REMOVE
+	    limitVisibility(track);
+	    }
+
+	if (tdbIsComposite(track->tdb))
+	    {
+	    struct track *subtrack;
+	    for (subtrack = track->subtracks; subtrack != NULL; subtrack = subtrack->next)
+		{
+		if (!isSubtrackVisible(subtrack))
+		    continue;
+
+		// subtrack vis can be explicit or inherited from composite/view.
+		// Then it could be limited because of pixel height
+		limitedVisFromComposite(subtrack);
+
+		if (subtrack->limitedVis != tvHide)
+		    {
+		    subtrack->hasUi = track->hasUi;
+		    }
+		}
+	    }
+
+	//warn("heights and vis: track %s vis=%d limitedVis==%d limitedVisSet=%d", track->track, track->visibility, track->limitedVis, track->limitedVisSet); // DEBUG REMOVE
+	}
+    }
+trackList = windows->trackList;
+setGlobalsFromWindow(windows); // first window
+
+// Construct flatTracks 
+if (galtDebug)
+warn("Construct flatTracks");
+for (track = trackList; track != NULL; track = track->next)
+    {
+    //warn("construct flat track %s vis=%d limitedVis==%d", track->track, track->visibility, track->limitedVis); // DEBUG REMOVE
+    if (tdbIsComposite(track->tdb))
+        {
+        struct track *subtrack;
+        for (subtrack = track->subtracks; subtrack != NULL; subtrack = subtrack->next)
+            {
+            if (!isSubtrackVisible(subtrack))
+                continue;
+
+	    //warn("about to call isLimitedVisHiddenForAllWindows(%s) on subtrack", subtrack->track);
+	    if (!isLimitedVisHiddenForAllWindows(subtrack))
+                {
+		//warn("adding flatTrack subtrack (%s)", subtrack->track); // DEBUG REMOVE
+                flatTracksAdd(&flatTracks,subtrack,cart);
+                }
+            }
+        }
+    else
+	{	
+	//warn("about to call isLimitedVisHiddenForAllWindows(%s) on track", track->track);// DEBUG REMOVE
+	if (!isLimitedVisHiddenForAllWindows(track))
+	    {
+	    //warn("adding flatTrack track (%s)", track->track); // DEBUG REMOVE
+	    flatTracksAdd(&flatTracks,track,cart);
+	    }
+	}
+    }
+//warn("slCount(flatTracks)=%d", slCount(flatTracks)); // DEBUG REMOVE
+flatTracksSort(&flatTracks); // Now we should have a perfectly good flat track list!
+
+
+// for each track, figure out maximum height needed from all windows
+if (galtDebug)
+warn("for each track, figure out maximum height needed from all windows");
+for (flatTrack = flatTracks; flatTrack != NULL; flatTrack = flatTrack->next)
+    {
+    track = flatTrack->track;
+
+    if (track->limitedVis == tvHide)
+	{
+	//warn("flat track %s limitedVis==tvHide. why?", track->track); // DEBUG REMOVE
+	continue;
+	}
+
+    setFlatTrackMaxHeight(flatTrack, fontHeight);
+    
+    }
+
+//warn("got done with flatTrack->maxHeight");
+//fflush(stdout); // DEBUG REMOVE
+
+
+// fill out track->prevTrack, and check for maxSafeHeight
+if (galtDebug)
+warn("fill out track->prevTrack, and check for maxSafeHeight");
+boolean safeHeight = TRUE;
+/* firefox on Linux worked almost up to 34,000 at the default 620 width */
+#define maxSafeHeight   32000
+struct track *prevTrack = NULL;
+for (flatTrack = flatTracks,prevTrack=NULL; flatTrack != NULL; flatTrack = flatTrack->next)
+    {
+    track = flatTrack->track;
+    assert(track->limitedVis != tvHide);
+    // ORIG int totalHeight = pixHeight+trackPlusLabelHeight(track,fontHeight);
+    //warn("track %s flat maxHeight=%d", track->track, flatTrack->maxHeight); // DEBUG REMOVE
+    int totalHeight = pixHeight+flatTrack->maxHeight;
+    if (totalHeight > maxSafeHeight)
+        {
+        char numBuf[SMALLBUF];
+        sprintLongWithCommas(numBuf, maxSafeHeight);
         if (safeHeight)  // Only one message
             warn("Image is over %s pixels high (%d pix) at the following track which is now "
                  "hidden:<BR>\"%s\".%s", numBuf, totalHeight, track->tdb->longLabel,
                  (flatTrack->next != NULL ?
                       "<BR>Additional tracks may have also been hidden at this zoom level." : ""));
         safeHeight = FALSE;
+	struct track *winTrack;
+	for(winTrack=track;winTrack;winTrack=winTrack->nextWindow)
+	    {
 	    track->limitedVis = tvHide;
 	    track->limitedVisSet = TRUE;
 	    }
-    if (track->limitedVis != tvHide)
+        }
+    if (!isLimitedVisHiddenForAllWindows(track))
         {
-        track->prevTrack = prevTrack; // Important for keeping track of conditional center labels!
-        pixHeight += trackPlusLabelHeight(track, fontHeight);
+	//warn("setting winTracks ->prevTrack to %lu for all windows", (unsigned long) prevTrack);  // DEBUG REMOVE
+	struct track *winTrack;
+	for(winTrack=track;winTrack;winTrack=winTrack->nextWindow)
+	    { // TODO this is currently still only using one prev track value.
+	    winTrack->prevTrack = prevTrack; // Important for keeping track of conditional center labels!
+	    }
+        // ORIG pixHeight += trackPlusLabelHeight(track, fontHeight);
+	if (!theImgBox) // prevTrack may have altered the height, so recalc height
+	    setFlatTrackMaxHeight(flatTrack, fontHeight);
+	pixHeight += flatTrack->maxHeight;
         prevTrack = track;
         }
     }
 
+if (galtDebug)
+warn("About to allocate hvg png pixWidth=%d, pixHeight=%d", pixWidth, pixHeight);
+fflush(stdout); // DEBUG REMOVE
+
 imagePixelHeight = pixHeight;
 if (psOutput)
     {
     hvg = hvGfxOpenPostScript(pixWidth, pixHeight, psOutput);
     hvgSide = hvg; // Always only one image
     }
 else
     {
     boolean transparentImage = FALSE;
     if (theImgBox!=NULL)
         transparentImage = TRUE;   // transparent because BG (blue ruler lines) is separate image
 
     if (measureTiming)
         measureTime("Time at start of obtaining trash hgt png image file");
     trashDirFile(&pngTn, "hgt", "hgt", ".png");
     hvg = hvGfxOpenPng(pixWidth, pixHeight, pngTn.forCgi, transparentImage);
 
     if (theImgBox)
         {
         // Adds one single image for all tracks (COULD: build the track by track images)
         theOneImg = imgBoxImageAdd(theImgBox,pngTn.forHtml,NULL,pixWidth, pixHeight,FALSE);
         theSideImg = theOneImg; // Unlkess this is overwritten below, there is a single image
         }
 
-    hvgSide = hvg; // Unlkess this is overwritten below, there is a single image
+    hvgSide = hvg; // Unless this is overwritten below, there is a single image
 
     if (theImgBox && theImgBox->showPortal && withLeftLabels)
         {
         // TODO: It would be great to make the two images smaller,
         //       but keeping both the same full size for now
         struct tempName pngTnSide;
         trashDirFile(&pngTnSide, "hgtSide", "side", ".png");
         hvgSide = hvGfxOpenPng(pixWidth, pixHeight, pngTnSide.forCgi, transparentImage);
 
         // Also add the side image
         theSideImg = imgBoxImageAdd(theImgBox,pngTnSide.forHtml,NULL,pixWidth, pixHeight,FALSE);
         hvgSide->rc = revCmplDisp;
         initColors(hvgSide);
         }
     }
 hvg->rc = revCmplDisp;
 initColors(hvg);
 
 /* Start up client side map. */
 hPrintf("<MAP id='map' Name=%s>\n", mapName);
 
-if (theImgBox == NULL)  // imageV2 highlighting is done by javascript.
-    highlightRegion(cart, hvg, insideX, imagePixelHeight, winStart, winEnd);
+if (theImgBox == NULL)  // imageV2 highlighting is done by javascript. This does pdf and view-image highlight
+    highlightRegion(cart, hvg, imagePixelHeight);
 
+for (window=windows; window; window=window->next)
+    {  // TODO GALT do I need to set globals here?
+    //setGlobalsFromWindow(window); // TEMP HACK DOES THIS HELP?
     /* Find colors to draw in. */
-findTrackColors(hvg, trackList);
+    findTrackColors(hvg, window->trackList);
+    }
+//setGlobalsFromWindow(windows); // first window // TEMP HACK DOES THIS HELP? // REMOVE?
+
 
 // Good to go ahead and add all imgTracks regardless of buttons, left label, centerLabel, etc.
 if (theImgBox)
     {
     if (rulerMode != tvHide)
         {
         curImgTrack = imgBoxTrackFindOrAdd(theImgBox,NULL,RULER_TRACK_NAME,rulerMode,FALSE,
                                            IMG_FIXEDPOS); // No tdb, no centerLbl, not reorderable
         }
 
     for (flatTrack = flatTracks; flatTrack != NULL; flatTrack = flatTrack->next)
         {
         track = flatTrack->track;
-        if (track->limitedVis != tvHide)
+	if (!isLimitedVisHiddenForAllWindows(track))
+            {
+	    //warn("hvGfxFindRgb !isLimitedVisHiddenForAllWindows(%s)", track->track);  // DEBUG REMOVE
+	    struct track *winTrack;
+	    for (winTrack=track; winTrack; winTrack=winTrack->nextWindow)
+	    //for (window=windows, winTrack=track; window; window=window->next, winTrack=winTrack->nextWindow)  // TEMP HACK DOES THIS HELP?
+		{ // TODO GALT do I need to set globals here?
+		//warn("hvGfxFindRgb (%s) winTrack labelColor=%d ixColor=%d color=%s", track->track, winTrack->labelColor, winTrack->ixColor, rgbColorToString(winTrack->color));  // DEBUG REMOVE
+		if (winTrack->labelColor == winTrack->ixColor && winTrack->ixColor == 0)
 		    {
-            if (track->labelColor == track->ixColor && track->ixColor == 0)
-                track->ixColor = hvGfxFindRgb(hvg, &track->color);
+		    //warn("hvGfxFindRgb got here window : %s %s %s:%d-%d offset %d width %d", // DEBUG REMOVE
+			  //window->organism, window->database, window->chromName, window->winStart+1, window->winEnd, window->insideX, window->insideWidth);
+		    //setGlobalsFromWindow(window); // TEMP HACK DOES THIS HELP?
+
+		    winTrack->ixColor = hvGfxFindRgb(hvg, &winTrack->color);
+
+		    //setGlobalsFromWindow(windows); // first window // TEMP HACK DOES THIS HELP? // REMOVE?
+		    }
+		}
             int order = flatTrack->order;
             curImgTrack = imgBoxTrackFindOrAdd(theImgBox,track->tdb,NULL,track->limitedVis,
                                                isCenterLabelIncluded(track),order);
             if (trackShouldUseAjaxRetrieval(track))
                 imgTrackMarkForAjaxRetrieval(curImgTrack,TRUE);
             }
         }
     }
 
+
 /* Draw mini-buttons. */
+if (galtDebug)
+warn("Draw mini-buttons.");
 if (withLeftLabels && psOutput == NULL)
     {
     int butOff;
     boolean grayButtonGroup = FALSE;
     struct group *lastGroup = NULL;
     y = gfxBorder;
     if (rulerMode != tvHide)
         {
         /* draw button for Base Position pseudo-track */
         int height = basePositionHeight;
         if (rulerCds)
             height += rulerTranslationHeight;
         if (theImgBox)
             {
             // Mini-buttons (side label slice) for ruler
@@ -2348,31 +4852,32 @@
         else if (!trackImgOnly) // Side buttons only need to be drawn when drawing page with js
             {                   // advanced features off  // TODO: Should remove wasted pixels too
             drawGrayButtonBox(hvgSide, trackTabX, y, trackTabWidth, height, TRUE);
             }
         mapBoxTrackUi(hvgSide, trackTabX, y, trackTabWidth, height,
                       RULER_TRACK_NAME, RULER_TRACK_LABEL, "ruler");
         y += height + 1;
         }
 
     for (flatTrack = flatTracks; flatTrack != NULL; flatTrack = flatTrack->next)
         {
         track = flatTrack->track;
         int h, yStart = y, yEnd;
         if (track->limitedVis != tvHide)
             {
-            y += trackPlusLabelHeight(track, fontHeight);
+            // ORIG y += trackPlusLabelHeight(track, fontHeight);
+	    y += flatTrack->maxHeight;  // DEBUG REMOVE
             yEnd = y;
             h = yEnd - yStart - 1;
 
             /* alternate button colors for track groups*/
             if (track->group != lastGroup)
                 grayButtonGroup = !grayButtonGroup;
             lastGroup = track->group;
             if (theImgBox)
                 {
                 // Mini-buttons (side label slice) for tracks
                 sliceHeight      = yEnd - yStart;
                 sliceOffsetY     = yStart - 1;
                 curImgTrack = imgBoxTrackFind(theImgBox,track->tdb,NULL);
                 //warn("GTEX 2: track %s, sliceHeight=%d\n", track->shortLabel, sliceHeight);
                 curSlice    = imgTrackSliceUpdateOrAdd(curImgTrack,stButton,NULL,NULL,
@@ -2394,30 +4899,34 @@
                     struct trackDb *parent = tdbGetComposite(track->tdb);
                     mapBoxTrackUi(hvgSide, trackTabX, yStart, trackTabWidth, (yEnd - yStart - 1),
                                   parent->track, parent->shortLabel, track->track);
                     }
                 else
                     mapBoxTrackUi(hvgSide, trackTabX, yStart, trackTabWidth, h, track->track,
                                   track->shortLabel, track->track);
                 }
             }
         }
     butOff = trackTabX + trackTabWidth;
     leftLabelX += butOff;
     leftLabelWidth -= butOff;
     }
 
+
+/* Draw left labels. */
+if (galtDebug)
+warn("Draw left labels");
 if (withLeftLabels)
     {
     if (theImgBox == NULL)
         {
         Color lightRed = hvGfxFindColorIx(hvgSide, 255, 180, 180);
 
         hvGfxBox(hvgSide, leftLabelX + leftLabelWidth, 0,
                  gfxBorder, pixHeight, lightRed);
         }
     y = gfxBorder;
     if (rulerMode != tvHide)
         {
         if (theImgBox)
             {
             // side label slice for ruler
@@ -2478,249 +4987,450 @@
                 drawComplementArrow(hvgSide,leftLabelX, y, leftLabelWidth-1, baseHeight, font);
             if (zoomedToBaseLevel)
                 y += baseHeight;
             }
         if (rulerCds)
             y += rulerTranslationHeight;
         }
     for (flatTrack = flatTracks; flatTrack != NULL; flatTrack = flatTrack->next)
         {
         track = flatTrack->track;
         if (track->limitedVis == tvHide)
             continue;
         if (theImgBox)
             {
             // side label slice for tracks
-            sliceHeight      = trackPlusLabelHeight(track, fontHeight);
+            //ORIG sliceHeight      = trackPlusLabelHeight(track, fontHeight);
+	    sliceHeight      = flatTrack->maxHeight;  // DEBUG REMOVE
             sliceOffsetY     = y;
             curImgTrack = imgBoxTrackFind(theImgBox,track->tdb,NULL);
                 //warn("GTEX 4: track %s, sliceHeight=%d\n", track->shortLabel, sliceHeight);
             curSlice    = imgTrackSliceUpdateOrAdd(curImgTrack,stSide,theSideImg,NULL,
                                                    sliceWidth[stSide],sliceHeight,
                                                    sliceOffsetX[stSide],sliceOffsetY);
             (void) sliceMapFindOrStart(curSlice,track->tdb->track,NULL); // No common linkRoot
             }
         if (trackShouldUseAjaxRetrieval(track))
             y += REMOTE_TRACK_HEIGHT;
         else
             {
         #ifdef IMAGEv2_NO_LEFTLABEL_ON_FULL
             if (theImgBox && track->limitedVis != tvDense)
                 y += sliceHeight;
             else
         #endif ///def IMAGEv2_NO_LEFTLABEL_ON_FULL
-                y = doLeftLabels(track, hvgSide, font, y);
+		{
+		setGlobalsFromWindow(windows); // use GLOBALS from first window
+		int ynew = doLeftLabels(track, hvgSide, font, y);
+		//setGlobalsFromWindow(windows); // first window  // REMOVE? this is the same as above
+
+		y += flatTrack->maxHeight;  // DEBUG REMOVE
+		if ((ynew - y) > flatTrack->maxHeight)
+		    { // TODO should be errAbort?
+		    warn("doLeftLabels(y=%d) returned new y value %d that is too high - should be %d at most.", 
+			y, ynew, flatTrack->maxHeight);
+		    }
+		}
             }
         }
     }
 else
     {
     leftLabelX = leftLabelWidth = 0;
     }
 
+
+//goto drawNow;
+
+
+//setGlobalsFromWindow(windows); // first window  //  TEMP HACK DEBUG REMOVE THIS should not be needed
+
+
+// DEBUG
+/* Draw Windows Dividers/Diffrentiators
+if (TRUE) // TODO make conditional
+    {
+
+    hvGfxSetClip(hvg, fullInsideX, 0, fullInsideWidth, pixHeight);
+
+    Color lightRed = hvGfxFindRgb(hvg, &vertWindowSeparatorColor);
+    for (window=windows->next; window; window=window->next) // skips first window because we already have a big vertical red where graphic begins.
+	hvGfxBox(hvg, window->insideX, 0, 1, pixHeight, lightRed);
+
+    hvGfxUnclip(hvg);
+    }	
+*/
+
 /* Draw guidelines. */
+if (galtDebug)
+warn("Draw guidelines");  // OR window separators in virtMode multi-window mode
 if (withGuidelines)
     {
     struct hvGfx *bgImg = hvg; // Default to the one image
     boolean exists = FALSE;
     if (theImgBox)
         {
         struct tempName gifBg;
         char base[64];
-        safef(base,sizeof(base),"blueLines%d-%s%d-%d",pixWidth,(revCmplDisp?"r":""),insideX,
+	if (virtMode) // window separators
+	    {
+	    safecpy(base,sizeof(base),"winSeparators");  // non-reusable temp file 
+	    trashDirFile(&gifBg, "hgt", base, ".png");
+	    exists = FALSE;
+	    }
+	else  // re-usable guidelines
+	    {
+	    safef(base,sizeof(base),"blueLines%d-%s%d-%d",pixWidth,(revCmplDisp?"r":""),fullInsideX,
 		  guidelineSpacing);  // reusable file needs width, leftLabel start and guidelines
 	    exists = trashDirReusableFile(&gifBg, "hgt", base, ".png");
 	    if (exists && cgiVarExists("hgt.reset")) // exists means don't remake bg image. However,
 		exists = FALSE;                      // for now, rebuild on "default tracks" request
-
+	    }
         if (!exists)
             {
             bgImg = hvGfxOpenPng(pixWidth, pixHeight, gifBg.forCgi, TRUE);
             bgImg->rc = revCmplDisp;
             }
         imgBoxImageAdd(theImgBox,gifBg.forHtml,NULL,pixWidth, pixHeight,TRUE); // Adds BG image
         }
 
     if (!exists)
         {
-        int x;
-        Color lightBlue = hvGfxFindRgb(bgImg, &guidelineColor);
 
-        hvGfxSetClip(bgImg, insideX, 0, insideWidth, pixHeight);
+        hvGfxSetClip(bgImg, fullInsideX, 0, fullInsideWidth, pixHeight);
         y = gfxBorder;
 
-        for (x = insideX+guidelineSpacing-1; x<pixWidth; x += guidelineSpacing)
+	if (virtMode)
+	    {
+	    // vertical windows separators
+
+	    if (emAltHighlight)
+		{
+		// light blue alternating backgrounds
+		Color lightBlue = hvGfxFindRgb(bgImg, &guidelineColor);
+		//boolean blueBack = FALSE;
+		for (window=windows; window; window=window->next) // background under every other window
+		    {
+		    //if (blueBack)
+		    if (window->regionOdd)
+			hvGfxBox(bgImg, window->insideX, 0, window->insideWidth, pixHeight, lightBlue);
+		    //blueBack = !blueBack;
+		    }
+		}
+	    else
+		{
+		// red vertical lines
+		Color lightRed = hvGfxFindRgb(bgImg, &vertWindowSeparatorColor);
+		for (window=windows->next; window; window=window->next) // skip first window, not needed
+		    hvGfxBox(bgImg, window->insideX, 0, 1, pixHeight, lightRed);
+		}
+	    }
+	else
+	    {
+	    int x;
+	    Color lightBlue = hvGfxFindRgb(bgImg, &guidelineColor);
+	    for (x = fullInsideX+guidelineSpacing-1; x<pixWidth; x += guidelineSpacing)
 		hvGfxBox(bgImg, x, 0, 1, pixHeight, lightBlue);
+	    }
+
         hvGfxUnclip(bgImg);
         if (bgImg != hvg)
             hvGfxClose(&bgImg);
         }
     }
 
+
+/* Draw ruler */
+if (galtDebug)
+warn("Draw rulers");
+
 /* Show ruler at top. */
 if (rulerMode != tvHide)
     {
+    newWinWidth = calcNewWinWidth(cart,virtWinStart,virtWinEnd,fullInsideWidth);
+
     if (theImgBox)
 	{
 	// data slice for ruler
 	sliceHeight      = basePositionHeight + (rulerCds ? rulerTranslationHeight : 0) + 1;
 	sliceOffsetY     = 0;
 	curImgTrack = imgBoxTrackFind(theImgBox,NULL,RULER_TRACK_NAME);
                 //warn("GTEX 5: track %s, sliceHeight=%d\n", track->shortLabel, sliceHeight);
 	curSlice    = imgTrackSliceUpdateOrAdd(curImgTrack,stData,theOneImg,rulerTtl,
 					       sliceWidth[stData],sliceHeight,
 					       sliceOffsetX[stData],sliceOffsetY);
 	(void) sliceMapFindOrStart(curSlice,RULER_TRACK_NAME,NULL); // No common linkRoot
 	}
-    y = doDrawRuler(hvg,&newWinWidth,&rulerClickHeight,rulerHeight,yAfterRuler,yAfterBases,font,
-                    fontHeight,rulerCds);
+
+    // need to have real winBaseCount to draw ruler scale
+
+    for (window=windows; window; window=window->next)
+	{
+
+	// DETAILS
+	//    warn("window : %s %s %s:%d-%d offset %d width %d window=%lu", // DEBUG REMOVE
+	//      window->organism, window->database, window->chromName, window->winStart+1, window->winEnd, 
+	//      window->insideX, window->insideWidth, (unsigned long) window);
+
+	setGlobalsFromWindow(window);
+
+	if (theImgBox)
+	    {
+	    // Show window positions as mouseover
+	    if (virtMode)
+		{
+		char position[256];
+		safef(position, sizeof position, "%s:%d-%d", window->chromName, window->winStart+1, window->winEnd);
+		int x = window->insideX;
+		if (revCmplDisp)
+		    x = tl.picWidth - (x + window->insideWidth); 
+		imgTrackAddMapItem(curImgTrack, "#", position, 
+		    x, sliceOffsetY, x+window->insideWidth, sliceOffsetY+sliceHeight, RULER_TRACK_NAME);
+		}
+
+	    }
+
+	y = doDrawRuler(hvg,&rulerClickHeight,rulerHeight,yAfterRuler,yAfterBases,font,
+			fontHeight,rulerCds, scaleBarTotalHeight, window);
+	}
+
+    setGlobalsFromWindow(windows); // first window
+    //TODO REMOVE winBaseCount = saveWinBaseCount; // TODO REMOVE later
+
     }
 
+
+
 /* Draw center labels. */
+if (galtDebug)
+warn("Draw center labels.");
 if (withCenterLabels)
     {
-    hvGfxSetClip(hvg, insideX, gfxBorder, insideWidth, pixHeight - 2*gfxBorder);
+    setGlobalsFromWindow(windows); // use GLOBALS from first window
+
+    hvGfxSetClip(hvg, fullInsideX, gfxBorder, fullInsideWidth, pixHeight - 2*gfxBorder);
     y = yAfterRuler;
     for (flatTrack = flatTracks; flatTrack != NULL; flatTrack = flatTrack->next)
         {
         track = flatTrack->track;
         if (track->limitedVis == tvHide)
             continue;
 
         if (theImgBox)
             {
             // center label slice of tracks Must always make, even if the centerLabel is empty
             sliceHeight      = fontHeight;
             sliceOffsetY     = y;
             curImgTrack = imgBoxTrackFind(theImgBox,track->tdb,NULL);
                 //warn("GTEX 6: track %s, sliceHeight=%d\n", track->shortLabel, sliceHeight);
             curSlice    = imgTrackSliceUpdateOrAdd(curImgTrack,stCenter,theOneImg,NULL,
                                                    sliceWidth[stData],sliceHeight,
                                                    sliceOffsetX[stData],sliceOffsetY);
             (void) sliceMapFindOrStart(curSlice,track->tdb->track,NULL); // No common linkRoot
-            if (isCenterLabelConditional(track))
+            if (isCenterLabelConditional(track)) // sometimes calls track height, especially when no data there
+		{
+		//warn("isCenterLabelConditional(track)=TRUE %s", track->track);
                 imgTrackUpdateCenterLabelSeen(curImgTrack,isCenterLabelConditionallySeen(track) ?
                                                                             clNowSeen : clNotSeen);
 		}
+            }
         if (trackShouldUseAjaxRetrieval(track))
+	    {
+	    //warn("trackShouldUseAjaxRetrieval(%s)", track->track); // DEBUG REMOVE
             y += REMOTE_TRACK_HEIGHT;
+	    }
         else
-            y = doCenterLabels(track, track, hvg, font, y);
+	    { 
+	    int savey = y; // GALT
+	    //warn("calling doCenterLabels fullInsideWidth=%d", fullInsideWidth); // DEBUG REMOVE
+            y = doCenterLabels(track, track, hvg, font, y, fullInsideWidth); // calls track height
+	    // TODO GALT why do I just pass track here instead of parentTrack? Did I lose something?
+	    // have to look at old code to see.
+
+	    y = savey + flatTrack->maxHeight; // GALT
+	    }
         }
     hvGfxUnclip(hvg);
+
+    setGlobalsFromWindow(windows); // first window
     }
 
+
+//warn("Start window draw: %s:%d-%d offset %d width %d", 
+  //  chromName, winStart+1, winEnd, insideX, insideWidth);
+
+//drawNow: // DEBUG REMOVE
+
 /* Draw tracks. */
+
+if (galtDebug)
+warn("Draw tracks");
+
     {
+    
     long lastTime = 0;
     y = yAfterRuler;
     if (measureTiming)
         lastTime = clock1000();
     for (flatTrack = flatTracks; flatTrack != NULL; flatTrack = flatTrack->next)
         {
         track = flatTrack->track;
-        if (track->limitedVis == tvHide)
+
+	// parallelize more this?:
+	
+        //ORIG if (track->limitedVis == tvHide)
+	if (isLimitedVisHiddenForAllWindows(track))
             continue;
 
+	//warn("flatTrack->track->track: %s", flatTrack->track->track); // DEBUG REMOVE
+	//fflush(stdout); // DEBUG REMOVE
+
         int centerLabelHeight = (isCenterLabelIncluded(track) ? fontHeight : 0);
+	//warn("flatTrack->track->track: %s isCenterLabelIncluded(track): %d "
+	   // "trackPlusLabelHeight(track, fontHeight): %d", 
+	    //flatTrack->track->track, isCenterLabelIncluded(track), trackPlusLabelHeight(track, fontHeight)); // DEBUG REMOVE
         int yStart = y + centerLabelHeight;
-        int yEnd   = y + trackPlusLabelHeight(track, fontHeight);
+        // ORIG int yEnd   = y + trackPlusLabelHeight(track, fontHeight);
+	int yEnd   = y + flatTrack->maxHeight;  // DEBUG REMOVE
         if (theImgBox)
             {
             // data slice of tracks
             sliceOffsetY     = yStart;
             sliceHeight      = yEnd - yStart - 1;
             curImgTrack = imgBoxTrackFind(theImgBox,track->tdb,NULL);
             if (sliceHeight > 0)
                 {
                 //warn("GTEX 7: track %s, sliceHeight=%d\n", track->shortLabel, sliceHeight);
                 curSlice    = imgTrackSliceUpdateOrAdd(curImgTrack,stData,theOneImg,NULL,
                                                        sliceWidth[stData],sliceHeight,
                                                        sliceOffsetX[stData],sliceOffsetY);
                 (void) sliceMapFindOrStart(curSlice,track->tdb->track,NULL); // No common linkRoot
                 }
             }
         if (trackShouldUseAjaxRetrieval(track))
             y += REMOTE_TRACK_HEIGHT;
         else
-            y = doDrawItems(track, hvg, font, y, &lastTime);
+	    { 
+	    int savey = y; // DEBUG REMOVE
+	    struct track *winTrack;
+
+	    for (window=windows, winTrack=track; window; window=window->next, winTrack=winTrack->nextWindow)
+		{
+		setGlobalsFromWindow(window);
+		//warn("Draw tracks track dump %s\n", makeTrackDumpLink(track)); // DEBUG REMOVE
+		if (winTrack->limitedVis == tvHide)
+		    {
+		    warn("Draw tracks skipping %s because winTrack->limitedVis=hide", winTrack->track);
+		    continue;
+		    }
+		if (insideWidth >= 1)  // do not try to draw if width < 1.
+		    {
+		    int ynew = doDrawItems(winTrack, hvg, font, y, &lastTime);
+		    //warn("y=%d ynew=%d (ynew-y)=%d flatTrack->maxHeight=%d", y, ynew, ynew - y, flatTrack->maxHeight);
+		    if ((ynew-y) > flatTrack->maxHeight)  // so compiler does not complain ynew is not used.
+			errAbort("oops track too high!"); 
+		    }
+		}
+	    setGlobalsFromWindow(windows); // first window
+	    y = savey + flatTrack->maxHeight; // DEBUG REMOVE
+	    }
 
         if (theImgBox && track->limitedVis == tvDense && tdbIsCompositeChild(track->tdb))
             mapBoxToggleVis(hvg, 0, yStart,tl.picWidth, sliceHeight,track);
             // Strange mapBoxToggleLogic handles reverse complement itself so x=0,width=tl.picWidth
 
         if (yEnd != y)
             warn("Slice height for track %s does not add up.  Expecting %d != %d actual",
                  track->shortLabel, yEnd - yStart - 1, y - yStart);
         }
     y++;
     }
+
+if (galtDebug)
+warn("post draw tracks leftLabels");
+
 /* if a track can draw its left labels, now is the time since it
  *  knows what exactly happened during drawItems
  */
+// TODO GALT Parellelize or not?
 if (withLeftLabels)
     {
     y = yAfterRuler;
     for (flatTrack = flatTracks; flatTrack != NULL; flatTrack = flatTrack->next)
         {
         track = flatTrack->track;
         if (track->limitedVis == tvHide)
             continue;
         if (theImgBox)
             {
             // side label slice of tracks
-            sliceHeight      = trackPlusLabelHeight(track, fontHeight);
+            // ORIG sliceHeight      = trackPlusLabelHeight(track, fontHeight);
+	    sliceHeight      = flatTrack->maxHeight;  // DEBUG REMOVE
             sliceOffsetY     = y;
             curImgTrack = imgBoxTrackFind(theImgBox,track->tdb,NULL);
                 //warn("WARN 8: track %s, sliceHeight=%d\n", track->shortLabel, sliceHeight);
             curSlice    = imgTrackSliceUpdateOrAdd(curImgTrack,stSide,theSideImg,NULL,
                                                    sliceWidth[stSide],sliceHeight,
                                                    sliceOffsetX[stSide],sliceOffsetY);
             (void) sliceMapFindOrStart(curSlice,track->tdb->track,NULL); // No common linkRoot
             }
 
         if (trackShouldUseAjaxRetrieval(track))
             y += REMOTE_TRACK_HEIGHT;
     #ifdef IMAGEv2_NO_LEFTLABEL_ON_FULL
         else if (track->drawLeftLabels != NULL
              &&  (theImgBox == NULL || track->limitedVis == tvDense))
     #else ///ndef IMAGEv2_NO_LEFTLABEL_ON_FULL
         else if (track->drawLeftLabels != NULL)
     #endif ///ndef IMAGEv2_NO_LEFTLABEL_ON_FULL
+	    {  // TODO parallelize?
+	    setGlobalsFromWindow(windows);
             y = doOwnLeftLabels(track, hvgSide, font, y);
+	    setGlobalsFromWindow(windows); // first window
+	    }
         else
-            y += trackPlusLabelHeight(track, fontHeight);
+            // ORIG y += trackPlusLabelHeight(track, fontHeight);
+	    y += flatTrack->maxHeight;  // DEBUG REMOVE
         }
     }
 
 
+
 /* Make map background. */
+if (galtDebug)
+warn("Make map background");
 y = yAfterRuler;
 for (flatTrack = flatTracks; flatTrack != NULL; flatTrack = flatTrack->next)
     {
     track = flatTrack->track;
     if (track->limitedVis != tvHide)
         {
         if (theImgBox)
             {
             // Set imgTrack in case any map items will be set
-            sliceHeight      = trackPlusLabelHeight(track, fontHeight);
+            // ORIG sliceHeight      = trackPlusLabelHeight(track, fontHeight);
+	    sliceHeight      = flatTrack->maxHeight;  // DEBUG REMOVE
             sliceOffsetY     = y;
             curImgTrack = imgBoxTrackFind(theImgBox,track->tdb,NULL);
             }
+
+	// TODO Parallelize?
+	setGlobalsFromWindow(windows); // first window
         doTrackMap(track, hvg, y, fontHeight, trackPastTabX, trackPastTabWidth);
-        y += trackPlusLabelHeight(track, fontHeight);
+
+        // ORIG y += trackPlusLabelHeight(track, fontHeight);
+	y += flatTrack->maxHeight;  // DEBUG REMOVE
         }
     }
 
 /* Finish map. */
 hPrintf("</MAP>\n");
 
 // turn off inPlaceUpdate when rows in imgTbl can arbitrarily reappear and disappear (see redmine #7306 and #6944)
 jsonObjectAdd(jsonForClient, "inPlaceUpdate", newJsonBoolean(withLeftLabels && withCenterLabels));
 jsonObjectAdd(jsonForClient, "rulerClickHeight", newJsonNumber(rulerClickHeight));
 if(newWinWidth)
     {
     jsonObjectAdd(jsonForClient, "newWinWidth", newJsonNumber(newWinWidth));
     }
 
 /* Save out picture and tell html file about it. */
@@ -2781,35 +5491,35 @@
         if(n)
             fwrite(buf, 1, n, stdout);
         else
             break;
         }
     fclose(fd);
     unlink(file);
     return;
     }
 #endif///def SUPPORT_CONTENT_TYPE
 
 if (theImgBox)
     {
     imageBoxDraw(theImgBox);
     // If a portal was established, then set the global dimensions back to the portal size
-    if (imgBoxPortalDimensions(theImgBox,NULL,NULL,NULL,NULL,&winStart,&winEnd,&(tl.picWidth),NULL))
+    if (imgBoxPortalDimensions(theImgBox,NULL,NULL,NULL,NULL,&virtWinStart,&virtWinEnd,&(tl.picWidth),NULL))
         {
         pixWidth = tl.picWidth;
-        winBaseCount = winEnd - winStart;
-        insideWidth = tl.picWidth-gfxBorder-insideX;
+        virtWinBaseCount = virtWinEnd - virtWinStart;
+        fullInsideWidth = tl.picWidth - gfxBorder - fullInsideX;
         }
     imgBoxFree(&theImgBox);
     }
 else
     {
     char *titleAttr = "title='click or drag mouse in base position track to zoom in'";
     hPrintf("<IMG SRC='%s' BORDER=1 WIDTH=%d HEIGHT=%d USEMAP=#%s %s id='trackMap'",
             pngTn.forHtml, pixWidth, pixHeight, mapName, titleAttr);
     hPrintf("><BR>\n");
     }
 flatTracksFree(&flatTracks);
 }
 
 void makeHgGenomeTrackVisible(struct track *track)
 /* This turns on a track clicked from hgGenome, even if it was previously */
@@ -2908,30 +5618,31 @@
     if (cgiVarExists("hgGenomeClick"))
 	makeHgGenomeTrackVisible(track);
     if (track->loadItems == NULL)
         warn("No load handler for %s; possible missing trackDb `type' or `subTrack' attribute", tdb->track);
     else if (track->drawItems == NULL)
         warn("No draw handler for %s", tdb->track);
     else
         slAddHead(pTrackList, track);
     }
 }
 
 void loadFromTrackDb(struct track **pTrackList)
 /* Load tracks from database, consulting handler list. */
 {
 char *trackNameFilter = cartOptionalString(cart, "hgt.trackNameFilter");
+//warn("DEBUG loadFromTrackDb:: hgt.trackNameFilter=%s\n", trackNameFilter); // DEBUG REMOVE GALT
 struct trackDb *tdbList;
 if(trackNameFilter == NULL)
     tdbList = hTrackDb(database);
 else
     tdbList = hTrackDbForTrack(database, trackNameFilter);
 addTdbListToTrackList(tdbList, trackNameFilter, pTrackList);
 }
 
 static int getScoreFilter(char *trackName)
 /* check for score filter configuration setting */
 {
 char optionScoreStr[256];
 
 safef(optionScoreStr, sizeof(optionScoreStr), "%s.scoreFilter", trackName);
 return cartUsualInt(cart, optionScoreStr, 0);
@@ -3974,39 +6685,30 @@
         track->visibility = tdbVisLimitedByAncestry(cart, track->tdb, FALSE);
     else if (tdbIsComposite(track->tdb) && track->visibility != tvHide)
 	{
 	struct trackDb *parent = track->tdb->parent;
 	char *parentShow = NULL;
 	if (parent)
 	    parentShow = cartUsualString(cart, parent->track,
 			 parent->isShow ? "show" : "hide");
 	if (!parent || sameString(parentShow, "show"))
 	    compositeTrackVis(track);
 	}
     }
 return trackList;
 }
 
-void doNextPrevItem(boolean goNext, char *trackName)
-/* In case a next item arrow was clicked on a track, change */
-/* position (i.e. winStart, winEnd, etc.) based on what track it was */
-{
-struct track *track = trackFindByName(trackList, trackName);
-if ((track != NULL) && (track->nextPrevItem != NULL))
-    track->nextPrevItem(track, goNext);
-}
-
 char *collapseGroupVar(char *name)
 /* Construct cart variable name for collapsing group */
 {
 static char varName[256];
 safef(varName, sizeof(varName),
         "%s%s_%s_%s", "hgt", "group", name, "close");
 return (cloneString(varName));
 }
 
 boolean isCollapsedGroup(struct group *grp)
 /* Determine if group is collapsed */
 {
 return cartUsualInt(cart, collapseGroupVar(grp->name), grp->defaultIsClosed);
 }
 
@@ -4368,292 +7070,656 @@
     char temp[256];
     safef(temp, sizeof temp, "Timeout %d milliseconds exceeded processing %s", maxTimeInMilliseconds, pfd->track->track);
     pfd->track->networkErrMsg = cloneString(temp);
     ++errCount;
     }
 for (pfd = pfdDone; pfd; pfd = pfd->next)
     {
     // some done tracks may have errors
     if (pfd->track->networkErrMsg)
         ++errCount;
     }
 pthread_mutex_unlock( &pfdMutex );
 return errCount;
 }
 
+static int avgLoadTime(struct track *track)
+/* calculate average loadtime across all windows */
+{
+int totalLoadTime = 0;
+int winCount = 0;
+while(track)
+    {
+    ++winCount;
+    totalLoadTime += track->loadTime;
+    track = track->nextWindow;
+    }
+return (((float)totalLoadTime / winCount) + 0.5);
+}
+
+static int avgDrawTime(struct track *track)
+/* calculate average drawtime across all windows */
+{
+int totalDrawTime = 0;
+int winCount = 0;
+while(track)
+    {
+    ++winCount;
+    totalDrawTime += track->drawTime;
+    track = track->nextWindow;
+    }
+return (((float)totalDrawTime / winCount) + 0.5);
+}
+
 static void printTrackTiming()
 {
-hPrintf("<span class='trackTiming'>track, load time, draw time, total<br />\n");
+hPrintf("<span class='trackTiming'>track, load time, draw time, total (first window)<br />\n");
+hPrintf("<span class='trackTiming'><idiv style='color:red' >average for all windows in red</idiv><br />\n");
 struct track *track;
 for (track = trackList; track != NULL; track = track->next)
     {
     if (track->visibility == tvHide)
         continue;
     if (trackIsCompositeWithSubtracks(track))  //TODO: Change when tracks->subtracks are always set for composite
         {
         struct track *subtrack;
         for (subtrack = track->subtracks; subtrack != NULL;
              subtrack = subtrack->next)
             if (isSubtrackVisible(subtrack))
+		{
                 hPrintf("%s, %d, %d, %d<br />\n", subtrack->shortLabel,
                             subtrack->loadTime, subtrack->drawTime,
                             subtrack->loadTime + subtrack->drawTime);
+		int avgLoad = avgLoadTime(subtrack);
+		int avgDraw = avgDrawTime(subtrack);
+                hPrintf("<idiv style='color:red' >%s, %d, %d, %d</idiv><br />\n", subtrack->shortLabel,
+                            avgLoad, avgDraw,
+                            avgLoad + avgDraw);
+		}
         }
     else
         {
         hPrintf("%s, %d, %d, %d<br />\n",
 		    track->shortLabel, track->loadTime, track->drawTime,
 		    track->loadTime + track->drawTime);
+	int avgLoad = avgLoadTime(track);
+	int avgDraw = avgDrawTime(track);
+	hPrintf("<idiv style='color:red' >%s, %d, %d, %d</idiv><br />\n", track->shortLabel,
+		    avgLoad, avgDraw,
+		    avgLoad + avgDraw);
         if (startsWith("wigMaf", track->tdb->type))
             if (track->subtracks)
                 if (track->subtracks->loadTime)
                     hPrintf("&nbsp; &nbsp; %s wiggle, load %d<br />\n",
                                 track->shortLabel, track->subtracks->loadTime);
         }
     }
 hPrintf("</span>\n");
 }
 
+void initTrackList()
+/* need to init tracklist, sometimes early */
+{
+if (!trackList)
+    { 
+    if (measureTiming)
+	measureTime("Time before getTrackList");
+    boolean defaultTracks = cgiVarExists("hgt.reset");
+    trackList = getTrackList(&groupList, defaultTracks ? -1 : -2);
+    if (measureTiming)
+	measureTime("getTrackList");
+    makeGlobalTrackHash(trackList);
+    }
+}
+
+struct track *getTrackListForOneTrack(char *trackName)
+/* Fetch trackList for a single trackName using hgt.trackNameFilter. */
+{
+struct track *saveTrackList = trackList;
+struct group *saveGroupList = groupList;
+char *saveTrackNameFilter = cloneString(cartOptionalString(cart, "hgt.trackNameFilter"));
+// This is an attempt to both get around the limitation imposed by ajax callback hgt.trackNameFilter,
+//  and also to try to optimize it a little so that callbacks only have to load a trackList containing
+//  only the emGeneTable.
+cartSetString(cart, "hgt.trackNameFilter", trackName);
+initTrackList(); // initialize trackList early if needed
+struct track *returnTrackList = trackList;
+cartRemove(cart, "hgt.trackNameFilter");
+// restore
+if (saveTrackNameFilter)
+    {
+    cartSetString(cart, "hgt.trackNameFilter", saveTrackNameFilter);
+    }
+trackList = saveTrackList;
+groupList = saveGroupList;
+return returnTrackList;
+}
+
+void doNextPrevItem(boolean goNext, char *trackName)
+/* In case a next item arrow was clicked on a track, change */
+/* position (i.e. winStart, winEnd, etc.) based on what track it was */
+{
+// create custom trackList with just trackName
+struct track *myTrackList = getTrackListForOneTrack(trackName);
+struct track *track = trackFindByName(myTrackList, trackName);
+if ((track != NULL) && (track->nextPrevItem != NULL))
+    track->nextPrevItem(track, goNext);
+}
+
+
+void findBestEMGeneTable(struct track *myTrackList)
+/* Find the best gene table to use for exonMostly and geneMostly. */
+{
+// TODO add support for choosing any gene table of type genePred, genePreExt, bigGenePred
+
+// TODO add support for assembly hubs
+if (trackHubDatabase(database)) // assembly hub? not supported yet
+    return; // any table-name matches might just be coincidence in an assembly hub
+            // although the hub_ prefix on the track name would help prevent name collisions.
+
+char *orderedTables[] = 
+{"knownGene", "refGene", "ensGene", 
+ "flybaseGene", "sangerGene", "augustusGene", "genscan"};
+int i, len;
+for(i=0, len=ArraySize(orderedTables); i <len; ++i)
+    {
+    char *table = orderedTables[i];
+    emGeneTrack = rFindTrackWithTable(table, myTrackList);
+    if (emGeneTrack)
+	{
+	emGeneTable = table;
+	cartSetString(cart, "emGeneTable", emGeneTable);
+	break;
+	}
+    }
+}
+
+void setEMGeneTrack()
+/* Find the track for the gene table to use for exonMostly and geneMostly. */
+{
+if (emGeneTable) // we already have it!
+    return;
+emGeneTable = cloneString(cartOptionalString(cart, "emGeneTable"));
+if (!emGeneTable)
+    {
+    cartRemove(cart, "emGeneTable");
+    return;
+    }
+struct track *myTrackList = getTrackListForOneTrack(emGeneTable);
+emGeneTrack = rFindTrackWithTable(emGeneTable, myTrackList);
+// note that we cannot easily call findBestEMGeneTable because we do not have a complete track list early on.
+if (!emGeneTrack) 
+    {
+    cartRemove(cart, "emGeneTable");
+    return;
+    }
+}
+
+
 void doTrackForm(char *psOutput, struct tempName *ideoTn)
 /* Make the tracks display form with the zoom/scroll buttons and the active
  * image.  If the ideoTn parameter is not NULL, it is filled in if the
  * ideogram is created.  */
 {
 struct group *group;
 struct track *track;
 char *freezeName = NULL;
 boolean hideAll = cgiVarExists("hgt.hideAll");
 boolean defaultTracks = cgiVarExists("hgt.reset");
 boolean showedRuler = FALSE;
 boolean showTrackControls = cartUsualBoolean(cart, "trackControlsOnMain", TRUE);
 long thisTime = 0, lastTime = 0;
 
-basesPerPixel = ((float)winBaseCount) / ((float)insideWidth);
-zoomedToBaseLevel = (winBaseCount <= insideWidth / tl.mWidth);
-zoomedToCodonLevel = (ceil(winBaseCount/3) * tl.mWidth) <= insideWidth;
-zoomedToCdsColorLevel = (winBaseCount <= insideWidth*3);
+basesPerPixel = ((float)virtWinBaseCount) / ((float)fullInsideWidth);
+zoomedToBaseLevel = (virtWinBaseCount <= fullInsideWidth / tl.mWidth);
+zoomedToCodonLevel = (ceil(virtWinBaseCount/3) * tl.mWidth) <= fullInsideWidth;
+zoomedToCdsColorLevel = (virtWinBaseCount <= fullInsideWidth*3);
 
 if (psOutput != NULL)
    {
    hPrintDisable();
    hideControls = TRUE;
    withNextItemArrows = FALSE;
    withNextExonArrows = FALSE;
    hgFindMatches = NULL;
    }
 
 /* Tell browser where to go when they click on image. */
 hPrintf("<FORM ACTION=\"%s\" NAME=\"TrackHeaderForm\" id=\"TrackHeaderForm\" METHOD=\"GET\">\n\n", hgTracksName());
-jsonObjectAdd(jsonForClient, "insideX", newJsonNumber(insideX));
+jsonObjectAdd(jsonForClient, "insideX", newJsonNumber(insideX)); // TODO GALT  fullInsideX? or does not matter?
 jsonObjectAdd(jsonForClient, "revCmplDisp", newJsonBoolean(revCmplDisp));
 
 if (hPrintStatus()) cartSaveSession(cart);
 
 /* See if want to include sequence search results. */
 userSeqString = cartOptionalString(cart, "ss");
 if (userSeqString && !ssFilesExist(userSeqString))
     {
     userSeqString = NULL;
     cartRemove(cart, "ss");
     }
 if (!hideControls)
     hideControls = cartUsualBoolean(cart, "hideControls", FALSE);
-if (measureTiming)
-    measureTime("Time before getTrackList");
-trackList = getTrackList(&groupList, defaultTracks ? -1 : -2);
-if (measureTiming)
-    measureTime("getTrackList");
-makeGlobalTrackHash(trackList);
+
+initTrackList();
+//warn("slCount(trackList) after getTrackList: %d", slCount(trackList));
 /* Tell tracks to load their items. */
 
 
 // honor defaultImgOrder
 if (cgiVarExists("hgt.defaultImgOrder"))
     {
     char wildCard[32];
     safef(wildCard,sizeof(wildCard),"*_%s",IMG_ORDER_VAR);
     cartRemoveLike(cart, wildCard);
     }
+
 // Subtrack settings must be removed when composite/view settings are updated
 parentChildCartCleanup(trackList,cart,oldVars);
 if (measureTiming)
     measureTime("parentChildCartCleanup");
 
 
 /* Honor hideAll and visAll variables */
 if (hideAll || defaultTracks)
     {
     int vis = (hideAll ? tvHide : -1);
     changeTrackVis(groupList, NULL, vis);
     }
 
-/* Before loading items, deal with the next/prev item arrow buttons if pressed. */
-if (cgiVarExists("hgt.nextItem"))
-    doNextPrevItem(TRUE, cgiUsualString("hgt.nextItem", NULL));
-else if (cgiVarExists("hgt.prevItem"))
-    doNextPrevItem(FALSE, cgiUsualString("hgt.prevItem", NULL));
-
 if(!psOutput && !cartUsualBoolean(cart, "hgt.imageV1", FALSE))
     {
-    // Start an imagebox (global for now to avoid huge rewrite of hgTracks)
-    // Set up imgBox dimensions
-    int sideSliceWidth  = 0;   // Just being explicit
-    if (withLeftLabels)
-        sideSliceWidth   = (insideX - gfxBorder*3) + 2;
-    theImgBox = imgBoxStart(database,chromName,winStart,winEnd,
-                            (!revCmplDisp),sideSliceWidth,tl.picWidth);
-    // Define a portal with a default expansion size,
-    // then set the global dimensions to the full image size
-    if (imgBoxPortalDefine(theImgBox,&winStart,&winEnd,&(tl.picWidth),0))
+
+   // re-establish the enlarged portal
+   if (imgBoxPortalDimensions(theImgBox,&virtWinStart,&virtWinEnd,&(tl.picWidth),NULL,NULL,NULL,NULL,NULL))
         {
-        winBaseCount = winEnd - winStart;
-        insideWidth = tl.picWidth-gfxBorder-insideX;
+        virtWinBaseCount = virtWinEnd - virtWinStart;
+        fullInsideWidth = tl.picWidth - gfxBorder - fullInsideX;
         }
+
     }
 
 char *jsCommand = cartCgiUsualString(cart, hgtJsCommand, "");
 if (!isEmpty(jsCommand))
    {
    cartRemove(cart, hgtJsCommand);
    jsCommandDispatch(jsCommand, trackList);
    }
 
 
 /* adjust visibility */
 for (track = trackList; track != NULL; track = track->next)
     {
     /* adjust track visibility based on supertrack just before load loop */
     if (tdbIsSuperTrackChild(track->tdb))
         limitSuperTrackVis(track);
 
     /* remove cart priority variables if they are set
        to the default values in the trackDb */
     if (!hTrackOnChrom(track->tdb, chromName))
         {
         track->limitedVis = tvHide;
         track->limitedVisSet = TRUE;
 	}
     }
 
 if (sameString(cfgOptionDefault("trackLog", "off"), "on"))
     logTrackVisibilities(cartSessionId(cart), trackList);
 
+
+/////////////////
+
+// NEED TO LOAD ALL WINDOWS NOW
+// 
+//   Need to load one window at a time!
+//
+//   The use of the global values for a window
+//   means that differerent threads cannot use different global window values.
+//   Threads must run on just one window value at a time.
+//
+//   Begin by making a copy of the track structure for visible tracks
+//   for all windows.
+
+
+//warn("copy track structures for multiple  windows"); // DEBUG REMOVE
+// COPY TRACK STRUCTURES for other windows.
+windows->trackList = trackList;  // save current track list in window
+struct window *window;
+for (window=windows; window->next; window=window->next)
+    {
+    struct track *newTrackList = NULL;
+    for (track = trackList; track != NULL; track = track->next)
+	{
+	track->nextWindow = NULL;
+	if (track->visibility != tvHide)
+	    {
+	    struct track *copy;
+	    AllocVar(copy);
+	    memmove(copy,track,sizeof(struct track));
+	    copy->next = NULL;
+	    copy->nextWindow = NULL;
+	    copy->prevWindow = track;
+	    slAddHead(&newTrackList, copy);
+	    track->nextWindow = copy;
+
+	    // copy subtracks.
+	    copy->subtracks = NULL;
+	    struct track *subtrack;
+	    for (subtrack = track->subtracks; subtrack != NULL; subtrack = subtrack->next)
+		{
+		if (subtrack->visibility != tvHide)
+		    {
+		    struct track *subcopy;
+		    AllocVar(subcopy);
+		    memmove(subcopy,subtrack,sizeof(struct track));
+		    subcopy->next = NULL;
+		    subcopy->nextWindow = NULL;
+		    subcopy->prevWindow = subtrack;
+		    slAddHead(&copy->subtracks, subcopy);
+		    subtrack->nextWindow = subcopy;
+		    }
+		}
+	    slReverse(&copy->subtracks);
+	    //warn("%s track subs: %d  copy subs: %d", track->track, slCount(track->subtracks), slCount(copy->subtracks));
+	    }
+	}
+    slReverse(&newTrackList);
+    trackList = newTrackList;
+    window->next->trackList = trackList;  // save new track list in window
+    }
+trackList = windows->trackList;  // restore original track list
+
+// DEBUG REMOVE verify results.
+//for (window=windows; window; window=window->next)
+//    {
+//    warn("%s %d", window->chromName, slCount(window->trackList));
+//    }
+
+// Loop over each window loading all tracks
+//warn("Loop over each window loading all tracks"); // DEBUG REMOVE
+trackLoadingInProgress = TRUE;
+
+// TEMP HACK GALT REMOVE
+bool loadHack = FALSE; //TRUE;  // probably should only be tried on non-wiggle tracks
+//warn ("loadHack = %d", loadHack); // DEBUG REMOVE
+int lastWinEnd = 0;
+for (window=windows; window; window=window->next)
+    lastWinEnd = window->winEnd;
+
+for (window=windows; window; window=window->next)
+    {
+    trackList = window->trackList;  // set track list
+    setGlobalsFromWindow(window);
+
+    // TEMP HACK GALT REMOVE
+    if (loadHack)
+	{
+	if (currentWindow == windows) // first window
+	    winEnd = lastWinEnd; // so now we load the entire span inside the first window.
+	}
+
     /* pre-load remote tracks in parallel */
     int ptMax = atoi(cfgOptionDefault("parallelFetch.threads", "20"));  // default number of threads for parallel fetch.
     int pfdListCount = 0;
     pthread_t *threads = NULL;
     if (ptMax > 0)     // parallelFetch.threads=0 to disable parallel fetch
 	{
 	findLeavesForParallelLoad(trackList, &pfdList);
 	pfdListCount = slCount(pfdList);
 	/* launch parallel threads */
 	ptMax = min(ptMax, pfdListCount);
 	if (ptMax > 0)
 	    {
 	    AllocArray(threads, ptMax);
 	    /* Create threads */
 	    int pt;
 	    for (pt = 0; pt < ptMax; ++pt)
 		{
 		int rc = pthread_create(&threads[pt], NULL, remoteParallelLoad, &threads[pt]);
 		if (rc)
 		    {
 		    errAbort("Unexpected error %d from pthread_create(): %s",rc,strerror(rc));
 		    }
 		}
 	    }
 	}
 
+    // TODO GALT
+    //warn("slCount(trackList) just before load regular tracks: %d", slCount(trackList)); // DEBUG REMOVE
+    int visCount = 0; // DEBUG REMOVE
     /* load regular tracks */
     for (track = trackList; track != NULL; track = track->next)
 	{
 	if (track->visibility != tvHide)
 	    {
+	    ++visCount; // DEBUG REMOVE
 	    if (!track->parallelLoading)
 		{
 		if (measureTiming)
 		    lastTime = clock1000();
 
+		//warn("load regular tracks BEFORE load and check max window to draw %s\n", makeTrackDumpLink(track)); // DEBUG REMOVE
 		checkMaxWindowToDraw(track);
+
+		//warn("load regular tracks BEFORE check if wiggling %s\n", makeTrackDumpLink(track)); // DEBUG REMOVE
 		checkIfWiggling(cart, track);
+
+		//warn("load regular tracks ABOUT to call loadItems() %s\n", makeTrackDumpLink(track)); // DEBUG REMOVE
+
+		if (!loadHack)
+		    {
+		    track->loadItems(track); // DEBUG RESTORE !!!
+		    }
+		else
+		    {
+		    // TEMP HACK GALT REMOVE
+		    if (currentWindow == windows) // first window
+			{
 			track->loadItems(track);
+			}
+		    else
+			{
+			track->items = track->prevWindow->items;  // just point to the previous windows items (faster than loading)
+			// apparently loadItems is setting some other fields that we want, but which ones?
+			track->visibility = track->prevWindow->visibility;
+			track->limitedVis = track->prevWindow->limitedVis;
+			track->limitedVisSet = track->prevWindow->limitedVisSet;
+			track->height = track->prevWindow->height;
+			track->lineHeight = track->prevWindow->lineHeight;
+			track->heightPer = track->prevWindow->heightPer;
+			// TODO does this work for subtracks or parents/children?
+			}
+		    }
+
+		// DEBUG REMOVE how many visible subtracks?
+		int visSubCount = 0;
+		struct track *sub;
+		for (sub=track->subtracks; sub; sub=sub->next)
+		    if (sub->visibility != tvHide)
+			++visSubCount;
+
+		//warn("load regular tracks AFTER loadItems #%d %s type %s subtracks %d vis %d %s\n", 
+		    //visCount, track->track, trackDbSetting(track->tdb, "type"),
+			//slCount(track->subtracks), visSubCount, makeTrackDumpLink(track)); // DEBUG REMOVE
 
 		if (measureTiming)
 		    {
 		    thisTime = clock1000();
 		    track->loadTime = thisTime - lastTime;
 		    }
 		}
 	    }
 	}
+    //warn("after load regular tracks: %d", visCount); // DEBUG REMOVE
 
     if (ptMax > 0)
 	{
+	// TODO GALT parallel actually not sure if anything to worry about here
 	/* wait for remote parallel load to finish */
 	remoteParallelLoadWait(atoi(cfgOptionDefault("parallelFetch.timeout", "90")));  // wait up to default 90 seconds.
 	if (measureTiming)
 	    measureTime("Waiting for parallel (%d threads for %d tracks) remote data fetch", ptMax, pfdListCount);
 	}
 
+    }
+trackLoadingInProgress = FALSE;
+
+setGlobalsFromWindow(windows); // first window // restore globals
+trackList = windows->trackList;  // restore track list
+
+//////////////  END OF MULTI-WINDOW LOOP
+
 printTrackInitJavascript(trackList);
 
 /* Generate two lists of hidden variables for track group visibility.  Kludgy,
    but required b/c we have two different navigation forms on this page, but
    we want open/close changes in the bottom form to be submitted even if the user
    submits via the top form. */
 struct dyString *trackGroupsHidden1 = newDyString(1000);
 struct dyString *trackGroupsHidden2 = newDyString(1000);
 for (group = groupList; group != NULL; group = group->next)
     {
     if (group->trackList != NULL)
         {
         int looper;
         for (looper=1;looper<=2;looper++)
             {
             boolean isOpen = !isCollapsedGroup(group);
             char buf[1000];
-            safef(buf, sizeof(buf), "<input type='hidden' name=\"%s\" id=\"%s_%d\" value=\"%s\">\n", collapseGroupVar(group->name), collapseGroupVar(group->name), looper, isOpen ? "0" : "1");
+            safef(buf, sizeof(buf), "<input type='hidden' name=\"%s\" id=\"%s_%d\" value=\"%s\">\n", 
+		collapseGroupVar(group->name), collapseGroupVar(group->name), looper, isOpen ? "0" : "1");
             dyStringAppend(looper == 1 ? trackGroupsHidden1 : trackGroupsHidden2, buf);
             }
         }
     }
 
 if (theImgBox)
     {
     // If a portal was established, then set the global dimensions back to the portal size
-    if (imgBoxPortalDimensions(theImgBox,NULL,NULL,NULL,NULL,&winStart,&winEnd,&(tl.picWidth),NULL))
+    if (imgBoxPortalDimensions(theImgBox,NULL,NULL,NULL,NULL,&virtWinStart,&virtWinEnd,&(tl.picWidth),NULL))
         {
-        winBaseCount = winEnd - winStart;
-        insideWidth = tl.picWidth-gfxBorder-insideX;
+        virtWinBaseCount = virtWinEnd - virtWinStart;
+        fullInsideWidth = tl.picWidth-gfxBorder-fullInsideX;
         }
     }
 /* Center everything from now on. */
 hPrintf("<CENTER>\n");
 
-// info for drag selection javascript
-jsonObjectAdd(jsonForClient, "winStart", newJsonNumber(winStart));
-jsonObjectAdd(jsonForClient, "winEnd", newJsonNumber(winEnd));
-jsonObjectAdd(jsonForClient, "chromName", newJsonString(chromName));
+// OLD WAY
+//jsonObjectAdd(jsonForClient, "winStart", newJsonNumber(winStart));
+//jsonObjectAdd(jsonForClient, "winEnd", newJsonNumber(winEnd));
+//jsonObjectAdd(jsonForClient, "chromName", newJsonString(chromName));
+
+// DONE instead of trying to add new variables here,
+// see if we can pass the virt versions in as if they were the original variables.
+// That way less of hgTracks.js would need to be changed.
+jsonObjectAdd(jsonForClient, "winStart", newJsonNumber(virtWinStart));
+jsonObjectAdd(jsonForClient, "winEnd", newJsonNumber(virtWinEnd));
+jsonObjectAdd(jsonForClient, "chromName", newJsonString(virtChromName));
+
+// Tell javascript about multiple windows info
+// GALT TODO if end up not using this information, turn this off
+// as it sends several K of extra data on a view with many windows.
+if (virtMode)
+    {
+    // pre windows
+    long preVWinStart = virtWinStart - virtWinBaseCount;
+    if (preVWinStart < 0)
+	preVWinStart = 0;
+    long preVWinEnd = virtWinStart;
+    struct window *preWindows = makeWindowListFromVirtChrom(preVWinStart, preVWinEnd);
+    struct jsonElement *jsonForList = newJsonList(NULL);
+    for(window=preWindows;window;window=window->next)
+	{
+	struct jsonElement *jsonForWindow = NULL;
+	jsonForWindow = newJsonObject(newHash(8));
+	jsonObjectAdd(jsonForWindow, "chromName",   newJsonString(window->chromName));
+	jsonObjectAdd(jsonForWindow, "winStart",    newJsonNumber(window->winStart));
+	jsonObjectAdd(jsonForWindow, "winEnd",      newJsonNumber(window->winEnd));
+	jsonObjectAdd(jsonForWindow, "insideX",     newJsonNumber(window->insideX));
+	jsonObjectAdd(jsonForWindow, "insideWidth", newJsonNumber(window->insideWidth));
+	jsonObjectAdd(jsonForWindow, "virtStart",   newJsonNumber(window->virtStart));
+	jsonObjectAdd(jsonForWindow, "virtEnd",     newJsonNumber(window->virtEnd));
+	jsonListAdd(jsonForList, jsonForWindow);
+	}
+    slReverse(&jsonForList->val.jeList);
+    jsonObjectAdd(jsonForClient, "windowsBefore", jsonForList);
+
+    jsonForList = newJsonList(NULL);
+    for(window=windows;window;window=window->next)
+	{
+	struct jsonElement *jsonForWindow = NULL;
+	jsonForWindow = newJsonObject(newHash(8));
+	jsonObjectAdd(jsonForWindow, "chromName",   newJsonString(window->chromName));
+	jsonObjectAdd(jsonForWindow, "winStart",    newJsonNumber(window->winStart));
+	jsonObjectAdd(jsonForWindow, "winEnd",      newJsonNumber(window->winEnd));
+	jsonObjectAdd(jsonForWindow, "insideX",     newJsonNumber(window->insideX));
+	jsonObjectAdd(jsonForWindow, "insideWidth", newJsonNumber(window->insideWidth));
+	jsonObjectAdd(jsonForWindow, "virtStart",   newJsonNumber(window->virtStart));
+	jsonObjectAdd(jsonForWindow, "virtEnd",     newJsonNumber(window->virtEnd));
+	jsonListAdd(jsonForList, jsonForWindow);
+	}
+    slReverse(&jsonForList->val.jeList);
+    jsonObjectAdd(jsonForClient, "windows", jsonForList);
+
+    // post windows
+    long postVWinStart = virtWinEnd;
+    long postVWinEnd = virtWinEnd + virtWinBaseCount;
+    if (postVWinEnd > virtSeqBaseCount)
+	postVWinEnd = virtSeqBaseCount;
+    struct window *postWindows = makeWindowListFromVirtChrom(postVWinStart, postVWinEnd);
+    jsonForList = newJsonList(NULL);
+    for(window=postWindows;window;window=window->next)
+	{
+	struct jsonElement *jsonForWindow = NULL;
+	jsonForWindow = newJsonObject(newHash(8));
+	jsonObjectAdd(jsonForWindow, "chromName",   newJsonString(window->chromName));
+	jsonObjectAdd(jsonForWindow, "winStart",    newJsonNumber(window->winStart));
+	jsonObjectAdd(jsonForWindow, "winEnd",      newJsonNumber(window->winEnd));
+	jsonObjectAdd(jsonForWindow, "insideX",     newJsonNumber(window->insideX));
+	jsonObjectAdd(jsonForWindow, "insideWidth", newJsonNumber(window->insideWidth));
+	jsonObjectAdd(jsonForWindow, "virtStart",   newJsonNumber(window->virtStart));
+	jsonObjectAdd(jsonForWindow, "virtEnd",     newJsonNumber(window->virtEnd));
+	jsonListAdd(jsonForList, jsonForWindow);
+	}
+    slReverse(&jsonForList->val.jeList);
+    jsonObjectAdd(jsonForClient, "windowsAfter", jsonForList);
+
+    jsonForList = newJsonList(NULL);
+    // also store js nonVirtPosition
+    jsonObjectAdd(jsonForClient, "nonVirtPosition", newJsonString(cartString(cart, "nonVirtPosition")));
+    jsonObjectAdd(jsonForClient, "virtChromChanged", newJsonBoolean(virtChromChanged));
+    jsonObjectAdd(jsonForClient, "virtualSingleChrom", newJsonBoolean(virtualSingleChrom())); // DISGUISE POS
+    }
+
+char dbPosKey[256];
+safef(dbPosKey, sizeof(dbPosKey), "position.%s", database);
+jsonObjectAdd(jsonForClient, "lastDbPos", newJsonString(cartString(cart, dbPosKey)));
+
+
 
 if(trackImgOnly && !ideogramToo)
     {
-    struct track *ideoTrack = chromIdeoTrack(trackList);
+    for(window=windows;window;window=window->next)
+	{
+	struct track *ideoTrack = chromIdeoTrack(window->trackList);
 	if (ideoTrack)
 	    {
 	    ideoTrack->limitedVisSet = TRUE;
 	    ideoTrack->limitedVis = tvHide; /* Don't draw in main gif. */
 	    }
+	}
     makeActiveImage(trackList, psOutput);
     fflush(stdout);
     return;  // bail out b/c we are done
     }
 
 if (!hideControls)
     {
     /* set white-space to nowrap to prevent buttons from wrapping when screen is
      * narrow */
     hPrintf("<DIV STYLE=\"white-space:nowrap;\">\n");
     printMenuBar();
     //menuBarAppendExtTools();
 
     /* Show title . */
     freezeName = hFreezeFromDb(database);
@@ -4717,82 +7783,121 @@
 
     if (showTrackControls)
         {
 	/* Break into a second form so that zooming and scrolling
 	 * can be done with a 'GET' so that user can back up from details
 	 * page without Internet Explorer popping up an annoying dialog.
 	 * Do rest of page as a 'POST' so that the ultra-long URL from
 	 * all the track controls doesn't break things.  IE URL limit
 	 * is 2000 bytes, but some firewalls impose a ~1000 byte limit.
 	 * As a side effect of breaking up the page into two forms
 	 * we need to repeat the position in a hidden variable here
 	 * so that zoom/scrolling always has current position to work
 	 * from. */
         // This 'dirty' field is used to check if js/ajax changes to the page have occurred.
         // If so and it is reached by the back button, a page reload will occur instead.
+	char buf[256];
+	// DEBUG RESTORE
+	if (virtualSingleChrom()) // DISGUISE VMODE
+	    safef(buf, sizeof buf, "%s", windowsSpanPosition());
+	else
+	    safef(buf, sizeof buf, "%s:%ld-%ld", virtChromName, virtWinStart+1, virtWinEnd);
         hPrintf("<INPUT TYPE='text' style='display:none;' name='dirty' id='dirty' VALUE='false'>\n");
-        hPrintf("<INPUT TYPE=HIDDEN id='positionHidden' NAME=\"position\" "
-                "VALUE=\"%s:%d-%d\">", chromName, winStart+1, winEnd);
+        hPrintf("<INPUT TYPE=HIDDEN id='positionHidden' name='position' "
+                "VALUE=\"%s\">", buf);
         hPrintf("\n%s", trackGroupsHidden1->string);
         hPrintf("</CENTER></FORM>\n");
         hPrintf("<FORM ACTION=\"%s\" NAME=\"TrackForm\" id=\"TrackForm\" METHOD=\"POST\">\n\n",
                 hgTracksName());
 	    hPrintf("%s", trackGroupsHidden2->string);
 	    freeDyString(&trackGroupsHidden1);
 	    freeDyString(&trackGroupsHidden2);
 	if (!psOutput) cartSaveSession(cart);   /* Put up hgsid= as hidden variable. */
 	hPrintf("<CENTER>");
 	}
 
 
     /* Make line that says position. */
 	{
 	char buf[256];
 	char *survey = cfgOptionEnv("HGDB_SURVEY", "survey");
 	char *surveyLabel = cfgOptionEnv("HGDB_SURVEY_LABEL", "surveyLabel");
 	    char *javascript = "onchange=\"document.location = '/cgi-bin/hgTracks?db=' + document.TrackForm.db.options[document.TrackForm.db.selectedIndex].value;\"";
 	    if (containsStringNoCase(database, "zoo"))
 		{
 		hPuts("Organism ");
 		printAssemblyListHtmlExtra(database, javascript);
 		}
 
-	sprintf(buf, "%s:%d-%d", chromName, winStart+1, winEnd);
+	// DEBUG RESTORE
+	if (virtualSingleChrom()) // DISGUISE VMODE
+	    safef(buf, sizeof buf, "%s", windowsSpanPosition());
+	else
+	    safef(buf, sizeof buf, "%s:%ld-%ld", virtChromName, virtWinStart+1, virtWinEnd);
+	
 	position = cloneString(buf);
 	hPrintf("<span class='positionDisplay' id='positionDisplay' title='click to copy position to input box'>%s</span>", addCommasToPos(database, position));
 	hPrintf("<input type='hidden' name='position' id='position' value='%s'>\n", buf);
-	sprintLongWithCommas(buf, winEnd - winStart);
+	sprintLongWithCommas(buf, virtWinEnd - virtWinStart);
 	hPrintf(" <span id='size'>%s</span> bp. ", buf);
 	hPrintf("<input class='positionInput' type='text' name='hgt.positionInput' id='positionInput' size='60'>\n");
 	hWrites(" ");
 	hButton("hgt.jump", "go");
 	if (!trackHubDatabase(database))
 	    {
-            jsonObjectAdd(jsonForClient, "assemblySupportsGeneSuggest", newJsonBoolean(assemblySupportsGeneSuggest(database)));
-            if (assemblySupportsGeneSuggest(database))
-                hPrintf("<input type='hidden' name='hgt.suggestTrack' id='suggestTrack' value='%s'>\n", assemblyGeneSuggestTrack(database));
+            jsonObjectAdd(jsonForClient, "assemblySupportsGeneSuggest", newJsonBoolean(assemblySupportsGeneSuggest(database)));
+            if (assemblySupportsGeneSuggest(database))
+                hPrintf("<input type='hidden' name='hgt.suggestTrack' id='suggestTrack' value='%s'>\n", assemblyGeneSuggestTrack(database));
+	    }
+	if (survey && differentWord(survey, "off"))
+            hPrintf("&nbsp;&nbsp;<span style='background-color:yellow;'>"
+                    "<A HREF='%s' TARGET=_BLANK><EM><B>%s</EM></B></A></span>\n",
+                    survey, surveyLabel ? surveyLabel : "Take survey");
+	hPutc('\n');
+	}
+    }
+
+// TODO GALT  how to handle ideos?
+boolean nukeIdeoFromList = FALSE;
+for(window=windows;window;window=window->next)
+    {
+    setGlobalsFromWindow(window);
+   
+    if (window == windows) // first window
+	{	
+	/* Make chromsome ideogram gif and map. */
+	nukeIdeoFromList = makeChromIdeoImage(&trackList, psOutput, ideoTn);  
+	window->trackList = trackList;  // the variable may have been updated.
+	// TODO make this not just be centered over the entire image,
+	// but rather centered over the individual chromosome.
+	// notice that it modifies trackList, and visibility settings potentially need parallelization for windows
+	}
+    else
+	{
+	// TODO should be more than this. But at least this makes the same trackList mods to the other windows.
+	if (nukeIdeoFromList)
+	    {
+	    struct track *ideoTrack = chromIdeoTrack(window->trackList);
+	    if (ideoTrack)
+		{
+		slRemoveEl(&window->trackList, ideoTrack);
 		}
-	if (survey && differentWord(survey, "off"))
-            hPrintf("&nbsp;&nbsp;<span style='background-color:yellow;'>"
-                    "<A HREF='%s' TARGET=_BLANK><EM><B>%s</EM></B></A></span>\n",
-                    survey, surveyLabel ? surveyLabel : "Take survey");
-	hPutc('\n');
 	    }
 	}
 
-/* Make chromsome ideogram gif and map. */
-makeChromIdeoImage(&trackList, psOutput, ideoTn);
+    }
+setGlobalsFromWindow(windows); // first window // restore globals
 
 #ifdef USE_NAVIGATION_LINKS
 hPrintf("<TABLE BORDER=0 CELLPADDING=0 width='%d'><tr style='font-size:small;'>\n",
         tl.picWidth);//min(tl.picWidth, 800));
 hPrintf("<td width='40' align='left'><a href='?hgt.left3=1' "
         "title='move 95&#37; to the left'>&lt;&lt;&lt;</a>\n");
 hPrintf("<td width='30' align='left'><a href='?hgt.left2=1' "
         "title='move 47.5&#37; to the left'>&lt;&lt;</a>\n");
 hPrintf("<td width='20' align='left'><a href='?hgt.left1=1' "
         "title='move 10&#37; to the left'>&lt;</a>\n");
 
 hPrintf("<td>&nbsp;</td>\n"); // Without width cell expands table with, forcing others to sides
 hPrintf("<td width='40' align='left'><a href='?hgt.in1=1' "
         "title='zoom in 1.5x'>&gt;&nbsp;&lt;</a>\n");
 hPrintf("<td width='60' align='left'><a href='?hgt.in2=1' "
@@ -4896,30 +8001,34 @@
             "return false;' title='%s'>",
             hasCustomTracks ? CT_MANAGE_BUTTON_LABEL : CT_ADD_BUTTON_LABEL,
             hasCustomTracks ? "Manage your custom tracks" : "Add your own custom tracks");
 
     hPrintf(" ");
     if (hubConnectTableExists())
         {
         hPrintf("<INPUT TYPE='button' VALUE='track hubs' onClick='document.trackHubForm.submit();"
                 "return false;' title='Import tracks from hubs'>");
         hPrintf(" ");
         }
 
     hButtonWithMsg("hgTracksConfigPage", "configure","Configure image and track selection");
     hPrintf(" ");
 
+    hButtonWithOnClick("hgTracksConfigMultiRegionPage", 
+	"multi-region", "Configure multi-region display options", "popUpHgt.hgTracks('multi-region config'); return false;");
+    hPrintf(" ");
+
     if (!hIsGsidServer())
         {
         hButtonWithMsg("hgt.toggleRevCmplDisp", "reverse",
                        revCmplDisp ? "Show forward strand at this location"
                                    : "Show reverse strand at this location");
         hPrintf(" ");
         }
 
     hButtonWithOnClick("hgt.setWidth", "resize", "Resize image width to browser window size", "hgTracksSetWidth()");
     hPrintf(" ");
 
     hButtonWithMsg("hgt.refresh", "refresh","Refresh image");
 
     hPrintf("<BR>\n");
 
@@ -5007,30 +8116,31 @@
 		hPrintf("<A HREF=\"%s\">", url);
 		hPrintf(" %s<BR> ", RULER_TRACK_LABEL);
 		hPrintf("</A>");
 		hDropListClassWithStyle("ruler", rulerMenu,
 			sizeof(rulerMenu)/sizeof(char *), rulerMenu[rulerMode],
 			rulerMode == tvHide ? "hiddenText" : "normalText",
 			TV_DROPDOWN_STYLE);
 		controlGridEndCell(cg);
 		freeMem(url);
 		}
 
 	    /* Add supertracks to  track list, sort by priority and
 	     * determine if they have visible member tracks */
 	    groupTrackListAddSuper(cart, group);
 
+	    // TODO GALT probably nothing to do here
 	    /* Display track controls */
 	    for (tr = group->trackList; tr != NULL; tr = tr->next)
 		{
 		struct track *track = tr->track;
 		if (tdbIsSuperTrackChild(track->tdb))
 		    /* don't display supertrack members */
 		    continue;
 		myControlGridStartCell(cg, isOpen, group->name);
 		if (track->hasUi)
 		    {
 		    char *url = trackUrl(track->track, chromName);
 		    char *longLabel = replaceChars(track->longLabel, "\"", "&quot;");
                     hPrintPennantIcon(track->tdb);
 
                     // Print an icon before the title when one is defined
@@ -5081,175 +8191,180 @@
     }
 if (showTrackControls)
     hButton("hgt.refresh", "refresh");
 hPrintf("</CENTER>\n");
 
 #ifdef SLOW
 /* We'll rely on the end of program to do the cleanup.
  * It turns out that the 'free' routine on Linux is
  * quite slow.  For chromosome level views the browser
  * spends about 1/3 of it's time doing the cleanup
  * below if it's enabled.  Since we really don't
  * need to reclaim this memory at this point I'm
  * taking this out.  Please don't delete the code though.
  * I'll like to keep it for testing now and then. -jk. */
 
+// TODO GALT cleanup sibs too? probably can do for window copies but low priority.
 /* Clean up. */
 for (track = trackList; track != NULL; track = track->next)
     {
     if (track->visibility != tvHide)
 	{
 	if (track->freeItems != NULL)
 	    track->freeItems(track);
 	lmCleanup(&track->lm);
 	}
     }
 #endif /* SLOW */
 hPrintf("</FORM>\n");
 
 /* hidden form for custom tracks CGI */
 hPrintf("<FORM ACTION='%s' NAME='customTrackForm'>", hgCustomName());
 cartSaveSession(cart);
 hPrintf("</FORM>\n");
 
 /* hidden form for track hub CGI */
 hPrintf("<FORM ACTION='%s' NAME='trackHubForm'>", hgHubConnectName());
 cartSaveSession(cart);
 hPrintf("</FORM>\n");
 
+// TODO GALT nothing to do here.
 pruneRedundantCartVis(trackList);
 if (measureTiming)
     measureTime("Done with trackForm");
 }
 
 static void toggleRevCmplDisp()
 /* toggle the reverse complement display mode */
 {
 // forces complement bases to match display
 revCmplDisp = !revCmplDisp;
 cartSetBooleanDb(cart, database, REV_CMPL_DISP, revCmplDisp);
 cartSetBooleanDb(cart, database, COMPLEMENT_BASES_VAR, revCmplDisp);
 }
 
-void zoomToSize(int newSize)
+void zoomToSize(long newSize)
 /* Zoom so that center stays in same place,
  * but window is new size.  If necessary move
  * center a little bit to keep it from going past
  * edges. */
 {
-int center = ((long long int)winStart + (long long int)winEnd)/2;
+long center = ((long long)virtWinStart + (long long)virtWinEnd)/2;
 if (center < 0)
-    errAbort("zoomToSize: error computing center: %d = (%d + %d)/2\n",
-             center, winStart, winEnd);
-if (newSize > seqBaseCount)
-    newSize = seqBaseCount;
-winStart = center - newSize/2;
-winEnd = winStart + newSize;
-if (winStart <= 0)
+    errAbort("zoomToSize: error computing center: %ld = (%ld + %ld)/2\n",
+             center, virtWinStart, virtWinEnd);
+if (newSize > virtSeqBaseCount)
+    newSize = virtSeqBaseCount;
+virtWinStart = center - newSize/2;
+virtWinEnd = virtWinStart + newSize;
+if (virtWinStart <= 0)
     {
-    winStart = 0;
-    winEnd = newSize;
+    virtWinStart = 0;
+    virtWinEnd = newSize;
     }
-else if (winEnd > seqBaseCount)
+else if (virtWinEnd > virtSeqBaseCount)
     {
-    winEnd = seqBaseCount;
-    winStart = winEnd - newSize;
+    virtWinEnd = virtSeqBaseCount;
+    virtWinStart = virtWinEnd - newSize;
     }
-winBaseCount = winEnd - winStart;
+virtWinBaseCount = virtWinEnd - virtWinStart;
 }
 
 void zoomAroundCenter(double amount)
 /* Set ends so as to zoom around center by scaling amount. */
 {
-double newSizeDbl = (winBaseCount*amount + 0.5);
-int newSize;
-if (newSizeDbl > seqBaseCount)
-    newSize = seqBaseCount;
+double newSizeDbl = (virtWinBaseCount*amount + 0.5);
+long newSize;
+if (newSizeDbl > virtSeqBaseCount)
+    newSize = virtSeqBaseCount;
 else if (newSizeDbl < 1.0)
     newSize = 1;
 else
-    newSize = (int)newSizeDbl;
+    newSize = (long)newSizeDbl;
 zoomToSize(newSize);
 }
 
 void zoomToBaseLevel()
 /* Set things so that it's zoomed to base level. */
 {
-zoomToSize(insideWidth/tl.mWidth);
+zoomToSize(fullInsideWidth/tl.mWidth);
 if (rulerMode == tvHide)
     cartSetString(cart, "ruler", "dense");
 }
 
 void relativeScroll(double amount)
 /* Scroll percentage of visible window. */
 {
-int offset;
-int newStart, newEnd;
+long offset;
+long newStart, newEnd;
 if (revCmplDisp)
     amount = -amount;
-offset = (int)(amount * winBaseCount);
+offset = (long)(amount * virtWinBaseCount);
 /* Make sure don't scroll of ends. */
-newStart = winStart + offset;
-newEnd = winEnd + offset;
+newStart = virtWinStart + offset;
+newEnd = virtWinEnd + offset;
 if (newStart < 0)
-    offset = -winStart;
-else if (newEnd > seqBaseCount)
-    offset = seqBaseCount - winEnd;
+    offset = -virtWinStart;
+else if (newEnd > virtSeqBaseCount)
+    offset = virtSeqBaseCount - virtWinEnd;
+
+//warn("\nGALT relativeScroll %f results in offset %ld to apply to virtWinStart,End\n", amount, offset); // DEBUG REMOVE
 
 /* Move window. */
-winStart += offset;
-winEnd += offset;
+virtWinStart += offset;
+virtWinEnd += offset;
 }
 
-void dinkWindow(boolean start, int dinkAmount)
+void dinkWindow(boolean start, long dinkAmount)
 /* Move one end or other of window a little. */
 {
 if (revCmplDisp)
     {
     start = !start;
     dinkAmount = -dinkAmount;
     }
 if (start)
     {
-   winStart += dinkAmount;
-   if (winStart < 0) winStart = 0;
+    virtWinStart += dinkAmount;
+    if (virtWinStart < 0) 
+	virtWinStart = 0;
     }
 else
     {
-   winEnd += dinkAmount;
-   if (winEnd > seqBaseCount)
-       winEnd = seqBaseCount;
+    virtWinEnd += dinkAmount;
+    if (virtWinEnd > virtSeqBaseCount)
+       virtWinEnd = virtSeqBaseCount;
     }
 }
 
-int dinkSize(char *var)
+long dinkSize(char *var)
 /* Return size to dink. */
 {
 char *stringVal = cartOptionalString(cart, var);
 double x;
-int insideX = trackOffsetX(); /* The global versions of these are not yet set */
-int insideWidth = tl.picWidth-gfxBorder-insideX;
-double guideBases = (double)guidelineSpacing * (double)(winEnd - winStart)
-                    / ((double)insideWidth);
+int fullInsideX = trackOffsetX(); /* The global versions of these are not yet set */
+int fullInsideWidth = tl.picWidth-gfxBorder-fullInsideX;
+double guideBases = (double)guidelineSpacing * (double)(virtWinEnd - virtWinStart)
+                    / ((double)fullInsideWidth);
 
 if (stringVal == NULL || !isdigit(stringVal[0]))
     {
     stringVal = "1";
     cartSetString(cart, var, stringVal);
     }
 x = atof(stringVal);
-int ret = round(x*guideBases);
+long ret = round(x*guideBases);
 
 return (ret == 0) ? 1 : ret;
 }
 
 void handlePostscript()
 /* Deal with Postscript output. */
 {
 struct tempName psTn, ideoPsTn;
 char *pdfFile = NULL, *ideoPdfFile = NULL;
 ZeroVar(&ideoPsTn);
 trashDirFile(&psTn, "hgt", "hgt", ".eps");
 
 if(!trackImgOnly)
     {
     printMenuBar();
@@ -5291,132 +8406,523 @@
     printf("<LI>Add assembly name and chromosome range to the image on the\n"
         "<A HREF=\"hgTrackUi?g=ruler\">configuration page of the base position track</A>.\n");
     printf("<LI>If using the UCSC Genes track, consider showing only one transcript per gene by turning off splice variants on the track configuration page.\n");
     printf("<LI>Increase the font size and remove the light blue vertical guidelines in the \n"
         "<A HREF=\"hgTracks?hgTracksConfigPage=configure\">image configuration menu</A>.");
     printf("<LI>In the image configuration menu, change the size of the image,\n"
             "to make it look more square.\n");
     printf("</UL>\n");
     printf("</div>\n");
 
 
     }
 else
     printf("<BR><BR>PDF format not available");
 
-printf("<a href='../cgi-bin/hgTracks'><input type='button' VALUE='Return to Browser'></a>\n");
+printf("<a href='%s?%s=%s'><input type='button' VALUE='Return to Browser'></a>\n",
+           hgTracksName(), cartSessionVarName(), cartSessionId(cart));
 }
 
 boolean isGenome(char *pos)
 /* Return TRUE if pos is genome. */
 {
 pos = trimSpaces(pos);
 return(sameWord(pos, "genome") || sameWord(pos, "hgBatch"));
 }
 
 void setRulerMode()
 /* Set the rulerMode variable from cart. */
 {
 char *s = cartUsualString(cart, RULER_TRACK_NAME, "dense");
 if (sameWord(s, "full") || sameWord(s, "on"))
     rulerMode = tvFull;
 else if (sameWord(s, "dense"))
     rulerMode = tvDense;
 else
     rulerMode = tvHide;
 }
 
 void setLayoutGlobals()
 /* Figure out basic dimensions of display.  */
 {
 withIdeogram = cartUsualBoolean(cart, "ideogram", TRUE);
 withLeftLabels = cartUsualBoolean(cart, "leftLabels", TRUE);
 withCenterLabels = cartUsualBoolean(cart, "centerLabels", TRUE);
 withGuidelines = cartUsualBoolean(cart, "guidelines", TRUE);
 if (!cartUsualBoolean(cart, "hgt.imageV1", FALSE))
+    {
     withNextItemArrows = cartUsualBoolean(cart, "nextItemArrows", FALSE);
-
     withNextExonArrows = cartUsualBoolean(cart, "nextExonArrows", TRUE);
+    }
 withExonNumbers = cartUsualBoolean(cart, "exonNumbers", TRUE);
+emAltHighlight = cartUsualBoolean(cart, "emAltHighlight", FALSE);
 if (!hIsGsidServer())
     {
     revCmplDisp = cartUsualBooleanDb(cart, database, REV_CMPL_DISP, FALSE);
     }
+emPadding = cartUsualInt(cart, "emPadding", emPadding);
 withPriorityOverride = cartUsualBoolean(cart, configPriorityOverride, FALSE);
-insideX = trackOffsetX();
-insideWidth = tl.picWidth-gfxBorder-insideX;
+fullInsideX = trackOffsetX();
+fullInsideWidth = tl.picWidth-gfxBorder-fullInsideX;
+}
+    
+void parseVirtPosition(char *position)
+/* parse virtual position 
+ *  TODO this is just temporary */
+{
+if (!position)
+    {
+    errAbort("position NULL");
+    }
+char *vPos = cloneString(position);
+char *colon = strchr(vPos, ':');
+if (!colon)
+    errAbort("position has no colon");
+char *dash = strchr(vPos, '-');
+if (!dash)
+    errAbort("position has no dash");
+*colon = 0;
+*dash = 0;
+virtWinStart = atol(colon+1) - 1;
+virtWinEnd = atol(dash+1);
+}
+
+void parseNonVirtPosition(char *position)
+/* parse non-virtual position */
+{
+if (!position)
+    {
+    errAbort("position NULL");
+    }
+char *vPos = cloneString(position);
+char *colon = strchr(vPos, ':');
+if (!colon)
+    errAbort("position has no colon");
+char *dash = strchr(vPos, '-');
+if (!dash)
+    errAbort("position has no dash");
+*colon = 0;
+*dash = 0;
+chromName = cloneString(vPos);
+winStart = atol(colon+1) - 1;
+winEnd = atol(dash+1);
+}
+
+boolean findNearestVirtMatch(char *chrom, int start, int end, boolean findNearest, long *retVirtStart, long *retVirtEnd)
+/* find nearest match on virt chrom. 
+ * findNearest flag means of no direct hits found, take the closest miss. */
+{
+// search for one or more overlapping windows
+struct positionMatch *mList = virtChromSearchForPosition(chrom, start, end, findNearest);
 
+// sort positions by virtPos (will be sorted by chrom, start, end)
+matchSortOnVPos(&mList);
+
+// merge contiguous matches spanning multiple touching windows
+mList = matchMergeContiguousVPos(mList);
+
+// DEBUG SHOW matching regions found.
+//warn("DEBUG mList post-sort-merge search positions: %d", slCount(mList));
+//struct positionMatch *m;
+//for (m=mList; m; m=m->next)
+    //{
+    //long span = m->virtEnd - m->virtStart; 
+    //warn("virtStar=%ld virtEnd=%ld span=%ld", m->virtStart, m->virtEnd, span);
+    //}
+
+// TODO search for the best match in pList
+// TODO this is crude, needs to fix, just finds the largest match:
+struct positionMatch *p, *best = NULL;
+long bigSpan = 0;
+for (p=mList; p; p=p->next)
+    {
+    long span = p->virtEnd - p->virtStart; 
+    if (span > bigSpan)
+	{
+	bigSpan = span;
+	best = p;
+	}
+    }
+if (best) // TODO do something better
+    {
+    // return the new location
+    *retVirtStart = best->virtStart;
+    *retVirtEnd   = best->virtEnd;
+    }
+else
+    { 
+    return FALSE;
+    }
+return TRUE;
+}
+
+void remapHighlightPos()
+// Remap non-virt highlight position if any to new virtMode chrom.
+{
+if (virtualSingleChrom())
+    return;
+struct highlightVar *h = parseHighlightInfo();
+//warn("remapHighlightPos h-> chrom=%s chromStart=%ld chromEnd=%ld", h->chrom, h->chromStart, h->chromEnd); // DEBUG REMOVE
+if (h && h->db && sameString(h->db, database))
+    {
+    long virtStart = 0, virtEnd = 0;
+    if (findNearestVirtMatch(h->chrom, h->chromStart, h->chromEnd, FALSE, &virtStart, &virtEnd)) // try to find the nearest match
+	{
+	// save new highlight position to cart var
+	char cartVar[1024];
+	safef(cartVar, sizeof cartVar, "%s.%s:%ld-%ld#%s", h->db, "virt", virtStart, virtEnd, h->hexColor);
+	cartSetString(cart, "highlight", cartVar);
+	}
+    else
+	{
+	// erase the highlight cartvar if it has no overlap with the new virt chrom
+	cartRemove(cart, "highlight");
+	}
+    }
 }
 
+
 void tracksDisplay()
 /* Put up main tracks display. This routine handles zooming and
  * scrolling. */
 {
-char newPos[256];
+//warn("top of tracksDisplay()\n"); // DEBUG REMOVE
+//warn("<a href=cartDump?hgsid=%s target=_blank>cartdump</a>", cartSessionId(cart)); // DEBUG REMOVE
 char *defaultPosition = hDefaultPos(database);
 char titleVar[256];
+char *oldPosition = cartUsualString(cart, "oldPosition", "");
+boolean findNearest = cartUsualBoolean(cart, "findNearest", FALSE);
+cartRemove(cart, "findNearest");
+//warn("findNearest = %d\n", findNearest); // DEBUG REMOVE
+
+boolean positionIsVirt = FALSE;
 position = getPositionFromCustomTracks();
+if (NULL == position)
+    {
+    if (sameOk(cgiOptionalString("position"), "lastDbPos"))
+	{
+	position = cartGetPosition(cart, database, &lastDbPosCart);
+	//warn("cartGetPosition: database=%s position=%s", database, position); // DEBUG REMOVE
+	// DEBUG REMOVE
+	//struct dyString *encoded = newDyString(4096);
+	//cartEncodeState(lastDbPosCart, encoded);
+	//warn("restored lastDbPosCart encoded state = [%s]", encoded->string); // DEBUG REMOVE
+        restoreSavedVirtPosition();
+	}
+    else
+	{
+	position = cloneString(cartUsualString(cart, "position", NULL));
+	}
+    //warn("position = %s\n", position); // DEBUG REMOVE
+    if (startsWith("virt:", position))
+	{
+	position = stripCommas(position); // sometimes the position string arrives with commas in it.
+	//warn("positionIsVirt=TRUE position = %s\n", position); // DEBUG REMOVE
+	positionIsVirt = TRUE;
+	goto gotVirtPos;
+	}
+    }
 
-if (position == NULL)
-    position = cartGetPosition(cart, database);
 
 if (sameString(position, ""))
     {
     hUserAbort("Please go back and enter a coordinate range or a search term in the \"search term\" field.<br>For example: chr22:20100000-20200000.\n");
     }
 
 chromName = NULL;
 winStart = 0;
 if (isGenome(position) || NULL ==
     (hgp = findGenomePos(database, position, &chromName, &winStart, &winEnd, cart)))
     {
+    //warn("doing weird stuff with isGenome() and findGenomePos(), add more debugging");
     if (winStart == 0)  /* number of positions found */
         {
         freeMem(position);
         position = cloneString(cartUsualString(cart, "lastPosition", defaultPosition));
         hgp = findGenomePos(database, position, &chromName, &winStart, &winEnd,cart);
         if (hgp != NULL && differentString(position, defaultPosition))
             cartSetString(cart, "position", position);
         }
     }
 
 /* After position is found set up hash of matches that should
    be drawn with names highlighted for easy identification. */
 createHgFindMatchHash();
 
 /* This means that no single result was found
 I.e., multiple results may have been found and are printed out prior to this code*/
 if (NULL == chromName)
     {
     // In case user manually edits the browser location as described in #13009,
     // revert the position.  If they instead choose from the list as we expect,
     // that will set the position to their choice.
     char *lastPosition = cartUsualString(cart, "lastPosition", hDefaultPos(database));
     cartSetString(cart, "position", lastPosition);
     return;
     }
 
-// save the current position to the cart var position.<db>
-cartSetDbPosition(cart, database, position);
+// TODO NOTE lastPosition gets set in cart.c
 
-seqBaseCount = hChromSize(database, chromName);
-winBaseCount = winEnd - winStart;
+
+gotVirtPos:
+
+virtMode = cartUsualBoolean(cart, "virtMode", FALSE);
 
 /* Figure out basic dimensions of display.  This
  * needs to be done early for the sake of the
  * zooming and dinking routines. */
 setLayoutGlobals();
+//warn("after setLayoutGlobals() fullInsideX=%d fullInsideWidth=%d tl.picWidth=%d gfxBorder=%d\n", 
+    //fullInsideX, fullInsideWidth, tl.picWidth, gfxBorder); // DEBUG REMOVE
+
+virtModeType = cartUsualString(cart, "virtModeType", virtModeType);
+//warn("virtModeType=%s\n", virtModeType); // DEBUG REMOVE
+
+if (positionIsVirt && virtualSingleChrom())
+    {
+    // we need chromName to be set before initRegionList() gets called.
+    position = cartUsualString(cart, "nonVirtPosition", "");
+    //warn("positionIsVirt && virtualSingleChrom(), going to nonVirtPosition %s", position);  // DEBUG REMOVE
+    if (!sameString(position,""))
+	parseNonVirtPosition(position);
+    }
+//warn("chromName=%s", chromName); // DEBUG REMOVE
+
+// TODO GALT do we need to add in other types that now depend on emGeneTable too? maybe singleTrans?
+if (sameString(virtModeType, "exonMostly") || sameString(virtModeType, "geneMostly")) 
+    {
+    setEMGeneTrack();
+    if (!emGeneTable) // there is no available gene table, undo exonMostly or geneMostly
+	{
+	virtModeType = "default";
+	cartSetString(cart, "virtModeType", virtModeType); 
+	}
+    }
+
+lastVirtModeType = cartUsualString(cart, "lastVirtModeType", lastVirtModeType); 
+
+again:
+//warn("virtModeType=%s lastVirtModeType=%s\n", virtModeType, lastVirtModeType); // DEBUG REMOVE
+if (sameString(virtModeType, "default") && !(sameString(lastVirtModeType, "default")))
+    { // RETURNING TO DEFAULT virtModeType
+    virtModeType = "default";
+    cartSetString(cart, "virtModeType", virtModeType); 
+    findNearest = TRUE;
+    if (positionIsVirt)
+	position = cartUsualString(cart, "nonVirtPosition", "");
+    char *nvh = cartUsualString(cart, "nonVirtHighlight", NULL);
+    if (nvh)
+	cartSetString(cart, "highlight", nvh); 
+    //warn("leaving virtMode, going to position %s", position);  // DEBUG REMOVE
+    if (!sameString(position,""))
+	parseNonVirtPosition(position);
+    }
+
+
+//warn("\nGALT before initRegionList chromName=%s winStart=%d winEnd=%d\n", chromName, winStart,winEnd); // DEBUG REMOVE
+if (!initRegionList())   // initialize the region list, sets virtModeExtraState
+    { // virt mode failed, forced to return to default
+    virtModeType = "default";
+    cartSetString(cart, "virtModeType", virtModeType);
+    position = cloneString(hDefaultPos(database));
+    hgp = findGenomePos(database, position, &chromName, &winStart, &winEnd, cart);
+    cartSetString(cart, "position", position);
+    positionIsVirt=FALSE;
+    virtMode=FALSE;
+    goto again;
+    }
+
+// PAD padding of exon regions is now being done inside the fetch/merge.
+//if (emPadding > 0) 
+    //padVirtRegions(emPadding); // this old routine does not handle multiple chroms yet
+
+//testRegionList(); // check if it is ascending non-overlapping regions. (this is not the case with custom user-defined-regions)
+
+makeVirtChrom();
+
+//testVirtChromBinarySearch();
+
+// ajax callback to convert chrom position to virt chrom position
+if (cartVarExists(cart, "hgt.convertChromToVirtChrom")) 
+    {
+    position = cartString(cart, "hgt.convertChromToVirtChrom");
+    char nvh[256];
+    safef(nvh, sizeof nvh, "%s.%s", database, position);
+    cartSetString(cart, "nonVirtHighlight", nvh);
+    parseNonVirtPosition(position);
+    if (findNearestVirtMatch(chromName, winStart, winEnd, FALSE, &virtWinStart, &virtWinEnd))
+	{
+	//hPrintf("<p id=virtWinStart>%ld</p><br>\n", virtWinStart);
+	//hPrintf("<p id=virtWinEnd>%ld</p><br>\n", virtWinEnd);
+	struct jsonElement *jsonForConvert = NULL;
+	jsonForConvert = newJsonObject(newHash(8));
+	jsonObjectAdd(jsonForConvert, "virtWinStart", newJsonNumber(virtWinStart));
+	jsonObjectAdd(jsonForConvert, "virtWinEnd", newJsonNumber(virtWinEnd));
+	hPrintf("<script type='text/javascript'>\n");
+	jsonPrint((struct jsonElement *) jsonForConvert, "convertChromToVirtChrom", 0);
+	hPrintf("</script>\n");
+	}
+    return;
+    }
+
+lastVirtModeExtraState = cartUsualString(cart, "lastVirtModeExtraState", lastVirtModeExtraState); 
+//warn("\nGALT virtModeExtraState=%s lastVirtModeExtraState=%s\n", virtModeExtraState, lastVirtModeExtraState); // DEBUG REMOVE
+
+// DISGUISED POSITION
+if (!startsWith("virt:", position) && (virtualSingleChrom()))
+    {
+    //warn("virtualSingleChrom trying to find best vchrom location corresponding to chrom=%s, winStart=%d, winEnd=%d\n", chromName, winStart, winEnd); // DEBUG REMOVE
+    findNearest = TRUE;
+
+    if (!(chromName && findNearestVirtMatch(chromName, winStart, winEnd, findNearest, &virtWinStart, &virtWinEnd))) // try to find the nearest match
+	{ // create 1k window near middle of vchrom
+	warn("Unable to find any region near the position on the chromosome in the multi-regions. Now using middle of view."); // KEEP?
+	virtWinStart = virtSeqBaseCount / 2;
+	virtWinEnd = virtWinStart + 1000;
+	if (virtWinEnd > virtSeqBaseCount)
+	    virtWinEnd = virtSeqBaseCount;
+	}
+    virtMode = TRUE;
+    }
+
+// when changing modes (or state like padding), first try to revert to plain non-virt position 
+if (!sameString(virtModeType, "default")
+ && !sameString(lastVirtModeType, "default")
+ && !(sameString(virtModeType, lastVirtModeType) && sameString(virtModeExtraState, lastVirtModeExtraState)))
+    { // CHANGE FROM ONE NON-DEFAULT virtMode to another.
+    virtChromChanged = TRUE;    // virtChrom changed
+    lastVirtModeType = "default";
+    cartSetString(cart, "lastVirtModeType", lastVirtModeType); // I think I do not need this
+    lastVirtModeExtraState = ""; 
+    findNearest = TRUE;
+    position = cartUsualString(cart, "nonVirtPosition", "");
+    //warn("leaving virtMode, going to position %s", position);  // DEBUG REMOVE
+    if (!sameString(position,""))
+	parseNonVirtPosition(position);
+    char *nvh = cartUsualString(cart, "nonVirtHighlight", "");
+    //warn("CHANGE FROM ONE NON-DEFAULT virtMode to another nvh=%s",nvh);  // DEBUG REMOVE
+    if (!sameString(nvh, "")) // DEBUG REMOVE? not needed probably
+	{
+	cartSetString(cart, "highlight", nvh); 
+	}
+    }
+
+// virt mode has not changed
+if (sameString(virtModeType, lastVirtModeType)
+ && sameString(virtModeExtraState, lastVirtModeExtraState))
+    {
+    if (virtMode)
+	{
+	if (positionIsVirt)
+	    {
+	    parseVirtPosition(position);
+	    //warn("\nGALT after parseVirtPosition(%s), virtWinStart=%ld virtWinEnd=%ld\n", position, virtWinStart, virtWinEnd); // DEBUG REMOVE
+	    }
+	else
+	    {	
+	    // Is this a new position to navigate to
+	    // or just an old inherited position.
+	    position = stripCommas(position);  // sometimes the position string arrives with commas in it.
+	    if (!sameString(position, oldPosition))
+		{
+		//warn("\nGALT before virtChromSearchForPosition(), position=%s oldPosition=%s\n", position, oldPosition); // DEBUG REMOVE
+
+		//warn("\nGALT          chrom=%s, winStart=%d, winEnd=%d\n", chromName, winStart, winEnd); // DEBUG REMOVE
+
+		if (!findNearestVirtMatch(chromName, winStart, winEnd, findNearest, &virtWinStart, &virtWinEnd))
+		    {
+		    // errAbort has kind of harsh behavior, and does not work well with ajax anyways
+		    warn("Location not found in Multi-Region View. " 
+		    "To return to default view at that location, click <a href=%s?%s=%s&position=%s:%d-%d&virtModeType=default>here</a>.\n"
+		    , hgTracksName(), cartSessionVarName(), cartSessionId(cart), chromName, winStart+1, winEnd); // DEBUG REMOVE
+		    // try to resume using oldPosition
+		    parseVirtPosition(oldPosition);
+		    }
+		}
+	    }
+	}
+    else
+	{
+	if (positionIsVirt)
+	    errAbort("positionIsVirt=%d but virtMode=%d", positionIsVirt, virtMode);
+	}
+
+
+    }
+else
+    {
+
+    if (sameString(virtModeType,"default"))  // we are leaving virtMode
+	{
+
+	virtMode = FALSE;
+
+	}
+    else
+	{
+
+	// ENTERING VIRTMODE
+
+	// First time initialization
+
+	findNearest = TRUE;
+
+	// For now, do this manually here:
+	// sets window to full genome size, which for these demos should be small except for allChroms
+	if (sameString(virtModeType, "exonMostly") || sameString(virtModeType, "geneMostly") || sameString(virtModeType, "kcGenes")) // create 1k window near middle of vchrom
+	    {
+	    //warn("trying to find best vchrom location corresponding to chrom=%s, winStart=%d, winEnd=%d\n", chromName, winStart, winEnd); // DEBUG REMOVE
+
+	    if (!(chromName && findNearestVirtMatch(chromName, winStart, winEnd, findNearest, &virtWinStart, &virtWinEnd))) // try to find the nearest match
+		{
+		warn("Unable to find any region near the position on the chromosome in the multi-regions. Now using middle of view."); // KEEP?
+		virtWinStart = virtSeqBaseCount / 2;
+		virtWinEnd = virtWinStart + 1000;
+		if (virtWinEnd > virtSeqBaseCount)
+		    virtWinEnd = virtSeqBaseCount;
+		}
+	    virtMode = TRUE;
+	    }
+	else if (sameString(virtModeType, "singleAltHaplo"))
+	    {
+	    virtWinStart = defaultVirtWinStart;
+	    virtWinEnd = defaultVirtWinEnd;
+	    virtMode = TRUE;
+	    }
+	else if (!sameString(virtModeType, "default"))
+	    {
+	    virtWinStart = 0;
+	    virtWinEnd = virtSeqBaseCount;
+	    virtMode = TRUE;
+	    }
+
+	remapHighlightPos(); 
+
+	}
+    //warn("\nGALT default virtWinStart=%ld virtWinEnd=%ld\n", virtWinStart, virtWinEnd); // DEBUG REMOVE
+
+    }
+
+if (virtMode)
+    virtChromName = "virt";
+else
+    virtChromName = chromName; 
+
+virtWinBaseCount = virtWinEnd - virtWinStart;
+
+
+//warn("\nGALT BEFORE navigation section, virtChromName=%s virtWinStart=%ld virtWinEnd=%ld\n", virtChromName, virtWinStart, virtWinEnd); // DEBUG REMOVE
 
 baseShowPos = cartUsualBoolean(cart, BASE_SHOWPOS, FALSE);
 baseShowAsm = cartUsualBoolean(cart, BASE_SHOWASM, FALSE);
 baseShowScaleBar = cartUsualBoolean(cart, BASE_SCALE_BAR, TRUE);
 baseShowRuler = cartUsualBoolean(cart, BASE_SHOWRULER, TRUE);
 safef(titleVar,sizeof(titleVar),"%s_%s", BASE_TITLE, database);
 baseTitle = cartUsualString(cart, titleVar, "");
 if (sameString(baseTitle, ""))
     baseTitle = NULL;
 
 if  (cgiVarExists("hgt.toggleRevCmplDisp"))
     toggleRevCmplDisp();
 setRulerMode();
 
 /* Do zoom/scroll if they hit it. */
@@ -5445,66 +8951,209 @@
 else if (cgiVarExists("hgt.out2"))
     zoomAroundCenter(3.0);
 else if (cgiVarExists("hgt.out3"))
     zoomAroundCenter(10.0);
 else if (cgiVarExists("hgt.out4"))
     zoomAroundCenter(100.0);
 else if (cgiVarExists("hgt.dinkLL"))
     dinkWindow(TRUE, -dinkSize("dinkL"));
 else if (cgiVarExists("hgt.dinkLR"))
     dinkWindow(TRUE, dinkSize("dinkL"));
 else if (cgiVarExists("hgt.dinkRL"))
     dinkWindow(FALSE, -dinkSize("dinkR"));
 else if (cgiVarExists("hgt.dinkRR"))
     dinkWindow(FALSE, dinkSize("dinkR"));
 
+/* Before loading items, deal with the next/prev item arrow buttons if pressed. */
+if (cgiVarExists("hgt.nextItem"))
+    doNextPrevItem(TRUE, cgiUsualString("hgt.nextItem", NULL));
+else if (cgiVarExists("hgt.prevItem"))
+    doNextPrevItem(FALSE, cgiUsualString("hgt.prevItem", NULL));
+
+//warn("\nGALT BEFORE clipping section, virtChromName=%s virtWinStart=%ld virtWinEnd=%ld\n", virtChromName, virtWinStart, virtWinEnd); // DEBUG REMOVE
+
+
 /* Clip chromosomal position to fit. */
-if (winEnd < winStart)
+if (virtWinEnd < virtWinStart)
     {
-    int temp = winEnd;
-    winEnd = winStart;
-    winStart = temp;
+    // swap start and end 
+    long temp = virtWinEnd;
+    virtWinEnd = virtWinStart;
+    virtWinStart = temp;
     }
-else if (winStart == winEnd)
+else if (virtWinStart == virtWinEnd)
     {
-    winStart -= 1;
-    winEnd += 1;
+    //warn("\nGALT weird virtWinStart == virtWinEnd = %ld\n", virtWinStart); // DEBUG REMOVE
+    // Size 0 window
+    virtWinStart -= 1;
+    virtWinEnd += 1;
     }
 
-if (winStart < 0)
+if (virtWinStart < 0)
     {
-    winStart = 0;
+    virtWinStart = 0;
     }
 
-if (winEnd > seqBaseCount)
+if (virtWinEnd > virtSeqBaseCount)
     {
-    winEnd = seqBaseCount;
+    virtWinEnd = virtSeqBaseCount;
     }
 
-if (winStart > seqBaseCount)
+if (virtWinStart > virtSeqBaseCount)
     {
-    winStart = seqBaseCount - 1000;
+    virtWinStart = virtSeqBaseCount - 1000;
     }
 
-winBaseCount = winEnd - winStart;
-if (winBaseCount <= 0)
-    hUserAbort("Window out of range on %s", chromName);
+virtWinBaseCount = virtWinEnd - virtWinStart;
+if (virtWinBaseCount <= 0)
+    hUserAbort("Window out of range on %s", virtChromName);
+
+//warn("\nGALT BEFORE portal start section, virtChromName=%s virtWinStart=%ld virtWinEnd=%ld\n", virtChromName, virtWinStart, virtWinEnd); // DEBUG REMOVE
+
+if (!cartUsualBoolean(cart, "hgt.psOutput", FALSE)
+ && !cartUsualBoolean(cart, "hgt.imageV1" , FALSE))
+    {
+
+    // TODO GALT Guidelines broken on virtChrom for 3X.
+    //  works in demo0 or real chrom. Only the guidelines seem to be messed up.
+    //  Other stuff works. 1X works too. 
+    // Since we are not using 3X for now, I will leave this for a future fix.
+    // To test 3X, do make clean; make CFLAGS=-DIMAGEv2_DRAG_SCROLL_SZ=3
+
+    // Start an imagebox (global for now to avoid huge rewrite of hgTracks)
+    // Set up imgBox dimensions
+    int sideSliceWidth  = 0;   // Just being explicit
+    if (withLeftLabels)
+        sideSliceWidth   = (fullInsideX - gfxBorder*3) + 2;
+
+    //  for the 3X expansion effect to work, this needs to happen BEFORE we create the windows list
+    //    in makeWindowListFromVirtChrom()
+    theImgBox = imgBoxStart(database,virtChromName,virtWinStart,virtWinEnd,
+                            (!revCmplDisp),sideSliceWidth,tl.picWidth);
+    // Define a portal with a default expansion size,
+    // then set the global dimensions to the full image size
+    if (imgBoxPortalDefine(theImgBox,&virtWinStart,&virtWinEnd,&(tl.picWidth),0))
+        {
+        virtWinBaseCount = virtWinEnd - virtWinStart;
+        fullInsideWidth = tl.picWidth - gfxBorder - fullInsideX;
+        }
+
+    }
+
+//warn("AFTER portal start section, virtChromName=%s virtWinStart=%ld virtWinEnd=%ld\n", virtChromName, virtWinStart, virtWinEnd); // DEBUG REMOVE
+
+
+// For portal 3x expansion to work right, it would have to take effect, at least temporarily,
+// right here before we call makeWindowListFromVirtChrom().
+windows = makeWindowListFromVirtChrom(virtWinStart, virtWinEnd); // creates windows, sets chrom, winStart, winEnd from virtual chrom
+if (slCount(windows) > 4000) // TODO a more graceful response
+	errAbort("Too many windows in view. Unable to display image at requested zoom level.");
+
+//warn("winCount=%d\n", slCount(windows));
+
+//warn("AFTER makeWindowListFromVirtChrom, virtChromName=%s virtWinStart=%ld virtWinEnd=%ld\n", virtChromName, virtWinStart, virtWinEnd); // DEBUG REMOVE
+
+
+allocPixelsToWindows(); // sets windows insideWidth and insideX
+
+//warn("AFTER allocPixelsToWindows, virtChromName=%s virtWinStart=%ld virtWinEnd=%ld\n", virtChromName, virtWinStart, virtWinEnd); // DEBUG REMOVE
+
+
+
+if (theImgBox)
+    {
+    // If a portal was established, then set the global dimensions back to the portal size
+    if (imgBoxPortalDimensions(theImgBox,NULL,NULL,NULL,NULL,&virtWinStart,&virtWinEnd,&(tl.picWidth),NULL))
+        {
+        virtWinBaseCount = virtWinEnd - virtWinStart;
+        fullInsideWidth = tl.picWidth-gfxBorder-fullInsideX;
+        }
+    }
+
+//warn("AFTER portal demensions section, virtChromName=%s virtWinStart=%ld virtWinEnd=%ld\n", virtChromName, virtWinStart, virtWinEnd); // DEBUG REMOVE
+
+
+setGlobalsFromWindow(windows); // first window
+
+seqBaseCount = hChromSize(database, chromName);
+
+//warn("virtWinStart=%ld virtWinEnd=%ld\n", virtWinStart, virtWinEnd); // DEBUG REMOVE
+//warn("virtWinBaseCount=%ld virtSeqBaseCount=%ld\n", virtWinBaseCount, virtSeqBaseCount); // DEBUG REMOVE
+
 /* Save computed position in cart. */
-sprintf(newPos, "%s:%d-%d", chromName, winStart+1, winEnd);
 cartSetString(cart, "org", organism);
 cartSetString(cart, "db", database);
-cartSetString(cart, "position", newPos);
+
+char newPos[256];
+
+// disguise the cart pos var
+if (virtualSingleChrom()) // DISGUISE VMODE
+    safef(newPos, sizeof newPos, "%s", windowsSpanPosition());
+else // usual
+    safef(newPos, sizeof newPos, "%s:%ld-%ld", virtChromName, virtWinStart+1, virtWinEnd);
+
+position = cloneString(newPos);
+cartSetString(cart, "position", position);
+cartSetString(cart, "oldPosition", position); // DEBUG REMOVE MAYBE
+//cartSetString(cart, "lastPosition", position); // DEBUG REMOVE TESTING.
+
+cartSetBoolean(cart, "virtMode", virtMode);
+cartSetString(cart, "virtModeType", virtModeType); 
+virtModeType = cartString(cart, "virtModeType"); // refresh the pointer after changing hash
+
+
+lastVirtModeType=virtModeType;
+cartSetString(cart, "lastVirtModeType", lastVirtModeType);
+lastVirtModeType = cartString(cart, "lastVirtModeType"); // refresh 
+
+lastVirtModeExtraState=virtModeExtraState;
+cartSetString(cart, "lastVirtModeExtraState", lastVirtModeExtraState);
+lastVirtModeExtraState = cartString(cart, "lastVirtModeExtraState"); // refresh 
+
+
+// save a quick position to use if user leaves virtMode.
+if (virtMode)
+    cartSetString(cart, "nonVirtPosition", nonVirtPositionFromWindows());  
+else
+    cartRemove(cart, "nonVirtPosition");
+
+// save a highlight position to use if user leaves virtMode.
+char *nvh = NULL;
+if (virtMode)
+   nvh = nonVirtPositionFromHighlightPos();
+if (virtMode && nvh)
+    cartSetString(cart, "nonVirtHighlight", nvh);  
+else
+    cartRemove(cart, "nonVirtHighlight");
+
+// save lastDbPos. save the current position and other important cart vars related to virtual view.
+
+lastDbPosSaveCartSetting("position");
+lastDbPosSaveCartSetting("nonVirtPosition");
+lastDbPosSaveCartSetting("virtMode");
+lastDbPosSaveCartSetting("virtModeType");
+lastDbPosSaveCartSetting("lastVirtModeType");
+lastDbPosSaveCartSetting("lastVirtModeExtraState");
+
+// DEBUG REMOVE
+//struct dyString *encoded = newDyString(4096);
+//cartEncodeState(lastDbPosCart, encoded);
+//warn("lastDbPosCart encoded state = [%s]", encoded->string); // DEBUG REMOVE
+
+//warn("database=%s, position=%s, virtModeType=%s, virtModeExtraState=%s", database, position, virtModeType, virtModeExtraState); // DEBUG REMOVE
+cartSetDbPosition(cart, database, lastDbPosCart);
+
 if (cartUsualBoolean(cart, "hgt.psOutput", FALSE))
     handlePostscript();
 else
     doTrackForm(NULL, NULL);
 }
 
 void chromInfoTotalRow(int count, long long total)
 /* Make table row with total number of sequences and size from chromInfo. */
 {
 cgiSimpleTableRowStart();
 cgiSimpleTableFieldStart();
 printf("Total: %d", count);
 cgiTableFieldEnd();
 cgiSimpleTableFieldStart();
 printLongWithCommas(stdout, total);
@@ -5850,52 +9499,60 @@
 hPrintf("Mousetrap.bind('c t', function() { document.customTrackForm.submit();return false; }); \n");
 hPrintf("Mousetrap.bind('t h', function() { document.trackHubForm.submit();return false; }); \n");
 hPrintf("Mousetrap.bind('r s', function() { $('input[name=\"hgt.setWidth\"]').click() }); \n");
 hPrintf("Mousetrap.bind('r f', function() { $('input[name=\"hgt.refresh\"]').click() }); \n");
 hPrintf("Mousetrap.bind('r v', function() { $('input[name=\"hgt.toggleRevCmplDisp\"]').click() }); \n");
 hPrintf("Mousetrap.bind('v d', gotoGetDnaPage); \n");
 
 // focus
 hPrintf("Mousetrap.bind('/', function() { $('input[name=\"hgt.positionInput\"]').focus(); return false; }, 'keydown'); \n");
 hPrintf("Mousetrap.bind('?', function() { $( \"#hotkeyHelp\" ).dialog({width:'600'});}); \n");
 
 // menu
 if (gotExtTools)
     hPrintf("Mousetrap.bind('s t', showExtToolDialog); \n");
 
+// multi-region views
+hPrintf("Mousetrap.bind('e v', function() { window.location.href='%s?%s=%s&virtModeType=exonMostly'; return false; });  \n",
+           hgTracksName(), cartSessionVarName(), cartSessionId(cart));
+hPrintf("Mousetrap.bind('d v', function() { window.location.href='%s?%s=%s&virtModeType=default'; return false; });  \n",
+           hgTracksName(), cartSessionVarName(), cartSessionId(cart));
+
+
 hPrintf("</script>\n");
 
 // help dialog
 hPrintf("<div style=\"display:none\" id=\"hotkeyHelp\" title=\"Keyboard shortcuts\">\n");
 hPrintf("<table style=\"width:580px; border-color:#666666; border-collapse:collapse\">\n");
 hPrintf("<tr><td style=\"width:18ch\">left 10&#37;</td><td width=\"auto\" class=\"hotkey\">ctrl+j</td>  <td style=\"width:24ch\"> track search</td><td class=\"hotkey\">t then s</td>               </tr>\n"); // percent sign
 hPrintf("<tr><td> left 1/2 screen</td><td class=\"hotkey\">j</td>   <td> default tracks</td><td class=\"hotkey\">d then t</td>             </tr>\n");
 hPrintf("<tr><td> left one screen</td><td class=\"hotkey\">J</td>   <td> default order</td><td class=\"hotkey\">d then o</td>              </tr>\n");
 hPrintf("<tr><td> right 10&#37;</td><td class=\"hotkey\">ctrl+l</td><td> hide all</td><td class=\"hotkey\">h then a</td>                   </tr>\n"); // percent sign
 hPrintf("<tr><td> right 1/2 screen</td><td class=\"hotkey\">l</td>  <td> custom tracks</td><td class=\"hotkey\">c then t</td>              </tr>\n");
 hPrintf("<tr><td> right one screen</td><td class=\"hotkey\">L</td>  <td> track hubs</td><td class=\"hotkey\">t then h</td>                 </tr>\n");
 hPrintf("<tr><td> zoom in 1.5x</td><td class=\"hotkey\">ctrl+i</td> <td> configure</td><td class=\"hotkey\">c then f</td>                  </tr>\n"); 
 hPrintf("<tr><td> zoom in 3x</td><td class=\"hotkey\">i</td>        <td> reverse</td><td class=\"hotkey\">r then v</td>                    </tr>\n");
 hPrintf("<tr><td> zoom in 10x</td><td class=\"hotkey\">I</td>       <td> resize</td><td class=\"hotkey\">r then s</td>                     </tr>\n");
 hPrintf("<tr><td> zoom in base level</td><td class=\"hotkey\">b</td><td> refresh</td><td class=\"hotkey\">r then f</td>                    </tr>\n");
 hPrintf("<tr><td> zoom out 1.5x</td><td class=\"hotkey\">ctrl+k</td><td> jump to position box</td><td class=\"hotkey\">/</td>              </tr>\n"); 
 hPrintf("<tr><td> zoom out 3x</td><td class=\"hotkey\">k</td>");
 if (gotExtTools)
     hPrintf("<td>send to external tool</td><td class=\"hotkey\">s then t</td>");
 hPrintf("               </tr>\n");
-hPrintf("<tr><td> zoom out 10x</td><td class=\"hotkey\">K</td>     <td>view DNA</td><td class='hotkey'>v then d</td>         </tr>\n");
-hPrintf("<tr><td> zoom out 100x</td><td class=\"hotkey\">0</td>             </tr>\n");
+hPrintf("<tr><td> zoom out 10x</td><td class=\"hotkey\">K</td>      <td> exon view</td><td class=\"hotkey\">e then v</td>                  </tr>\n");
+hPrintf("<tr><td> zoom out 100x</td><td class=\"hotkey\">0</td>     <td> default view</td><td class=\"hotkey\">d then v</td>               </tr>\n");
+hPrintf("<tr><td>              </td><td class=\"hotkey\"> </td>     <td> view DNA</td><td class='hotkey'>v then d</td>                     </tr>\n");
 hPrintf("</table>\n");
 hPrintf("<img style=\"margin:8px\" src=\"../images/shortcutHelp.png\">");
 hPrintf("</div>\n");
 }
 
 void doMiddle(struct cart *theCart)
 /* Print the body of an html file.   */
 {
 cart = theCart;
 measureTiming = hPrintStatus() && isNotEmpty(cartOptionalString(cart, "measureTiming"));
 if (measureTiming)
     measureTime("Startup");
 
 hgBotDelay();
 if (measureTiming)
@@ -5928,30 +9585,31 @@
 int timeout = cartUsualInt(cart, "udcTimeout", 300);
 if (udcCacheTimeout() < timeout)
     udcSetCacheTimeout(timeout);
 
 // tell UDC where to put its statistics file
 char *udcLogFile;
 if ((udcLogFile =  cfgOption("udcLog")) != NULL)
     {
     FILE *fp = mustOpen(udcLogFile, "a");
     udcSetLog(fp);
     }
 
 initTl();
 
 char *configPageCall = cartCgiUsualString(cart, "hgTracksConfigPage", "notSet");
+char *configMultiRegionPageCall = cartCgiUsualString(cart, "hgTracksConfigMultiRegionPage", "notSet");
 
 /* Do main display. */
 
 if (cartUsualBoolean(cart, "hgt.trackImgOnly", FALSE))
     {
     trackImgOnly = TRUE;
     ideogramToo = cartUsualBoolean(cart, "hgt.ideogramToo", FALSE);
     hideControls = TRUE;
     withNextItemArrows = FALSE;
     withNextExonArrows = FALSE;
     hgFindMatches = NULL;     // XXXX necessary ???
     }
 
 jsonForClient = newJsonObject(newHash(8));
 jsonObjectAdd(jsonForClient, "cgiVersion", newJsonString(CGI_VERSION));
@@ -5978,49 +9636,55 @@
 #ifdef LOWELAB
     jsIncludeFile("lowetooltip.js", NULL);
 #endif///def LOWELAB
 
     webIncludeResourceFile("jquery-ui.css");
     if (!searching)     // NOT doing search
         {
         webIncludeResourceFile("jquery.contextmenu.css");
         jsIncludeFile("jquery.contextmenu.js", NULL);
         webIncludeResourceFile("ui.dropdownchecklist.css");
         jsIncludeFile("ui.dropdownchecklist.js", NULL);
         jsIncludeFile("ddcl.js", NULL);
         }
 
     hPrintf("<div id='hgTrackUiDialog' style='display: none'></div>\n");
+    hPrintf("<div id='hgTracksDialog' style='display: none'></div>\n");
 
     cartFlushHubWarnings();
     }
 
 if (cartVarExists(cart, "chromInfoPage"))
     {
     cartRemove(cart, "chromInfoPage");
     chromInfoPage();
     }
 else if (differentString(cartUsualString(cart, TRACK_SEARCH,"0"),"0"))
     {
     doSearchTracks(groupList);
     }
 else if (sameWord(configPageCall, "configure") ||
          sameWord(configPageCall, "configure tracks and display"))
     {
     cartRemove(cart, "hgTracksConfigPage");
     configPage();
     }
+else if (sameWord(configMultiRegionPageCall, "multi-region"))
+    {
+    cartRemove(cart, "hgTracksConfigMultiRegionPage");
+    configMultiRegionPage();
+    }
 else if (cartVarExists(cart, configHideAll))
     {
     cartRemove(cart, configHideAll);
     configPageSetTrackVis(tvHide);
     }
 else if (cartVarExists(cart, configShowAll))
     {
     cartRemove(cart, configShowAll);
     configPageSetTrackVis(tvDense);
     }
 else if (cartVarExists(cart, configDefaultAll))
     {
     cartRemove(cart, configDefaultAll);
     configPageSetTrackVis(-1);
     }
@@ -6053,42 +9717,50 @@
 else if (cartVarExists(cart, configShowEncodeGroups))
     {
     /* currently not used */
     cartRemove(cart, configShowEncodeGroups);
     struct grp *grp = NULL, *grps = hLoadGrps(database);
     for (grp = grps; grp != NULL; grp = grp->next)
         if (startsWith("encode", grp->name))
             collapseGroup(grp->name, FALSE);
     configPageSetTrackVis(-2);
     }
 else
     {
     tracksDisplay();
     }
 
+if (cartVarExists(cart, "hgt.convertChromToVirtChrom"))
+    {
+    cartRemove(cart, "hgt.convertChromToVirtChrom");
+    return;
+    }
+
 jsonObjectAdd(jsonForClient, "measureTiming", newJsonBoolean(measureTiming));
 // js code needs to know if a highlightRegion is defined for this db
 char *highlightDef = cartOptionalString(cart, "highlight");
 if (highlightDef && startsWith(database,highlightDef) && highlightDef[strlen(database)] == '.')
     jsonObjectAdd(jsonForClient, "highlight", newJsonString(highlightDef));
 jsonObjectAdd(jsonForClient, "enableHighlightingDialog",
 	      newJsonBoolean(cartUsualBoolean(cart, "enableHighlightingDialog", TRUE)));
 hPrintf("<script type='text/javascript'>\n");
 jsonPrint((struct jsonElement *) jsonForClient, "hgTracks", 0);
 hPrintf("</script>\n");
 
+boolean gotExtTools = extToolsEnabled();
+setupHotkeys(gotExtTools);
+if (gotExtTools)
+    printExtMenuData();
+
 if (measureTiming)
     measureTime("Time at end of doMiddle, next up cart write");
+
 if (cartOptionalString(cart, "udcTimeout"))
     {
     warn("The Genome Browser cart currently includes the \"udcTimeout\" string. "
 	"While this is useful for debugging hubs, it may negatively impact "
 	"performance.   To clear this variable, click "
 	"<A HREF=hgTracks?hgsid=%s&udcTimeout=[]>here</A>.",cartSessionId(cart));
     }
 
-boolean gotExtTools = extToolsEnabled();
-setupHotkeys(gotExtTools);
-if (gotExtTools)
-    printExtMenuData();
-
 }
+