84440373a2ad67b39c36c614687807bda9a423b2
tdreszer
  Wed Sep 29 11:12:37 2010 -0700
Added definition of multiTrack and changed container to be either multiTrack or Composite.  This allows multiTracks (such as multiWigs) to behave as composites in some code paths.
diff --git src/hg/hgTracks/imageV2.c src/hg/hgTracks/imageV2.c
index 53ffb07..64471d2 100644
--- src/hg/hgTracks/imageV2.c
+++ src/hg/hgTracks/imageV2.c
@@ -1,1792 +1,1792 @@
 /* imageV2 - API for creating the image V2 features. */
 #include "common.h"
 #include "hPrint.h"
 #include "chromInfo.h"
 #include "hdb.h"
 #include "hui.h"
 #include "jsHelper.h"
 #include "cheapcgi.h"
 #include "htmshell.h"
 #include "imageV2.h"
 #include "hgTracks.h"
 #include "hgConfig.h"
 
 static char const rcsid[] = "$Id: imageV2.c,v 1.32 2010/05/24 19:53:42 hiram Exp $";
 
 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
 
 /////////////////////////
 // FLAT TRACKS
 // A simplistic way of flattening the track list before building the image
 // NOTE: Strategy is NOT to use imgBox->imgTracks, since this should be independednt of imageV2
 /////////////////////////
 void flatTracksAdd(struct flatTracks **flatTracks,struct track *track,struct cart *cart)
 // Adds one track into the flatTracks list
 {
 struct flatTracks *flatTrack;
 AllocVar(flatTrack);
 flatTrack->track = track;
 char var[256];  // The whole reason to do this is to reorder tracks/subtracks in the image!
 safef(var,sizeof(var),"%s_%s",track->tdb->track,IMG_ORDER_VAR);
 flatTrack->order = cartUsualInt(cart, var,IMG_ANYORDER);
 if(flatTrack->order >= IMG_ORDEREND)
     {
     cartRemove(cart,var);
     flatTrack->order = IMG_ANYORDER;
     }
 static int lastOrder = IMG_ORDEREND; // keep track of the order added and beyond end
 if( flatTrack->order == IMG_ANYORDER)
     flatTrack->order = ++lastOrder;
 
 slAddHead(flatTracks,flatTrack);
 }
 
 int flatTracksCmp(const void *va, const void *vb)
 // Compare to sort on flatTrack->order
 {
 const struct flatTracks *a = *((struct flatTracks **)va);
 const struct flatTracks *b = *((struct flatTracks **)vb);
 return (a->order - b->order);
 }
 
 void flatTracksSort(struct flatTracks **flatTracks)
 // This routine sorts the imgTracks then forces tight ordering, so new tracks wil go to the end
 {
 // flatTracks list has 2 sets of "order": those already dragReordered (below IMG_ORDEREND)
 // and those not yet reordered (above).  It has been decided that adding new tracks to an
 // existing order should always put the new tracks below existing and treat them as if they
 // were reordered there.  Thus all new tracks should get an imgOrd below IMG_ORDEREND.
 // The result is turning on a successive set of new tracks will have them appear below all others.
 int imgOrdSet = 0;
 boolean notYetOrdered = FALSE;
 struct flatTracks *oneTrack = *flatTracks;
 for(;oneTrack!=NULL;oneTrack = oneTrack->next)
     {
     if (oneTrack->order <= IMG_ORDEREND
     &&  imgOrdSet < oneTrack->order )
         imgOrdSet = oneTrack->order;
     else
         notYetOrdered = TRUE;
     }
 if (imgOrdSet > 0 && notYetOrdered) // Image order has previously been set, so givem all imgOrds
     {
     imgOrdSet = (IMG_ORDEREND - imgOrdSet);  // This difference should be removed from any with imgOrdSet
     for(oneTrack = *flatTracks;oneTrack!=NULL;oneTrack = oneTrack->next)
         {
         if (oneTrack->order >= imgOrdSet)
             {
             oneTrack->order -= imgOrdSet;
             char var[256];
             safef(var,sizeof(var),"%s_%s",oneTrack->track->track,IMG_ORDER_VAR);
             cartSetInt(cart, var, oneTrack->order);
             }
         }
     }
 
 if (flatTracks && *flatTracks)
     slSort(flatTracks, flatTracksCmp);
 }
 
 void flatTracksFree(struct flatTracks **flatTracks)
 // Frees all memory used to support flatTracks (underlying tracks are untouched)
 {
 if(flatTracks && *flatTracks)
     {
     struct flatTracks *flatTrack;
     while((flatTrack = slPopHead(flatTracks)) != NULL)
         freeMem(flatTrack);
     }
 }
 
 /////////////////////////
 // JSON support.  Eventually the whole imgTbl could be written out as JSON
 void jsonTdbSettingsBuild(struct dyString **jsonTdbSettingsString, struct track *track)
 // Creates then successively adds trackDb settings to the jsonTdbSettingsString
 // Initially pass in NULL pointer to a dyString to properly begin building
 {
 if (*jsonTdbSettingsString==NULL)
     {
     *jsonTdbSettingsString = newDyString(1024);
     dyStringPrintf(*jsonTdbSettingsString, "<script>var trackDbJson = {\nruler: {shortLabel: 'ruler', longLabel: 'Base Position Controls', canPack: 0, visibility: %d},\n", rulerMode);
     }
 else
     dyStringAppend(*jsonTdbSettingsString, ",\n");
 dyStringPrintf(*jsonTdbSettingsString, "\t%s: {", track->track);
 if (tdbIsSuperTrackChild(track->tdb))
     {
     dyStringPrintf(*jsonTdbSettingsString, "\n\t\tparentTrack: '%s',", track->tdb->parent->track);
     dyStringPrintf(*jsonTdbSettingsString, "\n\t\tparentLabel: '%s',", track->tdb->parent->shortLabel);
     }
-else if (tdbIsCompositeChild(track->tdb))
+else if (tdbIsContainerChild(track->tdb))
     {
-    struct trackDb *parentTdb = trackDbCompositeParent(track->tdb);
+    struct trackDb *parentTdb = tdbGetContainer(track->tdb);
     dyStringPrintf(*jsonTdbSettingsString, "\n\t\tparentTrack: '%s',", parentTdb->track);
     dyStringPrintf(*jsonTdbSettingsString, "\n\t\tparentLabel: '%s',", parentTdb->shortLabel);
     if (!track->canPack)
         {
         dyStringPrintf(*jsonTdbSettingsString, "\n\t\tshouldPack: 0,"); // default vis is full, but pack is an option
         track->canPack = parentTdb->canPack;
         }
     }
-dyStringPrintf(*jsonTdbSettingsString, "\n\t\tisSubtrack: %d,",tdbIsCompositeChild(track->tdb)?1:0);
+dyStringPrintf(*jsonTdbSettingsString, "\n\t\tisSubtrack: %d,",tdbIsContainerChild(track->tdb)?1:0);
 dyStringPrintf(*jsonTdbSettingsString, "\n\t\thasChildren: %d,", slCount(track->tdb->subtracks));
 dyStringPrintf(*jsonTdbSettingsString, "\n\t\ttype: '%s',", track->tdb->type);
 if (sameString(trackDbSettingClosestToHomeOrDefault(track->tdb, "configureByPopup", "on"), "off"))
     dyStringPrintf(*jsonTdbSettingsString, "\n\t\tconfigureByPopup: false,");
 if (sameWord(track->tdb->type, "remote") && trackDbSetting(track->tdb, "url") != NULL)
     dyStringPrintf(*jsonTdbSettingsString, "\n\t\turl: '%s',", trackDbSetting(track->tdb, "url"));
 dyStringPrintf(*jsonTdbSettingsString, "\n\t\tshortLabel: '%s',\n\t\tlongLabel: '%s',\n\t\tcanPack: %d,\n\t\tvisibility: %d\n\t}",
                javaScriptLiteralEncode(track->shortLabel), javaScriptLiteralEncode(track->longLabel), track->canPack, track->limitedVis);
 }
 
 char *jsonTdbSettingsUse(struct dyString **jsonTdbSettingsString)
 // Closes and returns the contents of the jsonTdbSettingsString
 {
 dyStringAppend(*jsonTdbSettingsString, "}\n</script>\n");
 return dyStringCannibalize(jsonTdbSettingsString);
 }
 
 /////////////////////////
 // IMAGEv2
 // The new way to do images: PLEASE REFER TO imageV2.h FOR A DETAILED DESCRIPTION
 /////////////////////////
 
 
 
 /////////////////////// Maps
 
 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 *mapSetItemFind(struct mapSet *map,int topLeftX,int topLeftY,int bottomRightX,int bottomRightY)
 /* Find a single mapItem based upon coordinates (within a pixel) */
 {
 struct mapItem *item;
 for(item=map->items;item!=NULL;item=item->next)
     {
     if((abs(item->topLeftX     - topLeftX)     < 2)
     && (abs(item->topLeftY     - topLeftY)     < 2)
     && (abs(item->bottomRightX - bottomRightX) < 2)
     && (abs(item->bottomRightY - bottomRightY) < 2)) // coordinates within a pixel is okay
         return item;
     }
 return NULL;
 }
 
 struct mapItem *mapSetItemUpdate(struct mapSet *map,struct mapItem *item,char *link,char *title,int topLeftX,int topLeftY,int bottomRightX,int bottomRightY,char *id)
 /* Update a single mapItem */
 {
 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;
 freeMem(item->id);
 item->id           = cloneString(id);
 return item;
 }
 
 struct mapItem *mapSetItemAdd(struct mapSet *map,char *link,char *title,int topLeftX,int topLeftY,int bottomRightX,int bottomRightY, char *id)
 /* Add 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;
 item->id           = cloneString(id);
 slAddHead(&(map->items),item);
 //warn("Added map(%s) item '%s' count:%d",map->name,title,slCount(map->items));
 return map->items;
 }
 
 struct mapItem *mapSetItemUpdateOrAdd(struct mapSet *map,char *link,char *title,int topLeftX,int topLeftY,int bottomRightX,int bottomRightY, char *id)
 /* Update or add a single mapItem */
 {
 struct mapItem *item = mapSetItemFind(map,topLeftX,topLeftY,bottomRightX,bottomRightY);
 if(item != NULL)
     return mapSetItemUpdate(map,item,link,title,topLeftX,topLeftY,bottomRightX,bottomRightY, id);
 else
     return mapSetItemAdd(map,link,title,topLeftX,topLeftY,bottomRightX,bottomRightY, id);
 }
 
 struct mapItem *mapSetItemFindOrAdd(struct mapSet *map,char *link,char *title,int topLeftX,int topLeftY,int bottomRightX,int bottomRightY, char *id)
 /* Finds or adds the map item */
 {
 struct mapItem *item = mapSetItemFind(map,topLeftX,topLeftY,bottomRightX,bottomRightY);
 if(item != NULL)
     return item;
 else
     return mapSetItemAdd(map,link,title,topLeftX,topLeftY,bottomRightX,bottomRightY,id);
 }
 
 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);
     if(item->id != NULL)
         freeMem(item->id);
     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;
 }
 
 static struct dyString *addIndent(struct dyString **dy,int indent)
 /* beginning indent for show functions */
 {
 struct dyString *myDy = (dy?*dy:NULL);
 if(dy == NULL || *dy == NULL)
     myDy = newDyString(256);
 else
     dyStringAppend(myDy,"<br>");
 dyStringAppend(myDy,"<code>");
 int times = indent;
 for(;times>0;times--)
     dyStringAppend(myDy,"&nbsp;");
 return myDy;
 }
 
 static void mapItemShow(struct dyString **dy,struct mapItem *item,int indent)
 /* show the map item */
 {
 if(item)
     {
     struct dyString *myDy = addIndent(dy,indent);
     dyStringPrintf(myDy,"mapItem: title:%s topX:%d topY:%d botX:%d botY:%d",
              (item->title?item->title:""),
             item->topLeftX,item->topLeftY,item->bottomRightX,item->bottomRightY);
     addIndent(&myDy,indent);
     dyStringPrintf(myDy,"&nbsp;&nbsp;linkVar:%s",
             (item->linkVar?item->linkVar:""));
     if(dy == NULL)
         warn("%s",dyStringCannibalize(&myDy));
     else
         *dy = myDy;
     }
 }
 
 
 static void mapShow(struct dyString **dy,struct mapSet *map,int indent)
 /* show the map */
 {
 if(map && map->items) // No map items then why bother?
     {
     struct dyString *myDy = addIndent(dy,indent);
     dyStringPrintf(myDy,"map: name:%s",(map->name?map->name:""));
     if(map->linkRoot)
         dyStringPrintf(myDy," linkRoot:%s",map->linkRoot);
     if(dy == NULL)
         warn("%s",dyStringCannibalize(&myDy));
 
     indent++;
     struct mapItem *item = map->items;
     for (;item != NULL; item = item->next)
         mapItemShow(dy,item,indent);
 
     if(dy != NULL)
         *dy = myDy;
     }
 }
 
 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;
 }
 
 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 *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;
 }
 
 static void imgShow(struct dyString **dy,struct image *img,char *prefix,int indent)
 /* show the img */
 {
 if(img)
     {
     struct dyString *myDy = addIndent(dy,indent);
     dyStringPrintf(myDy,"%simg: title:%s file:%s width:%d height:%d",(prefix?prefix:""),
             (img->title?img->title:""),(img->file?img->file:""),img->width,img->height);
     indent++;
     mapShow(&myDy,img->map,indent);
     if(dy == NULL)
         warn("%s",dyStringCannibalize(&myDy));
     else
         *dy = myDy;
     }
 }
 
 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 *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() */
 {
 if (height <= 0 || width <= 0)
     return NULL;
 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 */
 {
 //if(width==0 || height==0)
 //    return NULL;
 slice->type      = type;
 if(img != NULL && slice->parentImg != img)
     slice->parentImg = img;
 if(title != NULL && differentStringNullOk(title,slice->title))
     {
     if(slice->title != NULL)
         freeMem(slice->title);
     slice->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 stData:   return "data";
     case stSide:   return "side";
     case stCenter: return "center";
     case stButton: return "button";
     default:       return "unknown";
     }
 }
 
 static char *sliceTypeToClass(enum sliceType type)
 /* Translate enum slice type to the class */
 {
 switch (type)
     {
     case stSide:   return "sideLab";
     case stCenter: return "cntrLab";
     case stButton: return "button";
     case stData:   return "dataImg";
     default:       return "unknown";
     }
 }
 
 
 struct imgSlice *sliceAddLink(struct imgSlice *slice,char *link,char *title)
 /* Adds a slice wide link.  The link and map are mutually exclusive */
 {
 if(slice->map != NULL)
     {
     warn("sliceAddLink() but slice already has its own map. Being replaced.");
     mapSetFree(&(slice->map));
     }
 if(slice->link != NULL)
     {
     warn("sliceAddLink() but slice already has a link. Being replaced.");
     freeMem(slice->link);
     }
 slice->link = cloneString(link);
 if(slice->title != NULL)  // OK to replace title
     freeMem(slice->title);
 slice->title = cloneString(title);
 return slice;
 }
 
 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->link != NULL)
     {
     warn("sliceAddMap() but slice already has a link. Being replaced.");
     freeMem(slice->link);
     slice->link = 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[256];
 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 may 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 */
 {
 if(slice==NULL)
     return NULL;
 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[256];
 safef(qualifiedName,sizeof(qualifiedName),"%s_%s",sliceTypeToString(slice->type),name);
 return mapSetUpdate(map,qualifiedName,slice->parentImg,linkRoot);
 }
 
 static void sliceShow(struct dyString **dy,struct imgSlice *slice,int indent)
 /* show the slice */
 {
 if(slice)
     {
     struct dyString *myDy = addIndent(dy,indent);
     dyStringPrintf(myDy,"slice(%s): title:%s width:%d height:%d offsetX:%d offsetY:%d",
             sliceTypeToString(slice->type),(slice->title?slice->title:""),
             slice->width,slice->height,slice->offsetX,slice->offsetY);
     if(slice->link)
         {
         addIndent(&myDy,indent);
         dyStringPrintf(myDy,"&nbsp;&nbsp;link:%s",slice->link);
         }
     indent++;
     imgShow(&myDy,slice->parentImg,"parent ", indent); // Currently we just have the one image
     mapShow(&myDy,slice->map,indent);
     if(dy == NULL)
         warn("%s",dyStringCannibalize(&myDy));
     else
         *dy = myDy;
     }
 }
 
 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 && slice->type != stButton)
     {
     if (verbose)
         warn("slice(%s) has no image",sliceTypeToString(slice->type));
     return FALSE;
     }
 if ( slice->width == 0
 ||  (slice->parentImg && 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) // FIXME: This may be a temporary solution to empty data slices
     {
     if (verbose)
         warn("slice(%s) has an invalid height %d (image height %d)",
              sliceTypeToString(slice->type),slice->height,slice->parentImg->height);
     return FALSE;
     //return TRUE; // This may be valid (but is sloppy) when there is no data for the slice.
     }
 if (slice->parentImg && 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->parentImg
 &&  (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->link != NULL && slice->map != NULL)
     {
     warn("slice(%s) has both link and map of links",sliceTypeToString(slice->type));
     return FALSE;
     }
 if (slice->map != NULL)
     {
     //if(slice->map->items == NULL)
     //    mapSetFree(&slice->map); // An empty map is ok but should be removed.
     //else
     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->link);
     freeMem(slice);
     *pSlice = NULL;
     }
 }
 
 
 
 /////////////////////// imgTracks
 
 struct imgTrack *imgTrackStart(struct trackDb *tdb,char *name,char *db,char *chrom,int chromStart,int chromEnd,boolean plusStrand,boolean hasCenterLabel,enum trackVisibility vis,int order)
 /* 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);
 imgTrack->centerLabelSeen = clAlways;
 return imgTrackUpdate(imgTrack,tdb,name,db,chrom,chromStart,chromEnd,plusStrand,hasCenterLabel,vis,order);
 }
 
 struct imgTrack *imgTrackUpdate(struct imgTrack *imgTrack,struct trackDb *tdb,char *name,char *db,char *chrom,int chromStart,int chromEnd,boolean plusStrand,boolean hasCenterLabel,enum trackVisibility vis,int order)
 /* 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->hasCenterLabel = hasCenterLabel;
 imgTrack->vis             = vis;
 static int lastOrder = IMG_ORDEREND; // keep track of the order these images get added
 if(order == IMG_FIXEDPOS)
     {
     imgTrack->reorderable = FALSE;
     if(name != NULL && sameString(RULER_TRACK_NAME,name))
         imgTrack->order = 0;
     else
         imgTrack->order = 9999;
     }
 else
     {
     imgTrack->reorderable = TRUE;
     if(order == IMG_ANYORDER)
         {
         if(imgTrack->order <= 0)
             imgTrack->order = ++lastOrder;
         }
     else if(imgTrack->order != order)
         imgTrack->order = order;
     }
 return imgTrack;
 }
 
 int imgTrackOrderCmp(const void *va, const void *vb)
 /* Compare to sort on imgTrack->order */
 {
 const struct imgTrack *a = *((struct imgTrack **)va);
 const struct imgTrack *b = *((struct imgTrack **)vb);
 return (a->order - b->order);
 }
 
 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: stData, stButton, stSide and stCenter */
 {
 struct imgSlice *slice = sliceCreate(type,img,title,width,height,offsetX,offsetY);
 if(slice)
     slAddHead(&(imgTrack->slices),slice);
 return imgTrack->slices;
 //slAddTail(&(imgTrack->slices),slice);
 //return slice;
 }
 
 struct imgSlice *imgTrackSliceGetByType(struct imgTrack *imgTrack,enum sliceType type)
 /* Gets a specific slice already added to an image track.  Expected are types: stData, stButton, stSide and stCenter */
 {
 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
 }
 
 int imgTrackAddMapItem(struct imgTrack *imgTrack,char *link,char *title,int topLeftX,int topLeftY,int bottomRightX,int bottomRightY, char *id)
 /* Will add a map item to an imgTrack's appropriate slice's map.  Since a map item may span
  * slices, the imgTrack is in the best position to determine where to put the map item
  * returns count of map items added, which could be 0, 1 or more than one if item spans slices
  * NOTE: Precedence is given to first map item when adding items with same coordinates! */
 {
 struct imgSlice *slice;
 char *imgFile = NULL;               // name of file that hold the image
 
 int count = 0;
 for(slice = imgTrack->slices;slice != NULL;slice=slice->next)
     {
     if(slice->type == stButton) // Buttons don't have maps.  Overlap will be ignored!
         continue;
     if(slice->parentImg != NULL)
         {
         if(imgFile == NULL)
             imgFile = slice->parentImg->file;
         //else if(differentString(imgFile,slice->parentImg->file))
         //    {
         //    char * name = (imgTrack->name != NULL ? imgTrack->name : imgTrack->tdb != NULL ? imgTrack->tdb->track : imgFile);
         //    warn("imgTrackAddMapItem(%s) called, but not all slice images are the same for this track.",name);
         //    }
         // Not a valid warning!  Side image and data image may be different!!!
         }
     if(topLeftX     < (slice->offsetX + slice->width-1)
     && bottomRightX > (slice->offsetX + 1)
     && topLeftY     < (slice->offsetY + slice->height-1)
     && bottomRightY > (slice->offsetY + 1)) // Overlap of a pixel or 2 is tolerated
         {
         struct mapSet *map = sliceGetMap(slice,FALSE);
         if(map!=NULL)
             {          // NOTE: using find or add gives precedence to first of same coordinate map items added
             mapSetItemFindOrAdd(map,link,title,max(topLeftX,slice->offsetX),max(topLeftY,slice->offsetY),min(bottomRightX,slice->offsetX + slice->width),min(bottomRightY,slice->offsetY + slice->height), id);
             count++;
             }
         else
         {  // FIXME: This is assuming that if there is no map then the entire slice should get the link!
             char * name = (imgTrack->name != NULL ? imgTrack->name : imgTrack->tdb != NULL ? imgTrack->tdb->track : imgFile);
             warn("imgTrackAddMapItem(%s,%s) mapItem(lx:%d,rx:%d) is overlapping slice:%s(lx:%d,rx:%d)",name,title,topLeftX,bottomRightX,
                  sliceTypeToString(slice->type),slice->offsetX,(slice->offsetX + slice->width - 1));
             sliceAddLink(slice,link,title);
             count++;
             }
         }
     }
 //if(count>=2)
 //    {
 //    char * name = (imgTrack->name != NULL ? imgTrack->name : imgTrack->tdb != NULL ? imgTrack->tdb->track : imgFile);
 //    warn("imgTrackAddMapItem(%s) called for map items stretching across %d slice(s).",name,count);
 //    }
 return count;
 }
 
 static char *centerLabelSeenToString(enum centerLabelSeen seen)
 /* Translate enum slice type to string */
 {
 switch(seen)
     {
     case clAlways: return "always";
     case clNowSeen:return "now";
     case clNotSeen:return "notNow";
     default:       return "unknown";
     }
 }
 
 static void imgTrackShow(struct dyString **dy,struct imgTrack *imgTrack,int indent)
 /* show the imgTrack */
 {
 if(imgTrack)
     {
     struct dyString *myDy = addIndent(dy,indent);
     dyStringPrintf(myDy,"imgTrack: name:%s tdb:%s",
             (imgTrack->name?imgTrack->name:""),(imgTrack->tdb && imgTrack->tdb->track?imgTrack->tdb->track:""));
     if(imgTrack->hasCenterLabel)
         dyStringPrintf(myDy," centerLabel:%s",centerLabelSeenToString(imgTrack->centerLabelSeen));
     if(imgTrack->reorderable)
         dyStringPrintf(myDy," reorderable");
     dyStringPrintf(myDy," order:%d vis:%s",imgTrack->order,hStringFromTv(imgTrack->vis));
     if(dy == NULL)
         warn("%s",dyStringCannibalize(&myDy));
 
     indent++;
     struct imgSlice *slice = imgTrack->slices;
     for(; slice != NULL; slice = slice->next )
         sliceShow(dy,slice,indent);
 
     if(dy != NULL)
         *dy = myDy;
     }
 }
 
 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");
         imgTrackShow(NULL,imgTrack,0);
         }
     return FALSE;
     }
 char * name = (imgTrack->name != NULL ? imgTrack->name : imgTrack->tdb->track);
 if (imgTrack->db == NULL)
     {
     if (verbose)
         {
         warn("imgTrack(%s) has no db.",name);
         imgTrackShow(NULL,imgTrack,0);
         }
 
     return FALSE;
     }
 if (imgTrack->db == NULL)
     {
     if (verbose)
         {
         warn("imgTrack(%s) has no chrom.",name);
         imgTrackShow(NULL,imgTrack,0);
         }
     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);
         imgTrackShow(NULL,imgTrack,0);
         }
     return FALSE;
     }
 if(imgTrack->slices == NULL)
     {
     if (verbose)
         {
         warn("imgTrack(%s) has no slices.",name);
         imgTrackShow(NULL,imgTrack,0);
         }
     return FALSE;
     }
 // Can have no more than one of each type of slice
 if(imgTrack->slices && imgTrack->slices->type == stData)
    slReverse(&imgTrack->slices);
 boolean found[stMaxSliceTypes] = { 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));
             imgTrackShow(NULL,imgTrack,0);
             }
         return FALSE;
         }
     found[slice->type] = TRUE;
 
     if(!sliceIsConsistent(slice,verbose))
         {
         if (verbose)
             {
             warn("imgTrack(%s) has bad slice",name);
             imgTrackShow(NULL,imgTrack,0);
             }
         return FALSE;
         }
     }
 // This is not a requirement as the data portion could be empty (height==0) FIXME This still needs to be properly resolved
 //if(!found[stData])
 //    {
 //    if (verbose)
 //        {
 //        warn("imgTrack(%s) has no DATA slice.",name);
 //        imgTrackShow(NULL,imgTrack,0);
 //        }
 //    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 *imgBoxStart(char *db,char *chrom,int chromStart,int chromEnd,boolean plusStrand,int sideLabelWidth,int width)
 /* 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  = (sideLabelWidth != 0);
 imgBox->sideLabelWidth = sideLabelWidth;
 imgBox->images         = NULL;
 imgBox->bgImg          = NULL;
 imgBox->width          = width;
 imgBox->showPortal     = FALSE;
 //int oneThird = (chromEnd - chromStart)/3; // TODO: Currently defaulting to 1/3 of image width
 imgBox->portalStart    = chromStart;// + oneThird;
 imgBox->portalEnd      = chromEnd;// - oneThird;
 imgBox->portalWidth    = chromEnd - chromStart;
 imgBox->basesPerPixel  = ((double)imgBox->chromEnd - imgBox->chromStart)/(imgBox->width - imgBox->sideLabelWidth);
 return imgBox;
 }
 
 boolean imgBoxPortalDefine(struct imgBox *imgBox,int *chromStart,int *chromEnd,int *imgWidth,double imageMultiple)
 /* Defines the portal of the imgBox.  The portal is the initial viewable region when dragScroll is being used.
    the new chromStart,chromEnd and imgWidth are returned as OUTs, while the portal becomes the initial defined size
    returns TRUE if successfully defined as having a portal */
 {
 if( (int)imageMultiple == 0)
 #ifdef IMAGEv2_DRAG_SCROLL_SZ
     imageMultiple = IMAGEv2_DRAG_SCROLL_SZ;
 #else//ifndef IMAGEv2_DRAG_SCROLL_SZ
     imageMultiple = 1;
 #endif//ndef IMAGEv2_DRAG_SCROLL_SZ
 
 imgBox->portalStart = imgBox->chromStart;
 imgBox->portalEnd   = imgBox->chromEnd;
 imgBox->portalWidth = imgBox->width - imgBox->sideLabelWidth;
 imgBox->showPortal  = FALSE; // Guilty until proven innocent
 
 int positionWidth = (int)((imgBox->portalEnd - imgBox->portalStart) * imageMultiple);
 *chromStart = imgBox->portalStart - (int)(((imageMultiple - 1)/2) * (imgBox->portalEnd - imgBox->portalStart));
 if( *chromStart < 0)
     *chromStart = 0;
 *chromEnd = *chromStart + positionWidth;
 struct chromInfo *chrInfo = hGetChromInfo(imgBox->db,imgBox->chrom);
 if(chrInfo == NULL)
     {
     *chromStart = imgBox->chromStart;
     *chromEnd   = imgBox->chromEnd;
     return FALSE;
     }
 if (*chromEnd > (int)(chrInfo->size))  // Bound by chrom length
     {
     *chromEnd = (int)(chrInfo->size);
     *chromStart = *chromEnd - positionWidth;
     if (*chromStart < 0)
         *chromStart = 0;
     }
 // TODO: Normalize to power of 10 boundary
 // Normalize portal ends
 int diff = *chromStart - imgBox->portalStart;
 if(diff < 10 && diff > -10)
     *chromStart = imgBox->portalStart;
 diff = *chromEnd - imgBox->portalEnd;
 if(diff < 10 && diff > -10)
     *chromEnd = imgBox->portalEnd;
 
 double growthOfImage = (*chromEnd - *chromStart)/(imgBox->portalEnd - imgBox->portalStart);
 *imgWidth = (imgBox->portalWidth * growthOfImage) + imgBox->sideLabelWidth;
 
 //if(imgBox->portalStart < *chromStart || imgBox->portalEnd > *chromEnd
 //|| imgBox->portalWidth > *imgWidth)
 //    {
 //    *imgWidth   = imgBox->width;  // Undo damage
 //    *chromStart = imgBox->chromStart;
 //    *chromEnd   = imgBox->chromEnd;
 //    return FALSE;
 //    }
 imgBox->width      = *imgWidth;
 imgBox->chromStart = *chromStart;
 imgBox->chromEnd   = *chromEnd;
 imgBox->basesPerPixel = ((double)imgBox->chromEnd - imgBox->chromStart)/(imgBox->width - imgBox->sideLabelWidth);
 imgBox->showPortal    = TRUE;
 //warn("portal(%d,%d,%d)\nimage(%d,%d,%d) growth:%G",imgBox->portalStart,imgBox->portalEnd,imgBox->portalWidth,
 //     imgBox->chromStart,imgBox->chromEnd,(imgBox->width - imgBox->sideLabelWidth),growthOfImage);
 
 return imgBox->showPortal;
 }
 
 boolean imgBoxPortalRemove(struct imgBox *imgBox,int *chromStart,int *chromEnd,int *imgWidth)
 /* Will redefine the imgBox as the portal dimensions and return the dimensions as OUTs.
    Returns TRUE if a portal was defined in the first place */
 {
 if(imgBox->showPortal == FALSE)
     {
     *chromStart=imgBox->chromStart;  // return to original coordinates
     *chromEnd  =imgBox->chromEnd;
     *imgWidth  =imgBox->width;
     return FALSE;
     }
 *chromStart=imgBox->chromStart=imgBox->portalStart;  // return to original coordinates
 *chromEnd  =imgBox->chromEnd  =imgBox->portalEnd;
 *imgWidth  =imgBox->width     = (imgBox->portalWidth + imgBox->sideLabelWidth);
 imgBox->showPortal = FALSE;
 return TRUE;
 }
 
 boolean imgBoxPortalDimensions(struct imgBox *imgBox,int *chromStart,int *chromEnd,int *imgWidth,int *sideLabelWidth,int *portalStart,int *portalEnd,int *portalWidth,double *basesPerPixel)
 /* returns the imgBox portal dimensions in the OUTs  returns TRUE if portal defined */
 {
 if ( chromStart )
     *chromStart      = imgBox->chromStart;
 if ( chromEnd )
     *chromEnd        = imgBox->chromEnd;
 if ( imgWidth )
     *imgWidth        = imgBox->width;
 if ( sideLabelWidth )
     *sideLabelWidth  = imgBox->sideLabelWidth;
 if(imgBox->showPortal)
     {
     if ( portalStart )
         *portalStart = imgBox->portalStart;
     if ( portalEnd   )
         *portalEnd   = imgBox->portalEnd;
     if ( portalWidth )
         *portalWidth = imgBox->portalWidth + imgBox->sideLabelWidth;
     }
 else
     {
     if ( portalStart )
         *portalStart = imgBox->chromStart;
     if ( portalEnd   )
         *portalEnd   = imgBox->chromEnd;
     if ( portalWidth )
         *portalWidth = imgBox->width;
     }
 if ( basesPerPixel   )
     *basesPerPixel   = imgBox->basesPerPixel;
 return imgBox->showPortal;
 }
 
 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;
 }
 
 // TODO: Will we need this?
 //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 hasCenterLabel,int order)
 /* 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,hasCenterLabel,vis,order);
 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 hasCenterLabel,int order)
 /* 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,hasCenterLabel,order);
 return imgTrack;
 }
 
 struct imgTrack *imgBoxTrackUpdateOrAdd(struct imgBox *imgBox,struct trackDb *tdb,char *name,enum trackVisibility vis,boolean hasCenterLabel,int order)
 /* 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,hasCenterLabel,order);
 
 return imgTrackUpdate(imgTrack,tdb,name,imgBox->db,imgBox->chrom,imgBox->chromStart,imgBox->chromEnd,imgBox->plusStrand,hasCenterLabel,vis,order);
 }
 
 // TODO: Will we need this?
 //boolean imgBoxTrackRemove(struct imgBox *imgBox,struct imgTrack *imgTrack)
 //{
 //return slRemoveEl(&(imgBox->imgTracks),imgTrack);
 //}
 
 void imgBoxTracksNormalizeOrder(struct imgBox *imgBox)
 /* This routine sorts the imgTracks */
 {
 slSort(&(imgBox->imgTracks), imgTrackOrderCmp);
 }
 
 void imgBoxShow(struct dyString **dy,struct imgBox *imgBox,int indent)
 /* show the imgBox */
 {
 if(imgBox)
     {
     struct dyString *myDy = addIndent(dy,indent);
     dyStringPrintf(myDy,"imgBox: %s.%s:%d-%d %c width:%d basePer:%g sideLabe:%s w:%d portal:%s %d-%d w:%d",
             (imgBox->db?imgBox->db:""),(imgBox->chrom?imgBox->chrom:""),
             imgBox->chromStart,imgBox->chromEnd,(imgBox->plusStrand?'+':'-'),
             imgBox->width,imgBox->basesPerPixel,(imgBox->showSideLabel?"Yes":"No"),imgBox->sideLabelWidth,
             (imgBox->showPortal?"Yes":"No"),imgBox->portalStart,imgBox->portalEnd,imgBox->portalWidth);
     indent++;
     struct image *img;
     for(img=imgBox->images;img!=NULL;img=img->next)
         imgShow(&myDy,img,"data ",indent);
     if(imgBox->bgImg)
         imgShow(&myDy,imgBox->bgImg,"bgnd ",indent);
     if(dy == NULL)
         warn("%s",dyStringCannibalize(&myDy));
 
     struct imgTrack *imgTrack = NULL;
     for (imgTrack = imgBox->imgTracks; imgTrack != NULL; imgTrack = imgTrack->next )
         imgTrackShow(dy,imgTrack,indent);
 
     if(dy != NULL)
         *dy = myDy;
     }
 }
 
 int imgBoxDropEmpties(struct imgBox *imgBox)
 /* Empty imageTracks (without slices) is not an error but they should be dropped.
    returns remaining current track count */
 {
 if (imgBox == NULL)
     return 0;
 struct imgTrack *imgTrack = imgBox->imgTracks;
 while(imgTrack != NULL)
     {
     if(imgTrack->slices == NULL)
         {
         slRemoveEl(&(imgBox->imgTracks),imgTrack);
         imgTrackFree(&imgTrack);
         imgTrack = imgBox->imgTracks; // start over
         continue;
         }
     imgTrack = imgTrack->next;
     }
 return slCount(imgBox->imgTracks);
 }
 
 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 = imgBox->imgTracks;
 while(imgTrack != NULL)
     {
     if(!imgTrackIsComplete(imgTrack,verbose))
         {
         if (verbose)
             warn("imgBox(%s.%s:%d-%d) has bad track - being skipped.",imgBox->db,imgBox->chrom,imgBox->chromStart,imgBox->chromEnd);
         slRemoveEl(&(imgBox->imgTracks),imgTrack);
         imgTrackFree(&imgTrack);
         imgTrack = imgBox->imgTracks; // start over
         continue;
         //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;
         }
     struct imgSlice *slice = NULL;
     for (slice = imgTrack->slices; slice != NULL; slice = slice->next )
         {
         // Every slice that has an image must point to an image owned by the imgBox
         if(slice->parentImg && (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;
             }
         }
     imgTrack = imgTrack->next;
     }
 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;
     }
 }
 
 
 
 /////////////////////// imageV2 UI API
 
 static boolean 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 FALSE;
 
 slReverse(&(map->items)); // These must be reversed so that they are printed in the same order as created!
 
 hPrintf("  <MAP name='map_%s'>", name); // map_ prefix is implicit
 struct mapItem *item = map->items;
 for(;item!=NULL;item=item->next)
     {
     hPrintf("\n   <AREA SHAPE=RECT COORDS='%d,%d,%d,%d' onmouseover='mapItemMouseOver(this)' onmouseout='mapItemMouseOut(this)' onclick='return mapClk(this);'",
            item->topLeftX, item->topLeftY, item->bottomRightX, item->bottomRightY);
     // TODO: remove static portion of the link and handle in js
     if(map->linkRoot != NULL)
         {
         if(skipToSpaces(item->linkVar))
             hPrintf(" HREF=%s%s",map->linkRoot,(item->linkVar != NULL?item->linkVar:""));
         else
             hPrintf(" HREF='%s%s'",map->linkRoot,(item->linkVar != NULL?item->linkVar:""));
         }
     else if(item->linkVar != NULL)
         {
         if(skipToSpaces(item->linkVar))
             hPrintf(" HREF=%s",item->linkVar);
 	else
             hPrintf(" HREF='%s'",item->linkVar);
         }
     else
         warn("map item has no url!");
 
     if(item->title != NULL && strlen(item->title) > 0)
         hPrintf(" TITLE='%s'", htmlEncode(item->title) );
     if(item->id != NULL)
         hPrintf(" id='%s'", item->id);
     hPrintf(">" );
     }
 hPrintf("</MAP>\n");
 return TRUE;
 }
 
 static void imageDraw(struct imgBox *imgBox,struct imgTrack *imgTrack,struct imgSlice *slice,char *name,int offsetX,int offsetY,boolean useMap)
 /* writes an image as HTML */
 {
 if(slice->parentImg && slice->parentImg->file != NULL)
     {
     hPrintf("  <IMG id='img_%s' src='%s' style='left:-%dpx; top: -%dpx;'",
             name,slice->parentImg->file,offsetX,offsetY);
     // Problem: dragScroll beyond left shows ugly leftLabel!
     // Tried clip:rect() but this only works with position:absolute!
     // May need to split image betweeen side label and data!!! That is a big change.
 
     if(useMap)
         hPrintf(" usemap='#map_%s'",name);
     hPrintf(" class='sliceImg %s",sliceTypeToClass(slice->type));
     if(slice->type==stData && imgBox->showPortal)
         hPrintf(" panImg' ondrag='{return false;}'");
     else
         hPrintf("'");
     if(slice->title != NULL)
         hPrintf(" title='%s'", htmlEncode(slice->title) );           // Adds slice wide title
     else if(slice->parentImg->title != NULL)
         hPrintf("' title='%s'", htmlEncode(slice->parentImg->title) );// Adds image wide title
     hPrintf(">");
     }
 else
     {
     int height = slice->height;
     // Adjustment for centerLabel Conditional
     if (imgTrack->centerLabelSeen == clNotSeen
     &&  (slice->type == stSide || slice->type == stButton))
         {
         struct imgSlice *centerSlice = imgTrackSliceGetByType(imgTrack,stCenter);
         if (centerSlice != NULL)
             height -= centerSlice->height;
         }
     hPrintf("  <p id='p_%s' style='height:%dpx;",name,height);
     if(slice->type==stButton)
         {
         char *trackName = imgTrack->name;
         if(trackName == NULL)
             {
             struct trackDb * tdb = imgTrack->tdb;
             if(tdbIsCompositeChild(tdb))
-                tdb = trackDbCompositeParent(tdb);
+                tdb = tdbGetComposite(tdb);
             trackName = tdb->track;
             }
         hPrintf(" width:9px; display:none;' class='%s btn btnN'></p>",trackName);
         }
     else
         hPrintf("width:%dpx;'></p>",slice->width);
     }
 }
 
 // FF does not support newline code and '...' looks bad without newlines
 #define NEWLINE_ENCODED " &#x0A;"
 #define NEWLINE_NOT_SUPPORTED " - "
 #define NEWLINE_TO_USE(browser) ((browser) == btFF ? NEWLINE_NOT_SUPPORTED : NEWLINE_ENCODED)
 #define ELLIPSIS_TO_USE(browser) ((browser) == btFF ? "" : "...")
 
 static void sliceAndMapDraw(struct imgBox *imgBox,struct imgTrack *imgTrack,enum sliceType sliceType,char *name,boolean scrollHandle)
 /* writes a slice of an image and any assocated image map as HTML */
 {
 if(imgBox==NULL || imgTrack==NULL)
     return;
 struct imgSlice *slice = imgTrackSliceGetByType(imgTrack,sliceType);
 if(slice==NULL || slice->height == 0)
     return;
 
 boolean useMap=FALSE;
 int offsetX=slice->offsetX;
 int offsetY=slice->offsetY;
 int height = slice->height;
 int width=slice->width;
 if(slice->parentImg)
     {
     // Adjustment for centerLabel Conditional
     if (imgTrack->centerLabelSeen == clNotSeen
     &&  (sliceType == stSide || sliceType == stButton))
         {
         struct imgSlice *centerSlice = imgTrackSliceGetByType(imgTrack,stCenter);
         if (centerSlice != NULL)
             {
             height -= centerSlice->height;
             offsetY += centerSlice->height;
             }
         }
     // Adjustment for portal
     if(imgBox->showPortal && imgBox->basesPerPixel > 0
     && (sliceType==stData || sliceType==stCenter))
         {
         offsetX += (imgBox->portalStart - imgBox->chromStart) / imgBox->basesPerPixel;
         width=imgBox->portalWidth;
         }
         hPrintf("  <div style='width:%dpx; height:%dpx;",width,height);
         if (sliceType == stCenter && imgTrack->centerLabelSeen == clNotSeen)
             hPrintf(" display:none;");
         hPrintf("' class='sliceDiv %s",sliceTypeToClass(slice->type));
 
     #ifdef IMAGEv2_DRAG_SCROLL
     if(imgBox->showPortal && sliceType==stData)
         hPrintf(" panDiv%s",(scrollHandle?" scroller":""));
     #endif //def IMAGEv2_DRAG_SCROLL
     hPrintf("'>\n");
     }
 struct mapSet *map = sliceGetMap(slice,FALSE); // Could be the image map or slice specific
 if(map)
     useMap = imageMapDraw(map,name);
 else if(slice->link != NULL)
     {
     if(skipToSpaces(slice->link) != NULL)
         hPrintf("  <A HREF=%s",slice->link);
     else
         hPrintf("  <A HREF='%s'",slice->link);
     if (slice->title != NULL)
         {
         if (sliceType == stButton)
             {
             enum browserType browser = cgiClientBrowser(NULL,NULL,NULL);
             char *newLine = NEWLINE_TO_USE(browser);
             char *ellipsis = ELLIPSIS_TO_USE(browser);
             if(imgTrack->reorderable)
                 hPrintf(" TITLE='%s%sclick to configure%s%sdrag to reorder%s'",htmlEncode(slice->title), newLine,
                     ellipsis, newLine,(tdbIsCompositeChild(imgTrack->tdb)?" highlighted subtrack":"") );
             else
                 hPrintf(" TITLE='%s%sclick to configure%s'",htmlEncode(slice->title), newLine, ellipsis);
             }
         else
             hPrintf(" TITLE='Click for: &#x0A;%s'", htmlEncode(slice->title) );
         }
     hPrintf(">\n" );
     }
 
 imageDraw(imgBox,imgTrack,slice,name,offsetX,offsetY,useMap);
 if(slice->link != NULL)
     hPrintf("</A>");
 
 if(slice->parentImg)
     hPrintf("</div>");
 }
 
 void imageBoxDraw(struct imgBox *imgBox)
 /* writes a entire imgBox including all tracksas HTML */
 {
 if(imgBox->imgTracks == NULL)  // Not an error to have an empty image
     return;
 imgBoxDropEmpties(imgBox);
 boolean verbose = (hIsPrivateHost());   // Warnings for hgwdev only
 if(!imgBoxIsComplete(imgBox,verbose)) // dorps empties as okay
     return;
 char name[256];
 
 imgBoxTracksNormalizeOrder(imgBox);
 //if(verbose)
 //    imgBoxShow(NULL,imgBox,0);
 
 hPrintf("<!-- - - - - - - - vvv IMAGEv2 vvv - - - - - - - -->\n");  // DANGER FF interprets '--' as end of comment, not '-->'
 jsIncludeFile("jquery.tablednd.js", NULL);
 if(imgBox->bgImg)
     {
     int offset = 0;
     if(imgBox->showSideLabel && imgBox->plusStrand)
         {
         struct imgSlice *slice = imgTrackSliceGetByType(imgBox->imgTracks,stData);
         if(slice)
             offset = (slice->offsetX * -1);  // This works because the ruler has a slice
          }
     hPrintf("<style type='text/css'>\n");
     if(offset != 0)
         hPrintf("td.tdData {background-image:url(\"%s\");background-repeat:repeat-y;background-position:%dpx;}\n",imgBox->bgImg->file,offset);
     else
         hPrintf("td.tdData {background-image:url(\"%s\");background-repeat:repeat-y;}\n",imgBox->bgImg->file);
     hPrintf("</style>\n");
     }
 
 #ifdef IMAGEv2_DRAG_SCROLL
 if(imgBox->showPortal)
     {
     hPrintf("<script type='text/javascript'>var imgBoxPortal=true;");
     hPrintf("var imgBoxChromStart=%d;var imgBoxChromEnd=%d;var imgBoxWidth=%d;",
             imgBox->chromStart, imgBox->chromEnd,(imgBox->width - imgBox->sideLabelWidth));
     hPrintf("var imgBoxPortalStart=%d;var imgBoxPortalEnd=%d;var imgBoxPortalWidth=%d;",
             imgBox->portalStart, imgBox->portalEnd, imgBox->portalWidth);
     hPrintf("var imgBoxLeftLabel=%d;var imgBoxPortalOffsetX=%d;var imgBoxBasesPerPixel=%lf;</script>\n",
             (imgBox->plusStrand?imgBox->sideLabelWidth:0),
             (int)((imgBox->portalStart - imgBox->chromStart) / imgBox->basesPerPixel),imgBox->basesPerPixel);
     }
 #endif//def IMAGEv2_DRAG_SCROLL
 
 hPrintf("<TABLE id='imgTbl' border=0 cellspacing=0 cellpadding=0 BGCOLOR='%s'",COLOR_WHITE);//COLOR_RED); // RED to help find bugs
 hPrintf(" width=%d",imgBox->showPortal?(imgBox->portalWidth+imgBox->sideLabelWidth):imgBox->width);
 hPrintf(" class='tableWithDragAndDrop'");
 hPrintf(" style='border:1px solid blue;border-collapse:separate;'>\n");
 
 #if defined(CONTEXT_MENU) || defined(TRACK_SEARCH)
 struct dyString *jsonTdbVars = NULL;
 #endif/// defined(CONTEXT_MENU) || defined(TRACK_SEARCH)
 
 char *newLine = NEWLINE_TO_USE(cgiClientBrowser(NULL,NULL,NULL));
 struct imgTrack *imgTrack = imgBox->imgTracks;
 for(;imgTrack!=NULL;imgTrack=imgTrack->next)
     {
     char *trackName = (imgTrack->name != NULL ? imgTrack->name : imgTrack->tdb->track );
 #if defined(CONTEXT_MENU) || defined(TRACK_SEARCH)
     if (!trackImgOnly)
         {
         struct track *track = hashFindVal(trackHash, trackName);
         if(track)
             jsonTdbSettingsBuild(&jsonTdbVars, track);
         }
 #endif
     //if(verbose && imgTrack->order == 3)
     //    imgTrackShow(NULL,imgTrack,0);
     hPrintf("<TR id='tr_%s' abbr='%d' class='imgOrd%s%s'>\n",trackName,imgTrack->order,
         (imgTrack->reorderable?" trDraggable":" nodrop nodrag"),
         (imgTrack->centerLabelSeen != clAlways?" clOpt":"") );
 
     if(imgBox->showSideLabel && imgBox->plusStrand)
         {
         // button
         safef(name, sizeof(name), "btn_%s", trackName);
         hPrintf(" <TD id='td_%s'%s>\n",name,(imgTrack->reorderable?" class='dragHandle'":""));
         sliceAndMapDraw(imgBox,imgTrack,stButton,name,FALSE);
         hPrintf("</TD>\n");
         // leftLabel
         safef(name,sizeof(name),"side_%s",trackName);
         if (imgTrack->reorderable)
             hPrintf(" <TD id='td_%s' class='dragHandle' title='%s%sdrag to reorder'>\n",name,htmlEncode(imgTrack->tdb->longLabel),newLine);
         else
             hPrintf(" <TD id='td_%s'>\n",name);
         sliceAndMapDraw(imgBox,imgTrack,stSide,name,FALSE);
         hPrintf("</TD>\n");
         }
 
     // Main/Data image region
     hPrintf(" <TD id='td_data_%s' width=%d class='tdData'>\n", trackName, imgBox->width);
     // centerLabel
     if(imgTrack->hasCenterLabel)
         {
         safef(name, sizeof(name), "center_%s", trackName);
         sliceAndMapDraw(imgBox,imgTrack,stCenter,name,FALSE);
         hPrintf("\n");
         }
     // data image
     safef(name, sizeof(name), "data_%s", trackName);
     sliceAndMapDraw(imgBox,imgTrack,stData,name,(imgTrack->order>0));
     hPrintf("</TD>\n");
 
     if(imgBox->showSideLabel && !imgTrack->plusStrand)
         {
         // rightLabel
         safef(name, sizeof(name), "side_%s", trackName);
         if (imgTrack->reorderable)
             hPrintf(" <TD id='td_%s' class='dragHandle' title='%s%sdrag to reorder'>\n",name,htmlEncode(imgTrack->tdb->longLabel),newLine);
         else
             hPrintf(" <TD id='td_%s'>\n",name);
         sliceAndMapDraw(imgBox,imgTrack,stSide,name,FALSE);
         hPrintf("</TD>\n");
         // button
         safef(name, sizeof(name), "btn_%s", trackName);
         hPrintf(" <TD id='td_%s'%s>\n",name,(imgTrack->reorderable?" class='dragHandle'":""));
         sliceAndMapDraw(imgBox,imgTrack,stButton, name,FALSE);
         hPrintf("</TD>\n");
         }
     hPrintf("</TR>\n");
     }
 hPrintf("</TABLE>\n");
 hPrintf("<!-- - - - - - - - ^^^ IMAGEv2 ^^^ - - - - - - - -->\n");  // DANGER FF interprets '--' as end of comment, not '-->'
 
 #if defined(CONTEXT_MENU) || defined(TRACK_SEARCH)
 if (!trackImgOnly && jsonTdbVars != NULL)
     hPrintf(jsonTdbSettingsUse(&jsonTdbVars));
 #endif/// defined(CONTEXT_MENU) || defined(TRACK_SEARCH)
 }