1eb615411838193436b51e2a8a177b865544d85d chmalee Fri Jun 25 14:33:38 2021 -0700 Fix bug in trackDb include processing, when a bigDataUrl is referenced relative to it's trackDb file, and the trackDb file is referenced via an include statement, CGIs were trying to expand bigDataUrl relative to the top level trackDb instead of it's correct parent, refs #27767 diff --git src/hg/lib/trackDbCustom.c src/hg/lib/trackDbCustom.c index 396b9ed..b7bfd9b 100644 --- src/hg/lib/trackDbCustom.c +++ src/hg/lib/trackDbCustom.c @@ -1,1608 +1,1617 @@ /* trackDbCustom - custom (not autoSQL generated) code for working * with trackDb. This code is concerned with making the trackDb * MySQL table out of the trackDb.ra files. */ /* Copyright (C) 2014 The Regents of the University of California * See README in this or parent directory for licensing information. */ #include <sys/mman.h> #include "common.h" #include "linefile.h" #include "jksql.h" #include "trackDb.h" #include "hdb.h" #include "hui.h" #include "ra.h" #include "hash.h" #include "net.h" #include "sqlNum.h" #include "obscure.h" #include "hgMaf.h" #include "customTrack.h" #include "regexHelper.h" #include "fieldedTable.h" #include "tagRepo.h" +#include "htmlPage.h" struct trackDb *trackDbNew() /* Allocate a new trackDb with just very minimal stuff filled in. */ { struct trackDb *tdb; AllocVar(tdb); tdb->canPack = 2; /* Unknown value. */ return tdb; } int trackDbCmp(const void *va, const void *vb) /* Compare to sort based on priority; use shortLabel as secondary sort key. * Note: parallel code to hgTracks.c:tgCmpPriority */ { const struct trackDb *a = *((struct trackDb **)va); const struct trackDb *b = *((struct trackDb **)vb); float dif = a->priority - b->priority; if (dif < 0) return -1; else if (dif == 0.0) return strcasecmp(a->shortLabel, b->shortLabel); else return 1; } int trackDbCmpShortLabel(const void *va, const void *vb) /* Sort track by shortLabel. */ { const struct trackDb *a = *((struct trackDb **)va); const struct trackDb *b = *((struct trackDb **)vb); return strcmp(a->shortLabel, b->shortLabel); } void parseColor(char *text, unsigned char *r, unsigned char *g, unsigned char *b) /* Turn comma-separated string of three numbers into three * color components. */ { char dupe[64]; safecpy(dupe, sizeof(dupe), text); char *words[4]; int wordCount; wordCount = chopString(dupe, ", \t", words, ArraySize(words)); if (wordCount != 3) errAbort("Expecting 3 comma separated values in %s.", text); *r = atoi(words[0]); *g = atoi(words[1]); *b = atoi(words[2]); } static unsigned char parseVisibility(char *value) /* Parse a visibility value */ { if (sameString(value, "hide") || sameString(value, "0")) return tvHide; else if (sameString(value, "dense") || sameString(value, "1")) return tvDense; else if (sameString(value, "full") || sameString(value, "2") || sameString(value, "show")) return tvFull; else if (sameString(value, "pack") || sameString(value, "3")) return tvPack; else if (sameString(value, "squish") || sameString(value, "4")) return tvSquish; else errAbort("Unknown visibility %s ", value); return tvHide; /* never reached */ } static void parseTrackLine(struct trackDb *bt, char *value, struct lineFile *lf) /* parse the track line */ { char *val2 = cloneString(value); bt->track = nextWord(&val2); // check for override option if (val2 != NULL) { val2 = trimSpaces(val2); if (!sameString(val2, "override")) errAbort("invalid track line: %s:%d: track %s", lf->fileName, lf->lineIx, value); bt->overrides = hashNew(0); } } static void trackDbAddRelease(struct trackDb *bt, char *releaseTag) /* Add release tag */ { hashAdd(bt->settingsHash, "release", cloneString(releaseTag)); } static void trackDbAddInfo(struct trackDb *bt, char *var, char *value, struct lineFile *lf) /* Add info from a variable/value pair to browser table. */ { +char *subbedUrl = NULL; if (sameString(var, "track")) parseTrackLine(bt, value, lf); + +// Since we may have gotten here from an include statement, we +// need to expand the url relative to the include statement, and +// not later where the url will be expanded relative to the parent +// of the include statement +if (trackSettingIsFile(var)) + subbedUrl = htmlExpandUrl(lf->fileName, value); if (bt->settingsHash == NULL) bt->settingsHash = hashNew(7); if (bt->viewHash == NULL) bt->viewHash = hashNew(7); -char *storeValue = cloneString(value); +char *storeValue = cloneString(subbedUrl != NULL ? subbedUrl : value); // squirrel away views if (startsWith("subGroup", var)) { char *ptr = strchr(value, ' '); if (ptr) *ptr = 0; hashAdd(bt->viewHash, value, storeValue); if (ptr) *ptr = ' '; } hashAdd(bt->settingsHash, var, storeValue); if (bt->overrides != NULL) hashAdd(bt->overrides, var, NULL); } //not needed? int bedDetailSizeFromType(char *type) /* parse bedSize from type line for bedDetail, assume 4 if none */ { int ret = 4; /* minimal expected */ char *words[3]; int wordCount = chopLine(cloneString(type), words); if (wordCount > 1) ret = atoi(words[1]) - 2; /* trackDb has field count, we want bedSize */ return ret; } void trackDbFieldsFromSettings(struct trackDb *bt) /* Update trackDb fields from settings hash */ { char *shortLabel = trackDbSetting(bt, "shortLabel"); if (shortLabel != NULL) bt->shortLabel = cloneString(shortLabel); char *longLabel = trackDbSetting(bt, "longLabel"); if (longLabel != NULL) bt->longLabel = cloneString(longLabel); char *priority = trackDbSetting(bt, "priority"); if (priority != NULL) bt->priority = atof(priority); char *url = trackDbSetting(bt, "url"); if (url != NULL) bt->url = cloneString(url); char *visibility = trackDbSetting(bt, "visibility"); if (visibility != NULL) bt->visibility = parseVisibility(visibility); char *color = trackDbSetting(bt, "color"); if (color != NULL) parseColor(color, &bt->colorR, &bt->colorG, &bt->colorB); char *altColor = trackDbSetting(bt, "altColor"); if (altColor != NULL) parseColor(altColor, &bt->altColorR, &bt->altColorG, &bt->altColorB); char *type = trackDbSetting(bt, "type"); if (type != NULL) bt->type = cloneString(type); if (trackDbSetting(bt, "spectrum") != NULL || trackDbSetting(bt, "useScore") != NULL) bt->useScore = TRUE; char *canPack = trackDbSetting(bt, "canPack"); if (canPack != NULL) bt->canPack = !(sameString(canPack, "0") || sameString(canPack, "off")); char *chromosomes = trackDbSetting(bt, "chromosomes"); if (chromosomes != NULL) sqlStringDynamicArray(chromosomes, &bt->restrictList, &bt->restrictCount); if (trackDbSetting(bt, "private") != NULL) bt->private = TRUE; char *grp = trackDbSetting(bt, "group"); if (grp != NULL) bt->grp = cloneString(grp); } static void replaceStr(char **varPtr, char *val) /** replace string in varPtr with val */ { freeMem(*varPtr); *varPtr = cloneString(val); } static void overrideField(struct trackDb *td, struct trackDb *overTd, char *var) /* Update override one field from override. */ { if (sameString(var, "track") || sameString(var, "release")) return; char *val = hashMustFindVal(overTd->settingsHash, var); struct hashEl *hel = hashStore(td->settingsHash, var); replaceStr((char**)&hel->val, val); } void trackDbOverride(struct trackDb *td, struct trackDb *overTd) /* apply an trackOverride trackDb entry to a trackDb entry */ { assert(overTd->overrides != NULL); struct hashEl *hel; struct hashCookie hc = hashFirst(overTd->overrides); while ((hel = hashNext(&hc)) != NULL) { overrideField(td, overTd, hel->name); } } static boolean packableType(char *type) /* Return TRUE if we can pack this type. */ { char *t = cloneString(type); char *s = firstWordInLine(t); boolean canPack = (sameString("psl", s) || sameString("chain", s) || sameString("bed", s) || sameString("genePred", s) || sameString("makeItems", s) || sameString("expRatio", s) || sameString("wigMaf", s) || sameString("factorSource", s) || sameString("bed5FloatScore", s) || sameString("bed6FloatScore", s) || sameString("altGraphX", s) || sameString("bam", s) || sameString("bedDetail", s) || sameString("bed8Attrs", s) || sameString("gvf", s) || sameString("vcfTabix", s) || sameString("vcf", s) || sameString("pgSnp", s) || sameString("narrowPeak", s) || sameString("broadPeak", s) || sameString("bigLolly", s) || sameString("peptideMapping", s) || sameString("barChart", s) || sameString("interact", s) || (!startsWithWord("bigWig", s) && startsWith("big", s)) ); freeMem(t); return canPack; } void trackDbPolish(struct trackDb *bt) /* Fill in missing values with defaults. */ { if (bt->shortLabel == NULL) bt->shortLabel = cloneString(bt->track); if (bt->longLabel == NULL) bt->longLabel = cloneString(bt->shortLabel); if (bt->altColorR == 0 && bt->altColorG == 0 && bt->altColorB == 0) { bt->altColorR = (255+bt->colorR)/2; bt->altColorG = (255+bt->colorG)/2; bt->altColorB = (255+bt->colorB)/2; } if (bt->type == NULL) bt->type = cloneString(""); if (bt->priority == 0) bt->priority = 100.0; if (bt->url == NULL) bt->url = cloneString(""); if (bt->html == NULL) bt->html = cloneString(""); if (bt->grp == NULL) bt->grp = cloneString("x"); if (bt->canPack == 2) bt->canPack = packableType(bt->type); if (bt->settings == NULL) bt->settings = cloneString(""); } char *trackDbInclude(char *raFile, char *line, char **releaseTag) /* Get include filename from trackDb line. Return NULL if line doesn't contain include */ { static char incFile[256]; char *file; if (startsWith("include", line)) { splitPath(raFile, incFile, NULL, NULL); nextWord(&line); file = nextQuotedWord(&line); strcat(incFile, file); *releaseTag = nextWord(&line); return cloneString(incFile); } else return NULL; } struct trackDb *trackDbFromOpenRa(struct lineFile *lf, char *releaseTag, struct dyString *incFiles) /* Load track info from ra file already opened as lineFile into list. If releaseTag is * non-NULL then only load tracks that mesh with release. If incFiles is not-NULL, put * list of included files in there. */ { char *raFile = lf->fileName; char *line, *word; struct trackDb *btList = NULL, *bt; boolean done = FALSE; char *incFile; for (;;) { /* Seek to next line that starts with 'track' */ for (;;) { char *subRelease; if (!lineFileNextFull(lf, &line, NULL, NULL, NULL)) { // NOTE: lineFileNextFull joins continuation lines done = TRUE; break; } line = skipLeadingSpaces(line); if (startsWithWord("track", line)) { lineFileReuseFull(lf); // NOTE: only works with previous lineFileNextFull call break; } else if ((incFile = trackDbInclude(raFile, line, &subRelease)) != NULL) { if (subRelease) trackDbCheckValidRelease(subRelease); if (releaseTag && subRelease && !sameString(subRelease, releaseTag)) errAbort("Include with release %s inside include with release %s line %d of %s", subRelease, releaseTag, lf->lineIx, lf->fileName); struct trackDb *incTdb = trackDbFromRa(incFile, subRelease, incFiles); btList = slCat(btList, incTdb); if (incFiles) dyStringPrintf(incFiles, "%s\n", incFile); } } if (done) break; /* Allocate track structure and fill it in until next blank line. */ bt = trackDbNew(); slAddHead(&btList, bt); for (;;) { /* Break at blank line or EOF. */ if (!lineFileNextFull(lf, &line, NULL, NULL, NULL)) // NOTE: joins continuation lines break; line = skipLeadingSpaces(line); if (line == NULL || line[0] == 0) break; /* Skip comments. */ if (line[0] == '#') continue; /* Parse out first word and decide what to do. */ word = nextWord(&line); if (line == NULL) errAbort("No value for %s line %d of %s", word, lf->lineIx, lf->fileName); line = trimSpaces(line); trackDbUpdateOldTag(&word, &line); if (releaseTag && sameString(word, "release")) { if (differentString(releaseTag, line)) errAbort("Release tag %s in stanza with include override %s, line %d of %s", line, releaseTag, lf->lineIx, lf->fileName); } else trackDbAddInfo(bt, word, line, lf); } if (releaseTag) trackDbAddRelease(bt, releaseTag); } slReverse(&btList); return btList; } boolean trackDbCheckValidRelease(char *tag) /* check to make sure release tag is valid */ { char *words[5]; int count = chopString(cloneString(tag), ",", words, ArraySize(words)); if (count > 3) return FALSE; int ii; for(ii=0; ii < count; ii++) if (!sameString(words[ii], "alpha") && !sameString(words[ii], "beta") && !sameString(words[ii], "public")) return FALSE; return TRUE; } struct trackDb *trackDbFromRa(char *raFile, char *releaseTag, struct dyString *incFiles) /* Load track info from ra file into list. If releaseTag is non-NULL * then only load tracks that mesh with release. if incFiles is non-null, * add included file names to it.*/ { struct lineFile *lf = udcWrapShortLineFile(raFile, NULL, 16*1024*1024); struct trackDb *tdbList = trackDbFromOpenRa(lf, releaseTag, incFiles); lineFileClose(&lf); return tdbList; } struct hash *trackDbHashSettings(struct trackDb *tdb) /* Force trackDb to hash up it's settings. Usually this is just * done on demand. Returns settings hash. */ { if (tdb->settingsHash == NULL) tdb->settingsHash = trackDbSettingsFromString(tdb, tdb->settings); return tdb->settingsHash; } struct hash *trackDbSettingsFromString(struct trackDb *tdb, char *string) /* Return hash of key/value pairs from string. Differs * from raFromString in that it passes the key/val * pair through the backwards compatability routines. */ { char *dupe = cloneString(string); char *s = dupe, *lineEnd; struct hash *hash = newHash(7); char *key, *val; for (;;) { s = skipLeadingSpaces(s); if (s == NULL || s[0] == 0) break; lineEnd = strchr(s, '\n'); if (lineEnd != NULL) *lineEnd++ = 0; key = nextWord(&s); val = skipLeadingSpaces(s); trackDbUpdateOldTag(&key, &val); s = lineEnd; val = lmCloneString(hash->lm, val); hashAdd(hash, key, val); if (tdb && startsWith("subGroup", key)) { char *storeValue = cloneString(val); char *ptr = strchr(val, ' '); if (ptr) *ptr = 0; if (tdb->viewHash == NULL) tdb->viewHash = newHash(5); hashAdd(tdb->viewHash, val, storeValue); if (ptr) *ptr = ' '; } } freeMem(dupe); return hash; } char *trackDbViewSetting(struct trackDb *tdb, char *name) /* Return view setting from tdb, but *not* any of it's parents. */ { if (tdb == NULL) errAbort("Program error: null tdb passed to trackDbSetting."); if (tdb->viewHash == NULL) return NULL; return hashFindVal(tdb->viewHash, name); } char *trackDbLocalSetting(struct trackDb *tdb, char *name) /* Return setting from tdb, but *not* any of it's parents. */ { if (tdb == NULL) errAbort("Program error: null tdb passed to trackDbSetting."); if (tdb->settingsHash == NULL) tdb->settingsHash = trackDbSettingsFromString(tdb, tdb->settings); return hashFindVal(tdb->settingsHash, name); } struct slName *trackDbLocalSettingsWildMatch(struct trackDb *tdb, char *expression) // Return local settings that match expression else NULL. In alpha order. { if (tdb == NULL) errAbort("Program error: null tdb passed to trackDbSetting."); if (tdb->settingsHash == NULL) tdb->settingsHash = trackDbSettingsFromString(tdb, tdb->settings); struct slName *slFoundVars = NULL; struct hashCookie brownie = hashFirst(tdb->settingsHash); struct hashEl* el = NULL; while ((el = hashNext(&brownie)) != NULL) { if (wildMatch(expression, el->name)) slNameAddHead(&slFoundVars, el->name); } if (slFoundVars != NULL) slNameSort(&slFoundVars); return slFoundVars; } struct slName *trackDbSettingsWildMatch(struct trackDb *tdb, char *expression) // Return settings in tdb tree that match expression else NULL. In alpha order, no duplicates. { struct trackDb *generation; struct slName *slFoundVars = NULL; for (generation = tdb; generation != NULL; generation = generation->parent) { struct slName *slFoundHere = trackDbLocalSettingsWildMatch(generation,expression); if (slFoundHere != NULL) { if (slFoundVars == NULL) slFoundVars = slFoundHere; else { struct slName *one = NULL; while ((one = slPopHead(&slFoundHere)) != NULL) { slNameStore(&slFoundVars, one->name); // Will only store if it is not already found! slNameFree(&one); // This means closest to home will work } } } } if (slFoundVars != NULL) slNameSort(&slFoundVars); return slFoundVars; } boolean trackDbSettingOn(struct trackDb *tdb, char *name) /* Return true if a tdb setting is "on" "true" or "enabled". */ { char *setting = trackDbSetting(tdb,name); return (setting && ( sameWord(setting,"on") || sameWord(setting,"true") || sameWord(setting,"enabled"))); } char *trackDbRequiredSetting(struct trackDb *tdb, char *name) /* Return setting string or squawk and die. */ { char *ret = trackDbSetting(tdb, name); if (ret == NULL) errAbort("Missing required '%s' setting in %s track", name, tdb->track); return ret; } char *trackDbSettingOrDefault(struct trackDb *tdb, char *name, char *defaultVal) /* Return setting string, or defaultVal if none exists */ { char *val = trackDbSetting(tdb, name); return (val == NULL ? defaultVal : val); } float trackDbFloatSettingOrDefault(struct trackDb *tdb, char *name, float defaultVal) /* Return setting, convert to a float, or defaultVal if none exists */ { char *val = trackDbSetting(tdb, name); if (val == NULL) return defaultVal; else return sqlFloat(trimSpaces(val)); } struct hashEl *trackDbSettingsLike(struct trackDb *tdb, char *wildStr) /* Return a list of settings whose names match wildStr (may contain wildcard * characters). Free the result with hashElFreeList. */ { struct hashEl *allSettings = hashElListHash(tdb->settingsHash); struct hashEl *matchingSettings = NULL; struct hashEl *hel = allSettings; while (hel != NULL) { struct hashEl *next = hel->next; if (wildMatch(wildStr, hel->name)) { slAddHead(&matchingSettings, hel); } else hashElFree(&hel); hel = next; } return matchingSettings; } char *maybeSkipHubPrefix(char *track) { if (!startsWith("hub_", track)) return track; char *nextUnderBar = strchr(track + sizeof "hub_", '_'); if (nextUnderBar) return nextUnderBar + 1; return track; } void trackDbSuperMarkup(struct trackDb *tdbList) /* Set trackDb from superTrack setting */ { struct trackDb *tdb; struct hash *superHash = hashNew(0); char *setting = NULL; char *words[3]; int wordCt = 0; /* find supertracks, setup their settings */ for (tdb = tdbList; tdb != NULL; tdb = tdb->next) { setting = trackDbLocalSetting(tdb, "superTrack"); if (!setting) continue; wordCt = chopLine(cloneString(setting), words); if (sameWord("on", words[0])) { if (!hashLookup(superHash, tdb->track)) { hashAdd(superHash, maybeSkipHubPrefix(tdb->track), tdb); tdbMarkAsSuperTrack(tdb); if ((wordCt > 1) && sameString("show", words[1])) tdb->isShow = TRUE; } } freeMem(words[0]); } /* adjust settings on supertrack members after verifying they have * a supertrack configured in this trackDb */ for (tdb = tdbList; tdb != NULL; tdb = tdb->next) { if (tdbIsSuperTrack(tdb) || tdb->parent != NULL) continue; setting = trackDbLocalSetting(tdb, "parent"); if (!setting) setting = trackDbLocalSetting(tdb, "superTrack"); // Old style if (!setting) continue; wordCt = chopLine(cloneString(setting), words); assert(differentString("on", words[0])); // already weeded out "superTrack on" char *parentName = maybeSkipHubPrefix(words[0]); tdb->parent = hashFindVal(superHash, parentName); if (tdb->parent) { tdbMarkAsSuperTrackChild(tdb); tdb->parentName = cloneString(parentName); if (wordCt > 1) tdb->visibility = max(0, hTvFromStringNoAbort(words[1])); } freeMem(words[0]); } hashFree(&superHash); } char *trackDbOrigAssembly(struct trackDb *tdb) /* return setting from trackDb, if any */ { return (trackDbSetting(tdb, "origAssembly")); } void trackDbPrintOrigAssembly(struct trackDb *tdb, char *database) /* Print lift information from trackDb, if any */ { char *origAssembly = trackDbOrigAssembly(tdb); if (origAssembly) { if (differentString(origAssembly, database)) { printf("<B>Data coordinates converted via <A TARGET=_BLANK " "HREF=\"../goldenPath/help/hgTracksHelp.html#Liftover\">liftOver</A> from:</B> "); char *freeze = hFreezeFromDb(origAssembly); if (freeze == NULL) printf("%s<BR>\n", origAssembly); else if (stringIn(origAssembly, freeze)) printf("%s<BR>\n", freeze); else printf("%s (%s)<BR>\n", freeze, origAssembly); } } } eCfgType cfgTypeFromTdb(struct trackDb *tdb, boolean warnIfNecessary) /* determine what kind of track specific configuration is needed, warn if not multi-view compatible */ { eCfgType cType = cfgNone; char *type = tdb->type; assert(type != NULL); if(startsWith("wigMaf", type) || startsWith("bigMaf", type)) cType = cfgWigMaf; else if(startsWith("wig", type) || startsWith("mathWig", type) || startsWith("bigWig", type) || startsWith("bedGraph", type) || startsWith("bamWig", type)) cType = cfgWig; else if(startsWith("bigGenePred", type)) cType = cfgGenePred; else if(startsWith("chain",type) || startsWith("bigChain",type)) cType = cfgChain; else if (startsWith("psl", type) || startsWith("bigPsl", type)) cType = cfgPsl; else if (sameWord("barChart", type) || sameWord("bigBarChart", type)) cType = cfgBarChart; else if (sameWord("interact", type) || sameWord("bigInteract", type)) cType = cfgInteract; else if (startsWith("bigLolly", type)) cType = cfgLollipop; else if (sameWord("bigDbSnp", type)) cType = cfgBigDbSnp; else if(startsWith("longTabix", type)) cType = cfgLong; else if (startsWith("netAlign", type) || startsWith("net", tdb->track)) // SPECIAL CASE from hgTrackUi which might not be needed cType = cfgNetAlign; else if(sameWord("bed5FloatScore", type) || sameWord("bed5FloatScoreWithFdr",type)) { if (bedScoreHasCfgUi(tdb)) cType = cfgBedScore; } else if (encodePeakHasCfgUi(tdb)) cType = cfgPeak; else if (startsWithWord("genePred",type) && !startsWith("encodeGencodeRaceFrags", tdb->track)) // SPECIAL CASE should fix in trackDb! cType = cfgGenePred; else if (sameWord("bedLogR",type) || sameWord("peptideMapping", type)) cType = cfgBedScore; // This is a catch-all for big* types that are not special-cased above. big* types after this // point are assumed to be flavors of bigBed. else if (startsWith("bed ", type) || startsWith("big", type) || startsWith("bedDetail", type)) { if (trackDbSetting(tdb, "bedFilter") != NULL) cType = cfgBedFilt; else if (!startsWith("big", type) || startsWith("bigBed", type)) { char *words[3]; int wordCount = chopLine(cloneString( type), words); if (( ((wordCount > 1) && (atoi(words[1]) >= 5)) || trackDbSetting(tdb, "scoreMin") != NULL) && // Historically needed 'bed n .' but encode didn't follow bed n . ( (wordCount >= 3) || (!tdbIsTrackUiTopLevel(tdb) && trackDbSettingClosestToHome(tdb, "wgEncode")))) { cType = cfgBedScore; if (!bedScoreHasCfgUi(tdb)) cType = cfgNone; // FIXME: UGLY SPECIAL CASE should be handled in trackDb! else if (startsWith("encodeGencodeIntron", tdb->track)) cType = cfgNone; } } else cType = cfgBedScore; } else if (startsWith("bam", type)) cType = cfgBam; else if (sameWord("vcfPhasedTrio", type) || sameWord("vcfTabix",type) || sameWord("vcf", type)) cType = cfgVcf; else if (sameWord("halSnake",type)) cType = cfgSnake; else if (sameWord("hic", type)) cType = cfgHic; // TODO: Only these are configurable so far if (cType == cfgNone && warnIfNecessary) { if (!startsWith("bed ", type) && !startsWith("bedDetail", type) && !startsWith("bigBed", type) && !startsWith("gvf", type) && !sameString("pgSnp", type) && subgroupFind(tdb, "view", NULL)) warn("Track type \"%s\" is not yet supported in multi-view composites for %s.",type,tdb->track); } return cType; } int configurableByAjax(struct trackDb *tdb, eCfgType cfgTypeIfKnown) // Is this track configurable by right-click popup, or in hgTrackUi subCfg? // returns 0 = no; <0=explicitly blocked; >0=allowed and will be cfgType if determined { if (tdbIsMultiTrackSubtrack(tdb)) return cfgNone; // multitrack subtracks are never allowed to be separately configured. int ctPopup = (int)cfgTypeIfKnown; if (ctPopup <= cfgNone) ctPopup = (int)cfgTypeFromTdb(tdb,FALSE); if (ctPopup <= cfgNone && !tdbIsSubtrack(tdb)) // subtracks must receive CfgType! ctPopup = cfgUndetermined; // cfgTypeFromTdb() does not work for every case. if (ctPopup > cfgNone) { if (regexMatch(tdb->track, "^snp[0-9]+") // Special cases to be removed || regexMatch(tdb->track, "^cons[0-9]+way") // (matches logic in json setup in imageV2.c) || startsWith("hapmapSnps", tdb->track) || startsWith("hapmapAlleles", tdb->track) || trackDbSettingBlocksConfiguration(tdb,TRUE)) ctPopup *= -1; } return ctPopup; } boolean trackDbNoInheritField(char *field) /* Suppress inheritance of specific fields. * NOTE: make more efficient if more of these are added */ { return (sameString(field, "pennantIcon")); } char *trackDbSetting(struct trackDb *tdb, char *name) /* Look for a trackDb setting from lowest level on up chain of parents, * excepting fields specifically defined as not inheritable */ { struct trackDb *generation; char *trackSetting = NULL; for (generation = tdb; generation != NULL; generation = generation->parent) { trackSetting = trackDbLocalSetting(generation, name); if (trackSetting != NULL || trackDbNoInheritField(name)) break; } return trackSetting; } void trackDbAddSetting(struct trackDb *bt, char *name, char *val) { /* Add a setting to a trackDb rec */ hashAdd(trackDbHashSettings(bt), name, cloneString(val)); } char *trackDbSettingByView(struct trackDb *tdb, char *name) /* For a subtrack of a multiview composite, get a setting stored in the view or any other * ancestor. */ { if (tdb->parent == NULL) return NULL; return trackDbSetting(tdb->parent, name); } char *trackDbSettingClosestToHomeOrDefault(struct trackDb *tdb, char *name, char *defaultVal) /* Look for a trackDb setting (or default) from lowest level on up chain of parents. */ { char *trackSetting = trackDbSetting(tdb,name); if (trackSetting == NULL) trackSetting = defaultVal; return trackSetting; } boolean trackDbSettingClosestToHomeOn(struct trackDb *tdb, char *name) /* Return true if a tdb setting closest to home is "on" "true" or "enabled". */ { char *setting = trackDbSetting(tdb,name); return (setting && ( sameWord(setting,"on") || sameWord(setting,"true") || sameWord(setting,"enabled") || atoi(setting) != 0)); } struct trackDb *subTdbFind(struct trackDb *parent,char *childName) /* Return child tdb if it exists in parent. */ { if (parent == NULL) return NULL; struct trackDb *found = NULL; struct slRef *tdbRef, *tdbRefList = trackDbListGetRefsToDescendants(parent->subtracks); for (tdbRef = tdbRefList; tdbRef != NULL; tdbRef = tdbRef->next) { struct trackDb *tdb = tdbRef->val; if (sameString(tdb->track, childName)) { found = tdb; break; } } slFreeList(&tdbRefList); return found; } struct trackDb *tdbFindOrCreate(char *db,struct trackDb *parent,char *track) /* Find or creates the tdb for this track. May return NULL. */ { struct trackDb *tdb = NULL; if (parent != NULL) { if(sameString(parent->track, track)) tdb = parent; else if(consWiggleFind(db,parent,track) != NULL) tdb = parent; else tdb = subTdbFind(parent,track); } if (tdb == NULL && db != NULL) { struct sqlConnection *conn = hAllocConn(db); tdb = hMaybeTrackInfo(conn, track); hFreeConn(&conn); } return tdb; } #ifdef OMIT // NOTE: This may not be needed. struct trackDb *tdbFillInAncestry(char *db,struct trackDb *tdbChild) /* Finds parents and fills them in. Does not find siblings, however! */ { assert(tdbChild != NULL); struct trackDb *tdb = tdbChild; struct sqlConnection *conn = NULL; for (;tdb->parent != NULL;tdb = tdb->parent) ; // advance to highest parent already known // If track with no tdbParent has a parent setting then fill it in. for (;tdb->parent == NULL; tdb = tdb->parent) { char *parentTrack = trackDbLocalSetting(tdb,"parent"); if (parentTrack == NULL) break; if (conn == NULL) conn = hAllocConn(db); // Now there are 2 versions of this child! And what to do about views? tdb->parent = hMaybeTrackInfo(conn, parentTrack); printf("tdbFillInAncestry(%s): has %d children.",parentTrack,slCount(tdb->parent->subtracks)); } if (conn != NULL) hFreeConn(&conn); return tdb; } #endif///def OMIT boolean tdbIsView(struct trackDb *tdb,char **viewName) // Is this tdb a view? Will fill viewName if provided { if (tdbIsCompositeView(tdb)) { if (viewName) { *viewName = trackDbLocalSetting(tdb, "view"); if (*viewName == NULL) errAbort("track '%s' appears to be a view but does not have a " "<a href=\"../goldenpath/help/trackDb/trackDbHub.html#view\" target=_blank" ">view setting</a>.", tdb->track); } return TRUE; } return FALSE; } char *tdbGetViewName(struct trackDb *tdb) // returns NULL the view name for view or child track (do not free) { char *view = NULL; if (tdbIsComposite(tdb)) return NULL; else if (tdbIsCompositeChild(tdb) && subgroupFind(tdb,"view",&view)) return view; else if (tdbIsView(tdb,&view)) return view; return NULL; } struct trackDb *trackDbLinkUpGenerations(struct trackDb *tdbList) /* Convert a list to a forest - filling in parent and subtrack pointers. * The exact topology of the forest is a little complex due to the * fact there are two "inheritance" systems - the superTrack system * and the subTrack system. In the superTrack system (which is on it's * way out) the superTrack's themselves have the tag: * superTrack on * and the children of superTracks have the tag: * superTrack parentName * In the subTrack system the parents have the tag: * compositeTrack on * and the children have the tag: * parent parentName * In this routine the subtracks are removed from the list, and stuffed into * the subtracks lists of their parents. The highest level parents stay on * the list. There can be multiple levels of inheritance. * For the supertracks the _parents_ are removed from the list. The only * reference to them in the returned forest is that they are in the parent * field of their children. The parents of supertracks have no subtracks * after this call currently. */ { struct trackDb *forest = NULL; struct hash *trackHash = hashNew(0); struct trackDb *tdb, *next; for (tdb = tdbList; tdb != NULL; tdb = tdb->next) hashAdd(trackHash, tdb->track, tdb); /* Do superTrack inheritance. This involves setting up the parent pointers to superTracks, * but removing the superTracks themselves from the list. */ struct trackDb *superlessList = NULL; trackDbSuperMarkup(tdbList); for (tdb = tdbList; tdb != NULL; tdb = next) { next = tdb->next; if (tdbIsSuperTrack(tdb)) tdb->next = NULL; else slAddHead(&superlessList, tdb); } /* Do subtrack hierarchy - filling in parent and subtracks fields. */ for (tdb = superlessList; tdb != NULL; tdb = next) { next = tdb->next; char *subtrackSetting = trackDbLocalSetting(tdb, "parent"); if (subtrackSetting != NULL && !tdbIsSuperTrackChild(tdb)) // superChildren cannot be in both subtracks list AND tdbList { char *parentName = cloneFirstWord(subtrackSetting); struct trackDb *parent = hashFindVal(trackHash, parentName); if (parent != NULL) { if (trackDbLocalSetting(tdb, "container")) { errAbort("Composite track '%s' cannot have child track '%s'," " which is a container multiWig.", parentName, tdb->track); } slAddHead(&parent->subtracks, tdb); // composite/multiWig children are ONLY subtracks tdb->parent = parent; } else { errAbort("Parent track %s of child %s doesn't exist", parentName, tdb->track); } freez(&parentName); } else { slAddHead(&forest, tdb); } } hashFree(&trackHash); return forest; } void trackDbPrioritizeContainerItems(struct trackDb *tdbList) /* Set priorities in containers if they have no priorities already set priorities are based upon 'sortOrder' setting or else shortLabel */ { int countOfSortedContainers = 0; // Walk through tdbs looking for containers struct trackDb *tdbContainer; for (tdbContainer = tdbList; tdbContainer != NULL; tdbContainer = tdbContainer->next) { if (tdbContainer->subtracks == NULL) continue; sortOrder_t *sortOrder = sortOrderGet(NULL,tdbContainer); boolean needsSorting = TRUE; // default float firstPriority = -1.0; sortableTdbItem *item,*itemsToSort = NULL; struct slRef *child, *childList = trackDbListGetRefsToDescendantLeaves(tdbContainer->subtracks); // Walk through tdbs looking for items contained for (child = childList; child != NULL; child = child->next) { struct trackDb *tdbItem = child->val; if( needsSorting && sortOrder == NULL ) // do we? { if( firstPriority == -1.0) // all 0 or all the same value firstPriority = tdbItem->priority; if(firstPriority != tdbItem->priority && (int)(tdbItem->priority + 0.9) > 0) { needsSorting = FALSE; break; } } // create an Item item = sortableTdbItemCreate(tdbItem,sortOrder); if(item != NULL) slAddHead(&itemsToSort, item); else { verbose(1,"Error: '%s' missing shortLabels or sortOrder setting is inconsistent.\n",tdbContainer->track); needsSorting = FALSE; sortableTdbItemCreate(tdbItem,sortOrder); break; } } // Does this container need to be sorted? if(needsSorting && slCount(itemsToSort)) { verbose(2,"Sorting '%s' with %d items\n",tdbContainer->track,slCount(itemsToSort)); sortTdbItemsAndUpdatePriorities(&itemsToSort); countOfSortedContainers++; } // cleanup sortOrderFree(&sortOrder); sortableTdbItemsFree(&itemsToSort); } } void trackDbAddTableField(struct trackDb *tdbList) /* Add table field by looking it up in settings. */ { struct trackDb *tdb; for (tdb = tdbList; tdb != NULL; tdb = tdb->next) { char *table = trackDbLocalSetting(tdb, "table"); if (table != NULL) tdb->table = cloneString(table); else tdb->table = cloneString(tdb->track); } } void rGetRefsToDescendants(struct slRef **pList, struct trackDb *tdbList) /* Add all member of tdbList, and all of their children to pList recursively. */ /* Convert a list to a forest - filling in parent and subtrack pointers. * The exact topology of the forest is a little complex due to the * fact there are two "inheritance" systems - the superTrack system * and the subTrack system. In the superTrack system (which is on it's * way out) the superTrack's themselves have the tag: * superTrack on * and the children of superTracks have the tag: * superTrack parentName * In the subTrack system the parents have the tag: * compositeTrack on * and the children have the tag: * subTrack parentName * In this routine the subtracks are removed from the list, and stuffed into * the subtracks lists of their parents. The highest level parents stay on * the list. There can be multiple levels of inheritance. * For the supertracks the _parents_ are removed from the list. The only * reference to them in the returned forest is that they are in the parent * field of their children. The parents of supertracks have no subtracks * after this call currently. */ { struct trackDb *tdb; for (tdb = tdbList; tdb != NULL; tdb = tdb->next) { refAdd(pList, tdb); rGetRefsToDescendants(pList, tdb->subtracks); } } struct slRef *trackDbListGetRefsToDescendants(struct trackDb *tdbList) /* Return reference list to everything in forest. Do slFreeList when done. */ { struct slRef *refList = NULL; rGetRefsToDescendants(&refList, tdbList); slReverse(&refList); return refList; } int trackDbCountDescendants(struct trackDb *tdb) /* Count the number of tracks in subtracks list and their subtracks too . */ { struct slRef *tdbRefList = trackDbListGetRefsToDescendants(tdb->subtracks); int result = slCount(tdbRefList); slFreeList(&tdbRefList); return result; } void rGetRefsToDescendantLeaves(struct slRef **pList, struct trackDb *tdbList) /* Add all leaf members of trackList, and any leaf descendants to pList recursively. */ { struct trackDb *tdb; for (tdb = tdbList; tdb != NULL; tdb = tdb->next) { if (tdb->subtracks) rGetRefsToDescendantLeaves(pList, tdb->subtracks); else refAdd(pList, tdb); } } struct slRef *trackDbListGetRefsToDescendantLeaves(struct trackDb *tdbList) /* Return reference list all leaves in forest. Do slFreeList when done. */ { struct slRef *refList = NULL; rGetRefsToDescendantLeaves(&refList, tdbList); slReverse(&refList); return refList; } int trackDbCountDescendantLeaves(struct trackDb *tdb) /* Count the number of leaves in children list and their children. */ { struct slRef *leafRefs = trackDbListGetRefsToDescendantLeaves(tdb->subtracks); int result = slCount(leafRefs); slFreeList(&leafRefs); return result; } int trackDbRefCmp(const void *va, const void *vb) /* Do trackDbCmp on list of references as opposed to actual trackDbs. */ { const struct slRef *aRef = *((struct slRef **)va); const struct slRef *bRef = *((struct slRef **)vb); struct trackDb *a = aRef->val, *b = bRef->val; return trackDbCmp(&a, &b); } struct trackDb *trackDbTopLevelSelfOrParent(struct trackDb *tdb) /* Look for a parent who is a composite or multiTrack track and return that. Failing that * just return self. */ { struct trackDb *parent = tdbGetContainer(tdb); if (parent != NULL) return parent; else return tdb; } boolean trackDbUpdateOldTag(char **pTag, char **pVal) /* Look for obscolete tags and update them to new format. Return TRUE if any update * is done. Will allocate fresh memory for new tag and val if updated. */ { char *tag = *pTag; char *val = *pVal; boolean updated = FALSE; if (sameString(tag, "subTrack")) { tag = "parent"; updated = TRUE; } #ifdef SOON else if (sameString(tag, "compositeTrack")) { tag = "container"; val = "composite"; updated = TRUE; } else if (sameString(tag, "superTrack")) { if (sameWord(val, "on")) { tag = "container"; val = "folder"; } else { tag = "parent"; } updated = TRUE; } #endif /* SOON */ if (updated) { *pTag = cloneString(tag); *pVal = cloneString(val); } return updated; } static struct tdbExtras *tdbExtrasNew() // Return a new empty tdbExtras { struct tdbExtras *extras; AllocVar(extras); // Note no need for extras = AllocVar(extras) // Initialize any values that need an "empty" state extras->fourState = TDB_EXTRAS_EMPTY_STATE; // I guess it is 5 state! // pointers are NULL and booleans are FALSE by default return extras; } void tdbExtrasFree(struct tdbExtras **pTdbExtras) // Frees the tdbExtras structure { // Developer, add intelligent routines to free structures // NOTE: For now just leak contents, because complex structs would also leak freez(pTdbExtras); } static struct tdbExtras *tdbExtrasGet(struct trackDb *tdb) // Returns tdbExtras struct, initializing if needed. { if (tdb->tdbExtras == NULL) // Temporarily add this back in because Angie see asserts popping. tdb->tdbExtras = tdbExtrasNew(); return tdb->tdbExtras; } int tdbExtrasFourState(struct trackDb *tdb) // Returns subtrack four state if known, else TDB_EXTRAS_EMPTY_STATE { struct tdbExtras *extras = tdb->tdbExtras; if (extras) return extras->fourState; return TDB_EXTRAS_EMPTY_STATE; } void tdbExtrasFourStateSet(struct trackDb *tdb,int fourState) // Sets subtrack four state { tdbExtrasGet(tdb)->fourState = fourState; } boolean tdbExtrasReshapedComposite(struct trackDb *tdb) // Returns TRUE if composite has been declared as reshaped, else FALSE. { struct tdbExtras *extras = tdb->tdbExtras; if (extras) return extras->reshapedComposite; return FALSE; } void tdbExtrasReshapedCompositeSet(struct trackDb *tdb) // Declares that the composite has been reshaped. { tdbExtrasGet(tdb)->reshapedComposite = TRUE; } struct mdbObj *tdbExtrasMdb(struct trackDb *tdb) // Returns mdb metadata if already known, else NULL { struct tdbExtras *extras = tdb->tdbExtras; if (extras) return extras->mdb; return NULL; } void tdbExtrasMdbSet(struct trackDb *tdb,struct mdbObj *mdb) // Sets the mdb metadata structure for later retrieval. { tdbExtrasGet(tdb)->mdb = mdb; } struct _membersForAll *tdbExtrasMembersForAll(struct trackDb *tdb) // Returns composite view/dimension members for all, else NULL. { struct tdbExtras *extras = tdb->tdbExtras; if (extras) return extras->membersForAll; return NULL; } void tdbExtrasMembersForAllSet(struct trackDb *tdb, struct _membersForAll *membersForAll) // Sets the composite view/dimensions members for all for later retrieval. { tdbExtrasGet(tdb)->membersForAll = membersForAll; } members_t *tdbExtrasMembers(struct trackDb *tdb, char *groupNameOrTag) // Returns subtrack members if already known, else NULL { struct tdbExtras *extras = tdbExtrasGet(tdb); if (extras->membersHash == NULL) extras->membersHash = newHash(5); return (members_t *)hashFindVal(extras->membersHash, groupNameOrTag); } void tdbExtrasMembersSet(struct trackDb *tdb, char *groupNameOrTag, members_t *members) // Sets the subtrack members for later retrieval. { hashAdd(tdbExtrasGet(tdb)->membersHash, groupNameOrTag, members); } struct _membership *tdbExtrasMembership(struct trackDb *tdb) // Returns subtrack membership if already known, else NULL { struct tdbExtras *extras = tdb->tdbExtras; if (extras) return extras->membership; return tdbExtrasGet(tdb)->membership; } void tdbExtrasMembershipSet(struct trackDb *tdb,struct _membership *membership) // Sets the subtrack membership for later retrieval. { tdbExtrasGet(tdb)->membership = membership; } char *tdbBigFileName(struct sqlConnection *conn, struct trackDb *tdb) // Return file name associated with bigWig. Do a freeMem on returned string when done. { char *ret = NULL; char *fileName = trackDbSetting(tdb, "bigDataUrl"); // always takes precedence if (fileName != NULL) ret = cloneString(fileName); else if (conn != NULL) { char query[256]; sqlSafef(query, sizeof(query), "select fileName from %s", tdb->table); ret = sqlQuickString(conn, query); } // replace /gbdb if needed char *rewriteRet = hReplaceGbdb(ret); freeMem(ret); return rewriteRet; } static void rTdbTreeAllowPack(struct trackDb *tdb) // Force this tdb and all children to allow pack/squish { tdb->canPack = TRUE; struct trackDb *childTdb = tdb->subtracks; for ( ;childTdb!=NULL;childTdb=childTdb->next) rTdbTreeAllowPack(childTdb); } boolean rTdbTreeCanPack(struct trackDb *tdb) // Trees can pack as all or none, since they can share vis. { if (tdb->canPack == FALSE) { // If a single child of a composite can pack, then the entire composite can if (tdbIsComposite(tdb) || tdbIsCompositeView(tdb)) { struct trackDb *childTdb = tdb->subtracks; for ( ;childTdb!=NULL;childTdb=childTdb->next) { if (rTdbTreeCanPack(childTdb)) { tdb->canPack = TRUE; break; } } } // At the composite level if one was found then set the whole tree. if (tdb->canPack && tdbIsComposite(tdb)) rTdbTreeAllowPack(tdb); } return tdb->canPack; } void tdbSetCartVisibility(struct trackDb *tdb, struct cart *cart, char *vis) { // Set visibility in the cart. Handles all the complications necessary for subtracks. char buf[512]; cartSetString(cart, tdb->track, vis); if (tdbIsSubtrack(tdb)) { safef(buf,sizeof buf, "%s_sel", tdb->track); cartSetString(cart, buf, "1"); // Will reshape composite struct trackDb *composite = tdbGetComposite(tdb); if (composite && tdbIsSuperTrackChild(composite)) { safef(buf,sizeof buf, "%s_sel", composite->track); cartSetString(cart, buf, "1"); // Will reshape supertrack } } else if (tdbIsSuperTrackChild(tdb)) // solo track { safef(buf,sizeof buf, "%s_sel", tdb->track); cartSetString(cart, buf, "1"); // Will reshape supertrack } } boolean trackDbSettingBlocksConfiguration(struct trackDb *tdb, boolean onlyAjax) // Configuration dialogs may be explicitly blocked in tracDb settings { if (SETTING_IS_OFF(trackDbSettingClosestToHome(tdb, "configurable"))) return TRUE; // never configurable return (onlyAjax && SETTING_IS_OFF(trackDbSettingClosestToHome(tdb,"configureByPopup"))); } struct slPair *tabSepMetaPairs(char *tabSepMeta, struct trackDb *tdb, char *metaTag) { // If there's no file, there's no data. if (tabSepMeta == NULL) return NULL; // If the trackDb entry doesn't have a foreign key, there's no data. if (metaTag == NULL) return NULL; static char *cachedTableName = NULL; static struct hash *cachedHash = NULL; static struct fieldedTable *cachedTable = NULL; // Cache this table because there's a good chance we'll get called again with it. if ((cachedTableName == NULL) || differentString(tabSepMeta, cachedTableName)) { char *requiredFields[] = {"meta"}; cachedTable = fieldedTableFromTabFile(tabSepMeta, NULL, requiredFields, sizeof requiredFields / sizeof (char *)); cachedHash = fieldedTableUniqueIndex(cachedTable, requiredFields[0]); cachedTableName = cloneString(tabSepMeta); } // Look for this tag in the metadata. struct fieldedRow *fr = hashFindVal(cachedHash, metaTag); if (fr == NULL) return NULL; int ii; struct slPair *pairList = NULL; for(ii=0; ii < cachedTable->fieldCount; ii++) { char *fieldName = cachedTable->fields[ii]; char *fieldVal = fr->row[ii]; if (!isEmpty(fieldVal)) slAddHead(&pairList, slPairNew(fieldName, cloneString(fieldVal))); } slReverse(&pairList); return pairList; } int cmpPairAlpha(const void *e1, const void *e2) /* used with slSort to sort slPairs alphabetically */ { const struct slPair *a = *((struct slPair **)e1); const struct slPair *b = *((struct slPair **)e2); return strcmp(a->name, b->name); } static struct slPair *convertNameValueString(char *string) /* Convert a string composed of name=value pairs separated by white space. */ { char *clone = cloneString(string); int count = chopByWhiteRespectDoubleQuotes(clone,NULL,0); char **words = needMem(sizeof(char *) * count); count = chopByWhiteRespectDoubleQuotes(clone,words,count); if (count < 1 || words[0] == NULL) { errAbort("This is not formatted var=val pairs:\n\t%s\n",string); } int ix; struct slPair *pairList = NULL, *pair; for (ix = 0;ix<count;ix++) { if (*words[ix] == '#') break; if (strchr(words[ix], '=') == NULL) // treat this the same as "var=" { pair = slPairNew(words[ix], NULL); } else { char *name = cloneNextWordByDelimiter(&(words[ix]),'='); char *value = cloneString(words[ix]); pair = slPairNew(name, value); } slAddHead(&pairList, pair); } slSort(&pairList, cmpPairAlpha); return pairList; } struct slPair *trackDbMetaPairs(struct trackDb *tdb) /* Read in metadata given a trackDb entry. This routine understands the three ways * that metadata can be represented in a trackDb stanza: "metadata" lines per stanza, * or a tab-separated or tagStorm file with a foreign key specified by the "meta" tag. */ { char *metaTag = trackDbSetting(tdb, "meta"); if (metaTag != NULL) { char *tabSepMeta = trackDbSetting(tdb, "metaTab"); if (tabSepMeta) return tabSepMetaPairs(tabSepMeta, tdb, metaTag); char *tagStormFile = trackDbSetting(tdb, "metaDb"); if (tagStormFile) return tagRepoPairs(tagStormFile, "meta", metaTag); } char *metadataInTdb = trackDbSetting(tdb, "metadata"); if (metadataInTdb) return convertNameValueString(metadataInTdb); return NULL; } boolean trackSettingIsFile(char *setting) /* Returns TRUE if setting found in trackDb stanza is a file setting that * would benefit from directory $D substitution among other things - looks for * settings that ends in "Url" and a few others. */ { return endsWith(setting, "Url") || sameString(setting, "bigDataIndex") || sameString(setting, "frames") || sameString(setting, "summary") || sameString(setting, "searchTrix"); } char *labelAsFilteredNumber(char *label, unsigned numOut) /* add text to label to indicate filter is active */ { char buffer[2048]; safef(buffer, sizeof buffer, " (%d items filtered)", numOut); return catTwoStrings(label, buffer); } char *labelAsFiltered(char *label) /* add text to label to indicate filter is active */ { #define FILTER_ACTIVATED " (filter activated)" if (stringIn(FILTER_ACTIVATED, label)) return label; return (catTwoStrings(label, FILTER_ACTIVATED)); }