77dc06c4bdc5e10f5d2705a51300ec028a7d785f tdreszer Fri Oct 1 10:51:48 2010 -0700 FindTracks now finds superTracks (Advanced only) and hgTrackUi now does superTrack reshaping when children have vis changed diff --git src/hg/hgTracks/searchTracks.c.fxit src/hg/hgTracks/searchTracks.c.fxit new file mode 100644 index 0000000..eeff438 --- /dev/null +++ src/hg/hgTracks/searchTracks.c.fxit @@ -0,0 +1,861 @@ +/* Track search code used by hgTracks CGI */ + +#include "common.h" +#include "searchTracks.h" +#include "hCommon.h" +#include "memalloc.h" +#include "obscure.h" +#include "dystring.h" +#include "hash.h" +#include "cheapcgi.h" +#include "hPrint.h" +#include "htmshell.h" +#include "cart.h" +#include "hgTracks.h" +#include "web.h" +#include "jksql.h" +#include "hdb.h" +#include "mdb.h" +#include "trix.h" +#include "jsHelper.h" +#include "imageV2.h" + +static char const rcsid[] = "$Id: searchTracks.c,v 1.11 2010/06/11 18:21:40 larrym Exp $"; + +#define ANYLABEL "Any" +#define METADATA_NAME_PREFIX "hgt.metadataName" +#define METADATA_VALUE_PREFIX "hgt.metadataValue" + +static int gCmpGroup(const void *va, const void *vb) +/* Compare groups based on label. */ +{ +const struct group *a = *((struct group **)va); +const struct group *b = *((struct group **)vb); +return strcmp(a->label, b->label); +} + +// Would like to do a radio button choice ofsorts +#define FINDTRACKS_SORT +#ifdef FINDTRACKS_SORT +#define SORT_BY_VAR "findTracksSortBy" +#define SORT_BY_ABC "abc" +#define SORT_BY_HIER "hier" +#define SORT_BY_REL "rel" +enum sortBy + { + sbRelevance=0, + sbAbc =1, + sbHierarchy=2, + }; +static int gCmpTrackHierarchy(const void *va, const void *vb) +/* Compare tracks based on longLabel. */ +{ +const struct slRef *aa = *((struct slRef **)va); +const struct slRef *bb = *((struct slRef **)vb); +const struct track *a = ((struct track *) aa->val); +const struct track *b = ((struct track *) bb->val); + if ( tdbIsFolder(a->tdb) && !tdbIsFolder(b->tdb)) + return -1; +else if (!tdbIsFolder(a->tdb) && tdbIsFolder(b->tdb)) + return 1; + if ( tdbIsContainer(a->tdb) && !tdbIsContainer(b->tdb)) + return -1; +else if (!tdbIsContainer(a->tdb) && tdbIsContainer(b->tdb)) + return 1; + if (!tdbIsContainerChild(a->tdb) && tdbIsContainerChild(b->tdb)) + return -1; +else if ( tdbIsContainerChild(a->tdb) && !tdbIsContainerChild(b->tdb)) + return 1; +return strcasecmp(a->longLabel, b->longLabel); +} + +//static int gCmpTrackReverseHierarchy(const void *va, const void *vb) +///* Compare tracks based on longLabel. */ +//{ +//const struct slRef *aa = *((struct slRef **)va); +//const struct slRef *bb = *((struct slRef **)vb); +//const struct track *a = ((struct track *) aa->val); +//const struct track *b = ((struct track *) bb->val); +// if ( tdbIsContainerChild(a->tdb) && !tdbIsContainerChild(b->tdb)) +// return -1; +//else if (!tdbIsContainerChild(a->tdb) && tdbIsContainerChild(b->tdb)) +// return 1; +// if ( tdbIsContainer(a->tdb) && !tdbIsContainer(b->tdb)) +// return -1; +//else if (!tdbIsContainer(a->tdb) && tdbIsContainer(b->tdb)) +// return 1; +// if (!tdbIsFolder(a->tdb) && tdbIsFolder(b->tdb)) +// return -1; +//else if ( tdbIsFolder(a->tdb) && !tdbIsFolder(b->tdb)) +// return 1; +//return strcasecmp(a->longLabel, b->longLabel); +//} +#endif///def FINDTRACKS_SORT + +static int gCmpTrack(const void *va, const void *vb) +/* Compare tracks based on longLabel. */ +{ +const struct slRef *aa = *((struct slRef **)va); +const struct slRef *bb = *((struct slRef **)vb); +const struct track *a = ((struct track *) aa->val); +const struct track *b = ((struct track *) bb->val); +return strcasecmp(a->longLabel, b->longLabel); +} + +static void findTracksSort(struct slRef **pTrack, boolean simpleSearch, enum sortBy sortBy) +{ +#ifdef FINDTRACKS_SORT +if (sortBy == sbHierarchy) + slSort(pTrack, gCmpTrackHierarchy); +else if (sortBy == sbAbc) + slSort(pTrack, gCmpTrack); +else //if(simpleSearch) + slReverse(pTrack); +//else +// slSort(pTrack, gCmpTrackReverseHierarchy); +#else///ifndef FINDTRACKS_SORT +if (simpleSearch) + slReverse(pTrack); +else + slSort(&tracks, gCmpTrack); +#endif///ndef FINDTRACKS_SORT +} + + +// XXXX make a matchString function to support "contains", "is" etc. and wildcards in contains + +// ((sameString(op, "is") && !strcasecmp(track->shortLabel, str)) || + +static boolean isNameMatch(struct track *track, char *str, char *op) +{ +return str && strlen(str) && + ((sameString(op, "is") && !strcasecmp(track->shortLabel, str)) || + (sameString(op, "is") && !strcasecmp(track->longLabel, str)) || + (sameString(op, "contains") && containsStringNoCase(track->shortLabel, str) != NULL) || + (sameString(op, "contains") && containsStringNoCase(track->longLabel, str) != NULL)); +} + +static boolean isDescriptionMatch(struct track *track, char **words, int wordCount) +// We parse str and look for every word at the start of any word in track description (i.e. google style). +{ +if(words) + { + // We do NOT lookup up parent hierarchy for html descriptions. + char *html = track->tdb->html; + if(!isEmpty(html)) + { + /* This probably could be made more efficient by parsing the html into some kind of b-tree, but I am assuming + that the inner html loop while only happen for 1-2 words for vast majority of the tracks. */ + + int i, numMatches = 0; + html = stripRegEx(html, "<[^>]*>", REG_ICASE); + for(i = 0; i < wordCount; i++) + { + char *needle = words[i]; + char *haystack, *tmp = cloneString(html); + boolean found = FALSE; + while((haystack = nextWord(&tmp))) + { + char *ptr = strstrNoCase(haystack, needle); + if(ptr != NULL && ptr == haystack) + { + found = TRUE; + break; + } + } + if(found) + numMatches++; + else + break; + } + if(numMatches == wordCount) + return TRUE; + } + } +return FALSE; +} + +static int getTermArray(struct sqlConnection *conn, char ***terms, char *type) +// Pull out all term fields from ra entries with given type +// Returns count of items found and items via the terms argument. +{ +int i, count = 0; +char **retVal; +struct slName *termList = mdbValSearch(conn, type, MDB_VAL_STD_TRUNCATION, TRUE, FALSE); // Tables not files +count = slCount(termList) + 1; // make room for "Any" +AllocArray(retVal, count); +retVal[0] = cloneString(ANYLABEL); +for(i = 1; termList != NULL;termList = termList->next, i++) + { + retVal[i] = cloneString(termList->name); + } +*terms = retVal; +return count; +} + +static int metaDbVars(struct sqlConnection *conn, char *** metaVars, char *** metaLabels) +// Search the assemblies metaDb table; If name == NULL, we search every metadata field. +{ +char query[256]; +#define WHITE_LIST_COUNT 35 +#ifdef WHITE_LIST_COUNT +#define WHITE_LIST_VAR 0 +#define WHITE_LIST_LABEL 1 +char *whiteList[WHITE_LIST_COUNT][2] = { + {"age", "Age of experimental organism"}, + {"antibody", "Antibody or target protein"}, + {"origAssembly", "Assembly originally mapped to"}, + {"cell", "Cell, tissue or DNA sample"}, + {"localization", "Cell compartment"}, + {"control", "Control or Input for ChIPseq"}, + //{"controlId", "ControlId - explicit relationship"}, + {"dataType", "Experiment type"}, + {"dataVersion", "ENCODE release"}, + //{"fragLength", "Fragment Length for ChIPseq"}, + //{"freezeDate", "Gencode freeze date"}, + //{"level", "Gencode level"}, + //{"annotation", "Gencode annotation"}, + {"accession", "GEO accession"}, + {"growthProtocol", "Growth Protocol"}, + {"lab", "Lab producing data"}, + {"labVersion", "Lab specific details"}, + {"labExpId", "Lab specific identifier"}, + {"softwareVersion", "Lab specific informatics"}, + {"protocol", "Library Protocol"}, + {"mapAlgorithm", "Mapping algorithm"}, + {"readType", "Paired/Single reads lengths"}, + {"grant", "Principal Investigator"}, + {"replicate", "Replicate number"}, + //{"restrictionEnzyme","Restriction Enzyme used"}, + //{"ripAntibody", "RIP Antibody"}, + //{"ripTgtProtein", "RIP Target Protein"}, + {"rnaExtract", "RNA Extract"}, + {"seqPlatform", "Sequencing Platform"}, + {"setType", "Experiment or Input"}, + {"sex", "Sex of organism"}, + {"strain", "Strain of organism"}, + {"subId", "Submission Id"}, + {"treatment", "Treatment"}, + {"view", "View - Peaks or Signals"}, +}; +// FIXME: The whitelist should be a table or ra +// FIXME: The whitelist should be in list order +// FIXME: Should read in list, then verify that an mdb val exists. + +char **retVar = needMem(sizeof(char *) * WHITE_LIST_COUNT); +char **retLab = needMem(sizeof(char *) * WHITE_LIST_COUNT); +int ix,count; +for(ix=0,count=0;ix<WHITE_LIST_COUNT;ix++) + { + safef(query, sizeof(query), "select count(*) from metaDb where var = '%s'",whiteList[ix][WHITE_LIST_VAR]); + if(sqlQuickNum(conn,query) > 0) + { + retVar[count] = whiteList[ix][WHITE_LIST_VAR]; + retLab[count] = whiteList[ix][WHITE_LIST_LABEL]; + count++; + } + } +if(count == 0) + { + freez(&retVar); + freez(&retLab); + } +*metaVars = retVar; +*metaLabels = retLab; +return count; + +#else///ifndef WHITE_LIST_COUNT + +char **retVar; +char **retLab; +struct slName *el, *varList = NULL; +struct sqlResult *sr = NULL; +char **row = NULL; + +safef(query, sizeof(query), "select distinct var from metaDb order by var"); +sr = sqlGetResult(conn, query); +while ((row = sqlNextRow(sr)) != NULL) + slNameAddHead(&varList, row[0]); +sqlFreeResult(&sr); +retVar = needMem(sizeof(char *) * slCount(varList)); +retLab = needMem(sizeof(char *) * slCount(varList)); +slReverse(&varList); +//slNameSort(&varList); +int count = 0; +for (el = varList; el != NULL; el = el->next) + { + retVar[count] = el->name; + retLab[count] = el->name; + count++; + } +*metaVars = retVar; +*whiteLabels = retLab; +return count; +#endif///ndef WHITE_LIST_COUNT +} + +void doSearchTracks(struct group *groupList) +{ +struct group *group; +char *groups[128]; +char *labels[128]; +int numGroups = 1; +groups[0] = ANYLABEL; +labels[0] = ANYLABEL; +char *currentTab = cartUsualString(cart, "hgt.currentSearchTab", "simpleTab"); +char *nameSearch = cartOptionalString(cart, "hgt.nameSearch"); +char *descSearch; +char *groupSearch = cartOptionalString(cart, "hgt.groupSearch"); +boolean doSearch = sameString(cartOptionalString(cart, searchTracks), "Search") || cartUsualInt(cart, "hgt.forceSearch", 0) == 1; +struct sqlConnection *conn = hAllocConn(database); +boolean metaDbExists = sqlTableExists(conn, "metaDb"); +struct slRef *tracks = NULL; +int numMetadataSelects, tracksFound = 0; +int numMetadataNonEmpty = 0; +char **mdbVar; +char **mdbVal; +struct hash *parents = newHash(4); +boolean simpleSearch; +struct trix *trix; +char trixFile[HDB_MAX_PATH_STRING]; +char **descWords = NULL; +int descWordCount = 0; +boolean searchTermsExist = FALSE; +int cols; + +if(sameString(currentTab, "simpleTab")) + { + descSearch = cartOptionalString(cart, "hgt.simpleSearch"); + simpleSearch = TRUE; + freez(&nameSearch); + freez(&groupSearch); + } +else + { + descSearch = cartOptionalString(cart, "hgt.descSearch"); + simpleSearch = FALSE; + } + +getSearchTrixFile(database, trixFile, sizeof(trixFile)); +trix = trixOpen(trixFile); +//#define DO_RESHAPING +// Do shaping was meant to handle shaping supertracks after * configuring contained composite. +// HOWEVER, this will not work since supertrack reshaping relies upon the temporary "_sel" cart var. +// What to do? Alter hgTrackUi to set "_sel" when necessary? Probably. Then doReshaping will be needed. +// Possibly need doReshaping anyway, if it relies upon old/new cart! +#ifdef DO_RESHAPING +struct track *trackList = getTrackList(&groupList, -2); +#else///ifndef DO_RESHAPING +(void)getTrackList(&groupList, -2); +#endif///ndef DO_RESHAPING +slSort(&groupList, gCmpGroup); +for (group = groupList; group != NULL; group = group->next) + { +#define FIND_SUPERS_TOO +#ifdef FIND_SUPERS_TOO + groupTrackListAddSuper(cart, group); +#endif///def FIND_SUPERS_TOO + if (group->trackList != NULL) + { + groups[numGroups] = cloneString(group->name); + labels[numGroups] = cloneString(group->label); + numGroups++; + if (numGroups >= ArraySize(groups)) + internalErr(); + } + } +#ifdef DO_RESHAPING +// NOTE: Is this buying me anything?? It should buy composite/view override when subtrack specific vis exists. +// FIXME: Crashes when superTrackChild "_sel" is found +parentChildCartCleanup(trackList,cart,oldVars); // Subtrack settings must be removed when composite/view settings are updated +#endif///def DO_RESHAPING + +webStartWrapperDetailedNoArgs(cart, database, "", "Search for Tracks", FALSE, FALSE, FALSE, FALSE); + +hPrintf("<div style='max-width:1080px;'>"); +hPrintf("<form action='%s' name='SearchTracks' id='searchTracks' method='get'>\n\n", hgTracksName()); +cartSaveSession(cart); // Creates hidden var of hgsid to avoid bad voodoo + +hPrintf("<input type='hidden' name='db' value='%s'>\n", database); +hPrintf("<input type='hidden' name='hgt.currentSearchTab' id='currentSearchTab' value='%s'>\n", currentTab); +hPrintf("<input type='hidden' name='hgt.delRow' value=''>\n"); +hPrintf("<input type='hidden' name='hgt.addRow' value=''>\n"); +hPrintf("<input type='hidden' name='hgt.forceSearch' value=''>\n"); + +hPrintf("<div id='tabs' style='display:none; %s'>\n" + "<ul>\n" + "<li><a href='#simpleTab'><span>Search</span></a></li>\n" + "<li><a href='#advancedTab'><span>Advanced</span></a></li>\n" + "</ul>\n" + "<div id='simpleTab' style='max-width:inherit;'>\n",cgiBrowser()==btIE?"width:1060px;":"max-width:inherit;"); + +hPrintf("<table style='width:100%%;'><tr><td colspan='2'>"); +hPrintf("<input type='text' name='hgt.simpleSearch' id='simpleSearch' value='%s' style='max-width:1000px; width:100%%' onkeyup='findTracksSearchButtonsEnable(true);'>\n", descSearch == NULL ? "" : descSearch); +hPrintf("</td></tr><tr><td>"); +if (simpleSearch && descSearch) + searchTermsExist = TRUE; + +hPrintf("<input type='submit' name='%s' id='searchSubmit' value='Search' style='font-size:14px;'>\n", searchTracks); +hPrintf("<input type='button' name='clear' value='Clear' class='clear' style='font-size:14px;' onclick='findTracksClear();'>\n"); +hPrintf("<input type='submit' name='submit' value='Cancel' class='cancel' style='font-size:14px;'>\n"); +hPrintf("<a target='_blank' href='../goldenPath/help/trackSearch.html'>help</a></td></tr></table>\n"); +//hPrintf("</td><td align='right'><a target='_blank' href='../goldenPath/help/trackSearch.html'>help</a></td></tr></table>\n"); +hPrintf("</div>\n" + "<div id='advancedTab' style='width:inherit;'>\n" + "<table cellSpacing=0 style='width:inherit;'>\n"); + +cols = 7; + +// Track Name contains +hPrintf("<tr><td colspan=3></td>"); +hPrintf("<td nowrap><b style='max-width:100px;'>Track Name:</b></td>"); +hPrintf("<td align='right'>contains</td>\n"); +hPrintf("<td colspan='%d'>", cols - 4); +hPrintf("<input type='text' name='hgt.nameSearch' id='nameSearch' value='%s' onkeyup='findTracksSearchButtonsEnable(true);' style='min-width:326px;'>", nameSearch == NULL ? "" : nameSearch); +hPrintf("</td></tr>\n"); + + +// Description contains +hPrintf("<tr><td colspan=2></td><td align='right'>and </td>"); +hPrintf("<td><b style='max-width:100px;'>Description:</b></td>"); +hPrintf("<td align='right'>contains</td>\n"); +hPrintf("<td colspan='%d'>", cols - 4); +hPrintf("<input type='text' name='hgt.descSearch' id='descSearch' value='%s' onkeyup='findTracksSearchButtonsEnable(true);' style='max-width:536px; width:536px;'>", + descSearch == NULL ? "" : descSearch); +hPrintf("</td></tr>\n"); +if (!simpleSearch && descSearch) + searchTermsExist = TRUE; + +hPrintf("<tr><td colspan=2></td><td align='right'>and </td>\n"); +hPrintf("<td><b style='max-width:100px;'>Group</b></td>"); +hPrintf("<td align='right'>is</td>\n"); +hPrintf("<td colspan='%d'>", cols - 4); +cgiMakeDropListFull("hgt.groupSearch", labels, groups, numGroups, groupSearch, "class='groupSearch' style='min-width:40%%;'"); +hPrintf("</td></tr>\n"); +if (!simpleSearch && groupSearch) + searchTermsExist = TRUE; + +// figure out how many metadata selects are visible. +int delSearchSelect = cartUsualInt(cart, "hgt.delRow", 0); // 1-based row to delete +int addSearchSelect = cartUsualInt(cart, "hgt.addRow", 0); // 1-based row to insert after + +for(numMetadataSelects = 0;;) + { + char buf[256]; + safef(buf, sizeof(buf), "%s%d", METADATA_NAME_PREFIX, numMetadataSelects + 1); + char *str = cartOptionalString(cart, buf); + if(isEmpty(str)) + break; + else + numMetadataSelects++; + } + +if(delSearchSelect) + numMetadataSelects--; +if(addSearchSelect) + numMetadataSelects++; + +if(numMetadataSelects) + { + mdbVar = needMem(sizeof(char *) * numMetadataSelects); + mdbVal = needMem(sizeof(char *) * numMetadataSelects); + int i; + for(i = 0; i < numMetadataSelects; i++) + { + char buf[256]; + int offset; // used to handle additions/deletions + if(addSearchSelect > 0 && i >= addSearchSelect) + offset = 0; // do nothing to offset (i.e. copy data from previous row) + else if(delSearchSelect > 0 && i + 1 >= delSearchSelect) + offset = 2; + else + offset = 1; + safef(buf, sizeof(buf), "%s%d", METADATA_NAME_PREFIX, i + offset); + mdbVar[i] = cloneString(cartOptionalString(cart, buf)); + if(!simpleSearch) + { + safef(buf, sizeof(buf), "%s%d", METADATA_VALUE_PREFIX, i + offset); + mdbVal[i] = cloneString(cartOptionalString(cart, buf)); + if(sameString(mdbVal[i], ANYLABEL)) + mdbVal[i] = NULL; + if(!isEmpty(mdbVal[i])) + numMetadataNonEmpty++; + } + } + if(delSearchSelect > 0) + { + char buf[255]; + safef(buf, sizeof(buf), "%s%d", METADATA_NAME_PREFIX, numMetadataSelects + 1); + cartRemove(cart, buf); + safef(buf, sizeof(buf), "%s%d", METADATA_VALUE_PREFIX, numMetadataSelects + 1); + cartRemove(cart, buf); + } + } +else + { + // create defaults + numMetadataSelects = 2; + mdbVar = needMem(sizeof(char *) * numMetadataSelects); + mdbVal = needMem(sizeof(char *) * numMetadataSelects); + mdbVar[0] = "cell"; + mdbVar[1] = "antibody"; + mdbVal[0] = ANYLABEL; + mdbVal[1] = ANYLABEL; + } + +if(metaDbExists) + { + int i; + char **mdbVars = NULL; + char **mdbVarLabels = NULL; + int count = metaDbVars(conn, &mdbVars,&mdbVarLabels); + + hPrintf("<tr><td colspan='%d' align='right' class='lineOnTop' style='height:20px; max-height:20px;'><em style='color:%s; width:200px;'>ENCODE terms</em></td></tr>", cols,COLOR_DARKGREY); + for(i = 0; i < numMetadataSelects; i++) + { + char **terms; + char buf[256]; + int len; + + hPrintf("<tr><td>\n"); + if(numMetadataSelects > 2 || i >= 2) + { + safef(buf, sizeof(buf), "return delSearchSelect(this, %d);", i + 1); + hButtonWithOnClick(searchTracks, "-", "delete this row", buf); + } + else + hPrintf(" "); + hPrintf("</td><td>\n"); + safef(buf, sizeof(buf), "return addSearchSelect(this, %d);", i + 1); + hButtonWithOnClick(searchTracks, "+", "add another row after this row", buf); + + hPrintf("</td><td>and </td><td colspan=3 nowrap>\n"); + safef(buf, sizeof(buf), "%s%i", METADATA_NAME_PREFIX, i + 1); + cgiDropDownWithTextValsAndExtra(buf, mdbVarLabels, mdbVars,count,mdbVar[i],"class='mdbVar' onchange=findTracksMdbVarChanged(this)"); + hPrintf("</td><td nowrap style='max-width:600px;'>is\n"); + len = getTermArray(conn, &terms, mdbVar[i]); + safef(buf, sizeof(buf), "%s%i", METADATA_VALUE_PREFIX, i + 1); + cgiMakeDropListFull(buf, terms, terms, len, mdbVal[i], "class='mdbVal' style='min-width:200px;' onchange='findTracksSearchButtonsEnable(true)'"); + if (!simpleSearch && mdbVal[i]) + searchTermsExist = TRUE; + hPrintf("<span id='helpLink%d'>help</span></td>\n", i + 1); + hPrintf("</tr>\n"); + } + } + +hPrintf("<tr><td colspan='%d'>\n", cols); +hPrintf("<input type='submit' name='%s' id='searchSubmit' value='Search' style='font-size:14px;'>\n", searchTracks); +hPrintf("<input type='button' name='clear' value='Clear' class='clear' style='font-size:14px;' onclick='findTracksClear();'>\n"); +hPrintf("<input type='submit' name='submit' value='Cancel' class='cancel' style='font-size:14px;'>\n"); +hPrintf("<a target='_blank' href='../goldenPath/help/trackSearch.html'>help</a></td></tr>\n"); +hPrintf("</table>\n"); +hPrintf("</div>\n</div>\n"); +hPrintf("</form>\n"); +hPrintf("</div"); // Restricts to max-width:1000px; + +if(descSearch != NULL && !strlen(descSearch)) + descSearch = NULL; +if(groupSearch != NULL && sameString(groupSearch, ANYLABEL)) + groupSearch = NULL; + +if(!isEmpty(descSearch)) + { + char *tmp = cloneString(descSearch); + char *val = nextWord(&tmp); + struct slName *el, *descList = NULL; + int i; + while (val != NULL) + { + slNameAddTail(&descList, val); + descWordCount++; + val = nextWord(&tmp); + } + descWords = needMem(sizeof(char *) * descWordCount); + for(i = 0, el = descList; el != NULL; i++, el = el->next) + descWords[i] = strLower(el->name); + } +if (doSearch && simpleSearch && descWordCount <= 0) + doSearch = FALSE; + +#ifdef FINDTRACKS_SORT +enum sortBy sortBy = cartUsualInt(cart,SORT_BY_VAR,sbRelevance); +//boolean sortByHierarchy = sameString(cartUsualString(cart,SORT_BY_VAR,SORT_BY_HIER),SORT_BY_HIER); +#endif///def FINDTRACKS_SORT +if(doSearch) + { + if(simpleSearch) + { + struct trixSearchResult *tsList; + struct hash *trackHash = newHash(0); + + // Create a hash of tracks, so we can map the track name into a track struct. + for (group = groupList; group != NULL; group = group->next) + { + struct trackRef *tr; + for (tr = group->trackList; tr != NULL; tr = tr->next) + { + struct track *track = tr->track; + hashAdd(trackHash, track->track, track); + struct track *subTrack = track->subtracks; + for (subTrack = track->subtracks; subTrack != NULL; subTrack = subTrack->next) + hashAdd(trackHash, subTrack->track, subTrack); + } + } + for(tsList = trixSearch(trix, descWordCount, descWords, TRUE); tsList != NULL; tsList = tsList->next) + { + struct track *track = (struct track *) hashFindVal(trackHash, tsList->itemId); + if (track != NULL) + { + refAdd(&tracks, track); + tracksFound++; + } + //else // FIXME: Should get to the bottom of why some of these are null + // warn("found trix track is NULL."); + } + } + else if(!isEmpty(nameSearch) || descSearch != NULL || groupSearch != NULL || numMetadataNonEmpty) + { + // First do the metaDb searches, which can be done quickly for all tracks with db queries. + struct hash *matchingTracks = newHash(0); + struct hash *trackMetadata = newHash(0); + struct slName *el, *metaTracks = NULL; + int i; + + for(i = 0; i < numMetadataSelects; i++) + { + if(!isEmpty(mdbVal[i])) + { + struct slName *tmp = mdbObjSearch(conn, mdbVar[i], mdbVal[i], "is", MDB_VAL_STD_TRUNCATION, TRUE, FALSE); + if(metaTracks == NULL) + metaTracks = tmp; + else + metaTracks = slNameIntersection(metaTracks, tmp); + } + } + for (el = metaTracks; el != NULL; el = el->next) + hashAddInt(matchingTracks, el->name, 1); + + if(metaDbExists && !isEmpty(descSearch)) + { + // Load all metadata words for each track to facilitate metadata search. + char query[256]; + struct sqlResult *sr = NULL; + char **row; + safef(query, sizeof(query), "select obj, val from metaDb"); + sr = sqlGetResult(conn, query); + while ((row = sqlNextRow(sr)) != NULL) + { + char *str = cloneString(row[1]); + hashAdd(trackMetadata, row[0], str); + } + sqlFreeResult(&sr); + } + + for (group = groupList; group != NULL; group = group->next) + { + if(groupSearch == NULL || sameString(group->name, groupSearch)) + { + if (group->trackList != NULL) + { + struct trackRef *tr; + for (tr = group->trackList; tr != NULL; tr = tr->next) + { + struct track *track = tr->track; + if((isEmpty(nameSearch) || isNameMatch(track, nameSearch, "contains")) && + (isEmpty(descSearch) || isDescriptionMatch(track, descWords, descWordCount)) && + (!numMetadataNonEmpty || hashLookup(matchingTracks, track->track) != NULL)) + { + if (track != NULL) + { + tracksFound++; + refAdd(&tracks, track); + } + else + warn("found group track is NULL."); + } + if (track->subtracks != NULL) + { + struct track *subTrack; + for (subTrack = track->subtracks; subTrack != NULL; subTrack = subTrack->next) + { + if((isEmpty(nameSearch) || isNameMatch(subTrack, nameSearch, "contains")) && + (isEmpty(descSearch) || isDescriptionMatch(subTrack, descWords, descWordCount)) && + (!numMetadataNonEmpty || hashLookup(matchingTracks, subTrack->track) != NULL)) + { + // XXXX to parent hash. - use tdb->parent instead. + hashAdd(parents, subTrack->track, track); + if (track != NULL) + { + tracksFound++; + refAdd(&tracks, subTrack); + } + else + warn("found subtrack is NULL."); + } + } + } + } + } + } + } + } + if(tracksFound > 1) + findTracksSort(&tracks,simpleSearch,sortBy); + } + +hPrintf("<div id='found' style='display:none;'>\n"); // This div allows the clear button to empty it +if(tracksFound < 1) + { + if(doSearch) + hPrintf("<p>No tracks found</p>\n"); + } +else + { + struct hash *tdbHash = makeTrackHash(database, chromName); + hPrintf("<form action='%s' name='SearchTracks' id='searchResultsForm' method='post'>\n\n", hgTracksName()); + cartSaveSession(cart); // Creates hidden var of hgsid to avoid bad voodoo + #define MAX_FOUND_TRACKS 100 + if(tracksFound > MAX_FOUND_TRACKS) + { + hPrintf("<table class='redBox'><tr><td>Found %d tracks, but only the first %d are displayed.",tracksFound,MAX_FOUND_TRACKS); + hPrintf("<BR><B><I>Please narrow search criteria to find fewer tracks.</I></B></div></td></tr></table>\n"); + } + + #define ENOUGH_FOUND_TRACKS 10 + if(tracksFound >= ENOUGH_FOUND_TRACKS) + { + hPrintf("<INPUT TYPE=SUBMIT NAME='submit' VALUE='View in Browser' class='viewBtn'>"); + hPrintf(" <FONT class='selCbCount'></font>\n"); + } + + // Set up json for js functionality + struct dyString *jsonTdbVars = NULL; + + hPrintf("<table id='foundTracks'><tr><td colspan='2'>\n"); + hPrintf("</td><td align='right'>\n"); + #define PM_BUTTON "<IMG height=18 width=18 onclick=\"return findTracksCheckAllWithWait(%s);\" id='btn_%s' src='../images/%s' title='%s all found tracks'>" + hPrintf("</td></tr><tr bgcolor='#%s'><td>",HG_COL_HEADER); + hPrintf(PM_BUTTON,"true", "plus_all", "add_sm.gif", "Select"); + hPrintf(PM_BUTTON,"false","minus_all","remove_sm.gif","Unselect"); + hPrintf("</td><td><b>Visibility</b></td><td colspan=2> <b>Track Name</b>\n"); + if(tracksFound >= ENOUGH_FOUND_TRACKS) + { + #ifdef FINDTRACKS_SORT + hPrintf("<span style='float:right;'>Sort:"); + cgiMakeOnClickRadioButton(SORT_BY_VAR, "0", (sortBy == sbRelevance),"onchange=\"findTracksSortNow(this);\""); // FIXME: var is in wrong form! + hPrintf("by Relevance"); + cgiMakeOnClickRadioButton(SORT_BY_VAR, "1", (sortBy == sbAbc),"onchange=\"findTracksSortNow(this);\""); // FIXME: var is in wrong form! + //cgiMakeOnClickRadioButton(SORT_BY_VAR, SORT_BY_ABC, !sortByHierarchy,"onchange=\"findTracksSortNow(this);\""); // FIXME: var is in wrong form! + hPrintf("Alphabetically"); + cgiMakeOnClickRadioButton(SORT_BY_VAR, "2",(sortBy == sbHierarchy), "onchange=\"findTracksSortNow(this);\""); + //cgiMakeOnClickRadioButton(SORT_BY_VAR, SORT_BY_HIER,sortByHierarchy, "onchange=\"findTracksSortNow(this);\""); + hPrintf("by Hierarchy </span>\n"); + #endif///def FINDTRACKS_SORT + } + hPrintf("</td></tr>\n"); + + int trackCount=0; + boolean containerTrackCount = 0; + struct slRef *ptr; + while((ptr = slPopHead(&tracks))) + { + if(++trackCount > MAX_FOUND_TRACKS) + break; + + struct track *track = (struct track *) ptr->val; + jsonTdbSettingsBuild(&jsonTdbVars, track); + + #ifdef FINDTRACKS_SORT + if (tdbIsFolder(track->tdb)) // supertrack + hPrintf("<tr bgcolor='%s' valign='top' class='found'>\n","#EED5B7");//"#DEB887");//"#E6B426");//#FCECC0//COLOR_LTGREY);//COLOR_LTGREEN);//COLOR_TRACKLIST_LEVEL1); + else if (tdbIsContainer(track->tdb)) + hPrintf("<tr bgcolor='%s' valign='top' class='found'>\n",COLOR_TRACKLIST_LEVEL3); + else + #endif///def FINDTRACKS_SORT + hPrintf("<tr bgcolor='%s' valign='top' class='found'>\n",COLOR_TRACKLIST_LEVEL2); + + hPrintf("<td align='center'>\n"); + + // Determine visibility and checked state + track->visibility = tdbVisLimitedByAncestry(cart, track->tdb, FALSE); + boolean checked = ( track->visibility != tvHide ); + if(tdbIsContainerChild(track->tdb)) + { + //track->visibility = limitedVisFromComposite(track); + checked = fourStateVisible(subtrackFourStateChecked(track->tdb,cart)); // Don't need all 4 states here. Visible=checked&&enabled + checked = (checked && ( track->visibility != tvHide )); // Checked is only if subtrack level vis is also set! + } + + // Setup the check box + #define CB_HIDDEN_VAR "<INPUT TYPE=HIDDEN disabled=true NAME='%s_sel' VALUE='%s'>" + if (tdbIsContainerChild(track->tdb) || tdbIsFolderContent(track->tdb)) // subtracks and folder children get "_sel" var. ("_sel" var is temporary on folder children) + hPrintf(CB_HIDDEN_VAR,track->track,checked?"1":CART_VAR_EMPTY); + #define CB_SEEN "<INPUT TYPE=CHECKBOX id='%s_sel_id' VALUE='on' class='selCb' onclick='findTracksClickedOne(this,true);'%s>" + hPrintf(CB_SEEN,track->track,(checked?" CHECKED":"")); + hPrintf("</td><td>\n"); + + // Setup the visibility drop down + #define VIS_HIDDEN_VAR "<INPUT TYPE=HIDDEN disabled=true NAME='%s' VALUE='%s'>" + hPrintf(VIS_HIDDEN_VAR,track->track,CART_VAR_EMPTY); // All tracks get vis hidden var + char extra[512]; + if (tdbIsFolder(track->tdb)) + { + // FIXME, I haven't seen a single supertrack from simple search + safef(extra,sizeof(extra),"id='%s_id' onchange='findTracksChangeVis(this)'",track->track); + hideShowDropDownWithClassAndExtra(track->track, (track->visibility != tvHide), "normalText visDD",extra); + + } + else + { + safef(extra,sizeof(extra),"id='%s_id' onchange='findTracksChangeVis(this)'",track->track); + hTvDropDownClassWithJavascript(NULL, track->visibility,track->canPack,"normalText seenVis",extra); + } + + // If this is a container track, allow configuring... + if (tdbIsContainer(track->tdb) || tdbIsFolder(track->tdb)) + { + containerTrackCount++; + hPrintf(" <a href='hgTrackUi?db=%s&g=%s&hgt_searchTracks=1&hgt_searchTracks=Search' title='Configure this container track...'>*</a> ",database,track->track); + } + hPrintf("</td>\n"); + //if(tdbIsContainer(track->tdb) || tdbIsFolder(track->tdb)) + // hPrintf("<td><a target='_top' href='%s' title='Configure track...'>%s</a></td>\n", trackUrl(track->track, NULL), track->shortLabel); + //else + hPrintf("<td><a target='_top' onclick=\"hgTrackUiPopUp('%s',true); return false;\" href='%s' title='Display track details'>%s</a></td>\n", track->track, trackUrl(track->track, NULL), track->shortLabel); + hPrintf("<td>%s", track->longLabel); + compositeMetadataToggle(database, track->tdb, "...", TRUE, FALSE, tdbHash); + hPrintf("</td></tr>\n"); + } + hPrintf("</table>\n"); + if(containerTrackCount > 0) + hPrintf("* Tracks so marked are containers which group related data tracks. These may not be visible unless further configuration is done. Click on the * to configure these.<BR>\n"); + hPrintf("<INPUT TYPE=SUBMIT NAME='submit' VALUE='View in Browser' class='viewBtn'>"); + hPrintf(" <FONT class='selCbCount'></font>"); + hPrintf("\n</form>\n"); + + // be done with json + hWrites(jsonTdbSettingsUse(&jsonTdbVars)); + } + +if(!doSearch) + { + hPrintf("<p><b>Recently Done</b><ul>\n" + #ifdef FINDTRACKS_SORT + "<li>Added sort toggle: Relevance, Alphabetically or by Hierarchy.</li>" + //"<li>Added sort toggle: Alphabetically or by Hierarchy.</li>" + #endif///def FINDTRACKS_SORT + "<li>Composite/view visibilites in hgTrackUi get reshaped to reflect found/selected subtracks. (In demo1: only default state composites; demo2: all composites.)</li>" + "<li>Metadata variables have been 'white-listed' to only include vetted items. Short text descriptions and vetted list should be reviewed.</li>" + "<li>Clicking on shortLabel for found track will popup the description text. Subtracks should show their composite description.</li>" + "<li>Non-data 'container' tracks (composites and supertracks) have '*' to mark them, and can be configured before displaying. Better suggestions?</li>" + "<li>Found track list shows only the first 100 tracks with warning to narrow search. Larry suggests this could be done by pages of results in v2.0.</li>\n" + "</ul></p>" + "<p><b>Suggested improvments:</b><ul>\n" + "<li>The metadata values will not be white-listed, but it would be nice to have more descriptive text for them. A short label added to cv.ra?</li>" + "<li>Look and feel of found track list (here) and composite subtrack list (hgTrackUi) should converge. Jim suggests look and feel of hgTracks 'Configure Tracks...' list instead.</li>" + "<li>Drop-down list of terms (cells, antibodies, etc.) should be multi-select with checkBoxes as seen in filterComposites. Perhaps saved for v2.0.</li>" + "</ul></p>\n"); + } +hPrintf("</div"); // This div allows the clear button to empty it +webEndSectionTables(); +}