? .git
Index: devel_themer.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/devel/devel_themer.js,v
retrieving revision 1.17.2.7
diff -u -p -r1.17.2.7 devel_themer.js
--- devel_themer.js	7 Apr 2009 12:43:06 -0000	1.17.2.7
+++ devel_themer.js	2 Jun 2009 15:08:39 -0000
@@ -1,24 +1,10 @@
 // $Id: devel_themer.js,v 1.17.2.7 2009/04/07 12:43:06 jjeff Exp $
 
-if (Drupal.jsEnabled) {
+if (Drupal.jsEnabled) (function() {
   $(document).ready(function () {
-    lastObj = false;
-    thmrSpanified = false;
-    strs = Drupal.settings.thmrStrings;
-    $('body').addClass("thmr_call").attr("id", "thmr_" + Drupal.settings.page_id);
-    $('body.thmr_call,span.thmr_call')
-    .hover(
-      function () {
-        if (themerEnabled && this.parentNode.nodeName != 'BODY' && $(this).attr('thmr_curr') != 1) {
-          $(this).css('outline', 'red solid 1px');
-        }
-      },
-      function () {
-        if (themerEnabled && $(this).attr('thmr_curr') != 1) {
-          $(this).css('outline', 'none');
-        }
-      }
-    );
+    findThemerCalls();
+
+    var strs = Drupal.settings.thmrStrings;
 
     var themerEnabled = 0;
     var themerToggle = function () {
@@ -26,19 +12,28 @@ if (Drupal.jsEnabled) {
       $('#themer-toggle :checkbox').attr('checked', themerEnabled ? 'checked' : '');
       $('#themer-popup').css('display', themerEnabled ? 'block' : 'none');
       if (themerEnabled) {
-        document.onclick = themerEvent;
-        if (lastObj != false) {
-          $(lastObj).css('outline', '3px solid #999');
-        }
-        if (!thmrSpanified) {
-          spanify();
-        }
+        $(document)
+          .bind('click.themerEvent', function(event) {
+            // Select the element we've clicked on.
+            selectThemerCall($(event.target).attr('trig'));
+            
+            // Discard any clicks that aren't in the popup.
+            if(!thmrInPop(event.target)) return false;
+          })
+          .bind('mouseover.themerEvent', function(event) {
+            // Display the outlines for the current element, not showing
+            // parents for elements in the popup.
+
+            displayHoverOutlines($(event.target).attr('trig'), !thmrInPop(event.target));
+          });
+        displaySelectedThemerCall();
       }
       else {
-        document.onclick = null;
-        if (lastObj != false) {
-          $(lastObj).css('outline', 'none');
-        }
+        $(document)
+          .unbind('click.themerEvent')
+          .unbind('mouseover.themerEvent');
+        hideSelectedThemerCall();
+        hideHoverOutlines();
       }
     };
     $(Drupal.settings.thmr_popup)
@@ -61,110 +56,14 @@ if (Drupal.jsEnabled) {
       themerToggle();
     });
   });
-}
-
-/**
- * Known issue: IE does NOT support outline css property.
- * Solution: use another browser
- */
-function themerHilight(obj) {
-  // hilight the current object (and un-highlight the last)
-  if (lastObj != false) {
-    $(lastObj).css('outline', 'none').attr('thmr_curr', 0);
-  }
-  $(obj).css('outline', '#999 solid 3px').attr('thmr_curr', 1);
-  lastObj = obj;
-}
-
-function themerDoIt(obj) {
-  if (thmrInPop(obj)) {
-    return true;
-  }
-  // start throbber
-  //$('#themer-popup img.throbber').show();
-  var objs = thmrFindParents(obj);
-  if (objs.length) {
-    themerHilight(objs[0]);
-    thmrRebuildPopup(objs);
-  }
-  return false;
-}
 
-function spanify() {
-  $('span.thmr_call')
-    .each(function () {
-      // make spans around block elements into block elements themselves
-      var kids = $(this).children();
-      for(i=0;i<kids.length;i++) {
-        //console.log(kids[i].style.display);
-        if ($(kids[i]).css('display') != 'inline' && $(kids[i]).is('DIV, P, ADDRESS, BLOCKQUOTE, CENTER, DIR, DL, FIELDSET, FORM, H1, H2, H3, H4, H5, H6, HR, ISINDEX, MENU, NOFRAMES, NOSCRIPT, OL, PRE, TABLE, UL,  DD, DT, FRAMESET, LI, TBODY, TD, TFOOT, TH, THEAD, TR')) {
-          $(this).css('display', 'block');
-        }
-      }
-    });
-  thmrSpanified = true;
-  // turn off the throbber
-  //$('#themer-toggle img.throbber').hide();
-}
+//is the element in either the popup box or the toggle div?
 
 function thmrInPop(obj) {
-  //is the element in either the popup box or the toggle div?
-  if (obj.id == "themer-popup" || obj.id == "themer-toggle") return true;
-  if (obj.parentNode) {
-    while (obj = obj.parentNode) {
-      if (obj.id=="themer-popup" || obj.id == "themer-toggle") return true;
-    }
-  }
-  return false;
-}
-
-function themerEvent(e) {
-  if (!e) {
-    var e = window.event;
-  };
-  if (e.target) var tg = e.target;
-  else if (e.srcElement) var tg = e.srcElement;
-  return themerDoIt(tg);
-}
-
-/**
- * Find all parents with class="thmr_call"
- */
-function thmrFindParents(obj) {
-  var parents = new Array();
-  if ($(obj).hasClass('thmr_call')) {
-    parents[parents.length] = obj;
-  }
-  if (obj && obj.parentNode) {
-    while (obj = obj.parentNode) {
-      if ($(obj).hasClass('thmr_call')) {
-        parents[parents.length] = obj;
-      }
-    }
-  }
-  return parents;
-}
+  do {
+    if (obj.id == "themer-popup" || obj.id == "themer-toggle") return true;
+  } while (obj = obj.parentNode);
 
-/**
- * Check to see if object is a block element
- */
-function thmrIsBlock(obj) {
-  if (obj.style.display == 'block') {
-    return true;
-  }
-  else if (obj.style.display == 'inline' || obj.style.display == 'none') {
-    return false;
-  }
-  if (obj.tagName != undefined) {
-    var i = blocks.length;
-    if (i > 0) {
-      do {
-        if (blocks[i] === obj.tagName) {
-          return true;
-        }
-      } while (i--);
-    }
-  }
   return false;
 }
 
@@ -181,14 +80,12 @@ function thmrRefreshCollapse() {
 /**
  * Rebuild the popup
  *
- * @param objs
- *   The array of the current object and its parents. Current object is first element of the array
+ * @param obj
+ *   The current object.
  */
-function thmrRebuildPopup(objs) {
-  // rebuild the popup box
-  var id = objs[0].id;
+function thmrRebuildPopup(obj) {
   // vars is the settings array element for this theme item
-  var vars = Drupal.settings[id];
+  var vars = Drupal.settings[obj.id];
   // strs is the translatable strings
   var strs = Drupal.settings.thmrStrings;
   var type = vars.type;
@@ -211,28 +108,19 @@ function thmrRebuildPopup(objs) {
   // parents
   var parents = '';
   parents = strs.parents +' <span class="parents">';
-  for(i=1;i<objs.length;i++) {
-    var pvars = Drupal.settings[objs[i].id];
-    parents += i!=1 ? '&lt; ' : '';
+  var first = true;
+  for(var parent = obj.parent; parent; parent = parent.parent) {
+    var pvars = Drupal.settings[parent.id];
+
+    parents += !first ? '&lt; ' : '';
     // populate the parents
     // each parent is wrapped with a span containing a 'trig' attribute with the id of the element it represents
-    parents += '<span class="parent" trig="'+ objs[i].id +'">'+ pvars.name +'</span> ';
+    parents += '<span class="parent" trig="'+ parent.id +'">'+ pvars.name +'</span> ';
+    first = false;
   }
   parents += '</span>';
   // stick the parents spans in the #parents div
   $('#themer-popup #parents').empty().prepend(parents);
-  $('#themer-popup span.parent').click(function() {
-    // make them clickable
-    $('#'+ $(this).attr('trig')).each(function() { themerDoIt(this) });
-  })
-  .hover(function() {
-      // make them highlight their element on mouseover
-      $('#'+ $(this).attr('trig')).trigger('mouseover');
-    },
-    function() {
-      // and unhilight on mouseout
-      $('#'+ $(this).attr('trig')).trigger('mouseout');
-    });
 
   if (vars == undefined) {
     // if there's no item in the settings array for this element
@@ -245,7 +133,7 @@ function thmrRebuildPopup(objs) {
   else {
     $('#themer-popup div.duration').empty().prepend('<span class="dt">' + strs.duration + '</span>' + vars.duration + ' ms');
     $('#themer-popup dd.candidates').empty().prepend(vars.candidates.join('<span class="delimiter"> < </span>'));
-    var uri = Drupal.settings.devel_themer_uri + '/' + id;
+    var uri = Drupal.settings.devel_themer_uri + '/' + obj.id;
     if (type == 'func') {
       if (vars.candidates != undefined && vars.candidates.length != 0) {
         // populate the candidates
@@ -272,4 +160,214 @@ function thmrRebuildPopup(objs) {
   }
   // stop throbber
   //$('#themer-popup img.throbber').hide();
-}
\ No newline at end of file
+}
+
+// This walks the document tree, finding the comments which deliminate
+// theme calls.
+//
+// It has to be done manually because jQuery ignores comment nodes.
+
+var themerCalls;
+
+function findThemerCalls() {
+  var currentThemerCall;
+  var contentsStack = [];
+  var contents;
+  
+  themerCalls = [];
+  startThemerCall('thmr_' + Drupal.settings.page_id, false);
+  recurse(document.body);
+  endThemerCall('thmr_' + Drupal.settings.page_id);
+
+  if(currentThemerCall) throw "Missing end node.";
+
+  return;
+
+  function recurse(parent) {
+    for (var i = 0; i < parent.childNodes.length; ++i) {
+      var node = parent.childNodes[i];
+
+      switch (node.nodeType) {
+        case 8: // COMMENT_NODE:
+          var m = node.nodeValue.match(/thmr_call_(begin|end):(thmr_\d+)/)
+          if(m) {
+            if (m[1] == 'begin') startThemerCall(m[2], true);
+            else if (m[1] == 'end') endThemerCall(m[2]);
+          }
+          break;
+        case 1: // Node.ELEMENT_NODE:
+          var startId = currentThemerCall.id;
+          recurse(node);
+          if(startId == currentThemerCall.id)
+            addNode(node);
+          break;
+        case 3: // Node.TEXT_NODE:
+          // Put a 'span' around the text node to find its dimensions.
+          // Most of the time this isn't really necessary.
+          if(contents && node.parentNode.childNodes.length > 1 && node.nodeValue.match(/[^\s]/)) {
+            var span = document.createElement('span');
+            span.appendChild(node.cloneNode(false));
+            node.parentNode.replaceChild(span, node);
+            addNode(span);
+          }
+          break;
+      }
+    }
+  }
+
+  function startThemerCall(id, trackContents) {
+    currentThemerCall = themerCalls[id] = {
+      id: id,
+      parent: currentThemerCall
+    };
+    contentsStack.push(contents);
+    contents = trackContents ? [] : null;
+  }
+
+  function endThemerCall(id) {
+    if(!currentThemerCall || currentThemerCall.id != id)
+      throw "Mismatched end comment.";
+
+    if(contents && contents.length) {
+      var dimensions;
+
+      for(var j = 0; j < contents.length; ++j) {
+        if(contents[j].left != contents[j].right && contents[j].top != contents[j].bottom) {
+          if(!dimensions) {
+            //dimensions = $.extend({}, contents[j]);
+            dimensions = {
+              left : contents[j].left,
+              right : contents[j].right,
+              top : contents[j].top,
+              bottom : contents[j].bottom
+            };
+          }
+          else {
+            dimensions.left = Math.min(dimensions.left, contents[j].left);
+            dimensions.right = Math.max(dimensions.right, contents[j].right);
+            dimensions.top = Math.min(dimensions.top, contents[j].top);
+            dimensions.bottom = Math.max(dimensions.bottom, contents[j].bottom);
+          }
+        }
+      }
+
+      themerCalls[id].dimensions = dimensions;
+    }
+
+    contents = contentsStack.pop();
+    if(contents && dimensions) contents.push(dimensions);
+    currentThemerCall = currentThemerCall.parent;
+  }
+
+  function addNode(node) {
+    $(node).attr('trig', currentThemerCall.id);
+    var offset = $(node).offset();
+    if(contents) {
+      contents.push({
+        left: offset.left, right: offset.left + node.offsetWidth,
+        top: offset.top, bottom: offset.top + node.offsetHeight,
+      });
+    }
+  }
+}
+
+//
+// Outline for hovered themer call.
+//
+
+var hoverRoot = false;
+
+function displayHoverOutlines(id, showParents) {
+  if (!hoverRoot) {
+    hoverRoot = $('<div></div>').appendTo(document.body);
+  }
+  else {
+    hoverRoot.empty();
+  }
+
+  if(!id) return;
+
+  var themerCall = themerCalls[id];
+
+  drawThemerCallOutline(themerCall, 1, 'red', hoverRoot);
+  if(showParents) {
+    while(themerCall = themerCall.parent) {
+      drawThemerCallOutline(themerCall, 1, 'red', hoverRoot);
+    }
+  }
+}
+
+function hideHoverOutlines() {
+  if (hoverRoot) hoverRoot.empty();
+}
+
+//
+// Outline around selected themer call.
+//
+
+var mainOutline = false;
+var lastThemerCall = false;
+
+function selectThemerCall(id) {
+  // start throbber
+  //$('#themer-popup img.throbber').show();
+
+  if(id) {
+    lastThemerCall = themerCalls[id];
+    thmrRebuildPopup(lastThemerCall);
+    displaySelectedThemerCall();
+  }
+}
+
+function displaySelectedThemerCall() {
+  if(mainOutline)
+    mainOutline.empty();
+  else
+    mainOutline = $('<div></div>').appendTo(document.body);
+
+  drawThemerCallOutline(lastThemerCall, 3, '#999', mainOutline);
+}
+
+function hideSelectedThemerCall() {
+  if(mainOutline) mainOutline.empty();
+}
+
+// Draw Themer Call Outline
+
+function drawThemerCallOutline(themerCall, width, color, parent) {
+  var dimensions = themerCall && themerCall.dimensions;
+  if(!dimensions) return;
+
+  parent = parent || $(document.body);
+  var nodes = [];
+
+  for(var i = 0; i < 4; ++i) {
+    nodes[i] = document.createElement('div');
+  }
+
+  position(nodes[0],
+    dimensions.left - width, dimensions.top - width,
+    dimensions.right - dimensions.left + width * 2, width);
+  position(nodes[1],
+    dimensions.left - width, dimensions.top,
+    width, dimensions.bottom - dimensions.top);
+  position(nodes[2],
+    dimensions.right, dimensions.top,
+    width, dimensions.bottom - dimensions.top);
+  position(nodes[3],
+    dimensions.left - width, dimensions.bottom,
+    dimensions.right - dimensions.left + width * 2, width);
+
+  $(nodes)
+    .css({ display : "block", background : color, position : "absolute"})
+    .appendTo(parent);
+
+  function position(element, left, top, width, height) {
+    element.style.left = left + "px";
+    element.style.top = top + "px";
+    element.style.width = width + "px";
+    element.style.height = height + "px";
+  }
+}
+
+})();
Index: devel_themer.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/devel/devel_themer.module,v
retrieving revision 1.32.2.22
diff -u -p -r1.32.2.22 devel_themer.module
--- devel_themer.module	24 Dec 2008 02:28:16 -0000	1.32.2.22
+++ devel_themer.module	2 Jun 2009 15:08:39 -0000
@@ -493,7 +493,7 @@ function devel_themer_exit() {
 
 function devel_theme_call_marker($name, $counter, $type) {
   $id = "thmr_". $counter;
-  return array("<span id=\"$id\" class=\"thmr_call\">", "</span>\n");
+  return array("<!-- thmr_call_begin:$id -->", "<!-- thmr_call_end:$id -->\n");
 }
 
 // just hand out next counter, or return current value
