HTRACE-293. htrace-web: control-click should fully expand trace trees (Colin Patrick McCabe via iwasakims)
diff --git a/htrace-webapp/src/main/webapp/app/span.js b/htrace-webapp/src/main/webapp/app/span.js
index cd87543..ca867ec 100644
--- a/htrace-webapp/src/main/webapp/app/span.js
+++ b/htrace-webapp/src/main/webapp/app/span.js
@@ -145,6 +145,32 @@
     return obj;
   },
 
+  reifyParentsRecursive: function(depth) {
+    var rootDeferred = jQuery.Deferred();
+    var span = this;
+    span.reifyParents().done(function(err) {
+      if ((err != "") || (depth <= 0)) {
+        rootDeferred.resolve(err);
+        return;
+      }
+      var recursivePromises = [];
+      var reifiedParents = span.get("reifiedParents");
+      for (var j = 0; j < reifiedParents.length; j++) {
+        recursivePromises.push(reifiedParents[j].reifyParentsRecursive(depth - 1));
+      }
+      $.when.apply($, recursivePromises).then(function() {
+        for (var i = 0; i < arguments.length; i++) {
+          if (arguments[i] != "") {
+            rootDeferred.resolve(arguments[i]);
+            return;
+          }
+        }
+        rootDeferred.resolve("");
+      });
+    });
+    return rootDeferred.promise();
+  },
+
   //
   // Although the parent IDs are always present in the 'parents' field of the
   // span, sometimes we need the actual parent span models.  In that case we
@@ -193,6 +219,32 @@
     return rootDeferred.promise();
   },
 
+  reifyChildrenRecursive: function(depth) {
+    var rootDeferred = jQuery.Deferred();
+    var span = this;
+    span.reifyChildren().done(function(err) {
+      if ((err != "") || (depth <= 0)) {
+        rootDeferred.resolve(err);
+        return;
+      }
+      var recursivePromises = [];
+      var reifiedChildren = span.get("reifiedChildren");
+      for (var j = 0; j < reifiedChildren.length; j++) {
+        recursivePromises.push(reifiedChildren[j].reifyChildrenRecursive(depth - 1));
+      }
+      $.when.apply($, recursivePromises).then(function() {
+        for (var i = 0; i < arguments.length; i++) {
+          if (arguments[i] != "") {
+            rootDeferred.resolve(arguments[i]);
+            return;
+          }
+        }
+        rootDeferred.resolve("");
+      });
+    });
+    return rootDeferred.promise();
+  },
+
   //
   // The span itself does not contain its children.  However, the server has an
   // index which can be used to easily find the children of a particular span.
diff --git a/htrace-webapp/src/main/webapp/app/span_widget.js b/htrace-webapp/src/main/webapp/app/span_widget.js
index 50bea91..f7c447a 100644
--- a/htrace-webapp/src/main/webapp/app/span_widget.js
+++ b/htrace-webapp/src/main/webapp/app/span_widget.js
@@ -270,16 +270,18 @@
       xF: this.xB + (this.xDB / 2) - 2,
       y0: this.y0 + 2,
       yF: this.yF - 2,
-      callback: function() {
-        $.when(widget.span.reifyParents()).done(function (result) {
-          console.log("reifyParents: result was '" + result + "'");
+      callback: function(e) {
+        var depth = (e.raw.ctrlKey) ? 100 : 0;
+        $.when(widget.span.reifyParentsRecursive(depth)).done(function(result) {
+          console.log("reifyParentsRecursive(" + depth + "): result was '" +
+              result + "'");
           if (result != "") {
             alert(result);
           } else {
             widget.manager.searchResultsView.render();
           }
         });
-      },
+      }
     });
   }
   if ((this.span.get("reifiedChildren") == null) && (this.allowDownButton)) {
@@ -291,16 +293,18 @@
       xF: this.xD - 2,
       y0: this.y0 + 2,
       yF: this.yF - 2,
-      callback: function() {
-        $.when(widget.span.reifyChildren()).done(function (result) {
-          console.log("reifyChildren: result was '" + result + "'");
+      callback: function(e) {
+        var depth = (e.raw.ctrlKey) ? 100 : 0;
+        $.when(widget.span.reifyChildrenRecursive(depth)).done(function (result) {
+          console.log("reifyChildrenRecursive(" + depth + "): result was '" +
+              result + "'");
           if (result != "") {
             alert(result);
           } else {
             widget.manager.searchResultsView.render();
           }
         });
-      },
+      }
     });
   }
   this.manager.register("mouseDown", this);
diff --git a/htrace-webapp/src/main/webapp/app/triangle_button.js b/htrace-webapp/src/main/webapp/app/triangle_button.js
index f252476..cd8f39b 100644
--- a/htrace-webapp/src/main/webapp/app/triangle_button.js
+++ b/htrace-webapp/src/main/webapp/app/triangle_button.js
@@ -79,7 +79,7 @@
         return false;
       case "mouseUp":
         if (this.selected) {
-          this.callback();
+          this.callback(e);
           this.selected = false;
         }
         this.manager.unregister("mouseUp", this);