4b7050d8e3bce0273a6012e16587db4d8a75e127
braney
  Wed Aug 29 14:57:27 2018 -0700
add filtering in lists refs #20227

diff --git src/hg/lib/hui.c src/hg/lib/hui.c
index b9a30e2..8f6d815 100644
--- src/hg/lib/hui.c
+++ src/hg/lib/hui.c
@@ -40,30 +40,31 @@
 #include "makeItemsItem.h"
 #include "bedDetail.h"
 #include "pgSnp.h"
 #include "memgfx.h"
 #include "trackHub.h"
 #include "gtexUi.h"
 #include "genbank.h"
 #include "htmlPage.h"
 #include "longRange.h"
 #include "barChartUi.h"
 #include "interactUi.h"
 #include "interact.h"
 #include "customComposite.h"
 #include "trackVersion.h"
 #include "hubConnect.h"
+#include "bigBedFilter.h"
 
 #define SMALLBUF 256
 #define MAX_SUBGROUP 9
 #define ADD_BUTTON_LABEL        "add"
 #define CLEAR_BUTTON_LABEL      "clear"
 #define JBUFSIZE 2048
 
 
 #define DEF_BUTTON "<IMG id=\"btn_%s\" src=\"../images/%s\" alt=\"%s\">\n"
 #define DEF_BUTTON_JS "setCheckBoxesThatContain('%s',true,false,'%s','','%s');" \
 	       "setCheckBoxesThatContain('%s',false,false,'%s','_defOff','%s');" 
 #define DEFAULT_BUTTON(nameOrId,anc,beg,contains) \
     printf(DEF_BUTTON,(anc),"defaults_sm.png","default"); \
     safef(id, sizeof id, "btn_%s", (anc)); \
     jsOnEventByIdF("click", id, DEF_BUTTON_JS,(nameOrId),(beg),(contains),(nameOrId),(beg),(contains)); 
@@ -3624,31 +3625,31 @@
             strSwapChar(chipper,'_',' '); // Title does not have underscores
             }
         else if (filterBy->valueAndLabel)
             errAbort("filterBy values either all have labels in form of value|label "
                      "or none do.");
         }
     }
 }
 
 filterBy_t *buildFilterBy(struct trackDb *tdb, struct cart *cart, struct asObject *as, char *filterName)
 /* Build a filterBy_t structure from a <column>FilterValues statement. */
 {
 char *setting = trackDbSetting(tdb, filterName);
 char *value = cartUsualStringClosestToHome(cart, tdb, FALSE, filterName, setting);
 char *field = cloneString(filterName);
-int ix = strlen(field) - strlen("FilterValues");
+int ix = strlen(field) - strlen(FILTER_VALUES_NAME);
 assert(ix > 0);
 field[ix] = '\0';
 
 filterBy_t *filterBy;
 AllocVar(filterBy);
 filterBy->column = field;
 filterBy->title = field; ///  title should come from AS file, or trackDb variable
 struct asColumn *asCol = asColumnFind(as, field);
 if (asCol != NULL)
     filterBy->title = asCol->comment;
 filterBy->useIndex = FALSE;
 filterBy->slValues = slNameListFromComma(value);
 chopUpValues(filterBy);
 if (cart != NULL)
     {
@@ -3677,31 +3678,31 @@
 struct asObject *as = asForTdb(NULL, tdb);
 filterBy_t *filterByList = NULL, *filter;
 struct slName *fieldFilter;
 while ((fieldFilter = slPopHead(&filterValues)) != NULL)
     {
     if ((filter = buildFilterBy(tdb, cart, as, fieldFilter->name)) != NULL)
         slAddHead(&filterByList, filter);
     }
 return filterByList;
 }
 
 filterBy_t *filterBySetGetGuts(struct trackDb *tdb, struct cart *cart, char *name, char *subName, char *settingName)
 // Gets one or more "filterBy" settings (ClosestToHome).  returns NULL if not found
 {
 // first check to see if this tdb is using "new" FilterValues cart variables
-struct slName *filterValues = trackDbSettingsWildMatch(tdb, "*FilterValues");
+struct slName *filterValues = trackDbSettingsWildMatch(tdb, FILTER_VALUES_WILDCARD);
 if (filterValues)
     return filterByValues(tdb, cart, filterValues);
 
 filterBy_t *filterBySet = NULL;
 char *setting = trackDbSettingClosestToHome(tdb, settingName);
 if(setting == NULL)
     return NULL;
 if ( name == NULL )
     name = tdb->track;
 
 setting = cloneString(setting);
 char *filters[10];
 // multiple filterBys are delimited by space but spaces inside filter can be protected "by quotes"
 int filterCount = chopByWhiteRespectDoubleQuotes(setting, filters, ArraySize(filters));
 int ix;
@@ -3950,40 +3951,60 @@
 else
     printf("<B>%s items by:</B> (select multiple categories and items - %s)"
 	   "<TABLE cellpadding=3><TR valign='top'>\n",filterTypeTitle,FILTERBY_HELP_LINK);
 
 filterBy_t *filterBy = NULL;
 if (cartOptionalString(cart, "ajax") == NULL)
     {
     webIncludeResourceFile("ui.dropdownchecklist.css");
     jsIncludeFile("ui.dropdownchecklist.js",NULL);
     jsIncludeFile("ddcl.js",NULL);
     }
 
 int ix=0;
 for(filterBy = filterBySet;filterBy != NULL; filterBy = filterBy->next, ix++)
     {
+    char settingString[4096];
+    safef(settingString, sizeof settingString, "%s%s", filterBy->column, FILTER_TYPE_NAME);
+    char *setting = cartOrTdbString(cart, tdb, settingString, NULL);
+    boolean isMultiple = (setting != NULL) && (sameString(setting, FILTERBY_MULTIPLE) ||sameString(setting, FILTERBY_MULTIPLE_LIST_OR) ||sameString(setting, FILTERBY_MULTIPLE_LIST_AND));
+
     puts("<TD>");
+    char selectStatement[4096];
+    if (isMultiple)
+        safef(selectStatement, sizeof selectStatement, " (select multiple items - %s)", FILTERBY_HELP_LINK);
+    else
+        selectStatement[0] = 0;
     if(count == 1)
-	printf("<B>%s by %s</B> (select multiple items - %s)",filterTypeTitle,filterBy->title,FILTERBY_HELP_LINK);
+	printf("<B>%s by %s</B>%s",filterTypeTitle,filterBy->title,selectStatement);
     else
 	printf("<B>%s</B>",filterBy->title);
     printf("<BR>\n");
 
+    if (isMultiple)
+        {
+        char cartSettingString[4096];
+        safef(cartSettingString, sizeof cartSettingString, "%s.%s", tdb->track, settingString);
+        printf("<b>Match if  ");
+        cgiMakeRadioButton(cartSettingString, FILTERBY_MULTIPLE_LIST_AND, sameString(setting, FILTERBY_MULTIPLE_LIST_AND));
+        printf(" all ");
+        cgiMakeRadioButton(cartSettingString, FILTERBY_MULTIPLE_LIST_OR, sameString(setting, FILTERBY_MULTIPLE_LIST_OR));
+        printf(" one or more match</b> ");
+        }
     // TODO: columnCount (Number of filterBoxes per row) should be configurable through tdb setting
-    #define FILTER_BY_FORMAT "<SELECT id='%s%d' name='%s' multiple style='display: none; font-size:.9em;' class='filterBy'><BR>\n"
-    printf(FILTER_BY_FORMAT,selectIdPrefix,ix,filterBy->htmlName);
+    #define FILTER_BY_FORMAT "<SELECT id='%s%d' name='%s' %s style='display: none; font-size:.9em;' class='filterBy'><BR>\n"
+    printf(FILTER_BY_FORMAT,selectIdPrefix,ix,filterBy->htmlName, isMultiple ? "multiple" : "");
 
     // value is always "All", even if label is different, to simplify javascript code
     printf("<OPTION%s value=\"All\">%s</OPTION>\n", (filterByAllChosen(filterBy)?" SELECTED":""), allLabel);
     struct slName *slValue;
 
     int ix=1;
     for (slValue=filterBy->slValues;slValue!=NULL;slValue=slValue->next,ix++)
 	{
 	char varName[32];
 	char *label = NULL;
 	char *name = NULL;
 	if (filterBy->useIndex)
 	    {
 	    safef(varName, sizeof(varName), "%d",ix);
 	    name = varName;
@@ -5727,56 +5748,56 @@
         else
             printf("<TD align='left'%s",altLabel);
         }
     puts("</TR>");
     return TRUE;
     }
 return FALSE;
 }
 
 
 static int numericFiltersShowAll(char *db, struct cart *cart, struct trackDb *tdb, boolean *opened,
                                  boolean boxed, boolean parentLevel,char *name, char *title)
 // Shows all *Filter style filters.  Note that these are in random order and have no graceful title
 {
 int count = 0;
-struct slName *filterSettings = trackDbSettingsWildMatch(tdb, "*Filter");
+struct slName *filterSettings = trackDbSettingsWildMatch(tdb, FILTER_NUMBER_WILDCARD);
 if (filterSettings)
     {
     puts("<BR>");
     struct slName *filter = NULL;
 #ifdef EXTRA_FIELDS_SUPPORT
     struct extraField *extras = extraFieldsGet(db,tdb);
 #else///ifndef EXTRA_FIELDS_SUPPORT
     struct sqlConnection *conn = NULL;
     if (!isHubTrack(db))
         conn = hAllocConnTrack(db, tdb);
     struct asObject *as = asForTdb(conn, tdb);
     hFreeConn(&conn);
 #endif///ndef EXTRA_FIELDS_SUPPORT
 
     while ((filter = slPopHead(&filterSettings)) != NULL)
         {
         if (differentString(filter->name,NO_SCORE_FILTER))
             {
             // Determine floating point or integer
             char *setting = trackDbSetting(tdb, filter->name);
             boolean isFloat = (strchr(setting,'.') != NULL);
 
             char *scoreName = cloneString(filter->name);
             char *field = filter->name;   // No need to clone: will be thrown away at end of cycle
-            int ix = strlen(field) - strlen("Filter");
+            int ix = strlen(field) - strlen(FILTER_NUMBER_NAME);
             assert(ix > 0);
             field[ix] = '\0';
 
         #ifdef EXTRA_FIELDS_SUPPORT
             if (extras != NULL)
                 {
                 struct extraField *extra = extraFieldsFind(extras, field);
                 if (extra != NULL)
                     { // Found label so replace field
                     field = extra->label;
                     if (!isFloat)
                         isFloat = (extra->type == ftFloat);
                     }
                 }
         #else///ifndef EXTRA_FIELDS_SUPPORT
@@ -5816,31 +5837,31 @@
 if (count > 0)
     puts("</TABLE>");
 return count;
 }
 
 
 boolean bedScoreHasCfgUi(struct trackDb *tdb)
 // Confirms that this track has a bedScore Cfg UI
 {
 // Assumes that cfgType == cfgBedScore
 if (trackDbSettingClosestToHome(tdb, FILTER_BY))
     return TRUE;
 if (trackDbSettingClosestToHome(tdb, GRAY_LEVEL_SCORE_MIN))
     return TRUE;
 boolean blocked = FALSE;
-struct slName *filterSettings = trackDbSettingsWildMatch(tdb, "*Filter");
+struct slName *filterSettings = trackDbSettingsWildMatch(tdb, FILTER_NUMBER_WILDCARD);
 if (filterSettings != NULL)
     {
     boolean one = FALSE;
     struct slName *oneFilter = filterSettings;
     for (;oneFilter != NULL;oneFilter=oneFilter->next)
         {
         if (sameWord(NO_SCORE_FILTER,oneFilter->name))
             {
             blocked = TRUE;
             continue;
             }
         if (differentString(oneFilter->name,SCORE_FILTER)) // scoreFilter is implicit
             {                                              // but could be blocked
             one = TRUE;
             break;
@@ -5848,39 +5869,39 @@
         }
     slNameFreeList(&filterSettings);
     if (one)
         return TRUE;
     }
 if (!blocked)  // scoreFilter is implicit unless NO_SCORE_FILTER
     return TRUE;
 
 return FALSE;
 }
 
 
 void textFiltersShowAll(char *db, struct cart *cart, struct trackDb *tdb)
 /* Show all the text filters for this track. */
 {
-struct slName *filter, *filterSettings = trackDbSettingsWildMatch(tdb, "*FilterText");
+struct slName *filter, *filterSettings = trackDbSettingsWildMatch(tdb, FILTER_TEXT_WILDCARD);
 if (filterSettings)
     {
     while ((filter = slPopHead(&filterSettings)) != NULL)
         {
         char *setting = trackDbSetting(tdb, filter->name);
         char *value = cartUsualStringClosestToHome(cart, tdb, FALSE, filter->name, setting);
         char *field = cloneString(filter->name);
-        int ix = strlen(field) - strlen("FilterText");
+        int ix = strlen(field) - strlen(FILTER_TEXT_NAME);
         assert(ix > 0);
         field[ix] = '\0';
 
         printf("<P><B>Filter items by regular expression in '%s' field: ", field);
 
         char cgiVar[128];
         safef(cgiVar,sizeof(cgiVar),"%s.%s",tdb->track,filter->name);
         cgiMakeTextVar(cgiVar, value, 45);
         }
     }
 }
 
 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
@@ -6353,31 +6374,31 @@
 //    warn("SELECT FROM %s WHERE %s",tdb->table,dyStringContents(extraWhere));
 return extraWhere;
 }
 
 struct dyString *dyAddAllScoreFilters(struct cart *cart, struct trackDb *tdb,
                                       struct dyString *extraWhere,boolean *and)
 // creates the where clause condition to gather together all random double filters
 // Filters are expected to follow
 //      {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
 //          uses:  defaultLimits: function param if no tdb limits settings found)
 // The 'and' param and dyString in/out allows stringing multiple where clauses together
 {
-struct slName *filterSettings = trackDbSettingsWildMatch(tdb, "*Filter");
+struct slName *filterSettings = trackDbSettingsWildMatch(tdb, FILTER_NUMBER_WILDCARD);
 if (filterSettings)
     {
     struct slName *filter = NULL;
     while ((filter = slPopHead(&filterSettings)) != NULL)
         {
         if (differentString(filter->name,"noScoreFilter")
         &&  differentString(filter->name,"scoreFilter")) // TODO: scoreFilter could be included
             {
             char *field = cloneString(filter->name);
             int ix = strlen(field) - strlen("filter");
             assert(ix > 0);
             field[ix] = '\0';
             char *setting = trackDbSetting(tdb, filter->name);
             // How to determine float or int ?
             // If actual tracDb setting has decimal places, then float!