e1ba0aaa1baec345d56cc8a518b8450c3e5d74c3
tdreszer
  Wed Jan 22 14:31:22 2014 -0800
Checking in new feature 'drag-select highlight', which was originally coded by Larry.  Redmine #709 (been on the shelf for awhile).
diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index b4b2af5..514edb2 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -351,33 +351,48 @@
             else
                 minPixels = num;
         }
         return (   movedX > minPixels || movedX < (minPixels * -1)
                 || movedY > minPixels || movedY < (minPixels * -1));
     }
 }
 
   /////////////////
  //// posting ////
 /////////////////
 var posting = {
 
     blockUseMap: false,
 
-    blockMapClicks:   function ()  {         posting.blockUseMap=true;  },
-    allowMapClicks:   function ()  {         posting.blockUseMap=false; },
-    mapClicksAllowed: function ()  { return (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(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 it preserves the users current relative position in the track image).
@@ -533,121 +548,208 @@
         var form = $('form#TrackForm');
         $(form).submit(function () {
             $('select.normalText,select.hiddenText').attr('disabled',true);
         });
         $(form).attr('method','get');
 
     }
 
 }
   ////////////////////////////////////////////////////////////
  // dragSelect is also known as dragZoom or shift-dragZoom //
 ////////////////////////////////////////////////////////////
 var dragSelect = {
 
     areaSelector:    null, // formerly "imgAreaSelect". jQuery element used for imgAreaSelect
-    autoHideSetting: true, // Current state of imgAreaSelect autoHide setting
     originalCursor:  null,
     startTime:       null,
 
-    rulerModeToggle: function (ele) // UNUSED?
-    {
-        dragSelect.autoHideSetting = !ele.checked;
-        var obj = dragSelect.areaSelector.data('imgAreaSelect');
-        obj.setOptions({autoHide : dragSelect.autoHideSetting});
-    },
-
     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)
+    // set highlighting newPosition in server-side cart and apply the highlighting in local UI.
+    {
+        var start, end;
+        if (arguments.length == 2) {
+            start = arguments[0];
+            end = arguments[1];
+        } else {
+            var pos = parsePosition(newPosition);
+            start = pos.start;
+            end = pos.end;
+        }
+        hgTracks.highlight  = getDb() + "." + hgTracks.chromName + ":" + start + "-" + end;
+        hgTracks.highlight += '#AAFFFF'; // Also include highlight color
+        // we include enableHighlightingDialog because it may have been changed by the dialog
+        setCartVars(['highlight', 'enableHighlightingDialog'], 
+                    [hgTracks.highlight, hgTracks.enableHighlightingDialog ? 1 : 0]);
+        imageV2.highlightRegion();
+    },
+
+    selectionEndDialog: function (newPosition)
+    // Let user choose between zoom-in and highlighting.
+    {   
+        var dragSelectDialog = $("#dragSelectDialog")[0];
+        if (!dragSelectDialog) {
+            $("body").append("<div id='dragSelectDialog'>" + newPosition + 
+                             "<p><input type='checkbox' id='disableDragHighlight'>" + 
+                             "Don't show this dialog again and always zoom.<BR>" + 
+                             "(Re-enable highlight via the 'configure' menu at any time.)</p>");
+            dragSelectDialog = $("#dragSelectDialog")[0];
+        }
+        $(dragSelectDialog).dialog({
+                modal: true,
+                title: "Drag-and-select",
+                closeOnEscape: true,
+                resizable: false,
+                autoOpen: false,
+                revertToOriginalPos: true,
+                minWidth: 400,
+                buttons: {  
+                    "Zoom In": function() {
+                        // Zoom to selection
+                        $(this).dialog("option", "revertToOriginalPos", false);
+                        if (imageV2.inPlaceUpdate) {
+                            var params = "position=" + newPosition;
+                            if ($("#disableDragHighlight").attr('checked')) {
+                                hgTracks.enableHighlightingDialog = false;
+                                params += "&enableHighlightingDialog=0"
+                            }
+                            imageV2.navigateInPlace(params, null, true);
+                        } else {
+                            $('body').css('cursor', 'wait');
+                            document.TrackHeaderForm.submit();
+                        }
+                        $(this).dialog("close");
+                    },
+                    "Highlight": function() {
+                        // Highlight selection
+                        $(imageV2.imgTbl).imgAreaSelect({hide:true});
+                        if ($("#disableDragHighlight").attr('checked'))
+                            hgTracks.enableHighlightingDialog = false;
+                        dragSelect.highlightThisRegion(newPosition);
+                        $(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
+                }
+        });
+        $(dragSelectDialog).dialog('open');
+    },
+
     selectEnd: function (img, selection)
     {
         var now = new Date();
         var doIt = false;
         if(dragSelect.originalCursor != null)
             jQuery('body').css('cursor', dragSelect.originalCursor);
         // ignore releases outside of the image rectangle (allowing a 10 pixel slop)
-        if(dragSelect.autoHideSetting && 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
+        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;
         }
         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 != undefined) {
+                if (hgTracks.enableHighlightingDialog)
+                    dragSelect.selectionEndDialog(newPosition);
+                else {
+                    $(imageV2.imgTbl).imgAreaSelect({hide:true});
                     if (imageV2.inPlaceUpdate) {
                         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;
-        setTimeout('posting.allowMapClicks();',50); // Necessary incase the dragSelect.selectEnd was over a map item. select takes precedence.
+        // 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.height();
+            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:        dragSelect.autoHideSetting,
+                autoHide:        false, // gets hidden after possible dialog
                 movable:         false,
                 clickClipHeight: heights
             }));
         }
     }
 }
 
   /////////////////////////////////////
  //// 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
@@ -1308,51 +1410,53 @@
 }
 
 
   //////////////////////////
  //// Drag Scroll code ////
 //////////////////////////
 jQuery.fn.panImages = function(){
     // globals across all panImages
     genomePos.original = genomePos.getOriginalPos();              // XXXX what is this for? (this already happened in initVars).
     var leftLimit   = hgTracks.imgBoxLeftLabel * -1;
     var rightLimit  = (hgTracks.imgBoxPortalWidth - hgTracks.imgBoxWidth + leftLimit);
     var only1xScrolling = ((hgTracks.imgBoxWidth - hgTracks.imgBoxPortalWidth) == 0);//< hgTracks.imgBoxLeftLabel);
     var prevX       = (hgTracks.imgBoxPortalOffsetX + hgTracks.imgBoxLeftLabel) * -1;
     var portalWidth = 0;
     var savedPosition;
+    var highlightArea  = 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 == undefined || pic == undefined) {
         throw "Not a div with a 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 ( $.browser.msie )     // IE will override map item cursors if this gets set
@@ -1360,30 +1464,31 @@
             //else // NOTE: Open hand cursor is being removed because it makes vis toggling less obvious
             //    $(this).css('cursor',"url(../images/grabber.cur),w-resize");  // dragScroll
         });
 
         panAdjustHeight(prevX);
 
         pan.mousedown(function(e){
              if (e.which > 1 || e.button > 1 || e.shiftKey)
                  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;
             }
         });
     }
 
     function panner(e) {
         //if (!e) e = window.event;
         if ( mouseIsDown ) {
             var relativeX = (e.clientX - mouseDownX);
 
             if(relativeX != 0) {
                 if (posting.mapClicksAllowed()) {
@@ -1409,58 +1514,61 @@
                     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
 
                 newX = panUpdatePosition(newX,true);
                 var nowPos = newX.toString() + "px";
                 $(".panImg").css( {'left': nowPos });
                 $('.tdData').css( {'backgroundPosition': nowPos } );
+                scrollHighlight(relativeX);
                 if (!only1xScrolling)
                     panAdjustHeight(newX);  // NOTE: This will dynamically resize image while scrolling.  Do we want to?
             }
         }
     }
     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;
             setTimeout('posting.allowMapClicks();',50); // Necessary incase the dragSelect.selectEnd was over a map item. select takes precedence.
 
             // 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 != undefined)
                     genomePos.set(savedPosition,null);
                 var oldPos = prevX.toString() + "px";
                 $(".panImg").css( {'left': oldPos });
                 $('.tdData').css( {'backgroundPosition': oldPos } );
+                if (highlightArea)
+                    imageV2.highlightRegion();
                 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.
             }
@@ -1618,30 +1726,48 @@
         var imgTbl = $('#imgTbl');
         // Find or create the waitMask (which masks the whole page)
         var dragMask = $('div#dragMask');
         if (dragMask != undefined && dragMask.length >= 1) {
             $(dragMask).height( $(imgTbl).height() );
         }
     }
 
     function dragMaskClear() {        // Clears the dragMask
         $('body').css('cursor','auto')
         var  dragMask = $('#dragMask');
         if (dragMask != undefined )
             $(dragMask).hide();
     }
 
+    function scrollHighlight(relativeX) 
+    // Scrolls the highlight region if one exists
+    {        
+        if (highlightArea) {
+            // Best to have a left and right, then min/max the edges, then set width
+            var hiOffset = $(highlightArea).offset();
+            var hiDefinedLeft  = $(highlightArea).data('leftPixels');
+            var hiDefinedWidth = $(highlightArea).data('widthPixels');
+            hiOffset.left = Math.max(hiDefinedLeft + relativeX,
+                                     portalAbsoluteX);
+            var right     = Math.min(hiDefinedLeft + hiDefinedWidth + relativeX,
+                                     portalAbsoluteX + portalWidth);
+            var newWidth = Math.max(right - hiOffset.left,0);
+            if (hiDefinedWidth != newWidth)
+                $(highlightArea).width(newWidth);
+            $(highlightArea).offset(hiOffset);
+        }
+    }
 
 
 };
 
   ///////////////////////////////////////
  //// rightClick (aka context menu) ////
 ///////////////////////////////////////
 var rightClick = {
 
     menu: null,
     selectedMenuItem: null,   // currently choosen context menu item (via context menu).
     floatingMenuItem: null,
     currentMapItem:   null,
     supportZoomCodon: false,  // turn on experimental zoom-to-codon functionality (currently only on in larry's tree).
 
@@ -1693,31 +1819,31 @@
             genomePos.set(json.pos, 3);
             if(document.TrackForm)
                 document.TrackForm.submit();
             else
                 document.TrackHeaderForm.submit();
         } 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 = "<IMG[^>]*SRC='([^']+)'";
         var reg = new RegExp(str);
-        a = reg.exec(response);
+        var a = reg.exec(response);
         if(a && a[1]) {
             if(window.open(a[1]) == null) {
                 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("<div id = 'myPrompt'><div id='dialog' title='Basic dialog'><form>" +
                             msg + "<input id='myPromptText' value=''></form>");
         $("#myPrompt").dialog({
@@ -1736,31 +1862,31 @@
     {
         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;
         if(menuObject.shown) {
             // warn("Spinning: menu is still shown");
             setTimeout(function() { rightClick.hitFinish(menuItemClicked, menuObject, cmd); }, 10);
             return;
         }
-        if(cmd == 'selectWholeGene' || cmd == 'getDna') {
+        if (cmd == 'selectWholeGene' || cmd == 'getDna' || cmd == 'highlightItem') {
                 // bring whole gene into view or redirect to DNA screen.
                 var href = rightClick.selectedMenuItem.href;
                 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 = hgTracks.chromName;
                 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]);
@@ -1775,30 +1901,32 @@
                     a = /t=(\d+)/.exec(href);
                     if(a && a[1])
                         chromEnd = parseInt(a[1]);
                 }
                 if(chrom == null || chromStart == null || chromEnd == null) {
                     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) === null) {
                             rightClick.windowOpenFailedMsg();
                         }
+                    } else if (cmd == 'highlightItem') {
+                        dragSelect.highlightThisRegion(parseInt(chromStart), parseInt(chromEnd));
                     } else {
                         var newPosition = genomePos.setByCoordinates(chrom, chromStart, chromEnd);
                         var reg = new RegExp("hgg_gene=([^&]+)");
                         var a = reg.exec(href);
                         var name;
                         // pull item name out of the url so we can set hgFind.matches (redmine 3062)
                         if(a && a[1]) {
                             name = a[1];
                         } else {
                             reg = new RegExp("[&?]i=([^&]+)");
                             a = reg.exec(href);
                             if(a && a[1]) {
                                 name = a[1];
                             }
                         }
@@ -1867,38 +1995,30 @@
         } else if (cmd == 'hgTrackUi_follow') {
 
             var url = "hgTrackUi?hgsid=" + getHgsid() + "&g=";
             var rec = hgTracks.trackDb[id];
             if (tdbHasParent(rec) && tdbIsLeaf(rec))
                 url += rec.parentTrack
             else {
                 var link = $( 'td#td_btn_'+ rightClick.selectedMenuItem.id ).children('a'); // The button already has the ref
                 if ($(link) != undefined)
                     url = $(link).attr('href');
                 else
                     url += rightClick.selectedMenuItem.id;
             }
             location.assign(url);
 
-        } else if (cmd == 'dragZoomMode') {
-            dragSelect.autoHideSetting = true;
-            var obj = dragSelect.areaSelector.data('imgAreaSelect');
-            obj.setOptions({autoHide : true, movable: false});
-        } else if (cmd == 'hilightMode') {
-            dragSelect.autoHideSetting = false;
-            var obj = dragSelect.areaSelector.data('imgAreaSelect');
-            obj.setOptions({autoHide : false, movable: true});
         } 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).
 /* Here's how to do this more directly with hgRenderTracks:
             if(window.open("../cgi-bin/hgRenderTracks?hgt.internal=1&hgsid=" + getHgsid()) == null) {
                 rightClick.windowOpenFailedMsg();
             }
             return;
 */
             var data = "hgt.imageV1=1&hgt.trackImgOnly=1&hgsid=" + getHgsid();
             jQuery('body').css('cursor', 'wait');
             $.ajax({
                     type: "GET",
                     url: "../cgi-bin/hgTracks",
                     data: data,
@@ -1960,75 +2080,87 @@
             var rows = dragReorder.getContiguousRowSet(row);
             if (rows && rows.length > 0) {
                 var vars = new Array();
                 var vals = new Array();
                 for (var ix=rows.length - 1; ix >= 0; ix--) { // from bottom, just in case remove screws with us
                     var rowId = $(rows[ix]).attr('id').substring('tr_'.length);
                     //if (tdbIsSubtrack(hgTracks.trackDb[rowId]) == false)
                     //    warn('What went wrong?');
 
                     vars.push(rowId, rowId+'_sel'); // Remove subtrack level vis and explicitly uncheck.
                     vals.push('[]', 0);
                     $(rows[ix]).remove();
                 }
                 if (vars.length > 0) {
                     setCartVars( vars, vals );
-                    dragReorder.init();
-                    dragSelect.load(false);
                 }
-                imageV2.markAsDirtyPage();
+                imageV2.afterImgChange(true);
             }
         } else if (cmd == 'hideComposite') {
             var rec = hgTracks.trackDb[id];
             if (tdbIsSubtrack(rec)) {
                 var row = $( 'tr#tr_' + id );
                 var rows = dragReorder.getCompositeSet(row);
                 if (rows && rows.length > 0) {
                     for (var ix=rows.length - 1; ix >= 0; ix--) { // from bottom, just in case remove screws with us
                         $(rows[ix]).remove();
                     }
                 var selectUpdated = vis.update(rec.parentTrack, 'hide');
                 setCartVar(rec.parentTrack, 'hide' );
-                dragReorder.init();
-                dragSelect.load(false);
-                imageV2.markAsDirtyPage();
+                imageV2.afterImgChange(true);
                 }
             }
             //else
             //    warn('What went wrong?');
+        } else if (cmd == 'zoomToHighlight') { // If highlight exists for this assembly, zoom to it
+            if (hgTracks.highlight) {
+                var pos = parsePositionWithDb(hgTracks.highlight);
+                if (pos && pos.db == getDb()) {
+                    if (imageV2.inPlaceUpdate) {
+                        var params = "position=" + pos.chrom + ':' + pos.start + '-' + pos.end;
+                        imageV2.navigateInPlace(params, null, true);
+                    } else {
+                        genomePos.setByCoordinates(pos.chrom, pos.start, pos.end);
+                        jQuery('body').css('cursor', 'wait');
+                        document.TrackHeaderForm.submit();
+                    }
+                }
+            }
+        } else if (cmd == 'removeHighlight') {
+            hgTracks.highlight = null;
+            setCartVars(['highlight'], ['[]']);
+            imageV2.highlightRegion();
         } else {   // if ( cmd in 'hide','dense','squish','pack','full','show' )
             // Change visibility settings:
             //
             // First change the select on our form:
             var rec = hgTracks.trackDb[id];
             var 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.  Others, just set vis hide.
                 if(tdbIsSubtrack(rec))
                     setCartVars( [ id, id+"_sel" ], [ '[]', 0 ] ); // Remove subtrack level vis and explicitly uncheck.
                 else if(tdbIsFolderContent(rec))
                     setCartVars( [ id, id+"_sel" ], [ 'hide', 0 ] ); // supertrack children need to have _sel set to trigger superttrack reshaping
                 else
                     setCartVar(id, 'hide' );
                 $(document.getElementById('tr_' + id)).remove();
-                dragReorder.init();
-                dragSelect.load(false);
-                imageV2.markAsDirtyPage();
+                imageV2.afterImgChange(true);
             } else if (!imageV2.mapIsUpdateable) {
                 jQuery('body').css('cursor', 'wait');
                 if(selectUpdated) {
                     // assert(document.TrackForm);
                     document.TrackForm.submit();
                 } else {
                         // add a hidden with new visibility value
                         var form = $(document.TrackHeaderForm);
                         $("<input type='hidden' name='"+id+"'value='"+cmd+"'>").appendTo(form);
                         document.TrackHeaderForm.submit();
                 }
             } else {
                 imageV2.requestImgUpdate(id, id + "=" + cmd, "", cmd);
             }
         }
@@ -2189,30 +2321,37 @@
                                 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;
                                 }
                             }
                             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.cse.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.cse.ucsc.edu/cgi-bin/hgc?o=55086724&t=55275031&g=refGene&i=NM_005228&c=chr7
                                         // http://hgwdev-larrym.cse.ucsc.edu/cgi-bin/hgc?o=55086713&t=55270769&g=wgEncodeGencodeManualV4&i=ENST00000455089&c=chr7
@@ -2273,59 +2412,30 @@
                                     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 (!done) {
-                    if(false) {
-                        // Currently toggling b/n drag-and-zoom mode and hilite mode is disabled b/c we don't know how to keep hilite mode from disabling the
-                        // context menus.
-                        var o = new Object();
-                        var str = "drag-and-zoom mode";
-                        if(dragSelect.autoHideSetting) {
-                            str += selectedImg;
-                            // menu[str].className = 'context-menu-checked-item';
-                        }
-                        o[str] = { onclick: function(menuItemClicked, menuObject) {
-                                     rightClick.hit(menuItemClicked, menuObject, "dragZoomMode");
-                                     return true; }
-                                 };
-                        menu.push(o);
-                        o = new Object();
-                        // console.dir(ele);
-                        str = "hilight mode";
-                        if (!dragSelect.autoHideSetting) {
-                            str += selectedImg;
-                        }
-                        o[str] = { onclick: function(menuItemClicked, menuObject) {
-                                      rightClick.hit(menuItemClicked, menuObject, "hilightMode");
-                                      return true; }
-                                 };
-                        menu.push(o);
-                    }
-                    //menu.push({"view image": {onclick: function(menuItemClicked, menuObject) { rightClick.hit(menuItemClicked, menuObject, "viewImg"); return true; }}});
-                }
 
                 if(rightClick.selectedMenuItem && rec) {
                     // Add cfg options at just shy of end...
                     var o = new Object();
                     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; }
                             };
@@ -2347,38 +2457,58 @@
                                 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; }
                         };
                     }
                     menu.push($.contextMenu.separator);
                     menu.push(o);
                 }
 
+                menu.push($.contextMenu.separator);
+                if(hgTracks.highlight) {
+                    var o;
+                    if (hgTracks.highlight.search(getDb() + '.') == 0) {
+                        o = new Object();
+                        o[rightClick.makeImgTag("highlightZoom.png") + 
+                                                                  " Zoom to highlighted region"] = 
+                            {   onclick:function(menuItemClicked, menuObject) {
+                                    rightClick.hit(menuItemClicked, menuObject, "zoomToHighlight");
+                                    return true; 
+                                }
+                            };
+                        o[rightClick.makeImgTag("highlightRemove.png") + " Remove highlighting"] = 
+                            {   onclick:function(menuItemClicked, menuObject) {
+                                    rightClick.hit(menuItemClicked, menuObject, "removeHighlight");
+                                    return true; 
+                                }
+                            };
+                        menu.push(o);
+                    }
+                }
                 // Add view image at end
                 var o = new Object();
                 o[rightClick.makeImgTag("eye.png") + " View image"] = {
                     onclick: function(menuItemClicked, menuObject) {
                         rightClick.hit(menuItemClicked, menuObject, "viewImg");
                         return true; }
                 };
-                menu.push($.contextMenu.separator);
                 menu.push(o);
 
                 return menu;
             },
             {
                 beforeShow: function(e) {
                     // console.log(mapItems[rightClick.selectedMenuItem]);
                     rightClick.selectedMenuItem = rightClick.findMapItem(e);
                     // XXXX? posting.blockUseMap = true;
                     return true;
                 },
                 hideTransition:'hide', // hideCallback fails if these are not defined.
                 hideSpeed:10,
                 hideCallback: function() {
                     $('p.btn.blueButtons').removeClass('blueButtons');
@@ -2451,32 +2581,31 @@
         var rec = hgTracks.trackDb[trackName];
         var subtrack = tdbIsSubtrack(rec) ? trackName :undefined;  // If subtrack then vis rules differ
         var allVars = getAllVars($('#hgTrackUiDialog'), subtrack );// For unknown reasons IE8 fails to find $('#pop'), occasionally
         var changedVars = varHashChanges(allVars,popUp.saveAllVars);
         //warn("cfgVars:"+varHashToQueryString(changedVars));
         var newVis = changedVars[trackName];
         var hide = (newVis != null && (newVis == 'hide' || newVis == '[]'));  // subtracks do not have "hide", thus '[]'
         if($('#imgTbl') == undefined) { // On findTracks or config page
             setVarsFromHash(changedVars);
             //if(hide) // TODO: When findTracks or config page has cfg popup, then vis change needs to be handled in page here
         }
         else {  // On image page
             if(hide) {
                 setVarsFromHash(changedVars);
                 $(document.getElementById('tr_' + trackName)).remove();
-                dragReorder.init();
-                dragSelect.load(false);
+                imageV2.afterImgChange(true);
             } else {
                 // Keep local state in sync if user changed visibility
                 if(newVis != null) {
                     vis.update(trackName, newVis);
                 }
                 var urlData = varHashToQueryString(changedVars);
                 if(urlData.length > 0) {
                     if(imageV2.mapIsUpdateable) {
                         imageV2.requestImgUpdate(trackName,urlData,"");
                     } else {
                         window.location = "../cgi-bin/hgTracks?" + urlData +
                                           "&hgsid=" + getHgsid();
                     }
                 }
             }
@@ -2646,53 +2775,64 @@
                             },
                             function (position) {
                                 genomePos.set(position, commify(getSizeFromCoordinates(position)));
                             });
             // Make sure suggestTrack is visible when user chooses something via gene select (#3484).
             if($("#suggestTrack").length) {
                 $(document.TrackForm || document.TrackHeaderForm).submit(function(event) {
                                                        if($('#hgFindMatches').length) {
                                                            vis.makeTrackVisible($("#suggestTrack").val());
                                                        }
                                                    });
             }
         }
     },
     
+    afterImgChange: function (dirty)
+    {   // Standard things to do when manipulations change image without ajax update.
+        dragReorder.init();
+        dragSelect.load(false);
+        imageV2.highlightRegion();
+        if (dirty)
+            imageV2.markAsDirtyPage();
+    },
+    
     afterReload: function (id)
     {   // Reload various UI widgets after updating imgTbl map.
         dragReorder.init();
         dragSelect.load(false);
         // Do NOT reload context menu (otherwise we get the "context menu sticks" problem).
         // rightClick.load($('#tr_' + id));
         if(imageV2.imgTbl.tableDnDUpdate)
             imageV2.imgTbl.tableDnDUpdate();
         rightClick.reloadFloatingItem();
         // Turn on drag scrolling.
         if(hgTracks.imgBoxPortal) {
             $("div.scroller").panImages();
         }
         if (imageV2.backSupport) {
             if (id) { // The remainder is only needed for full reload
                 imageV2.markAsDirtyPage(); // vis of cfg change
+                imageV2.highlightRegion();
                 return;
             }
         }
         
         imageV2.loadRemoteTracks();
         makeItemsByDrag.load();
         imageV2.loadSuggestBox();
+        imageV2.highlightRegion();
 
         if (imageV2.backSupport) {
             imageV2.setInHistory(false);    // Set this new position into History stack
         } else {
             imageV2.markAsDirtyPage();
         }
     },
 
     updateImgForId: function (html, id, fullImageReload, newJsonRec)
     {   // update row in imgTbl for given id.
         // return true if we successfully pull slice for id and update it in imgTrack.
         var str = "<TR id='tr_" + id + "'[^>]*>([\\s\\S]+?)</TR>";
         var reg = new RegExp(str);
         var a = reg.exec(html);
         if(a && a[1]) {
@@ -3067,30 +3207,80 @@
                 url: "../cgi-bin/hgTracks",
                 data: params + "&hgt.trackImgOnly=1&hgt.ideogramToo=1&hgsid=" + getHgsid(),
                 dataType: "html",
                 trueSuccess: imageV2.updateImgAndMap,
                 success: catchErrorOrDispatch,
                 error: errorHandler,
                 cmd: 'wholeImage',
                 loadingId: showLoadingImage("imgTbl"),
                 disabledEle: disabledEle,
                 currentId: currentId,
                 currentIdYOffset: currentIdYOffset,
                 cache: false
             });
     },
     
+    highlightRegion: function()
+    // highlight vertical region in imgTbl based on hgTracks.highlight (#709).
+    {
+        var pos;
+        var hexColor = '#FFAAAA'
+        $('#highlightItem').remove();
+        if(hgTracks.highlight) {
+            pos = parsePositionWithDb(hgTracks.highlight);
+            if(pos) {
+                pos.start--;  // make start 0-based to match hgTracks.winStart
+                if (pos.color)
+                    hexColor = pos.color;
+            }
+        }
+        if(pos != null && pos.chrom == hgTracks.chromName && pos.db == getDb() 
+        && pos.start <= hgTracks.imgBoxPortalEnd && pos.end >= hgTracks.imgBoxPortalStart) {
+            var portalWidthBases = hgTracks.imgBoxPortalEnd - hgTracks.imgBoxPortalStart;
+            var portal = $('#imgTbl td.tdData')[0];
+            var leftPixels = $(portal).offset().left + 3; // 3 for borders and cgi item calcs ??
+            var pixelsPerBase = ($(portal).width() - 2) / portalWidthBases;
+            var clippedStartBases = Math.max(pos.start, hgTracks.imgBoxPortalStart);
+            var clippedEndBases = Math.min(pos.end, hgTracks.imgBoxPortalEnd);
+            var widthPixels = (clippedEndBases - clippedStartBases) * pixelsPerBase;
+            if(hgTracks.revCmplDisp)
+                leftPixels += (hgTracks.imgBoxPortalEnd - clippedEndBases) * pixelsPerBase - 1;
+            else
+                leftPixels += (clippedStartBases - hgTracks.imgBoxPortalStart) * pixelsPerBase;
+            // Impossible to get perfect... Okay to overrun by a pixel on each side
+            leftPixels  = Math.floor(leftPixels);
+            widthPixels = Math.ceil(widthPixels);
+            if (widthPixels < 2) {
+                widthPixels = 3;
+                leftPixels -= 1;
+            }
+
+            var area = jQuery("<div id='highlightItem' class='highlightItem'></div>");
+            $(area).css({ backgroundColor: hexColor, // display: 'none'
+                        left: leftPixels + 'px', top: $('#imgTbl').offset().top + 1 + 'px',
+                        width: widthPixels + 'px',
+                        height: $('#imgTbl').css('height') });
+            $(area).data({leftPixels: leftPixels, widthPixels: widthPixels});// needed for dragScroll
+
+            // Larry originally appended to imgTbl, but discovered that doesn't work on IE 8 and 9.
+            $('body').append($(area)); 
+            // z-index is done in css class, so highlight is beneath transparent data images.
+            // NOTE: ideally highlight would be below transparent blue-lines, but THAT is a  
+            // background-image so z-index can't get below it!  PS/PDF looks better for blue-lines!
+        }
+    },
+
     backSupport: (window.History.enabled != undefined), // support of r back button via: 
     history: null,                                     //  jquery.history.js and HTML5 history API
     
     setupHistory: function ()
     {   // Support for back-button using jquery.history.js.
         // Sets up the history and initializes a state.
     
         // Since ajax updates leave the browser cached pages different from the server state, 
         // simple back-button fails.  Using a 'dirty flag' we had forced an update from server,
         // whenever the back button was hit, meaning there was no going back from server-state!
         // NOW using the hitsory API, the back-button triggers a 'statechange' event which can 
         // contain data.  We save the position in the data and ajax update the image when the
         // back-button is pressed.  This works great for going back through ajax-updated position
         // changes, but is a bit messier when going back past a full-page retrieved state (as
         // described below).
@@ -3330,30 +3520,31 @@
             // Turn on drag scrolling.
             $("div.scroller").panImages();
         }
         //$("#zoomSlider").slider({ min: -4, max: 3, step: 1 });//, handle: '.ui-slider-handle' });
 
         // Retrieve tracks via AJAX that may take too long to draw initialliy (i.e. a remote bigWig)
         var retrievables = $('#imgTbl').find("tr.mustRetrieve")
         if($(retrievables).length > 0) {
             $(retrievables).each( function (i) {
                 var trackName = $(this).attr('id').substring(3);
                 imageV2.requestImgUpdate(trackName,"","");
             });
         }
         imageV2.loadRemoteTracks();
         makeItemsByDrag.load();
+        imageV2.highlightRegion();
     }
 
     // Drag select in chromIdeogram
     if($('img#chrom').length == 1) {
         if($('area.cytoBand').length >= 1) {
             $('img#chrom').chromDrag();
         }
     }
 
     // Track search uses tabs
     trackSearch.init();
 
     // Drag select initialize
     if (imageV2.enabled) {   // moved from window.load().
         dragSelect.load(true);