3162d1fec3dffece6f608f15eb6d46f6c46f7436
chmalee
  Fri Dec 5 16:13:05 2025 -0800
Working version of custom track group 'visible' that is at the top of the group list and has all the currently visible tracks, refs #36609

diff --git src/hg/hgTracks/hgTracks.c src/hg/hgTracks/hgTracks.c
index 942c1730fef..2415e6f55e4 100644
--- src/hg/hgTracks/hgTracks.c
+++ src/hg/hgTracks/hgTracks.c
@@ -255,30 +255,36 @@
    return 0;
 else
    return 1;
 }
 
 int gCmpPriority(const void *va, const void *vb)
 /* Compare groups based on priority, paying attention to hub groups */
 {
 const struct group *a = *((struct group **)va);
 const struct group *b = *((struct group **)vb);
 float dif = a->priority - b->priority;
 int iDif = makeInt(dif);
 boolean aIsHub = startsWith("hub_", a->name);
 boolean bIsHub = startsWith("hub_", b->name);
 
+// visible tracks sorts first always
+if (sameString(a->name, "visible"))
+    return -1;
+else if (sameString(b->name, "visible"))
+    return 1;
+
 if (aIsHub)
     {
     if (bIsHub)
         {
         char *aNullPos = strchr(&a->name[4], '_');
         char *bNullPos = strchr(&b->name[4], '_');
         if ((aNullPos != NULL) && (bNullPos != NULL) )
             *aNullPos = *bNullPos = 0;
 
         int strDiff = strcmp(a->name, b->name);
         int ret = 0;
         if (strDiff == 0)   // same hub
             ret = iDif;
         else
             ret = strDiff;
@@ -2179,30 +2185,65 @@
 
 	    /* create list of codons in the specified coding frame */
 	    sfList = baseColorCodonsFromDna(refFrame, winStart, winEnd,
                                             extraSeq, complementRulerBases);
 	    /* draw the codons in the list, with alternating colors */
 	    baseColorDrawRulerCodons(hvg, sfList, scale, insideX, y,
                                      codonHeight, font, winStart, MAXPIXELS,
                                      zoomedToCodonLevel);
 	    }
 	}
     }
 hvGfxUnclip(hvg);
 return y;
 }
 
+static struct track *getVisibleTracks(struct track *trackList)
+/* Return just the tracks that are currently on */
+{
+struct track *retList = NULL;
+if (trackList == NULL)
+    return NULL;
+
+struct track *track;
+for (track = trackList; track != NULL; track = track->next)
+    {
+    if (sameString(trackHubSkipHubName(track->track), "cytoBandIdeo"))
+        continue;
+    int vis = track->limitedVisSet ? track->limitedVis : track->visibility;
+    if (vis)
+        {
+        int subtrackVis = 0;
+        if (track->subtracks)
+            {
+            struct track *subtrack;
+            for (subtrack = track->subtracks; subtrack != NULL; subtrack = subtrack->next)
+                subtrackVis |= subtrack->limitedVisSet ? subtrack->limitedVis : subtrack->visibility;
+            vis &= subtrackVis;
+            }
+        if (vis)
+            {
+            struct track *clone = CloneVar(track);
+            clone->next = NULL;
+            slAddHead(&retList, clone);
+            }
+        }
+    }
+// this will be sorted later, no need to reverse or sort now
+return retList;
+}
+
 static void logTrackList(struct dyString *dy, struct track *trackList)
 /* add visibile tracks to dyString, recursively called */
 {
 if (trackList == NULL)
     return;
 
 struct track *track;
 for (track = trackList; track != NULL; track = track->next)
     {
     int vis = track->limitedVisSet ? track->limitedVis : track->visibility;
     if (vis)
 	{
 	logTrackList(dy, track->subtracks);
 	if (dy->stringSize)
 	    dyStringAppendC(dy, ',');
@@ -7053,30 +7094,41 @@
 //
 // If there isn't a map group, make one and set the priority so it will be right after custom tracks
 // and hub groups.
 if (!foundMap)
     {
     AllocVar(group);
     group->name = cloneString("map");
     group->label = cloneString("Mapping and Sequencing");
     group->defaultPriority = priority;
     group->priority = priority;
     group->defaultIsClosed = FALSE;
     slAddHead(&list, group);
     hashAdd(hash, "map", group);
     }
 
+// The "Visible Tracks" group is now the default top group
+struct group *visible = NULL;
+AllocVar(visible);
+visible->name = "visible";
+visible->label = "Visible Tracks";
+visible->defaultPriority = priority;
+visible->priority = -1;
+visible->defaultIsClosed = FALSE;
+slAddHead(&list, visible);
+hashAdd(hash, "visible", visible);
+
 
 /* Loop through tracks and fill in their groups.
  * If necessary make up an unknown group. */
 for (track = *pTrackList; track != NULL; track = track->next)
     {
     /* handle track reordering feature -- change group assigned to track */
     if (withPriorityOverride)
         {
         char *groupName = NULL;
         char cartVar[256];
 
         /* belt and suspenders -- accomodate inconsistent track/trackDb
          * creation.  Note -- with code cleanup, these default variables
          * could be retired, and the tdb versions used as defaults */
         if (!track->defaultGroupName)
@@ -7154,67 +7206,72 @@
 for (track = *pTrackList; track != NULL; track = track->next)
     {
     AllocVar(tr);
     tr->track = track;
     slAddHead(&track->group->trackList, tr);
     }
 
 /* Straighten things out, clean up, and go home. */
 for (group = list; group != NULL; group = group->next)
     slReverse(&group->trackList);
 slSort(&list, gCmpPriority);
 hashFree(&hash);
 *pGroupList = list;
 }
 
-void groupTrackListAddSuper(struct cart *cart, struct group *group, struct hash *superHash)
+}
+void groupTrackListAddSuper(struct cart *cart, struct group *group, struct hash *superHash, struct hash *trackHashRef)
 /* Construct a new track list that includes supertracks, sort by priority,
  * and determine if supertracks have visible members.
  * Replace the group track list with this new list.
  * Shared by hgTracks and configure page to expand track list,
  * in contexts where no track display functions (which don't understand
- * supertracks) are invoked. */
+ * supertracks) are invoked.
+ * In general, trackHashRef is just a pointer to the global trackHash,
+ * except in the case of building the Visible Tracks group, in which
+ * case it is a new hash, because we want super tracks duplicated into
+ * the visible tracks list and their normal group list */
 {
 struct trackRef *newList = NULL, *tr, *ref;
 
 if (!group || !group->trackList)
     return;
 for (tr = group->trackList; tr != NULL; tr = tr->next)
     {
     struct track *track = tr->track;
     AllocVar(ref);
     ref->track = track;
     slAddHead(&newList, ref);
     if (tdbIsSuperTrackChild(track->tdb))
         {
         assert(track->tdb->parentName != NULL);
         if (hTvFromString(cartUsualString(cart, track->track,
                         hStringFromTv(track->tdb->visibility))) != tvHide)
             setSuperTrackHasVisibleMembers(track->tdb->parent);
         assert(track->parent == NULL);
         track->parent = hashFindVal(superHash, track->tdb->parentName);
         if (track->parent)
             continue;
         /* create track and reference for the supertrack */
         struct track *superTrack = track->parent = trackFromTrackDb(track->tdb->parent);
         track->parent = superTrack;
-        if (trackHash != NULL)
-            hashAddUnique(trackHash,superTrack->track,superTrack);
+        if (trackHashRef != NULL)
+            hashAddUnique(trackHashRef,superTrack->track,superTrack);
         superTrack->hasUi = TRUE;
-        superTrack->group = group;
-        superTrack->groupName = cloneString(group->name);
-        superTrack->defaultGroupName = cloneString(group->name);
+        superTrack->group = track->group;
+        superTrack->groupName = cloneString(track->group->name);
+        superTrack->defaultGroupName = cloneString(track->group->name);
 
         /* handle track reordering */
         char cartVar[256];
         safef(cartVar, sizeof(cartVar), "%s.priority",track->tdb->parentName);
         float priority = (float)cartUsualDouble(cart, cartVar,
                                         track->tdb->parent->priority);
         /* remove cart variables that are the same as the trackDb settings */
         if (priority == track->tdb->parent->priority)
             cartRemove(cart, cartVar);
         superTrack->priority = priority;
 
         AllocVar(ref);
         ref->track = superTrack;
         slAddHead(&newList, ref);
         hashAdd(superHash, track->tdb->parentName, superTrack);
@@ -8821,30 +8878,53 @@
     }
 
 if (cartUsualBoolean(cart, "dumpTracks", FALSE))
     {
     struct dyString *dy = dyStringNew(1024);
     logTrackList(dy, trackList);
 
     printf("Content-type: text/html\n\n");
     printf("%s\n", dy->string);
     exit(0);
     }
 
 if (sameString(cfgOptionDefault("trackLog", "off"), "on"))
     logTrackVisibilities(cartSessionId(cart), trackList, position);
 
+struct track *visibleTracks = getVisibleTracks(trackList);
+if (visibleTracks)
+    {
+    // add these tracks to the special 'visible' group
+    struct group *group, *visibleGroup = NULL;
+    for (group = groupList; group != NULL; group = group->next)
+        {
+        if (sameString(group->name, "visible"))
+            {
+            visibleGroup = group;
+            break;
+            }
+        }
+    struct track *t;
+    for (t = visibleTracks; t != NULL; t=t->next)
+        {
+        struct trackRef *tr;
+        AllocVar(tr);
+        tr->track = t;
+        slAddHead(&visibleGroup->trackList, tr);
+        }
+    slReverse(&visibleGroup->trackList);
+    }
 
 /////////////////
 
 // NEED TO LOAD ALL WINDOWS NOW
 //
 //   Need to load one window at a time!
 //
 //   The use of the global values for a window
 //   means that differerent threads cannot use different global window values.
 //   Threads must run on just one window value at a time.
 //
 //   Begin by making a copy of the track structure for visible tracks
 //   for all windows.
 
 
@@ -9079,30 +9159,41 @@
 struct dyString *trackGroupsHidden2 = dyStringNew(1000);
 for (group = groupList; group != NULL; group = group->next)
     {
     if (group->trackList != NULL)
         {
         int looper;
         for (looper=1;looper<=2;looper++)
             {
             boolean isOpen = !isCollapsedGroup(group);
             char buf[1000];
             safef(buf, sizeof(buf), "<input type='hidden' name=\"%s\" id=\"%s_%d\" value=\"%s\">\n",
 		collapseGroupVar(group->name), collapseGroupVar(group->name), looper, isOpen ? "0" : "1");
             dyStringAppend(looper == 1 ? trackGroupsHidden1 : trackGroupsHidden2, buf);
             }
         }
+    else if (sameString(group->name, "visible"))
+        {
+            boolean isOpen = !isCollapsedGroup(group);
+            char buf[1000];
+            safef(buf, sizeof(buf), "<input type='hidden' name=\"%s\" id=\"%s_1\" value=\"%s\">\n",
+                collapseGroupVar(group->name), collapseGroupVar(group->name), isOpen ? "0" : "1");
+            dyStringAppend(trackGroupsHidden1, buf);
+            safef(buf, sizeof(buf), "<input type='hidden' name=\"%s\" id=\"%s_2\" value=\"%s\">\n",
+                collapseGroupVar(group->name), collapseGroupVar(group->name), isOpen ? "0" : "1");
+            dyStringAppend(trackGroupsHidden2, buf);
+        }
     }
 
 if (theImgBox)
     {
     // If a portal was established, then set the global dimensions back to the portal size
     if (imgBoxPortalDimensions(theImgBox,NULL,NULL,NULL,NULL,&virtWinStart,&virtWinEnd,&(tl.picWidth),NULL))
         {
         virtWinBaseCount = virtWinEnd - virtWinStart;
         fullInsideWidth = tl.picWidth-gfxBorder-fullInsideX;
         }
     }
 /* Center everything from now on. */
 hPrintf("<CENTER>\n");
 
 outCollectionsToJson();
@@ -9653,30 +9744,32 @@
 	    /* check if group section should be displayed */
 	    char *otherState;
 	    char *indicator;
 	    char *indicatorImg;
 	    boolean isOpen = !isCollapsedGroup(group);
 	    collapseGroupGoodies(isOpen, TRUE, &indicatorImg,
 				    &indicator, &otherState);
 	    hPrintf("<TR>");
 	    cg->rowOpen = TRUE;
             if (group->errMessage)
                 hPrintf("<th align=\"left\" colspan=%d class='errorToggleBar'>",MAX_CONTROL_COLUMNS);
             else if (startsWith("Hub", group->label))
                 hPrintf("<th align=\"left\" colspan=%d class='hubToggleBar'>",MAX_CONTROL_COLUMNS);
             else if (startsWith("QuickLift", group->label))
                 hPrintf("<th align=\"left\" colspan=%d class='quickToggleBar'>",MAX_CONTROL_COLUMNS);
+            else if (sameString("Visible Tracks", group->label))
+                hPrintf("<th align=\"left\" colspan=%d class='visibleTracksToggleBar'>",MAX_CONTROL_COLUMNS);
             else
                 hPrintf("<th align=\"left\" colspan=%d class='nativeToggleBar'>",MAX_CONTROL_COLUMNS);
             hPrintf("<table style='width:100%%;'><tr><td style='text-align:left;'>");
             hPrintf("\n<A NAME=\"%sGroup\"></A>",group->name);
 
 	    char idText[256];
 	    safef(idText, sizeof idText, "%s_button", group->name);
             hPrintf("<IMG class='toggleButton'"
                     " id='%s' src=\"%s\" alt=\"%s\" title='%s this group'>&nbsp;&nbsp;",
                     idText, indicatorImg, indicator,isOpen?"Collapse":"Expand");
 	    jsOnEventByIdF("click", idText, "return vis.toggleForGroup(this, '%s');", group->name);
 
             if (isHubTrack(group->name))
 		{
 
@@ -9787,31 +9880,39 @@
 		showedRuler = TRUE;
 		myControlGridStartCell(cg, isOpen, group->name, FALSE);
 		hPrintf("<A HREF=\"%s\">", url);
 		hPrintf(" %s<BR> ", RULER_TRACK_LABEL);
 		hPrintf("</A>");
 		hDropListClassWithStyle("ruler", rulerMenu,
 			sizeof(rulerMenu)/sizeof(char *), rulerMenu[rulerMode],
 			rulerMode == tvHide ? "hiddenText" : "normalText",
 			TV_DROPDOWN_STYLE);
 		controlGridEndCell(cg);
 		freeMem(url);
 		}
 
 	    /* Add supertracks to track list, sort by priority and
 	     * determine if they have visible member tracks */
-	    groupTrackListAddSuper(cart, group, superHash);
+            if (sameString(group->name, "visible"))
+                {
+                // we want tracks in the visible list to also be visible
+                // in the normal group list, so use a separate hash for the
+                // visible tracks grouping
+                groupTrackListAddSuper(cart, group, hashNew(8), hashNew(8));
+                }
+            else
+                groupTrackListAddSuper(cart, group, superHash, trackHash);
 
 	    /* Display track controls */
             if (group->errMessage)
                 {
 		myControlGridStartCell(cg, isOpen, group->name,
                                        shouldBreakAll(group->errMessage));
                 hPrintf("%s", group->errMessage);
 		controlGridEndCell(cg);
                 }
 
 	    for (tr = group->trackList; tr != NULL; tr = tr->next)
 		{
 		struct track *track = tr->track;
 		if (tdbIsSuperTrackChild(track->tdb))
 		    /* don't display supertrack members */