Exemplo n.º 1
0
static boolean mdbSelectsAddFoundComposites(struct slPair **pMdbSelects,struct trackDb *tdbsFound)
// Adds a composite mdbSelect (if found in tdbsFound) to the head of the pairs list.
// If tdbsFound is NULL, then add dummy composite search criteria
{
// create comma separated list of composites
struct dyString *dyComposites = dyStringNew(256);
struct trackDb *tdb = tdbsFound;
for (;tdb != NULL; tdb = tdb->next)
    {
    if (tdbIsComposite(tdb))
        dyStringPrintf(dyComposites,"%s,",tdb->track);
    else if (tdbIsCompositeChild(tdb))
        {
        struct trackDb *composite = tdbGetComposite(tdb);
        dyStringPrintf(dyComposites,"%s,",composite->track);
        }
    }
if (dyStringLen(dyComposites) > 0)
    {
    char *composites = dyStringCannibalize(&dyComposites);
    composites[strlen(composites) - 1] = '\0';  // drop the last ','
    slPairAdd(pMdbSelects,MDB_VAR_COMPOSITE,composites); 
    // Composite should not already be in the list, because it is only indirectly sortable
    return TRUE;
    }

//warn("No composites found");
dyStringFree(&dyComposites);
return FALSE;
}
Exemplo n.º 2
0
static struct trackDb *tdbFilterBy(struct trackDb **pTdbList, char *name, char *description, 
                                   char *group)
// returns tdbs that match supplied criterion, leaving unmatched in list passed in
{
// Set the word lists up once
struct slName *nameList = NULL;
if (name)
    nameList = slNameListOfUniqueWords(cloneString(name),TRUE); // TRUE means respect quotes
struct slName *descList = NULL;
if (description)
    descList = slNameListOfUniqueWords(cloneString(description),TRUE);

struct trackDb *tdbList = *pTdbList;
struct trackDb *tdbRejects = NULL;
struct trackDb *tdbMatched = NULL;

while (tdbList != NULL)
    {
    struct trackDb *tdb = slPopHead(&tdbList);

    if (!tdbIsComposite(tdb))
        slAddHead(&tdbRejects,tdb);
    else if (group && differentString(tdb->grp,group))
        slAddHead(&tdbRejects,tdb);
    else if (name && !searchNameMatches(tdb, nameList))
        slAddHead(&tdbRejects,tdb);
    else if (description && !searchDescriptionMatches(tdb, descList))
        slAddHead(&tdbRejects,tdb);
    else
        slAddHead(&tdbMatched,tdb);
    }
*pTdbList = tdbRejects;

//warn("matched %d tracks",slCount(tdbMatched));
return tdbMatched;
}
static struct slRef *advancedSearchForTracks(struct sqlConnection *conn,struct group *groupList, char **descWords,int descWordCount, char *nameSearch, char *typeSearch, char *descSearch, char *groupSearch,
                                             int numMetadataNonEmpty,int numMetadataSelects,char **mdbVar,char **mdbVal)
// Performs the advanced search and returns the found tracks.
{
int tracksFound = 0;
struct slRef *tracks = NULL;

    if(!isEmpty(nameSearch) || typeSearch != NULL || 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 slName *el, *metaTracks = NULL;
        int i;

        for(i = 0; i < numMetadataSelects; i++)
            {
            if(!isEmpty(mdbVal[i]))
                {
            #ifdef CV_SEARCH_SUPPORTS_FREETEXT
                enum mdbCvSearchable searchBy = mdbCvSearchMethod(mdbVar[i]);
                struct slName *tmp = NULL;
                // If select is by free text then like
                if (searchBy == cvsSearchByMultiSelect)
                    {
                    // TO BE IMPLEMENTED
                    // The mdbVal[1] will hve to be filled cartOptionalSlNameList(cart,???)
                    struct slName *choices = (struct slName *)mdbVal[i];
                    if (slCount(choices) == 1)
                        {
                        tmp = mdbObjSearch(conn, mdbVar[i], choices->name, "is", MDB_VAL_STD_TRUNCATION, TRUE, FALSE);
                        }
                    else if(choices != NULL)
                        {
                        // Then slNames will need to be assembled into a string in the form of 'a','b','c'
                        struct dyString *dyList = dyStringNew(256);
                        dyStringPrintf(dyList,"'%s'",choices->name);
                        struct slName *choice = choices->next;
                        for(;choice!=NULL;choice=choice->next)
                            dyStringPrintf(dyList,",'%s'",choice->name);
                        tmp = mdbObjSearch(conn, mdbVar[i], dyStringContents(dyList), "in", MDB_VAL_STD_TRUNCATION, TRUE, FALSE);
                        dyStringFree(&dyList);
                        }
                    }
                else if (searchBy == cvsSearchBySingleSelect)
                    {
                    tmp = mdbObjSearch(conn, mdbVar[i], mdbVal[i], "is", MDB_VAL_STD_TRUNCATION, TRUE, FALSE);
                    }
                else if (searchBy == cvsSearchByFreeText)
                    {
                    tmp = mdbObjSearch(conn, mdbVar[i], mdbVal[i], "like", MDB_VAL_STD_TRUNCATION, TRUE, FALSE);
                    }
                else if (searchBy == cvsSearchByDateRange || searchBy == cvsSearchByIntegerRange)
                    {
                    // TO BE IMPLEMENTED
                    // Requires new mdbObjSearch API and more than one mdbVal[i]
                    }
                if (tmp != NULL)
                    {
                    if(metaTracks == NULL)
                        metaTracks = tmp;
                    else
                        metaTracks = slNameIntersection(metaTracks, tmp);
                    }
            #else///ifndif CV_SEARCH_SUPPORTS_FREETEXT
                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);
            #endif///ndef CV_SEARCH_SUPPORTS_FREETEXT
                }
            }
        for (el = metaTracks; el != NULL; el = el->next)
            hashAddInt(matchingTracks, el->name, 1);

        struct group *group;
        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;
                    #ifdef TRACK_SEARCH_ON_TYPE
                        char *trackType = cloneFirstWord(track->tdb->type); // will be spilled
                    #endif///def TRACK_SEARCH_ON_TYPE
                        if((isEmpty(nameSearch) || isNameMatch(track, nameSearch, "contains")) &&
                    #ifdef TRACK_SEARCH_ON_TYPE
                           (isEmpty(typeSearch) || (sameWord(typeSearch, trackType) && !tdbIsComposite(track->tdb))) &&
                    #endif///def TRACK_SEARCH_ON_TYPE
                           (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)
                                {
                            #ifdef TRACK_SEARCH_ON_TYPE
                                trackType = cloneFirstWord(subTrack->tdb->type); // will be spilled
                            #endif///def TRACK_SEARCH_ON_TYPE
                                if((isEmpty(nameSearch) || isNameMatch(subTrack, nameSearch, "contains")) &&
                            #ifdef TRACK_SEARCH_ON_TYPE
                                   (isEmpty(typeSearch) || sameWord(typeSearch, trackType)) &&
                            #endif///def TRACK_SEARCH_ON_TYPE
                                   (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.");
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

return tracks;
}
Exemplo n.º 4
0
void showMainControlTable(struct sqlConnection *conn)
/* Put up table with main controls for main page. */
{
struct grp *selGroup;
boolean isWig = FALSE, isPositional = FALSE, isMaf = FALSE, isBedGr = FALSE,
        isChromGraphCt = FALSE, isPal = FALSE, isArray = FALSE, isBam = FALSE, isVcf = FALSE, isHalSnake = FALSE, isLongTabix = FALSE;
boolean gotClade = hGotClade();
struct hTableInfo *hti = NULL;

hPrintf("<TABLE BORDER=0>\n");

/* Print clade, genome and assembly line. */
    {
    if (gotClade)
        {
        hPrintf("<TR><TD><B>clade:</B>\n");
        printCladeListHtml(hGenome(database), onChangeClade());
        nbSpaces(3);
        hPrintf("<B>genome:</B>\n");
        printGenomeListForCladeHtml(database, onChangeOrg());
        }
    else
        {
        hPrintf("<TR><TD><B>genome:</B>\n");
        printGenomeListHtml(database, onChangeOrg());
        }
    nbSpaces(3);
    hPrintf("<B>assembly:</B>\n");
    printAssemblyListHtml(database, onChangeDb());
    hPrintf("</TD></TR>\n");
    }

/* Print group and track line. */
    {
    hPrintf("<TR><TD>");
    selGroup = showGroupField(hgtaGroup, onChangeGroupOrTrack(), conn, hAllowAllTables());
    nbSpaces(3);
    curTrack = showTrackField(selGroup, hgtaTrack, onChangeGroupOrTrack(), FALSE);
    nbSpaces(3);
    boolean hasCustomTracks = FALSE;
    struct trackDb *t;
    for (t = fullTrackList;  t != NULL;  t = t->next)
        {
        if (isCustomTrack(t->table))
            {
            hasCustomTracks = TRUE;
            break;
            }
        }
    hOnClickButton("document.customTrackForm.submit();return false;",
                   hasCustomTracks ? CT_MANAGE_BUTTON_LABEL : CT_ADD_BUTTON_LABEL);

    hPrintf(" ");
    if (hubConnectTableExists())
	hOnClickButton("document.trackHubForm.submit();return false;", "track hubs");

    hPrintf("</TD></TR>\n");
    }

/* Print table line. */
    {
    hPrintf("<TR><TD>");
    curTable = showTableField(curTrack, hgtaTable, TRUE);
    if (isHubTrack(curTable) || (strchr(curTable, '.') == NULL))  /* In same database */
        {
        hti = getHti(database, curTable, conn);
        isPositional = htiIsPositional(hti);
        }
    isLongTabix = isLongTabixTable( curTable);
    isBam = isBamTable( curTable);
    isVcf = isVcfTable(curTable, NULL);
    isWig = isWiggle(database, curTable);
    if (isBigWigTable(curTable))
        {
        isPositional = TRUE;
        isWig = TRUE;
        }
    isHalSnake = isHalTable( curTable);
    isMaf = isMafTable(database, curTrack, curTable);
    isBedGr = isBedGraph(curTable);
    isArray = isMicroarray(curTrack, curTable);
    struct trackDb *tdb = findTdbForTable(database, curTrack, curTable, ctLookupName);
    isPal = isPalCompatible(conn, tdb, curTable);
    nbSpaces(1);
    if (isCustomTrack(curTable))
        {
        isChromGraphCt = isChromGraph(tdb);
        }
    cgiMakeButton(hgtaDoSchema, "describe table schema");
    hPrintf("</TD></TR>\n");
    }

if (curTrack == NULL)
    {
    struct trackDb *tdb = hTrackDbForTrack(database, curTable);
    struct trackDb *cTdb = hCompositeTrackDbForSubtrack(database, tdb);
    if (cTdb)
        curTrack = cTdb;
    else
        curTrack = tdb;
    isMaf = isMafTable(database, curTrack, curTable);
    }

/* Region line */
{
char *regionType = cartUsualString(cart, hgtaRegionType, hgtaRegionTypeGenome);
char *range = cartUsualString(cart, hgtaRange, "");
if (isPositional)
    {
    boolean doEncode = FALSE; 

    if (!trackHubDatabase(database))
	doEncode = sqlTableExists(conn, "encodeRegions");

    hPrintf("<TR><TD><B>region:</B>\n");

    /* If regionType not allowed force it to "genome". */
    if ((sameString(regionType, hgtaRegionTypeUserRegions) &&
	 userRegionsFileName() == NULL) ||
	(sameString(regionType, hgtaRegionTypeEncode) && !doEncode))
	regionType = hgtaRegionTypeGenome;
    // Is "genome" is not allowed because of tdb 'tableBrowser noGenome'?
    boolean disableGenome = ((curTrack && cartTrackDbIsNoGenome(database, curTrack->table)) ||
                             (curTable && cartTrackDbIsNoGenome(database, curTable)));
    // If "genome" is selected but not allowed, force it to "range":
    if (sameString(regionType, hgtaRegionTypeGenome) && disableGenome)
        regionType = hgtaRegionTypeRange;
    jsTrackingVar("regionType", regionType);
    if (disableGenome)
        {
        makeRegionButtonExtraHtml(hgtaRegionTypeGenome, regionType, "DISABLED");
        hPrintf("&nbsp;<span"NO_GENOME_CLASS">genome (unavailable for selected track)</span>"
                "&nbsp;");
        }
    else
        {
        makeRegionButton(hgtaRegionTypeGenome, regionType);
        hPrintf("&nbsp;genome&nbsp;");
        }
    if (doEncode)
        {
	makeRegionButton(hgtaRegionTypeEncode, regionType);
	hPrintf("&nbsp;ENCODE Pilot regions&nbsp;");
	}
    makeRegionButton(hgtaRegionTypeRange, regionType);
    hPrintf("&nbsp;position&nbsp;");
    hPrintf("<INPUT TYPE=TEXT NAME=\"%s\" SIZE=26 VALUE=\"%s\" onFocus=\"%s\">\n",
    	hgtaRange, range, jsRadioUpdate(hgtaRegionType, "regionType", "range"));
    cgiMakeButton(hgtaDoLookupPosition, "lookup");
    hPrintf("&nbsp;");
    if (userRegionsFileName() != NULL)
	{
	makeRegionButton(hgtaRegionTypeUserRegions, regionType);
	hPrintf("&nbsp;defined regions&nbsp;");
	cgiMakeButton(hgtaDoSetUserRegions, "change");
	hPrintf("&nbsp;");
	cgiMakeButton(hgtaDoClearUserRegions, "clear");
	}
    else
	cgiMakeButton(hgtaDoSetUserRegions, "define regions");
    hPrintf("</TD></TR>\n");
    }
else
    {
    /* Need to put at least stubs of cgi variables in for JavaScript to work. */
    jsTrackingVar("regionType", regionType);
    cgiMakeHiddenVar(hgtaRange, range);
    cgiMakeHiddenVar(hgtaRegionType, regionType);
    }

/* Select identifiers line (if applicable). */
if (!isWig && getIdField(database, curTrack, curTable, hti) != NULL)
    {
    hPrintf("<TR><TD><B>identifiers (names/accessions):</B>\n");
    cgiMakeButton(hgtaDoPasteIdentifiers, "paste list");
    hPrintf(" ");
    cgiMakeButton(hgtaDoUploadIdentifiers, "upload list");
    if (identifierFileName() != NULL)
        {
	hPrintf("&nbsp;");
	cgiMakeButton(hgtaDoClearIdentifiers, "clear list");
	}
    hPrintf("</TD></TR>\n");
    }
}

/* microarray options */
/*   button for option page here (median/log-ratio, etc)  */

/* Filter line. */
{
hPrintf("<TR><TD><B>filter:</B>\n");
if (anyFilter())
    {
    cgiMakeButton(hgtaDoFilterPage, "edit");
    hPrintf(" ");
    cgiMakeButton(hgtaDoClearFilter, "clear");
    if (isWig || isBedGr)
	wigShowFilter(conn);
    }
else
    {
    cgiMakeButton(hgtaDoFilterPage, "create");
    }
hPrintf("</TD></TR>\n");
}

/* Composite track subtrack merge line. */
boolean canSubtrackMerge = (curTrack && tdbIsComposite(curTrack) && !isBam && !isVcf && !isLongTabix);
if (canSubtrackMerge)
    {
    hPrintf("<TR><TD><B>subtrack merge:</B>\n");
    if (anySubtrackMerge(database, curTable))
	{
	cgiMakeButton(hgtaDoSubtrackMergePage, "edit");
	hPrintf(" ");
	cgiMakeButton(hgtaDoClearSubtrackMerge, "clear");
	}
    else
	{
	cgiMakeButton(hgtaDoSubtrackMergePage, "create");
	}
    hPrintf("</TD></TR>\n");
    }

/* Intersection line. */
if (isPositional)
    {
    if (anyIntersection())
        {
	hPrintf("<TR><TD><B>intersection with %s:</B>\n",
		cartString(cart, hgtaIntersectTable));
	cgiMakeButton(hgtaDoIntersectPage, "edit");
	hPrintf(" ");
	cgiMakeButton(hgtaDoClearIntersect, "clear");
        hPrintf("</TD></TR>\n");
	}
    else if (canIntersect(database, curTable))
        {
	hPrintf("<TR><TD><B>intersection:</B>\n");
	cgiMakeButton(hgtaDoIntersectPage, "create");
        hPrintf("</TD></TR>\n");
	}
    }

/* Correlation line. */
struct trackDb *tdb = findTdbForTable(database, curTrack, curTable, ctLookupName);
if (correlateTrackTableOK(tdb, curTable))
    {
    char *table2 = cartUsualString(cart, hgtaCorrelateTable, "none");
    hPrintf("<TR><TD><B>correlation:</B>\n");
    if (differentWord(table2, "none") && strlen(table2) && ! isNoGenomeDisabled(database, table2))
        {
        struct grp *groupList = fullGroupList;
        struct grp *selGroup = findSelectedGroup(groupList, hgtaCorrelateGroup);
        struct trackDb *tdb2 = findSelectedTrack(fullTrackList, selGroup,hgtaCorrelateTrack);
        if (tdbIsComposite(tdb2))
            {
	    struct slRef *tdbRefList = trackDbListGetRefsToDescendantLeaves(tdb2->subtracks);
	    struct slRef *tdbRef;
	    for (tdbRef = tdbRefList; tdbRef != NULL; tdbRef = tdbRef->next)
                {
		struct trackDb *subTdb = tdbRef->val;
                if (sameString(table2, subTdb->table))
                    {
                    tdb2 = subTdb;
                    break;
                    }
                }
	    slFreeList(&tdbRefList);
            }
        cgiMakeButton(hgtaDoCorrelatePage, "calculate");
        cgiMakeButton(hgtaDoClearCorrelate, "clear");
        if (tdb2 && tdb2->shortLabel)
            hPrintf("&nbsp;(with:&nbsp;&nbsp;%s)", tdb2->shortLabel);

#ifdef NOT_YET
        /* debugging 	dbg	vvvvv	*/
        if (curTrack && curTrack->type)		/*	dbg	*/
            {
            hPrintf("<BR>&nbsp;(debug:&nbsp;'%s',&nbsp;'%s(%s)')",
                    curTrack->type, tdb2->type, table2);
            }
        /* debugging 	debug	^^^^^	*/
#endif

        }
    else
        cgiMakeButton(hgtaDoCorrelatePage, "create");

    hPrintf("</TD></TR>\n");
    }

/* Print output type line. */
showOutputTypeRow(isWig, isBedGr, isPositional, isMaf, isChromGraphCt, isPal, isArray, isHalSnake);

/* Print output destination line. */
    {
    char *compressType =
	cartUsualString(cart, hgtaCompressType, textOutCompressNone);
    char *fileName = cartUsualString(cart, hgtaOutFileName, "");
    hPrintf("<TR><TD>\n");
    hPrintf("<B>output file:</B>&nbsp;");
    cgiMakeTextVar(hgtaOutFileName, fileName, 29);
    hPrintf("&nbsp;(leave blank to keep output in browser)</TD></TR>\n");
    hPrintf("<TR><TD>\n");
    hPrintf("<B>file type returned:&nbsp;</B>");
    cgiMakeRadioButton(hgtaCompressType, textOutCompressNone,
	sameWord(textOutCompressNone, compressType));
    hPrintf("&nbsp;plain text&nbsp&nbsp");
    cgiMakeRadioButton(hgtaCompressType, textOutCompressGzip,
	sameWord(textOutCompressGzip, compressType));
    hPrintf("&nbsp;gzip compressed");
    hPrintf("</TD></TR>\n");
    }

hPrintf("</TABLE>\n");


/* Submit buttons. */
    {
    hPrintf("<BR>\n");
    if (isWig || isBam || isVcf || isLongTabix)
	{
	char *name;
	extern char *maxOutMenu[];
	char *maxOutput = maxOutMenu[0];

	if (isCustomTrack(curTable))
	    name=filterFieldVarName("ct", curTable, "_", filterMaxOutputVar);
	else
	    name=filterFieldVarName(database,curTable, "_",filterMaxOutputVar);

	maxOutput = cartUsualString(cart, name, maxOutMenu[0]);

	if (isWig)
	    hPrintf(
		"<I>Note: to return more than %s lines, change the filter setting"
		" (above). The entire data set may be available for download as"
		" a very large file that contains the original data values (not"
		" compressed into the wiggle format) -- see the Downloads page."
		"</I><BR>", maxOutput);
	else if (isBam || isVcf || isLongTabix)
	    hPrintf(
		"<I>Note: to return more than %s lines, change the filter setting"
		" (above). Please consider downloading the entire data from our Download pages."
		"</I><BR>", maxOutput);
	}
    else if (anySubtrackMerge(database, curTable) || anyIntersection())
	{
	hPrintf("<I>Note: The all fields and selected fields output formats "
		"are not available when a%s has been specified.</I><BR>",
		canSubtrackMerge ? " subtrack merge or intersection" : "n intersection");
	}
    cgiMakeButton(hgtaDoTopSubmit, "get output");
    hPrintf(" ");
    if (isPositional || isWig)
	{
	cgiMakeButton(hgtaDoSummaryStats, "summary/statistics");
	hPrintf(" ");
	}

#ifdef SOMETIMES
    hPrintf(" ");
    cgiMakeButton(hgtaDoTest, "test");
#endif /* SOMETIMES */
    }
hPrintf("<P>"
	"To reset <B>all</B> user cart settings (including custom tracks), \n"
	"<A HREF=\"/cgi-bin/cartReset?destination=%s\">click here</A>.\n",
	getScriptName());

}
Exemplo n.º 5
0
static struct slRef *advancedSearchForTracks(struct sqlConnection *conn,struct group *groupList,
                                             char *nameSearch, char *typeSearch, char *descSearch,
                                             char *groupSearch, struct slPair *mdbPairs)
// Performs the advanced search and returns the found tracks.
{
int tracksFound = 0;
struct slRef *tracks = NULL;
int numMetadataNonEmpty = 0;
struct slPair *pair = mdbPairs;
for (; pair!= NULL;pair=pair->next)
    {
    if (!isEmpty((char *)(pair->val)))
        numMetadataNonEmpty++;
    }

if (!isEmpty(groupSearch) && sameString(groupSearch,ANYLABEL))
    groupSearch = NULL;
if (!isEmpty(typeSearch) && sameString(typeSearch,ANYLABEL))
    typeSearch = NULL;

if (isEmpty(nameSearch) && isEmpty(typeSearch) && isEmpty(descSearch)
&& isEmpty(groupSearch) && numMetadataNonEmpty == 0)
    return NULL;

// First do the metaDb searches, which can be done quickly for all tracks with db queries.
struct hash *matchingTracks = NULL;

if (numMetadataNonEmpty)
    {
    struct mdbObj *mdbObj, *mdbObjs = mdbObjRepeatedSearch(conn,mdbPairs,TRUE,FALSE);
    if (mdbObjs)
        {
        for (mdbObj = mdbObjs; mdbObj != NULL; mdbObj = mdbObj->next)
            {
            if (matchingTracks == NULL)
                matchingTracks = newHash(0);
            hashAddInt(matchingTracks, mdbObj->obj, 1);
            }
        mdbObjsFree(&mdbObjs);
        }
    if (matchingTracks == NULL)
        return NULL;
    }

// Set the word lists up once
struct slName *nameList = NULL;
if (!isEmpty(nameSearch))
    nameList = slNameListOfUniqueWords(cloneString(nameSearch),TRUE); // TRUE means respect quotes
struct slName *descList = NULL;
if (!isEmpty(descSearch))
    descList = slNameListOfUniqueWords(cloneString(descSearch),TRUE);

struct group *group;
for (group = groupList; group != NULL; group = group->next)
    {
    if (isEmpty(groupSearch) || sameString(group->name, groupSearch))
        {
        if (group->trackList == NULL)
            continue;

        struct trackRef *tr;
        for (tr = group->trackList; tr != NULL; tr = tr->next)
            {
            struct track *track = tr->track;
            char *trackType = cloneFirstWord(track->tdb->type); // will be spilled
            if ((matchingTracks == NULL || hashLookup(matchingTracks, track->track) != NULL)
            && (  isEmpty(typeSearch)
               || (sameWord(typeSearch, trackType) && !tdbIsComposite(track->tdb)))
            && (isEmpty(nameSearch) || searchNameMatches(track->tdb, nameList))
            && (isEmpty(descSearch) || searchDescriptionMatches(track->tdb, descList)))
                {
                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)
                    {
                    trackType = cloneFirstWord(subTrack->tdb->type); // will be spilled
                    if (  (matchingTracks == NULL
                       || hashLookup(matchingTracks, subTrack->track) != NULL)
                    && (isEmpty(typeSearch) || sameWord(typeSearch, trackType))
                    && (isEmpty(nameSearch) || searchNameMatches(subTrack->tdb, nameList))
                    && (isEmpty(descSearch) // subtracks inherit description
                        || searchDescriptionMatches(subTrack->tdb, descList)
                        || (tdbIsCompositeChild(subTrack->tdb) && subTrack->parent
                            && searchDescriptionMatches(subTrack->parent->tdb, descList))))
                        {
                        if (track != NULL)
                            {
                            tracksFound++;
                            refAdd(&tracks, subTrack);
                            }
                        else
                            warn("found subtrack is NULL.");
                        }
                    }
                }
            }
        }
    }

return tracks;
}