Google Tag Manager

2019/12/30

aterai

Create a speedometer by changing the shape of JProgressBar to a donut-shaped semicircle

Code

class SolidGaugeUI extends BasicProgressBarUI {
  private final int[] pallet;
  private final double extent;

  protected SolidGaugeUI(int range, double extent) {
    super();
    this.pallet = makeGradientPallet(range);
    this.extent = extent;
  }

  @Override public void paint(Graphics g, JComponent c) {
    Rectangle rect = SwingUtilities.calculateInnerArea(progressBar, null);
    if (rect.isEmpty()) {
      return;
    }
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    // double extent = -150d;
    double start = 90d + extent * .5;
    double degree = extent * progressBar.getPercentComplete();
    double or = Math.min(rect.width, rect.height);
    double cx = rect.getCenterX();
    double cy = rect.getMaxY();
    double sz = or * 2d;
    double ir = or * .6;
    Shape inner = new Arc2D.Double(cx - ir, cy - ir, ir * 2d, ir * 2d, start, -extent, Arc2D.PIE);
    Shape outer = new Arc2D.Double(cx - or, cy - or, sz, sz, start, -extent, Arc2D.PIE);
    Shape sector = new Arc2D.Double(cx - or, cy - or, sz, sz, start, -degree, Arc2D.PIE);

    Area foreground = new Area(sector);
    Area background = new Area(outer);
    Area hole = new Area(inner);

    foreground.subtract(hole);
    background.subtract(hole);

    // Draw the track
    g2.setPaint(new Color(0xDD_DD_DD));
    g2.fill(background);

    // Draw the circular sector
    g2.setPaint(getColorFromPallet(pallet, progressBar.getPercentComplete()));
    g2.fill(foreground);

    // Draw ...
    Font font = progressBar.getFont();
    float fsz = font.getSize2D();
    float min = (float) (cx - or - fsz);
    float max = (float) (cx + or + 4d);
    g2.setPaint(progressBar.getForeground());
    g2.drawString(Objects.toString(progressBar.getMinimum()), min, (float) cy);
    g2.drawString(Objects.toString(progressBar.getMaximum()), max, (float) cy);

    // Deal with possible text painting
    if (progressBar.isStringPainted()) {
      float h = (float) cy - fsz;
      String str = String.format("%d", progressBar.getValue());
      float vx = (float) cx - g2.getFontMetrics().stringWidth(str) * .5f;
      g2.drawString(str, vx, h);
      float ksz = fsz * .66f;
      g2.setFont(font.deriveFont(ksz));
      String kmh = "㎞/h";
      float tx = (float) cx - g2.getFontMetrics().stringWidth(kmh) * .5f;
      g2.drawString(kmh, tx, h + ksz);
    }
    g2.dispose();
  }

  private static int[] makeGradientPallet(int range) {
    BufferedImage image = new BufferedImage(range, 1, BufferedImage.TYPE_INT_RGB);
    Graphics2D g2 = image.createGraphics();
    Point2D start = new Point2D.Float();
    Point2D end = new Point2D.Float(range - 1f, 0f);
    float[] dist = {0f, .8f, .9f, 1f};
    Color[] colors = {Color.GREEN, Color.YELLOW, Color.ORANGE, Color.RED};
    g2.setPaint(new LinearGradientPaint(start, end, dist, colors));
    g2.fillRect(0, 0, range, 1);
    g2.dispose();

    int width = image.getWidth(null);
    int[] pallet = new int[width];
    PixelGrabber pg = new PixelGrabber(image, 0, 0, width, 1, pallet, 0, width);
    try {
      pg.grabPixels();
    } catch (InterruptedException ex) {
      ex.printStackTrace();
      Toolkit.getDefaultToolkit().beep();
      Thread.currentThread().interrupt();
    }
    return pallet;
  }

  private static Color getColorFromPallet(int[] pallet, double pos) {
    if (pos < 0d || pos > 1d) {
      throw new IllegalArgumentException("Parameter outside of expected range");
    }
    int i = (int) (pallet.length * pos);
    int max = pallet.length - 1;
    int index = Math.min(Math.max(i, 0), max);
    return new Color(pallet[index] & 0x00_FF_FF_FF);
  }
}

References

2019/11/29

aterai

Change the word selection behavior by double clicking the mouse in JTextArea

Code

class SelectWordCaret extends DefaultCaret {
  private SelectingMode selectingMode = SelectingMode.CHAR;
  private int p0; // = Math.min(getDot(), getMark());
  private int p1; // = Math.max(getDot(), getMark());

  @Override public void mousePressed(MouseEvent e) {
    super.mousePressed(e);
    int clickCount = e.getClickCount();
    if (SwingUtilities.isLeftMouseButton(e) && !e.isConsumed()) {
      if (clickCount == 2) {
        selectingMode = SelectingMode.WORD;
        p0 = Math.min(getDot(), getMark());
        p1 = Math.max(getDot(), getMark());
      } else if (clickCount >= 3) {
        selectingMode = SelectingMode.ROW;
        JTextComponent target = getComponent();
        int offs = target.getCaretPosition();
        try {
          p0 = Utilities.getRowStart(target, offs);
          p1 = Utilities.getRowEnd(target, offs);
          setDot(p0);
          moveDot(p1);
        } catch (BadLocationException ex) {
          UIManager.getLookAndFeel().provideErrorFeedback(target);
        }
      }
    } else {
      selectingMode = SelectingMode.CHAR;
    }
  }

  @Override public void mouseDragged(MouseEvent e) {
    if (!e.isConsumed() && SwingUtilities.isLeftMouseButton(e)) {
      if (selectingMode == SelectingMode.WORD) {
        continuouslySelectWords(e);
      } else if (selectingMode == SelectingMode.ROW) {
        continuouslySelectRows(e);
      }
    } else {
      super.mouseDragged(e);
    }
  }

  // If you continue to drag the mouse after selecting a word with double press:
  private void continuouslySelectWords(MouseEvent e) {
    Position.Bias[] biasRet = new Position.Bias[1];
    JTextComponent c = getComponent();
    int pos = getCaretPositionByLocation(c, e.getPoint(), biasRet);
    try {
      if (p0 < pos && pos < p1) {
        setDot(p0);
        moveDot(p1, biasRet[0]);
      } else if (p1 < pos) {
        setDot(p0);
        moveDot(Utilities.getWordEnd(c, pos), biasRet[0]);
      } else if (p0 > pos) {
        setDot(p1);
        moveDot(Utilities.getWordStart(c, pos), biasRet[0]);
      }
    } catch (BadLocationException ex) {
      UIManager.getLookAndFeel().provideErrorFeedback(c);
    }
  }

  // If you drag the mouse continuously after selecting the entire line in triple press:
  private void continuouslySelectRows(MouseEvent e) {
    Position.Bias[] biasRet = new Position.Bias[1];
    JTextComponent c = getComponent();
    int pos = getCaretPositionByLocation(c, e.getPoint(), biasRet);
    try {
      if (p0 < pos && pos < p1) {
        setDot(p0);
        moveDot(p1, biasRet[0]);
      } else if (p1 < pos) {
        setDot(p0);
        moveDot(Utilities.getRowEnd(c, pos), biasRet[0]);
      } else if (p0 > pos) {
        setDot(p1);
        moveDot(Utilities.getRowStart(c, pos), biasRet[0]);
      }
    } catch (BadLocationException ex) {
      UIManager.getLookAndFeel().provideErrorFeedback(c);
    }
  }
  // ...
}

References

2019/10/30

aterai

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/09/30

aterai

Change drawing area of selected highlight in JEditorPane

Code

class ParagraphMarkHighlightPainter extends DefaultHighlightPainter {
  protected ParagraphMarkHighlightPainter(Color color) {
    super(color);
  }

  @Override public Shape paintLayer(
      Graphics g, int offs0, int offs1,
      Shape bounds, JTextComponent c, View view) {
    Shape s = super.paintLayer(g, offs0, offs1, bounds, c, view);
    Rectangle r = s.getBounds();
    if (r.width - 1 <= 0) {
      g.fillRect(r.x + r.width, r.y, r.width + r.height / 2, r.height);
    }
    return s;
  }
}

class WholeLineHighlightPainter extends DefaultHighlightPainter {
  protected WholeLineHighlightPainter(Color color) {
    super(color);
  }

  @Override public Shape paintLayer(
      Graphics g, int offs0, int offs1,
      Shape bounds, JTextComponent c, View view) {
    Rectangle rect = bounds.getBounds();
    rect.width = c.getSize().width;
    return super.paintLayer(g, offs0, offs1, rect, c, view);
  }
}

References

2019/08/31

aterai

Create a minimap of JEditorPane with ImageIcon and overlay it on JScrollPane using LayoutManager

Code

private final JEditorPane editor = new JEditorPane();
private final JScrollPane scroll = new JScrollPane(editor);
private final JLabel label = new JLabel() {
  private transient MouseInputListener handler;
  @Override public void updateUI() {
    removeMouseListener(handler);
    removeMouseMotionListener(handler);
    super.updateUI();
    handler = new MiniMapHandler();
    addMouseListener(handler);
    addMouseMotionListener(handler);
  }

  @Override public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Container c = SwingUtilities.getAncestorOfClass(JViewport.class, editor);
    if (!(c instanceof JViewport) || editor == null) {
      return;
    }
    JViewport vport = (JViewport) c;
    Rectangle vrect = vport.getBounds(); // scroll.getViewportBorderBounds();
    Rectangle erect = editor.getBounds();
    Rectangle crect = SwingUtilities.calculateInnerArea(this, new Rectangle());

    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
    double sy = crect.getHeight() / erect.getHeight();
    AffineTransform at = AffineTransform.getScaleInstance(1d, sy);

    // paint Thumb
    Rectangle thumbRect = new Rectangle(vrect);
    thumbRect.y = vport.getViewPosition().y;
    Rectangle r = at.createTransformedShape(thumbRect).getBounds();
    int y = crect.y + r.y;
    g2.setColor(THUMB_COLOR);
    g2.fillRect(0, y, crect.width, r.height);
    g2.setColor(THUMB_COLOR.darker());
    g2.drawRect(0, y, crect.width - 1, r.height - 1);
    g2.dispose();
  }
};

private class MiniMapHandler extends MouseInputAdapter {
  @Override public void mousePressed(MouseEvent e) {
    processMiniMapMouseEvent(e);
  }

  @Override public void mouseDragged(MouseEvent e) {
    processMiniMapMouseEvent(e);
  }

  protected final void processMiniMapMouseEvent(MouseEvent e) {
    Point pt = e.getPoint();
    Component c = (Component) e.getComponent();
    BoundedRangeModel m = scroll.getVerticalScrollBar().getModel();
    int brm = m.getMaximum() - m.getMinimum();
    int iv = (int) (.5 - m.getExtent() * .5 + pt.y * brm / (double) c.getHeight());
    m.setValue(iv);
  }
}
// ...
scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scroll.getVerticalScrollBar().getModel().addChangeListener(e -> label.repaint());

JPanel pp = new JPanel(new BorderLayout(0, 0));
pp.add(label, BorderLayout.NORTH);
JScrollPane minimap = new JScrollPane(pp);
minimap.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
minimap.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

Box box = Box.createHorizontalBox();
box.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
box.add(Box.createHorizontalGlue());
box.add(button);

JPanel p = new JPanel() {
  @Override public boolean isOptimizedDrawingEnabled() {
    return false;
  }
};
p.setLayout(new BorderLayout(0, 0) {
  @Override public void layoutContainer(Container parent) {
    synchronized (parent.getTreeLock()) {
      Insets insets = parent.getInsets();
      int width = parent.getWidth();
      int height = parent.getHeight();
      int top = insets.top;
      int bottom = height - insets.bottom;
      int left = insets.left;
      int right = width - insets.right;
      Component ec = getLayoutComponent(parent, BorderLayout.EAST);
      if (Objects.nonNull(ec)) {
        Dimension d = ec.getPreferredSize();
        JScrollBar vsb = scroll.getVerticalScrollBar();
        int vsw = vsb.isVisible() ? vsb.getSize().width : 0;
        ec.setBounds(right - d.width - vsw, top, d.width, bottom - top);
      }
      Component cc = getLayoutComponent(parent, BorderLayout.CENTER);
      if (Objects.nonNull(cc)) {
        cc.setBounds(left, top, right - left, bottom - top);
      }
    }
  }
});
p.add(minimap, BorderLayout.EAST);
p.add(scroll);

References