Google Tag Manager

2022/11/30

Rotate JTableHeader column title string to display vertically

Code

class VerticalTableHeaderRenderer implements TableCellRenderer {
  private static final String ASCENDING = "Table.ascendingSortIcon";
  private static final String DESCENDING = "Table.descendingSortIcon";
  private final Icon ascendingIcon;
  private final Icon descendingIcon;
  private final EmptyIcon emptyIcon = new EmptyIcon();
  private final JPanel intermediate = new JPanel();
  private final JLabel label = new JLabel("", null, SwingConstants.LEADING);

  protected VerticalTableHeaderRenderer() {
    ascendingIcon = UIManager.getLookAndFeelDefaults().getIcon(ASCENDING);
    descendingIcon = UIManager.getLookAndFeelDefaults().getIcon(DESCENDING);
    emptyIcon.width = ascendingIcon.getIconWidth();
    emptyIcon.height = ascendingIcon.getIconHeight();
  }

  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected, boolean hasFocus,
      int row, int column) {
    UIManager.put(ASCENDING, emptyIcon);
    UIManager.put(DESCENDING, emptyIcon);
    TableCellRenderer r = table.getTableHeader().getDefaultRenderer();
    Component c = r.getTableCellRendererComponent(
      table, value, isSelected, hasFocus, row, column);
    UIManager.put(ASCENDING, ascendingIcon);
    UIManager.put(DESCENDING, descendingIcon);
    if (c instanceof JLabel) {
      JLabel l = (JLabel) c;
      l.setHorizontalAlignment(SwingConstants.CENTER);
      SortOrder sortOrder = getColumnSortOrder(table, column);
      Icon sortIcon;
      switch (sortOrder) {
        case ASCENDING:
          sortIcon = ascendingIcon;
          break;
        case DESCENDING:
          sortIcon = descendingIcon;
          break;
        default: // case UNSORTED:
          sortIcon = emptyIcon;
          break;
      }
      label.setText(l.getText());
      label.setIcon(new RotateIcon(sortIcon, 90));
      label.setHorizontalTextPosition(SwingConstants.LEFT);
      label.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
      // l.setIcon(new RotateIcon(new ComponentIcon(label), -90));
      l.setIcon(makeVerticalHeaderIcon(label));
      l.setText(null);
    }
    return c;
  }

  public static SortOrder getColumnSortOrder(JTable table, int column) {
    SortOrder rv = SortOrder.UNSORTED;
    if (table != null && table.getRowSorter() != null) {
      List<? extends RowSorter.SortKey> sortKeys =
        table.getRowSorter().getSortKeys();
      int mi = table.convertColumnIndexToModel(column);
      if (!sortKeys.isEmpty() && sortKeys.get(0).getColumn() == mi) {
        rv = sortKeys.get(0).getSortOrder();
      }
    }
    return rv;
  }

  private Icon makeVerticalHeaderIcon(Component label) {
    Dimension d = label.getPreferredSize();
    int w = d.height;
    int h = d.width;
    BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2 = (Graphics2D) bi.getGraphics();
    AffineTransform at = AffineTransform.getTranslateInstance(0, h);
    at.quadrantRotate(-1);
    g2.setTransform(at);
    SwingUtilities.paintComponent(g2, label, intermediate, 0, 0, h, w);
    g2.dispose();
    return new ImageIcon(bi);
  }
}

References

2022/10/31

Add a header to JPopupMenu to enable repositioning by mouse dragging

Code

class PopupHeaderMouseListener extends MouseAdapter {
  private final Point startPt = new Point();

  @Override public void mousePressed(MouseEvent e) {
    if (SwingUtilities.isLeftMouseButton(e)) {
      startPt.setLocation(e.getPoint());
    }
  }

  @Override public void mouseDragged(MouseEvent e) {
    Component c = e.getComponent();
    Window w = SwingUtilities.getWindowAncestor(c);
    if (w != null && SwingUtilities.isLeftMouseButton(e)) {
      if (w.getType() == Window.Type.POPUP) { // Popup$HeavyWeightWindow
        Point pt = e.getLocationOnScreen();
        w.setLocation(pt.x - startPt.x, pt.y - startPt.y);
      } else { // Popup$LightWeightWindow
        Container popup = SwingUtilities.getAncestorOfClass(JPopupMenu.class, c);
        Point pt = popup.getLocation();
        popup.setLocation(pt.x - startPt.x + e.getX(), pt.y - startPt.y + e.getY());
      }
    }
  }
}

References

2022/09/30

Make the editable JTextArea scrollable beyond its last line

Code

// https://stackoverflow.com/questions/36715803/scrolling-past-the-end-in-idea
// IntelliJ IDEA: Settings -> Editor -> General -> Check Show virtual space at file bottom
JTextArea textArea = new JTextArea() {
  // https://stackoverflow.com/questions/32679335/java-jtextarea-allow-scrolling-beyond-end-of-text
  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    Container c = SwingUtilities.getAncestorOfClass(JScrollPane.class, this);
    if (c instanceof JScrollPane && isEditable()) {
      Rectangle r = ((JScrollPane) c).getViewportBorderBounds();
      d.height += r.height - getRowHeight() - getInsets().bottom;
    }
    return d;
  }
};

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

2022/07/31

use a newspaper-style JList that can scroll and display multiple items as a ComboBoxEditor

Code

ListModel<ListItem> model = makeModel();
JList<ListItem> list = new NewspaperStyleList<>(model);
JScrollPane scroll = new JScrollPane(list) {
  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.width = 62 * 3 + 10;
    d.height = 32;
    return d;
  }
};
scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
scroll.setBorder(BorderFactory.createEmptyBorder());
scroll.setViewportBorder(BorderFactory.createEmptyBorder());

JList<ListItem> list2 = new NewspaperStyleList<>(model);
JScrollPane scroll2 = new JScrollPane(list2) {
  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.width = 62 * 4 + 10;
    return d;
  }
};
scroll2.setBorder(BorderFactory.createEmptyBorder());
scroll2.setViewportBorder(BorderFactory.createEmptyBorder());

JPopupMenu popup = new JPopupMenu();
popup.setLayout(new BorderLayout());
list2.addMouseListener(new MouseAdapter() {
  @Override public void mouseClicked(MouseEvent e) {
    if (popup.isVisible() && e.getClickCount() >= 2) {
      popup.setVisible(false);
    }
  }
});
popup.add(scroll2);
popup.setBorder(BorderFactory.createLineBorder(Color.GRAY));

JToggleButton dropDown = new JToggleButton(new DropDownArrowIcon());
popup.addPopupMenuListener(new PopupMenuListener() {
  @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
    list2.setSelectedIndex(list.getSelectedIndex());
    EventQueue.invokeLater(popup::pack);
  }

  @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
    dropDown.setSelected(false);
    list.requestFocusInWindow();
    int i = list2.getSelectedIndex();
    if (i >= 0) {
      list.setSelectedIndex(i);
      list.scrollRectToVisible(list.getCellBounds(i, i));
    }
  }

  @Override public void popupMenuCanceled(PopupMenuEvent e) {
    popupMenuWillBecomeInvisible(e);
  }
});

dropDown.setBorder(BorderFactory.createEmptyBorder());
dropDown.setContentAreaFilled(false);
dropDown.setFocusPainted(false);
dropDown.setFocusable(false);
dropDown.addItemListener(e -> {
  AbstractButton b = (AbstractButton) e.getItemSelectable();
  if (e.getStateChange() == ItemEvent.SELECTED) {
    popup.show(b, -scroll.getWidth(), b.getHeight());
  }
});

JScrollBar verticalScrollBar = scroll.getVerticalScrollBar();
JPanel verticalBox = new JPanel(new BorderLayout()) {
  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.height = 32 + 5 + 5;
    return d;
  }
};
verticalBox.setOpaque(false);
verticalBox.add(verticalScrollBar);
verticalBox.add(dropDown, BorderLayout.SOUTH);

JPanel panel = new JPanel(new BorderLayout(0, 0));
panel.add(scroll);
panel.add(verticalBox, BorderLayout.EAST);
panel.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));

References