5c213974bb28e98d1b10ad9a86063101e684e5e3
braney
  Mon Oct 31 12:16:52 2022 -0700
add Jim's track duplication code

diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c
index e6accf8..42fcdbd 100644
--- src/hg/hgTrackUi/hgTrackUi.c
+++ src/hg/hgTrackUi/hgTrackUi.c
@@ -18,30 +18,31 @@
 #include "snpUi.h"
 #include "snp125Ui.h"
 #include "snp125.h"
 #include "sample.h"
 #include "wiggle.h"
 #include "hgMaf.h"
 #include "obscure.h"
 #include "chainCart.h"
 #include "chainDb.h"
 #include "gvUi.h"
 #include "grp.h"
 #include "oregannoUi.h"
 #include "chromGraph.h"
 #include "hgConfig.h"
 #include "customTrack.h"
+#include "dupTrack.h"
 #include "dbRIP.h"
 #include "tfbsConsSites.h"
 #include "hapmapSnps.h"
 #include "nonCodingUi.h"
 #include "expRecord.h"
 #include "wikiTrack.h"
 #include "hubConnect.h"
 #include "trackHub.h"
 #include "pcrResult.h"
 #include "dgv.h"
 #include "transMapStuff.h" 
 #include "vcfUi.h" 
 #include "bbiFile.h"
 #include "ensFace.h"
 #include "microarray.h"
@@ -3152,30 +3153,46 @@
         }
     printf("<tr>");
     printf("<td><a href='%s?%s=%s&c=%s&g=%s'>%s</a>&nbsp;</td>", 
                 tdbIsDownloadsOnly(sibTdb) ? hgFileUiName(): hTrackUiForTrack(sibTdb->track),
                 cartSessionVarName(), cartSessionId(cart), chromosome, cgiEncode(sibTdb->track), 
                 sibTdb->shortLabel);
     printf("<td>%s</td></tr>\n", sibTdb->longLabel);
     }
 printf("</table>");
 jsEndCollapsibleSection();
 printf("</table>"); // required by jsCollapsible
 printf("<hr>");
 printf("</p>");
 }
 
+boolean tdbIsDupable(struct trackDb *tdb)
+/* Return TRUE if a track is duplicatable */
+{
+/* Can't handle container tracks yet at least */
+if (!tdbIsDataTrack(tdb))
+    return FALSE;
+/* A few other special case we can't handle */
+if (sameString(tdb->track, "ruler"))
+    return FALSE;
+if (sameString(tdb->track, "cutters"))
+    return FALSE;
+if (sameString(tdb->track, "oligoMatch"))
+    return FALSE;
+return TRUE;
+}
+
 void trackUi(struct trackDb *tdb, struct trackDb *tdbList, struct customTrack *ct, boolean ajax)
 /* Put up track-specific user interface. */
 {
 if (!ajax)
     {
     jsIncludeFile("jquery.js", NULL);
     webIncludeResourceFile("jquery-ui.css");
     jsIncludeFile("jquery-ui.js", NULL);
     jsIncludeFile("utils.js",NULL);
     webIncludeResourceFile("spectrum.min.css");
     jsIncludeFile("spectrum.min.js",NULL);
     jsIncludeFile("jquery.tablednd.js", NULL);
     jsonObjectAddGlobal("track", newJsonString(tdb->track));
     jsonObjectAddGlobal("db", newJsonString(database));
     }
@@ -3377,32 +3394,46 @@
             {
             printf("&nbsp;");
             cgiMakeOnClickButton("htui_cancel", "window.history.back();","Cancel");
             }
 
         if (tdbIsComposite(tdb))
 	    {
             printf("\n&nbsp;&nbsp;<a href='#' id='htui_reset'>Reset to defaults</a>\n");
 	    jsOnEventByIdF("click", "htui_reset",
                    "setVarAndPostForm('%s','1','mainForm'); return false;", setting);
 	    }
         if ( isCustomComposite(tdb))
             {
             printf("\n&nbsp;&nbsp;<a href='%s' >Go to Track Collection Builder</a>\n", hgCollectionName());
             }
+	/* Offer to dupe the non-containery tracks including composite and supertrack elements */
+	if (tdbIsDupable(tdb))
+	    {
+	    printf("\n&nbsp;&nbsp;<a href='%s?%s=%s&c=%s&g=%s&hgTrackUi_op=dupe' >Duplicate track</a>\n", 
+		hgTrackUiName(), cartSessionVarName(), cartSessionId(cart),
+		chromosome, cgiEncode(tdb->track));
+	    if (isDupTrack(tdb->track))
+		{
+		/* Offer to undupe */
+		printf("\n&nbsp;&nbsp;<a href='%s?%s=%s&c=%s&g=%s&hgTrackUi_op=undupe' >Remove duplicate</a>\n", 
+		    hgTrackUiName(), cartSessionVarName(), cartSessionId(cart),
+		    chromosome, cgiEncode(tdb->track));
+		}
 
 	    }
+	}
 
     if (ct)
         {
         puts("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
         cgiMakeButton(CT_DO_REMOVE_VAR, "Remove custom track");
         cgiMakeHiddenVar(CT_SELECTED_TABLE_VAR, tdb->track);
         puts("&nbsp;");
         if (differentString(tdb->type, "chromGraph"))
             {
             char buf[256];
             if (ajax)
                 // reference to a separate form doesn't work in modal dialog,
                 // so change window.location directly.
                 safef(buf, sizeof(buf), "window.location='%s?hgsid=%s&%s=%s';return false;",
                       hgCustomName(), cartSessionId(cart), CT_SELECTED_TABLE_VAR, tdb->track);
@@ -3546,56 +3577,129 @@
 struct trackDb *trackDbForRuler()
 /* Create a trackDb entry for the base position ruler.
    It is not (yet?) a real track, so doesn't appear in trackDb */
 {
 return trackDbForPseudoTrack(RULER_TRACK_NAME,
 	RULER_TRACK_LABEL, RULER_TRACK_LONGLABEL, tvFull, FALSE);
 }
 
 struct trackDb *trackDbForOligoMatch()
 /* Create a trackDb entry for the oligo matcher pseudo-track. */
 {
 return trackDbForPseudoTrack(OLIGO_MATCH_TRACK_NAME,
 	OLIGO_MATCH_TRACK_LABEL, OLIGO_MATCH_TRACK_LONGLABEL, tvHide, TRUE);
 }
 
+static char *handleDupOp(char *track, struct hash *trackHash)
+/* Handle the duplication operation in the URL if any.  Return dupe name if
+ * a dupe has happened. The trackHash is keyed by track name and has 
+ * struct trackDb values. */
+{
+char *newTrack = NULL;
+
+/* Handle duplicate, and possible in the future other operations on tracks. */
+char *opVar = "hgTrackUi_op";
+char *operation = cartUsualString(cart, opVar, NULL);
+if (operation != NULL)
+   {
+   if (sameString(operation, "dupe"))
+       {
+       struct trackDb *tdb = hashFindVal(trackHash, dupTrackSkipToSourceName(track));
+       newTrack = dupTrackInCartAndTrash(track, cart, tdb);
+       }
+   else if (sameString(operation, "undupe"))
+       {
+       newTrack = dupTrackSkipToSourceName(track);
+       undupTrackInCartAndTrash(track, cart);
+       }
+   else
+       {
+       internalErr();
+       }
+   cartRemove(cart, opVar);
+   }
+return newTrack;
+}
+
 void doMiddle(struct cart *theCart)
 /* Write body of web page. */
 {
 struct trackDb *tdbList = NULL;
 struct trackDb *tdb = NULL;
 char *track;
 struct customTrack *ct = NULL, *ctList = NULL;
 char *ignored;
 
 /* used to have hgBotDelayFrac(0.25) here, replaced with earlyBotCheck()
  * at the beginning of main() to output message here if in delay time
  * 2021-06-21 - Hiram
  */
 if (issueBotWarning)
     {
     char *ip = getenv("REMOTE_ADDR");
     botDelayMessage(ip, botDelayMillis);
     }
 
 cart = theCart;
 track = cartString(cart, "g");
 getDbAndGenome(cart, &database, &ignored, NULL);
 initGenbankTableNames(database);
 chromosome = cartUsualString(cart, "c", hDefaultChrom(database));
-
 trackHash = trackHashMakeWithComposites(database,chromosome,&tdbList,FALSE); 
+
+/* Handle dup of track related stuff */
+char *dupeName = handleDupOp(track, trackHash);
+if (dupeName != NULL)
+    track = dupeName;
+struct dupTrack *dupList = dupTrackListFromCart(cart);
+char *dupWholeName = NULL;
+boolean isDup = isDupTrack(track);
+if (isDup)
+    {
+    dupWholeName = track;
+    track = dupTrackSkipToSourceName(track);
+    }
+
+
+/* Add in duplicate tracks. */
+struct dupTrack *dup;
+for (dup = dupList; dup != NULL; dup = dup->next)
+    {
+    struct trackDb *sourceTdb = hashFindVal(trackHash, dup->source);
+    if (sourceTdb != NULL)
+	{
+	struct trackDb *dupTdb = dupTdbFrom(sourceTdb, dup);
+        hashAdd(trackHash, dupTdb->track, dupTdb);
+	if (sourceTdb->parent != NULL)
+	    {
+	    struct trackDb *parent = sourceTdb->parent;
+	    // Add to parent here depending on whether composite or something else?
+	    if (tdbIsFolder(parent))
+		{
+		refAdd(&parent->children, dupTdb);
+		}
+	    else
+		{
+		slAddHead(&parent->subtracks, dupTdb);
+		dupTdb = NULL;  /* We use it! */
+		}
+	    }
+	if (dupTdb != NULL)
+	    slAddHead(&tdbList, dupTdb);
+	}
+    }
+
 if (sameWord(track, WIKI_TRACK_TABLE))
     tdb = trackDbForWikiTrack();
 else if (sameWord(track, RULER_TRACK_NAME))
     /* special handling -- it's not a full-fledged track */
     tdb = trackDbForRuler();
 else if (sameWord(track, OLIGO_MATCH_TRACK_NAME))
     tdb = trackDbForOligoMatch();
 else if (sameWord(track, CUTTERS_TRACK_NAME))
     tdb = trackDbForPseudoTrack(CUTTERS_TRACK_NAME, CUTTERS_TRACK_LABEL, CUTTERS_TRACK_LONGLABEL, tvHide, TRUE);
 else if (isCustomTrack(track))
     {
     ctList = customTracksParseCart(database, cart, NULL, NULL);
     for (ct = ctList; ct != NULL; ct = ct->next)
         {
         if (sameString(track, ct->tdb->track))
@@ -3608,30 +3712,39 @@
 else if (isHubTrack(track))
     {
     tdb = hubConnectAddHubForTrackAndFindTdb(database, track, &tdbList, trackHash);
     }
 else if (sameString(track, "hgPcrResult"))
     tdb = pcrResultFakeTdb();
 else
     {
     tdb = tdbForTrack(database, track,&tdbList);
     }
 if (tdb == NULL)
    {
    errAbort("Can't find %s in track database %s chromosome %s",
 	    track, database, chromosome);
    }
+
+// Do  little more dupe handling - make a tdb for dupe if any 
+if (isDup)
+    {
+    struct dupTrack *dup = dupTrackFindInList(dupList, dupWholeName);
+    tdb = dupTdbFrom(tdb, dup);
+    }
+
+
 if(cartOptionalString(cart, "ajax"))
     {
     // html is going to be used w/n a dialog in hgTracks.js so serve up stripped down html
     // still need CSP2 header for security
     printf("%s", getCspMetaHeader());
     trackUi(tdb, tdbList, ct, TRUE);
     cartRemove(cart,"ajax");
     jsInlineFinish();
     }
 else
     {
     char title[1000];
     if (tdb->parent)
         {
         safef(title, sizeof title,