fc3472e7e9978eb81e3ac14e985bf27e5196ab73 kate Fri Jul 24 10:58:01 2020 -0700 Recommended track sets feature. refs #25601 diff --git src/hg/hgTracks/hgTracks.c src/hg/hgTracks/hgTracks.c index 80dc532..423316e 100644 --- src/hg/hgTracks/hgTracks.c +++ src/hg/hgTracks/hgTracks.c @@ -60,30 +60,33 @@ #include "search.h" #include "errCatch.h" #include "iupac.h" #include "botDelay.h" #include "chromInfo.h" #include "extTools.h" #include "basicBed.h" #include "customFactory.h" #include "genbank.h" #include "bigWarn.h" #include "wigCommon.h" #include "knetUdc.h" #include "hex.h" #include <openssl/sha.h> #include "customComposite.h" + +boolean isSessChanged = FALSE; + //#include "bed3Sources.h" /* Other than submit and Submit all these vars should start with hgt. * to avoid weeding things out of other program's namespaces. * Because the browser is a central program, most of its cart * variables are not hgt. qualified. It's a good idea if other * program's unique variables be qualified with a prefix though. */ char *excludeVars[] = { "submit", "Submit", "dirty", "hgt.reset", "hgt.in1", "hgt.in2", "hgt.in3", "hgt.inBase", "hgt.out1", "hgt.out2", "hgt.out3", "hgt.out4", "hgt.left1", "hgt.left2", "hgt.left3", "hgt.right1", "hgt.right2", "hgt.right3", "hgt.dinkLL", "hgt.dinkLR", "hgt.dinkRL", "hgt.dinkRR", "hgt.tui", "hgt.hideAll", "hgt.visAllFromCt", "hgt.psOutput", "hideControls", "hgt.toggleRevCmplDisp", @@ -7766,30 +7769,179 @@ { if (jsonList == NULL) jsonList = newJsonList(NULL); struct jsonElement *collection = newJsonObject(newHash(4)); jsonObjectAdd(collection, "track", newJsonString(tdb->track)); jsonObjectAdd(collection, "shortLabel", newJsonString(tdb->shortLabel)); jsonListAdd(jsonList, collection); } } if (jsonList != NULL) jsonObjectAdd(jsonForClient, "collections", jsonList); } } +// TODO: move to a file in lib (cheapcgi?) +char *cgiVarStringSort(char *cgiVarString) +/* Return cgi var string sorted alphabetically by vars */ +{ +struct slName *vars = slNameListFromString(cgiVarString, '&'); +slNameSort(&vars); +char *cgiString = slNameListToString(vars, '&'); +return cgiString; +} + +char *cgiTrackVisString(char *cgiVarString) +/* Filter cgi var string (var=val&) to just track visibilities, but equivalence + * dense/pack/squish/full by replacing as 'on'. + * Return string with track data vars in alphabetic order */ +{ +// TODO: use cheapcgi.cgiParsedVarsNew() to parse and get list ? +#define MAX_CGI_VARS 1000 +// NOTE: Ana featured sessions have: 473, 308, 288 +char *cgiVars[MAX_CGI_VARS]; +int ct = chopByChar(cgiVarString, '&', cgiVars, sizeof cgiVars); + +char *cgiVar = NULL; +char *val = NULL; +int i; +struct dyString *dsCgiTrackVis = dyStringCreate("db=%s", database); +for (i=0; i<ct; i++) + { + // TODO: attention to memory allocation + cgiVar = cloneString(cgiVars[i]); + val = rStringIn("=", cgiVar); + if (!val) // expect always + continue; + val++; + if (sameString(val, "hide") || sameString(val, "dense") || sameString(val, "squish") || + sameString(val, "pack") || sameString(val, "full") || sameString(val, "show")) + { + char *p = val; + *--p = 0; + dyStringPrintf(dsCgiTrackVis, "&%s=%s", cgiVar, sameString(val, "hide") ? "hide" : "on"); + } + else if (stringIn("_sel=", cgiVar) && sameString(val, "1")) + { + val -= 5; + *val = 0; + dyStringAppend(dsCgiTrackVis, "&"); + dyStringAppend(dsCgiTrackVis, cloneString(cgiVars[i])); + } + else + { + val = rStringIn("_imgOrd=", cgiVar); + if (!val) + val = rStringIn(".showCfg=", cgiVar); + if (!val) + continue; + *val = 0; + } + } +return cgiVarStringSort(dyStringCannibalize(&dsCgiTrackVis)); +} + +// TODO: move to lib. Used by hgSession and hgTracks +static void outIfNotPresent(struct cart *cart, struct dyString *dy, char *track, int tdbVis) +/* Output default trackDb visibility if it's not mentioned in the cart. */ +{ +char *cartVis = cartOptionalString(cart, track); +if (cartVis == NULL) + { + if (dy) + dyStringPrintf(dy,"&%s=%s", track, hStringFromTv(tdbVis)); + else + printf("%s %s\n", track, hStringFromTv(tdbVis)); + } +} + +// TODO: move to lib. Used by hgSession and hgTracks +static void outDefaultTracks(struct cart *cart, struct dyString *dy) +/* Output the default trackDb visibility for all tracks + * in trackDb if the track is not mentioned in the cart. */ +{ +struct hash *parentHash = newHash(5); +struct track *track; +for (track=trackList; track; track=track->next) + { + struct trackDb *parent = track->tdb->parent; + if (parent) + { + if (hashLookup(parentHash, parent->track) == NULL) + { + hashStore(parentHash, parent->track); + if (parent->isShow) + outIfNotPresent(cart, dy, parent->track, tvShow); + } + } + if (track->tdb->visibility != tvHide) + outIfNotPresent(cart, dy, track->tdb->track, track->tdb->visibility); + } +// Put a variable in the cart that says we put the default +// visibilities in it. +if (dy) + dyStringPrintf(dy,"&%s=on", CART_HAS_DEFAULT_VISIBILITY); +else + printf("%s on", CART_HAS_DEFAULT_VISIBILITY); +} + +boolean hasSessionChanged() +{ +/* Have any tracks been hidden or added ? */ + +// get featured session from database +char *sessionName = cartString(cart, "hgS_otherUserSessionName"); +if (!sessionName) + return FALSE; +struct sqlConnection *conn = hConnectCentral(); +char query[1000]; +sqlSafef(query, sizeof query, + "SELECT contents FROM namedSessionDb where sessionName='%s'", + replaceChars(sessionName, " ", "%20")); +char *cartString = sqlQuickNonemptyString(conn, query); +hDisconnectCentral(&conn); + +// TODO: use cheapcgi.cgiParsedVarsNew() to parse and get list ? +#define MAX_SESSION_LEN 100000 +char *curSessCart = (char *)needMem(MAX_SESSION_LEN); +cgiDecodeFull(cartString, curSessCart, MAX_SESSION_LEN); +char *curSessVisTracks = cgiTrackVisString(curSessCart); + +// get track-related vars from current cart +struct dyString *dsCgiVars = newDyString(0); +cartEncodeState(cart, dsCgiVars); +outDefaultTracks(cart, dsCgiVars); +char *this = dyStringCannibalize(&dsCgiVars); +// TODO: again, better parsing +char *this2 = replaceChars(this, "%2D", "-"); +char *thisSessVars = replaceChars(this2, "%2B", "+"); +char *thisSessVisTracks = cgiTrackVisString(thisSessVars); + +//freeMem(curSessCart); +boolean isSessChanged = FALSE; +if (differentString(curSessVisTracks, thisSessVisTracks)) + { + isSessChanged = TRUE; + #ifdef DEBUG + uglyf("<br>curSess vis tracks: %s", curSessVisTracks); + uglyf("<br>thsSess vis tracks: %s", thisSessVisTracks); + #endif + } +return isSessChanged; +} + void doTrackForm(char *psOutput, struct tempName *ideoTn) /* Make the tracks display form with the zoom/scroll buttons and the active * image. If the ideoTn parameter is not NULL, it is filled in if the * ideogram is created. */ { struct group *group; struct track *track; char *freezeName = NULL; boolean hideAll = cgiVarExists("hgt.hideAll"); boolean defaultTracks = cgiVarExists("hgt.reset"); boolean showedRuler = FALSE; boolean showTrackControls = cartUsualBoolean(cart, "trackControlsOnMain", TRUE); long thisTime = 0, lastTime = 0; basesPerPixel = ((float)virtWinBaseCount) / ((float)fullInsideWidth); @@ -8250,71 +8402,103 @@ ) { for(window=windows;window;window=window->next) { struct track *ideoTrack = chromIdeoTrack(window->trackList); if (ideoTrack) { ideoTrack->limitedVisSet = TRUE; ideoTrack->limitedVis = tvHide; /* Don't draw in main gif. */ } } } if (trackImgOnly && !ideogramToo) { + // right-click to change viz makeActiveImage(trackList, psOutput); fflush(stdout); return; // bail out b/c we are done } if (!hideControls) { /* set white-space to nowrap to prevent buttons from wrapping when screen is * narrow */ hPrintf("<DIV STYLE=\"white-space:nowrap;\">\n"); printMenuBar(); //menuBarAppendExtTools(); - /* Show title . */ + /* Show title */ freezeName = hFreezeFromDb(database); if(freezeName == NULL) freezeName = "Unknown"; hPrintf("<span style='font-size:x-large;'><B>"); if (startsWith("zoo",database) ) { hPrintf("%s %s on %s June 2002 Assembly %s target1", organization, browserName, organism, freezeName); } else { if (sameString(organism, "Archaea")) { hPrintf("%s %s on Archaeon %s Assembly", organization, browserName, freezeName); } else { if (stringIn(database, freezeName)) hPrintf("%s %s on %s %s Assembly", organization, browserName, organism, freezeName); else hPrintf("%s %s on %s %s Assembly (%s)", organization, browserName, trackHubSkipHubName(organism), freezeName, trackHubSkipHubName(database)); } } - hPrintf("</B></span><BR>\n"); + hPrintf("</B></SPAN>"); + + if (defaultTracks || hideAll) + cartRemove(cart, "hgS_otherUserSessionLabel"); + char *sessionLabel = cartOptionalString(cart, "hgS_otherUserSessionLabel"); + if (sessionLabel) + { + char *panel = "recTrackSetsPanel"; + isSessChanged = hasSessionChanged(); + + struct dyString *hoverText = dyStringNew(0); + dyStringPrintf(hoverText, "Your browser is displaying the %s track set%s. " + " Click to change to another.", sessionLabel, + isSessChanged ? + ", with changes (added or removed tracks) you have requested" : ""); + // TODO: cleanup layout tweaking for FF on IE10 + hPrintf(" "); + hPrintf("<span id='spacer' style='display: inline; padding-left: 10px;' > </span>"); + + hPrintf("<span id='%s' class='gbSessionLabelPanel' style='display: inline-block;' title='%s'>", + panel, dyStringCannibalize(&hoverText)); + hPrintf("<span id='recTrackSetLabel' class='gbSessionLabelText gbSessionChangeIndicator %s' " + "style='margin-right: 3px;'>%s</span>", + isSessChanged ? "gbSessionChanged" : "", sessionLabel); + hPrintf("<i id='removeSessionPanel' title='Close' class='fa fa-remove' " + "style='color: #a9a9a9; font-size:smaller; vertical-align: super;'></i>"); + hPrintf("</span>"); + + jsOnEventById("click", "recTrackSetLabel", "showRecTrackSetsPopup(); return false;"); + jsOnEventById("click", "removeSessionPanel", "removeSessionPanel(); return false;"); + } + hPrintf("<BR>\n"); /* This is a clear submit button that browsers will use by default when enter is pressed in position box. */ hPrintf("<INPUT TYPE=IMAGE BORDER=0 NAME=\"hgt.dummyEnterButton\" src=\"../images/DOT.gif\">"); /* Put up scroll and zoom controls. */ #ifndef USE_NAVIGATION_LINKS hWrites("move "); hButtonWithOnClick("hgt.left3", "<<<", "move 95% to the left", "return imageV2.navigateButtonClick(this);"); hButtonWithOnClick("hgt.left2", " <<", "move 47.5% to the left", "return imageV2.navigateButtonClick(this);"); hButtonWithOnClick("hgt.left1", " < ", "move 10% to the left", "return imageV2.navigateButtonClick(this);"); hButtonWithOnClick("hgt.right1", " > ", "move 10% to the right", "return imageV2.navigateButtonClick(this);"); hButtonWithOnClick("hgt.right2", ">> ", "move 47.5% to the right", @@ -8475,30 +8659,31 @@ hPrintf("<td width='80' align='right'><a href='?hgt.out3=1' " "title='zoom out 10x'><<< >>></a>\n"); hPrintf("<td width='80' align='right'><a href='?hgt.out4=1' " "title='zoom out 100x'><<< >>></a>\n"); hPrintf("<td> </td>\n"); // Without width cell expands table width, forcing others to sides hPrintf("<td width='20' align='right'><a href='?hgt.right1=1' " "title='move 10% to the right'>></a>\n"); hPrintf("<td width='30' align='right'><a href='?hgt.right2=1' " "title='move 47.5% to the right'>>></a>\n"); hPrintf("<td width='40' align='right'><a href='?hgt.right3=1' """ "title='move 95% to the right'>>>></a>\n"); hPrintf("</tr></table>\n"); #endif///def USE_NAVIGATION_LINKS + /* Make clickable image and map. */ makeActiveImage(trackList, psOutput); fflush(stdout); if (trackImgOnly) { // bail out b/c we are done if (measureTiming) { printTrackTiming(); } return; } if (!hideControls) @@ -9670,31 +9855,32 @@ lastDbPosSaveCartSetting("virtModeType"); lastDbPosSaveCartSetting("lastVirtModeType"); lastDbPosSaveCartSetting("lastVirtModeExtraState"); cartSetDbPosition(cart, database, lastDbPosCart); if (cartUsualBoolean(cart, "hgt.psOutput", FALSE)) handlePostscript(); else doTrackForm(NULL, NULL); boolean gotExtTools = extToolsEnabled(); setupHotkeys(gotExtTools); if (gotExtTools) printExtMenuData(chromName); - +if (recTrackSetsEnabled()) + printRecTrackSets(); } void chromInfoTotalRow(int count, long long total) /* Make table row with total number of sequences and size from chromInfo. */ { cgiSimpleTableRowStart(); cgiSimpleTableFieldStart(); printf("Total: %d", count); cgiTableFieldEnd(); cgiSimpleTableFieldStart(); printLongWithCommas(stdout, total); cgiTableFieldEnd(); cgiTableRowEnd(); }