text: /* 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(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));
  }
  
  
  