010882a6948505235ea0516ca085178b70299eb8 braney Fri Mar 24 15:10:45 2023 -0700 fix bug in squishyPack if squishy part was empty diff --git src/hg/hgTracks/imageV2.c src/hg/hgTracks/imageV2.c index cdfd9f2..bef93c4 100644 --- src/hg/hgTracks/imageV2.c +++ src/hg/hgTracks/imageV2.c @@ -1,2155 +1,2155 @@ // imageV2 - API for creating the image V2 features. /* Copyright (C) 2014 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #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" #include "regexHelper.h" #include "customComposite.h" #include "chromAlias.h" // Note: when right-click View image (or pdf output) then theImgBox==NULL, so it will be rendered as a single simple image struct imgBox *theImgBox = NULL; // Make this global for now to avoid huge rewrite struct imgTrack *curImgTrack = 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, struct slName *orderedWiggles) // 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! if (track->originalTrack != NULL) safef(var,sizeof(var),"%s_%s",track->originalTrack,IMG_ORDER_VAR); else safef(var,sizeof(var),"%s_%s",track->tdb->track,IMG_ORDER_VAR); flatTrack->order = cartUsualInt(cart, var,IMG_ANYORDER); if (flatTrack->order >= IMG_ORDERTOP) { cartRemove(cart,var); flatTrack->order = IMG_ANYORDER; } static int topOrder = IMG_ORDERTOP; // keep track of the order added to top of image static int lastOrder = IMG_ORDEREND; // keep track of the order added and beyond end if ( flatTrack->order == IMG_ANYORDER) { int index; if (track->customTrack) flatTrack->order = ++topOrder; // Custom tracks go to top else if ((orderedWiggles != NULL) && ((index = slNameFindIx(orderedWiggles, track->track)) != -1)) flatTrack->order = topOrder + index + 1; else 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); if (a->order == b->order) { if ((a->track->originalTrack != NULL) || (b->track->originalTrack != NULL)) return a->track->visibility - b->track->visibility; return tgCmpPriority(&(a->track),&(b->track)); } 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_ORDERTOP) // and those not yet reordered (above). Within those not yet dragReordered are 2 sets: // Those that begin numbering at IMG_ORDERTOP and those that begin at IMG_ORDEREND. // This routine must determine if there are any already dragOrdered, and if so, position the // newcomers in place. Newly appearing customTracks will appear at top, while newly appearing // standard tracks appear at the end of the image. int haveBeenOrderd = 0, imgOrdHighest=0; // Keep track of reordered count and position int notYetOrdered = 0, toBeTopHighest=0; // Keep track of those to be reordered, and top ordered // First determine what if anything needs to be rearranged. struct flatTracks *oneTrack = *flatTracks; for (;oneTrack!=NULL;oneTrack = oneTrack->next) { if (oneTrack->order <= IMG_ORDERTOP) { haveBeenOrderd++; if (imgOrdHighest < oneTrack->order ) imgOrdHighest = oneTrack->order; } else { notYetOrdered++; if (oneTrack->order <= IMG_ORDEREND) // && oneTrack->order >= IMG_ORDERTOP { if (toBeTopHighest < oneTrack->order ) toBeTopHighest = oneTrack->order; } } } // If some have previously been dragOrdered AND some new ones need to be given an explicit order if (haveBeenOrderd > 0 && notYetOrdered > 0) { char var[256]; int gapOnTopNeeded = 0; if (toBeTopHighest > 0) { gapOnTopNeeded = toBeTopHighest - IMG_ORDERTOP; imgOrdHighest += gapOnTopNeeded; // Will be after this loop // Warning: Will need to throw away ALL previous orderings // (even those not currently in image)! safef(var,sizeof(var),"*_%s",IMG_ORDER_VAR); cartRemoveLike(cart, var); } // This difference should be removed from any with IMG_ORDEREND int gapFromOrderedToEnd = (IMG_ORDEREND - imgOrdHighest); for (oneTrack = *flatTracks;oneTrack!=NULL;oneTrack = oneTrack->next) { if (oneTrack->order <= IMG_FIXEDPOS) ; // Untouchables else if (oneTrack->order <= IMG_ORDERTOP && gapOnTopNeeded > 0) { // Already order tracks will need to be pushed down. oneTrack->order += gapOnTopNeeded; safef(var,sizeof(var),"%s_%s",oneTrack->track->track,IMG_ORDER_VAR); cartSetInt(cart, var, oneTrack->order); } else if (oneTrack->order >= IMG_ORDERTOP && oneTrack->order < IMG_ORDEREND && gapOnTopNeeded > 0) { // Unordered custom tracks will need to be added to top! oneTrack->order -= IMG_ORDERTOP; // Force to top safef(var,sizeof(var),"%s_%s",oneTrack->track->track,IMG_ORDER_VAR); cartSetInt(cart, var, oneTrack->order); } else if (oneTrack->order >= IMG_ORDEREND && gapFromOrderedToEnd) { // Normal unordered tracks can fill in the trailing numbers oneTrack->order -= gapFromOrderedToEnd; 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); } } // TODO: Move to trackDb.h and trackDbCustom.c enum kindOfParent { kopChildless = 0, kopFolder = 1, kopComposite = 2, kopMultiTrack = 3, kopCompositeView = 4 }; enum kindOfChild { kocOrphan = 0, kocFolderContent = 1, kocCompositeChild = 2, kocMultiTrackChild = 3 }; enum kindOfParent tdbKindOfParent(struct trackDb *tdb) { enum kindOfParent kindOfParent = kopChildless; if (tdbIsFolder(tdb)) kindOfParent = kopFolder; else if (tdbIsComposite(tdb)) kindOfParent = kopComposite; else if (tdbIsMultiTrack(tdb)) kindOfParent = kopMultiTrack; else if (tdbIsCompositeView(tdb)) // NOTE: This should not be needed in js kindOfParent = kopCompositeView; return kindOfParent; } enum kindOfChild tdbKindOfChild(struct trackDb *tdb) { enum kindOfChild kindOfChild = kocOrphan; if (tdbIsFolderContent(tdb)) kindOfChild = kocFolderContent; else if (tdbIsCompositeChild(tdb)) kindOfChild = kocCompositeChild; else if (tdbIsMultiTrackChild(tdb)) kindOfChild = kocMultiTrackChild; return kindOfChild; } ///////////////////////// // JSON support. Eventually the whole imgTbl could be written out as JSON static void jsonTdbSettingsInit(struct jsonElement *settings) // Inititializes trackDbJson { struct jsonElement *ele = newJsonObject(newHash(8)); jsonObjectAdd(ele, "shortLabel", newJsonString("ruler")); jsonObjectAdd(ele, "type", newJsonString("ruler")); jsonObjectAdd(ele, "longLabel", newJsonString("Base Position Controls")); jsonObjectAdd(ele, "canPack", newJsonNumber(0)); jsonObjectAdd(ele, "visibility", newJsonNumber(rulerMode)); jsonObjectAdd(ele, "configureBy", newJsonString("popup")); jsonObjectAdd(ele, "kindOfParent", newJsonNumber(0)); jsonObjectAdd(settings, "ruler", (struct jsonElement *) ele); } void jsonTdbSettingsBuild(struct jsonElement *settings, struct track *track, boolean configurable) // Adds trackDb settings to the jsonTdbSettings { struct jsonElement *ele = newJsonObject(newHash(8)); jsonObjectAdd(settings, track->track, (struct jsonElement *) ele); // track name and type jsonObjectAdd(ele, "type", newJsonString(track->tdb->type)); // Tell which kind of parent and which kind of child enum kindOfParent kindOfParent = tdbKindOfParent(track->tdb); enum kindOfChild kindOfChild = tdbKindOfChild(track->tdb); jsonObjectAdd(ele, "kindOfParent", newJsonNumber(kindOfParent)); jsonObjectAdd(ele, "kindOfChild", newJsonNumber(kindOfChild)); // Tell something about the parent and/or children if (kindOfChild != kocOrphan) { struct trackDb *parentTdb = (kindOfChild == kocFolderContent ? track->tdb->parent : tdbGetContainer(track->tdb)); jsonObjectAdd(ele, "parentTrack", newJsonString(parentTdb->track)); jsonObjectAdd(ele, "parentLabel", newJsonString(parentTdb->shortLabel)); if (kindOfChild != kocFolderContent && !track->canPack) { jsonObjectAdd(ele, "shouldPack", newJsonNumber(0)); // default vis is full, track->canPack = rTdbTreeCanPack(parentTdb); // but pack is an option } } boolean isCustomComposite = trackDbSettingOn(track->tdb, CUSTOM_COMPOSITE_SETTING); jsonObjectAdd(ele, "isCustomComposite", newJsonBoolean(isCustomComposite)); // check if track can have merged items, needed for context clicks in track char *canHaveMergedItems = trackDbSetting(track->tdb, MERGESPAN_TDB_SETTING); if (canHaveMergedItems != NULL) { // tells hgTracks.js whether we currently are merged or not char setting[256]; safef(setting, sizeof(setting), "%s.%s", track->track, MERGESPAN_CART_SETTING); jsonObjectAdd(ele, setting, newJsonNumber(cartUsualInt(cart, setting, 0))); } // XXXX really s/d be numChildren jsonObjectAdd(ele, "hasChildren", newJsonNumber(slCount(track->tdb->subtracks))); // Configuring? int cfgByPopup = configurableByAjax(track->tdb,0); if (!configurable || track->hasUi == FALSE || cfgByPopup == cfgNone) jsonObjectAdd(ele, "configureBy", newJsonString("none")); else if (cfgByPopup < 0) // denied via ajax, but allowed via full normal hgTrackUi page jsonObjectAdd(ele, "configureBy", newJsonString("clickThrough")); else jsonObjectAdd(ele, "configureBy", newJsonString("popup")); // Remote access by URL? if (sameWord(track->tdb->type, "remote") && trackDbSetting(track->tdb, "url") != NULL) jsonObjectAdd(ele, "url", newJsonString(trackDbSetting(track->tdb, "url"))); // Close with some standard vars jsonObjectAdd(ele, "shortLabel", newJsonString(track->shortLabel)); jsonObjectAdd(ele, "longLabel", newJsonString(track->longLabel)); jsonObjectAdd(ele, "canPack", newJsonNumber(track->canPack)); if (track->limitedVis != track->visibility) jsonObjectAdd(ele, "limitedVis", newJsonNumber(track->limitedVis)); jsonObjectAdd(ele, "visibility", newJsonNumber(track->visibility)); } void jsonTdbSettingsUse(struct jsonElement *settings) { // add the settings to the hgTracks output object jsonObjectAdd(jsonForClient, "trackDb", (struct jsonElement *) settings); } ///////////////////////// // 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 *doMapSetItemFindOrAdd(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); } struct mapItem *mapSetItemFindOrAdd(struct mapSet *map,char *link,char *title, int topLeftX,int topLeftY,int bottomRightX,int bottomRightY, char *id) // Function to allow conf variable to turn off or on the searching of overlapping // previous boxes. { static struct mapItem *(*mapFunc)() = NULL; if (mapFunc == NULL) { if (cfgOption("restoreMapFind")) mapFunc = doMapSetItemFindOrAdd; else mapFunc = mapSetItemAdd; } return (*mapFunc)(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 = dyStringNew(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 *png,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 (png != NULL) img->file = cloneString(png); 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, long chromStart, long 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; // pngTn.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, long chromStart, long 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; } void imgTrackMarkForAjaxRetrieval(struct imgTrack *imgTrack,boolean ajaxRetrieval) // Updates the imgTrack to trigger an ajax callback from the html client to get this track { imgTrack->ajaxRetrieval = ajaxRetrieval; } 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); if (a->order == b->order) return a->vis - b->vis; 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); } int imgTrackCoordinates(struct imgTrack *imgTrack, int *leftX,int *topY,int *rightX,int *bottomY) // Fills in topLeft x,y and bottomRight x,y coordinates, returning topY. { int xLeft = 0,yTop = 0,xRight = 0,yBottom = 0; struct imgSlice *slice; for (slice = imgTrack->slices;slice != NULL;slice=slice->next) { if (revCmplDisp) { if (xLeft == 0 || xLeft > slice->offsetX - slice->width) xLeft = slice->offsetX - slice->width; if (xRight < slice->offsetX) xRight = slice->offsetX; } else { if (xLeft == 0 || xLeft > slice->offsetX) xLeft = slice->offsetX; if (xRight < slice->offsetX + slice->width) xRight = slice->offsetX + slice->width; } if (yTop == 0 || yTop > slice->offsetY) yTop = slice->offsetY; if (yBottom < slice->offsetY + slice->height) yBottom = slice->offsetY + slice->height; } if ( leftX != NULL) *leftX = xLeft; if ( topY != NULL) *topY = yTop; if ( rightX != NULL) *rightX = xRight; if ( bottomY != NULL) *bottomY = yBottom; return yTop; } #define imgTrackTopY(imgTrack) imgTrackCoordinates(imgTrack,NULL,NULL,NULL,NULL) int imgTrackBottomY(struct imgTrack *imgTrack) // Returns the Y coordinate of the bottom of the track. { int bottomY = 0; imgTrackCoordinates(imgTrack,NULL,NULL,NULL,&bottomY); return bottomY; } 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! { if (imgTrack == NULL) return 0; struct imgSlice *slice; char *imgFile = NULL; // name of file that hold the image char *neededId = NULL; // id is only added it it is NOT the trackId. if (imgTrack->tdb == NULL || differentStringNullOk(id, imgTrack->tdb->track)) neededId = id; // Trap surprising location s for map items, but only on test machines. if (hIsPrivateHost()) { int leftX, topY, rightX, bottomY; imgTrackCoordinates(imgTrack, &leftX, &topY, &rightX, &bottomY); // TODO for sideLabels=0, many track item maps are extending down 1 pixel too far. EXISTING BUG. if (topLeftY < topY || bottomRightY > (bottomY + 1)) // Ignoring problem for now by using + 1. { char * name = (imgTrack->name != NULL ? imgTrack->name : imgTrack->tdb != NULL ? imgTrack->tdb->track : imgFile); warn("imgTrackAddMapItem(%s,%s) mapItem (%d,%d)(%d,%d) spills over track bounds(%d,%d)(%d,%d)", name,title,topLeftX,topLeftY,bottomRightX,bottomRightY,leftX,topY,rightX,bottomY); } } 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; } 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 mapSetItemFindOrAdd(map,link,title,max(topLeftX,slice->offsetX), max(topLeftY,slice->offsetY), min(bottomRightX,slice->offsetX + slice->width), min(bottomRightY,slice->offsetY + slice->height), neededId); count++; } else { // NOTE: This assumes that if there is no map, 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++; } } } 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"); if (imgTrack->ajaxRetrieval) dyStringPrintf(myDy," ajaxRetrieval"); 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:%ld-%ld 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; } } //if (!found[stData]) // This is not a requirement as the data portion could be empty (height==0) 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, long chromStart, long 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; // pngTn.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, long *chromStart, long *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) imageMultiple = 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 long positionWidth = (long)((imgBox->portalEnd - imgBox->portalStart) * imageMultiple); *chromStart = imgBox->portalStart - (long)( ((imageMultiple - 1)/2) * (imgBox->portalEnd - imgBox->portalStart)); if (*chromStart < 0) *chromStart = 0; *chromEnd = *chromStart + positionWidth; // get chrom size long virtChromSize = 0; if (sameString(imgBox->chrom, MULTI_REGION_VIRTUAL_CHROM_NAME)) { virtChromSize = virtSeqBaseCount; } else { struct chromInfo *chrInfo = hGetChromInfo(imgBox->db,imgBox->chrom); if (chrInfo == NULL) { char *native = chromAliasFindNative(imgBox->chrom); if (native != NULL) chrInfo = hGetChromInfo(imgBox->db, native); } if (chrInfo == NULL) { *chromStart = imgBox->chromStart; *chromEnd = imgBox->chromEnd; return FALSE; } virtChromSize = chrInfo->size; } if (*chromEnd > virtChromSize) // Bound by chrom length { *chromEnd = virtChromSize; *chromStart = *chromEnd - positionWidth; if (*chromStart < 0) *chromStart = 0; } // TODO: Normalize to power of 10 boundary // Normalize portal ends long 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; return imgBox->showPortal; } boolean imgBoxPortalRemove(struct imgBox *imgBox, long *chromStart, long *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, long *chromStart, long *chromEnd, int *imgWidth, int *sideLabelWidth, long *portalStart, long *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 *png,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(png,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 *png) // 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,png)) 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:%ld-%ld %c width:%d basePer:%g sideLabel:%s w:%d " "portal:%s %ld-%ld 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:%ld-%ld) 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:%ld-%ld) has bad portal range: %ld-%ld", 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:%ld-%ld) 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:%ld-%ld) 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:%ld-%ld) 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:%ld-%ld) has inconsistent imgTrack for %s.%s:%ld-%ld", 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:%ld-%ld) 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 { 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'", item->topLeftX, item->topLeftY, item->bottomRightX, item->bottomRightY); // TODO: remove static portion of the link and handle in js if (sameString(TITLE_BUT_NO_LINK,item->linkVar)) { // map items could be for mouse-over titles only hPrintf(" class='area %s'",TITLE_BUT_NO_LINK); } else 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 : "")); hPrintf(" class='area'"); } else if (item->linkVar != NULL) { if (skipToSpaces(item->linkVar)) hPrintf(" HREF=%s",item->linkVar); else if (startsWith("/cgi-bin/hgGene", item->linkVar)) // redmine #4151 hPrintf(" HREF='..%s'",item->linkVar); // FIXME: Chin should get rid else // of this special case! hPrintf(" HREF='%s'",item->linkVar); hPrintf(" class='area'"); } else warn("map item has no url!"); if (item->title != NULL && strlen(item->title) > 0) { // for TITLEs, which we use for mouseOvers, since they can't have HTML in // them, we substitute a unicode new line for <br> after we've encoded it. // This is stop-gap until we start doing mouseOvers entirely in Javascript char *encodedString = attributeEncode(item->title); hPrintf(" TITLE='%s'", replaceChars(encodedString,"<br>", "
")); } 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 unsightly 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) // || slice->type==stCenter will make centerLabels scroll too hPrintf(" panImg'"); else hPrintf("'"); if (slice->title != NULL) hPrintf(" title='%s'", attributeEncode(slice->title) ); // Adds slice wide title else if (slice->parentImg->title != NULL) hPrintf("' title='%s'", attributeEncode(slice->parentImg->title) );// Adds image wide title if (slice->type==stData || slice->type==stCenter) { char id[256]; safef(id, sizeof id, "img_%s", name); jsOnEventById("drag", id, "return false;"); } 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 %sbtn btnN'></p>", trackName,(slice->link == NULL ? "inset " : "")); } 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; } } // this makes it look like view image theImgBox==NULL if ((slice->type==stData) && !sameString(name,"side_ruler")) // data not high enough by 1 pixel GALT height += 1; // 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)); if (imgBox->showPortal && (sliceType==stData || sliceType==stCenter)) hPrintf(" panDiv%s",(scrollHandle ? " scroller" : "")); 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 (sameString(TITLE_BUT_NO_LINK,slice->link)) { // This fake link ensures a mouse-over title is seen but not heard hPrintf("<A class='%s'",TITLE_BUT_NO_LINK); } else 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 or right click to configure%s%sdrag to reorder%s'", attributeEncode(slice->title), newLine, ellipsis, newLine, (tdbIsCompositeChild(imgTrack->tdb) ? " highlighted subtracks" : "") ); else hPrintf(" TITLE='%s%sclick or right click to configure%s'", attributeEncode(slice->title), newLine, ellipsis); } else hPrintf(" TITLE='Click for: 
%s'", attributeEncode(slice->title) ); } hPrintf(">\n" ); } imageDraw(imgBox,imgTrack,slice,name,offsetX,offsetY,useMap); if (slice->link != NULL) hPrintf("</A>"); if (slice->parentImg) hPrintf("</div>"); } struct imgTrack *smashSquish(struct imgTrack *imgTrackList) /* Javascript doesn't know about our trick to do squishyPack so we need to pass it a single div instead of two. * We assume that the linked track immediately follows the original track in the sorted list. */ { struct imgTrack *nextImg, *imgTrack; for (imgTrack = imgTrackList; imgTrack!=NULL;imgTrack = nextImg) { nextImg = imgTrack->next; boolean joinNext = ((nextImg != NULL) && nextImg->linked); if (joinNext) // Smash these together { struct imgSlice *packedSlices = imgTrack->slices; struct imgSlice *squishSlices = imgTrack->next->slices; - for(; packedSlices; packedSlices = packedSlices->next, squishSlices = squishSlices->next) + for(; packedSlices && squishSlices; packedSlices = packedSlices->next, squishSlices = squishSlices->next) { if (packedSlices->type != stCenter) { if ((packedSlices->map != NULL) && (squishSlices->map != NULL)) packedSlices->map->items = slCat(packedSlices->map->items, squishSlices->map->items); packedSlices->height += squishSlices->height; } } imgTrack->next = nextImg->next; nextImg = nextImg->next; } } return imgTrackList; } 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"); } if (imgBox->showPortal) { // Let js code know what's up // TODO REMOVE OLD WAY int chromSize = hChromSize(database, chromName); long chromSize = virtSeqBaseCount; jsonObjectAdd(jsonForClient,"chromStart", newJsonNumber( 1)); // yep, the js code expects 1-based closed coord here. jsonObjectAdd(jsonForClient,"chromEnd", newJsonNumber(chromSize)); jsonObjectAdd(jsonForClient,"imgBoxPortal", newJsonBoolean(TRUE)); jsonObjectAdd(jsonForClient,"imgBoxWidth", newJsonNumber(imgBox->width-imgBox->sideLabelWidth)); jsonObjectAdd(jsonForClient,"imgBoxPortalStart", newJsonNumber(imgBox->portalStart)); jsonObjectAdd(jsonForClient,"imgBoxPortalEnd", newJsonNumber(imgBox->portalEnd)); jsonObjectAdd(jsonForClient,"imgBoxPortalWidth", newJsonNumber(imgBox->portalWidth)); jsonObjectAdd(jsonForClient,"imgBoxLeftLabel", newJsonNumber(imgBox->plusStrand ? imgBox->sideLabelWidth : 0)); jsonObjectAdd(jsonForClient,"imgBoxPortalOffsetX", newJsonNumber( (long)((imgBox->portalStart - imgBox->chromStart) / imgBox->basesPerPixel))); jsonObjectAdd(jsonForClient,"imgBoxBasesPerPixel", newJsonDouble(imgBox->basesPerPixel)); } else jsonObjectAdd(jsonForClient,"imgBoxPortal", newJsonBoolean(FALSE)); hPrintf("<TABLE id='imgTbl' cellspacing='0' cellpadding='0'"); hPrintf(" width='%d'",imgBox->showPortal?(imgBox->portalWidth+imgBox->sideLabelWidth):imgBox->width); hPrintf(" class='tableWithDragAndDrop'>\n"); struct jsonElement *jsonTdbVars = newJsonObject(newHash(8)); jsonTdbSettingsInit(jsonTdbVars); char *newLine = NEWLINE_TO_USE(cgiClientBrowser(NULL,NULL,NULL)); struct imgTrack *imgTrack = smashSquish(imgBox->imgTracks); for (;imgTrack!=NULL;imgTrack=imgTrack->next) { char *trackName = (imgTrack->name != NULL ? imgTrack->name : imgTrack->tdb->track ); struct track *track = hashFindVal(trackHash, trackName); if (track) jsonTdbSettingsBuild(jsonTdbVars, track, TRUE); hPrintf("<TR id='tr_%s' abbr='%d' class='imgOrd%s%s%s'>\n",trackName,imgTrack->order, (imgTrack->reorderable ? " trDraggable" : " nodrop nodrag"), (imgTrack->centerLabelSeen != clAlways ? " clOpt" : ""), (imgTrack->ajaxRetrieval ? " mustRetrieve" : "")); 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 tdLeft' title='%s%sdrag to reorder'>\n", name,attributeEncode(imgTrack->tdb->longLabel),newLine); else hPrintf(" <TD id='td_%s' class='tdLeft'>\n",name); sliceAndMapDraw(imgBox,imgTrack,stSide,name,FALSE); hPrintf("</TD>\n"); } // Main/Data image region hPrintf(" <TD id='td_data_%s' title='click & drag to scroll; shift+click & drag to zoom'" " width=%d class='tdData'>\n", trackName, imgBox->width); // centerLabel if (imgTrack->hasCenterLabel) { safef(name, sizeof(name), "center_%s", trackName); sliceAndMapDraw(imgBox,imgTrack,stCenter,name,TRUE); 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 tdRight' title='%s%sdrag to reorder'>\n", name,attributeEncode(imgTrack->tdb->longLabel),newLine); else hPrintf(" <TD id='td_%s' class='tdRight'>\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"); jsonTdbSettingsUse(jsonTdbVars); }