Google Tag Manager

2019/06/28

aterai

Make JToolTip translucent, and change its shape and display position

Code

class BalloonToolTip extends JToolTip {
  private static final int TRI_HEIGHT = 4;
  private HierarchyListener listener;

  @Override public void updateUI() {
    removeHierarchyListener(listener);
    super.updateUI();
    listener = e -> {
      Component c = e.getComponent();
      if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0
          && c.isShowing()) {
        Optional.ofNullable(SwingUtilities.getRoot(c))
           .filter(JWindow.class::isInstance).map(JWindow.class::cast)
           .ifPresent(w -> w.setBackground(new Color(0x0, true)));
      }
    };
    addHierarchyListener(listener);
    setOpaque(false);
    setForeground(Color.WHITE);
    setBackground(new Color(0xC8_00_00_00, true));
    setBorder(BorderFactory.createEmptyBorder(5, 5, 5 + TRI_HEIGHT, 5));
  }

  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.height = 32;
    return d;
  }

  @Override protected void paintComponent(Graphics g) {
    Shape s = makeBalloonShape();
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(getBackground());
    g2.fill(s);
    g2.dispose();
    super.paintComponent(g);
  }

  private Shape makeBalloonShape() {
    int w = getWidth() - 1;
    int h = getHeight() - TRI_HEIGHT - 1;
    int r = 10;
    int cx = getWidth() / 2;
    Polygon triangle = new Polygon();
    triangle.addPoint(cx - TRI_HEIGHT, h);
    triangle.addPoint(cx, h + TRI_HEIGHT);
    triangle.addPoint(cx + TRI_HEIGHT, h);
    Area area = new Area(new RoundRectangle2D.Float(0, 0, w, h, r, r));
    area.add(new Area(triangle));
    return area;
  }
}

// ...
@Override public String getToolTipText(MouseEvent e) {
  Point p = e.getPoint();
  int idx = locationToIndex(p);
  Rectangle rect = getCellBounds(idx, idx);
  if (idx < 0 || !rect.contains(p.x, p.y)) {
    return null;
  }
  Contribution value = getModel().getElementAt(idx);
  String act = value.activity == 0 ? "No" : Objects.toString(value.activity);
  return "<html>" + act + " contribution <span style='color:#C8C8C8'> on "
      + value.date.toString();
}

@Override public Point getToolTipLocation(MouseEvent e) {
  Point p = e.getPoint();
  int i = locationToIndex(p);
  Rectangle rect = getCellBounds(i, i);

  String toolTipText = getToolTipText(e);
  if (Objects.nonNull(toolTipText)) {
    JToolTip tip = createToolTip();
    tip.setTipText(toolTipText);
    Dimension d = tip.getPreferredSize();
    int gap = 2;
    return new Point((int) (rect.getCenterX() - d.getWidth() / 2d),
                     rect.y - d.height - gap);
  }
  return null;
}

@Override public JToolTip createToolTip() {
  if (tip == null) {
    tip = new BalloonToolTip();
    tip.setComponent(this);
  }
  return tip;
}

References

2019/05/28

aterai

Draw a color wheel on JPanel

Code

// Colors: a Color Dialog | Java Graphics
// https://javagraphics.blogspot.com/2007/04/jcolorchooser-making-alternative.html
private BufferedImage updateImage() {
  BufferedImage image = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_ARGB);
  int[] row = new int[SIZE];
  float size = (float) SIZE;
  float radius = size / 2f;

  for (int yidx = 0; yidx < SIZE; yidx++) {
    float y = yidx - size / 2f;
    for (int xidx = 0; xidx < SIZE; xidx++) {
      float x = xidx - size / 2f;
      double theta = Math.atan2(y, x) - 3d * Math.PI / 2d;
      if (theta < 0) {
        theta += 2d * Math.PI;
      }
      double r = Math.sqrt(x * x + y * y);
      float hue = (float) (theta / (2d * Math.PI));
      float sat = Math.min((float) (r / radius), 1f);
      float bri = 1f;
      row[xidx] = Color.HSBtoRGB(hue, sat, bri);
    }
    image.getRaster().setDataElements(0, yidx, SIZE, 1, row);
  }
  return image;
}

// campbell: Java 2D Trickery: Soft Clipping Blog | Oracle Community
// https://community.oracle.com/blogs/campbell/2006/07/19/java-2d-trickery-soft-clipping
GraphicsConfiguration gc = g2.getDeviceConfiguration();
BufferedImage buf = gc.createCompatibleImage(s, s, Transparency.TRANSLUCENT);
Graphics2D g2d = buf.createGraphics();

g2d.setComposite(AlphaComposite.Src);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fill(new Ellipse2D.Float(0f, 0f, s, s));

g2d.setComposite(AlphaComposite.SrcAtop);
g2d.drawImage(colorWheelImage, 0, 0, null);
g2d.dispose();

g2.drawImage(buf, null, (getWidth() - s) / 2, (getHeight() - s) / 2);
g2.dispose();

References

2019/04/25

aterai

Use the GridBagLayout to layout the JButton like a keyboard

Code

private static final String[][] KEYS = {
  {"`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "BS"},
  {"Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\", ""},
  {"Ctrl", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "Enter", ""},
  {"Shift", "Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "", "↑"},
  {"Fn", "Alt", "                             ", "Alt", "←", "↓", "→"}
};

private static Component makeKeyboardPanel() {
  JPanel keyboard = new JPanel(new GridBagLayout());

  GridBagConstraints c = new GridBagConstraints();
  c.fill = GridBagConstraints.BOTH;
  c.gridy = 50;
  for (int i = 0; i < KEYS[0].length * 2; i++) {
    c.gridx = i;
    keyboard.add(Box.createHorizontalStrut(KeyButton.SIZE));
  }

  for (int row = 0; row < KEYS.length; row++) {
    c.gridx = 0;
    c.gridy = row;
    for (int col = 0; col < KEYS[row].length; col++) {
      String key = KEYS[row][col];
      int len = key.length();
      c.gridwidth = len > 10 ? 14
                  : len > 4  ? 4
                  : len > 1  ? 3
                  : len == 1 ? 2 : 1;
      if (key.isEmpty()) {
        keyboard.add(Box.createHorizontalStrut(KeyButton.SIZE), c);
      } else {
        keyboard.add(new KeyButton(key, len <= 2), c);
      }
      c.gridx += c.gridwidth;
    }
  }
  EventQueue.invokeLater(() -> SwingUtilities.updateComponentTreeUI(keyboard));
  return keyboard;
}

References

2019/03/27

aterai

Perform hover effect animation using RadialGradientPaint on soft clipped JButton

Code

protected void update() {
  if (!getBounds().equals(base)) {
    base = getBounds();
    int w = getWidth();
    int h = getHeight();
    if (w > 0 && h > 0) {
      buf = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
    }
    shape = new RoundRectangle2D.Double(
      0, 0, w - 1, h - 1, ARC_WIDTH, ARC_HEIGHT);
  }
  if (buf == null) {
    return;
  }

  Graphics2D g2 = buf.createGraphics();
  g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                      RenderingHints.VALUE_ANTIALIAS_ON);
  Color c1 = new Color(0x00_F7_23_59, true);
  Color c2 = new Color(0x64_44_05_F7, true);

  g2.setComposite(AlphaComposite.Clear);
  g2.fillRect(0, 0, getWidth(), getHeight());

  g2.setComposite(AlphaComposite.Src);
  g2.setPaint(new Color(getModel().isArmed() ? 0xFF_AA_AA : 0xF7_23_59));
  g2.fill(shape);

  if (radius > 0) {
    int cx = pt.x - radius;
    int cy = pt.y - radius;
    int r2 = radius + radius;
    float[] dist = { 0f, 1f };
    Color[] colors = { c2, c1 };
    g2.setPaint(new RadialGradientPaint(pt, r2, dist, colors));
    g2.setComposite(AlphaComposite.SrcAtop);
    // g2.setClip(shape);
    g2.fill(new Ellipse2D.Double(cx, cy, r2, r2));
  }
  g2.dispose();
}

References

2019/02/27

aterai

Limit the movable range of JSlider's value

Code

public static final int MAXI = 80;
public static final int MINI = 40;

private static class MetalDragLimitedSliderUI extends MetalSliderUI {
  @Override protected TrackListener createTrackListener(JSlider slider) {
    return new TrackListener() {
      @Override public void mouseDragged(MouseEvent e) {
        // case HORIZONTAL:
        int halfThumbWidth = thumbRect.width / 2;
        int thumbLeft = e.getX() - offset;
        int maxPos = xPositionForValue(MAXI) - halfThumbWidth;
        int minPos = xPositionForValue(MINI) - halfThumbWidth;
        if (thumbLeft > maxPos) {
          e.translatePoint(maxPos + offset - e.getX(), 0);
        } else if (thumbLeft < minPos) {
          e.translatePoint(minPos + offset - e.getX(), 0);
        }
        super.mouseDragged(e);
      }
    };
  }
}

private static JSlider makeSilder(String title) {
  JSlider slider = new JSlider(0, 100, 40);
  slider.setBorder(BorderFactory.createTitledBorder(title));
  slider.setMajorTickSpacing(10);
  slider.setPaintTicks(true);
  slider.setPaintLabels(true);
  Dictionary<?, ?> dictionary = slider.getLabelTable();
  if (Objects.nonNull(dictionary)) {
    Enumeration<?> elements = dictionary.elements();
    while (elements.hasMoreElements()) {
      JLabel label = (JLabel) elements.nextElement();
      int v = Integer.parseInt(label.getText());
      if (v > MAXI || v < MINI) {
        label.setForeground(Color.RED);
      }
    }
  }
  slider.getModel().addChangeListener(e -> {
    BoundedRangeModel m = (BoundedRangeModel) e.getSource();
    if (m.getValue() > MAXI) {
      m.setValue(MAXI);
    } else if (m.getValue() < MINI) {
      m.setValue(MINI);
    }
  });
  return slider;
}

References

2019/01/30

aterai

Swap the position of the child components in the JSplitPane

Code

JSplitPane sp = new JSplitPane();
sp.setLeftComponent(new JScrollPane(new JTree()));
sp.setRightComponent(new JScrollPane(new JTable(6, 3)));
sp.setResizeWeight(.4);

JButton button = new JButton("swap");
button.setFocusable(false);
button.addActionListener(e -> {
  Component left = sp.getLeftComponent();
  Component right = sp.getRightComponent();

  // sp.removeAll(); // Divider is also removed
  sp.remove(left);
  sp.remove(right);
  // or: https://stackoverflow.com/questions/4871874/java-problem-with-jsplitpane
  // sp.setLeftComponent(null);
  // sp.setRightComponent(null);

  sp.setLeftComponent(right);
  sp.setRightComponent(left);

  sp.setResizeWeight(1d - sp.getResizeWeight());
  if (check.isSelected()) {
    sp.setDividerLocation(sp.getDividerLocation());
  }
});

References