77dc06c4bdc5e10f5d2705a51300ec028a7d785f tdreszer Fri Oct 1 10:51:48 2010 -0700 FindTracks now finds superTracks (Advanced only) and hgTrackUi now does superTrack reshaping when children have vis changed diff --git src/hg/hgTracks/imageV2.c.old src/hg/hgTracks/imageV2.c.old new file mode 100644 index 0000000..64471d2 --- /dev/null +++ src/hg/hgTracks/imageV2.c.old @@ -0,0 +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 (tdbIsContainerChild(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,",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," "); +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," 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," 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 = 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 " 
" +#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: 
%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) +}