80444b67d516cfc76198b8416b8f302aa7dabe25
lrnassar
Thu Mar 30 10:18:41 2023 -0700
One more tweak to the speed message, refs #30591
diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index f21d384..14db4c9 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -1,5522 +1,5521 @@
// hgTracks.js - Javascript for use in hgTracks CGI
// Copyright (C) 2008 The Regents of the University of California
// "use strict";
// Don't complain about line break before '||' etc:
/* jshint -W014 */
var debug = false;
/* Data passed in from CGI via the hgTracks object:
*
* string cgiVersion // CGI_VERSION
* string chromName // current chromosome
* int winStart // genomic start coordinate (0-based, half-open)
* int winEnd // genomic end coordinate
* int newWinWidth // new width (in bps) if user clicks on the top ruler
* boolean revCmplDisp // true if we are in reverse display
* int insideX // width of side-bar (in pixels)
* int rulerClickHeight // height of ruler (in pixels) - zero if ruler is hidden
* boolean inPlaceUpdate // true if in-place-update is turned on
* int imgBox* // various drag-scroll values
* boolean measureTiming // true if measureTiming is on
* Object trackDb // hash of trackDb entries for tracks which are visible on current page
* string highlight // highlight string, in format chrom#start#end#color|chrom2#start2#end2#color2|...
* string prevHlColor // the last highlight color that the user picked
*/
/* IE11 compatibility - IE doesn't have string startsWith and never will */
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(searchString, position) {
position = position || 0;
return this.indexOf(searchString, position) === position;
};
}
function initVars()
{ // There are various entry points, so we call initVars in several places to make sure all is well
if (typeof(hgTracks) !== "undefined" && !genomePos.original) {
// remember initial position and size so we can restore it if user cancels
genomePos.original = genomePos.getOriginalPos();
genomePos.originalSize = $('#size').text().replace(/,/g, ""); // strip out any commas
dragSelect.originalCursor = jQuery('body').css('cursor');
imageV2.imgTbl = $('#imgTbl');
// imageV2.enabled === true unless: advancedJavascript===false, or trackSearch, or config pg
imageV2.enabled = (imageV2.imgTbl && imageV2.imgTbl.length > 0);
// jQuery load function with stuff to support drag selection in track img
if (theClient.isSafari()) {
// Safari has the following bug: if we update the hgTracks map dynamically,
// the browser ignores the changes (even though if you look in the DOM the changes
// are there). So we have to do a full form submission when the user changes
// visibility settings or track configuration.
// As of 5.0.4 (7533.20.27) this is problem still exists in safari.
// As of 5.1 (7534.50) this problem appears to have been fixed - unfortunately,
// logs for 7/2011 show vast majority of safari users are pre-5.1 (5.0.5 is by far
// the most common).
//
// Early versions of Chrome had this problem too, but this problem went away
// as of Chrome 5.0.335.1 (or possibly earlier).
//
// KRR/JAT 2/2016:
// This Safari issue is likely resolved in all current versions. However the test
// for version had been failing, likely for some time now.
// (As of 9.0.9, possibly earlier, the 3rd part of the version is included in the
// user agent string, so must be accounted for in string match)
// Consequences were that page refresh was used instead of img update (e.g.
// for drag-zoom). And UI dialog was unable to update (e.g. via Apply button).
imageV2.mapIsUpdateable = false;
var reg = new RegExp("Version\/([0-9]+.[0-9]+)(.[0-9]+)? Safari");
var a = reg.exec(navigator.userAgent);
if (a && a[1]) {
var version = Number(a[1]);
if (version >= 5.1) {
imageV2.mapIsUpdateable = true;
}
}
}
imageV2.inPlaceUpdate = hgTracks.inPlaceUpdate && imageV2.mapIsUpdateable;
}
}
/////////////////////////////////////
////////// Genomic position /////////
/////////////////////////////////////
var genomePos = {
original: null,
originalSize: 0,
linkFixup: function (pos, id, reg, endParamName)
{ // fixup external links (e.g. ensembl)
var ele = $(document.getElementById(id));
if (ele.length) {
var link = ele.attr('href');
var a = reg.exec(link);
if (a && a[1]) {
ele.attr('href', a[1] + pos.start + "&" + endParamName + "=" + pos.end);
}
}
},
setByCoordinates: function (chrom, start, end)
{
var newPosition = chrom + ":" + start + "-" + end;
genomePos.set(newPosition, end - start + 1);
return newPosition;
},
getElement: function ()
{
// Return position box object
var tags = document.getElementsByName("position");
// There are multiple tags with name === "position" (the visible position text input
// and a hidden with id='positionHidden'); we return value of visible element.
for (var i = 0; i < tags.length; i++) {
var ele = tags[i];
if (ele.id !== "positionHidden") {
return ele;
}
}
return null;
},
get: function ()
{
// Return current value of position box
var ele = genomePos.getElement();
if (ele) {
return ele.value;
}
return null;
},
getOriginalPos: function ()
{
return genomePos.original || genomePos.get();
},
revertToOriginalPos: function ()
{
// undo changes to position (i.e. after user aborts a drag-and-select).
this.set(this.original, this.originalSize);
},
undisguisePosition: function(position) // UN-DISGUISE VMODE
{ // find the virt position
// position should be real chrom span
var pos = parsePosition(position);
if (!pos)
return position; // some parsing error, return original
var start = pos.start - 1;
var end = pos.end;
var chromName = hgTracks.windows[0].chromName;
if (pos.chrom !== chromName)
return position; // return original
var newStart = -1;
var newEnd = -1;
var lastW = null;
var windows = null;
for (j=0; j < 3; ++j) {
if (j === 0) windows = hgTracks.windowsBefore;
if (j === 1) windows = hgTracks.windows;
if (j === 2) windows = hgTracks.windowsAfter;
for (i=0,len=windows.length; i < len; ++i) {
var w = windows[i];
// double check chrom is same thoughout all windows, otherwise warning, return original value
if (w.chromName != chromName) {
return position; // return original
}
// check that the regions are ascending and non-overlapping
if (lastW && w.winStart < lastW.winEnd) {
return position; // return original
}
// overlap with position?
// if intersection,
if (w.winEnd > start && end > w.winStart) {
var s = Math.max(start, w.winStart);
var e = Math.min(end, w.winEnd);
var cs = s - w.winStart + w.virtStart;
var ce = e - w.winStart + w.virtStart;
if (newStart === -1)
newStart = cs;
newEnd = ce;
}
lastW = w;
}
}
// return new virt undisguised position as a string
var newPos = "multi:" + (newStart+1) + "-" + newEnd;
return newPos;
},
disguiseSize: function(position) // DISGUISE VMODE
{ // find the real size of the windows spanned
// position should be a real chrom span
var pos = parsePosition(position);
if (!pos)
return 0;
var start = pos.start - 1;
var end = pos.end;
var newSize = 0;
var windows = null;
for (j=0; j < 3; ++j) {
if (j === 0) windows = hgTracks.windowsBefore;
if (j === 1) windows = hgTracks.windows;
if (j === 2) windows = hgTracks.windowsAfter;
for (i=0,len=windows.length; i < len; ++i) {
var w = windows[i];
// overlap with position?
// if intersection,
if (w.winEnd > start && end > w.winStart) {
var s = Math.max(start, w.winStart);
var e = Math.min(end, w.winEnd);
newSize += (e - s);
}
}
}
// return real size of the disguised position
return newSize;
},
disguisePosition: function(position) // DISGUISE VMODE
{ // find the single-chrom range spanned
// position should be virt
var pos = parsePosition(position);
if (!pos)
return position; // some parsing error, return original
var start = pos.start - 1;
var end = pos.end;
var chromName = hgTracks.windows[0].chromName;
var newStart = -1;
var newEnd = -1;
var lastW = null;
var windows = null;
for (j=0; j < 3; ++j) {
if (j === 0) windows = hgTracks.windowsBefore;
if (j === 1) windows = hgTracks.windows;
if (j === 2) windows = hgTracks.windowsAfter;
for (i=0,len=windows.length; i < len; ++i) {
var w = windows[i];
// double check chrom is same thoughout all windows, otherwise warning, return original value
if (w.chromName != chromName) {
return position; // return undisguised original
}
// check that the regions are ascending and non-overlapping
if (lastW && w.winStart < lastW.winEnd) {
return position; // return undisguised original
}
// overlap with position?
// if intersection,
if (w.virtEnd > start && end > w.virtStart) {
var s = Math.max(start, w.virtStart);
var e = Math.min(end, w.virtEnd);
var cs = s - w.virtStart + w.winStart;
var ce = e - w.virtStart + w.winStart;
if (newStart === -1)
newStart = cs;
newEnd = ce;
}
lastW = w;
}
}
// return new non-virt disguised position as a string
var newPos = chromName + ":" + (newStart+1) + "-" + newEnd;
return newPos;
},
set: function (position, size)
{ // Set value of position and size (in hiddens and input elements).
// We assume size has already been commified.
// Either position or size may be null.
// stack dump // DEBUG
//console.trace();
// NOT work on safari
//var obj = {};
//Error.captureStackTrace(obj);
//warn("genomePos.set() called "+obj.stack);
position = position.replace(/,/g, ""); // strip out any commas
position.replace("virt:", "multi:");
if (position) {
// DISGUISE VMODE
//warn("genomePos.set() called, position = "+position);
if (hgTracks.virtualSingleChrom && (position.search("multi:")===0)) {
var newPosition = genomePos.disguisePosition(position);
//warn("genomePos.set() position = "+position+", newPosition = "+newPosition);
position = newPosition;
}
}
if (position) {
// There are multiple tags with name === "position"
// (one in TrackHeaderForm and another in TrackForm).
var tags = document.getElementsByName("position");
for (var i = 0; i < tags.length; i++) {
var ele = tags[i];
ele.value = position;
}
}
var pos = parsePosition(position);
if ($('#positionDisplay').length) {
// add commas to positionDisplay
var commaPosition = position;
if (pos)
commaPosition = pos.chrom+":"+commify(pos.start)+"-"+commify(pos.end);
$('#positionDisplay').text(commaPosition);
}
if (size) {
if (hgTracks.virtualSingleChrom && (position.search("multi:")!==0)) {
var newSize = genomePos.disguiseSize(position);
//warn("genomePos.set() position = "+position+", newSize = "+newSize);
if (newSize > 0)
size = newSize;
}
$('#size').text(commify(size)); // add commas
}
if (pos) {
// fixup external static links on page'
// Example ensembl link:
// http://www.ensembl.org/Homo_sapiens/contigview?chr=21&start=33031934&end=33041241
genomePos.linkFixup(pos, "ensemblLink", new RegExp("(.+start=)[0-9]+"), "end");
// Example NCBI Map Viewer link (obsolete):
// https://www.ncbi.nlm.nih.gov/mapview/maps.cgi?taxid=9606&CHR=21&BEG=33031934&END=33041241
genomePos.linkFixup(pos, "ncbiLink", new RegExp("(.+BEG=)[0-9]+"), "END");
// Example NCBI Genome Data Viewer link
// https://www.ncbi.nlm.nih.gov/genome/gdv/browser/?id=GCF_000001405.37&chr=4&from=45985744&to=45991655&context=genome
genomePos.linkFixup(pos, "ncbiLink", new RegExp("(.+from=)[0-9]+"), "to");
// Example medaka link:
// http://utgenome.org/medakabrowser_ens_jump.php?revision=version1.0&chr=chromosome18&start=14435198&end=14444829
genomePos.linkFixup(pos, "medakaLink", new RegExp("(.+start=)[0-9]+"), "end");
var link;
var reg;
var a;
if ($('#wormbaseLink').length) {
// e.g. http://www.wormbase.org/db/gb2/gbrowse/c_elegans?name=II:14646301-14667800
link = $('#wormbaseLink').attr('href');
reg = new RegExp("(.+:)[0-9]+");
a = reg.exec(link);
if (a && a[1]) {
$('#wormbaseLink').attr('href', a[1] + pos.start + "-" + pos.end);
}
}
// Fixup DNA link; e.g.:
// hgc?hgsid=2999470&o=114385768&g=getDna&i=mixed&c=chr7&l=114385768&r=114651696&db=panTro2&hgsid=2999470
if ($('#dnaLink').length) {
link = $('#dnaLink').attr('href');
reg = new RegExp("(.+&o=)[0-9]+.+&db=[^&]+(.*)");
a = reg.exec(link);
if (a && a[1]) {
var url = a[1] + (pos.start - 1) + "&g=getDna&i=mixed&c=" + pos.chrom;
url += "&l=" + (pos.start - 1) + "&r=" + pos.end + "&db=" + getDb() + a[2];
$('#dnaLink').attr('href', url);
}
}
}
if (!imageV2.backSupport)
imageV2.markAsDirtyPage();
},
getXLimits : function(img, slop) {
// calculate the min/max x position for drag-select, such that user cannot drag into the label area
var imgWidth = jQuery(img).width();
var imgOfs = jQuery(img).offset();
var leftX = hgTracks.revCmplDisp ? imgOfs.left - slop :
imgOfs.left + hgTracks.insideX - slop;
var rightX = hgTracks.revCmplDisp ? imgOfs.left + imgWidth - hgTracks.insideX + slop :
imgOfs.left + imgWidth + slop;
return [leftX, rightX];
},
check: function (img, selection)
{ // return true if user's selection is still w/n the img (including some slop).
var imgWidth = jQuery(img).width();
var imgHeight = jQuery(img).height();
var imgOfs = jQuery(img).offset();
var slop = 10;
// No need to check the x limits anymore, as imgAreaSelect is doing that now.
return ( (selection.event.pageY >= (imgOfs.top - slop))
&& (selection.event.pageY < (imgOfs.top + imgHeight + slop)));
},
pixelsToBases: function (img, selStart, selEnd, winStart, winEnd, addHalfBp)
{ // Convert image coordinates to chromosome coordinates
var imgWidth = jQuery(img).width() - hgTracks.insideX;
var width = hgTracks.winEnd - hgTracks.winStart;
var mult = width / imgWidth; // mult is bp/pixel multiplier
// where does a bp position start on the screen?
// For things like drag-select, if the user ends just before the nucleotide itself, do not count
// the nucleotide itself as selected. But for things like clicks onto
// a selection, if the user right-clicks just before the middle of the
// nucleotide, we certainly want to use this position.
var halfBpWidth = 0;
if (addHalfBp)
halfBpWidth = (imgWidth / width) / 2; // how many pixels does one bp take up;
var startDelta; // startDelta is how many bp's to the right/left
var x1;
// The magic number three appear at another place in the code
// as LEFTADD. It was originally annotated as "borders or cgi item calc
// ?" by Larry. It has to be used when going any time when converting
// between pixels and coordinates.
selStart -= 3;
selEnd -= 3;
if (hgTracks.revCmplDisp) {
x1 = Math.min(imgWidth, selStart);
startDelta = Math.floor(mult * (imgWidth - x1 - halfBpWidth));
} else {
x1 = Math.max(hgTracks.insideX, selStart);
startDelta = Math.floor(mult * (x1 - hgTracks.insideX + halfBpWidth));
}
var endDelta;
var x2;
if (hgTracks.revCmplDisp) {
endDelta = startDelta;
x2 = Math.min(imgWidth, selEnd);
startDelta = Math.floor(mult * (imgWidth - x2 + halfBpWidth));
} else {
x2 = Math.max(hgTracks.insideX, selEnd);
endDelta = Math.floor(mult * (x2 - hgTracks.insideX - halfBpWidth));
}
var newStart = hgTracks.winStart + startDelta;
var newEnd = hgTracks.winStart + 1 + endDelta;
// if user selects space between two bases, start>end can happen
if (newStart >= newEnd)
newStart = newEnd-1;
if (newEnd > winEnd) {
newEnd = winEnd;
}
return {chromStart : newStart, chromEnd : newEnd};
},
chromToVirtChrom: function (chrom, chromStart, chromEnd)
{ // Convert regular chromosome position to virtual chrom coordinates using hgTracks.windows list
// Consider the first contiguous set of overlapping regions to define the match (for now).
// only works for regions covered by the current hgTracks.windows
var virtStart = -1, virtEnd = -1;
var s,e;
var i, len;
for (i = 0, len = hgTracks.windows.length; i < len; ++i) {
var w = hgTracks.windows[i];
var overlap = (chrom == w.chromName && chromEnd > w.winStart && w.winEnd > chromStart);
if (virtStart == -1) {
if (overlap) {
// when they overlap the first time
s = Math.max(chromStart, w.winStart);
e = Math.min(chromEnd, w.winEnd);
virtStart = w.virtStart + (s - w.winStart);
virtEnd = w.virtStart + (e - w.winStart);
} else {
// until they overlap
// do nothing
}
} else {
if (overlap) {
// while they continue to overlap, extend
e = Math.min(chromEnd, w.winEnd);
virtEnd = w.virtStart + (e - w.winStart);
} else {
// when they do not overlap anymore, stop
break;
}
}
}
return {chromStart : virtStart, chromEnd : virtEnd};
},
selectionPixelsToBases: function (img, selection)
{ // Convert selection x1/x2 coordinates to chromStart/chromEnd.
return genomePos.pixelsToBases(img, selection.x1, selection.x2,
hgTracks.winStart, hgTracks.winEnd, true);
},
update: function (img, selection, singleClick)
{
var pos = genomePos.pixelsToBases(img, selection.x1, selection.x2,
hgTracks.winStart, hgTracks.winEnd, true);
// singleClick is true when the mouse hasn't moved (or has only moved a small amount).
if (singleClick) {
var center = (pos.chromStart + pos.chromEnd)/2;
pos.chromStart = Math.floor(center - hgTracks.newWinWidth/2);
pos.chromEnd = pos.chromStart + hgTracks.newWinWidth;
// clip
if (pos.chromStart < hgTracks.chromStart)
pos.chromStart = hgTracks.chromStart; // usually 1
if (pos.chromEnd > hgTracks.chromEnd)
pos.chromEnd = hgTracks.chromEnd; // usually virt chrom size
// save current position so that that it may be restored after highlight or cancel.
genomePos.original = genomePos.getOriginalPos();
genomePos.originalSize = $('#size').text().replace(/,/g, ""); // strip out any commas
}
var newPosition = genomePos.setByCoordinates(hgTracks.chromName,
pos.chromStart+1, pos.chromEnd);
return newPosition;
},
handleChange: function (response, status)
{
var json = JSON.parse(response);
genomePos.set(json.pos);
},
changeAssemblies: function (ele) // UNUSED? Larry's experimental code
{ // code to update page when user changes assembly select list.
$.ajax({
type: "GET",
url: "../cgi-bin/hgApi",
data: cart.varsToUrlData({ 'cmd': 'defaultPos', 'db': getDb() }),
dataType: "html",
trueSuccess: genomePos.handleChange,
success: catchErrorOrDispatch,
error: errorHandler,
cache: true
});
return false;
},
convertedVirtCoords : {chromStart : -1, chromEnd : -1},
handleConvertChromPosToVirtCoords: function (response, status)
{
var virtStart = -1, virtEnd = -1;
var newJson = scrapeVariable(response, "convertChromToVirtChrom");
if (!newJson) {
warn("convertChromToVirtChrom object is missing from the response");
} else {
virtStart = newJson.virtWinStart;
virtEnd = newJson.virtWinEnd;
}
genomePos.convertedVirtCoords = {chromStart : virtStart, chromEnd : virtEnd};
},
convertChromPosToVirtCoords: function (chrom, chromStart, chromEnd)
{ // code to convert chrom position to virt coords
genomePos.convertedVirtCoords = {chromStart : -1, chromEnd : -1}; // reset
var pos = chrom+":"+(chromStart+1)+"-"+chromEnd; // easier to pass 1 parameter than 3
$.ajax({
type: "GET",
async: false, // wait for result
url: "../cgi-bin/hgTracks",
data: cart.varsToUrlData({ 'hgt.convertChromToVirtChrom': pos, 'hgt.trackImgOnly' : 1, 'hgsid': getHgsid() }),
dataType: "html",
trueSuccess: genomePos.handleConvertChromPosToVirtCoords,
success: catchErrorOrDispatch,
error: errorHandler,
cache: false
});
return genomePos.convertedVirtCoords;
},
positionDisplayDialog: function ()
// Show the virtual and real positions of the windows
{
var position = genomePos.get();
position.replace("virt:", "multi:");
var positionDialog = $("#positionDialog")[0];
if (!positionDialog) {
$("body").append("
");
positionDialog = $("#positionDialog")[0];
}
if (hgTracks.windows) {
var i, len, end;
var matches = /^multi:[0-9]+-([0-9]+)/.exec(position);
var modeType = (hgTracks.virtModeType === "customUrl" ? "Custom regions on virtual chromosome" :
(hgTracks.virtModeType === "exonMostly" ? "Exon view of" :
(hgTracks.virtModeType === "geneMostly" ? "Gene view of" :
(hgTracks.virtModeType === "singleAltHaplo" ? "Alternate haplotype as virtual chromosome" :
"Unknown mode"))));
var str = modeType + " " + position;
if (matches) {
end = matches[1];
if (end < hgTracks.chromEnd) {
str += ". Full virtual region is multi:1-" + hgTracks.chromEnd + ". Zoom out to view.";
}
}
if (!(hgTracks.virtualSingleChrom && (hgTracks.windows.length === 1))) {
var w;
if (hgTracks.windows.length <= 10) {
str += "
\n";
for (i=0,len=hgTracks.windows.length; i < len; ++i) {
w = hgTracks.windows[i];
str += "
\n";
}
}
$("#positionDisplayPosition").html(str);
} else {
$("#positionDisplayPosition").html(position);
}
$(positionDialog).dialog({
modal: true,
title: "Multi-Region Position Ranges",
closeOnEscape: true,
resizable: false,
autoOpen: false,
minWidth: 400,
minHeight: 40,
close: function() {
// All exits to dialog should go through this
$(imageV2.imgTbl).imgAreaSelect({hide:true});
$(this).hide();
$('body').css('cursor', ''); // Occasionally wait cursor got left behind
}
});
$(positionDialog).dialog('open');
}
};
/////////////////////////////////////
//// Creating items by dragging /////
/////////////////////////////////////
var makeItemsByDrag = {
end: function (img, selection)
{
var image = $(img);
var imageId = image.attr('id');
var trackName = imageId.substring('img_data_'.length);
var pos = genomePos.selectionPixelsToBases(image, selection);
var command = document.getElementById('hgt_doJsCommand');
command.value = "makeItems " + trackName + " " + hgTracks.chromName;
command.value += " " + pos.chromStart + " " + pos.chromEnd;
document.TrackHeaderForm.submit();
return true;
},
init: function (trackName)
{
// Set up so that they can drag out to define a new item on a makeItems track.
var img = $("#img_data_" + trackName);
if (img && img.length !== 0) {
var imgHeight = imageV2.imgTbl.height();
jQuery(img.imgAreaSelect( { selectionColor: 'green', outerColor: '',
minHeight: imgHeight, maxHeight: imgHeight, onSelectEnd: makeItemsByDrag.end,
autoHide: true, movable: false}));
}
},
load: function ()
{
for (var id in hgTracks.trackDb) {
var rec = hgTracks.trackDb[id];
if (rec && rec.type && rec.type.indexOf("makeItems") === 0) {
this.init(id);
}
}
}
};
/////////////////
//// posting ////
/////////////////
var posting = {
blockUseMap: false,
blockMapClicks: function ()
// Blocks clicking on map items when in effect. Drag opperations frequently call this.
{
posting.blockUseMap=true;
},
allowMapClicks:function ()
// reallows map clicks. Called after operations that compete with clicks (e.g. dragging)
{
$('body').css('cursor', ''); // Explicitly remove wait cursor.
posting.blockUseMap=false;
},
mapClicksAllowed: function ()
// Verify that click-competing operation (e.g. dragging) isn't currently occurring.
{
return (posting.blockUseMap === false);
},
blockTheMapOnMouseMove: function (ev)
{
if (!posting.blockUseMap && mouse.hasMoved(ev)) {
posting.blockUseMap=true;
}
},
mapClk: function ()
{
var done = false;
// if we clicked on a merged item then show all the items, similar to clicking a
// dense track to turn it to pack
if (this && this.href && this.href.indexOf("i=mergedItem") !== -1) {
var id = this.href.slice(this.href.indexOf("&g="));
id = id.split(/&[^=]+=/)[1];
updateObj={};
updateObj[id+".doMergeItems"] = 0;
hgTracks.trackDb[id][id+".doMergeItems"] = 0;
cart.setVarsObj(updateObj,null,false);
imageV2.requestImgUpdate(id, id + ".doMergeItems=0");
return false;
}
if (false && imageV2.inPlaceUpdate) {
// XXXX experimental and only turned on in larrym's tree.
// Use in-place update if the map item just modifies the current position (this is nice
// because it's faster and preserves the users relative position in the track image).
//
// First test handles next/prev item.
var str = "/cgi-bin/hgTracks\\?position=([^:]+):(.+)&hgsid=(\\d+)" +
"&(hgt.(next|prev)Item=[^&]+)";
var reg = new RegExp(str);
var a = reg.exec(this.href);
if (a && a[1] && a[1] === hgTracks.chromName) {
imageV2.navigateInPlace("position=" + encodeURIComponent(a[1] + ":" + a[2]) +
"&" + a[4], null, true);
done = true;
} else {
// handle next/prev exon
str = "/cgi-bin/hgTracks\\?position=([^:]+):(.+)&hgsid=(\\d+)$";
reg = new RegExp(str);
a = reg.exec(this.href);
if (a && a[1]) {
imageV2.navigateInPlace("position=" + encodeURIComponent(a[1] + ":" + a[2]),
null, true);
done = true;
} else {
// handle toggle visibility.
// Request may include a track set, so we cannot use requestImgUpdate.
str = "/cgi-bin/hgTracks\\?(position=[^:]+:.+&hgsid=\\d+&([^=]+)=([^&]+))$";
reg = new RegExp(str);
a = reg.exec(this.href);
if (a && a[1]) {
imageV2.navigateInPlace(a[1], null, true);
// imageV2.requestImgUpdate(a[1], a[1] + "=" + a[2], "", a[2]);
done = true;
}
}
}
}
if (done)
return false;
else
return posting.saveSettings(this);
},
saveSettings: function (obj)
{
if (posting.blockUseMap === true) {
return false;
}
if (!obj || !obj.href) // called directly with obj
obj = this; // and from callback without obj
if ($(obj).hasClass('noLink')) // TITLE_BUT_NO_LINK
return false;
if (obj.href.match('#') || obj.target.length > 0) {
//alert("Matched # ["+obj.href+"] or has target:"+obj.target);
return true;
}
var thisForm = normed($(obj).parents('form').first());
if (!thisForm)
thisForm = normed($("FORM").first());
if (thisForm) {
//alert("posting form:"+$(thisForm).attr('name'));
return postTheForm($(thisForm).attr('name'),cart.addUpdatesToUrl(obj.href));
}
return true;
}
};
/////////////////////////
//// cart updating /////
///////////////////////
var cart = {
// Controls queuing and ultimately updating cart variables vis ajax or submit. Queued vars
// are held in an object with unique keys preventing duplicate updates and ensuring last update
// takes precedence. WARNING: be careful creating an object with variables on the fly:
// cart.setVarsObj({track: vis}) is invalid but cart.setVarsObj({'knownGene': vis}) is ok!
updateQueue: {},
updatesWaiting: function ()
{ // returns TRUE if updates are waiting.
return objNotEmpty(cart.updateQueue);
},
addUpdatesToUrl: function (url)
{ // adds any outstanding cart updates to the url, then clears the queue
if (cart.updatesWaiting()) {
//console.log('cart.addUpdatesToUrl: '+objKeyCount(cart.updateQueue)+' vars');
var updates = cart.varsToUrlData(); // clears the queue
if (!url || url.length === 0)
return updates;
if (updates.length > 0) {
var dataOnly = (url.indexOf("cgi-bin") === -1); // all urls should be to our cgis
if (!dataOnly && url.lastIndexOf("?") === -1)
url += "?" + updates;
else
url += '&' + updates;
}
}
return url;
},
varsToUrlData: function (varsObj)
{ // creates a url data (var1=val1&var2=val2...) string from vars object and queue
// The queue will be emptied by this call.
cart.queueVarsObj(varsObj); // lay ontop of queue, to give new values precedence
// Now convert to url data and clear queue
var urlData = '';
if (cart.updatesWaiting()) {
//console.log('cart.varsToUrlData: '+objKeyCount(cart.updateQueue)+' vars');
urlData = varHashToQueryString(cart.updateQueue);
cart.updateQueue = {};
}
return urlData;
},
setVarsObj: function (varsObj, errFunc, async)
{ // Set all vars in a var hash, appending any queued updates
// The default behavior is async = true
//console.log('cart.setVarsObj: were:'+objKeyCount(cart.updateQueue) +
// ' new:'+objKeyCount(varsObj);
cart.queueVarsObj(varsObj); // lay ontop of queue, to give new values precedence
// Now ajax update all in queue and clear queue
if (cart.updatesWaiting()) {
setVarsFromHash(cart.updateQueue, errFunc, async);
cart.updateQueue = {};
}
},
setVars: function (names, values, errFunc, async)
{ // ajax updates the cart, and includes any queued updates.
cart.updateSessionPanel(); // handles hide from left minibutton
cart.setVarsObj(arysToObj(names, values), errFunc, async);
},
queueVarsObj: function (varsObj)
{ // Add object worth of cart updates to the 'to be updated' queue, so they can be sent to
// the server later. Note: hash allows overwriting previous updates to the same variable.
if (typeof varsObj !== 'undefined' && objNotEmpty(varsObj)) {
//console.log('cart.queueVarsObj: were:'+objKeyCount(cart.updateQueue) +
// ' new:'+objKeyCount(varsObj));
for (var name in varsObj) {
cart.updateQueue[name] = varsObj[name];
}
}
},
addVarsToQueue: function (names,values)
{ // creates a string of updates to save for ajax batch or a submit
cart.queueVarsObj(arysToObj(names,values));
},
updateSessionPanel: function()
{
if (typeof recTrackSetsDetectChanges === 'undefined' || recTrackSetsDetectChanges === null)
return;
// change color of text
$('span.gbSessionChangeIndicator').addClass('gbSessionChanged');
// change mouseover on the panel. A bit fragile here inserting text in the mouseover specified in
// hgTracks.js, so depends on match with text there, and should present same message as C code
// (Perhaps this could be added as a script tag, so not duplicated)
var txt = $('span.gbSessionLabelPanel').attr('title');
if (txt && !txt.match(/with changes/)) {
$('span.gbSessionLabelPanel').attr('title', txt.replace(
"track set",
"track set, with changes (added or removed tracks) you have requested"));
}
}
};
///////////////////////////////////////////////
//// visibility (mixed with group toggle) /////
///////////////////////////////////////////////
var vis = {
// map cgi enum visibility codes to strings
enumOrder: new Array("hide", "dense", "full", "pack", "squish"),
update: function (track, visibility)
{ // Updates visibility state in hgTracks.trackDb and any visible elements on the page.
// returns true if we modify at least one select in the group list
var rec = hgTracks.trackDb[track];
var selectUpdated = false;
$("select[name=" + escapeJQuerySelectorChars(track) + "]").each(function(t) {
$(this).attr('class', visibility === 'hide' ? 'hiddenText' : 'normalText');
$(this).val(visibility);
selectUpdated = true;
});
if (rec) {
rec.localVisibility = visibility;
}
return selectUpdated;
},
get: function (track)
{ // return current visibility for given track
var rec = hgTracks.trackDb[track];
if (rec) {
if (rec.localVisibility) {
return rec.localVisibility;
} else {
return vis.enumOrder[rec.visibility];
}
} else {
return null;
}
},
makeTrackVisible: function (track)
{ // Sets the vis box to visible, and ques a cart update, but does not update the image
// Trusts that the cart update will be submitted later.
if (track && vis.get(track) !== "full") {
vis.update(track, 'pack');
cart.addVarsToQueue([track], ['pack']);
}
},
toggleForGroup: function (button, prefix)
{ // toggle visibility of a track group; prefix is the prefix of all the id's of tr's in the
// relevant group. This code also modifies the corresponding hidden fields and the gif
// of the +/- img tag.
imageV2.markAsDirtyPage();
if (arguments.length > 2)
return setTableRowVisibility(button, prefix, "hgtgroup", "group",false,arguments[2]);
else
return setTableRowVisibility(button, prefix, "hgtgroup", "group",false);
},
expandAllGroups: function (newState)
{ // Set visibility of all track groups to newState (true means expanded).
// This code also modifies the corresponding hidden fields and the gif's of the +/- img tag.
imageV2.markAsDirtyPage();
$(".toggleButton[id$='_button']").each( function (i) {
// works for old img type AND new BUTTONS_BY_CSS // - 7: clip '_button' suffix
vis.toggleForGroup(this,this.id.substring(0,this.id.length - 7),newState);
});
return false;
},
initForAjax: function()
{ // To better support the back-button, it is good to eliminate extraneous form puts
// Towards that end, we support visBoxes making ajax calls to update cart.
var sels = $('select.normalText,select.hiddenText');
$(sels).change(function() {
var track = $(this).attr('name');
if ($(this).val() === 'hide') {
var rec = hgTracks.trackDb[track];
if (rec)
rec.visibility = 0;
// else Would be nice to hide subtracks as well but that may be overkill
$(document.getElementById('tr_' + track)).remove();
cart.updateSessionPanel();
imageV2.drawHighlights();
$(this).attr('class', 'hiddenText');
} else
$(this).attr('class', 'normalText');
cart.setVars([track], [$(this).val()]);
imageV2.markAsDirtyPage();
return false;
});
// Now we can rid the submt of the burden of all those vis boxes
var form = $('form#TrackForm');
$(form).submit(function () {
$('select.normalText,select.hiddenText').attr('disabled',true);
});
$(form).attr('method','get');
},
restoreFromBackButton: function()
// Re-enabling vis dropdowns is necessary because initForAjax() disables them on submit.
{
$('select.normalText,select.hiddenText').attr('disabled',false);
}
};
////////////////////////////////////////////////////////////
// dragSelect is also known as dragZoom or shift-dragZoom //
////////////////////////////////////////////////////////////
var dragSelect = {
hlColorDefault: '#aaedff', // default highlight color, if nothing specified
hlColor : '#aaedff', // current highlight color
areaSelector: null, // formerly "imgAreaSelect". jQuery element used for imgAreaSelect
originalCursor: null,
startTime: null,
escPressed : false, // flag is set when user presses Escape
selectStart: function (img, selection)
{
initVars();
dragSelect.escPressed = false;
if (rightClick.menu) {
rightClick.menu.hide();
}
var now = new Date();
dragSelect.startTime = now.getTime();
posting.blockMapClicks();
},
selectChange: function (img, selection)
{
if (selection.x1 !== selection.x2) {
if (genomePos.check(img, selection)) {
genomePos.update(img, selection, false);
jQuery('body').css('cursor', dragSelect.originalCursor);
} else {
jQuery('body').css('cursor', 'not-allowed');
}
}
return true;
},
findHighlightIdxForPos : function(findPos) {
// return the index of the highlight string e.g. hg19.chrom1:123-345#AABBDCC that includes a chrom range findPos
// mostly copied from drawHighlights()
var currDb = getDb();
if (hgTracks.highlight) {
var hlArray = hgTracks.highlight.split("|"); // support multiple highlight items
for (var i = 0; i < hlArray.length; i++) {
hlString = hlArray[i];
pos = parsePositionWithDb(hlString);
imageV2.undisguiseHighlight(pos);
if (!pos)
continue; // ignore invalid position strings
pos.start--;
if (pos.chrom === hgTracks.chromName && pos.db === currDb
&& pos.start <= findPos.chromStart && pos.end >= findPos.chromEnd) {
return i;
}
}
}
return null;
},
saveHlColor : function (hlColor)
// save the current hlColor to the object and also the cart variable hlColor and return it.
// hlColor is a 6-character hex string prefixed by #
{
dragSelect.hlColor = hlColor;
cart.setVars(["prevHlColor"], [dragSelect.hlColor], null, false);
hgTracks.prevHlColor = hlColor; // cart.setVars does not update the hgTracks-variables. The cart-variable system is a problem.
return hlColor;
},
loadHlColor : function ()
// load hlColor from prevHlColor in the cart, or use default color, set and return it
// color is a 6-char hex string prefixed by #
{
if (hgTracks.prevHlColor)
dragSelect.hlColor = hgTracks.prevHlColor;
else
dragSelect.hlColor = dragSelect.hlColorDefault;
return dragSelect.hlColor;
},
highlightThisRegion: function(newPosition, doAdd, hlColor)
// set highlighting newPosition in server-side cart and apply the highlighting in local UI.
// hlColor can be undefined, in which case it defaults to the last used color or the default light blue
// if hlColor is set, it is also saved into the cart.
// if doAdd is true, the highlight is added to the current list. If it is false, all old highlights are deleted.
{
newPosition.replace("virt:", "multi:");
var hlColorName = null;
if (hlColor==="" || hlColor===null || hlColor===undefined)
hlColorName = dragSelect.loadHlColor();
else
hlColorName = dragSelect.saveHlColor( hlColor );
var pos = parsePosition(newPosition);
var start = pos.start;
var end = pos.end;
var newHighlight = makeHighlightString(getDb(), pos.chrom, start, end, hlColorName);
newHighlight = imageV2.disguiseHighlight(newHighlight);
var oldHighlight = hgTracks.highlight;
if (oldHighlight===undefined || doAdd===undefined || doAdd===false || oldHighlight==="") {
// just set/overwrite the old highlight position, this used to be the default
hgTracks.highlight = newHighlight;
}
else {
// add to the end of a |-separated list
hgTracks.highlight = oldHighlight+"|"+newHighlight;
}
// we include enableHighlightingDialog because it may have been changed by the dialog
var cartSettings = { 'highlight': hgTracks.highlight,
'enableHighlightingDialog': hgTracks.enableHighlightingDialog ? 1 : 0 };
if (hgTracks.windows && !hgTracks.virtualSingleChrom) {
var nonVirtChrom = "";
var nonVirtStart = -1;
var nonVirtEnd = -1;
for (i=0,len=hgTracks.windows.length; i < len; ++i) {
var w = hgTracks.windows[i];
// overlap with new position?
if (w.virtEnd > start && end > w.virtStart) {
var s = Math.max(start, w.virtStart);
var e = Math.min(end, w.virtEnd);
var cs = s - w.virtStart + w.winStart;
var ce = e - w.virtStart + w.winStart;
if (nonVirtChrom === "") {
nonVirtChrom = w.chromName;
nonVirtStart = cs;
nonVirtEnd = ce;
} else {
if (w.chromName === nonVirtChrom) {
nonVirtEnd = Math.max(ce, nonVirtEnd);
} else {
break;
}
}
}
}
if (nonVirtChrom !== "")
cartSettings.nonVirtHighlight = makeHighlightString(getDb(), nonVirtChrom, nonVirtStart, (nonVirtEnd+1), hlColorName);
} else if (hgTracks.windows && hgTracks.virtualSingleChrom) {
cartSettings.nonVirtHighlight = hgTracks.highlight;
}
// TODO if not virt, do we need to erase cart nonVirtHighlight ?
cart.setVarsObj(cartSettings);
imageV2.drawHighlights();
},
selectionEndDialog: function (newPosition)
// Let user choose between zoom-in and highlighting.
{
newPosition.replace("virt:", "multi:");
// if the user hit Escape just before, do not show this dialo
if (dragSelect.startTime===null)
return;
var dragSelectDialog = $("#dragSelectDialog")[0];
if (!dragSelectDialog) {
$("body").append("
" +
"
"+
"
Hold Shift+drag to show this dialog" +
"
Hold Alt+drag (Windows) or Option+drag (Mac) to add a highlight" +
"
Hold Ctrl+drag (Windows) or Cmd+drag (Mac) to zoom" +
"
To cancel, press Esc anytime during the drag" +
"
Using the keyboard, highlight the current position with h then m" +
"
Clear all highlights with View - Clear Highlights or h then c" +
"
To merely save the color for the next keyboard or right-click > Highlight operations, click 'Save Color' below" +
"
" +
"" +
"Don't show this again and always zoom with shift. " +
"Re-enable via 'View - Configure Browser' (c then f)"+
"Selected chromosome position: ");
dragSelectDialog = $("#dragSelectDialog")[0];
// reset value
$('#hlReset').click(function() {
var hlDefault = dragSelect.hlColorDefault;
$('#hlColorInput').val(hlDefault);
$("#hlColorPicker").spectrum("set", hlDefault);
dragSelect.saveHlColor(hlDefault);
});
// allow to click checkbox by clicking on the label
$('#hlNotShowAgainMsg').click(function() { $('#disableDragHighlight').click();});
// click "add highlight" when enter is pressed in color input box
$("#hlColorInput").keyup(function(event){
if(event.keyCode == 13){
$(".ui-dialog-buttonset button:nth-child(3)").click();
}
});
// activate the color picker
var opt = {
hideAfterPaletteSelect : true,
color : $('#hlColorInput').val(),
showPalette: true,
showInput: true,
preferredFormat: "hex",
change: function() { var color = $("#hlColorPicker").spectrum("get"); $('#hlColorInput').val(color); },
};
$("#hlColorPicker").spectrum(opt);
// update the color picker if you change the input box
$("#hlColorInput").change(function(){ $("#hlColorPicker").spectrum("set", $('#hlColorInput').val()); });
}
$("#hlColorPicker").spectrum("set", $('#hlColorInput').val());
if (hgTracks.windows) {
var i,len;
var newerPosition = newPosition;
if (hgTracks.virtualSingleChrom && (newPosition.search("multi:")===0)) {
newerPosition = genomePos.disguisePosition(newPosition);
}
var str = newerPosition + " \n";
var str2 = " \n";
str2 += "
\n";
var pos = parsePosition(newPosition);
var start = pos.start - 1;
var end = pos.end;
var selectedRegions = 0;
for (i=0,len=hgTracks.windows.length; i < len; ++i) {
var w = hgTracks.windows[i];
// overlap with new position?
if (w.virtEnd > start && end > w.virtStart) {
var s = Math.max(start, w.virtStart);
var e = Math.min(end, w.virtEnd);
var cs = s - w.virtStart + w.winStart;
var ce = e - w.virtStart + w.winStart;
str2 += "
" + w.chromName + ":" + (cs+1) + "-" + ce + "
\n";
selectedRegions += 1;
}
}
str2 += "
\n";
if (!(hgTracks.virtualSingleChrom && (selectedRegions === 1))) {
str += str2;
}
$("#dragSelectPosition").html(str);
} else {
$("#dragSelectPosition").html(newPosition);
}
$(dragSelectDialog).dialog({
modal: true,
title: "Drag-and-select",
closeOnEscape: true,
resizable: false,
autoOpen: false,
revertToOriginalPos: true,
minWidth: 550,
buttons: {
"Zoom In": function() {
// Zoom to selection
$(this).dialog("option", "revertToOriginalPos", false);
if ($("#disableDragHighlight").attr('checked'))
hgTracks.enableHighlightingDialog = false;
if (imageV2.inPlaceUpdate) {
if (hgTracks.virtualSingleChrom && (newPosition.search("multi:")===0)) {
newPosition = genomePos.disguisePosition(newPosition); // DISGUISE
}
var params = "position=" + newPosition;
if (!hgTracks.enableHighlightingDialog)
params += "&enableHighlightingDialog=0";
imageV2.navigateInPlace(params, null, true);
} else {
$('body').css('cursor', 'wait');
if (!hgTracks.enableHighlightingDialog)
cart.setVarsObj({'enableHighlightingDialog': 0 },null,false); // async=false
document.TrackHeaderForm.submit();
}
$(this).dialog("close");
},
"Single Highlight": function() {
// Clear old highlight and Highlight selection
$(imageV2.imgTbl).imgAreaSelect({hide:true});
if ($("#disableDragHighlight").attr('checked'))
hgTracks.enableHighlightingDialog = false;
var hlColor = $("#hlColorInput").val();
dragSelect.highlightThisRegion(newPosition, false, hlColor);
$(this).dialog("close");
},
"Add Highlight": function() {
// Highlight selection
if ($("#disableDragHighlight").attr('checked'))
hgTracks.enableHighlightingDialog = false;
var hlColor = $("#hlColorInput").val();
dragSelect.highlightThisRegion(newPosition, true, hlColor);
$(this).dialog("close");
},
"Save Color": function() {
var hlColor = $("#hlColorInput").val();
dragSelect.saveHlColor( hlColor );
$(this).dialog("close");
},
"Cancel": function() {
$(this).dialog("close");
}
},
open: function () { // Make zoom the focus/default action
$(this).parents('.ui-dialog-buttonpane button:eq(0)').focus();
},
close: function() {
// All exits to dialog should go through this
$(imageV2.imgTbl).imgAreaSelect({hide:true});
if ($(this).dialog("option", "revertToOriginalPos"))
genomePos.revertToOriginalPos();
if ($("#disableDragHighlight").attr('checked'))
$(this).remove();
else
$(this).hide();
$('body').css('cursor', ''); // Occasionally wait cursor got left behind
$("#hlColorPicker").spectrum("hide");
}
});
$(dragSelectDialog).dialog('open');
// put the cursor into the input field
// we are not doing this for now - default behavior was to zoom when enter was pressed
// so people may still expect that "enter" on the dialog will zoom.
//var el = $("#hlColorInput")[0];
//el.selectionStart = 0;
//el.selectionEnd = el.value.length;
//el.focus();
},
selectEnd: function (img, selection, event)
{
var now = new Date();
var doIt = false;
var rulerClicked = selection.y1 <= hgTracks.rulerClickHeight; // = drag on base position track (no shift)
if (dragSelect.originalCursor)
jQuery('body').css('cursor', dragSelect.originalCursor);
if (dragSelect.escPressed)
return false;
// ignore releases outside of the image rectangle (allowing a 10 pixel slop)
if (genomePos.check(img, selection)) {
// ignore single clicks that aren't in the top of the image
// (this happens b/c the clickClipHeight test in dragSelect.selectStart
// doesn't occur when the user single clicks).
doIt = (dragSelect.startTime !== null || rulerClicked);
}
if (doIt) {
// dragSelect.startTime is null if mouse has never been moved
var singleClick = ( (selection.x2 === selection.x1)
|| dragSelect.startTime === null
|| (now.getTime() - dragSelect.startTime) < 100);
var newPosition = genomePos.update(img, selection, singleClick);
newPosition.replace("virt:", "multi:");
if (newPosition) {
if (event.altKey) {
// with the alt-key, only highlight the region, do not zoom
dragSelect.highlightThisRegion(newPosition, true);
$(imageV2.imgTbl).imgAreaSelect({hide:true});
} else {
if (hgTracks.enableHighlightingDialog && !(event.metaKey || event.ctrlKey))
// don't show the dialog if: clicked on ruler, if dialog deactivated or meta/ctrl was pressed
dragSelect.selectionEndDialog(newPosition);
else {
// in every other case, show the dialog
$(imageV2.imgTbl).imgAreaSelect({hide:true});
if (imageV2.inPlaceUpdate) {
if (hgTracks.virtualSingleChrom && (newPosition.search("multi:")===0)) {
newPosition = genomePos.disguisePosition(newPosition); // DISGUISE
}
imageV2.navigateInPlace("position=" + newPosition, null, true);
} else {
jQuery('body').css('cursor', 'wait');
document.TrackHeaderForm.submit();
}
}
}
} else {
$(imageV2.imgTbl).imgAreaSelect({hide:true});
genomePos.revertToOriginalPos();
}
dragSelect.startTime = null;
// blockMapClicks/allowMapClicks() is necessary if selectEnd was over a map item.
setTimeout(posting.allowMapClicks,50);
return true;
}
},
load: function (firstTime)
{
var imgHeight = 0;
if (imageV2.enabled)
imgHeight = imageV2.imgTbl.innerHeight() - 1; // last little bit makes border look ok
// No longer disable without ruler, because shift-drag still works
if (typeof(hgTracks) !== "undefined") {
if (hgTracks.rulerClickHeight === undefined || hgTracks.rulerClickHeight === null)
hgTracks.rulerClickHeight = 0; // will be zero if no ruler track
var heights = hgTracks.rulerClickHeight;
var xLimits = genomePos.getXLimits($(imageV2.imgTbl), 0);
dragSelect.areaSelector = jQuery((imageV2.imgTbl).imgAreaSelect({
minX : xLimits[0],
maxX : xLimits[1],
selectionColor: 'blue',
outerColor: '',
minHeight: imgHeight,
maxHeight: imgHeight,
onSelectStart: dragSelect.selectStart,
onSelectChange: dragSelect.selectChange,
onSelectEnd: dragSelect.selectEnd,
autoHide: false, // gets hidden after possible dialog
movable: false,
clickClipHeight: heights
}));
// remove any ongoing drag-selects when the esc key is pressed anywhere for this document
// This allows to abort zooming / highlighting
$(document).keyup(function(e){
if(e.keyCode === 27) {
$(imageV2.imgTbl).imgAreaSelect({hide:true});
dragSelect.escPressed = true;
}
});
// hide and redraw all current highlights when the browser window is resized
$(window).resize(function() {
$(imageV2.imgTbl).imgAreaSelect({hide:true});
imageV2.drawHighlights();
});
}
}
};
/////////////////////////////////////
//// Chrom Drag/Zoom/Expand code ////
/////////////////////////////////////
jQuery.fn.chromDrag = function(){
this.each(function(){
// Plan:
// mouseDown: determine where in map: convert to img location: pxDown
// mouseMove: flag drag
// mouseUp: if no drag, then create href centered on bpDown loc with current span
// if drag, then create href from bpDown to bpUp
// if ctrlKey then expand selection to containing cytoBand(s)
// Image dimensions all in pix
var img = { top: -1, scrolledTop: -1, height: -1, left: -1, scrolledLeft: -1, width: -1 };
// chrom Dimensions beg,end,size in bases, rest in pix
var chr = { name: "", reverse: false, beg: -1, end: -1, size: -1,
top: -1, bottom: -1, left: -1, right: -1, width: -1 };
var pxDown = 0; // pix X location of mouseDown
var chrImg = $(this);
var mouseIsDown = false;
var mouseHasMoved = false;
var hilite = null;
initialize();
function initialize(){
findDimensions();
if (chr.top === -1)
warn("chromIdeo(): failed to register "+this.id);
else {
hiliteSetup();
$('area.cytoBand').unbind('mousedown'); // Make sure this is only bound once
$('area.cytoBand').mousedown( function(e)
{ // mousedown on chrom portion of image only (map items)
updateImgOffsets();
pxDown = e.clientX - img.scrolledLeft;
var pxY = e.clientY - img.scrolledTop;
if (mouseIsDown === false
&& isWithin(chr.left,pxDown,chr.right) && isWithin(chr.top,pxY,chr.bottom)) {
mouseIsDown = true;
mouseHasMoved = false;
$(document).bind('mousemove',chromMove);
$(document).bind( 'mouseup', chromUp);
hiliteShow(pxDown,pxDown);
return false;
}
});
}
}
function chromMove(e)
{ // If mouse was down, determine if dragged, then show hilite
if ( mouseIsDown ) {
var pxX = e.clientX - img.scrolledLeft;
var relativeX = (pxX - pxDown);
if (mouseHasMoved || (mouseHasMoved === false && Math.abs(relativeX) > 2)) {
mouseHasMoved = true;
if (isWithin(chr.left,pxX,chr.right))
hiliteShow(pxDown,pxX);
else if (pxX < chr.left)
hiliteShow(pxDown,chr.left);
else
hiliteShow(pxDown,chr.right);
}
}
}
function chromUp(e)
{ // If mouse was down, handle final selection
$(document).unbind('mousemove',chromMove);
$(document).unbind('mouseup',chromUp);
chromMove(e); // Just in case
if (mouseIsDown) {
updateImgOffsets();
var bands;
var pxUp = e.clientX - img.scrolledLeft;
var pxY = e.clientY - img.scrolledTop;
if (isWithin(0,pxY,img.height)) { // within vertical range or else cancel
var selRange = { beg: -1, end: -1, width: -1 };
var dontAsk = true;
if (e.ctrlKey) {
bands = findCytoBand(pxDown,pxUp);
if (bands.end > -1) {
pxDown = bands.left;
pxUp = bands.right;
mouseHasMoved = true;
dontAsk = false;
selRange.beg = bands.beg;
selRange.end = bands.end;
hiliteShow(pxDown,pxUp);
}
}
else if (mouseHasMoved) {
// bounded by chrom dimensions: but must remain within image!
if (isWithin(-20,pxUp,chr.left))
pxUp = chr.left;
if (isWithin(chr.right,pxUp,img.width + 20))
pxUp = chr.right;
if ( isWithin(chr.left,pxUp,chr.right+1) ) {
selRange.beg = convertToBases(pxDown);
selRange.end = convertToBases(pxUp);
if (Math.abs(selRange.end - selRange.beg) < 20)
mouseHasMoved = false; // Drag so small: treat as simple click
else
dontAsk = false;
}
}
if (mouseHasMoved === false) { // Not else because small drag turns this off
hiliteShow(pxUp,pxUp);
var curWidth = hgTracks.winEnd - hgTracks.winStart;
// Notice that beg is based upon up position
selRange.beg = convertToBases(pxUp) - Math.round(curWidth/2);
selRange.end = selRange.beg + curWidth;
}
if (selRange.end > -1) {
// prompt, then submit for new position
selRange = rangeNormalizeToChrom(selRange,chr);
if (mouseHasMoved === false) { // Update highlight by converting bp back to pix
pxDown = convertFromBases(selRange.beg);
pxUp = convertFromBases(selRange.end);
hiliteShow(pxDown,pxUp);
}
//if ((selRange.end - selRange.beg) < 50000)
// dontAsk = true;
if (dontAsk
|| confirm("Jump to new position:\n\n"+chr.name+":"+commify(selRange.beg)+
"-"+commify(selRange.end)+" size:"+commify(selRange.width)) ) {
genomePos.setByCoordinates(chr.name, selRange.beg, selRange.end);
// Stop the presses :0)
$('area.cytoBand').mousedown( function(e) { return false; });
if (imageV2.backSupport) {
imageV2.navigateInPlace("position=" +
encodeURIComponent(genomePos.get().replace(/,/g,'')) +
"&findNearest=1",null,true);
hiliteCancel();
} else
document.TrackHeaderForm.submit();
return true; // Make sure the setTimeout below is not called.
}
}
}
hiliteCancel();
setTimeout(posting.allowMapClicks,50);
}
mouseIsDown = false;
mouseHasMoved = false;
}
function isWithin(beg,here,end)
{ // Simple utility
return ( beg <= here && here < end );
}
function convertToBases(pxX)
{ // Simple utility to convert pix to bases
var offset = (pxX - chr.left)/chr.width;
if (chr.reverse)
offset = 1 - offset;
return Math.round(offset * chr.size);
}
function convertFromBases(bases)
{ // Simple utility to convert bases to pix
var offset = bases/chr.size;
if (chr.reverse)
offset = 1 - offset;
return Math.round(offset * chr.width) + chr.left;
}
function findDimensions()
{ // Called at init: determine the dimensions of chrom from 'cytoband' map items
var lastX = -1;
$('area.cytoBand').each(function(ix) {
var loc = this.coords.split(",");
if (loc.length === 4) {
var myLeft = parseInt(loc[0]);
var myRight = parseInt(loc[2]);
if (chr.top === -1) {
chr.left = myLeft;
chr.right = myRight;
chr.top = parseInt(loc[1]);
chr.bottom = parseInt(loc[3]);
} else {
if (chr.left > myLeft)
chr.left = myLeft;
if (chr.right < parseInt(loc[2]))
chr.right = parseInt(loc[2]);
}
var range = this.title.substr(this.title.lastIndexOf(':')+1);
var pos = range.split('-');
if (pos.length === 2) {
if (chr.name.length === 0) {
chr.beg = parseInt(pos[0]);
//chr.end = parseInt(pos[1]);
chr.name = this.title.substring(this.title.lastIndexOf(' ')+1,
this.title.lastIndexOf(':'));
} else {
if (chr.beg > parseInt(pos[0]))
chr.beg = parseInt(pos[0]);
}
if (chr.end < parseInt(pos[1])) {
chr.end = parseInt(pos[1]);
if (lastX === -1)
lastX = myRight;
else if (lastX > myRight)
chr.reverse = true; // end is advancing, but X is not, so reverse
} else if (lastX !== -1 && lastX < myRight)
chr.reverse = true; // end is not advancing, but X is, so reverse
}
$(this).css( 'cursor', 'text');
$(this).attr("href","");
}
});
chr.size = (chr.end - chr.beg );
chr.width = (chr.right - chr.left);
}
function findCytoBand(pxDown,pxUp)
{ // Called when mouseup and ctrl: Find the bounding cytoband dimensions (in pix and bases)
var cyto = { left: -1, right: -1, beg: -1, end: -1 };
$('area.cytoBand').each(function(ix) {
var loc = this.coords.split(",");
if (loc.length === 4) {
var myLeft = parseInt(loc[0]);
var myRight = parseInt(loc[2]);
var range;
var pos;
if (cyto.left === -1 || cyto.left > myLeft) {
if ( isWithin(myLeft,pxDown,myRight) || isWithin(myLeft,pxUp,myRight) ) {
cyto.left = myLeft;
range = this.title.substr(this.title.lastIndexOf(':')+1);
pos = range.split('-');
if (pos.length === 2) {
cyto.beg = (chr.reverse ? parseInt(pos[1]) : parseInt(pos[0]));
}
}
}
if (cyto.right === -1 || cyto.right < myRight) {
if ( isWithin(myLeft,pxDown,myRight) || isWithin(myLeft,pxUp,myRight) ) {
cyto.right = myRight;
range = this.title.substr(this.title.lastIndexOf(':')+1);
pos = range.split('-');
if (pos.length === 2) {
cyto.end = (chr.reverse ? parseInt(pos[0]) : parseInt(pos[1]));
}
}
}
}
});
return cyto;
}
function rangeNormalizeToChrom(selection,chrom)
{ // Called before presenting or using base range: make sure chrom selection
// is within chrom range
if (selection.end < selection.beg) {
var tmp = selection.end;
selection.end = selection.beg;
selection.beg = tmp;
}
selection.width = (selection.end - selection.beg);
selection.beg += 1;
if (selection.beg < chrom.beg) {
selection.beg = chrom.beg;
selection.end = chrom.beg + selection.width;
}
if (selection.end > chrom.end) {
selection.end = chrom.end;
selection.beg = chrom.end - selection.width;
if (selection.beg < chrom.beg) { // spans whole chrom
selection.width = (selection.end - chrom.beg);
selection.beg = chrom.beg + 1;
}
}
return selection;
}
function hiliteShow(down,cur)
{ // Called on mousemove, mouseup: set drag hilite dimensions
var topY = img.top;
var high = img.height;
var begX = -1;
var wide = -1;
if (cur < down) {
begX = cur + img.left;
wide = (down - cur);
} else {
begX = down + img.left;
wide = (cur - down);
}
$(hilite).css({ left: begX + 'px', width: wide + 'px', top: topY + 'px',
height: high + 'px', display:'' });
$(hilite).show();
}
function hiliteCancel(left,width,top,height)
{ // Called on mouseup: Make green drag hilite disappear when no longer wanted
$(hilite).hide();
$(hilite).css({ left: '0px', width: '0px', top: '0px', height: '0px' });
}
function hiliteSetup()
{ // Called on init: setup of drag region hilite (but don't show yet)
if (hilite === null) { // setup only once
hilite = jQuery("");
$(hilite).css({ backgroundColor: 'green', opacity: 0.4, borderStyle: 'solid',
borderWidth: '1px', bordercolor: '#0000FF' });
$(hilite).css({ display: 'none', position: 'absolute', overflow: 'hidden', zIndex: 1 });
jQuery($(chrImg).parents('body')).append($(hilite));
}
return hilite;
}
function updateImgOffsets()
{ // Called on mousedown: Gets the current offsets
var offs = $(chrImg).offset();
img.top = Math.round(offs.top );
img.left = Math.round(offs.left);
img.scrolledTop = img.top - $("body").scrollTop();
img.scrolledLeft = img.left - $("body").scrollLeft();
if (theClient.isIePre11()) {
img.height = $(chrImg).outerHeight();
img.width = $(chrImg).outerWidth();
} else {
img.height = $(chrImg).height();
img.width = $(chrImg).width();
}
return img;
}
});
};
//////////////////////////
//// Drag Scroll code ////
//////////////////////////
jQuery.fn.panImages = function(){
// globals across all panImages
genomePos.original = genomePos.getOriginalPos(); // redundant but makes certain original is set.
var leftLimit = hgTracks.imgBoxLeftLabel * -1;
var rightLimit = (hgTracks.imgBoxPortalWidth - hgTracks.imgBoxWidth + leftLimit);
var only1xScrolling = ((hgTracks.imgBoxWidth - hgTracks.imgBoxPortalWidth) === 0);
var prevX = (hgTracks.imgBoxPortalOffsetX + hgTracks.imgBoxLeftLabel) * -1;
var portalWidth = 0;
var portalAbsoluteX = 0;
var savedPosition;
var highlightAreas = null; // Used to ensure dragSelect highlight will scroll.
this.each(function(){
var pic;
var pan;
if ( $(this).is("img") ) {
pan = $(this).parent("div");
pic = $(this);
}
else if ( $(this).is("div.scroller") ) {
pan = $(this);
pic = $(this).children("img#panImg"); // Get the real pic
}
if (!pan || !pic) {
throw "Not a div with child image! 'panImages' can only be used with divs contain images.";
}
// globals across all panImages
portalWidth = $(pan).width();
portalAbsoluteX = $(pan).offset().left;
// globals to one panImage
var newX = 0;
var mouseDownX = 0;
var mouseIsDown = false;
var beyondImage = false;
var atEdge = false;
initialize();
function initialize(){
$(pan).parents('td.tdData').mousemove(function(e) {
if (e.shiftKey)
$(this).css('cursor',"crosshair"); // shift-dragZoom
else if ( theClient.isIePre11() ) // IE will override map item cursors if this gets set
$(this).css('cursor',""); // normal pointer when not over clickable item
});
panAdjustHeight(prevX);
pan.mousedown(function(e){
if (e.which > 1 || e.button > 1 || e.shiftKey || e.metaKey || e.altKey || e.ctrlKey)
return true;
if (mouseIsDown === false) {
if (rightClick.menu) {
rightClick.menu.hide();
}
mouseIsDown = true;
mouseDownX = e.clientX;
highlightAreas = $('.highlightItem');
atEdge = (!beyondImage && (prevX >= leftLimit || prevX <= rightLimit));
$(document).bind('mousemove',panner);
$(document).bind( 'mouseup', panMouseUp); // Will exec only once
return false;
}
});
}
function panner(e) {
//if (!e) e = window.event;
if ( mouseIsDown ) {
var relativeX = (e.clientX - mouseDownX);
if (relativeX !== 0) {
if (posting.mapClicksAllowed()) {
// need to throw up a z-index div. Wait mask?
savedPosition = genomePos.get();
dragMaskShow();
posting.blockMapClicks();
}
var decelerator = 1;
//var wingSize = 1000; // 0 stops the scroll at the edges.
// Remeber that offsetX (prevX) is negative
newX = prevX + relativeX;
if ( newX >= leftLimit ) { // scrolled all the way to the left
if (atEdge) { // Do not drag straight off edge. Force second drag
beyondImage = true;
newX = leftLimit + (newX - leftLimit)/decelerator;// slower
//if (newX >= leftLimit + wingSize) // Don't go too far over the edge!
// newX = leftLimit + wingSize;
} else
newX = leftLimit;
} else if ( newX < rightLimit ) { // scrolled all the way to the right
if (atEdge) { // Do not drag straight off edge. Force second drag
beyondImage = true;
newX = rightLimit - (rightLimit - newX)/decelerator;// slower
//if (newX < rightLimit - wingSize) // Don't go too far over the edge!
// newX = rightLimit - wingSize;
} else
newX = rightLimit;
} else if (newX >= rightLimit && newX < leftLimit)
beyondImage = false; // could have scrolled back without mouse up
posStatus = panUpdatePosition(newX,true);
newX = posStatus.newX;
// do not update highlights if we are at the end of a chromsome
if (!posStatus.isOutsideChrom)
scrollHighlight(relativeX);
var nowPos = newX.toString() + "px";
$(".panImg").css( {'left': nowPos });
$('.tdData').css( {'backgroundPosition': nowPos } );
if (!only1xScrolling)
panAdjustHeight(newX); // Will dynamically resize image while scrolling.
}
}
}
function panMouseUp(e) { // Must be a separate function instead of pan.mouseup event.
//if (!e) e = window.event;
if (mouseIsDown) {
dragMaskClear();
$(document).unbind('mousemove',panner);
$(document).unbind('mouseup',panMouseUp);
mouseIsDown = false;
// timeout incase the dragSelect.selectEnd was over a map item. select takes precedence.
setTimeout(posting.allowMapClicks,50);
// Outside image? Then abandon.
var curY = e.pageY;
var imgTbl = $('#imgTbl');
var north = $(imgTbl).offset().top;
var south = north + $(imgTbl).height();
if (curY < north || curY > south) {
atEdge = false;
beyondImage = false;
if (savedPosition)
genomePos.set(savedPosition);
var oldPos = prevX.toString() + "px";
$(".panImg").css( {'left': oldPos });
$('.tdData').css( {'backgroundPosition': oldPos } );
if (highlightAreas)
imageV2.drawHighlights();
return true;
}
// Do we need to fetch anything?
if (beyondImage) {
if (imageV2.inPlaceUpdate) {
var pos = parsePosition(genomePos.get());
imageV2.navigateInPlace("position=" +
encodeURIComponent(pos.chrom + ":" + pos.start + "-" + pos.end),
null, true);
} else {
document.TrackHeaderForm.submit();
}
return true; // Make sure the setTimeout below is not called.
}
// Just a normal scroll within a >1X image
if (prevX !== newX) {
prevX = newX;
if (!only1xScrolling) {
//panAdjustHeight(newX); // Will resize image AFTER scrolling.
// Important, since AJAX could lead to reinit after this within bounds scroll
hgTracks.imgBoxPortalOffsetX = (prevX * -1) - hgTracks.imgBoxLeftLabel;
hgTracks.imgBoxPortalLeft = newX.toString() + "px";
}
}
}
}
}); // end of this.each(function(){
function panUpdatePosition(newOffsetX,bounded)
{
// Updates the 'position/search" display with change due to panning
var closedPortalStart = hgTracks.imgBoxPortalStart + 1; // Correction for half open
var portalWidthBases = hgTracks.imgBoxPortalEnd - closedPortalStart;
var portalScrolledX = hgTracks.imgBoxPortalOffsetX+hgTracks.imgBoxLeftLabel + newOffsetX;
var recalculate = false;
var newPortalStart = 0;
if (hgTracks.revCmplDisp)
newPortalStart = closedPortalStart + // As offset goes down, so do bases seen.
Math.round(portalScrolledX*hgTracks.imgBoxBasesPerPixel);
else
newPortalStart = closedPortalStart - // As offset goes down, bases seen goes up!
Math.round(portalScrolledX*hgTracks.imgBoxBasesPerPixel);
if (newPortalStart < hgTracks.chromStart && bounded) { // Stay within bounds
newPortalStart = hgTracks.chromStart;
recalculate = true;
}
var newPortalEnd = newPortalStart + portalWidthBases;
if (newPortalEnd > hgTracks.chromEnd && bounded) {
newPortalEnd = hgTracks.chromEnd;
newPortalStart = newPortalEnd - portalWidthBases;
recalculate = true;
}
if (newPortalStart > 0) {
var newPos = hgTracks.chromName + ":" + newPortalStart + "-" + newPortalEnd;
genomePos.set(newPos); // no need to change the size
}
if (recalculate && hgTracks.imgBoxBasesPerPixel > 0) {
// Need to recalculate X for bounding drag
portalScrolledX = (closedPortalStart - newPortalStart) / hgTracks.imgBoxBasesPerPixel;
newOffsetX = portalScrolledX - (hgTracks.imgBoxPortalOffsetX+hgTracks.imgBoxLeftLabel);
}
ret = {};
ret.newX = newOffsetX;
ret.isOutsideChrom = recalculate;
return ret;
}
function mapTopAndBottom(mapName,east,west)
{
// Find the top and bottom px given left and right boundaries
var mapPortal = { top: -10, bottom: -10 };
var items = $("map[name='"+mapName+"']").children();
if ($(items).length>0) {
$(items).each(function(t) {
var loc = this.coords.split(",");
var aleft = parseInt(loc[0]);
var aright = parseInt(loc[2]);
if (aleft < west && aright >= east) {
var atop = parseInt(loc[1]);
var abottom = parseInt(loc[3]);
if (mapPortal.top < 0 ) {
mapPortal.top = atop;
mapPortal.bottom = abottom;
} else if (mapPortal.top > atop) {
mapPortal.top = atop;
} else if (mapPortal.bottom < abottom) {
mapPortal.bottom = abottom;
}
}
});
}
return mapPortal;
}
function panAdjustHeight(newOffsetX) {
// Adjust the height of the track data images so that bed items scrolled off screen
// do not waste vertical real estate
// Is the > 1x?
if (only1xScrolling)
return;
var east = newOffsetX * -1;
var west = east + portalWidth;
$(".panImg").each(function(t) {
var mapid = this.id.replace('img_','map_');
var hDiv = $(this).parent();
var north = parseInt($(this).css("top")) * -1;
var south = north + $(hDiv).height();
var mapPortal = mapTopAndBottom(mapid,east,west);
if (mapPortal.top > 0) {
var topdif = Math.abs(mapPortal.top - north);
var botdif = Math.abs(mapPortal.bottom - south);
if (topdif > 2 || botdif > 2) {
$(hDiv).height( mapPortal.bottom - mapPortal.top );
north = mapPortal.top * -1;
$(this).css( {'top': north.toString() + "px" });
// Need to adjust side label height as well!
var imgId = this.id.split("_");
var titlePx = 0;
var center = $("#img_center_"+imgId[2]);
if (center.length > 0) {
titlePx = $(center).parent().height();
north += titlePx;
}
var side = $("#img_side_"+imgId[2]);
if (side.length > 0) {
$(side).parent().height( mapPortal.bottom - mapPortal.top + titlePx);
$(side).css( {'top': north.toString() + "px" });
}
var btn = $("#p_btn_"+imgId[2]);
if (btn.length > 0) {
$(btn).height( mapPortal.bottom - mapPortal.top + titlePx);
} else {
btn = $("#img_btn_"+imgId[2]);
if (btn.length > 0) {
$(btn).parent().height( mapPortal.bottom - mapPortal.top + titlePx);
$(btn).css( {'top': top.toString() + "px" });
}
}
}
}
});
dragMaskResize(); // Resizes the dragMask to match current image size
}
function dragMaskShow()
{ // Sets up the dragMask to show grabbing cursor within image
// and not allowed north and south of image
var imgTbl = $('#imgTbl');
// Find or create the waitMask (which masks the whole page)
var dragMask = normed($('div#dragMask'));
if (!dragMask) {
$("body").prepend("");
dragMask = $('div#dragMask');
}
$('body').css('cursor','not-allowed');
$(dragMask).css('cursor',"url(../images/grabbing.cur),w-resize");
$(dragMask).css({opacity:0.0,display:'block',
top: $(imgTbl).position().top.toString() + 'px',
height: $(imgTbl).height().toString() + 'px' });
}
function dragMaskResize()
{ // Resizes dragMask (called when image is dynamically resized in >1x scrolling)
var imgTbl = $('#imgTbl');
// Find or create the waitMask (which masks the whole page)
var dragMask = normed($('div#dragMask'));
if (dragMask) {
$(dragMask).height( $(imgTbl).height() );
}
}
function dragMaskClear() { // Clears the dragMask
$('body').css('cursor','auto');
var dragMask = normed($('#dragMask'));
if (dragMask)
$(dragMask).hide();
}
function scrollHighlight(relativeX)
// Scrolls the highlight region if one exists
{
if (highlightAreas) {
for (i=0; i 0 && hgTracks.trackDb) {
var title;
var rec = hgTracks.trackDb[id];
if (rec) {
title = rec.shortLabel;
} else {
title = id;
}
return {id: id, title: "configure " + title};
} else {
return null;
}
},
findMapItem: function (e)
{ // Find mapItem for given event; returns item object or null if none found.
if (rightClick.currentMapItem) {
return rightClick.currentMapItem;
}
// rightClick for non-map items that can be resolved to their parent tr and
// then trackName (e.g. items in gray bar)
var tr = $( e.target ).parents('tr.imgOrd');
if ($(tr).length === 1) {
var a = /tr_(.*)/.exec($(tr).attr('id')); // voodoo
if (a && a[1]) {
var id = a[1];
return rightClick.makeMapItem(id);
}
}
return null;
},
windowOpenFailedMsg: function ()
{
warn("Your web browser prevented us from opening a new window.\n\n" +
"Please change your browser settings to allow pop-up windows from " +
document.domain + ".");
},
handleZoomCodon: function (response, status)
{
var json = JSON.parse(response);
if (json.pos) {
imageV2.navigateInPlace("position="+json.pos);
} else {
alert(json.error);
}
},
handleViewImg: function (response, status)
{ // handles view image response, which must get new image without imageV2 gimmickery
jQuery('body').css('cursor', '');
var str = "]*SRC='([^']+)'";
var reg = new RegExp(str);
var a = reg.exec(response);
if (a && a[1]) {
if ( ! window.open(a[1]) ) {
rightClick.windowOpenFailedMsg();
}
return;
}
warn("Couldn't parse out img src");
},
myPrompt: function (msg, callback)
{ // replacement for prompt; avoids misleading/confusing security warnings which are caused
// by prompt in IE 7+. Callback is called if user presses "OK".
$("body").append("
");
$("#myPrompt").dialog({
modal: true,
closeOnEscape: true,
buttons: { "OK": function() {
var myPromptText = $("#myPromptText").val();
$(this).dialog("close");
callback(myPromptText);
}
}
});
},
hit: function (menuItemClicked, menuObject, cmd, args)
{
setTimeout( function() {
rightClick.hitFinish(menuItemClicked, menuObject, cmd, args);
}, 1);
},
hitFinish: function (menuItemClicked, menuObject, cmd, args)
{ // dispatcher for context menu hits
var id = rightClick.selectedMenuItem.id;
var url = null; // TODO: Break this giant routine with shared vars into some sub-functions
var href = null;
var rec = null;
var row = null;
var rows = null;
var selectUpdated = null;
function mySuccess() {}
if (menuObject.shown) {
// warn("Spinning: menu is still shown");
setTimeout(function() { rightClick.hitFinish(menuItemClicked, menuObject, cmd); }, 10);
return;
}
if (cmd === 'selectWholeGene' || cmd === 'getDna' || cmd === 'highlightItem') {
// bring whole gene into view or redirect to DNA screen.
href = rightClick.selectedMenuItem.href;
var chrom, chromStart, chromEnd;
// Many links leave out the chrom (b/c it's in the server side cart as "c")
// var chrom = hgTracks.chromName; // This is no longer acceptable
// with multi-window capability drawing multiple positions on multiple chroms.
var a = /hgg_chrom=(\w+)&/.exec(href);
if (a) {
if (a && a[1])
chrom = a[1];
a = /hgg_start=(\d+)/.exec(href);
if (a && a[1])
chromStart = parseInt(a[1]) + 1;
a = /hgg_end=(\d+)/.exec(href);
if (a && a[1])
chromEnd = parseInt(a[1]);
} else {
// a = /hgc.*\W+c=(\w+)/.exec(href);
a = /hgc.*\W+c=(\w+)/.exec(href);
if (a && a[1])
chrom = a[1];
a = /o=(\d+)/.exec(href);
if (a && a[1])
chromStart = parseInt(a[1]) + 1;
a = /t=(\d+)/.exec(href);
if (a && a[1])
chromEnd = parseInt(a[1]);
}
if (!chrom || chrom.length === 0 || !chromStart || !chromEnd) {// 1-based chromStart
warn("couldn't parse out genomic coordinates");
} else {
if (cmd === 'getDna') {
// NOTE: this should be shared with URL generation for getDna blue bar menu
url = "../cgi-bin/hgc?g=getDna&i=mixed&c=" + chrom;
url += "&l=" + (chromStart - 1) + "&r=" + chromEnd;
url += "&db=" + getDb() + "&hgsid=" + getHgsid();
if ( ! window.open(url) ) {
rightClick.windowOpenFailedMsg();
}
} else if (cmd === 'highlightItem') {
if (hgTracks.windows && !hgTracks.virtualSingleChrom) {
// orig way only worked if the entire item was visible in the windows.
//var result = genomePos.chromToVirtChrom(chrom, parseInt(chromStart-1), parseInt(chromEnd));
var result = genomePos.convertChromPosToVirtCoords(chrom, parseInt(chromStart-1), parseInt(chromEnd));
if (result.chromStart != -1)
{
var newPos2 = hgTracks.chromName+":"+(result.chromStart+1)+"-"+result.chromEnd;
dragSelect.highlightThisRegion(newPos2, true);
}
} else {
var newChrom = hgTracks.chromName;
if (hgTracks.windows && hgTracks.virtualSingleChrom) {
newChrom = hgTracks.windows[0].chromName;
}
var newPos3 = newChrom+":"+(parseInt(chromStart))+"-"+parseInt(chromEnd);
dragSelect.highlightThisRegion(newPos3, true);
}
} else {
var newPosition = genomePos.setByCoordinates(chrom, chromStart, chromEnd);
var reg = new RegExp("hgg_gene=([^&]+)");
var b = reg.exec(href);
var name;
// pull item name out of the url so we can set hgFind.matches (redmine 3062)
if (b && b[1]) {
name = b[1];
} else {
reg = new RegExp("[&?]i=([^&]+)");
b = reg.exec(href);
if (b && b[1]) {
name = b[1];
}
}
if (imageV2.inPlaceUpdate) {
// XXXX This attempt to "update whole track image in place" didn't work
// for a variety of reasons (e.g. safari doesn't parse map when we
// update on the client side), so this is currently dead code.
// However, this now works in all other browsers, so we may turn this
// on for non-safari browsers (see redmine #4667).
jQuery('body').css('cursor', '');
var data = "hgt.trackImgOnly=1&hgt.ideogramToo=1&position=" +
newPosition + "&hgsid=" + getHgsid();
if (name)
data += "&hgFind.matches=" + name;
$.ajax({
type: "GET",
url: "../cgi-bin/hgTracks",
data: cart.addUpdatesToUrl(data),
dataType: "html",
trueSuccess: imageV2.updateImgAndMap,
success: catchErrorOrDispatch,
error: errorHandler,
cmd: cmd,
loadingId: showLoadingImage("imgTbl"),
cache: false
});
} else {
// do a full page refresh to update hgTracks image
jQuery('body').css('cursor', 'wait');
var ele;
if (document.TrackForm)
ele = document.TrackForm;
else
ele = document.TrackHeaderForm;
if (name)
// Add or update form input with gene to highlight
suggestBox.updateFindMatches(name);
ele.submit();
}
}
}
} else if (cmd === 'zoomCodon' || cmd === 'zoomExon') {
var num, ajaxCmd, msg;
if (cmd === 'zoomCodon') {
msg = "Please enter the codon number to jump to:";
ajaxCmd = 'codonToPos';
} else {
msg = "Please enter the exon number to jump to:";
ajaxCmd = 'exonToPos';
}
rightClick.myPrompt(msg, function(results) {
$.ajax({
type: "GET",
url: "../cgi-bin/hgApi",
data: cart.varsToUrlData({ 'db': getDb(), 'cmd': ajaxCmd, 'num': results,
'table': args.table, 'name': args.name }),
trueSuccess: rightClick.handleZoomCodon,
success: catchErrorOrDispatch,
error: errorHandler,
cache: true
});
});
} else if (cmd === 'hgTrackUi_popup') {
// Launches the popup but shields the ajax with a waitOnFunction
popUp.hgTrackUi( rightClick.selectedMenuItem.id, false );
} else if (cmd === 'hgTrackUi_follow') {
url = "hgTrackUi?hgsid=" + getHgsid() + "&g=";
rec = hgTracks.trackDb[id];
if (tdbHasParent(rec) && tdbIsLeaf(rec))
url += rec.parentTrack;
else {
// The button already has the ref
var link = normed($( 'td#td_btn_'+ rightClick.selectedMenuItem.id ).children('a'));
if (link)
url = $(link).attr('href');
else
url += rightClick.selectedMenuItem.id;
}
location.assign(url);
} else if (cmd === 'newCollection') {
$.ajax({
type: "PUT",
async: false,
url: "../cgi-bin/hgCollection",
data: "cmd=newCollection&track=" + id + "&hgsid=" + getHgsid(),
trueSuccess: mySuccess,
success: catchErrorOrDispatch,
error: errorHandler,
});
imageV2.fullReload();
} else if (cmd === 'addCollection') {
var shortLabel = $(menuItemClicked).text().substring(9).slice(0,-1);
var ii;
var collectionName;
for(ii=0; ii < hgTracks.collections.length; ii++) {
if ( hgTracks.collections[ii].shortLabel === shortLabel) {
collectionName = hgTracks.collections[ii].track;
break;
}
}
$.ajax({
type: "PUT",
async: false,
url: "../cgi-bin/hgCollection",
data: "cmd=addTrack&track=" + id + "&collection=" + collectionName + "&hgsid=" + getHgsid(),
trueSuccess: mySuccess,
success: catchErrorOrDispatch,
error: errorHandler,
});
imageV2.fullReload();
} else if ((cmd === 'sortExp') || (cmd === 'sortSim')) {
url = "hgTracks?hgsid=" + getHgsid() + "&" + cmd + "=";
rec = hgTracks.trackDb[id];
if (tdbHasParent(rec) && tdbIsLeaf(rec))
url += rec.parentTrack;
else {
// The button already has the ref
var link2 = normed($( 'td#td_btn_'+ rightClick.selectedMenuItem.id ).children('a'));
if (link2)
url = $(link2).attr('href');
else
url += rightClick.selectedMenuItem.id;
}
location.assign(url);
} else if (cmd === 'viewImg') {
// Fetch a new copy of track img and show it to the user in another window. This code
// assume we have updated remote cart with all relevant chages (e.g. drag-reorder).
jQuery('body').css('cursor', 'wait');
$.ajax({
type: "GET",
url: "../cgi-bin/hgTracks",
data: cart.varsToUrlData({ 'hgt.imageV1': '1','hgt.trackImgOnly': '1',
'hgsid': getHgsid() }),
dataType: "html",
trueSuccess: rightClick.handleViewImg,
success: catchErrorOrDispatch,
error: errorHandler,
cmd: cmd,
cache: false
});
} else if (cmd === 'openLink' || cmd === 'followLink') {
href = rightClick.selectedMenuItem.href;
var vars = new Array("c", "l", "r", "db");
var valNames = new Array("chromName", "winStart", "winEnd");
for (var i in vars) {
// make sure the link contains chrom and window width info
// (necessary b/c we are stripping hgsid and/or the cart may be empty);
// but don't add chrom to wikiTrack links (see redmine #2476).
var v = vars[i];
var val;
if (v === "db") {
val = getDb();
} else {
val = hgTracks[valNames[i]];
}
if (val
&& id !== "wikiTrack"
&& (href.indexOf("?" + v + "=") === -1)
&& (href.indexOf("&" + v + "=") === -1)) {
href = href + "&" + v + "=" + val;
}
}
if (cmd === 'followLink') {
// XXXX This is blocked by Safari's popup blocker (without any warning message).
location.assign(href);
} else {
// Remove hgsid to force a new session (see redmine ticket 1333).
href = removeHgsid(href);
if ( ! window.open(href) ) {
rightClick.windowOpenFailedMsg();
}
}
} else if (cmd === 'float') {
if (rightClick.floatingMenuItem && rightClick.floatingMenuItem === id) {
$.floatMgr.FOArray = [];
rightClick.floatingMenuItem = null;
} else {
if (rightClick.floatingMenuItem) {
// This doesn't work.
$('#img_data_' + rightClick.floatingMenuItem).parent().restartFloat();
// This does work
$.floatMgr.FOArray = [];
}
rightClick.floatingMenuItem = id;
rightClick.reloadFloatingItem();
imageV2.requestImgUpdate(id, "hgt.transparentImage=0", "");
}
} else if (cmd === 'hideSet') {
row = $( 'tr#tr_' + id );
rows = dragReorder.getContiguousRowSet(row);
if (rows && rows.length > 0) {
var varsToUpdate = {};
// from bottom up, just in case remove screws with us
for (var ix=rows.length - 1; ix >= 0; ix--) {
var rowId = $(rows[ix]).attr('id').substring('tr_'.length);
// Remove subtrack level vis and explicitly uncheck.
varsToUpdate[rowId] = '[]';
varsToUpdate[rowId+'_sel'] = 0;
$(rows[ix]).remove();
}
if (objNotEmpty(varsToUpdate)) {
cart.setVarsObj(varsToUpdate);
}
imageV2.afterImgChange(true);
}
} else if (cmd === 'hideComposite') {
rec = hgTracks.trackDb[id];
if (tdbIsSubtrack(rec)) {
row = $( 'tr#tr_' + id );
rows = dragReorder.getCompositeSet(row);
// from bottom up, just in case remove screws with us
if (rows && rows.length > 0) {
for (var rIx=rows.length - 1; rIx >= 0; rIx--) {
$(rows[rIx]).remove();
}
selectUpdated = vis.update(rec.parentTrack, 'hide');
cart.setVars( [rec.parentTrack], ['hide']);
imageV2.afterImgChange(true);
}
}
} else if (cmd === 'jumpToHighlight') { // If highlight exists for this assembly, jump to it
if (hgTracks.highlight && rightClick.clickedHighlightIdx!==null) {
var newPos = getHighlight(hgTracks.highlight, rightClick.clickedHighlightIdx);
if (newPos && newPos.db === getDb()) {
if ( $('#highlightItem').length === 0) { // not visible? jump to it
var curPos = parsePosition(genomePos.get());
var diff = ((curPos.end - curPos.start) - (newPos.end - newPos.start));
if (diff > 0) { // new position is smaller then current, then center it
newPos.start = Math.max( Math.floor(newPos.start - (diff/2) ), 0 );
newPos.end = newPos.start + (curPos.end - curPos.start);
}
}
if (imageV2.inPlaceUpdate) {
var params = "position=" + newPos.chrom+':'+newPos.start+'-'+newPos.end;
imageV2.navigateInPlace(params, null, true);
} else {
genomePos.setByCoordinates(newPos.chrom, newPos.start, newPos.end);
jQuery('body').css('cursor', 'wait');
document.TrackHeaderForm.submit();
}
}
}
} else if (cmd === 'removeHighlight') {
var highlights = hgTracks.highlight.split("|");
highlights.splice(rightClick.clickedHighlightIdx, 1); // splice = remove element from array
hgTracks.highlight = highlights.join("|");
cart.setVarsObj({'highlight' : hgTracks.highlight});
imageV2.drawHighlights();
} else if (cmd === 'toggleMerge') {
// toggle both the cart (if the user goes to trackUi)
// and toggle args[key], if the user doesn't leave hgTracks
var key = id + ".doMergeItems";
var updateObj = {};
if (args[key] === 1) {
args[key] = 0;
updateObj[key] = 0;
cart.setVarsObj(updateObj,null,false);
imageV2.requestImgUpdate(id, id + ".doMergeItems=0");
} else {
args[key] = 1;
updateObj[key] = 1;
cart.setVarsObj(updateObj,null,false);
imageV2.requestImgUpdate(id, id + ".doMergeItems=1");
}
} else { // if ( cmd in 'hide','dense','squish','pack','full','show' )
// Change visibility settings:
//
// First change the select on our form:
rec = hgTracks.trackDb[id];
selectUpdated = vis.update(id, cmd);
// Now change the track image
if (imageV2.enabled && cmd === 'hide') {
// Hide local display of this track and update server side cart.
// Subtracks controlled by 2 settings so del vis and set sel=0.
if (tdbIsSubtrack(rec)) {
// Remove subtrack level vis and explicitly uncheck.
cart.setVars( [ id, id+"_sel" ], [ '[]', 0 ] );
} else if (tdbIsFolderContent(rec)) {
// supertrack children need to have _sel set to trigger superttrack reshaping
cart.setVars( [ id, id+"_sel" ], [ 'hide', 0 ] );
} else {
cart.setVars([id], ['hide']); // Others, just set vis hide.
}
$(document.getElementById('tr_' + id)).remove();
imageV2.afterImgChange(true);
} else if (!imageV2.mapIsUpdateable) {
jQuery('body').css('cursor', 'wait');
if (selectUpdated) {
// assert(document.TrackForm);
document.TrackForm.submit();
} else {
// Add vis update to queue then submit
cart.setVars([id], [cmd], null, false); // synchronous
document.TrackHeaderForm.submit();
}
} else {
imageV2.requestImgUpdate(id, id + "=" + cmd, "", cmd);
}
}
},
makeHitCallback: function (title)
{ // stub to avoid problem with a function closure w/n a loop
return function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, title); return true;
};
},
reloadFloatingItem: function ()
{ // currently dead (experimental code)
if (rightClick.floatingMenuItem) {
$('#img_data_' + rightClick.floatingMenuItem).parent().makeFloat(
{x:"current",y:"current", speed: 'fast', alwaysVisible: true, alwaysTop: true});
}
},
makeImgTag: function (img)
{ // Return img tag with explicit dimensions for img (dimensions are currently hardwired).
// This fixes the "weird shadow problem when first loading the right-click menu"
// seen in FireFox 3.X, which occurred b/c FF doesn't actually fetch the image until
// the menu is being shown.
return "";
},
load: function (img)
{
rightClick.menu = img.contextMenu(function() {
popUp.cleanup(); // Popup box is not getting closed properly so must do it here
if ( ! rightClick.selectedMenuItem ) // This is literally an edge case so ignore
return;
var o; // TODO: Break this giant routine with shared vars into some sub-functions
var str;
var rec = null;
var menu = [];
var selectedImg = rightClick.makeImgTag("greenChecksm.png");
var blankImg = rightClick.makeImgTag("invisible16.png");
var done = false;
if (rightClick.selectedMenuItem && rightClick.selectedMenuItem.id) {
var href = rightClick.selectedMenuItem.href;
var isHgc, isGene;
if (href) {
isGene = href.match("hgGene");
isHgc = href.match("hgc");
}
var id = rightClick.selectedMenuItem.id;
rec = hgTracks.trackDb[id];
var offerHideSubset = false;
var offerHideComposite = false;
var offerSingles = true;
var row = $( 'tr#tr_' + id );
if (row) {
var btn = $(row).find('p.btnBlue'); // btnBlue means cursor over left button
if (btn.length === 1) {
var compositeSet = dragReorder.getCompositeSet(row);
if (compositeSet && compositeSet.length > 0) { // There is composite set
offerHideComposite = true;
$( compositeSet ).find('p.btn').addClass('blueButtons');// blue persists
var subSet = dragReorder.getContiguousRowSet(row);
if (subSet && subSet.length > 1) {
offerSingles = false;
if (subSet.length < compositeSet.length) {
offerHideSubset = true;
$( subSet ).addClass("greenRows"); // green persists
}
}
}
}
}
// First option is hide sets
if (offerHideComposite) {
if (offerHideSubset) {
o = {};
o[blankImg + " hide track subset (green)"] = {
onclick: rightClick.makeHitCallback('hideSet')};
menu.push(o);
}
o = {};
str = blankImg + " hide track set";
if (offerHideSubset)
str += " (blue)";
o[str] = {onclick: rightClick.makeHitCallback('hideComposite')};
menu.push(o);
}
// Second set of options: visibility for single track
if (offerSingles) {
if (offerHideComposite)
menu.push($.contextMenu.separator);
// XXXX what if select is not available (b/c trackControlsOnMain is off)?
// Move functionality to a hidden variable?
var select = $("select[name=" + escapeJQuerySelectorChars(id) + "]");
if (select.length > 1)
// Not really needed if $('#hgTrackUiDialog').html(""); has worked
select = [ $(select)[0] ];
var cur = $(select).val();
if (cur) {
$(select).children().each(function(index, o) {
var title = $(this).val();
str = blankImg + " " + title;
if (title === cur)
str = selectedImg + " " + title;
o = {};
o[str] = {onclick: function (menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, title);
return true;}};
menu.push(o);
});
done = true;
} else {
if (rec) {
// XXXX check current state from a hidden variable.
var visStrings = new Array("hide","dense","squish","pack","full");
for (var i in visStrings) {
// use maxVisibility and change hgTracks so it can hide subtracks
o = {};
str = blankImg + " " + visStrings[i];
if (rec.canPack
|| (visStrings[i] !== "pack" && visStrings[i] !== "squish")) {
if (rec.localVisibility) {
if (visStrings[i] === rec.localVisibility) {
str = selectedImg + " " + visStrings[i];
}
} else if (visStrings[i] === vis.enumOrder[rec.visibility]) {
str = selectedImg + " " + visStrings[i];
}
o[str] = { onclick:
rightClick.makeHitCallback(visStrings[i])
};
menu.push(o);
}
}
done = true;
}
}
}
if (done) {
o = {};
var any = false;
var title = rightClick.selectedMenuItem.title || "feature";
var exonNum = 0;
var maxLength = 60;
if (title.length > maxLength) {
title = title.substring(0, maxLength) + "...";
}
if ((isGene || isHgc || id === "wikiTrack") && href.indexOf("i=mergedItem") === -1) {
// Add "Open details..." item
var displayItemFunctions = false;
if (rec) {
if (rec.type.indexOf("wig") === 0
|| rec.type.indexOf("bigWig") === 0
|| id === "wikiTrack") {
displayItemFunctions = false;
} else if (rec.type.indexOf("expRatio") === 0) {
displayItemFunctions = title !== "zoomInMore";
} else {
displayItemFunctions = true;
}
// For barChart mouseovers, replace title (which may be a category
// name+value) with item name
if (rec.type.indexOf("barChart") === 0
|| rec.type.indexOf("bigBarChart") === 0) {
a = /i=([^&]+)/.exec(href);
if (a && a[1]) {
title = a[1];
}
}
}
// when "exonNumbers on", the mouse over text is not a good item description for the right-click menu
// "exonNumbers on" is the default for genePred/bigGenePred tracks but can also be actived for bigBed and others
// We don't have the value of "exonNumbers" here, so just use a heuristic to see if it's on
if (title.search(/, strand [+-], (Intron|Exon) /)!==-1) {
re = /(Exon) ([1-9]+) of/;
matches = re.exec(title);
if (matches !== null && matches[2].length > 0)
exonNum = matches[2];
title = title.split(",")[0];
}
else if (isHgc && ( href.indexOf('g=gtexGene')!== -1
|| href.indexOf('g=unip') !== -1
|| href.indexOf('g=knownGene') !== -1 )) {
// For GTEx gene and UniProt mouseovers, replace title (which may be a tissue name) with
// item (gene) name. Also need to unescape the urlencoded characters and the + sign.
a = /i=([^&]+)/.exec(href);
if (a && a[1]) {
title = decodeURIComponent(a[1].replace(/\+/g, " "));
}
}
if (displayItemFunctions) {
o[rightClick.makeImgTag("magnify.png") + " Zoom to " + title] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject,
"selectWholeGene"); return true;
}
};
o[rightClick.makeImgTag("highlight.png") + " Highlight " + title] =
{ onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject,
"highlightItem");
return true;
}
};
if (rightClick.supportZoomCodon && rec.type.indexOf("genePred") !== -1) {
// http://hgwdev-larrym.gi.ucsc.edu/cgi-bin/hgGene?hgg_gene=uc003tqk.2&hgg_prot=P00533&hgg_chrom=chr7&hgg_start=55086724&hgg_end=55275030&hgg_type=knownGene&db=hg19&c=chr7
var name, table;
var reg = new RegExp("hgg_gene=([^&]+)");
var a = reg.exec(href);
if (a && a[1]) {
name = a[1];
reg = new RegExp("hgg_type=([^&]+)");
a = reg.exec(href);
if (a && a[1]) {
table = a[1];
}
} else {
// http://hgwdev-larrym.gi.ucsc.edu/cgi-bin/hgc?o=55086724&t=55275031&g=refGene&i=NM_005228&c=chr7
// http://hgwdev-larrym.gi.ucsc.edu/cgi-bin/hgc?o=55086713&t=55270769&g=wgEncodeGencodeManualV4&i=ENST00000455089&c=chr7
reg = new RegExp("i=([^&]+)");
a = reg.exec(href);
if (a && a[1]) {
name = a[1];
reg = new RegExp("g=([^&]+)");
a = reg.exec(href);
if (a && a[1]) {
table = a[1];
}
}
}
if (name && table) {
o[rightClick.makeImgTag("magnify.png")+" Zoom to codon"] =
{ onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject,
"zoomCodon",
{name: name, table: table});
return true;}
};
if (exonNum > 0) {
o[rightClick.makeImgTag("magnify.png")+" Zoom to exon"] = {
onclick: function(menuItemClicked, menuObject) {
$.ajax({
type: "GET",
url: "../cgi-bin/hgApi",
data: cart.varsToUrlData({ 'db': getDb(),
'cmd': "exonToPos", 'num': exonNum,
'table': table, 'name': name}),
trueSuccess: rightClick.handleZoomCodon,
success: catchErrorOrDispatch,
error: errorHandler,
cache: true
});
return true; }
};
}
}
}
o[rightClick.makeImgTag("dnaIcon.png")+" Get DNA for "+title] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "getDna");
return true; }
};
}
o[rightClick.makeImgTag("bookOut.png")+
" Open details page in new window..."] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "openLink");
return true; }
};
any = true;
}
if (href && href.length > 0 && href.indexOf("i=mergedItem") === -1) {
// Add "Show details..." item
if (title.indexOf("Click to alter ") === 0) {
// suppress the "Click to alter..." items
} else if (rightClick.selectedMenuItem.href.indexOf("cgi-bin/hgTracks")
!== -1) {
// suppress menu items for hgTracks links (e.g. Next/Prev map items).
} else {
var item;
if (title === "zoomInMore")
// avoid showing menu item that says
// "Show details for zoomInMore..." (redmine 2447)
item = rightClick.makeImgTag("book.png") + " Show details...";
else
item = rightClick.makeImgTag("book.png")+" Show details for "+
title + "...";
o[item] = {onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked,menuObject,"followLink");
return true; }
};
any = true;
}
}
if (any) {
menu.push($.contextMenu.separator);
menu.push(o);
}
}
}
if (rightClick.selectedMenuItem && rec) {
// Add cfg options at just shy of end...
o = {};
if (tdbIsLeaf(rec)) {
if (rec.configureBy !== 'none'
&& (!tdbIsCompositeSubtrack(rec) || rec.configureBy !== 'clickThrough')) {
// Note that subtracks never do clickThrough because
// parentTrack cfg is the desired clickThrough
o[rightClick.makeImgTag("wrench.png")+" Configure "+rec.shortLabel] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "hgTrackUi_popup");
return true; }
};
}
if (rec.parentTrack) {
o[rightClick.makeImgTag("folderWrench.png")+" Configure "+
rec.parentLabel + " track set..."] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked,menuObject,"hgTrackUi_follow");
return true; }
};
}
} else {
o[rightClick.makeImgTag("folderWrench.png")+" Configure "+rec.shortLabel +
" track set..."] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "hgTrackUi_follow");
return true; }
};
}
if (jQuery.floatMgr) {
o[(rightClick.selectedMenuItem.id === rightClick.floatingMenuItem ?
selectedImg : blankImg) + " float"] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "float");
return true; }
};
}
// add a toggle to hide/show the merged item(s)
mergeTrack = rightClick.selectedMenuItem.id + ".doMergeItems";
if (rec.hasOwnProperty(mergeTrack)) {
var hasMergedItems = rec[mergeTrack] === 1;
titleStr = rightClick.makeImgTag("wrench.png") + " ";
if (hasMergedItems) {
titleStr += "Show merged items";
} else {
titleStr += "Merge items that span the current region";
}
o[titleStr] = {onclick: function(menuItemClick, menuObject) {
rightClick.hit(menuItemClick, menuObject, "toggleMerge", rec);
return true; }
};
}
menu.push($.contextMenu.separator);
menu.push(o);
}
menu.push($.contextMenu.separator);
if (hgTracks.highlight && rightClick.clickedHighlightIdx!==null) {
var currentlySeen = ($('#highlightItem').length > 0);
o = {};
// Jumps to highlight when not currently seen in image
var text = (currentlySeen ? " Zoom" : " Jump") + " to highlight";
o[rightClick.makeImgTag("highlightZoom.png") + text] = {
onclick: rightClick.makeHitCallback('jumpToHighlight')
};
if ( currentlySeen ) { // Remove only when seen
o[rightClick.makeImgTag("highlightRemove.png") +
" Remove highlight"] = {
onclick: rightClick.makeHitCallback('removeHighlight')
};
}
menu.push(o);
}
if (rec.isCustomComposite)
{
// add delete from composite
}
else if ((!rec.type.startsWith("wigMaf")) &&
(rec.type.startsWith("bigWig") || rec.type.startsWith("multiWig") || rec.type.startsWith("wig") || rec.type.startsWith("bedGraph"))) {
o = {};
o[" Make a New Collection with \"" + rec.shortLabel + "\""] = {
onclick: rightClick.makeHitCallback("newCollection")
};
menu.push(o);
if (hgTracks.collections) {
var ii;
for(ii=0; ii < hgTracks.collections.length; ii++) {
o = {};
o[" Add to \"" + hgTracks.collections[ii].shortLabel + "\""] = {
onclick: rightClick.makeHitCallback("addCollection")
};
menu.push(o);
}
}
menu.push($.contextMenu.separator);
}
// add sort options if this is a custom composite
if (rec.isCustomComposite && tdbHasParent(rec) && tdbIsLeaf(rec)) {
o = {};
o[" Sort by Magnitude "] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "sortExp");
return true; }
};
menu.push(o);
o = {};
o[" Sort by Similarity "] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "sortSim");
return true; }
};
menu.push(o);
menu.push($.contextMenu.separator);
}
// Add view image at end
o = {};
o[rightClick.makeImgTag("eye.png") + " View image"] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "viewImg");
return true; }
};
menu.push(o);
return menu;
},
{
beforeShow: function(e) {
// console.log(mapItems[rightClick.selectedMenuItem]);
rightClick.selectedMenuItem = rightClick.findMapItem(e);
// find the highlight that was clicked
var imageX = (imageV2.imgTbl[0].getBoundingClientRect().left) + imageV2.LEFTADD;
var xDiff = (e.clientX) - imageX;
var clickPos = genomePos.pixelsToBases(img, xDiff, xDiff+1, hgTracks.winStart, hgTracks.winEnd, false);
rightClick.clickedHighlightIdx = dragSelect.findHighlightIdxForPos(clickPos);
// XXXX? posting.blockUseMap = true;
return true;
},
hideTransition:'hide', // hideCallback fails if these are not defined.
hideSpeed:10,
hideCallback: function() {
$('p.btn.blueButtons').removeClass('blueButtons');
$('tr.trDraggable.greenRows').removeClass('greenRows');
}
});
return;
}
};
//////////////////////////////////
//// external tools ////
//////////////////////////////////
function showExtToolDialog() {
/* show the 'send to external tool' dialog */
// information about external tools is stored in the extTools global list
// defined by a