Index: src/hg/hgTracks/imageV2.c
===================================================================
RCS file: src/hg/hgTracks/imageV2.c
diff -N src/hg/hgTracks/imageV2.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/hg/hgTracks/imageV2.c	26 Jun 2009 17:49:09 -0000	1.1
@@ -0,0 +1,1020 @@
+/* hgTracks - Human Genome browser main cgi script. */
+#include "common.h"
+#include "hCommon.h"
+#include "linefile.h"
+#include "portable.h"
+#include "memalloc.h"
+#include "localmem.h"
+#include "obscure.h"
+#include "dystring.h"
+#include "hash.h"
+#include "jksql.h"
+#include "gfxPoly.h"
+#include "memgfx.h"
+#include "hvGfx.h"
+#include "psGfx.h"
+#include "cheapcgi.h"
+#include "hPrint.h"
+#include "htmshell.h"
+#include "cart.h"
+#include "hdb.h"
+#include "hui.h"
+#include "hgFind.h"
+#include "hgTracks.h"
+#include "trashDir.h"
+#include "grp.h"
+#include "versionInfo.h"
+#include "web.h"
+#include "cds.h"
+#include "cutterTrack.h"
+#include "wikiTrack.h"
+#include "ctgPos.h"
+#include "bed.h"
+#include "bigBed.h"
+#include "bedCart.h"
+#include "customTrack.h"
+#include "cytoBand.h"
+#include "ensFace.h"
+#include "liftOver.h"
+#include "pcrResult.h"
+#include "wikiLink.h"
+#include "jsHelper.h"
+#include "mafTrack.h"
+#include "hgConfig.h"
+#include "encode.h"
+#include "agpFrag.h"
+#include "imageV2.h"
+
+static char const rcsid[] = "$Id$";
+
+struct imgBox   *theImgBox   = NULL; // Make this global for now to avoid huge rewrite
+struct image    *theOneImg   = NULL; // Make this global for now to avoid huge rewrite
+struct imgTrack *curImgTrack = NULL; // Make this global for now to avoid huge rewrite
+struct imgSlice *curSlice    = NULL; // Make this global for now to avoid huge rewrite
+struct mapSet   *curMap      = NULL; // Make this global for now to avoid huge rewrite
+//struct mapItem  *curMapItem  = NULL; // Make this global for now to avoid huge rewrite
+
+//#define IMAGEv2_UI
+#ifdef IMAGEv2_UI
+// IMAGEv2
+// The new way to do images
+// Terms:
+// "image box": The new version of the image on the html page.  It is a table with rows that contain tracks which are made up of parts of images.
+//    "imgBox": The C struct that contains all information to render the image box on the html page.
+//    "imgTbl": The HTML structure that contains individual track images.  The javascript client controls the imgTbl.
+//          Thus cgi knows imgBox while html/js knows imgTbl.
+// "image": A single gif.  An image may contain mutilple tracks.
+//          Even as a single track and image may contain a data image, sideLabel and centerLabel
+// "map" or "mapSet": The image map for providing links to items in a data image.
+//          An image and map are a 1 to 1 pair and pixel coordinates are alway image relative.
+// "slice" or "imgSlice": The cgi concept of the portion of an image that is sent to the html/js side.
+//          Thus, if we are sending a 3X sized image, the "slice" spans the entire 3X.
+//          Frequently a subset of an image, but possibly the whole image.
+//          Even if the image is of a single track, it might still be cut into data image, sideLabel and centerLabel slices.
+// "sliceMap": The portion of a map that belongs to a slice.  The pixel coordinates are always image relative, not sice relative.
+// "portal" or "imgPortal": The html/js concept of the portion of a slice that is visible in the browser.
+//          Thus, if we are sending a 3X sized data image slice, the "portal" seen in the browser spans only 1X.
+//          Frequently a subset of a slice, but possibly the whole image slice.
+// "imgTrack" or track image: The cgi side whole shabang for rendering one track.
+//          It contains track specific information and three slices: data, sideLabel and centerLabel
+//          The imgBox contains N imgTracks.  Typically an ajax/JSON request gets a single imgTrack returned.
+// image box support: This can contain all the support variables that are global to the image box (like font size, box width, etc.).
+//          At this point, lets not talk structure or cgi vs. html/js but leave this container as a concept that we know needs filling.
+// imgBox is the top level and contains all info to draw the image in HTML
+//    - contains slList of all imgTrack structs shown
+// imgTrack contains all info to display one track/subtrack in the imgBox
+//    - contains 3 imgSlice structs (data, centerLabel, sideLabel
+// imgSlice contains all info to display a portion of an image file
+//    - contains 1 image struct
+// image contains all information about an image file and associated map box
+//
+//////// Maps
+//struct mapItem // IMAGEv2: single map item in an image map.
+//   {
+//   struct mapItem *next;      // slList
+//   char *linkVar;             // the get variables associated with the map link
+//   char *title;               // item title
+//   int topLeftX;              // in pixels relative to image
+//   int topLeftY;              // in pixels relative to image
+//   int bottomRightX;          // in pixels relative to image
+//   int bottomRightY;          // in pixels relative to image
+//   };
+//struct mapSet // IMAGEv2: full map for image OR partial map for slice
+//   {
+//   char *name;                // to point an image to a map in HTML
+//   struct image *parentImg;   // points to the image this map belongs to
+//   char *linkRoot;            // the common or static portion of the link for the entire image
+//   struct mapItem *items;     // list of items
+//   };
+struct mapSet *mapSetStart(char *name,struct image *img,char *linkRoot)
+/* Starts a map (aka mapSet) which is the seet of links and image locations used in HTML.
+   Complete a map by adding items with mapItemAdd() */
+{
+struct mapSet *map;
+AllocVar(map);
+return mapSetUpdate(map,name,img,linkRoot);
+}
+struct mapSet *mapSetUpdate(struct mapSet *map,char *name,struct image *img,char *linkRoot)
+/* Updates an existing map (aka mapSet) */
+{
+if(name != NULL && differentStringNullOk(name,map->name))
+    {
+    if(map->name != NULL)
+        freeMem(map->name);
+    map->name = cloneString(name);
+    }
+if(img != NULL && img != map->parentImg)
+    map->parentImg = img;
+if(linkRoot != NULL && differentStringNullOk(linkRoot,map->linkRoot))
+    {
+    if(map->linkRoot != NULL)
+        freeMem(map->linkRoot);
+    map->linkRoot = cloneString(linkRoot);
+    }
+return map;
+}
+struct mapItem *mapSetItemAdd(struct mapSet *map,char *link,char *title,int topLeftX,int topLeftY,int bottomRightX,int bottomRightY)
+/* Added a single mapItem to a growing mapSet */
+{
+struct mapItem *item;
+AllocVar(item);
+if(title != NULL)
+    item->title = cloneString(title);
+if(link != NULL)
+    {
+    if(map->linkRoot != NULL && startsWith(map->linkRoot,link))
+        item->linkVar = cloneString(link + strlen(map->linkRoot));
+    else
+        item->linkVar = cloneString(link);
+    }
+item->topLeftX     = topLeftX;
+item->topLeftY     = topLeftY;
+item->bottomRightX = bottomRightX;
+item->bottomRightY = bottomRightY;
+slAddHead(&(map->items),item);
+//warn("Added map(%s) item '%s' count:%d",map->name,title,slCount(map->items));
+return map->items;
+}
+void mapItemFree(struct mapItem **pItem)
+/* frees all memory assocated with a single mapItem */
+{
+if(pItem != NULL && *pItem != NULL)
+    {
+    struct mapItem *item = *pItem;
+    if(item->title != NULL)
+        freeMem(item->title);
+    if(item->linkVar != NULL)
+        freeMem(item->linkVar);
+    freeMem(item);
+    *pItem = NULL;
+    }
+}
+boolean mapItemConsistentWithImage(struct mapItem *item,struct image *img,boolean verbose)
+/* Test whether a map item is consistent with the image it is supposed to be for */
+{
+if ((item->topLeftX     < 0 || item->topLeftX     >= img->width)
+||  (item->bottomRightX < 0 || item->bottomRightX >  img->width  || item->bottomRightX <= item->topLeftX)
+||  (item->topLeftY     < 0 || item->topLeftY     >= img->height)
+||  (item->bottomRightY < 0 || item->bottomRightY >  img->height || item->bottomRightY <= item->topLeftY))
+    {
+    if (verbose)
+        warn("mapItem has coordinates (topX:%d topY:%d botX:%d botY:%d) outside of image (width:%d height:%d)",
+             item->topLeftX,item->topLeftY,item->bottomRightX,item->bottomRightY,img->width,img->height);
+    return FALSE;
+    }
+return TRUE;
+}
+boolean mapSetIsComplete(struct mapSet *map,boolean verbose)
+/* Tests the completeness and consistency of this map (mapSet) */
+{
+if (map == NULL)
+    {
+    if (verbose)
+        warn("map is NULL");
+    return FALSE;
+    }
+if (map->parentImg == NULL)
+    {
+    if (verbose)
+        warn("map is missing a parant image.");
+    return FALSE;
+    }
+if (map->parentImg->file == NULL)
+    {
+    if (verbose)
+        warn("map has image which has no file.");
+    return FALSE;
+    }
+if (map->items == NULL) // This is okay
+    {
+    //if (verbose)
+    //    warn("map(%s) has no items.",(map->name?map->name:map->parentImg->file));
+    //return FALSE;
+    return TRUE; // Accept this as legitimate
+    }
+struct mapItem *item = map->items;
+for (;item != NULL; item = item->next)
+    {
+    if(!mapItemConsistentWithImage(item,map->parentImg,verbose))
+        return FALSE;
+    if(item->linkVar == NULL && map->linkRoot == NULL)
+        {
+        if (verbose)
+            warn("item for map(%s) has no link.",(map->name?map->name:map->parentImg->file));
+        return FALSE;
+        }
+    }
+return TRUE;
+}
+/* Tests the completeness and consistency of this imgTrack (not including slices) */
+void mapSetFree(struct mapSet **pMap)
+/* frees all memory (including items) assocated with a single mapSet */
+{
+if(pMap != NULL && *pMap != NULL)
+    {
+    struct mapSet *map = *pMap;
+    struct mapItem *item = NULL;
+    while((item = slPopHead(&(map->items))) != NULL )
+        mapItemFree(&item);
+    freeMem(map->name);
+    // Don't free parentImg, as it should be freed independently
+    freeMem(map->linkRoot);
+    freeMem(map);
+    *pMap = NULL;
+    }
+}
+
+//////// Images
+//struct image // IMAGEv2: single image which may have multiple imgSlices focused on it
+//    {
+//    struct image *next;       // slList (Not yet used)
+//    char *file;               // name of file that hold the image
+//    char *title;              // image wide title
+//    int  width;               // in pixels
+//    int  height;              // in pixels
+//    struct mapSet *map;       // map assocated with this image (may be NULL)
+//    };
+struct image *imgCreate(char *gif,char *title,int width,int height)
+/* Creates a single image container.
+   A map map be added with imgMapStart(),mapSetItemAdd() */
+{
+struct image *img;
+AllocVar(img);
+if(gif != NULL)
+    img->file   = cloneString(gif);
+if(title != NULL)
+    img->title  = cloneString(title);
+img->height = height;
+img->width  = width;
+img->map    = NULL;
+return img;
+}
+struct mapSet *imgMapStart(struct image *img,char *name,char *linkRoot)
+/* Starts a map associated with an image. Map items can then be added to the returned pointer with mapSetItemAdd() */
+{
+if(img->map != NULL)
+    {
+    warn("imgAddMap() but map already exists.  Being replaced.");
+    mapSetFree(&(img->map));
+    }
+img->map = mapSetStart(name,img,linkRoot);
+return img->map;
+}
+struct mapSet *imgGetMap(struct image *img)
+/* Gets the map associated with this image. Map items can then be added to the map with mapSetItemAdd() */
+{
+return img->map;
+}
+void imgFree(struct image **pImg)
+/* frees all memory assocated with an image (including a map) */
+{
+if(pImg != NULL && *pImg != NULL)
+    {
+    struct image *img = *pImg;
+    mapSetFree(&(img->map));
+    freeMem(img->file);
+    freeMem(img->title);
+    freeMem(img);
+    *pImg = NULL;
+    }
+}
+
+//////// Slices
+//struct imgSlice // IMAGEv2: the portion of an image that is displayable for one track
+//    {
+//    struct imgSlice *next;  // slList
+//    enum sliceType type;    // Type of slice (currently only 3)
+//    struct image *parentImg;// the actual image/gif
+//    char *title;            // slice wide title
+//    struct mapSet *map;     // A slice specific map.  It contains a subset of the img->map. Coordinates must be img relative NOT slice relative!
+//    int  width;             // in pixels (differs from img->width if img contains sideLabel)
+//    int  height;            // in pixels (differs from img->height if img contains centerLabel and/or multiple tracks)
+//    int  offsetX;           // offset from left (when img->width > slice->width)
+//    int  offsetY;           // offset from top (when img->height > slice->height)
+//    };
+struct imgSlice *sliceCreate(enum sliceType type,struct image *img,char *title,int width,int height,int offsetX,int offsetY)
+/* Creates of a slice which is a portion of an image.
+   A slice specific map map be added with sliceMapStart(),mapSetItemAdd() */
+{
+struct imgSlice *slice;
+AllocVar(slice);
+slice->map       = NULL; // This is the same as defaulting to slice->parentImg->map
+return sliceUpdate(slice,type,img,title,width,height,offsetX,offsetY);
+}
+struct imgSlice *sliceUpdate(struct imgSlice *slice,enum sliceType type,struct image *img,char *title,int width,int height,int offsetX,int offsetY)
+/* updates an already created slice */
+{
+slice->type      = type;
+if(img != NULL && slice->parentImg != img)
+    slice->parentImg = img;
+if(title != NULL && differentStringNullOk(title,img->title))
+    {
+    if(img->title != NULL)
+        freeMem(img->title);
+    img->title   = cloneString(title);
+    }
+slice->width     = width;
+slice->height    = height;
+slice->offsetX   = offsetX;
+slice->offsetY   = offsetY;
+return slice;
+}
+char *sliceTypeToString(enum sliceType type)
+/* Translate enum slice type to string */
+{
+switch(type)
+    {
+    case isData:   return "data";
+    case isSide:   return "side";
+    case isCenter: return "center";
+    default:       return "unknown";
+    }
+}
+struct mapSet *sliceMapStart(struct imgSlice *slice,char *name,char *linkRoot)
+/* Adds a slice specific map to a slice of an image. Map items can then be added to the returned pointer with mapSetItemAdd()*/
+{
+if(slice->parentImg == NULL)
+    {
+    warn("sliceAddMap() but slice has no image.");
+    return NULL;
+    }
+if(slice->map != NULL && slice->map != slice->parentImg->map)
+    {
+    warn("sliceAddMap() but slice already has its own map. Being replaced.");
+    mapSetFree(&(slice->map));
+    }
+char qualifiedName[128];
+safef(qualifiedName,sizeof(qualifiedName),"%s_%s",sliceTypeToString(slice->type),name);
+slice->map = mapSetStart(qualifiedName,slice->parentImg,linkRoot);
+return slice->map;
+}
+struct mapSet *sliceGetMap(struct imgSlice *slice,boolean sliceSpecific)
+/* Gets the map associate with a slice which male be sliceSpecific or it map belong to the slices' image.
+   Map items can then be added to the map returned with mapSetItemAdd() */
+{
+if(!sliceSpecific && slice->map == NULL && slice->parentImg != NULL)
+        return slice->parentImg->map;
+return slice->map;
+}
+struct mapSet *sliceMapFindOrStart(struct imgSlice *slice,char *name,char *linkRoot)
+/* Finds the slice specific map or starts it */
+{
+struct mapSet *map = sliceGetMap(slice,TRUE); // Must be specific to this slice
+if (map == NULL)
+    map = sliceMapStart(slice,name,linkRoot);
+return map;
+}
+struct mapSet *sliceMapUpdateOrStart(struct imgSlice *slice,char *name,char *linkRoot)
+/* Updates the slice specific map or starts it */
+{
+struct mapSet *map = sliceGetMap(slice,TRUE); // Must be specific to this slice
+if (map == NULL)
+    return sliceMapStart(slice,name,linkRoot);
+char qualifiedName[128];
+safef(qualifiedName,sizeof(qualifiedName),"%s_%s",sliceTypeToString(slice->type),name);
+return mapSetUpdate(map,qualifiedName,slice->parentImg,linkRoot);
+}
+/* Adds a slice specific map to a slice of an image. Map items can then be added to the returned pointer with mapSetItemAdd()*/
+boolean sliceIsConsistent(struct imgSlice *slice,boolean verbose)
+/* Test whether the slice and it's associated image and map are consistent with each other */
+{
+if (slice == NULL)
+    {
+    if (verbose)
+        warn("slice is NULL");
+    return FALSE;
+    }
+if (slice->parentImg == NULL)
+    {
+    if (verbose)
+        warn("slice(%s) has no image",sliceTypeToString(slice->type));
+    return FALSE;
+    }
+if (slice->width == 0  || slice->width  > slice->parentImg->width)
+    {
+    if (verbose)
+        warn("slice(%s) has an invalid width %d (image width %d)",
+             sliceTypeToString(slice->type),slice->width,slice->parentImg->width);
+    return FALSE;
+    }
+if (slice->height == 0 || slice->height > slice->parentImg->height)
+    {
+    if (verbose)
+        warn("slice(%s) has an invalid height %d (image height %d)",
+             sliceTypeToString(slice->type),slice->height,slice->parentImg->height);
+    return FALSE;
+    }
+if (slice->offsetX >= slice->parentImg->width
+||  slice->offsetY >= slice->parentImg->height)
+    {
+    if (verbose)
+        warn("slice(%s) has an invalid X:%d or Y:%d offset (image width:%d height:%d)",
+             sliceTypeToString(slice->type),slice->offsetX,slice->offsetY,
+             slice->parentImg->width,slice->parentImg->height);
+    return FALSE;
+    }
+if (slice->map != NULL)
+    {
+    if(!mapSetIsComplete(slice->map,verbose))
+        {
+        warn("slice(%s) has bad map",sliceTypeToString(slice->type));
+        return FALSE;
+        }
+
+    struct mapItem *item = slice->map->items;
+    for (;item != NULL; item = item->next)
+        {
+        if(!mapItemConsistentWithImage(item,slice->parentImg,verbose))
+            {
+            warn("slice(%s) map is inconsistent with slice image",sliceTypeToString(slice->type));
+            return FALSE;
+            }
+        }
+    }
+return TRUE;
+}
+void sliceFree(struct imgSlice **pSlice)
+/* frees all memory assocated with a slice (not including the image or a map belonging to the image) */
+{
+if(pSlice != NULL && *pSlice != NULL)
+    {
+    struct imgSlice *slice = *pSlice;
+    // Don't free parentImg: remember that a slice is a portion of an image
+    struct mapSet *map = sliceGetMap(slice,TRUE);// Only one that belongs to slice, not image
+    if(map != NULL)
+        mapSetFree(&map);
+    freeMem(slice->title);
+    freeMem(slice);
+    *pSlice = NULL;
+    }
+}
+
+//////// imgTracks
+//struct imgTrack // IMAGEv2: imageBox conatins list of displayed imageTracks
+//    {
+//    struct imgTrack *next;    // slList
+//    struct trackDb *tdb;	    // trackDb entry (should this be struct track* entry?)
+//    char *name;	            // It is possible to have an imgTrack without a tdb, but then it mist have a name
+//    char *db;                 // Image for db (species) (assert imgTrack matches imgBox)
+//    char *chrom;              // Image for chrom (assert imgTrack matches imgBox)
+//    int  chromStart;          // Image start (absolute, not portal position)
+//    int  chromEnd;            // Image end (absolute, not portal position)
+//    boolean plusStrand;       // Image covers plus strand, not minus strand
+//    boolean showCenterLabel;  // Initially display center label? TODO: Isn't this redundent with vis?
+//    enum trackVisibility vis; // Current visibility of track image
+//    struct imgSlice *slices;  // Currently there should be three slices for every track: data, centerLabel, sideLabel
+//    };
+struct imgTrack *imgTrackStart(struct trackDb *tdb,char *name,char *db,char *chrom,int chromStart,int chromEnd,boolean plusStrand,boolean showCenterLabel,enum trackVisibility vis,boolean reorderable)
+/* Starts an image track which will contain all image slices needed to render one track
+   Must completed by adding slices with imgTrackAddSlice() */
+{
+struct imgTrack *imgTrack;     //  gifTn.forHtml, pixWidth, mapName
+AllocVar(imgTrack);
+return imgTrackUpdate(imgTrack,tdb,name,db,chrom,chromStart,chromEnd,plusStrand,showCenterLabel,vis,reorderable);
+}
+struct imgTrack *imgTrackUpdate(struct imgTrack *imgTrack,struct trackDb *tdb,char *name,char *db,char *chrom,int chromStart,int chromEnd,boolean plusStrand,boolean showCenterLabel,enum trackVisibility vis,boolean reorderable)
+/* Updates an already existing image track */
+{
+if(tdb != NULL && tdb != imgTrack->tdb)
+    imgTrack->tdb    = tdb;
+if(name != NULL && differentStringNullOk(imgTrack->name,name))
+    {
+    if(imgTrack->name != NULL)
+        freeMem(imgTrack->name);
+    imgTrack->name   = cloneString(name);
+    }
+if(db != NULL && db != imgTrack->db)
+    imgTrack->db     = db;        // NOTE: Not allocated
+if(chrom != NULL && chrom != imgTrack->chrom)
+    imgTrack->chrom  = chrom;     // NOTE: Not allocated
+imgTrack->chromStart = chromStart;
+imgTrack->chromEnd   = chromEnd;
+imgTrack->plusStrand = plusStrand;
+imgTrack->showCenterLabel = showCenterLabel;
+imgTrack->vis             = vis;
+imgTrack->reorderable     = reorderable;
+return imgTrack;
+}
+struct imgSlice *imgTrackSliceAdd(struct imgTrack *imgTrack,enum sliceType type, struct image *img,char *title,int width,int height,int offsetX,int offsetY)
+/* Adds slices to an image track.  Expected are types: isData, isSide and isCenter */
+{
+struct imgSlice *slice = sliceCreate(type,img,title,width,height,offsetX,offsetY);
+slAddHead(&(imgTrack->slices),slice);
+return imgTrack->slices;
+}
+struct imgSlice *imgTrackSliceGetByType(struct imgTrack *imgTrack,enum sliceType type)
+/* Gets a specific slice already added to an image track.  Expected are types: isData, isSide and isCenter */
+{
+struct imgSlice *slice;
+for(slice = imgTrack->slices;slice != NULL;slice=slice->next)
+    {
+    if(slice->type == type)
+        return slice;
+    }
+return NULL;
+}
+struct imgSlice *imgTrackSliceFindOrAdd(struct imgTrack *imgTrack,enum sliceType type, struct image *img,char *title,int width,int height,int offsetX,int offsetY)
+/* Find the slice or adds it */
+{
+struct imgSlice *slice = imgTrackSliceGetByType(imgTrack,type);
+if (slice == NULL)
+    slice = imgTrackSliceAdd(imgTrack,type,img,title,width,height,offsetX,offsetY);
+return slice;
+}
+struct imgSlice *imgTrackSliceUpdateOrAdd(struct imgTrack *imgTrack,enum sliceType type, struct image *img,char *title,int width,int height,int offsetX,int offsetY)
+/* Updates the slice or adds it */
+{
+struct imgSlice *slice = imgTrackSliceGetByType(imgTrack,type);
+if (slice == NULL)
+    return imgTrackSliceAdd(imgTrack,type,img,title,width,height,offsetX,offsetY);
+return sliceUpdate(slice,type,img,title,width,height,offsetX,offsetY);
+}
+
+struct mapSet *imgTrackGetMapByType(struct imgTrack *imgTrack,enum sliceType type)
+/* Gets the map assocated with a specific slice belonging to the imgTrack */
+{
+struct imgSlice *slice = imgTrackSliceGetByType(imgTrack,type);
+if(slice == NULL)
+    return NULL;
+return sliceGetMap(slice,FALSE); // Map could belong to image or could be slice specific
+}
+
+boolean imgTrackIsComplete(struct imgTrack *imgTrack,boolean verbose)
+/* Tests the completeness and consistency of this imgTrack (including slices) */
+{
+if (imgTrack == NULL)
+    {
+    if (verbose)
+        warn("imgTrack is NULL");
+    return FALSE;
+    }
+if (imgTrack->tdb == NULL && imgTrack->name == NULL)
+    {
+    if (verbose)
+        warn("imgTrack has no tdb or name");
+    return FALSE;
+    }
+char * name = (imgTrack->name != NULL ? imgTrack->name : imgTrack->tdb->tableName);
+if (imgTrack->db == NULL)
+    {
+    if (verbose)
+        warn("imgTrack(%s) has no db.",name);
+    return FALSE;
+    }
+if (imgTrack->db == NULL)
+    {
+    if (verbose)
+        warn("imgTrack(%s) has no chrom.",name);
+    return FALSE;
+    }
+if (imgTrack->chromStart  >= imgTrack->chromEnd)
+    {
+    if (verbose)
+        warn("imgTrack(%s) for %s.%s:%d-%d has bad genome range.",name,
+             imgTrack->db,imgTrack->chrom,imgTrack->chromStart,imgTrack->chromEnd);
+    return FALSE;
+    }
+if(imgTrack->slices == NULL)
+    {
+    if (verbose)
+        warn("imgTrack(%s) has no slices.",name);
+    return FALSE;
+    }
+// Can have no more than one of each type of slice
+boolean found[isMaxSliceTypes] = { FALSE,FALSE,FALSE,FALSE};
+struct imgSlice *slice = imgTrack->slices;
+for(; slice != NULL; slice = slice->next )
+    {
+    if(found[slice->type])
+        {
+        if (verbose)
+            warn("imgTrack(%s) found more than one slice of type %s.",
+                 name,sliceTypeToString(slice->type));
+        return FALSE;
+        }
+    found[slice->type] = TRUE;
+
+    if(!sliceIsConsistent(slice,verbose))
+        {
+        if (verbose)
+            warn("imgTrack(%s) has bad slice",name);
+        return FALSE;
+        }
+    }
+if(!found[isData])
+    {
+    if (verbose)
+        warn("imgTrack(%s) has no DATA slice.",name);
+    return FALSE;
+    }
+
+return TRUE;
+}
+void imgTrackFree(struct imgTrack **pImgTrack)
+/* frees all memory assocated with an imgTrack (including slices) */
+{
+if(pImgTrack != NULL && *pImgTrack != NULL)
+    {
+    struct imgTrack *imgTrack = *pImgTrack;
+    struct imgSlice *slice;
+    while((slice = slPopHead(&(imgTrack->slices))) != NULL )
+        sliceFree(&slice);
+    freeMem(imgTrack->name);
+    freeMem(imgTrack);
+    *pImgTrack = NULL;
+    }
+}
+
+
+//////// Image Box
+//struct imgBox // IMAGEv2: imageBox conatins all the definitions to draw an image in hgTracks
+//    {
+//    char *db;                 // database (species)
+//    char *chrom;              // chrom
+//    int  chromStart;          // imageBox displays this position (though images may cover more)
+//    int  chromEnd;            // imageBox displays this position (though images may cover more)
+//    boolean plusStrand;       // imgBox currently shows plus strand, not minus strand
+//    boolean showSideLabel;    // Initially display side label? (use 'plusStrand' for left/right)
+//    struct image *images;     // Contains all images for the imgBox. TEMPORARY: hgTracks creates it's current one image and I'll store it here
+//    struct image *bgImg;      // When track images are transparent, bgImage contains blue lines that are db coordinate granularity.
+//    int  portalStart;         // initial visible portal within html image table (db coodinates) [May be obsoleted by js client]
+//    int  portalEnd;           // initial visible portal within html image table (db coodinates) [May be obsoleted by js client]
+//    int  portalWidth;         // in pixels (note that width in visible position within image position (db coodinates)
+//    // TODO: I am certain there are more details needed
+//    struct imgTrack *imgTracks; // slList of all images to display
+//    };
+struct imgBox *imgBoxStart(char *db,char *chrom,int chromStart,int chromEnd,boolean plusStrand,boolean showSideLabel,int portalWidth)
+/* Starts an imgBox which should contain all info needed to draw the hgTracks image with multiple tracks
+   The image box must be completed using imgBoxImageAdd() and imgBoxTrackAdd() */
+{
+struct imgBox * imgBox;     //  gifTn.forHtml, pixWidth, mapName
+AllocVar(imgBox);
+if(db != NULL)
+    imgBox->db        = cloneString(db);     // NOTE: Is allocated
+if(chrom != NULL)
+    imgBox->chrom     = cloneString(chrom);  // NOTE: Is allocated
+imgBox->chromStart    = chromStart;
+imgBox->chromEnd      = chromEnd;
+imgBox->plusStrand    = plusStrand;
+imgBox->showSideLabel = showSideLabel;
+imgBox->images        = NULL;
+imgBox->bgImg         = NULL;
+int oneThird = (chromEnd - chromStart)/3; // TODO: Currently defaulting to 1/3 of image width
+imgBox->portalStart   = chromStart + oneThird;
+imgBox->portalEnd     = chromEnd - oneThird;
+imgBox->portalWidth   = portalWidth;
+// images are added with imgBoxImageAdd()
+// imgTracks are added with imgBoxTrackAdd()
+// Plans for slices and maps:  Fill in imgTracks with slices as the map is created.
+return imgBox;
+}
+
+struct image *imgBoxImageAdd(struct imgBox *imgBox,char *gif,char *title,int width,int height,boolean backGround)
+/* Adds an image to an imgBox.  The image may be extended with imgMapStart(),mapSetItemAdd() */
+{
+struct image *img = imgCreate(gif,title,width,height);
+if(backGround)
+    {
+    if(imgBox->bgImg != NULL)
+        {
+        warn("imgBoxImageAdd() for background but already exists. Being replaced.");
+        imgFree(&(imgBox->bgImg));
+        }
+        imgBox->bgImg = img;
+        return imgBox->bgImg;
+    }
+slAddHead(&(imgBox->images),img);
+return imgBox->images;
+}
+struct image *imgBoxImageFind(struct imgBox *imgBox,char *gif)
+/* Finds a specific image already added to this imgBox */
+{
+struct image *img = NULL;
+for (img = imgBox->images; img != NULL; img = img->next )
+    {
+    if (sameOk(img->file,gif))
+        return img;
+    }
+return NULL;
+}
+//boolean imgBoxImageRemove(struct imgBox *imgBox,struct image *img)
+//{
+//return slRemoveEl(&(imgBox->images),img);
+//}
+
+struct imgTrack *imgBoxTrackAdd(struct imgBox *imgBox,struct trackDb *tdb,char *name,enum trackVisibility vis,boolean showCenterLabel,boolean reorderable)
+/* Adds an imgTrack to an imgBox.  The imgTrack needs to be extended with imgTrackAddSlice() */
+{
+struct imgTrack *imgTrack = imgTrackStart(tdb,name,imgBox->db,imgBox->chrom,imgBox->chromStart,imgBox->chromEnd,imgBox->plusStrand,showCenterLabel,vis,reorderable);
+slAddHead(&(imgBox->imgTracks),imgTrack);
+return imgBox->imgTracks;
+}
+struct imgTrack *imgBoxTrackFind(struct imgBox *imgBox,struct trackDb *tdb,char *name)
+/* Finds a specific imgTrack already added to this imgBox */
+{
+struct imgTrack *imgTrack = NULL;
+for (imgTrack = imgBox->imgTracks; imgTrack != NULL; imgTrack = imgTrack->next )
+    {
+    if (name != NULL && sameOk(name,imgTrack->name))
+        return imgTrack;
+    else if (imgTrack->tdb == tdb)
+        return imgTrack;
+    }
+return NULL;
+}
+struct imgTrack *imgBoxTrackFindOrAdd(struct imgBox *imgBox,struct trackDb *tdb,char *name,enum trackVisibility vis,boolean showCenterLabel,boolean reorderable)
+/* Find the imgTrack, or adds it if not found */
+{
+struct imgTrack *imgTrack = imgBoxTrackFind(imgBox,tdb,name);
+if( imgTrack == NULL)
+    imgTrack = imgBoxTrackAdd(imgBox,tdb,name,vis,showCenterLabel,reorderable);
+return imgTrack;
+}
+struct imgTrack *imgBoxTrackUpdateOrAdd(struct imgBox *imgBox,struct trackDb *tdb,char *name,enum trackVisibility vis,boolean showCenterLabel,boolean reorderable)
+/* Updates the imgTrack, or adds it if not found */
+{
+struct imgTrack *imgTrack = imgBoxTrackFind(imgBox,tdb,name);
+if( imgTrack == NULL)
+    return imgBoxTrackAdd(imgBox,tdb,name,vis,showCenterLabel,reorderable);
+
+return imgTrackUpdate(imgTrack,tdb,name,imgBox->db,imgBox->chrom,imgBox->chromStart,imgBox->chromEnd,imgBox->plusStrand,showCenterLabel,vis,reorderable);
+}
+//boolean imgBoxTrackRemove(struct imgBox *imgBox,struct imgTrack *imgTrack)
+//{
+//return slRemoveEl(&(imgBox->imgTracks),imgTrack);
+//}
+
+boolean imgBoxIsComplete(struct imgBox *imgBox,boolean verbose)
+/* Tests the completeness and consistency of an imgBox. */
+{
+if (imgBox == NULL)
+    {
+    if (verbose)
+        warn("No imgBox.");
+    return FALSE;
+    }
+if (imgBox->db == NULL)
+    {
+    if (verbose)
+        warn("imgBox has no db.");
+    return FALSE;
+    }
+if (imgBox->db == NULL)
+    {
+    if (verbose)
+        warn("imgBox has no chrom.");
+    return FALSE;
+    }
+if (imgBox->chromStart  >= imgBox->chromEnd)
+    {
+    if (verbose)
+        warn("imgBox(%s.%s:%d-%d) has bad genome range.",imgBox->db,imgBox->chrom,imgBox->chromStart,imgBox->chromEnd);
+    return FALSE;
+    }
+if (imgBox->portalStart >= imgBox->portalEnd
+||  imgBox->portalStart <  imgBox->chromStart
+||  imgBox->portalEnd   >  imgBox->chromEnd  )
+    {
+    if (verbose)
+        warn("imgBox(%s.%s:%d-%d) has bad portal range: %d-%d",imgBox->db,imgBox->chrom,imgBox->chromStart,imgBox->chromEnd,imgBox->portalStart,imgBox->portalEnd);
+    return FALSE;
+    }
+
+// Must have images
+if (imgBox->images == NULL)
+    {
+    if (verbose)
+        warn("imgBox(%s.%s:%d-%d) has no images",imgBox->db,imgBox->chrom,imgBox->chromStart,imgBox->chromEnd);
+    return FALSE;
+    }
+// Must have tracks
+if (imgBox->imgTracks == NULL)
+    {
+    if (verbose)
+        warn("imgBox(%s.%s:%d-%d) has no imgTracks",imgBox->db,imgBox->chrom,imgBox->chromStart,imgBox->chromEnd);
+    return FALSE;
+    }
+struct imgTrack *imgTrack = NULL;
+for (imgTrack = imgBox->imgTracks; imgTrack != NULL; imgTrack = imgTrack->next )
+    {
+    if(!imgTrackIsComplete(imgTrack,verbose))
+        {
+        if (verbose)
+            warn("imgBox(%s.%s:%d-%d) has bad track",imgBox->db,imgBox->chrom,imgBox->chromStart,imgBox->chromEnd);
+        return FALSE;
+        }
+    if(differentWord(imgTrack->db,           imgBox->db)
+    || differentWord(imgTrack->chrom,        imgBox->chrom)
+    ||               imgTrack->chromStart != imgBox->chromStart
+    ||               imgTrack->chromEnd   != imgBox->chromEnd
+    ||               imgTrack->plusStrand != imgBox->plusStrand)
+    {
+        if (verbose)
+            warn("imgBox(%s.%s:%d-%d) has inconsistent imgTrack for %s.%s:%d-%d",
+                  imgBox->db,  imgBox->chrom,  imgBox->chromStart,  imgBox->chromEnd,
+                imgTrack->db,imgTrack->chrom,imgTrack->chromStart,imgTrack->chromEnd);
+        return FALSE;
+    }
+    // Every track must have slices
+    if (imgTrack->slices == NULL)
+        {
+        if (verbose)
+            warn("imgBox(%s.%s:%d-%d) has no slices",
+                imgBox->db,imgBox->chrom,imgBox->chromStart,imgBox->chromEnd);
+        return FALSE;
+        }
+    struct imgSlice *slice = NULL;
+    for (slice = imgTrack->slices; slice != NULL; slice = slice->next )
+        {
+        if(!sliceIsConsistent(slice,verbose))
+            {
+            if (verbose)
+                warn("imgBox(%s.%s:%d-%d) has bad slice",imgBox->db,imgBox->chrom,imgBox->chromStart,imgBox->chromEnd);
+            return FALSE;
+            }
+        // Every slice must point to an image owned by the imgBox
+        if(slIxFromElement(imgBox->images,slice->parentImg) == -1)
+            {
+            if (verbose)
+                warn("imgBox(%s.%s:%d-%d) has slice(%s) for unknown image (%s)",
+                    imgBox->db,imgBox->chrom,imgBox->chromStart,imgBox->chromEnd,
+                    sliceTypeToString(slice->type),slice->parentImg->file);
+            return FALSE;
+            }
+        }
+    }
+return TRUE;
+}
+
+void imgBoxFree(struct imgBox **pImgBox)
+/* frees all memory assocated with an imgBox (including images and imgTracks) */
+{
+if(pImgBox != NULL && *pImgBox != NULL)
+    {
+    struct imgBox *imgBox = *pImgBox;
+    struct imgTrack *imgTrack = NULL;
+    while((imgTrack = slPopHead(&(imgBox->imgTracks))) != NULL )
+        imgTrackFree(&imgTrack);
+    struct image *img = NULL;
+    while((img = slPopHead(&(imgBox->images))) != NULL )
+        imgFree(&img);
+    imgFree(&(imgBox->bgImg));
+    freeMem(imgBox->db);
+    freeMem(imgBox->chrom);
+    freeMem(imgBox);
+    *pImgBox = NULL;
+    }
+}
+
+// Now the real work:
+void imageMapDraw(struct mapSet *map,char *name)
+/* writes an image map as HTML */
+{
+//warn("Drawing map_%s %s",name,(map == NULL?"map is NULL":map->items == NULL?"map->items is NULL":"Should draw!"));
+if(map == NULL || map->items == NULL)
+    return;
+
+hPrintf("<MAP name='map_%s'>\n", name); // map_ prefix is implicit
+struct mapItem *item = map->items;
+for(;item!=NULL;item=item->next)
+    {
+    hPrintf("<AREA SHAPE=RECT COORDS='%d,%d,%d,%d'",
+           item->topLeftX, item->topLeftY, item->bottomRightX, item->bottomRightY);
+    // TODO: remove static portion of the link and handle in js
+    if(map->linkRoot != NULL)
+        hPrintf(" HREF='%s%s'",map->linkRoot,(item->linkVar != NULL?item->linkVar:""));
+    else if(item->linkVar != NULL)
+        hPrintf(" HREF='%s'",item->linkVar);
+    else
+        warn("map item has no url!");
+
+    if(item->title != NULL)
+        hPrintf(" TITLE='%s'", item->title );
+    hPrintf(">\n" );
+    }
+hPrintf("</MAP>\n");
+}
+void sliceAndMapDraw(struct imgSlice *slice,char *name)
+/* writes a slice of an image and any assocated image map as HTML */
+{
+hPrintf("<div style='width:%dpx; height:%dpx; overflow:hidden;'",slice->width,slice->height);
+if(slice->type==isData)
+    hPrintf(" class='panDiv'");
+hPrintf(">\n");
+
+struct mapSet *map = sliceGetMap(slice,FALSE); // Could be the image map or slice specific
+if(map)
+    imageMapDraw(map,name);
+
+hPrintf("<IMG id='img_%s' src='%s' style='position:relative; left:-%dpx; top: -%dpx; border:0;'",
+        name,slice->parentImg->file,slice->offsetX,slice->offsetY);
+if(map && map->items)
+    hPrintf(" usemap='#map_%s'",name);
+if(slice->type==isSide)
+    hPrintf(" class='sideLab'");
+else if(slice->type==isCenter)
+    hPrintf(" class='centerLab'");
+else if(slice->type==isData)
+    hPrintf(" class='panImg'");
+//hPrintf(" title='[%s] width:%d  height: %d  offsetX: %d  offsetY: %d'",
+//        sliceTypeToString(slice->type),slice->width,slice->height,slice->offsetX,slice->offsetY);
+if(slice->title != NULL)
+    hPrintf(" title='%s'",slice->title);           // Adds slice wide title
+else if(slice->parentImg->title != NULL)
+    hPrintf(" title='%s'",slice->parentImg->title);// Adds image wide title
+hPrintf("></div>");
+}
+void imageBoxDraw(struct imgBox *imgBox)
+/* writes a entire imgBox including all tracksas HTML */
+{
+if(!imgBoxIsComplete(imgBox,TRUE))
+    return;
+char name[128];
+
+// TODO: Add in sorting
+if(differentStringNullOk(imgBox->imgTracks->name,RULER_TRACK_NAME))
+    slReverse(&(imgBox->imgTracks));
+
+hPrintf("<!--------------- IMAGEv2 ---------------->\n");
+//commonCssStyles();
+jsIncludeFile("jquery.tablednd.js", NULL);
+// TODO: jsIncludeFile("dragScroll.js", NULL);
+hPrintf("<style type='text/css'>\n");
+hPrintf(".trDrag {opacity:0.4; padding:1px; background-color:red;}\n");// outline:red solid thin;}\n"); // opacity for FF, padding/bg for IE
+hPrintf(".dragHandle {cursor: s-resize;}\n");
+hPrintf("div.dragZoom       { cursor: text; }\n");
+//hPrintf("div.panDiv         { width: %dpx; overflow: hidden; }\n",imgBox->portalWidth);
+//hPrintf("div.panDivScroller { width: %dpx; overflow: hidden; }\n",imgBox->portalWidth);
+//hPrintf("img.panImg    { position: relative; left: -%dpx; top: 0px; border: 0; }\n",imgBox->portalWidth);
+//hPrintf("img.centerLab { position: relative; left: -%dpx; top: 0px; border: 0; }\n",imgBox->portalWidth);
+//hPrintf("img.sideLab { border: 0; }\n");
+hPrintf("</style>\n");
+
+hPrintf("<TABLE id='imgTbl' border=0 cellspacing=0 cellpadding=0");
+hPrintf(" width=%d",imgBox->portalWidth);
+hPrintf(" class='tableWithDragAndDrop' style='border:1px solid blue;border-collapse:separate'");
+hPrintf(">\n");
+
+struct imgTrack *imgTrack = imgBox->imgTracks;
+for(;imgTrack!=NULL;imgTrack=imgTrack->next)
+    {
+    char *trackName = (imgTrack->name != NULL ? imgTrack->name : imgTrack->tdb->tableName );
+    hPrintf("<TR id='tr_%s'%s>\n",trackName,
+        (imgTrack->reorderable?" class='trDraggable'":" class='nodrop nodrag'"));
+    // leftLabel
+    if(imgBox->showSideLabel && imgBox->plusStrand)
+        {
+        safef(name,sizeof(name),"left_%s",trackName);
+        hPrintf("<TD id='td_%s'%s>\n",name,
+            (imgTrack->reorderable?" class='dragHandle' title='Drag to reorder'":""));
+        sliceAndMapDraw(imgTrackSliceGetByType(imgTrack,isSide), name);
+        hPrintf("</TD>");
+        }
+    // Main/Data image region
+    hPrintf("<TD id='td_data_%s' width=%d>\n", trackName, imgBox->portalWidth);
+    // centerLabel
+    if(imgTrack->showCenterLabel)
+        {
+        safef(name, sizeof(name), "center_%s", trackName);
+        sliceAndMapDraw(imgTrackSliceGetByType(imgTrack,isCenter), name);
+        //hPrintf("<BR>\n");
+        }
+    // data image
+    safef(name, sizeof(name), "data_%s", trackName);
+    sliceAndMapDraw(imgTrackSliceGetByType(imgTrack,isData), name);
+    hPrintf("</TD>");
+
+    // rightLabel
+    if(imgBox->showSideLabel && !imgTrack->plusStrand)
+        {
+        safef(name, sizeof(name), "right_%s", trackName);
+        hPrintf("<TD id='td_%s'>\n", name);
+        sliceAndMapDraw(imgTrackSliceGetByType(imgTrack,isSide), name);
+        hPrintf("</TD>");
+        }
+    hPrintf("</TR>");
+    }
+hPrintf("</TABLE>\n");
+hPrintf("<!--------------- IMAGEv2 ---------------->\n");
+}
+#endif//def IMAGEv2_UI

