HTRACE-99: gui: Double clicking on spans should bring up span details (iwasakims / cmccabe)
diff --git a/htrace-webapp/src/main/web/app/search_results_view.js b/htrace-webapp/src/main/web/app/search_results_view.js
index 3f009d4..81197c3 100644
--- a/htrace-webapp/src/main/web/app/search_results_view.js
+++ b/htrace-webapp/src/main/web/app/search_results_view.js
@@ -96,6 +96,17 @@
     this.draw();
   },
 
+  handleDblclick: function(e) {
+    e.preventDefault();
+    this.widgetManager.handle({
+      type: "dblclick",
+      x: this.getCanvasX(e),
+      y: this.getCanvasY(e),
+      raw: e
+    });
+    this.draw();
+  },
+
   render: function() {
     console.log("SearchResultsView#render.");
     $(this.el).html(_.template($("#search-results-view-template").html()));
@@ -259,6 +270,10 @@
     $("#resultsCanvas").on("mousemove", function(e) {
       view.handleMouseMove(e);
     });
+    $("#resultsCanvas").off("dblclick");
+    $("#resultsCanvas").on("dblclick", function(e) {
+      view.handleDblclick(e);
+    });
     $("#resultsCanvas").off("contextmenu");
     $("#resultsCanvas").on("contextmenu", function(e) {
       return false;
@@ -269,7 +284,10 @@
     $(window).off("resize");
     $("#resultsCanvas").off("mousedown");
     $("#resultsCanvas").off("mouseup");
+    $("#resultsCanvas").off("mouseout");
     $("#resultsCanvas").off("mousemove");
+    $("#resultsCanvas").off("dblclick");
+    $("#resultsCanvas").off("contextmenu");
     Backbone.View.prototype.remove.apply(this, arguments);
   },
 
diff --git a/htrace-webapp/src/main/web/app/span_widget.js b/htrace-webapp/src/main/web/app/span_widget.js
index e06c8b4..1ef402c 100644
--- a/htrace-webapp/src/main/web/app/span_widget.js
+++ b/htrace-webapp/src/main/web/app/span_widget.js
@@ -19,7 +19,7 @@
 
 var htrace = htrace || {};
 
-htrace.fillSpanDetailsView = function(span) {
+htrace.showSpanDetails = function(span) {
   var info = {
     spanID: span.get("spanID"),
     begin: htrace.dateToString(span.get("begin"), 10),
@@ -94,17 +94,12 @@
   for (i = 0; i < len; i++) {
     // Make every other row grey to improve visibility.
     var colorString = ((i%2) == 1) ? "#f1f1f1" : "#ffffff";
-    h += _.template('<tr bgcolor="' + colorString + '">' +
-          '<td style="width:30%;word-wrap:break-word"><%- key %></td>' +
-          '<td style="width:70%;word-wrap:break-word"><%- val %></td>' +
-        "</tr>")({key: keys[i], val: info[keys[i]]});
+    h += _.template($("#table-row-template").html())(
+        {bgcolor: colorString, key: keys[i], val: info[keys[i]]});
   }
   h += '</table>';
-  $("#spanDetails").html(h);
-};
-
-htrace.clearSpanDetailsView = function() {
-  $("#spanDetails").html("");
+  htrace.showModal(_.template($("#modal-table-template").html())(
+      {title: "Span Details", body: h}));
 };
 
 // Widget containing the trace span displayed on the canvas.
@@ -182,6 +177,17 @@
 //          ", begin=" + this.span.get('begin') + ", end=" + this.span.get('end'));
       this.ctx.fillRect(beginX, this.y0 + gapY, endX - beginX,
           this.ySize - (gapY * 2));
+
+      // Draw a dots showing time points where annotations are.
+      var annotations = this.span.get('timeAnnotations');
+      var annotationY = this.y0 + gapY;
+      var annotationW = 4;
+      var annotationH = (this.ySize - (gapY * 2)) / 2;
+      this.ctx.fillStyle="#419641";
+      for (var i = 0; i < annotations.length; i++) {
+        this.ctx.fillRect(this.timeToPosition(annotations[i].t), annotationY,
+            annotationW, annotationH);
+      }
     }
 
     // Draw description text
@@ -209,45 +215,40 @@
           return true;
         }
         if (e.raw.ctrlKey) {
-          // If the control key is pressed, we can unselect the current
-          // selection, or create multiple selections.
+          // If the control key is pressed, we toggle the current selection.
+          // The user can create multiple selections this way.
           if (this.span.get("selected")) {
             this.span.set("selected", false);
           } else {
             this.span.set("selected", true);
           }
-          var selection = null;
-          var multipleSelections = false;
-          this.manager.searchResultsView.applyToAllSpans(function(span) {
-              if (span.get("selected")) {
-                if (selection == null) {
-                  selection = span;
-                } else {
-                  multipleSelections = true;
-                }
-              }
-            });
-          if (multipleSelections) {
-            selection = null;
-          }
-          if (selection == null) {
-            htrace.clearSpanDetailsView();
-          } else {
-            htrace.fillSpanDetailsView(selection);
-          }
         } else {
+          var that = this;
           this.manager.searchResultsView.applyToAllSpans(function(span) {
-              if (span.get("selected")) {
+              // Note: we don't want to set the selection state unless we need
+              // to.  Setting the state (even to the same thing it already is)
+              // triggers a full re-render, if the span is one in the results
+              // collection.  A full re-render slows us down and disrupts events
+              // like double-clicking.
+              if (that.span === span) {
+                if (!span.get("selected")) {
+                  span.set("selected", true);
+                }
+              } else if (span.get("selected")) {
                 span.set("selected", false);
               }
             });
-          this.span.set("selected", true);
-          htrace.fillSpanDetailsView(this.span);
         }
         return true;
       case "draw":
         this.draw();
         return true;
+      case "dblclick":
+        if (htrace.inBoundingBox(e.x, e.y,
+            this.x0, this.xF, this.y0, this.yF)) {
+          htrace.showSpanDetails(this.span);
+        }
+        return true;
     }
   };
 
@@ -303,5 +304,6 @@
     });
   }
   this.manager.register("mouseDown", this);
+  this.manager.register("dblclick", this);
   return this;
 };
diff --git a/htrace-webapp/src/main/web/app/widget_manager.js b/htrace-webapp/src/main/web/app/widget_manager.js
index 5f393b0..ae14b2b 100644
--- a/htrace-webapp/src/main/web/app/widget_manager.js
+++ b/htrace-webapp/src/main/web/app/widget_manager.js
@@ -32,6 +32,7 @@
     "mouseUp": [],
     "mouseMove": [],
     "mouseOut": [],
+    "dblclick": [],
     "draw": [],
   };
 
diff --git a/htrace-webapp/src/main/web/index.html b/htrace-webapp/src/main/web/index.html
index 0e8e6c3..77cbe09 100644
--- a/htrace-webapp/src/main/web/index.html
+++ b/htrace-webapp/src/main/web/index.html
@@ -92,14 +92,6 @@
           </div>
           <div class="panel panel-default">
             <div class="panel-heading">
-              <h1 class="panel-title">Span Details</h1>
-            </div style="border: 1px solid #000000;">
-            <div class="panel-body">
-              <div id="spanDetails" ></div>
-            </div>
-          </div>
-          <div class="panel panel-default">
-            <div class="panel-heading">
               <h1 class="panel-title">Search</h1>
             </div style="border: 1px solid #000000;">
             <div class="panel-body">
@@ -197,6 +189,33 @@
       </div>
     </script>
 
+    <script id="modal-table-template" type="text/template">
+      <div class="modal-dialog modal-lg">
+        <div class="modal-content">
+          <div class="modal-header">
+            <button type="button" class="close" data-dismiss="modal"
+                  aria-label="Close">
+              <span aria-hidden="true">&times;</span>
+            </button>
+            <h4 class="modal-title"><%= title %></h4>
+          </div>
+          <div class="modal-body">
+            <%= body %><p/>
+          </div>
+          <div class="modal-footer">
+            <button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
+          </div>
+        </div>
+      </div>
+    </script>
+
+    <script id="table-row-template" type="text/template">
+      <tr bgcolor="<%= bgcolor %>">
+        <td style="width:30%;word-wrap:break-word"><%- key %></td>
+        <td style="width:70%;word-wrap:break-word"><%- val %></td>
+      </tr>
+    </script>
+
     <script src="lib/jquery-2.1.4.js" type="text/javascript"></script>
     <script src="lib/bootstrap-3.3.1/js/bootstrap.min.js" type="text/javascript"></script>
     <script src="lib/underscore-1.7.0.js" type="text/javascript"></script>