483878d23907da9695facf9610aa986208f0a1d7 galt Fri May 9 11:26:36 2025 -0700 Revert "Simplify use of BLAT results by always creating a custom track. fixes #32751" This reverts commit a5a45be7e379b64278e667f80c75cc0cc41d9ea4. diff --git src/hg/hgBlat/hgBlat.c src/hg/hgBlat/hgBlat.c index 691ccbf9cc9..220789493a5 100644 --- src/hg/hgBlat/hgBlat.c +++ src/hg/hgBlat/hgBlat.c @@ -490,40 +490,61 @@ return same; } boolean allDigits(char *s) /* Return TRUE if s is all digits */ { char c; while ((c = *s++) != 0) if (!isdigit(c)) return FALSE; return TRUE; } +void printLuckyRedirect(char *browserUrl, struct psl *psl, char *database, char *pslName, + char *faName, char *uiState, char *unhideTrack) +/* Print out a very short page that redirects us. */ +{ +char url[1024]; +safef(url, sizeof(url), "%s?position=%s:%d-%d&db=%s&ss=%s+%s&%s%s", + browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, + pslName, faName, uiState, unhideTrack); +/* htmlStart("Redirecting"); */ +/* Odd it appears that we've already printed the Content-Typ:text/html line + but I can't figure out where... */ +htmStart(stdout, "Redirecting"); +jsInlineF("location.replace('%s');\n", url); +printf("<noscript>No javascript support:<br>Click <a href='%s'>here</a> for browser.</noscript>\n", url); +htmlEnd(); + +} + + /* forward declaration to reduce churn */ static void getCustomName(char *database, struct cart *cart, struct psl *psl, char **pName, char **pDescription); void showAliPlaces(char *pslName, char *faName, char *customText, char *database, enum gfType qType, enum gfType tType, char *organism, boolean feelingLucky) /* Show all the places that align. */ { +boolean useBigPsl = cfgOptionBooleanDefault("useBlatBigPsl", TRUE); struct lineFile *lf = pslFileOpen(pslName); struct psl *pslList = NULL, *psl; +char *browserUrl = hgTracksName(); char *hgcUrl = hgcName(); char uiState[64]; char *vis; char unhideTrack[64]; char *sort = cartUsualString(cart, "sort", pslSortList[0]); char *output = cartUsualString(cart, "output", outputList[0]); boolean pslOut = startsWith("psl", output); boolean pslRawOut = sameWord("pslRaw", output); boolean jsonOut = sameWord(output, "json"); sprintf(uiState, "%s=%s", cartSessionVarName(), cartSessionId(cart)); /* If user has hidden BLAT track, add a setting that will unhide the track if user clicks on a browser link. */ vis = cartOptionalString(cart, "hgUserPsl"); @@ -537,84 +558,199 @@ if (psl->match >= minMatchShown) slAddHead(&pslList, psl); } lineFileClose(&lf); if (pslList == NULL && !jsonOut) { printf("<table><tr><td><hr>Sorry, no matches found"); if (!allResults) printf(" (with a score of at least %d)", minMatchShown); printf("<hr><td></tr></table>\n"); return; } pslSortListByVar(&pslList, sort); -if (pslOut) +if(feelingLucky) + { + /* If we found something jump browser to there. */ + if(slCount(pslList) > 0) + printLuckyRedirect(browserUrl, pslList, database, pslName, faName, uiState, unhideTrack); + /* Otherwise call ourselves again not feeling lucky to print empty + results. */ + else + { + cartWebStart(cart, trackHubSkipHubName(database), "%s (%s) BLAT Results", trackHubSkipHubName(organism), trackHubSkipHubName(database)); + showAliPlaces(pslName, faName, customText, database, qType, tType, organism, FALSE); + cartWebEnd(); + } + } +else if (pslOut) { if (!pslRawOut) printf("<TT><PRE>"); if (!sameString(output, "psl no header")) pslxWriteHead(stdout, qType, tType); for (psl = pslList; psl != NULL; psl = psl->next) pslTabOut(psl, stdout); if (pslRawOut) exit(0); printf("<TT><PRE>"); printf("</PRE></TT>"); } else if (jsonOut) { webStartText(); pslWriteAllJson(pslList, stdout, database, TRUE); exit(0); } else // hyperlink { + printf("<H2>BLAT Search Results</H2>"); + char* posStr = cartOptionalString(cart, "position"); + if (posStr != NULL) + printf("<P>Go back to <A HREF=\"%s\">%s</A> on the Genome Browser.</P>\n", browserUrl, posStr); + if (useBigPsl) + { char *trackName = NULL; char *trackDescription = NULL; getCustomName(database, cart, pslList, &trackName, &trackDescription); psl = pslList; printf( "<DIV STYLE=\"display:block;\"><FORM ACTION=\"%s\" METHOD=\"%s\" NAME=\"customTrackForm\">\n", hgcUrl,cartUsualString(cart, "formMethod", "POST")); printf("<INPUT TYPE=\"hidden\" name=\"o\" value=\"%d\" />\n",psl->tStart); printf("<INPUT TYPE=\"hidden\" name=\"t\" value=\"%d\" />\n",psl->tEnd); printf("<INPUT TYPE=\"hidden\" name=\"g\" value=\"%s\" />\n","buildBigPsl"); printf("<INPUT TYPE=\"hidden\" name=\"i\" value=\"%s %s %s\" />\n",pslName,faName,psl->qName); printf("<INPUT TYPE=\"hidden\" name=\"c\" value=\"%s\" />\n",psl->tName); printf("<INPUT TYPE=\"hidden\" name=\"l\" value=\"%d\" />\n",psl->tStart); printf("<INPUT TYPE=\"hidden\" name=\"r\" value=\"%d\" />\n",psl->tEnd); printf("<INPUT TYPE=\"hidden\" name=\"%s\" value=\"%s\" />\n", cartSessionVarName(), cartSessionId(cart)); if (pslIsProtein(psl)) printf("<INPUT TYPE=\"hidden\" name=\"isProt\" value=\"on\" />\n"); - if (feelingLucky) - printf("<INPUT TYPE=\"hidden\" name=\"feelingLucky\" value=\"on\" />\n"); + printf("<TABLE><TR><TD>Custom track name: "); + cgiMakeTextVar( "trackName", trackName, 30); + printf("</TD></TR>"); + + printf("<TR><TD> Custom track description: "); + cgiMakeTextVar( "trackDescription", trackDescription,50); + printf("</TD></TR>"); + printf("<TR><TD><INPUT TYPE=SUBMIT NAME=Submit VALUE=\"Create a stable custom track with these results\">\n"); + printInfoIcon("The BLAT results below are temporary and will be replaced by your next BLAT search. " + "However, when saved as a custom track with the button on the left, BLAT results are stored on our " + "servers and can be saved as stable session (View > My Sessions) links that can be shared via email or in manuscripts. " + "\n<p>We have never cleaned up the data under stable session links so far. " + "To reduce track clutter in your own sessions, you can delete BLAT custom tracks from the main Genome Browser " + "view using the little trash icon next to each custom track.</p>"); + puts("</TD></TR>"); + printf("</TABLE></FORM></DIV>"); + } - printf("<INPUT TYPE=\"hidden\" name=\"trackName\" value=\"%s\" />\n", trackName); + printf("<DIV STYLE=\"display:block;\"><PRE>"); - printf("<INPUT TYPE=\"hidden\" name=\"trackDescription\" value=\"%s\" />\n", trackDescription); + // find maximum query name size for padding calculations and + // find maximum target chrom name size for padding calculations + int maxQChromNameSize = 0; + int maxTChromNameSize = 0; + for (psl = pslList; psl != NULL; psl = psl->next) + { + int qLen = strlen(psl->qName); + maxQChromNameSize = max(maxQChromNameSize,qLen); + int tLen = strlen(psl->tName); + maxTChromNameSize = max(maxTChromNameSize,tLen); + } + maxQChromNameSize = max(maxQChromNameSize,5); + maxTChromNameSize = max(maxTChromNameSize,5); - printf("<TR><TD><INPUT TYPE=SUBMIT NAME=Submit VALUE=\"Create custom track\">\n"); - puts("</TD></TR>"); - printf("</TABLE></FORM></DIV>"); - jsInline("$(document).ready(function() {\n" - "$(\"input[value='Create custom track']\").trigger('click');\n" - "});\n"); + printf(" ACTIONS QUERY "); + + spaceOut(stdout, maxQChromNameSize - 5); + + printf("SCORE START END QSIZE IDENTITY CHROM "); + spaceOut(stdout, maxTChromNameSize - 5); + + printf(" STRAND START END SPAN\n"); + + printf("----------------------------------------------------------------------------------------------------------"); + repeatCharOut(stdout, '-', maxQChromNameSize - 5); + repeatCharOut(stdout, '-', maxTChromNameSize - 5); + + printf("\n"); + + for (psl = pslList; psl != NULL; psl = psl->next) + { + char *browserHelp = "Open a Genome Browser showing this match"; + char *helpText = "Open a Genome Browser with the BLAT results, but in a new internet browser tab"; + // XX putting SVG into C code like this is ugly. define somewhere? maybe have globals for these? + char *icon = "<svg style='height:10px; padding-left:2px' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d='M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z'/></svg>"; + if (customText) + { + printf("<A TITLE='%s' HREF=\"%s?position=%s:%d-%d&db=%s&hgt.customText=%s&%s%s\">browser</A> ", + browserHelp, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, + customText, uiState, unhideTrack); + printf("<A TITLE='%s' TARGET=_BLANK HREF=\"%s?position=%s:%d-%d&db=%s&hgt.customText=%s&%s\">new tab%s</A> ", + helpText, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, + customText, unhideTrack, icon); + } + else + { + printf("<A TITLE='%s' HREF=\"%s?position=%s:%d-%d&db=%s&ss=%s+%s&%s%s\">browser</A> ", + browserHelp, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, + pslName, faName, uiState, unhideTrack); + printf("<A TITLE='%s' TARGET=_BLANK HREF=\"%s?position=%s:%d-%d&db=%s&ss=%s+%s&%s\">new tab%s</A> ", + helpText, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, + pslName, faName, unhideTrack, icon); + } + printf("<A title='Show query sequence, genome hit and sequence alignment' " + "HREF=\"%s?o=%d&g=htcUserAli&i=%s+%s+%s&c=%s&l=%d&r=%d&db=%s&%s\">", + hgcUrl, psl->tStart, pslName, cgiEncode(faName), psl->qName, psl->tName, + psl->tStart, psl->tEnd, database, uiState); + printf("details</A> "); + + printf("%s",psl->qName); + spaceOut(stdout, maxQChromNameSize - strlen(psl->qName)); + printf(" %5d %5d %5d %5d %5.1f%% ", + pslScore(psl), psl->qStart+1, psl->qEnd, psl->qSize, + 100.0 - pslCalcMilliBad(psl, TRUE) * 0.1); + char *displayChromName = chromAliasGetDisplayChrom(database, cart, psl->tName); + printf("%s",displayChromName); + spaceOut(stdout, maxTChromNameSize - strlen(displayChromName)); + printf(" %-2s %9d %9d %6d", + psl->strand, psl->tStart+1, psl->tEnd, + psl->tEnd - psl->tStart); + + // if you modify this, also modify hgPcr.c:doQuery, which implements a similar feature + char *seq = psl->tName; + if (endsWith(seq, "_fix")) + printf(" <A target=_blank HREF=\"../FAQ/FAQdownloads.html#downloadFix\">What is chrom_fix?</A>"); + else if (endsWith(seq, "_alt")) + printf(" <A target=_blank HREF=\"../FAQ/FAQdownloads.html#downloadAlt\">What is chrom_alt?</A>"); + else if (endsWith(seq, "_random")) + printf(" <A target=_blank HREF=\"../FAQ/FAQdownloads.html#download10\">What is chrom_random?</A>"); + else if (startsWith(seq, "chrUn")) + printf(" <A target=_blank HREF=\"../FAQ/FAQdownloads.html#download11\">What is a chrUn sequence?</A>"); + printf("\n"); + } + printf("</PRE>\n"); + webNewSection("Help"); + puts("<P style=\"text-align:left\"><A target=_blank HREF=\"../FAQ/FAQblat.html#blat1b\">Missing a match?</A><br>"); + puts("<A target=_blank HREF=\"../FAQ/FAQblat.html#blat1c\">What is chr_alt & chr_fix?</A></P>\n"); + puts("</DIV>\n"); } pslFreeList(&pslList); } void trimUniq(bioSeq *seqList) /* Check that all seq's in list have a unique name. Try and * abbreviate longer sequence names. */ { struct hash *hash = newHash(0); bioSeq *seq; for (seq = seqList; seq != NULL; seq = seq->next) { char *saferString = needMem(strlen(seq->name)+1); @@ -767,50 +903,48 @@ safef(buffer, sizeof buffer, "%s (%d)", name, count + 1); } return cloneString(buffer); } static void getCustomName(char *database, struct cart *cart, struct psl *psl, char **pName, char **pDescription) // Find a track name that isn't currently a custom track. Also fill in description. { struct slName *names = namesInPsl(psl); char shortName[4096]; char description[4096]; unsigned count = slCount(names); - if (count == 1) { safef(shortName, sizeof shortName, "blat %s", names->name); safef(description, sizeof description, "blat on %s", names->name); } else if (count == 2) { safef(shortName, sizeof shortName, "blat %s+%d", names->name, count - 1); safef(description, sizeof description, "blat on %d queries (%s, %s)", count, names->name, names->next->name); } else { safef(shortName, sizeof shortName, "blat %s+%d", names->name, count - 1); safef(description, sizeof description, "blat on %d queries (%s, %s, ...)", count, names->name, names->next->name); } *pName = makeNameUnique(shortName, database, cart); *pDescription = cloneString(description); - } void queryServer(char *host, char *port, char *db, struct dnaSeq *seq, char *type, char *xType, boolean complex, boolean isProt, boolean queryRC, int seqNumber, char *genomeDataDir) /* Send simple query to server and report results. (no, it doesn't do this) * queryRC is true when the query has been reverse-complemented */ { struct genomeHits *gH; AllocVar(gH); gH->host=cloneString(host); gH->port=cloneString(port); gH->db = cloneString(db); gH->genome = cloneString(hGenome(db)); @@ -1356,31 +1490,31 @@ boolean feelingLucky = cgiBoolean("Lucky") && !allGenomes; char *xType = NULL; if (allGenomes) { db = database; genome = organism; } else getDbAndGenome(cart, &db, &genome, oldVars); char *output = cgiOptionalString("output"); boolean isJson= sameWordOk(output, "json"); boolean isPslRaw= sameWordOk(output, "pslRaw"); -if (!allGenomes && !isJson && !isPslRaw) +if (!feelingLucky && !allGenomes && !isJson && !isPslRaw) cartWebStart(cart, db, "%s (%s) BLAT Results", trackHubSkipHubName(organism), trackHubSkipHubName(db)); seqList = faSeqListFromMemTextRaw(seqLetters); /* Load user sequence and figure out if it is DNA or protein. */ if (sameWord(type, "DNA")) { isTx = FALSE; xType = "dna"; } else if (sameWord(type, "translated RNA")) { isTx = TRUE; isTxTx = TRUE; xType = "rnax"; @@ -1656,31 +1790,31 @@ { gfAlignStrand(conn, serve->nibDir, seq, TRUE, minMatchShown, tFileCache, gvo); } } gfOutputQuery(gvo, f); ++seqNumber; } carefulClose(&f); if (!allGenomes) { showAliPlaces(pslTn.forCgi, faTn.forCgi, NULL, serve->db, qType, tType, organism, feelingLucky); } -if(!allGenomes) +if(!feelingLucky && !allGenomes) cartWebEnd(); gfFileCacheFree(&tFileCache); } void askForSeq(char *organism, char *db) /* Put up a little form that asks for sequence. * Call self.... */ { /* ignore struct serverTable* return, but can error out if not found */ findServer(db, FALSE); /* JavaScript to update form when org changes */ char *onChangeText = "" "document.mainForm.changeInfo.value='orgChange';"