src/hg/lib/hui.c 1.190

1.190 2009/04/30 15:55:29 tdreszer
Rewrote score filtering UI and extraWhere to ensure they all play by the same rules.
Index: src/hg/lib/hui.c
===================================================================
RCS file: /projects/compbio/cvsroot/kent/src/hg/lib/hui.c,v
retrieving revision 1.189
retrieving revision 1.190
diff -b -B -U 4 -r1.189 -r1.190
--- src/hg/lib/hui.c	29 Apr 2009 16:27:33 -0000	1.189
+++ src/hg/lib/hui.c	30 Apr 2009 15:55:29 -0000	1.190
@@ -43,9 +43,9 @@
 {
 // Downloads directory if this is ENCODE
 if(trackDbSetting(tdb, "wgEncode") != NULL)
     {
-    printf("<P><A HREF=\"http://%s/goldenPath/%s/%s/%s/\" TARGET=_BLANK>%s</A></P>\n",
+    printf("<P><A HREF=\"http://%s/goldenPath/%s/%s/%s/\" TARGET=ucscDownloads>%s</A></P>\n",
             cfgOptionDefault("downloads.server", "hgdownload.cse.ucsc.edu"),
             trackDbSettingOrDefault(tdb, "origAssembly","hg18"),
             ENCODE_DCC_DOWNLOADS,
             tdb->tableName,name);
@@ -2271,8 +2271,74 @@
     slFreeList(items);
     }
 }
 
+static boolean colonPairToStrings(char * colonPair,char **first,char **second)
+{ // Will set *first and *second to NULL.  Must free any string returned!  No colon: value goes to *first
+if(first)
+    *first =NULL; // default to NULL !
+if(second)
+    *second=NULL;
+if(colonPair != NULL)
+    {
+    if(strchr(colonPair,':'))
+        {
+        if(second)
+            *second = cloneString(strchr(colonPair,':') + 1);
+        if(first)
+            *first = strSwapChar(cloneString(colonPair),':',0);
+        }
+    else if(first)
+        *first = cloneString(colonPair);
+    return (*first != NULL || *second != NULL);
+    }
+return FALSE;
+}
+static boolean colonPairToInts(char * colonPair,int *first,int *second)
+{ // Non-destructive. Only sets values if found. No colon: value goes to *first
+char *a=NULL;
+char *b=NULL;
+if(colonPairToStrings(colonPair,&a,&b))
+    {
+    if(a!=NULL)
+        {
+        if(first)
+            *first = atoi(a);
+        freeMem(a);
+        }
+    if(b!=NULL)
+        {
+        if(second)
+            *second = atoi(b);
+        freeMem(b);
+        }
+    return TRUE;
+    }
+return FALSE;
+}
+static boolean colonPairToDoubles(char * colonPair,double *first,double *second)
+{ // Non-destructive. Only sets values if found. No colon: value goes to *first
+char *a=NULL;
+char *b=NULL;
+if(colonPairToStrings(colonPair,&a,&b))
+    {
+    if(a!=NULL)
+        {
+        if(first)
+            *first = strtod(a,NULL);
+        freeMem(a);
+        }
+    if(b!=NULL)
+        {
+        if(second)
+            *second = strtod(b,NULL);
+        freeMem(b);
+        }
+    return TRUE;
+    }
+return FALSE;
+}
+
 filterBy_t *filterBySetGet(struct trackDb *tdb, struct cart *cart, char *name)
 /* Gets one or more "filterBy" settings (ClosestToHome).  returns NULL if not found */
 {
 filterBy_t *filterBySet = NULL;
@@ -3195,16 +3261,15 @@
 
 cfgEndBox(boxed);
 }
 
-#define  MIN_GRAY_LEVEL  "minGrayLevel"
 
 void scoreGrayLevelCfgUi(struct cart *cart, struct trackDb *tdb, char *prefix, int scoreMax)
 /* If scoreMin has been set, let user select the shade of gray for that score, in case
  * the default is too light to see or darker than necessary. */
 {
 boolean compositeLevel = isNameAtCompositeLevel(tdb,prefix);
-char *scoreMinStr = trackDbSettingClosestToHome(tdb, SCORE_MIN);
+char *scoreMinStr = trackDbSettingClosestToHome(tdb, GRAY_LEVEL_SCORE_MIN);
 if (scoreMinStr != NULL)
     {
     int scoreMin = atoi(scoreMinStr);
     // maxShade=9 taken from hgTracks/simpleTracks.c.  Ignore the 10 in shadesOfGray[10+1] --
@@ -3248,89 +3313,173 @@
     }
 }
 
 static boolean getScoreLimits(struct trackDb *tdb, char *scoreName,char *defaults,char**min,char**max)
-{ // returns TRUE if limits exist and sets the string pointer (because they may be float or int)
+/* returns TRUE if limits exist and sets the string pointer (because they may be float or int)
+   if min or max are set, then they should be freed */
+{
+*min = NULL; // default these outs!
+*max = NULL;
 char scoreLimitName[128];
 safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _LIMITS);
-char *setting = trackDbSettingClosestToHomeOrDefault(tdb, scoreLimitName,defaults);
+char *setting = trackDbSettingClosestToHome(tdb, scoreLimitName);
 if(setting)
     {
-    *min = strSwapChar(cloneString(setting),':',0);
-    *max = cloneString(*min + strlen(*min) + 1);
+    return colonPairToStrings(setting,min,max);
+    }
+else
+    {
+    safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MIN);
+    setting = trackDbSettingClosestToHome(tdb, scoreLimitName);
+    if(setting)
+        *min = cloneString(setting);
+    safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MAX);
+    setting = trackDbSettingClosestToHome(tdb, scoreLimitName);
+    if(setting)
+        *max = cloneString(setting);
     return TRUE;
     }
-    *min = NULL;
-    *max = NULL;
-    return FALSE;
+if(defaults != NULL && (*min == NULL || *max == NULL))
+    {
+    char *minLoc=NULL;
+    char *maxLoc=NULL;
+    if(colonPairToStrings(defaults,&minLoc,&maxLoc))
+        {
+        if(*min == NULL && minLoc != NULL)
+            *min=minLoc;
+        else
+            freeMem(minLoc);
+        if(*max == NULL && maxLoc != NULL)
+            *max=maxLoc;
+        else
+            freeMem(maxLoc);
+        return TRUE;
+        }
+    }
+return FALSE;
+}
+
+static void getScoreIntRangeFromCart(struct cart *cart, struct trackDb *tdb, char *scoreName,
+                                 int *limitMin, int *limitMax,int *min,int *max)
+/* gets an integer score range from the cart, but the limits from trackDb
+   for any of the pointers provided, will return a value found, if found, else it's contents
+   are undisturbed (use NO_VALUE to recognize unavaliable values) */
+{
+char scoreLimitName[128];
+char *deMin=NULL,*deMax=NULL;
+if(limitMin || limitMax)
+    {
+    getScoreLimits(tdb,scoreName,NULL,&deMin,&deMax);
+    if(deMin != NULL)
+        {
+        if(limitMin)
+            *limitMin = atoi(deMin);
+        freeMem(deMin);
+        }
+    if(deMax != NULL)
+        {
+        if(limitMax)
+            *limitMax = atoi(deMax);
+        freeMem(deMax);
+        }
+    }
+
+if(max)
+    {
+    safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MAX);
+    deMax = cartOptionalStringClosestToHome(cart, tdb,FALSE,scoreLimitName);
+    if(deMax != NULL)
+        *max = atoi(deMax);
+    }
+if(min)
+    {
+    safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, (max && deMax? _MIN:"")); // Warning: name changes if max!
+    deMin = cartOptionalStringClosestToHome(cart, tdb,FALSE,scoreLimitName);
+    if(deMin != NULL)
+        *min = atoi(deMin);
+    }
+}
+
+static void getScoreFloatRangeFromCart(struct cart *cart, struct trackDb *tdb, char *scoreName,
+                                   double *limitMin,double *limitMax,double*min,double*max)
+/* gets an double score range from the cart, but the limits from trackDb
+   for any of the pointers provided, will return a value found, if found, else it's contents
+   are undisturbed (use NO_VALUE to recognize unavaliable values) */
+{
+char scoreLimitName[128];
+char *deMin=NULL,*deMax=NULL;
+if(limitMin || limitMax)
+    {
+    getScoreLimits(tdb,scoreName,NULL,&deMin,&deMax);
+    if(deMin != NULL)
+        {
+        if(limitMin)
+            *limitMin = strtod(deMin,NULL);
+        freeMem(deMin);
+        }
+    if(deMax != NULL)
+        {
+        if(limitMax)
+            *limitMax =strtod(deMax,NULL);
+        freeMem(deMax);
+        }
+    }
+
+if(max)
+    {
+    safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MAX);
+    deMax = cartOptionalStringClosestToHome(cart, tdb,FALSE,scoreLimitName);
+    if(deMax != NULL)
+        *max = strtod(deMax,NULL);
+    }
+if(min)
+    {
+    safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MIN); // name is always {filterName}Min
+    deMin = cartOptionalStringClosestToHome(cart, tdb,FALSE,scoreLimitName);
+    if(deMin != NULL)
+        *min = strtod(deMin,NULL);
+    }
 }
 
 void scoreCfgUi(char *db, struct cart *cart, struct trackDb *tdb, char *name, char *title,  int maxScore, boolean boxed)
 /* Put up UI for filtering bed track based on a score */
 {
 char option[256];
-int val=0;
 boolean compositeLevel = isNameAtCompositeLevel(tdb,name);
 
 boolean scoreFilterOk = (trackDbSettingClosestToHome(tdb, NO_SCORE_FILTER) == NULL);
-boolean gotScoreMin = (trackDbSettingClosestToHome(tdb, SCORE_MIN) != NULL);
-if (! (scoreFilterOk || gotScoreMin))
+boolean glvlScoreMin = (trackDbSettingClosestToHome(tdb, GRAY_LEVEL_SCORE_MIN) != NULL);
+if (! (scoreFilterOk || glvlScoreMin))
     return;
 
 cfgBeginBoxAndTitle(boxed, title);
 
 if (scoreFilterOk)
     {
+    int minLimit=0,maxLimit=maxScore,minVal=0,maxVal=maxScore;
+    getScoreIntRangeFromCart(cart,tdb,SCORE_FILTER,&minLimit,&maxLimit,&minVal,&maxVal);
 
     boolean filterByRange = trackDbSettingClosestToHomeOn(tdb, SCORE_FILTER _BY_RANGE);
     if (filterByRange)
         {
-        char *setting = trackDbSettingClosestToHomeOrDefault(tdb, SCORE_FILTER,"0:1000");
-        char *min = strSwapChar(cloneString(setting),':',0);
-        char *max = min + strlen(min) + 1;
-        min = cartUsualStringClosestToHome(cart, tdb, compositeLevel, SCORE_FILTER _MIN, min);
-        max = cartUsualStringClosestToHome(cart, tdb, compositeLevel, SCORE_FILTER _MAX, max);
         puts("<B>Filter score range:  min:</B>");
-        safef(option, sizeof(option), "%s.%s", name, SCORE_FILTER _MIN);
-        val = atoi(min);
-        if( val < 0)
-            val = 0;
-        char *rangeMin=NULL;
-        char *rangeMax=NULL;
-        getScoreLimits(tdb,SCORE_FILTER,"0:1000",&rangeMin,&rangeMax);
-        cgiMakeIntVarInRange(option, val, "Minimum score",0,rangeMin,rangeMax);
-
+        snprintf(option, sizeof(option), "%s.%s", name,SCORE_FILTER _MIN);
+        cgiMakeIntVarWithLimits(option, minVal, "Minimum score",0, minLimit,maxLimit);
         puts("<B>max:</B>");
-        safef(option, sizeof(option), "%s.%s", name, SCORE_FILTER _MAX);
-        val = atoi(max);
-        if( val > 1000)
-            val = 1000;
-        cgiMakeIntVarInRange(option, val, "Maximum score",0,rangeMin,rangeMax);
-        printf("(%s to %s)\n",rangeMin,rangeMax);
-        freeMem(rangeMin);
-        freeMem(rangeMax);
+        snprintf(option, sizeof(option), "%s.%s", name,SCORE_FILTER _MAX);
+        cgiMakeIntVarWithLimits(option, maxVal, "Maximum score",0,minLimit,maxLimit);
+        printf("(%d to %d)\n",minLimit,maxLimit);
         }
     else
         {
-        /* initial value of score theshold is 0, unless
-        * overridden by the scoreFilter setting in the track */
-        char *scoreValString = trackDbSettingClosestToHome(tdb, SCORE_FILTER);
-        int scoreVal = 0;
-        if (scoreValString != NULL)
-        scoreVal = atoi(scoreValString);
-        if( scoreVal < 0)
-            scoreVal = 0;
-        else if(scoreVal > 1000)
-                scoreVal = 1000;
         printf("<b>Show only items with score at or above:</b> ");
         snprintf(option, sizeof(option), "%s.%s", name,SCORE_FILTER);
-        val = cartUsualIntClosestToHome(cart, tdb, compositeLevel, SCORE_FILTER,  scoreVal);
-        cgiMakeIntVarWithLimits(option, val, "Minimum score",0, scoreVal,maxScore);
-        printf("&nbsp;&nbsp;(range: %d&nbsp;to&nbsp;%d)", scoreVal, maxScore);
+        cgiMakeIntVarWithLimits(option, minVal, "Minimum score",0, minLimit,maxLimit);
+        printf("&nbsp;&nbsp;(range: %d to %d)", minLimit, maxLimit);
         }
     }
 
-if (gotScoreMin)
+if (glvlScoreMin)
     scoreGrayLevelCfgUi(cart, tdb, name, maxScore);
 
 /* filter top-scoring N items in track */
 char *scoreCtString = trackDbSettingClosestToHome(tdb, "filterTopScorers");
@@ -3427,88 +3576,110 @@
         *opened = TRUE;
         }
     printf("<TR><TD align='right'><B>%s:</B><TD align='left'>",label);
     char varName[256];
+    char altLabel[256];
+    safef(varName, sizeof(varName), "%s%s", scoreName, _BY_RANGE);
+    boolean filterByRange = trackDbSettingClosestToHomeOn(tdb, varName);
+    double minLimit=NO_VALUE,maxLimit=NO_VALUE;
+    colonPairToDoubles(limitsDefault,&minLimit,&maxLimit);
+    double minVal=minLimit,maxVal=maxLimit;
+    colonPairToDoubles(setting,&minVal,&maxVal);
+    getScoreFloatRangeFromCart(cart,tdb,scoreName,&minLimit,&maxLimit,&minVal,&maxVal);
     safef(varName, sizeof(varName), "%s.%s%s", name, scoreName, _MIN);
-    double val = cartUsualDoubleClosestToHome(cart, tdb, compositeLevel, varName + (strlen(name) + 1), atof(setting));
-    char *rangeMin=NULL;
-    char *rangeMax=NULL;
-    getScoreLimits(tdb, scoreName,limitsDefault,&rangeMin,&rangeMax);
-    cgiMakeDoubleVarInRange(varName,val, label, 0,rangeMin, rangeMax);
-    if(rangeMin && rangeMax)
-        printf("<TD align='left' colspan=3> (%s to %s)",rangeMin, rangeMax);
-    else if(rangeMin)
-        printf("<TD align='left' colspan=3> (minimum %s)",rangeMin);
-    else if(rangeMax)
-        printf("<TD align='left' colspan=3> (maximum %s)",rangeMax);
+    safef(altLabel, sizeof(altLabel), "%s%s", (filterByRange?"Minimum ":""), label);
+    cgiMakeDoubleVarWithLimits(varName,minVal, altLabel, 0,minLimit, maxLimit);
+    if(filterByRange) // TODO: Test this range stuff which is not yet used
+        {
+        printf("<TD align='left'>to<TD align='left'>");
+        safef(varName, sizeof(varName), "%s.%s%s", name, scoreName, _MAX);
+        safef(altLabel, sizeof(altLabel), "%s%s", (filterByRange?"Maximum ":""), label);
+        cgiMakeDoubleVarWithLimits(varName,maxVal, altLabel, 0,minLimit, maxLimit);
+        }
+    safef(altLabel, sizeof(altLabel), "%s", (filterByRange?"": "colspan=3"));
+    if(minLimit != NO_VALUE && maxLimit != NO_VALUE)
+        printf("<TD align='left'%s> (%g to %g)",altLabel,minLimit, maxLimit);
+    else if(minLimit != NO_VALUE)
+        printf("<TD align='left'%s> (minimum %g)",altLabel,minLimit);
+    else if(maxLimit != NO_VALUE)
+        printf("<TD align='left'%s> (maximum %g)",altLabel,maxLimit);
     else
-        printf("<TD align='left' colspan=3>&nbsp;");
+        printf("<TD align='left'%s",altLabel);
     puts("</TR>");
-    freeMem(rangeMin);
-    freeMem(rangeMax);
     return TRUE;
     }
 return FALSE;
 }
 struct dyString *dyAddFilterAsInt(struct cart *cart, struct trackDb *tdb,
        struct dyString *extraWhere,char *filter,char *defaultLimits, char*field, boolean *and)
 /* creates the where clause condition to support numeric int filter range.
    Filters are expected to follow
-        {fiterName}: trackDb min[:max] values
-        {filterName}Min: cart variable
-        {filterName}Max: cart variable Optional (and considered non-existent if -99)
+        {fiterName}: trackDb min or min:max - default value(s);
+        {filterName}Min or {filterName}: min (user supplied) cart variable;
+        {filterName}Max: max (user supplied) cart variable;
         {filterName}Limits: trackDb allowed range "0:1000" Optional
-   The 'and' param allows stringing multiple where clauses together */
-{
+           uses:{filterName}Min: old trackDb value if {filterName}Limits not found
+                {filterName}Max: old trackDb value if {filterName}Limits not found
+                defaultLimits: function param if no tdb limits settings found)
+   The 'and' param and dyString in/out allows stringing multiple where clauses together */
+{
+char filterLimitName[64];
+if(sameWord(filter,NO_SCORE_FILTER))
+    safef(filterLimitName, sizeof(filterLimitName), "%s", NO_SCORE_FILTER);
+else
+    safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter,_NO);
+if(trackDbSettingClosestToHome(tdb, filterLimitName) != NULL)
+    return extraWhere;
+
 char *setting = trackDbSettingClosestToHome(tdb, filter);
-if(setting)
+if(setting || sameWord(filter,NO_SCORE_FILTER))
     {
     boolean invalid = FALSE;
-    char filterLimitName[64];
     int minValueTdb = 0,maxValueTdb = NO_VALUE;
-    if(strchr(setting,':') != NULL)
-        {
-        char *minStr = strSwapChar(cloneString(setting),':',0);
-        minValueTdb = atoi(minStr);
-        maxValueTdb = atoi(minStr + strlen(minStr) + 1);
-        freeMem(minStr);
-        }
-    else
-        minValueTdb = atoi(setting);
-    safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _LIMITS);
-    setting = trackDbSettingClosestToHomeOrDefault(tdb, filterLimitName,defaultLimits);
-    safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _MAX);
-    int max = cartUsualIntClosestToHome(cart,tdb,FALSE,filterLimitName, maxValueTdb);
-    safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _MIN);
-    int min = cartUsualIntClosestToHome(cart,tdb,FALSE,filterLimitName, minValueTdb);
-    if(setting)
-        {
-        char *minStr = strSwapChar(cloneString(setting),':',0);
-        char *maxStr = minStr + strlen(minStr) + 1;
-        if((min != minValueTdb && (min < atoi(minStr) || min > atoi(maxStr)) && min != 0)
-        || (max != maxValueTdb && (max < atoi(minStr) || max > atoi(maxStr))))
+    colonPairToInts(setting,&minValueTdb,&maxValueTdb);
+    int minLimit=NO_VALUE,maxLimit=NO_VALUE,min=minValueTdb,max=maxValueTdb;
+    colonPairToInts(defaultLimits,&minLimit,&maxLimit);
+    getScoreIntRangeFromCart(cart,tdb,filter,&minLimit,&maxLimit,&min,&max);
+    if(minLimit != NO_VALUE || maxLimit != NO_VALUE)
+        {
+        // assume tdb default values within range! (don't give user errors that have no consequence)
+        if((min != minValueTdb && ((minLimit != NO_VALUE && min < minLimit)
+                                || (maxLimit != NO_VALUE && min > maxLimit)))
+        || (max != maxValueTdb && ((minLimit != NO_VALUE && max < minLimit)
+                                || (maxLimit != NO_VALUE && max > maxLimit))))
             {
             invalid = TRUE;
+            char value[64];
             if(max == NO_VALUE) // min only is allowed, but max only is not
-                warn("invalid filter by %s: %d is outside of range (%s to %s) for track %s", field, min, minStr, maxStr, tdb->tableName);
+                safef(value, sizeof(value), "entered minimum (%d)", min);
             else
-                warn("invalid filter by %s: min:%d and max:%d is outside of range (%s to %s) for track %s", field, min, max, minStr, maxStr, tdb->tableName);
+                safef(value, sizeof(value), "entered range (min:%d and max:%d)", min, max);
+            char limits[64];
+            if(minLimit != NO_VALUE && maxLimit != NO_VALUE)
+                safef(limits, sizeof(limits), "violates limits (%d to %d)", minLimit, maxLimit);
+            else if(minLimit != NO_VALUE)
+                safef(limits, sizeof(limits), "violates lower limit (%d)", minLimit);
+            else //if(maxLimit != NO_VALUE)
+                safef(limits, sizeof(limits), "violates uppper limit (%d)", maxLimit);
+            warn("invalid filter by %s: %s %s for track %s", field, value, limits, tdb->tableName);
             }
-        freeMem(minStr);
         }
     // else no default limits!
     if(invalid)
         {
+        safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, (max!=NO_VALUE?_MIN:""));
         cartRemoveVariableClosestToHome(cart,tdb,FALSE,filterLimitName);
-        safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _MAX);  // Remake name
+        safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _MAX);
         cartRemoveVariableClosestToHome(cart,tdb,FALSE,filterLimitName);
         }
-    else if(min != 0) // Assumes 0 is no filter!
+    else if((min != 0 && min != NO_VALUE) || max != NO_VALUE) // Assumes min==0 is no filter!
         {
-        if(max == NO_VALUE)
-            dyStringPrintf(extraWhere, "%s(%s >= %d)", (*and?" and ":""),field,min);
-        else
+        if((min != 0 && min != NO_VALUE) && max != NO_VALUE)
             dyStringPrintf(extraWhere, "%s(%s BETWEEN %d and %d)", (*and?" and ":""),field,min,max);
+        else if(min != 0 && min != NO_VALUE)
+            dyStringPrintf(extraWhere, "%s(%s >= %d)", (*and?" and ":""),field,min);
+        else //if(max != NO_VALUE)
+            dyStringPrintf(extraWhere, "%s(%s <= %d)", (*and?" and ":""),field,max);
         *and=TRUE;
         }
     }
 return extraWhere;
@@ -3517,47 +3688,64 @@
 struct dyString *dyAddFilterAsDouble(struct cart *cart, struct trackDb *tdb,
        struct dyString *extraWhere,char *filter,char *defaultLimits, char*field, boolean *and)
 /* creates the where clause condition to support numeric double filters.
    Filters are expected to follow
-        {fiterName}: trackDb min value;
-        {filterName}Min: cart variable;
+        {fiterName}: trackDb min or min:max - default value(s);
+        {filterName}Min or {filterName}: min (user supplied) cart variable;
+        {filterName}Max: max (user supplied) cart variable;
         {filterName}Limits: trackDb allowed range "0.0:10.0" Optional
-   The and param allows stringing multiple where clauses together */
+            uses:  defaultLimits: function param if no tdb limits settings found)
+   The 'and' param and dyString in/out allows stringing multiple where clauses together */
 {
 char *setting = trackDbSettingClosestToHome(tdb, filter);
 if(setting)
     {
-    double minValueTdb = atof(setting);
     boolean invalid = FALSE;
-    char filterLimitName[64];
-    safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _LIMITS);
-    setting = trackDbSettingClosestToHomeOrDefault(tdb, filterLimitName,defaultLimits);
-    safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _MIN);
-    double value = cartUsualDoubleClosestToHome(cart,tdb,FALSE,filterLimitName, minValueTdb);
-    if(value != minValueTdb && value != 0) // assume tdb min value is within range! (don't give user errors that have no consequence)
-        {
-        if(setting)
-            {
-            char *minStr = strSwapChar(cloneString(setting),':',0);
-            char *maxStr = minStr + strlen(minStr) + 1;
-            if(value < atof(minStr) || value > atof(maxStr))
-                {
-                invalid = TRUE;
-                warn("invalid filter by %s: %lf is outside of range (%s to %s) for track %s", field, value, minStr, maxStr, tdb->tableName);
-                }
-            freeMem(minStr);
-            }
-        else if (value < 0)  // No range specified: must be non-negative
+    double minValueTdb = 0,maxValueTdb = NO_VALUE;
+    colonPairToDoubles(setting,&minValueTdb,&maxValueTdb);
+    double minLimit=NO_VALUE,maxLimit=NO_VALUE,min=minValueTdb,max=maxValueTdb;
+    colonPairToDoubles(defaultLimits,&minLimit,&maxLimit);
+    getScoreFloatRangeFromCart(cart,tdb,filter,&minLimit,&maxLimit,&min,&max);
+    if((int)minLimit != NO_VALUE || (int)maxLimit != NO_VALUE)
+        {
+        // assume tdb default values within range! (don't give user errors that have no consequence)
+        if((min != minValueTdb && (((int)minLimit != NO_VALUE && min < minLimit)
+                                || ((int)maxLimit != NO_VALUE && min > maxLimit)))
+        || (max != maxValueTdb && (((int)minLimit != NO_VALUE && max < minLimit)
+                                || ((int)maxLimit != NO_VALUE && max > maxLimit))))
             {
             invalid = TRUE;
-            warn("invalid filter by %s: %lf is less than 0 for track %s", field,value, tdb->tableName);
+            char value[64];
+            if((int)max == NO_VALUE) // min only is allowed, but max only is not
+                safef(value, sizeof(value), "entered minimum (%g)", min);
+            else
+                safef(value, sizeof(value), "entered range (min:%g and max:%g)", min, max);
+            char limits[64];
+            if((int)minLimit != NO_VALUE && (int)maxLimit != NO_VALUE)
+                safef(limits, sizeof(limits), "violates limits (%g to %g)", minLimit, maxLimit);
+            else if((int)minLimit != NO_VALUE)
+                safef(limits, sizeof(limits), "violates lower limit (%g)", minLimit);
+            else //if((int)maxLimit != NO_VALUE)
+                safef(limits, sizeof(limits), "violates uppper limit (%g)", maxLimit);
+            warn("invalid filter by %s: %s %s for track %s", field, value, limits, tdb->tableName);
             }
         }
     if(invalid)
+        {
+        char filterLimitName[64];
+        safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _MIN);
         cartRemoveVariableClosestToHome(cart,tdb,FALSE,filterLimitName);
-    else if(value != 0) // Assume zero is no filter in all cases
+        safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _MAX);
+        cartRemoveVariableClosestToHome(cart,tdb,FALSE,filterLimitName);
+        }
+    else if((min != 0 && (int)min != NO_VALUE) || (int)max != NO_VALUE) // Assumes min==0 is no filter!
         {
-        dyStringPrintf(extraWhere, "%s((%s >= %f) or (%s = -1))", (*and?" and ":""),field,value,field);
+        if((min != 0 && (int)min != NO_VALUE) && (int)max != NO_VALUE)
+            dyStringPrintf(extraWhere, "%s(%s BETWEEN %g and %g)", (*and?" and ":""),field,min,max);
+        else if(min != 0 && (int)min != NO_VALUE)
+            dyStringPrintf(extraWhere, "%s(%s >= %g)", (*and?" and ":""),field,min);
+        else //if((int)max != NO_VALUE)
+            dyStringPrintf(extraWhere, "%s(%s <= %g)", (*and?" and ":""),field,max);
         *and=TRUE;
         }
     }
 return extraWhere;
@@ -3581,38 +3769,29 @@
         puts("<TABLE>");
         opened = TRUE;
         }
     char varName[256];
-    char *min=setting;
-    char *max = strrchr(setting,':');
-    if(max != NULL)
-        {
-        max += 1;
-        min = strSwapChar(cloneString(setting),':',0);
+    int minLimit=0,maxLimit=1000,minVal=0,maxVal=NO_VALUE;
+    colonPairToInts(setting,&minVal,&maxVal);
+    getScoreIntRangeFromCart(cart,tdb,SCORE_FILTER,&minLimit,&maxLimit,&minVal,&maxVal);
+    if(maxVal != NO_VALUE)
         puts("<TR><TD align='right'><B>Score range: min:</B><TD align='left'>");
-        }
     else
         puts("<TR><TD align='right'><B>Minimum score:</B><TD align='left'>");
-    safef(varName, sizeof(varName), "%s.%s%s", name, SCORE_FILTER,_MIN);
-    int val = cartUsualIntClosestToHome(cart, tdb, compositeLevel, varName + (strlen(name) + 1), atoi(min));
-    char *rangeMin=NULL;
-    char *rangeMax=NULL;
-    getScoreLimits(tdb, SCORE_FILTER,"0:1000",&rangeMin,&rangeMax);
-
-    cgiMakeIntVarInRange(varName, val, "Minimum score", 0, rangeMin, rangeMax);
-    if(max != NULL)
-        {
-        puts("<TD align='right'><B>max:</B><TD align='left'>");
+    safef(varName, sizeof(varName), "%s%s", SCORE_FILTER, _BY_RANGE);
+    boolean filterByRange = trackDbSettingClosestToHomeOn(tdb, varName);
+    safef(varName, sizeof(varName), "%s.%s%s", name, SCORE_FILTER, (filterByRange?_MIN:""));
+    cgiMakeIntVarWithLimits(varName, minVal, "Minimum score", 0, minLimit, maxLimit);
+    if(filterByRange)
+        {
+        if(maxVal == NO_VALUE)
+            maxVal = maxLimit;
+        puts("<TD align='right'>to<TD align='left'>");
         safef(varName, sizeof(varName), "%s.%s%s", name, SCORE_FILTER,_MAX);
-        val = cartUsualIntClosestToHome(cart, tdb, compositeLevel, varName + (strlen(name) + 1), atoi(max));
-        cgiMakeIntVarInRange(varName, val, "Maximum score", 0, rangeMin, rangeMax);
-        freeMem(min);
-        }
-    printf("<TD align='left' colspan=3> (%s to %s)",rangeMin,rangeMax);
-    freeMem(rangeMin);
-    freeMem(rangeMax);
-    puts("</TR>");
-    if(trackDbSettingClosestToHome(tdb, SCORE_MIN) != NULL)
+        cgiMakeIntVarWithLimits(varName, maxVal, "Maximum score", 0, minLimit, maxLimit);
+        }
+    printf("<TD align='left'%s> (%d to %d)",(filterByRange?"":" colspan=3"),minLimit, maxLimit);
+    if(trackDbSettingClosestToHome(tdb, GRAY_LEVEL_SCORE_MIN) != NULL)
         {
         printf("<TR><TD align='right'colspan=5>");
         scoreGrayLevelCfgUi(cart, tdb, name, 1000);
         puts("</TR>");