/** ***************************************************************************** */
  BddtHistoryBubble(BddtLaunchControl ctrl, BddtHistoryData hd) {
    history_data = hd;
    for_control = ctrl;
    history_graph = null;
    last_update = 0;
    update_needed = true;
    restart_needed = true;
    x_scale = 1;
    y_scale = 1;
    thread_colors = new HashMap<BumpThread, Color>();
    type_strokes = new EnumMap<LinkType, Stroke>(LinkType.class);
    type_strokes.put(
        LinkType.ENTER,
        new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, DOTTED, 0));
    type_strokes.put(
        LinkType.RETURN,
        new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, DASHED, 0));
    type_strokes.put(LinkType.NEXT, new BasicStroke(1));
    arrow_stroke = new BasicStroke(1);

    updateGraph();

    hd.addHistoryListener(this);

    setupPanel();

    draw_area.addMouseListener(new FocusOnEntry());
  }
  private Color getThreadBlockColor(BumpThread bt) {
    if (bt == null) return Color.BLACK;

    synchronized (thread_colors) {
      Color c = thread_colors.get(bt);
      if (c == null) {
        double v;
        int ct = thread_colors.size();
        if (ct == 0) v = 0;
        else if (ct == 1) v = 1;
        else {
          v = 0.5;
          int p0 = ct - 1;
          int p1 = 1;
          for (int p = p0; p > 1; p /= 2) {
            v /= 2.0;
            p0 -= p1;
            p1 *= 2;
          }
          if ((p0 & 1) == 0) p0 = 2 * p1 - p0 + 1;
          v = v * p0;
        }
        float h = (float) (v * 0.8);
        float s = 0.7f;
        float b = 1.0f;
        int rgb = Color.HSBtoRGB(h, s, b);
        rgb |= 0xc0000000;
        c = new Color(rgb, true);
        thread_colors.put(bt, c);
      }
      return c;
    }
  }
 void finish(BddtHistoryItem hi) {
   GraphBlock gb = in_blocks.get(hi.getThread());
   if (gb != null) {
     gb.finish(hi.getTime());
     in_blocks.remove(hi.getThread());
   }
 }
 void startBlock(BddtHistoryItem hi) {
   if (start_time == 0) start_time = hi.getTime();
   else start_time = Math.min(start_time, hi.getTime());
   GraphBlock gb = new GraphBlock(hi);
   all_blocks.add(gb);
   in_blocks.put(hi.getThread(), gb);
 }
  /** ***************************************************************************** */
  @Override
  public void handleHistoryStarted() {
    synchronized (this) {
      update_needed = true;
      restart_needed = true;
      thread_colors.clear();
    }

    draw_area.repaint();
  }
 GraphObject getObject(BddtHistoryItem hi) {
   BumpRunValue rv = hi.getThisValue();
   GraphObject go = null;
   if (rv != null) {
     String key = rv.getValue();
     go = object_map.get(key);
     if (go == null) {
       go = new GraphObject(rv);
       graph_objects.add(go);
       object_map.put(key, go);
     }
   } else {
     String cnm = hi.getClassName();
     go = object_map.get(cnm);
     if (go == null) {
       go = new GraphObject(cnm);
       graph_objects.add(go);
       object_map.put(cnm, go);
     }
   }
   return go;
 }
  private void drawLinks(Graphics2D g, GraphObject go) {
    double x0 = graph_locations.get(go);

    for (GraphLink gl : go.getLinks()) {
      GraphObject got = gl.getToObject();
      double x1 = graph_locations.get(got);
      double y0 = getTimeY(gl.getTime());
      double x3, x4;
      if (x0 < x1) {
        x3 = x0 + active_width / 2;
        x4 = x1 - active_width / 2;
      } else {
        x3 = x0 - active_width / 2;
        x4 = x1 + active_width / 2;
      }
      Stroke sk = type_strokes.get(gl.getType());
      Color c = getThreadBlockColor(gl.getThread());
      g.setColor(c);
      g.setStroke(sk);
      Line2D ln = new Line2D.Double(x3, y0, x4, y0);
      g.draw(ln);
      drawArrow(g, x3, y0, x4, y0);
    }
  }
  private void drawObject(Graphics2D g, int idx, GraphObject go) {
    double x0 = left_right_margin + (object_width + object_hspacing) * idx;
    Rectangle2D r = new Rectangle2D.Double(x0, top_bottom_margin, object_width, object_height);
    graph_locations.put(go, x0 + object_width / 2);

    g.setColor(Color.WHITE);
    g.fill(r);
    g.setColor(Color.BLACK);
    g.draw(r);
    g.setColor(Color.RED);
    SwingText.drawText(go.getName(), g, r);

    double x1 = x0 + object_width / 2;
    double y1 = top_bottom_margin + object_height;
    double y2 = time_end;
    Line2D tl = new Line2D.Double(x1, y1, x1, y2);
    g.setColor(Color.BLACK);
    g.draw(tl);

    for (GraphBlock gb : go.getBlocks()) {
      drawBlock(g, x1, gb);
    }
  }
  /** ***************************************************************************** */
  private BddtHistoryItem getItemAtPoint(int x, int y) {
    if (object_width == 0) return null;

    double t =
        history_graph.getStartTime()
            + (y - time_start)
                / (time_end - time_start)
                * (history_graph.getEndTime() - history_graph.getStartTime() + 1);
    if (t < history_graph.getStartTime() || t > history_graph.getEndTime()) return null;

    GraphObject gobj = getObjectAtPoint(x, y);

    // correlate with blocks
    if (gobj != null) {
      GraphBlock gb0 = null;
      double dtime = 0;
      for (GraphBlock gb : gobj.getBlocks()) {
        double dt = gb.getEndTime() - gb.getStartTime();
        if (t >= gb.getStartTime() && t <= gb.getEndTime()) {
          if (gb0 == null || dt < dtime) {
            gb0 = gb;
            dtime = dt;
          }
        }
      }
      if (gb0 != null) {
        BddtHistoryItem bi0 = null;
        dtime = 0;
        for (BddtHistoryItem bhi : gb0.getItems()) {
          double dt = t - bhi.getTime();
          if (dt >= 0) {
            if (bi0 == null || dt < dtime) {
              bi0 = bhi;
              dtime = dt;
            }
          }
        }
        if (bi0 != null) return bi0;
      }
    }

    // correlate with links
    double delta =
        (history_graph.getEndTime() - history_graph.getStartTime()) / (time_end - time_start) * 3;

    double dtime = 0;
    GraphLink gl0 = null;
    for (int i = 0; i < history_graph.getNumObjects(); ++i) {
      GraphObject go = history_graph.getObject(i);
      double x0 = graph_locations.get(go);
      for (GraphLink gl : go.getLinks()) {
        double x1 = graph_locations.get(gl.getToObject());
        if (x > x0 && x < x1) {
          double dt = Math.abs(t - gl.getTime());
          if (gl0 == null || dt < dtime) {
            gl0 = gl;
            dtime = dt;
          }
        }
      }
    }
    if (gl0 != null && dtime <= delta) {
      return gl0.getItem();
    }

    return null;
  }
 void extendBlock(BddtHistoryItem hi) {
   GraphBlock gb = in_blocks.get(hi.getThread());
   if (gb == null) startBlock(hi);
   else gb.addItem(hi);
 }