469c053163292224b75ee9ebb41117f694aed36b
chmalee
  Tue Apr 18 16:12:07 2023 -0700
Add a checkbox to enable/disable saving multiple hgPcr searches into the same track, refs #30925

diff --git src/hg/hgPcr/hgPcr.c src/hg/hgPcr/hgPcr.c
index b19e076..aed2967 100644
--- src/hg/hgPcr/hgPcr.c
+++ src/hg/hgPcr/hgPcr.c
@@ -216,30 +216,31 @@
 "PCR primers, using an indexing strategy for fast performance.\n"
 "See an example\n"
 "<a href='https://youtu.be/U8_QYwmdGYU'"
 "target='_blank'>video</a>\n"
 "on our YouTube channel.\n"
 "\n"
 "<H3>Configuration Options</H3>\n"
 "<B>Genome and Assembly</B> - The sequence database to search.<BR>\n"
 "<B>Target</B> - If available, choose to query transcribed sequences.<BR>\n" 
 "<B>Forward Primer</B> - Must be at least 15 bases in length.<BR>\n"
 "<B>Reverse Primer</B> - On the opposite strand from the forward primer. Minimum length of 15 bases.<BR>\n"
 "<B>Max Product Size</B> - Maximum size of amplified region.<BR>\n"
 "<B>Min Perfect Match</B> - Number of bases that match exactly on 3' end of primers.  Minimum match size is 15.<BR>\n"
 "<B>Min Good Match</B> - Number of bases on 3' end of primers where at least 2 out of 3 bases match.<BR>\n"
 "<B>Flip Reverse Primer</B> - Invert the sequence order of the reverse primer and complement it.<BR>\n"
+"<B>Append to existing PCR result</B> - Add this PCR result list to the currently existing track of PCR results.<BR>\n"
 "\n"
 "<H3>Output</H3>\n"
 "When successful, the search returns a sequence output file in fasta format \n"
 "containing all sequence in the database that lie between and include the \n"
 "primer pair.  The fasta header describes the region in the database\n"
 "and the primers.  The fasta body is capitalized in areas where the primer\n"
 "sequence matches the database sequence and in lower-case elsewhere.  Here\n"
 "is an example from human:<BR>\n"
 "<TT><PRE>\n"
 ">chr22:31000551+31001000  TAACAGATTGATGATGCATGAAATGGG CCCATGAGTGGCTCCTAAAGCAGCTGC\n"
 "TtACAGATTGATGATGCATGAAATGGGgggtggccaggggtggggggtga\n"
 "gactgcagagaaaggcagggctggttcataacaagctttgtgcgtcccaa\n"
 "tatgacagctgaagttttccaggggctgatggtgagccagtgagggtaag\n"
 "tacacagaacatcctagagaaaccctcattccttaaagattaaaaataaa\n"
 "gacttgctgtctgtaagggattggattatcctatttgagaaattctgtta\n"
@@ -377,31 +378,31 @@
     }
 
 /* If no server for db, change db. */
 if (!gotDb)
     {
     if (differentString(db, orgServer->db))
 	printf("<HR><P><EM><B>Note:</B> In-Silico PCR is not available for %s %s; "
 	       "defaulting to %s %s</EM></P><HR>\n",
 	       hGenome(db), hFreezeDate(db), organism, hFreezeDate(orgServer->db));
     *pDb = db = orgServer->db;
     }
 }
 
 void doGetPrimers(char *db, char *organism, struct pcrServer *serverList,
 	char *fPrimer, char *rPrimer, int maxSize, int minPerfect, int minGood,
-	boolean flipReverse)
+	boolean flipReverse, boolean appendToResults)
 /* Put up form to get primers. */
 {
 redoDbAndOrgIfNoServer(serverList, &db, &organism);
 struct sqlConnection *conn = hConnectCentral();
 boolean gotTargetDb = sqlTableExists(conn, "targetDb");
 hDisconnectCentral(&conn);
 
 
 printf("<FORM ACTION=\"../cgi-bin/hgPcr\" METHOD=\"GET\" NAME=\"mainForm\">\n");
 cartSaveSession(cart);
 
 printf("<TABLE BORDER=0 WIDTH=\"96%%\" COLS=7><TR>\n");
 
 printf("%s", "<TD><CENTER>\n");
 printf("Genome:<BR>");
@@ -458,30 +459,36 @@
 cgiMakeIntVar("wp_perfect", minPerfect, 2);
 printf("%s", "</CENTER></TD>\n");
 
 jsOnEventById("click", "Submit", "if ($('#wp_r').val()==='' || $('#wp_f').val()==='') "\
         "{ alert('Please specify at least a forward and reverse primer. Both input boxes need to be filled out.'); event.preventDefault(); }");
 
 printf("%s", "<TD><CENTER>\n");
 printf(" Min Good Match: ");
 cgiMakeIntVar("wp_good", minGood, 2);
 printf("%s", "</CENTER></TD>\n");
 
 printf("%s", "<TD><CENTER>\n");
 printf(" Flip Reverse Primer: ");
 cgiMakeCheckBox("wp_flipReverse", flipReverse);
 printf("%s", "</CENTER></TD>\n");
+
+printf("%s", "<TD><CENTER>\n");
+printf(" Append to existing PCR result: ");
+cgiMakeCheckBox("wp_append", appendToResults);
+printf("%s", "</CENTER></TD>\n");
+
 printf("</TR></TABLE><BR>");
 
 printf("</FORM>\n");
 
 /* Put up a second form who's sole purpose is to preserve state
  * when the user flips the genome button. */
 printf("<FORM ACTION=\"../cgi-bin/hgPcr\" METHOD=\"GET\" NAME=\"orgForm\">"
        "<input type=\"hidden\" name=\"wp_target\" value=\"\">\n"
        "<input type=\"hidden\" name=\"db\" value=\"\">\n"
        "<input type=\"hidden\" name=\"org\" value=\"\">\n"
        "<input type=\"hidden\" name=\"wp_f\" value=\"\">\n"
        "<input type=\"hidden\" name=\"wp_r\" value=\"\">\n"
        "<input type=\"hidden\" name=\"wp_size\" value=\"\">\n"
        "<input type=\"hidden\" name=\"wp_perfect\" value=\"\">\n"
        "<input type=\"hidden\" name=\"wp_good\" value=\"\">\n"
@@ -498,38 +505,38 @@
 "for academic, personal, and non-profit purposes.  Non-exclusive commercial\n"
 "licenses are also available.  Contact Jim for details.</P>\n");
 }
 
 void writePrimers(struct gfPcrOutput *gpo, char *fileName)
 /* Write primer sequences to file.  Look at only the first gpo because there
  * is only one set of primers in the input form. */
 {
 if (gpo == NULL)
     return;
 FILE *f = mustOpen(fileName, "a");
 fprintf(f, "%s\t%s\n", gpo->fPrimer, gpo->rPrimer);
 carefulClose(&f);
 }
 
-void writePcrResultTrack(struct gfPcrOutput *gpoList, char *db, char *target)
+void writePcrResultTrack(struct gfPcrOutput *gpoList, char *db, char *target, boolean appendToResults)
 /* Write trash files and store their name in a cart variable. */
 {
 char *cartVar = pcrResultCartVar(db);
 struct tempName bedTn, primerTn;
 char buf[2048];
 char *pslFile, *txtFile, *cartResult;
-if ( (cartResult = cartOptionalString(cart, cartVar)) != NULL)
+if ( (cartResult = cartOptionalString(cart, cartVar)) != NULL && appendToResults)
     {
     char *pcrFiles[3];
     chopByWhite(cloneString(cartResult), pcrFiles, 3);
     pslFile = pcrFiles[0];
     txtFile = pcrFiles[1];
     gfPcrOutputWriteAll(gpoList, "psl", NULL, pslFile);
     writePrimers(gpoList, txtFile);
     }
 else
     {
     trashDirFile(&bedTn, "hgPcr", "hgPcr", ".psl");
     trashDirFile(&primerTn, "hgPcr", "hgPcr", ".txt");
     gfPcrOutputWriteAll(gpoList, "psl", NULL, bedTn.forCgi);
     writePrimers(gpoList, primerTn.forCgi);
     if (isNotEmpty(target))
@@ -567,63 +574,63 @@
 
     if (isFix || isRandom || isAlt || isChrUn)
         webNewSection("Notes on the results above");
 
     if (isFix)
         printf("<A target=_blank HREF=\"../FAQ/FAQdownloads#downloadFix\">What is chrom_fix?</A><BR>");
     if (isAlt)
         printf("<A target=_blank HREF=\"../FAQ/FAQdownloads#downloadAlt\">What is chrom_alt?</A><BR>");
     if (isRandom)
         printf("<A target=_blank HREF=\"../FAQ/FAQdownloads#download10\">What is chrom_random?</A><BR>");
     if (isChrUn)
         printf("<A target=_blank HREF=\"../FAQ/FAQdownloads#download11\">What is a chrUn sequence?</A><BR>");
 }
 
 void doQuery(struct pcrServer *server, struct gfPcrInput *gpi,
-	     int maxSize, int minPerfect, int minGood)
+	     int maxSize, int minPerfect, int minGood, boolean appendToResults)
 /* Send a query to a genomic assembly PCR server and print the results. */
 {
 struct gfConnection *conn = gfConnect(server->host, server->port,
                                       trackHubDatabaseToGenome(server->db), server->genomeDataDir);
 struct gfPcrOutput *gpoList =
     gfPcrViaNet(conn, server->seqDir, gpi,
 		maxSize, minPerfect, minGood);
 
 
 if (gpoList != NULL)
     {
     char urlFormat[2048];
     safef(urlFormat, sizeof(urlFormat), "%s?%s&db=%s&position=%%s:%%d-%%d"
 	  "&hgPcrResult=pack", 
 	  hgTracksName(), cartSidUrlString(cart), server->db);
     printf("<TT><PRE>");
     gfPcrOutputWriteAll(gpoList, "fa", urlFormat, "stdout");
     printf("</PRE></TT>");
     
     printHelpLinks(gpoList);
-    writePcrResultTrack(gpoList, server->db, NULL);
+    writePcrResultTrack(gpoList, server->db, NULL, appendToResults);
     }
 else
     {
     printf("No matches to %s %s in %s %s", gpi->fPrimer, gpi->rPrimer, 
 	   server->genome, server->description);
     }
 gfDisconnect(&conn);
 }
 
 void doTargetQuery(struct targetPcrServer *server, struct gfPcrInput *gpi,
-		   int maxSize, int minPerfect, int minGood)
+		   int maxSize, int minPerfect, int minGood, boolean appendToResults)
 /* Send a query to a non-genomic target PCR server and print the results. */
 {
 struct gfConnection *conn = gfConnect(server->host, server->port, NULL, NULL);
 struct gfPcrOutput *gpoList;
 char seqDir[PATH_LEN];
 splitPath(server->targetDb->seqFile, seqDir, NULL, NULL);
 if (endsWith("/", seqDir))
     seqDir[strlen(seqDir) - 1] = '\0';
 gpoList = gfPcrViaNet(conn, seqDir, gpi, maxSize, minPerfect, minGood);
 
 if (gpoList != NULL)
     {
     struct gfPcrOutput *gpo;
     char urlFormat[2048];
     printf("The sequences and coordinates shown below are from %s, "
@@ -635,67 +642,67 @@
     for (gpo = gpoList;  gpo != NULL;  gpo = gpo->next)
 	{
 	/* Not used as a format here; we modify the name used for position: */
 	safef(urlFormat, sizeof(urlFormat), "%s?%s&db=%s&position=%s"
 	      "&hgPcrResult=pack", 
 	      hgTracksName(), cartSidUrlString(cart), server->targetDb->db,
 	      pcrResultItemAccession(gpo->seqName));
 	if (gpo->strand == '-')
 	    printf("<EM>Warning: this amplification is on the reverse-"
 		   "complement of %s</EM>.\n", gpo->seqName);
 	gfPcrOutputWriteOne(gpo, "fa", urlFormat, stdout);
 	printf("\n");
 	}
 
     printf("</PRE></TT>");
-    writePcrResultTrack(gpoList, server->targetDb->db, server->targetDb->name);
+    writePcrResultTrack(gpoList, server->targetDb->db, server->targetDb->name, appendToResults);
     }
 else
     {
     printf("No matches to %s %s in %s", gpi->fPrimer, gpi->rPrimer, 
 	   server->targetDb->description);
     }
 gfDisconnect(&conn);
 }
 
 boolean doPcr(struct pcrServer *server, struct targetPcrServer *targetServer,
 	char *fPrimer, char *rPrimer, 
-	int maxSize, int minPerfect, int minGood, boolean flipReverse)
+	int maxSize, int minPerfect, int minGood, boolean flipReverse, boolean appendToResults)
 /* Do the PCR, and show results. */
 {
 struct errCatch *errCatch = errCatchNew();
 boolean ok = FALSE;
 
 if (issueBotWarning)
     {
     char *ip = getenv("REMOTE_ADDR");
     botDelayMessage(ip, botDelayMillis);
     }
 
 if (flipReverse)
     reverseComplement(rPrimer, strlen(rPrimer));
 if (errCatchStart(errCatch))
     {
     struct gfPcrInput *gpi;
 
     AllocVar(gpi);
     gpi->fPrimer = fPrimer;
     gpi->rPrimer = rPrimer;
     if (server != NULL)
-	doQuery(server, gpi, maxSize, minPerfect, minGood);
+	doQuery(server, gpi, maxSize, minPerfect, minGood, appendToResults);
     if (targetServer != NULL)
-	doTargetQuery(targetServer, gpi, maxSize, minPerfect, minGood);
+	doTargetQuery(targetServer, gpi, maxSize, minPerfect, minGood, appendToResults);
     ok = TRUE;
     }
 errCatchEnd(errCatch);
 if (errCatch->gotError)
     warn("%s", errCatch->message->string);
 errCatchFree(&errCatch); 
 if (flipReverse)
     reverseComplement(rPrimer, strlen(rPrimer));
 webNewSection("Primer Melting Temperatures");
 printf("<TT>");
 printf("<B>Forward:</B> %4.1f C %s<BR>\n", oligoTm(fPrimer, 50.0, 50.0), fPrimer);
 printf("<B>Reverse:</B> %4.1f C %s<BR>\n", oligoTm(rPrimer, 50.0, 50.0), rPrimer);
 printf("</TT>");
 printf("The temperature calculations are done assuming 50 mM salt and 50 nM annealing "
        "oligo concentration.  The code to calculate the melting temp comes from "
@@ -705,30 +712,31 @@
 printf("<a href='../FAQ/FAQblat.html#blat1c'>What is chr_alt & chr_fix?</a><BR>");
 printf("<a href='../FAQ/FAQblat.html#blat5'>Replicating in-Silico PCR results on local machine</a>");
 return ok;
 }
 
 void dispatch()
 /* Look at input variables and figure out which page to show. */
 {
 char *db, *organism;
 int maxSize = 4000;
 int minPerfect = 15;
 int minGood = 15;
 char *fPrimer = cartUsualString(cart, "wp_f", "");
 char *rPrimer = cartUsualString(cart, "wp_r", "");
 boolean flipReverse = cartUsualBoolean(cart, "wp_flipReverse", FALSE);
+boolean appendToResults = cartUsualBoolean(cart, "wp_append", TRUE);
 struct pcrServer *serverList = getServerList();
 
 getDbAndGenome(cart, &db, &organism, oldVars);
 
 /* Get variables. */
 maxSize = cartUsualInt(cart, "wp_size", maxSize);
 minPerfect = cartUsualInt(cart, "wp_perfect", minPerfect);
 minGood = cartUsualInt(cart, "wp_good", minGood);
 if (minPerfect < 15)
      minPerfect = 15;
 if (minGood < minPerfect)
      minGood = minPerfect;
 
 /* Decide based on transient variables what page to put up. 
  * By default put up get primer page. */
@@ -739,35 +747,35 @@
     struct targetPcrServer *targetServer = NULL;
     char *target = cartUsualString(cart, "wp_target", "genome");
     if (isEmpty(target) || sameString(target, "genome"))
 	server = findServer(db, serverList);
     else
 	{
 	targetServer = getTargetServerList(db, target);
 	if (targetServer == NULL)
 	    errAbort("Can't find targetPcr server for db=%s, target=%s",
 		     db, target);
 	}
 
     fPrimer = gfPcrMakePrimer(fPrimer);
     rPrimer = gfPcrMakePrimer(rPrimer);
     if (doPcr(server, targetServer, fPrimer, rPrimer,
-	      maxSize, minPerfect, minGood, flipReverse))
+	      maxSize, minPerfect, minGood, flipReverse, appendToResults))
          return;
     }
 doGetPrimers(db, organism, serverList,
-	fPrimer, rPrimer, maxSize, minPerfect, minGood, flipReverse);
+	fPrimer, rPrimer, maxSize, minPerfect, minGood, flipReverse, appendToResults);
 }
 
 void doMiddle(struct cart *theCart)
 /* Write header and body of html page. */
 {
 cart = theCart;
 dnaUtilOpen();
 cartWebStart(cart, NULL, "UCSC In-Silico PCR");
 dispatch();
 cartWebEnd();
 }
 
 
 char *excludeVars[] = {"Submit", "submit", "wp_f", "wp_r", "wp_showPage", NULL};