// ==UserScript==
// @name          Focus Follows Mouse
// @description   Move the mouse cursor over any link, textarea, select or input element to set focus on that element.
// @include       *
// @exclude       
// ==/UserScript==

// by Philip Dorrell
// $Revision: 1.17 $
// Official download URL: http://www.1729.com/greasemonkey/focusFollowsMouse.user.js
// version 0.3
// Licensed under General Public License 2.0
// http://www.gnu.org/copyleft/gpl.html

var disableIfLoadedExplicitly = true;

function iAmInGreaseMonkey() {
  try {
    return !(window instanceof Window);
  }
  catch(err) {
    return false;
  }
}

function hasFocusFollowsMouseScriptElement() {
  var scripts = document.getElementsByTagName ("script");
  for (var i=0; i<scripts.length; i++) {
    var script = scripts[i];
    if (script.src && script.src.match (/[/]focusFollowsMouse.user.js$/)) {
      return true;
    }
  }
  return false;
}

function setEventHandler(control, eventName, handlerOnElement) {
  var theControl = control;
  var theHandlerOnElement = handlerOnElement;
  var handler = function(event) {theHandlerOnElement(theControl, event);};
  try {
    control.addEventListener (eventName, handler, false);
  }
  catch(err) {
    control["on" + eventName] = handler;
  }
}

function setupLink (control, demoMode) {
  setEventHandler(control, "mouseover", linkMouseOverHandler);
  setEventHandler(control, "mouseout", linkMouseOutHandler);
  if (demoMode) {
    setEventHandler(control, "focus", demoFocusHandler);
    setEventHandler(control, "blur", demoBlurHandler);
  }
}

var F9_code = 120;

var linksMouseOver = false;

function keyDownHandler(element, event) {
  if (!event) event = window.event;
  if (event.keyCode == F9_code && !event.altKey && !event.ctrlKey) {
    toggleLinkMouseOver();
  }
}

function toggleLinkMouseOver() {
  if (iAmInGreaseMonkey()) {
    linksMouseOver = GM_getValue("linksMouseOver", false);
  }
  linksMouseOver = !linksMouseOver;
  refreshLinksMouseOverView(linksMouseOver);
  if (linksMouseOver) {
    alert ("Enabled mouse-motion-only navigation");
  }
  else {
    if (lastMouseOverLink) {
      resetOldLink (lastMouseOverLink);
      lastMouseOverLink = null;
    }
    alert ("Disabled mouse-motion-only navigation");
  }
    
  if (iAmInGreaseMonkey()) {
    GM_setValue("linksMouseOver", linksMouseOver);
  }
}

function initialiseLinksMouseOverView() {
  if (iAmInGreaseMonkey()) {
    linksMouseOver = GM_getValue("linksMouseOver", false);
  }
  refreshLinksMouseOverView(linksMouseOver);
}

function getLinksMouseOver() {
  return linksMouseOver;
}

var activeColors = ["black", "pink", "red", "pink", "black"];
var lastMouseOverLink = null;
var goingToLink = null;


function resetOldLink (element) {
  goingToLink = null;
  element.mouseOverCount = null;
  if (element.activeIndicator) {
    element.removeChild (element.activeIndicator);
    element.activeIndicator = null;
  }
}

function updateActiveIndicator (activeIndicator, count) {
  if (count > 2) {
    var indicatorText = "\u00A0";
    for (var i=0; i<count-1; i++) {
      indicatorText = indicatorText + "#";
    }
    activeIndicator.firstChild.nodeValue = indicatorText;
  }
}

function goToParentsLink (activeIndicator, event) {
  var parentLinkElement = activeIndicator.parentNode;
  if (parentLinkElement != goingToLink) {
    goingToLink = parentLinkElement;
    activeIndicator.firstChild.nodeValue = "\u00A0 following ...";
    // alert ("new activeIndicator.firstChild.nodeValue = " + activeIndicator.firstChild.nodeValue);
    window.location = parentLinkElement.href;
  }
}

function mouseOverLinkToFollow(element, event) {
  if (linksMouseOver && element != goingToLink) {
    if (lastMouseOverLink == element) {
      element.mouseOverCount++;
      if (element.mouseOverCount == 3) {
        var activeIndicator = document.createElement("b");
        activeIndicator.appendChild(document.createTextNode("\u00A0##"));
        element.appendChild (activeIndicator);
        setEventHandler (activeIndicator, "mouseover", goToParentsLink);
        element.activeIndicator = activeIndicator;
      }
      else {
        updateActiveIndicator(element.activeIndicator, element.mouseOverCount);
      }
    }
    else {
      if (lastMouseOverLink) resetOldLink (lastMouseOverLink);
      lastMouseOverLink = element;
      element.mouseOverCount = 1;
    }
  }
  return false;
}  

function mouseOutLinkToFollow(element, event) {
  if (linksMouseOver && element != goingToLink) {
    if (element.mouseOverCount) {
      element.mouseOverCount++;
      updateActiveIndicator(element.activeIndicator, element.mouseOverCount);
    }
  }
  return false;
}

function refreshLinksMouseOverView(linksMouseOver) {
  var linkElements = document.getElementsByTagName("a");
  var hrefLinkElements = [];
  var j = 0;
  for (var i=0; i<linkElements.length; i++) {
    if (linkElements[i].href) {
      hrefLinkElements[j++] = linkElements[i];
    }
  }
  if (linksMouseOver) {
    for (var i=0; i<hrefLinkElements.length; i++) {
      var linkElement = hrefLinkElements[i];
    }
  }
  else {
    for (var i=0; i<hrefLinkElements.length; i++) {
      var linkElement = hrefLinkElements[i];
    }
  }
}

var mouseDownCount = 0;

function mouseDownHandler(element, event) {
  mouseDownCount++;
}

function mouseUpHandler(element, event) {
  mouseDownCount--;
}

function linkMouseOverHandler(element, event) {
  if (mouseDownCount <= 0) {
    element.focus();
    mouseOverLinkToFollow (element, event);
  }
  return false;
}

function linkMouseOutHandler(element, event) {
  mouseOutLinkToFollow (element, event);
  return false;
}

setEventHandler(document, "mouseup", mouseUpHandler);
setEventHandler(document, "mousedown", mouseDownHandler);

function setupControl (control, demoMode) {
  setEventHandler(control, "mouseover", mouseOverHandler);
  if (demoMode) {
    setEventHandler(control, "focus", demoFocusHandler);
    setEventHandler(control, "blur", demoBlurHandler);
  }
}

function mouseOverHandler(element, event) {
  if (mouseDownCount <= 0) {
    element.focus();
  }
}

function demoFocusHandler(element, event) {
  element.focus();
  var style = element.style;
  element.savedStyle = [style.backgroundColor, style.color];
  style.backgroundColor = "#ffffa0";
  style.color = "#000000";
}

function demoBlurHandler(element, event) {
  if (element.savedStyle) {
    var style = element.style;
    var savedStyle = element.savedStyle;
    style.backgroundColor = savedStyle[0];
    style.color = savedStyle[1];
  }    
}

function setupElementsWithTag(node, tag, demoMode, setupFunction) {
  var elements = node.getElementsByTagName(tag);
  for (var i=0; i<elements.length; i++) {
    setupFunction (elements[i], demoMode);
  }
}  

function setupFocusFollowsMouse(node, demoMode) {
  var tagTypes = ["textarea", "input", "select"];
  for (var i = 0; i<tagTypes.length; i++) {
    setupElementsWithTag (node, tagTypes[i], demoMode, setupControl);
  }
  setupElementsWithTag (node, "a", demoMode, setupLink);

  setEventHandler(document, "keydown", keyDownHandler);
  initialiseLinksMouseOverView();
}

if (iAmInGreaseMonkey() && !hasFocusFollowsMouseScriptElement()) {
  setupFocusFollowsMouse(document, false);
}
