Google Tag Manager

Showing posts with label JPanel. Show all posts
Showing posts with label JPanel. Show all posts

2023/02/28

Draw a projection-transformed regular hexahedron wireframe in JPanel and rotate it in space by mouse dragging

Code

class Vertex {
  private double dx;
  private double dy;
  private double dz;
  public double vx;
  public double vy;

  protected Vertex(double dx, double dy, double dz) {
    this.dx = dx;
    this.dy = dy;
    this.dz = dz;
    projectionTransformation();
  }

  private void projectionTransformation() {
    double screenDistance = 500d;
    double depth = 1000d;
    double gz = dz + depth;
    this.vx = screenDistance * dx / gz;
    this.vy = screenDistance * dy / gz;
  }

  public void rotateTransformation(double kx, double ky, double kz) {
    double x0 = dx * Math.cos(ky) - dz * Math.sin(ky);
    double y0 = dy;
    double z0 = dx * Math.sin(ky) + dz * Math.cos(ky);
    double y1 = y0 * Math.cos(kx) - z0 * Math.sin(kx);
    double z1 = y0 * Math.sin(kx) + z0 * Math.cos(kx);
    this.dx = x0 * Math.cos(kz) - y1 * Math.sin(kz);
    this.dy = x0 * Math.sin(kz) + y1 * Math.cos(kz);
    this.dz = z1;
    projectionTransformation();
  }
}

// ...
List<Vertex> cube = new ArrayList<>(8);
double sideLength = 100;
cube.add(new Vertex(sideLength, sideLength, sideLength));
cube.add(new Vertex(sideLength, sideLength, -sideLength));
cube.add(new Vertex(-sideLength, sideLength, -sideLength));
cube.add(new Vertex(-sideLength, sideLength, sideLength));
cube.add(new Vertex(sideLength, -sideLength, sideLength));
cube.add(new Vertex(sideLength, -sideLength, -sideLength));
cube.add(new Vertex(-sideLength, -sideLength, -sideLength));
cube.add(new Vertex(-sideLength, -sideLength, sideLength));

// ...
// Projection transformation of each vertex of a regular hexahedron by moving the viewpoint to the origin, the distance to the screen to 500, and the z-axis coordinate value to the back of the screen to 1000.
// Draw a wireframe in the center of the JPanel by connecting the transformed vertex coordinates with Path2D
@Override protected void paintComponent(Graphics g) {
  super.paintComponent(g);
  Graphics2D g2 = (Graphics2D) g.create();
  g2.setRenderingHint(
    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  Path2D path = new Path2D.Double();
  path.moveTo(cube.get(0).vx, cube.get(0).vy);
  path.lineTo(cube.get(1).vx, cube.get(1).vy);
  path.lineTo(cube.get(2).vx, cube.get(2).vy);
  path.lineTo(cube.get(3).vx, cube.get(3).vy);
  path.lineTo(cube.get(0).vx, cube.get(0).vy);
  path.lineTo(cube.get(4).vx, cube.get(4).vy);
  path.lineTo(cube.get(5).vx, cube.get(5).vy);
  path.lineTo(cube.get(6).vx, cube.get(6).vy);
  path.lineTo(cube.get(7).vx, cube.get(7).vy);
  path.lineTo(cube.get(4).vx, cube.get(4).vy);
  path.moveTo(cube.get(1).vx, cube.get(1).vy);
  path.lineTo(cube.get(5).vx, cube.get(5).vy);
  path.moveTo(cube.get(2).vx, cube.get(2).vy);
  path.lineTo(cube.get(6).vx, cube.get(6).vy);
  path.moveTo(cube.get(3).vx, cube.get(3).vy);
  path.lineTo(cube.get(7).vx, cube.get(7).vy);
  Rectangle r = SwingUtilities.calculateInnerArea(this, null);
  g2.setPaint(Color.WHITE);
  g2.fill(r);
  g2.translate(r.getCenterX(), r.getCenterY());
  g2.setPaint(Color.BLACK);
  g2.draw(path);
  g2.dispose();
}

// ...
// Add the following `MouseAdapter` to the `JPanel` that transforms and draws the projection of the regular hexahedron, and rotate each vertex in the space
private class DragRotateHandler extends MouseAdapter {
  private final Cursor defCursor = Cursor.getDefaultCursor();
  private final Cursor hndCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
  private final Point pp = new Point();

  @Override public void mouseDragged(MouseEvent e) {
    Point pt = e.getPoint();
    double rotY = (pt.x - pp.x) * .03;
    double rotX = (pt.y - pp.y) * .03;
    double rotZ = 0d;
    for (Vertex v : cube) {
      v.rotateTransformation(rotX, rotY, rotZ);
    }
    pp.setLocation(pt);
    e.getComponent().repaint();
  }

  @Override public void mousePressed(MouseEvent e) {
    e.getComponent().setCursor(hndCursor);
    pp.setLocation(e.getPoint());
  }

  @Override public void mouseReleased(MouseEvent e) {
    e.getComponent().setCursor(defCursor);
  }
}

References

2023/01/31

Create numbers for a 7-segment digital clock using Path2D

Code

class DigitalNumber {
  private final double isosceles;
  private final double dx;
  private final double dy;
  private final double width;
  private final double height;
  private final Rectangle rect = new Rectangle();
  public static final Color OFF = new Color(0xCC_CC_CC);
  public static final Color ON = Color.DARK_GRAY;
  public static final Color BGC = Color.LIGHT_GRAY;
  private final List<Set<Seg>> numbers = Arrays.asList(
      EnumSet.of(Seg.A, Seg.B, Seg.C, Seg.D, Seg.E, Seg.F),
      EnumSet.of(Seg.B, Seg.C),
      EnumSet.of(Seg.A, Seg.B, Seg.D, Seg.E, Seg.G),
      EnumSet.of(Seg.A, Seg.B, Seg.C, Seg.D, Seg.G),
      EnumSet.of(Seg.B, Seg.C, Seg.F, Seg.G),
      EnumSet.of(Seg.A, Seg.C, Seg.D, Seg.F, Seg.G),
      EnumSet.of(Seg.A, Seg.C, Seg.D, Seg.E, Seg.F, Seg.G),
      EnumSet.of(Seg.A, Seg.B, Seg.C),
      EnumSet.of(Seg.A, Seg.B, Seg.C, Seg.D, Seg.E, Seg.F, Seg.G),
      EnumSet.of(Seg.A, Seg.B, Seg.C, Seg.D, Seg.F, Seg.G));
  private Set<Seg> led = EnumSet.noneOf(Seg.class);

  protected DigitalNumber(double dx, double dy, double isosceles) {
    this.isosceles = isosceles;
    this.dx = dx;
    this.dy = dy;
    this.width = 2d * isosceles;
    this.height = width + isosceles;
    rect.setLocation((int) dx, (int) dy);
    rect.setSize((int) (width + 3 * isosceles), (int) (height * 2));
  }

  public Rectangle getBounds() {
    return rect;
  }

  public void setNumber(int num) {
    led = numbers.get(num);
  }

  public void turnOffNumber() {
    led.clear();
  }

  public void drawNumber(Graphics2D g2) {
    EnumSet.allOf(Seg.class).forEach(s -> {
      g2.setColor(led.contains(s) ? ON : OFF);
      Shape seg = s.getShape(dx, dy, width, height, isosceles);
      g2.fill(seg);
      g2.setColor(BGC);
      g2.draw(seg);
    });
  }
}

enum Seg { A, B, C, D, E, F, G }

References

2022/08/31

Use AffineTransform to place Roman numerals on an analog clock face

Code

private final String[] arabicNumerals = {
  "12", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"
};
private final String[] romanNumerals = {
  "XII", "I", "II", "III", "IIII", "V", "VI", "VII", "VIII", "IX", "X", "XI"
};

private void paintClockNumbers(
    Graphics2D g2, double radius, double hourMarkerLen) {
  AffineTransform at = AffineTransform.getRotateInstance(0d);
  g2.setColor(Color.WHITE);
  Font font = g2.getFont();
  FontRenderContext frc = g2.getFontRenderContext();
  if (isRomanNumerals) {
    AffineTransform si = AffineTransform.getScaleInstance(1d, 2d);
    for (String txt : romanNumerals) {
      Shape s = getTextLayout(txt, font, frc).getOutline(si);
      Rectangle2D r = s.getBounds2D();
      double tx = r.getCenterX();
      double ty = radius - hourMarkerLen - r.getHeight() + r.getCenterY() * .5;
      Shape t = AffineTransform.getTranslateInstance(-tx, -ty)
                               .createTransformedShape(s);
      g2.fill(at.createTransformedShape(t));
      at.rotate(Math.PI / 6d);
    }
  } else {
    Point2D ptSrc = new Point2D.Double();
    for (String txt : arabicNumerals) {
      Shape s = getTextLayout(txt, font, frc).getOutline(null);
      Rectangle2D r = s.getBounds2D();
      double ty = radius - hourMarkerLen - r.getHeight() - r.getCenterY() * .5;
      ptSrc.setLocation(0d, -ty);
      Point2D pt = at.transform(ptSrc, null);
      double dx = pt.getX() - r.getCenterX();
      double dy = pt.getY() - r.getCenterY();
      // g2.drawString(txt, (float) dx, (float) dy);
      g2.fill(AffineTransform.getTranslateInstance(dx, dy)
        .createTransformedShape(s));
      at.rotate(Math.PI / 6d);
    }
  }
}

private static TextLayout getTextLayout(
    String txt, Font font, FontRenderContext frc) {
  return new TextLayout(txt, font, frc);
}

References

2021/05/31

Drawing Analog Clock Hands in JPanel using Timer

Code

class AnalogClock extends JPanel {
  protected LocalTime time = LocalTime.now(ZoneId.systemDefault());
  protected Timer timer = new Timer(200, e -> {
    time = LocalTime.now(ZoneId.systemDefault());
    repaint();
  });
  private transient HierarchyListener listener;

  @Override public void updateUI() {
    removeHierarchyListener(listener);
    super.updateUI();
    listener = e -> {
      if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
        if (e.getComponent().isShowing()) {
          // System.out.println("start");
          timer.start();
        } else {
          // System.out.println("stop");
          timer.stop();
        }
      }
    };
    addHierarchyListener(listener);
  }

  @Override protected void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    Rectangle rect = SwingUtilities.calculateInnerArea(this, null);
    g2.setColor(Color.BLACK);
    g2.fill(rect);
    float radius = Math.min(rect.width, rect.height) / 2f - 10f;
    g2.translate(rect.getCenterX(), rect.getCenterY());

    // Drawing the hour and minute markers
    float hourMarkerLen = radius / 6f - 10f;
    Shape hourMarker = new Line2D.Float(0f, hourMarkerLen - radius, 0f, -radius);
    Shape minuteMarker = new Line2D.Float(0f, hourMarkerLen / 2f - radius, 0f, -radius);
    AffineTransform at = AffineTransform.getRotateInstance(0d);
    g2.setStroke(new BasicStroke(2f));
    g2.setColor(Color.WHITE);
    for (int i = 0; i < 60; i++) {
      if (i % 5 == 0) {
        g2.draw(at.createTransformedShape(hourMarker));
      } else {
        g2.draw(at.createTransformedShape(minuteMarker));
      }
      at.rotate(Math.PI / 30d);
    }

    // Drawing the hour hand
    float hourHandLen = radius / 2f;
    Shape hourHand = new Line2D.Float(0f, 0f, 0f, -hourHandLen);
    double minuteRot = time.getMinute() * Math.PI / 30d;
    double hourRot = time.getHour() * Math.PI / 6d + minuteRot / 12d;
    g2.setStroke(new BasicStroke(8f));
    g2.setPaint(Color.LIGHT_GRAY);
    g2.draw(AffineTransform.getRotateInstance(hourRot).createTransformedShape(hourHand));

    // Drawing the minute hand
    float minuteHandLen = 5f * radius / 6f;
    Shape minuteHand = new Line2D.Float(0f, 0f, 0f, -minuteHandLen);
    g2.setStroke(new BasicStroke(4f));
    g2.setPaint(Color.WHITE);
    g2.draw(AffineTransform.getRotateInstance(minuteRot).createTransformedShape(minuteHand));

    // Drawing the second hand
    float r = radius / 6f;
    float secondHandLen = radius - r;
    Shape secondHand = new Line2D.Float(0f, r, 0f, -secondHandLen);
    double secondRot = time.getSecond() * Math.PI / 30d;
    g2.setPaint(Color.RED);
    g2.setStroke(new BasicStroke(1f));
    g2.draw(AffineTransform.getRotateInstance(secondRot).createTransformedShape(secondHand));
    g2.fill(new Ellipse2D.Float(-r / 4f, -r / 4f, r / 2f, r / 2f));

    g2.dispose();
  }
}

References

2020/09/30

Creating StatusBar with size grips to resize the JFrame

Code

class ResizeWindowListener extends MouseInputAdapter {
  private final Rectangle rect = new Rectangle();
  private final Point startPt = new Point();

  @Override public void mousePressed(MouseEvent e) {
    Component p = SwingUtilities.getRoot(e.getComponent());
    if (p instanceof Window) {
      startPt.setLocation(e.getPoint());
      rect.setBounds(p.getBounds());
    }
  }

  @Override public void mouseDragged(MouseEvent e) {
    Component p = SwingUtilities.getRoot(e.getComponent());
    if (!rect.isEmpty() && p instanceof Window) {
      Point pt = e.getPoint();
      rect.width += pt.x - startPt.x;
      rect.height += pt.y - startPt.y;
      p.setBounds(rect);
    }
  }
}

// Size Grip
// Create a GripIcon with a JLabel and position it at the right end(BorderLayout.EAST) of the StatusBar
class BottomRightCornerLabel extends JLabel {
  private transient MouseInputListener handler;

  protected BottomRightCornerLabel() {
    super(new BottomRightCornerIcon());
  }

  @Override public void updateUI() {
    removeMouseListener(handler);
    removeMouseMotionListener(handler);
    super.updateUI();
    handler = new ResizeWindowListener();
    addMouseListener(handler);
    addMouseMotionListener(handler);
    setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
  }
}

// Grip Icon
// Draw six rectangles to create a Windows 10 like icon
class BottomRightCornerIcon implements Icon {
  private static final Color SQUARE_COLOR = new Color(160, 160, 160, 160);

  @Override public void paintIcon(Component c, Graphics g, int x, int y) {
    int diff = 3;
    Graphics2D g2 = (Graphics2D) g.create();
    g2.translate(getIconWidth() - diff * 3 - 1, getIconHeight() - diff * 3 - 1);

    int firstRow = 0;
    int secondRow = firstRow + diff;
    int thirdRow = secondRow + diff;

    int firstColumn = 0;
    drawSquare(g2, firstColumn, thirdRow);

    int secondColumn = firstColumn + diff;
    drawSquare(g2, secondColumn, secondRow);
    drawSquare(g2, secondColumn, thirdRow);

    int thirdColumn = secondColumn + diff;
    drawSquare(g2, thirdColumn, firstRow);
    drawSquare(g2, thirdColumn, secondRow);
    drawSquare(g2, thirdColumn, thirdRow);

    g2.dispose();
  }

  @Override public int getIconWidth() {
    return 16;
  }

  @Override public int getIconHeight() {
    return 20;
  }

  private void drawSquare(Graphics g, int x, int y) {
    g.setColor(SQUARE_COLOR);
    g.fillRect(x, y, 2, 2);
  }
}

References

2019/10/30

Wraps JPanel child components horizontally and dynamically expands their horizontal spacing

Code

class ScrollableWrapLayout extends FlowLayout {
  private final int fixedHorizontalGap;

  protected ScrollableWrapLayout(int align, int hgap, int vgap) {
    super(align, hgap, vgap);
    fixedHorizontalGap = hgap;
  }

  private int getPreferredHorizontalGap(Container target) {
    Insets insets = target.getInsets();
    int columns = 0;
    int width = target.getWidth();
    if (target.getParent() instanceof JViewport) {
      width = target.getParent().getBounds().width;
    }
    width -= insets.left + insets.right + fixedHorizontalGap * 2;
    for (int i = 0; i < target.getComponentCount(); i++) {
      Component m = target.getComponent(i);
      if (m.isVisible()) {
        Dimension d = m.getPreferredSize();
        if (width - d.width - fixedHorizontalGap < 0) {
          columns = i;
          break;
        }
        width -= d.width + fixedHorizontalGap;
      }
    }
    return fixedHorizontalGap + width / columns;
  }

  @Override public void layoutContainer(Container target) {
    setHgap(getPreferredHorizontalGap(target));
    super.layoutContainer(target);
  }

  @Override public Dimension preferredLayoutSize(Container target) {
    Dimension dim = super.preferredLayoutSize(target);
    synchronized (target.getTreeLock()) {
      if (target.getParent() instanceof JViewport) {
        dim.width = target.getParent().getBounds().width;
        for (int i = 0; i < target.getComponentCount(); i++) {
          Component m = target.getComponent(i);
          if (m.isVisible()) {
            Dimension d = m.getPreferredSize();
            dim.height = Math.max(dim.height, d.height + m.getY());
          }
        }
      }
      return dim;
    }
  }
}

References

2019/05/28

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 - radius;
    for (int xidx = 0; xidx < SIZE; xidx++) {
      float x = xidx - radius;
      double theta = Math.atan2(y, x) - 3d * Math.PI / 2d;
      if (theta < 0) {
        theta += 2d * Math.PI;
      }
      double r = Math.hypot(x, y); // 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

2017/05/30

Use JComboBox as JTree's node cell editor

Code

class PluginCellEditor extends DefaultCellEditor {
  private final PluginPanel panel;
  private transient Node node;

  public PluginCellEditor(JComboBox<String> comboBox) {
    super(comboBox);
    panel = new PluginPanel(comboBox);
  }

  @Override public Component getTreeCellEditorComponent(
      JTree tree, Object value, boolean selected, boolean expanded,
      boolean leaf, int row) {
    Node node = panel.extractNode(value);
    panel.setContents(node);
    this.node = node;
    return panel;
  }

  @Override public Object getCellEditorValue() {
    Object o = super.getCellEditorValue();
    if (node == null) {
      return o;
    }
    DefaultComboBoxModel<String> m =
        (DefaultComboBoxModel<String>) panel.comboBox.getModel();
    Node n = new Node(panel.pluginName.getText(), node.plugins);
    n.setSelectedPluginIndex(m.getIndexOf(o));
    return n;
  }

  @Override public boolean isCellEditable(EventObject e) {
    Object source = e.getSource();
    if (!(source instanceof JTree) || !(e instanceof MouseEvent)) {
      return false;
    }
    JTree tree = (JTree) source;
    MouseEvent me = (MouseEvent) e;
    TreePath path = tree.getPathForLocation(me.getX(), me.getY());
    if (path == null) {
      return false;
    }
    Object node = path.getLastPathComponent();
    if (!(node instanceof DefaultMutableTreeNode)) {
      return false;
    }
    Rectangle r = tree.getPathBounds(path);
    if (r == null) {
      return false;
    }
    Dimension d = panel.getPreferredSize();
    r.setSize(new Dimension(d.width, r.height));
    if (r.contains(me.getX(), me.getY())) {
      showComboPopup(tree, me);
      return true;
    }
    return delegate.isCellEditable(e);
  }

  private void showComboPopup(final JTree tree, final MouseEvent me) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        Point pt = SwingUtilities.convertPoint(tree, me.getPoint(), panel);
        Component o = SwingUtilities.getDeepestComponentAt(panel, pt.x, pt.y);
        if (o instanceof JComboBox) {
          panel.comboBox.showPopup();
        } else if (o != null) {
          Container c = SwingUtilities.getAncestorOfClass(
              JComboBox.class, (Component) o);
          if (c instanceof JComboBox) {
            panel.comboBox.showPopup();
          }
        }
      }
    });
  }
}

References

2015/06/30

An Image inside a JScrollPane zooming by mouse wheel and panning by mouse click to drag

Code

class ZoomAndPanePanel extends JPanel {
  private final AffineTransform zoomTransform = new AffineTransform();
  private final transient Image img;
  private final Rectangle imgrect;
  private transient ZoomHandler handler;
  private transient DragScrollListener listener;

  protected ZoomAndPanePanel(Image img) {
    super();
    this.img = img;
    this.imgrect = new Rectangle(img.getWidth(this), img.getHeight(this));
  }

  @Override protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setPaint(new Color(0x55_FF_00_00, true));
    Rectangle r = new Rectangle(500, 140, 150, 150);

    // use: AffineTransform#concatenate(...) and Graphics2D#setTransform(...)
    // AffineTransform at = g2.getTransform();
    // at.concatenate(zoomTransform);
    // g2.setTransform(at);
    // g2.drawImage(img, 0, 0, this);
    // g2.fill(r);

    // or use: Graphics2D#drawImage(Image, AffineTransform, ImageObserver)
    g2.drawImage(img, zoomTransform, this);
    // or: g2.drawRenderedImage((RenderedImage) img, zoomTransform);
    g2.fill(zoomTransform.createTransformedShape(r));

    // BAD EXAMPLE
    // g2.setTransform(zoomTransform);
    // g2.drawImage(img, 0, 0, this);

    g2.dispose();
  }

  @Override public Dimension getPreferredSize() {
    Rectangle r = zoomTransform.createTransformedShape(imgrect).getBounds();
    return new Dimension(r.width, r.height);
  }

  @Override public void updateUI() {
    removeMouseListener(listener);
    removeMouseMotionListener(listener);
    removeMouseWheelListener(handler);
    super.updateUI();
    listener = new DragScrollListener();
    addMouseListener(listener);
    addMouseMotionListener(listener);
    handler = new ZoomHandler();
    addMouseWheelListener(handler);
  }

  protected class ZoomHandler extends MouseAdapter {
    private static final double ZOOM_MULTIPLICATION_FACTOR = 1.2;
    private static final int MIN_ZOOM = -10;
    private static final int MAX_ZOOM = 10;
    private static final int EXTENT = 1;
    private final BoundedRangeModel zoomRange = new DefaultBoundedRangeModel(
        0, EXTENT, MIN_ZOOM, MAX_ZOOM + EXTENT);
    @Override public void mouseWheelMoved(MouseWheelEvent e) {
      int dir = e.getWheelRotation();
      int z = zoomRange.getValue();
      zoomRange.setValue(z + EXTENT * (dir > 0 ? -1 : 1));
      if (z != zoomRange.getValue()) {
        Component c = e.getComponent();
        Container p = SwingUtilities.getAncestorOfClass(JViewport.class, c);
        if (p instanceof JViewport) {
          JViewport vport = (JViewport) p;
          Rectangle ovr = vport.getViewRect();
          double s = dir > 0 ? 1d / ZOOM_MULTIPLICATION_FACTOR
                             : ZOOM_MULTIPLICATION_FACTOR;
          zoomTransform.scale(s, s);
          // double s = 1d + zoomRange.getValue() * .1;
          // zoomTransform.setToScale(s, s);
          Rectangle nvr = AffineTransform.getScaleInstance(s, s)
                                         .createTransformedShape(ovr)
                                         .getBounds();
          Point vp = nvr.getLocation();
          vp.translate(
              (nvr.width - ovr.width) / 2,
              (nvr.height - ovr.height) / 2);
          vport.setViewPosition(vp);
          c.revalidate();
          c.repaint();
        }
      }
    }
  }
}

References

2014/12/31

Draggable and sortable JPanels

Code

class RearrangingHandler extends MouseAdapter {
  private static final Rectangle R1 = new Rectangle();
  private static final Rectangle R2 = new Rectangle();
  private final Rectangle prevRect = new Rectangle();
  private final int gestureMotionThreshold = DragSource.getDragThreshold();
  private final JWindow window = new JWindow();
  private final Point startPt = new Point();
  private int index = -1;
  private Component draggingComponent;
  private Component gap;
  private final Point dragOffset = new Point();

  public RearrangingHandler() {
    super();
    window.setBackground(new Color(0x0, true));
  }

  @Override public void mousePressed(MouseEvent e) {
    if (((JComponent) e.getComponent()).getComponentCount() <= 1) {
      startPt.setLocation(0, 0);
    } else {
      startPt.setLocation(e.getPoint());
    }
  }

  private void startDragging(JComponent parent, Point pt) {
    Component c = parent.getComponentAt(pt);
    index = parent.getComponentZOrder(c);
    if (Objects.equals(c, parent) || index < 0) {
      return;
    }
    draggingComponent = c;
    Dimension d = draggingComponent.getSize();

    Point dp = draggingComponent.getLocation();
    dragOffset.setLocation(pt.x - dp.x, pt.y - dp.y);

    gap = Box.createRigidArea(d);
    swapComponentLocation(parent, c, gap, index);

    window.add(draggingComponent);
    // window.setSize(d);
    window.pack();

    updateWindowLocation(pt, parent);
    window.setVisible(true);
  }

  private void updateWindowLocation(Point pt, JComponent parent) {
    if (window.isVisible() && Objects.nonNull(draggingComponent)) {
      Point p = new Point(pt.x - dragOffset.x, pt.y - dragOffset.y);
      SwingUtilities.convertPointToScreen(p, parent);
      window.setLocation(p);
    }
  }

  private int getTargetIndex(Rectangle r, Point pt, int i) {
    int ht2 = (int) (.5 + r.height * .5);
    R1.setBounds(r.x, r.y, r.width, ht2);
    R2.setBounds(r.x, r.y + ht2, r.width, ht2);
    if (R1.contains(pt)) {
      prevRect.setBounds(R1);
      return i - 1 > 0 ? i : 0;
    } else if (R2.contains(pt)) {
      prevRect.setBounds(R2);
      return i;
    }
    return -1;
  }

  private static void swapComponentLocation(
      Container parent, Component remove, Component add, int idx) {
    parent.remove(remove);
    parent.add(add, idx);
    parent.revalidate();
    parent.repaint();
  }

  @Override public void mouseDragged(MouseEvent e) {
    Point pt = e.getPoint();
    JComponent parent = (JComponent) e.getComponent();
    if (Objects.isNull(draggingComponent)) {
      if (startPt.distance(pt) > gestureMotionThreshold) {
        startDragging(parent, pt);
      }
      return;
    }
    updateWindowLocation(pt, parent);

    if (prevRect.contains(pt)) {
      return;
    }
    for (int i = 0; i < parent.getComponentCount(); i++) {
      Component c = parent.getComponent(i);
      Rectangle r = c.getBounds();
      if (Objects.equals(c, gap) && r.contains(pt)) {
        return;
      }
      int tgt = getTargetIndex(r, pt, i);
      if (tgt >= 0) {
        swapComponentLocation(parent, gap, gap, tgt);
        return;
      }
    }
    // System.out.println("outer");
    parent.remove(gap);
    parent.revalidate();
  }

  @Override public void mouseReleased(MouseEvent e) {
    startPt.setLocation(0, 0);
    dragOffset.setLocation(0, 0);
    prevRect.setBounds(0, 0, 0, 0);
    window.setVisible(false);

    Point pt = e.getPoint();
    JComponent parent = (JComponent) e.getComponent();
    Component cmp = draggingComponent;
    draggingComponent = null;

    for (int i = 0; i < parent.getComponentCount(); i++) {
      Component c = parent.getComponent(i);
      if (Objects.equals(c, gap)) {
        swapComponentLocation(parent, gap, cmp, i);
        return;
      }
      int tgt = getTargetIndex(c.getBounds(), pt, i);
      if (tgt >= 0) {
        swapComponentLocation(parent, gap, cmp, tgt);
        return;
      }
    }
    if (parent.getParent().getBounds().contains(pt)) {
      swapComponentLocation(parent, gap, cmp, parent.getComponentCount());
    } else {
      swapComponentLocation(parent, gap, cmp, index);
    }
  }
}

References

2014/02/26

Translucent JFrame repaint

Code

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
JLabel label = new JLabel(LocalTime.now().format(formatter), SwingConstants.CENTER);
Timer timer = new Timer(100, null);
timer.addActionListener(e -> {
  label.setText(LocalTime.now().format(formatter));
  Container parent = SwingUtilities.getUnwrappedParent(label);
  if (Objects.nonNull(parent) && parent.isOpaque()) {
    repaintWindowAncestor(label);
  }
});
// ...
private void repaintWindowAncestor(JComponent c) {
  JRootPane root = c.getRootPane();
  if (root == null) {
    return;
  }
  Rectangle r = SwingUtilities.convertRectangle(c, c.getBounds(), root);
  root.repaint(r.x, r.y, r.width, r.height);
}

References

2013/12/09

Breadcrumb Navigation with JRadioButton

Code

private static JComponent makeBreadcrumbList(int overlap, List<String> list) {
  JPanel p = new JPanel(new FlowLayout(FlowLayout.LEADING, -overlap, 0));
  p.setBorder(BorderFactory.createEmptyBorder(4, overlap + 4, 4, 4));
  p.setOpaque(false);
  ButtonGroup bg = new ButtonGroup();
  for (String title: list) {
    AbstractButton b = makeButton(title, Color.PINK);
    p.add(b);
    bg.add(b);
  }
  return p;
}

References

2012/05/25

Creating a Custom Layout Manager

Code

final double A2 = 4.0;
panel2.setLayout(new FlowLayout() {
  @Override public void layoutContainer(Container target) {
    synchronized(target.getTreeLock()) {
      Insets insets = target.getInsets();
      int nmembers  = target.getComponentCount();
      if(nmembers<=0) return;
      int vgap = getVgap();
      int hgap = getHgap();
      int rowh = (target.getHeight()-(insets.top+insets.bottom+vgap*2))/nmembers;
      int x = insets.left + hgap;
      int y = insets.top  + vgap;
      for(int i=0;i < nmembers; i++) {
        Component m = target.getComponent(i);
        if(m.isVisible()) {
          Dimension d = m.getPreferredSize();
          m.setSize(d.width, d.height);
          m.setLocation(x, y);
          y += (vgap + Math.min(rowh, d.height));
          x = (int)(A2 * Math.sqrt(y));
        }
      }
    }
  }
});

References

2011/03/10

CheckBoxes in JTable Cell

Code

class CheckBoxesPanel extends JPanel {
  private static final String OSNAME = System.getProperty("os.name");
  protected final String[] title = {"r", "w", "x"};
  public JCheckBox[] buttons;
  public CheckBoxesPanel() {
    super();
    setOpaque(false);
    setBackground(new Color(0x0, true));
    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
    initButtons();
  }

  protected void initButtons() {
    buttons = new JCheckBox[title.length];
    for (int i = 0; i < buttons.length; i++) {
      JCheckBox b = new JCheckBox(title[i]);
      b.setOpaque(false);
      b.setFocusable(false);
      b.setRolloverEnabled(false);
      b.setBackground(new Color(0x0, true));
      buttons[i] = b;
      add(b);
      add(Box.createHorizontalStrut(5));
    }
  }

  protected void updateButtons(Object v) {
    if ("Windows 7".equals(OSNAME)) { //Windows aero?
      removeAll();
      initButtons();
    }
    Integer i = (Integer) (v == null ? 0 : v);
    buttons[0].setSelected((i & (1 << 2)) != 0);
    buttons[1].setSelected((i & (1 << 1)) != 0);
    buttons[2].setSelected((i & (1 << 0)) != 0);
  }
}

class CheckBoxesRenderer extends CheckBoxesPanel
                         implements TableCellRenderer, Serializable {
  public CheckBoxesRenderer() {
    super();
    setName("Table.cellRenderer");
  }

  @Override public Component getTableCellRendererComponent(JTable table,
      Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    updateButtons(value);
    return this;
  }

  public static class UIResource extends CheckBoxesRenderer implements UIResource {}
}

class CheckBoxesEditor extends CheckBoxesPanel
                       implements TableCellEditor, Serializable {
  public CheckBoxesEditor() {
    ActionListener al = new ActionListener() {
      @Override public void actionPerformed(ActionEvent e) {
        fireEditingStopped();
      }
    };
    ActionMap am = getActionMap();
    for (int i = 0; i < buttons.length; i++) {
      final JCheckBox b = buttons[i];
      b.addActionListener(al);
      am.put(title[i], new AbstractAction(title[i]) {
        @Override public void actionPerformed(ActionEvent e) {
          b.setSelected(!b.isSelected());
          fireEditingStopped();
        }
      });
    }
    InputMap im = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0), title[0]);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), title[1]);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, 0), title[2]);
  }

  @Override public Component getTableCellEditorComponent(JTable table,
        Object value, boolean isSelected, int row, int column) {
    updateButtons(value);
    return this;
  }

  @Override public Object getCellEditorValue() {
    int i = 0;
    if (buttons[0].isSelected()) i |= 1 << 2;
    if (buttons[1].isSelected()) i |= 1 << 1;
    if (buttons[2].isSelected()) i |= 1 << 0;
    return i;
  }

  // Copied from AbstractCellEditor
  protected EventListenerList listenerList = new EventListenerList();
  transient protected ChangeEvent changeEvent = null;
  // ...

References

2010/05/10

Custom Decorated TitleBar JFrame

Code

class ResizeWindowListener extends MouseAdapter {
  private Rectangle startSide = null;
  private final JFrame frame;
  public ResizeWindowListener(JFrame frame) {
    this.frame = frame;
  }

  @Override public void mousePressed(MouseEvent e) {
    startSide = frame.getBounds();
  }

  @Override public void mouseDragged(MouseEvent e) {
    if (startSide == null) return;
    Component c = e.getComponent();
    if (c == topleft) {
      startSide.y += e.getY();
      startSide.height -= e.getY();
      startSide.x += e.getX();
      startSide.width -= e.getX();
    } else if (c == top) {
      startSide.y += e.getY();
      startSide.height -= e.getY();
    } else if (c == topright) {
      startSide.y += e.getY();
      startSide.height -= e.getY();
      startSide.width += e.getX();
    } else if (c == left) {
      startSide.x += e.getX();
      startSide.width -= e.getX();
    } else if (c == right) {
      startSide.width += e.getX();
    } else if (c == bottomleft) {
      startSide.height += e.getY();
      startSide.x += e.getX();
      startSide.width -= e.getX();
    } else if (c == bottom) {
      startSide.height += e.getY();
    } else if (c == bottomright) {
      startSide.height += e.getY();
      startSide.width += e.getX();
    }
    frame.setBounds(startSide);
  }
}

References

2009/10/06

Multiple JButtons in a JTable cell

Code

class ButtonsPanel extends JPanel {
  public final List<JButton> buttons =
    Arrays.asList(new JButton("view"), new JButton("edit"));
  public ButtonsPanel() {
    super();
    setOpaque(true);
    for (JButton b: buttons) {
      b.setFocusable(false);
      b.setRolloverEnabled(false);
      add(b);
    }
  }
}

class ButtonsRenderer extends ButtonsPanel
                      implements TableCellRenderer {
  public ButtonsRenderer() {
    super();
    setName("Table.cellRenderer");
  }

  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected,
      boolean hasFocus, int row, int column) {
    Color bgc = isSelected ? table.getSelectionBackground()
                           : table.getBackground()
    setBackground(bgc);
    return this;
  }
}

class ButtonsEditor extends ButtonsPanel
                    implements TableCellEditor {
  public ButtonsEditor(final JTable table) {
    super();
    // ---->
    // DEBUG: view button click
    //   -> control key down + edit button(same cell) press
    //   -> remain selection color
    MouseListener ml = new MouseAdapter() {
      @Override public void mousePressed(MouseEvent e) {
        ButtonModel m = ((JButton) e.getSource()).getModel();
        if (m.isPressed() && table.isRowSelected(table.getEditingRow())
                          && e.isControlDown()) {
          setBackground(table.getBackground());
        }
      }
    };
    buttons.get(0).addMouseListener(ml);
    buttons.get(1).addMouseListener(ml);
    // <----

    buttons.get(0).addActionListener(new ActionListener() {
      @Override public void actionPerformed(ActionEvent e) {
        fireEditingStopped();
        JOptionPane.showMessageDialog(table, "Viewing");
      }
    });

    buttons.get(1).addActionListener(new ActionListener() {
      @Override public void actionPerformed(ActionEvent e) {
        int row = table.convertRowIndexToModel(table.getEditingRow());
        Object o = table.getModel().getValueAt(row, 0);
        fireEditingStopped();
        JOptionPane.showMessageDialog(table, "Editing: " + o);
      }
    });

    addMouseListener(new MouseAdapter() {
      @Override public void mousePressed(MouseEvent e) {
        fireEditingStopped();
      }
    });
  }

  @Override public Component getTableCellEditorComponent(JTable table,
        Object value, boolean isSelected, int row, int column) {
    this.setBackground(table.getSelectionBackground());
    return this;
  }

  @Override public Object getCellEditorValue() {
    return "";
  }

  // Copied from AbstractCellEditor
  // protected EventListenerList listenerList = new EventListenerList();
  transient protected ChangeEvent changeEvent = null;
  @Override public boolean isCellEditable(java.util.EventObject e) {
    return true;
  }
// ...

References

2008/08/13

Multi Column JComboBox

Code

class MultiColumnCellRenderer extends JPanel implements ListCellRenderer {
  private final JLabel leftLabel = new JLabel();
  private final JLabel rightLabel;

  public MultiColumnCellRenderer(int rightWidth) {
    super(new BorderLayout());
    this.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));

    leftLabel.setOpaque(false);
    leftLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0));

    final Dimension dim = new Dimension(rightWidth, 0);
    rightLabel = new JLabel() {
      @Override public Dimension getPreferredSize() {
        return dim;
      }
    };
    rightLabel.setOpaque(false);
    rightLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
    rightLabel.setForeground(Color.GRAY);
    rightLabel.setHorizontalAlignment(SwingConstants.RIGHT);

    this.add(leftLabel);
    this.add(rightLabel, BorderLayout.EAST);
  }

  @Override public Component getListCellRendererComponent(
      JList list, Object value, int index,
      boolean isSelected, boolean cellHasFocus) {
    LRItem item = (LRItem) value;
    leftLabel.setText(item.getLeftText());
    rightLabel.setText(item.getRightText());

    leftLabel.setFont(list.getFont());
    rightLabel.setFont(list.getFont());

    if (index < 0) {
      leftLabel.setForeground(list.getForeground());
      this.setOpaque(false);
    } else {
      leftLabel.setForeground(
          isSelected ? list.getSelectionForeground() : list.getForeground());
      this.setBackground(
          isSelected ? list.getSelectionBackground() : list.getBackground());
      this.setOpaque(true);
    }
    return this;
  }

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

  @Override public void updateUI() {
    super.updateUI();
    this.setName("List.cellRenderer");
  }
}

References