7340e66cb860fa73cc651af31eb8fda2b668cb0d
max
Wed Apr 5 11:41:31 2017 -0700
Modifying zoom dialog box so one does not have to see it by using
the modifier keys Alt or Ctrl/Cmd. Also allowing user to choose a color for the
highlight and allowing to delete all highlights in the View menu. refs #19118
diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index 5886fd9..f8916e0 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -957,66 +957,96 @@
$(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,
selectStart: function (img, selection)
{
initVars();
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;
},
- highlightThisRegion: function(newPosition, doAdd)
+ 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 highlightRegion()
+ 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;
+ },
+
+ highlightThisRegion: function(newPosition, doAdd, hlColor)
// set highlighting newPosition in server-side cart and apply the highlighting in local UI.
{
- var hlColor = '#1ff3f0';
+ var hlColorName = hlColor; // js convention: do not assign to argument variables
+ if (hlColor==="" || hlColor===null || hlColor===undefined)
+ hlColorName = dragSelect.hlColor;
+
var pos = parsePosition(newPosition);
var start = pos.start;
var end = pos.end;
- var newHighlight = getDb() + "." + pos.chrom + ":" + start + "-" + end + hlColor;
+ var newHighlight = 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) {
@@ -1033,51 +1063,94 @@
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 = getDb() + '.' + nonVirtChrom + ':' + nonVirtStart + '-' + (nonVirtEnd+1) + hlColor;
+ cartSettings.nonVirtHighlight = 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.highlightRegion();
},
selectionEndDialog: function (newPosition)
// Let user choose between zoom-in and highlighting.
{
+ // if the user hit Escape just before, do not show this dialog
+ if (dragSelect.startTime===null)
+ return;
var dragSelectDialog = $("#dragSelectDialog")[0];
if (!dragSelectDialog) {
- $("body").append("
" +
- "
" +
- "Don't show this dialog again and always zoom. " +
- "(Re-enable highlight via the 'configure' menu at any time.)
"+
- "Using the keyboard, you can highlight the current range with 'h then m' (mark) and clear all highlights with 'h then c'. Type '?' to show the other shortcuts.
");
+ $("body").append("
" +
+ "
"+
+ "
Hold Shift+drag to show this dialog or zoom" +
+ "
Hold Alt+drag to add a highlight (no dialog)" +
+ "
Hold Ctrl+drag or Cmd+drag to zoom (no dialog)" +
+ "
To cancel, press Esc anytime or drag mouse outside image" +
+ "
Highlight the current position with h then m" +
+ "
Clear all highlights with View - Clear Highlights or h then c" +
+ "
" +
+ "" +
+ "Don't show this again and always zoom with shift. " +
+ "Re-enable via the 'configure' menu"+
+ "Selected chromosome position: ");
dragSelectDialog = $("#dragSelectDialog")[0];
+ // reset value
+ $('#hlReset').click(function() {
+ $('#hlColorInput').val(dragSelect.hlColorDefault);
+ $("#hlColorPicker").spectrum("set", dragSelect.hlColorDefault);
+ });
+ // 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("virt:")===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];
@@ -1094,162 +1167,201 @@
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: 450,
+ minWidth: 500,
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("virt:")===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;
+ dragSelect.hlColor = $("#hlColorInput").val();
dragSelect.highlightThisRegion(newPosition, false);
$(this).dialog("close");
},
"Add Highlight": function() {
// Highlight selection
if ($("#disableDragHighlight").attr('checked'))
hgTracks.enableHighlightingDialog = false;
+ dragSelect.hlColor = $("#hlColorInput").val();
dragSelect.highlightThisRegion(newPosition, true);
$(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)
+ selectEnd: function (img, selection, event)
{
var now = new Date();
var doIt = false;
+ var rulerClicked = selection.y1 <= hgTracks.rulerClickHeight;
if (dragSelect.originalCursor)
jQuery('body').css('cursor', dragSelect.originalCursor);
// 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 || selection.y1 <= hgTracks.rulerClickHeight;
+ 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);
if (newPosition) {
- if (hgTracks.enableHighlightingDialog)
+ 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("virt:")===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;
dragSelect.areaSelector = jQuery((imageV2.imgTbl).imgAreaSelect({
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.startTime = null;
+ }
+ });
+
+ // hide and redraw all current highlights when the browser window is resized
+ $(window).resize(function() {
+ $(imageV2.imgTbl).imgAreaSelect({hide:true});
+ imageV2.highlightRegion();
+ });
+
}
}
};
/////////////////////////////////////
//// 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)
@@ -1986,31 +2098,31 @@
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)
+ 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;
highlightArea = $('#highlightItem')[0];
atEdge = (!beyondImage && (prevX >= leftLimit || prevX <= rightLimit));
$(document).bind('mousemove',panner);
$(document).bind( 'mouseup', panMouseUp); // Will exec only once
return false;
}
});
}
@@ -2289,30 +2401,31 @@
}
};
///////////////////////////////////////
//// rightClick (aka context menu) ////
///////////////////////////////////////
var rightClick = {
menu: null,
selectedMenuItem: null, // currently choosen context menu item (via context menu).
floatingMenuItem: null,
currentMapItem: null,
supportZoomCodon: false, // turns on experimental feature (currently only in larry's tree).
+ clickedHighlightIdx : null, // the index (0,1,...) of the highlight item that overlaps the last right-click
makeMapItem: function (id)
{ // Create a dummy mapItem on the fly
// (for objects that don't have corresponding entry in the map).
if (id && id.length > 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;
@@ -2451,40 +2564,40 @@
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);
+ dragSelect.highlightThisRegion(newPos2, true, dragSelect.hlColorDefault);
}
} 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);
+ dragSelect.highlightThisRegion(newPos3, true, dragSelect.hlColorDefault);
}
} 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];
}
@@ -2653,55 +2766,60 @@
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) {
- var newPos = parsePositionWithDb(hgTracks.highlight);
+ if (hgTracks.highlight && rightClick.clickedHighlightIdx!==null) {
+ var newPos = parsePositionWithDb(hgTracks.highlight.split("|")[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') {
- hgTracks.highlight = null;
- cart.setVarsObj({ 'highlight': '[]' });
+
+ 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.highlightRegion();
+
} 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)) {
@@ -3034,64 +3152,70 @@
};
}
if (jQuery.floatMgr) {
o[(rightClick.selectedMenuItem.id === rightClick.floatingMenuItem ?
selectedImg : blankImg) + " float"] = {
onclick: function(menuItemClicked, menuObject) {
rightClick.hit(menuItemClicked, menuObject, "float");
return true; }
};
}
menu.push($.contextMenu.separator);
menu.push(o);
}
menu.push($.contextMenu.separator);
- if (hgTracks.highlight) {
+ if (hgTracks.highlight && rightClick.clickedHighlightIdx!==null) {
+
if (hgTracks.highlight.search(getDb() + '.') === 0) {
var currentlySeen = ($('#highlightItem').length > 0);
o = {};
// Jumps to highlight when not currently seen in image
- var text = (currentlySeen ? " Zoom" : " Jump") + " to highlighted region";
+ 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 highlighting"] = {
+ " Remove highlight"] = {
onclick: rightClick.makeHitCallback('removeHighlight')
};
}
menu.push(o);
}
}
// 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);
+ // some right-click functions need to know the clicked chrom position
+ var xDiff = e.clientX - imageV2.imgTbl[0].getBoundingClientRect().left; // current position - position of table
+ var clickPos = genomePos.pixelsToBases(img, xDiff, xDiff+1, hgTracks.winStart, hgTracks.winEnd);
+ 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;
}
};
//////////////////////////////////
@@ -3309,31 +3433,31 @@
$('#hgTracksDialog').dialog('open');
}
};
// A function to show the keyboard help dialog box, bound to ? and called from the menu bar
function showHotkeyHelp() {
$("#hotkeyHelp").dialog({width:'600'});
}
// A function to add an entry for the keyboard help dialog box to the menubar
// and add text that indicates the shortcuts to many static menubar items as suggested by good old IBM CUA/SAA
function addKeyboardHelpEntries() {
var html = '