private void checkSizes() {
    Dimension vsz = scroll_pane.getViewport().getViewSize();
    Dimension csz = draw_area.getSize();
    Dimension nsz = new Dimension((int) (vsz.width * x_scale), (int) (vsz.height * y_scale));
    boolean chng = false;

    if (nsz.width > csz.width) {
      if (vsz.width >= csz.width) {
        csz.width = vsz.width;
        chng = true;
        x_scale = 1;
      }
    } else if (nsz.width < csz.width) {
      if (x_scale == 1) {
        csz.width = vsz.width;
        chng = true;
      }
    }

    if (nsz.height > csz.height) {
      if (vsz.height >= csz.height) {
        csz.height = vsz.height;
        chng = true;
        y_scale = 1;
      }
    } else if (nsz.height < csz.height) {
      if (y_scale == 1) {
        csz.height = vsz.height;
        chng = true;
      }
    }

    if (chng) draw_area.setSize(csz);
  }
  /** ***************************************************************************** */
  private void setupPanel() {
    draw_area = new HistoryPanel();
    scroll_pane = new JScrollPane(draw_area);

    Dimension d = new Dimension(BDDT_HISTORY_WIDTH, BDDT_HISTORY_HEIGHT);
    draw_area.setPreferredSize(d);
    draw_area.setSize(d);

    setContentPane(scroll_pane);
  }
  /** ***************************************************************************** */
  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());
  }
  @Override
  public void handleHistoryUpdated() {
    synchronized (this) {
      update_needed = true;
    }

    draw_area.repaint();
  }
  /** ***************************************************************************** */
  @Override
  public void handleHistoryStarted() {
    synchronized (this) {
      update_needed = true;
      restart_needed = true;
      thread_colors.clear();
    }

    draw_area.repaint();
  }
  private void handlePaint(Graphics2D g) {
    checkSizes();
    Dimension csz = draw_area.getSize();

    synchronized (this) {
      if (update_needed) updateGraph();
    }

    synchronized (history_graph) {
      int nobj = history_graph.getNumObjects();
      double twid =
          nobj * GRAPH_OBJECT_WIDTH
              + 2 * GRAPH_LEFT_RIGHT_MARGIN
              + (nobj - 1) * GRAPH_OBJECT_H_SPACE;
      double sx = csz.width / twid;
      left_right_margin = GRAPH_LEFT_RIGHT_MARGIN * sx;
      object_width = GRAPH_OBJECT_WIDTH * sx;
      object_hspacing = GRAPH_OBJECT_H_SPACE * sx;
      active_width = GRAPH_ACTIVE_WIDTH * sx;
      arrow_size = 3 / y_scale;

      double tht = 2 * GRAPH_TOP_BOTTOM_MARGIN + GRAPH_OBJECT_V_SPACE + GRAPH_TIME_SPACE;
      double sy = csz.height / tht;
      top_bottom_margin = GRAPH_TOP_BOTTOM_MARGIN * sy;
      object_height = GRAPH_OBJECT_HEIGHT * sy;
      time_start = top_bottom_margin + object_height + GRAPH_OBJECT_V_SPACE * sy;
      time_end = csz.height - top_bottom_margin;

      graph_locations = new HashMap<GraphObject, Double>();

      for (int i = 0; i < nobj; ++i) {
        GraphObject go = history_graph.getObject(i);
        drawObject(g, i, go);
      }

      for (int i = 0; i < nobj; ++i) {
        GraphObject go = history_graph.getObject(i);
        drawLinks(g, go);
      }
    }
  }