src/hg/js/hgTracks.js 1.39

1.39 2009/09/13 21:05:49 larrym
use 'not-allowed' cursor when user drags out of image (suggested by braney); add lots of imageV2 specific code (currently only active in larrym's tree)
Index: src/hg/js/hgTracks.js
===================================================================
RCS file: /projects/compbio/cvsroot/kent/src/hg/js/hgTracks.js,v
retrieving revision 1.38
retrieving revision 1.39
diff -b -B -U 4 -r1.38 -r1.39
--- src/hg/js/hgTracks.js	27 Aug 2009 00:15:30 -0000	1.38
+++ src/hg/js/hgTracks.js	13 Sep 2009 21:05:49 -0000	1.39
@@ -3,8 +3,9 @@
 
 var debug = false;
 var originalPosition;
 var originalSize;
+var originalCursor;
 var clickClipHeight;
 var startDragZoom = null;
 var newWinWidth;
 var imageV2 = false;
@@ -17,8 +18,9 @@
 var imgAreaSelect;          // jQuery element used for imgAreaSelect
 var originalImgTitle;
 var autoHideSetting = true; // Current state of imgAreaSelect autoHide setting
 var selectedMapItem;        // index of currently choosen map item (via context menu).
+var browser;                // browser ("msie", "safari" etc.)
 
 function commify (str) {
     if(typeof(str) == "number")
 	str = str + "";
@@ -39,8 +41,9 @@
     if(!originalPosition) {
         // remember initial position and size so we can restore it if user cancels
         originalPosition = $('#positionHidden').val();
         originalSize = $('#size').text();
+        originalCursor = jQuery('body').css('cursor');
     }
 }
 
 function selectStart(img, selection)
@@ -89,8 +92,19 @@
         $('#size').text(size);
     }
 }
 
+function checkPosition(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;
+    return (selection.event.pageX >= (imgOfs.left - slop)) && (selection.event.pageX < (imgOfs.left + imgWidth + slop))
+        && (selection.event.pageY >= (imgOfs.top - slop)) && (selection.event.pageY < (imgOfs.top + imgHeight + slop));
+}
+
 function updatePosition(img, selection, singleClick)
 {
     // singleClick is true when the mouse hasn't moved (or has only moved a small amount).
     var insideX = parseInt(document.getElementById("hgt.insideX").value);
@@ -154,29 +168,32 @@
 function selectChange(img, selection)
 {
     initVars();
     updatePosition(img, selection, false);
+    if(checkPosition(img, selection)) {
+        jQuery('body').css('cursor', originalCursor);
+    } else {
+        jQuery('body').css('cursor', 'not-allowed');
+    }
     return true;
 }
 
 function selectEnd(img, selection)
 {
-    var imgWidth = jQuery(img).width();
-    var imgHeight = jQuery(img).height();
-    var imgOfs = jQuery(img).offset();
-    // ignore releases outside of the image rectangle (allowing a 10 pixel slop)
-    var slop = 10;
     var now = new Date();
     var doIt = false;
-    if(autoHideSetting && (selection.event.pageX >= (imgOfs.left - slop)) && (selection.event.pageX < (imgOfs.left + imgWidth + slop))
-       && (selection.event.pageY >= (imgOfs.top - slop)) && (selection.event.pageY < (imgOfs.top + imgHeight + slop))) {
+    if(originalCursor != null)
+        jQuery('body').css('cursor', originalCursor);
+    // ignore releases outside of the image rectangle (allowing a 10 pixel slop)
+    if(autoHideSetting && checkPosition(img, selection)) {
        // ignore single clicks that aren't in the top of the image (this happens b/c the clickClipHeight test in selectStart
        // doesn't occur when the user single clicks).
        doIt = startDragZoom != null || selection.y1 <= clickClipHeight;
     }
     if(doIt) {
         // startDragZoom is null if mouse has never been moved
 	if(updatePosition(img, selection, (selection.x2 == selection.x1) || startDragZoom == null || (now.getTime() - startDragZoom) < 100)) {
+            jQuery('body').css('cursor', 'wait');
 	    document.TrackHeaderForm.submit();
 	}
     } else {
         setPosition(originalPosition, originalSize);
@@ -191,8 +208,13 @@
     return true;
 }
 
 $(window).load(function () {
+    jQuery.each(jQuery.browser, function(i, val) {
+        if(val) {
+            browser = i;
+        }
+        });
     // jQuery load function with stuff to support drag selection in track img
     loadImgAreaSelect(true);
 
     // Don't load contextMenu if jquery.contextmenu.js hasn't been loaded
@@ -209,20 +231,8 @@
         originalImgTitle = trackImg.attr("title");
         if(imageV2) {
             loadContextMenu(trackImgTbl);
             $(".trDraggable,.nodrop").each( function(t) { loadContextMenu($(this)); });
-            $(".trDraggable,.nodrop").each( function(t) {
-                                                $(this).mousemove(
-                                                    function (e) {
-                                                        mapEvent(e);
-                                                    }
-                                                );
-                                                $(this).mousedown(
-                                                    function (e) {
-                                                        mapMouseDown(e);
-                                                    }
-                                                );
-                                            });
         } else {
             loadContextMenu(trackImg);
             trackImg.mousemove(
                 function (e) {
@@ -254,8 +264,10 @@
         } else {
             imageV2   = true;
             trackImgTbl = $('#imgTbl');
             imgHeight = trackImg.height();
+            // XXXX Tim, I think we should get height from trackImgTbl, b/c it automatically adjusts as we add/delete items.
+            imgHeight = trackImgTbl.height();
         }
 
         clickClipHeight = parseInt(rulerEle.value);
         newWinWidth = parseInt(document.getElementById("hgt.newWinWidth").value);
@@ -272,9 +284,9 @@
 {
 // 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.
     var retval = true;
-    var hidden = $("input[name='hgtgroup_"+prefix+"_close']")
+    var hidden = $("input[name='hgtgroup_"+prefix+"_close']");
     var newVal=1; // we're going - => +
     if($(button) != undefined && $(hidden) != undefined && $(hidden).length > 0) {
         var oldSrc = $(button).attr("src");
         if(arguments.length > 2)
@@ -291,8 +303,9 @@
             $("tr[id^='"+prefix+"-']").show();
         }
         $(button).attr("src",newSrc);
         $(hidden).val(newVal);
+        // setCartVar("hgtgroup_" + prefix + "_close", newVal);
         retval = false;
     }
     return retval;
 }
@@ -603,19 +616,22 @@
     var retval = -1;
     for(var i=0;i<mapItems.length;i++)
     {
         if(mapItems[i].obj && e.target === mapItems[i].obj) {
+            // This never occurs under IE
             // console.log("Found match by objects comparison");
             retval = i;
             break;
-        } else {
-            // XXXX !imageV2???
+        } else if (!imageV2 || browser == "msie") {
+            // 
+            // We start falling through to here under safari under imageV2 once something has been modified
             if(mapItems[i].r.contains(x, y)) {
                 retval = i;
                 break;
             }
         }
     }
+    // showWarning(x + " " + y + " " + retval + " " + $(e.target).attr('src'));
     // console.log("findMapItem:", e.clientX, e.clientY, x, y, pos.left, pos.top, retval, mapItems.length, e.target.tagName);
     // console.log(e.clientX, pos);
     return retval;
 }
@@ -652,56 +668,71 @@
 }
 
 function contextMenuHit(menuItemClicked, menuObject, cmd)
 {
-    setTimeout(function() { contextMenuHitFinish(menuItemClicked, menuObject, cmd); }, 10);
+    setTimeout(function() { contextMenuHitFinish(menuItemClicked, menuObject, cmd); }, 1);
 }
 
 function contextMenuHitFinish(menuItemClicked, menuObject, cmd)
 {
 // dispatcher for context menu hits
     if(menuObject.shown) {
-        // XXXX This doesn't work; i.e. I still occassionally get a menu that doesn't get hidden.
-        // console.log("Spinning: menu is still shown");
-        setTimeout(function() { contextMenuHitFinish(menuItemClicked, menuObject, cmd); }, 50);
+        // showWarning("Spinning: menu is still shown");
+        setTimeout(function() { contextMenuHitFinish(menuItemClicked, menuObject, cmd); }, 10);
+        return;
     }
     if(cmd == 'selectWholeGene') {
             // bring whole gene into view
             var href = mapItems[selectedMapItem].href;
-            var chrom, chromStart, chromEnd;
+            var chromStart, chromEnd;
             var a = /hgg_chrom=(\w+)&/.exec(href);
+            // Many links leave out the chrom (b/c it's in the server side cart as "c")
+            var chrom = document.getElementById("hgt.chromName").value;
             if(a) {
+                if(a && a[1])
                 chrom = a[1];
                 a = /hgg_start=(\d+)/.exec(href);
+                if(a && a[1])
                 // XXXX does chromStart have to be incremented by 1?
                 chromStart = a[1];
                 a = /hgg_end=(\d+)/.exec(href);
+                if(a && a[1])
                 chromEnd = 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 == null || chromStart == null || chromEnd == null) {
+                showWarning("couldn't parse out genomic coordinates");
+            } else {
             var newPosition = setPositionByCoordinates(chrom, chromStart, chromEnd);
-            if(imageV2) {
-                // XXXX I don't know how to collapse down to just this portion of the display (is that possible?)
+                if(browser == "safari" || imageV2) {
+                    // See comments below on safari.
+                    // We need to parse out more stuff to support imageV2 via ajax, but it's probably possible.
+                    jQuery('body').css('cursor', 'wait');
                 document.TrackHeaderForm.submit();
             } else {
-                jQuery('body').css('cursor', 'wait');
+                    jQuery('body').css('cursor', '');
                 $.ajax({
                            type: "GET",
                            url: "../cgi-bin/hgTracks",
                            data: "hgt.trackImgOnly=1&hgt.ideogramToo=1&position=" + newPosition + "&hgsid=" + getHgsid(),
                            dataType: "html",
                            trueSuccess: handleUpdateTrackMap,
                            success: catchErrorOrDispatch,
+                               cmd: cmd,
                            cache: false
                        });
                 }
+            }
     } else if (cmd == 'hgTrackUi') {
         // data: ?
         jQuery('body').css('cursor', 'wait');
         $.ajax({
@@ -722,33 +753,50 @@
         obj.setOptions({autoHide : false, movable: true});
     } else if (cmd == 'viewImg') {
         window.open(trackImg.attr('src'));
     } else if (cmd == 'openLink') {
+        // XXXX This is blocked by Safari's popup blocker (without any warning message).
         window.open(mapItems[selectedMapItem].href);
     } else {
-        $("select[name=" + mapItems[selectedMapItem].id + "]").each(function(t) {
+        var id = mapItems[selectedMapItem].id;
+        var rec = trackDbJson[id];
+        if(rec && rec.parentTrack) {
+            // currently we fall back to the parentTrack
+            id = rec.parentTrack;
+        }
+        $("select[name=" + id + "]").each(function(t) {
             $(this).val(cmd);
                 });
         if(imageV2 && cmd == 'hide')
         {
-            $('#tr_' + mapItems[selectedMapItem].id).remove();
-        } else {
-            if(imageV2) {
+            // Tell remote cart what happened (to keep them in sync with us).
+            setCartVar(id, cmd);
+            $('#tr_' + id).remove();
+            loadImgAreaSelect(false);
+        } else if (browser == "safari") {
+            // Safari has the following bug: if we update the local map dynamically, the changes don't get registered (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.
+            jQuery('body').css('cursor', 'wait');
 	        document.TrackForm.submit();
             } else {
+            var data = "hgt.trackImgOnly=1&" + id + "=" + cmd + "&hgsid=" + getHgsid();
+            if(imageV2) {
+	        data += "&hgt.trackNameFilter=" + id;
+            }
                 jQuery('body').css('cursor', 'wait');
                 $.ajax({
                            type: "GET",
                            url: "../cgi-bin/hgTracks",
-                           data: "hgt.trackImgOnly=1&" + mapItems[selectedMapItem].id + "=" + cmd + "&hgsid=" + getHgsid(),
+                       data: data,
                            dataType: "html",
                            trueSuccess: handleUpdateTrackMap,
                            success: catchErrorOrDispatch,
+                       cmd: cmd,
                            cache: false
                        });
             }
         }
-    }
 }
 
 function loadContextMenu(img)
 {
@@ -761,11 +809,18 @@
             {
                 var href = mapItems[selectedMapItem].href;
                 var isGene = href.match("hgGene");
                 var isHgc = href.match("hgc");
+                var rec = trackDbJson[id];
+                var id = mapItems[selectedMapItem].id;
+                var rec = trackDbJson[id];
+                if(rec && rec.parentTrack) {
+                    // currently we fall back to the parentTrack
+                    id = rec.parentTrack;
+                }
                 // XXXX what if select is not available (b/c trackControlsOnMain is off)?
                 // Move functionality to a hidden variable?
-                var select = $("select[name=" + mapItems[selectedMapItem].id + "]");
+                var select = $("select[name=" + id + "]");
                 var cur = select.val();
                 if(cur) {
                     select.children().each(function(index, o) {
                                                var title = $(this).val();
@@ -787,8 +842,28 @@
                         o[mapItems[selectedMapItem].title] = {onclick: function(menuItemClicked, menuObject) { contextMenuHit(menuItemClicked, menuObject, "hgTrackUi"); return true; }};
                     }
                     menu.push(o);
                     done = true;
+                } else {
+                    // XXXX currently dead code
+                    if(rec) {
+                        // XXXX check current state from a hidden variable.
+                        var visibilityStrsOrder = new Array("hide", "dense", "full", "pack", "squish");
+                        var visibilityStrs = new Array("hide", "dense", "squish", "pack", "full");
+                        for (i in visibilityStrs) {
+                            // XXXX use maxVisibility and change hgTracks so it can hide subtracks
+                            var o = new Object();
+                            var str = visibilityStrs[i];
+                            if(rec.canPack || (str != "pack" && str != "squish")) {
+                                if(str == visibilityStrsOrder[rec.visibility]) {
+                                    str += selectedImg;
+                                }
+                                o[str] = {onclick: function(menuItemClicked, menuObject) { contextMenuHit(menuItemClicked, menuObject, visibilityStrs[i]); return true; }};
+                                menu.push(o);
+                            }
+                        }
+                        done = true;
+                    }
                 }
             }
             if(!done) {
                 var str = "drag-and-zoom mode";
@@ -813,8 +888,9 @@
         {
             beforeShow: function(e) {
                 // console.log(mapItems[selectedMapItem]);
                 selectedMapItem = findMapItem(e);
+                // XXXX? blockUseMap = true;
             },
             hideCallback: function() {
                 // this doesn't work
                 alert("hideCallback");
@@ -822,24 +898,26 @@
         });
     return;
 }
 
-function parseMap(map, reset)
+function parseMap(ele, reset)
 {
-// Parse the jQuery map object into returned mapItems array (map needn't be the element attached to current document).
+// Parse the jQuery <map> object into returned mapItems array (ele needn't be the element attached to current document).
         if(reset || !mapItems) {
             mapItems = new Array();
         }
+        if(ele) {
         var i = mapItems.length;
-        map.children().each(function() {
+                ele.children().each(function() {
                                       mapItems[i++] = {
                                           r : new Rectangle(this.coords),
                                           href : this.href,
                                           title : this.title,
                                           id : this.id,
                                           obj : this
                                       };
                                   });
+    }
     return mapItems;
 }
 
 function showWarning(str)
@@ -887,11 +965,13 @@
 
 function handleUpdateTrackMap(response, status)
 {
 // Handle ajax response with an updated trackMap image (gif or png) and map.
+//
+// this.cmd can be used to figure out which menu item triggered this.
+//
 //    var a= /(<IMG SRC\s*=\s*([^"]+)"[^>]+id='trackMap'\s*>/.exec(response);
 // <IMG SRC = "../trash/hgtIdeo/hgtIdeo_hgwdev_larrym_61d1_8b4a80.gif" BORDER=1 WIDTH=1039 HEIGHT=21 USEMAP=#ideoMap id='chrom'>
-
     // Parse out new ideoGram url (if available)
     var a = /<IMG([^>]+SRC[^>]+id='chrom'[^>]*)>/.exec(response);
     if(a && a[1]) {
         b = /SRC\s*=\s*"([^")]+)"/.exec(a[1]);
@@ -898,8 +978,43 @@
         if(b[1]) {
             $('#chrom').attr('src', b[1]);
         }
     }
+    if(imageV2 && this.cmd && this.cmd != 'selectWholeGene') {
+          // Extract <TR id='tr_ID' class='trDraggable'>...</TR> and update appropriate row in imgTbl;
+          // this updates src in img_left_ID, img_center_ID and img_data_ID and map in map_data_ID
+          var id = mapItems[selectedMapItem].id;
+          var rec = trackDbJson[id];
+          if(rec && rec.parentTrack) {
+              // currently we fall back to the parentTrack
+              id = rec.parentTrack;
+          }
+          var str = "<TR id='tr_" + id + "' class='trDraggable'>([\\s\\S]+?)</TR>";
+          var reg = new RegExp(str);
+          a = reg.exec(response);
+          if(a && a[1]) {
+//               $('#tr_' + id).html();
+//               $('#tr_' + id).empty();
+               $('#tr_' + id).html(a[1]);
+               // XXXX move following to a shared method
+               parseMap(null, true);
+               $("map[name!=ideoMap]").each( function(t) { parseMap($(this, false));});
+               loadImgAreaSelect(false);
+               // Do NOT reload context menu (otherwise we get the "context menu sticks" problem).
+               // loadContextMenu($('#tr_' + id));
+          } else {
+               showWarning("Couldn't parse out new image");
+          }
+    } else {
+        if(imageV2) {
+            a= /<TABLE id=\'imgTbl\'[^>]*>([\S\s]+?)<\/TABLE>/.exec(response);
+            if(a[1]) {
+                // This doesn't work (much weirdness ensues).
+                $('#imgTbl').html(a[1]);
+            } else {
+                showWarning("Couldn't parse out new image");
+            }
+        } else {
     a= /<IMG([^>]+SRC[^>]+id='trackMap[^>]*)>/.exec(response);
     // Deal with a is null
     if(a[1]) {
             var b = /WIDTH\s*=\s*['"]?(\d+)['"]?/.exec(a[1]);
@@ -926,18 +1041,22 @@
 
                // After much debugging, I found the best way to have imgAreaSelect continue to work
                // was to reload it:
                loadImgAreaSelect(false);
+                       // XXX this doesn't work (i.e. does't make the re-sized row draggable).
+                       jQuery.tableDnD.updateTables();
            }
     } else {
         showWarning("Couldn't parse out new image");
     }
+        }
     // now pull out and parse the map.
     a = /<MAP id='map' Name=map>([\s\S]+)<\/MAP>/.exec(response);
     if(a[1]) {
         var $map = $('<map>' + a[1] + '</map>');
         parseMap($map, true);
     } else {
         showWarning("Couldn't parse out map");
     }
+    }
     jQuery('body').css('cursor', '');
 }