Google Tag Manager

2021/06/30

Round the corners of JTableHeader

Code

class RoundedHeaderRenderer extends DefaultTableCellRenderer {
  private final JLabel firstLabel = new JLabel() {
    @Override public void paintComponent(Graphics g) {
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setPaint(getBackground());
      float r = 8f;
      float x = 0f;
      float y = 0f;
      float w = getWidth();
      float h = getHeight();
      Path2D p = new Path2D.Float();
      p.moveTo(x, y + r);
      p.quadTo(x, y, x + r, y);
      p.lineTo(x + w, y);
      p.lineTo(x + w, y + h);
      p.lineTo(x + r, y + h);
      p.quadTo(x, y + h, x, y + h - r);
      p.closePath();
      g2.fill(p);
      g2.dispose();
      super.paintComponent(g);
    }
  };
  private final JLabel lastLabel = new JLabel() {
    @Override public void paintComponent(Graphics g) {
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setPaint(getBackground());
      float r = 8f;
      float x = 0f;
      float y = 0f;
      float w = getWidth();
      float h = getHeight();
      Path2D p = new Path2D.Float();
      p.moveTo(x, y);
      p.lineTo(x + w - r, y);
      p.quadTo(x + w, y, x + w, y + r);
      p.lineTo(x + w, y + h - r);
      p.quadTo(x + w, y + h, x + w - r, y + h);
      p.lineTo(x, y + h);
      p.closePath();
      g2.fill(p);
      g2.dispose();
      super.paintComponent(g);
    }
  };

  public RoundedHeaderRenderer() {
    super();
    firstLabel.setOpaque(false);
    lastLabel.setOpaque(false);
    setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    firstLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
    lastLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
  }

  @Override public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    JLabel l = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
    if (column == 0) {
      l = firstLabel;
    } else if (column == table.getColumnCount() - 1) {
      l = lastLabel;
    }
    l.setFont(table.getFont());
    l.setText(value.toString());
    l.setForeground(table.getTableHeader().getForeground());
    l.setBackground(table.getTableHeader().getBackground());
    l.setHorizontalAlignment(SwingConstants.CENTER);
    return l;
  }
}

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

2021/04/30

Resize the tab area of the JTabbedPane by dragging with the mouse cursor

Code

class TabAreaResizeLayer extends LayerUI<ClippedTitleTabbedPane> {
  private int offset;
  private boolean resizing;

  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(
          AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }
  }

  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }

  @Override protected void processMouseEvent(
        MouseEvent e, JLayer<? extends ClippedTitleTabbedPane> l) {
    ClippedTitleTabbedPane tabbedPane = l.getView();
    if (e.getID() == MouseEvent.MOUSE_PRESSED) {
      Rectangle rect = getDividerBounds(tabbedPane);
      Point pt = e.getPoint();
      SwingUtilities.convertPoint(e.getComponent(), pt, tabbedPane);
      if (rect.contains(pt)) {
        offset = pt.x - tabbedPane.getTabAreaWidth();
        tabbedPane.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
        resizing = true;
        e.consume();
      }
    } else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
      tabbedPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      resizing = false;
    }
  }

  @Override protected void processMouseMotionEvent(
        MouseEvent e, JLayer<? extends ClippedTitleTabbedPane> l) {
    ClippedTitleTabbedPane tabbedPane = l.getView();
    Point pt = e.getPoint();
    SwingUtilities.convertPoint(e.getComponent(), pt, tabbedPane);
    if (e.getID() == MouseEvent.MOUSE_MOVED) {
      Rectangle r = getDividerBounds(tabbedPane);
      Cursor c = Cursor.getPredefinedCursor(
          r.contains(pt) ? Cursor.W_RESIZE_CURSOR : Cursor.DEFAULT_CURSOR);
      tabbedPane.setCursor(c);
    } else if (e.getID() == MouseEvent.MOUSE_DRAGGED && resizing) {
      tabbedPane.setTabAreaWidth(pt.x - offset);
      e.consume();
    }
  }

  private static Rectangle getDividerBounds(ClippedTitleTabbedPane tabbedPane) {
    Dimension dividerSize = new Dimension(4, 4);
    Rectangle bounds = tabbedPane.getBounds();
    Rectangle compRect = Optional.ofNullable(tabbedPane.getSelectedComponent())
        .map(Component::getBounds).orElseGet(Rectangle::new);
    switch (tabbedPane.getTabPlacement()) {
      case SwingConstants.LEFT:
        bounds.x = compRect.x - dividerSize.width;
        bounds.width = dividerSize.width * 2;
        break;
      case SwingConstants.RIGHT:
        bounds.x += compRect.x + compRect.width - dividerSize.width;
        bounds.width = dividerSize.width * 2;
        break;
      case SwingConstants.BOTTOM:
        bounds.y += compRect.y + compRect.height - dividerSize.height;
        bounds.height = dividerSize.height * 2;
        break;
      default: // case SwingConstants.TOP:
        bounds.y = compRect.y - dividerSize.height;
        bounds.height = dividerSize.height * 2;
        break;
    }
    return bounds;
  }
}

References

2021/03/31

Add header and footer in the JComboBox dropdown list

Add the header created by JLabel and the clickable footer created by JMenuItem in the drop-down list of JComboBox

Code

class HeaderFooterComboPopup extends BasicComboPopup {
  protected transient JLabel header;
  protected transient JMenuItem footer;

  public HeaderFooterComboPopup(JComboBox combo) {
    super(combo);
  }

  @Override protected void configurePopup() {
    super.configurePopup();
    configureHeader();
    configureFooter();
    add(header, 0);
    add(footer);
  }

  protected void configureHeader() {
    header = new JLabel("History");
    header.setBorder(BorderFactory.createEmptyBorder(4, 5, 4, 0));
    header.setMaximumSize(new Dimension(Short.MAX_VALUE, 20));
    header.setAlignmentX(1f);
  }

  protected void configureFooter() {
    int modifiers = InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK;
    footer = new JMenuItem("Show All Bookmarks");
    footer.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, modifiers));
    footer.addActionListener(e -> {
      Window w = SwingUtilities.getWindowAncestor(getInvoker());
      JOptionPane.showMessageDialog(w, "Bookmarks");
    });
  }
}

References

2021/02/28

Create a JList heavyweight cell editor with a centered, fixed width, and line wrapping editor

Code

class EditableList<E extends ListItem> extends JList<E> {
  public static final String RENAME = "rename-title";
  public static final String CANCEL = "cancel-editing";
  public static final String EDITING = "start-editing";
  protected int editingIndex = -1;
  protected int editorWidth = -1;
  private transient MouseAdapter handler;
  // protected final Container glassPane = new EditorGlassPane(); // LightWeightEditor
  protected Window window; // HeavyWeightEditor
  protected final JTextPane editor = new JTextPane() {
    @Override public Dimension getPreferredSize() {
      Dimension d = super.getPreferredSize();
      d.width = editorWidth;
      return d;
    }
  };
  protected final Action startEditing = new AbstractAction() {
    @Override public void actionPerformed(ActionEvent e) {
      // getRootPane().setGlassPane(glassPane);
      int idx = getSelectedIndex();
      editingIndex = idx;
      Rectangle rect = getCellBounds(idx, idx);
      // Point p = SwingUtilities.convertPoint(EditableList.this, rect.getLocation(), glassPane);
      // rect.setLocation(p);
      editorWidth = rect.width;
      editor.setText(getSelectedValue().title);
      int rowHeight = editor.getFontMetrics(editor.getFont()).getHeight();
      rect.y += rect.height - rowHeight - 2 - 1;
      rect.height = editor.getPreferredSize().height;
      editor.setBounds(rect);
      editor.selectAll();
      // glassPane.add(editor);
      // glassPane.setVisible(true);
      Point p = new Point(rect.getLocation());
      SwingUtilities.convertPointToScreen(p, EditableList.this);
      if (window == null) {
        window = new JWindow(SwingUtilities.getWindowAncestor(EditableList.this));
        window.setFocusableWindowState(true);
        window.setModalExclusionType(Dialog.ModalExclusionType.APPLICATION_EXCLUDE);
        // window.setAlwaysOnTop(true);
        window.add(editor);
      }
      window.setLocation(p);
      window.pack();
      window.setVisible(true);
      editor.requestFocusInWindow();
    }
  };
  protected final Action cancelEditing = new AbstractAction() {
    @Override public void actionPerformed(ActionEvent e) {
      // glassPane.setVisible(false);
      window.setVisible(false);
      editingIndex = -1;
    }
  };
  protected final Action renameTitle = new AbstractAction() {
    @Override public void actionPerformed(ActionEvent e) {
      ListModel<E> m = getModel();
      String title = editor.getText().trim();
      int index = editingIndex; // getSelectedIndex();
      if (!title.isEmpty() && index >= 0 && m instanceof DefaultListModel<?>) {
        @SuppressWarnings("unchecked")
        DefaultListModel<ListItem> model = (DefaultListModel<ListItem>) getModel();
        ListItem item = m.getElementAt(index);
        model.remove(index);
        model.add(index, new ListItem(editor.getText().trim(), item.icon));
        setSelectedIndex(index); // 1. Both must be run
        EventQueue.invokeLater(() -> setSelectedIndex(index)); // 2. Both must be run
      }
      // glassPane.setVisible(false);
      window.setVisible(false);
      editingIndex = -1;
    }
  };

  protected EditableList(DefaultListModel<E> model) {
    super(model);
    editor.setBorder(BorderFactory.createLineBorder(Color.GRAY));
    editor.setEditorKit(new WrapEditorKit());
    editor.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
    editor.setFont(UIManager.getFont("TextField.font"));
    // editor.setHorizontalAlignment(SwingConstants.CENTER); // JTextField
    // editor.setLineWrap(true); // JTextArea
    StyledDocument doc = editor.getStyledDocument();
    SimpleAttributeSet center = new SimpleAttributeSet();
    StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER);
    doc.setParagraphAttributes(0, doc.getLength(), center, false);
    editor.setComponentPopupMenu(new TextComponentPopupMenu());
    editor.getDocument().addDocumentListener(new DocumentListener() {
      private int prev = -1;
      private void update() {
        EventQueue.invokeLater(() -> {
          int h = editor.getPreferredSize().height;
          if (prev != h) {
            Rectangle rect = editor.getBounds();
            rect.height = h;
            editor.setBounds(rect);
            window.pack();
            editor.requestFocusInWindow();
          }
          prev = h;
        });
      }

      @Override public void insertUpdate(DocumentEvent e) {
        update();
      }

      @Override public void removeUpdate(DocumentEvent e) {
        update();
      }

      @Override public void changedUpdate(DocumentEvent e) {
        update();
      }
    });

    InputMap im = editor.getInputMap(JComponent.WHEN_FOCUSED);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), RENAME);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), RENAME);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), CANCEL);

    ActionMap am = editor.getActionMap();
    am.put(RENAME, renameTitle);
    am.put(CANCEL, cancelEditing);

    getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), EDITING);
    getActionMap().put(EDITING, startEditing);
  }

  @Override public void updateUI() {
    removeMouseListener(handler);
    removeMouseMotionListener(handler);
    setSelectionForeground(null);
    setSelectionBackground(null);
    setCellRenderer(null);
    super.updateUI();
    setLayoutOrientation(JList.HORIZONTAL_WRAP);
    getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    setVisibleRowCount(0);
    setFixedCellWidth(72);
    setFixedCellHeight(64);
    setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    setCellRenderer(new ListItemListCellRenderer<>());
    handler = new EditingHandler();
    addMouseListener(handler);
    addMouseMotionListener(handler);
  }

  class EditingHandler extends MouseAdapter {
    private boolean startOutside;

    @Override public void mouseClicked(MouseEvent e) {
      int idx = getSelectedIndex();
      Rectangle rect = getCellBounds(idx, idx);
      if (rect == null) {
        return;
      }
      int h = editor.getPreferredSize().height;
      rect.y = rect.y + rect.height - h - 2 - 1;
      rect.height = h;
      boolean isDoubleClick = e.getClickCount() >= 2;
      if (isDoubleClick && rect.contains(e.getPoint())) {
        startEditing.actionPerformed(new ActionEvent(e.getComponent(), ActionEvent.ACTION_PERFORMED, ""));
      }
    }

    @Override public void mousePressed(MouseEvent e) {
      JList<?> list = (JList<?>) e.getComponent();
      startOutside = !contains(list, e.getPoint());
      if (window != null && window.isVisible() && editingIndex >= 0) {
        renameTitle.actionPerformed(new ActionEvent(editor, ActionEvent.ACTION_PERFORMED, ""));
      } else if (startOutside) {
        clearSelectionAndFocus(list);
      }
    }

    @Override public void mouseReleased(MouseEvent e) {
      startOutside = false;
    }

    @Override public void mouseDragged(MouseEvent e) {
      JList<?> list = (JList<?>) e.getComponent();
      if (contains(list, e.getPoint())) {
        startOutside = false;
      } else if (startOutside) {
        clearSelectionAndFocus(list);
      }
    }

    private void clearSelectionAndFocus(JList<?> list) {
      list.clearSelection();
      list.getSelectionModel().setAnchorSelectionIndex(-1);
      list.getSelectionModel().setLeadSelectionIndex(-1);
    }

    private boolean contains(JList<?> list, Point pt) {
      for (int i = 0; i < list.getModel().getSize(); i++) {
        if (list.getCellBounds(i, i).contains(pt)) {
          return true;
        }
      }
      return false;
    }
  }
}
class WrapLabelView extends LabelView {
  protected WrapLabelView(Element element) {
    super(element);
  }

  @Override public float getMinimumSpan(int axis) {
    switch (axis) {
      case View.X_AXIS:
        return 0;
      case View.Y_AXIS:
        return super.getMinimumSpan(axis);
      default:
        throw new IllegalArgumentException("Invalid axis: " + axis);
    }
  }
}

References