Google Tag Manager

Showing posts with label JList. Show all posts
Showing posts with label JList. Show all posts

2024/12/01

Implement sticky header in JList

Code

class StickyLayerUI extends LayerUI<JScrollPane> {
  private final JPanel renderer = new JPanel();
  private int currentHeaderIdx = -1;
  private int nextHeaderIdx = -1;

  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(
          AWTEvent.MOUSE_WHEEL_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 processMouseMotionEvent(
      MouseEvent e, JLayer<? extends JScrollPane> l) {
    super.processMouseMotionEvent(e, l);
    Component c = l.getView().getViewport().getView();
    if (e.getID() == MouseEvent.MOUSE_DRAGGED && c instanceof JList) {
      update((JList<?>) c);
    }
  }

  @Override protected void processMouseWheelEvent(
      MouseWheelEvent e, JLayer<? extends JScrollPane> l) {
    super.processMouseWheelEvent(e, l);
    Component c = l.getView().getViewport().getView();
    if (c instanceof JList) {
      update((JList<?>) c);
    }
  }

  private void update(JList<?> list) {
    int idx = list.getFirstVisibleIndex();
    if (idx >= 0) {
      currentHeaderIdx = getHeaderIndex1(list, idx);
      nextHeaderIdx = getNextHeaderIndex1(list, idx);
    } else {
      currentHeaderIdx = -1;
      nextHeaderIdx = -1;
    }
  }

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    JList<?> list = getList(c);
    if (list != null && currentHeaderIdx >= 0) {
      JScrollPane scroll = (JScrollPane) ((JLayer<?>) c).getView();
      Rectangle headerRect = scroll.getViewport().getBounds();
      headerRect.height = list.getFixedCellHeight();
        Graphics2D g2 = (Graphics2D) g.create();
      int firstVisibleIdx = list.getFirstVisibleIndex();
      if (firstVisibleIdx + 1 == nextHeaderIdx) {
        Dimension d = headerRect.getSize();
        Component c1 = getComponent(list, currentHeaderIdx);
        Rectangle r1 = getHeaderRect(list, firstVisibleIdx, c, d);
        SwingUtilities.paintComponent(g2, c1, renderer, r1);
        Component c2 = getComponent(list, nextHeaderIdx);
        Rectangle r2 = getHeaderRect(list, nextHeaderIdx, c, d);
        SwingUtilities.paintComponent(g2, c2, renderer, r2);
        } else {
        Component c1 = getComponent(list, currentHeaderIdx);
        SwingUtilities.paintComponent(g2, c1, renderer, headerRect);
        }
        g2.dispose();
      }
    }

  private static JList<?> getList(JComponent layer) {
    JList<?> list = null;
    if (layer instanceof JLayer) {
      JScrollPane scroll = (JScrollPane) ((JLayer<?>) layer).getView();
      Component view = scroll.getViewport().getView();
      if (view instanceof JList) {
        list = (JList<?>) view;
      }
    }
    return list;
  }

  private static int getHeaderIndex1(JList<?> list, int start) {
    return list.getNextMatch("0", start, Position.Bias.Backward);
  }

  private static int getNextHeaderIndex1(JList<?> list, int start) {
    return list.getNextMatch("0", start, Position.Bias.Forward);
  }

  private static Rectangle getHeaderRect(
      JList<?> list, int i, Component dst, Dimension d) {
    Rectangle r = SwingUtilities.convertRectangle(
        list, list.getCellBounds(i, i), dst);
    r.setSize(d);
    return r;
  }

  private static <E> Component getComponent(JList<E> list, int idx) {
    E value = list.getModel().getElementAt(idx);
    ListCellRenderer<? super E> r = list.getCellRenderer();
    Component c = r.getListCellRendererComponent(
        list, value, idx, false, false);
    c.setBackground(Color.GRAY);
    c.setForeground(Color.WHITE);
    return c;
  }
}

References

2024/04/30

Rounding the corners of a rectilinear polygon generated by a selection of calendar made with JList

Code

public static Path2D convertRoundedPath(List<Point2D> list, double arc) {
  double kappa = 4d * (Math.sqrt(2d) - 1d) / 3d; // = 0.55228...;
  double akv = arc - arc * kappa;
  int sz = list.size();
  Point2D pt0 = list.get(0);
  Path2D path = new Path2D.Double();
  path.moveTo(pt0.getX() + arc, pt0.getY());
  for (int i = 0; i < sz; i++) {
    Point2D prv = list.get((i - 1 + sz) % sz);
    Point2D cur = list.get(i);
    Point2D nxt = list.get((i + 1) % sz);
    double dx0 = Math.signum(cur.getX() - prv.getX());
    double dy0 = Math.signum(cur.getY() - prv.getY());
    double dx1 = Math.signum(nxt.getX() - cur.getX());
    double dy1 = Math.signum(nxt.getY() - cur.getY());
    path.curveTo(
        cur.getX() - dx0 * akv, cur.getY() - dy0 * akv,
        cur.getX() + dx1 * akv, cur.getY() + dy1 * akv,
        cur.getX() + dx1 * arc, cur.getY() + dy1 * arc);
    path.lineTo(nxt.getX() - dx1 * arc, nxt.getY() - dy1 * arc);
  }
  path.closePath();
  return path;
}

References

2023/09/30

Move the selected item in JList up or down by clicking on the JButton placed on the JToolBar

Since DefaultListModel does not have a move method like DefaultTableModel#moveRow(int start, int end, int to), the DefaultListModel#get(int index), DefaultListModel#remove(int index), and DefaultListModel#add(int index, E element) methods are used in combination to move selected items up and down the JList.

Code

JButton up = new JButton("▲");
up.setFocusable(false);
up.addActionListener(e -> {
  int[] pos = list.getSelectedIndices();
  if (pos.length == 0) {
    return;
  }
  boolean isShiftDown = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
  int index0 = isShiftDown ? 0 : Math.max(0, pos[0] - 1);
  int idx = index0;
  for (int i : pos) {
    model.add(idx, model.remove(i));
    list.addSelectionInterval(idx, idx);
    idx++;
  }
  // scroll
  Rectangle r = list.getCellBounds(index0, index0 + pos.length);
  list.scrollRectToVisible(r);
});

JButton down = new JButton("▼");
down.setFocusable(false);
down.addActionListener(e -> {
  int[] pos = list.getSelectedIndices();
  if (pos.length == 0) {
    return;
  }
  boolean isShiftDown = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
  int max = model.getSize();
  int index = isShiftDown ? max : Math.min(max, pos[pos.length - 1] + 1);
  int index0 = index;
  // copy
  for (int i : pos) {
    int idx = Math.min(model.getSize(), ++index);
    model.add(idx, model.get(i));
    list.addSelectionInterval(idx, idx);
  }
  // clean
  for (int i = pos.length - 1; i >= 0; i--) {
    model.remove(pos[i]);
  }
  // scroll
  Rectangle r = list.getCellBounds(index0 - pos.length, index0);
  list.scrollRectToVisible(r);
});

References

2023/07/31

Add item check boxes to JList cells

Code

@Override public void setSelectionInterval(int anchor, int lead) {
  if (checkedIndex < 0 && isDragging()) {
    super.setSelectionInterval(anchor, lead);
  } else {
    EventQueue.invokeLater(() -> {
      if (checkedIndex >= 0 && lead == anchor && checkedIndex == anchor) {
        super.addSelectionInterval(checkedIndex, checkedIndex);
      } else {
        super.setSelectionInterval(anchor, lead);
      }
    });
  }
}

protected boolean isDragging() {
  Rectangle r = getRubberBand().getBounds();
  return r.width != 0 || r.height != 0;
}

@Override public void removeSelectionInterval(int index0, int index1) {
  if (checkedIndex < 0) {
    super.removeSelectionInterval(index0, index1);
  } else {
    EventQueue.invokeLater(() -> super.removeSelectionInterval(index0, index1));
  }
}

private static <E> Optional<AbstractButton> getItemCheckBox(
    JList<E> list, MouseEvent e, int index) {
  if (e.isShiftDown() || e.isControlDown() || e.isAltDown()) {
    return Optional.empty();
  }
  E proto = list.getPrototypeCellValue();
  ListCellRenderer<? super E> cr = list.getCellRenderer();
  Component c = cr.getListCellRendererComponent(
      list, proto, index, false, false);
  Rectangle r = list.getCellBounds(index, index);
  c.setBounds(r);
  Point pt = e.getPoint();
  pt.translate(-r.x, -r.y);
  return Optional
    .ofNullable(SwingUtilities.getDeepestComponentAt(c, pt.x, pt.y))
    .filter(AbstractButton.class::isInstance)
    .map(AbstractButton.class::cast);
}

private final class ItemCheckBoxesListener extends MouseAdapter {
  private final Point srcPoint = new Point();

  @Override public void mouseDragged(MouseEvent e) {
    checkedIndex = -1;
    JList<?> l = (JList<?>) e.getComponent();
    l.setFocusable(true);
    Point destPoint = e.getPoint();
    Path2D rb = getRubberBand();
    rb.reset();
    rb.moveTo(srcPoint.x, srcPoint.y);
    rb.lineTo(destPoint.x, srcPoint.y);
    rb.lineTo(destPoint.x, destPoint.y);
    rb.lineTo(srcPoint.x, destPoint.y);
    rb.closePath();

    int[] indices = IntStream.range(0, l.getModel().getSize())
            .filter(i -> rb.intersects(l.getCellBounds(i, i))).toArray();
    l.setSelectedIndices(indices);
    l.repaint();
  }

  @Override public void mouseExited(MouseEvent e) {
    rollOverIndex = -1;
    e.getComponent().repaint();
  }

  @Override public void mouseMoved(MouseEvent e) {
    Point pt = e.getPoint();
    int idx = locationToIndex(pt);
    if (!getCellBounds(idx, idx).contains(pt)) {
      idx = -1;
    }
    Rectangle rect = new Rectangle();
    if (idx > 0) {
      rect.add(getCellBounds(idx, idx));
      if (rollOverIndex >= 0 && idx != rollOverIndex) {
        rect.add(getCellBounds(rollOverIndex, rollOverIndex));
      }
      rollOverIndex = idx;
    } else {
      if (rollOverIndex >= 0) {
        rect.add(getCellBounds(rollOverIndex, rollOverIndex));
      }
      rollOverIndex = -1;
    }
    ((JComponent) e.getComponent()).repaint(rect);
  }

  @Override public void mouseReleased(MouseEvent e) {
    getRubberBand().reset();
    Component c = e.getComponent();
    c.setFocusable(true);
    c.repaint();
  }

  @Override public void mousePressed(MouseEvent e) {
    JList<?> l = (JList<?>) e.getComponent();
    int index = l.locationToIndex(e.getPoint());
    if (l.getCellBounds(index, index).contains(e.getPoint())) {
      l.setFocusable(true);
      cellPressed(l, e, index);
    } else {
      l.setFocusable(false);
      l.clearSelection();
      l.getSelectionModel().setAnchorSelectionIndex(-1);
      l.getSelectionModel().setLeadSelectionIndex(-1);
    }
    srcPoint.setLocation(e.getPoint());
    l.repaint();
  }

  private void cellPressed(JList<?> l, MouseEvent e, int index) {
    if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() > 1) {
      ListItem item = getModel().getElementAt(index);
      JOptionPane.showMessageDialog(l.getRootPane(), item.title);
    } else {
      checkedIndex = -1;
      getItemCheckBox(l, e.getPoint(), index).ifPresent(rb -> {
        checkedIndex = index;
        if (l.isSelectedIndex(index)) {
          l.setFocusable(false);
          removeSelectionInterval(index, index);
        } else {
          setSelectionInterval(index, index);
        }
      });
    }
  }
}

References

2023/03/31

Create a dot matrix LED digital clock with JList

Code

private static JList<Boolean> makeLedDotMatrixList(
    ListModel<Boolean> model, Dimension dim) {
  return new JList<Boolean>(model) {
    @Override public void updateUI() {
      setFixedCellWidth(dim.width);
      setFixedCellHeight(dim.height);
      setVisibleRowCount(ROW);
      setCellRenderer(null);
      super.updateUI();
      setLayoutOrientation(JList.VERTICAL_WRAP);
      setFocusable(false);
      ListCellRenderer<? super Boolean> renderer = getCellRenderer();
      Icon on = new LedDotIcon(true, dim);
      Icon off = new LedDotIcon(false, dim);
      setCellRenderer((list, value, index, isSelected, cellHasFocus) -> {
        Component c = renderer.getListCellRendererComponent(
            list, null, index, false, false);
        if (c instanceof JLabel) {
          ((JLabel) c).setIcon(Objects.equals(Boolean.TRUE, value) ? on : off);
        }
        return c;
      });
      setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
      setBackground(Color.BLACK);
    }
  };
}

class LedDotIcon implements Icon {
  private final Color on = new Color(0x32_FF_AA);
  private final boolean led;
  private final Dimension dim;

  protected LedDotIcon(boolean led, Dimension dim) {
    this.led = led;
    this.dim = dim;
  }

  @Override public void paintIcon(Component c, Graphics g, int x, int y) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    // JList#setLayoutOrientation(VERTICAL_WRAP) + Synth, Nimbus, GTK bug???
    // g2.translate(x, y);
    g2.setPaint(led ? on : c.getBackground());
    g2.fillOval(0, 0, getIconWidth() - 1, getIconHeight() - 1);
    g2.dispose();
  }

  @Override public int getIconWidth() {
    return dim.width;
  }

  @Override public int getIconHeight() {
    return dim.height;
  }
}

DefaultListModel<Boolean> model1 = new DefaultListModel<Boolean>() {
  @Override public Boolean getElementAt(int index) {
    return getHoursMinutesDotMatrix(time, index);
  }
};
model1.setSize((COLUMN * 4 + 5) * ROW);

private static boolean getHoursMinutesDotMatrix(LocalTime time, int index) {
  int ten = 10;
  int hours = time.getHour();
  int h1 = hours / ten;
  int start = 0;
  int end = start + COLUMN;
  if (contains(index, start, end, h1)) {
    return hours >= ten;
  }
  int gap = 1;
  int h2 = hours - h1 * ten;
  start = end + gap;
  end = start + COLUMN;
  if (contains(index, start, end, h2)) {
    return true;
  }
  int seconds = time.getSecond();
  int s1 = seconds / ten;
  int s2 = seconds - s1 * ten;
  start = end + gap;
  end = start + gap;
  if (index < end * ROW && s2 % 2 == 0 && DOT.contains(index - start * ROW)) {
    return true;
  }
  int minutes = time.getMinute();
  int m1 = minutes / ten;
  start = end + gap;
  end = start + COLUMN;
  if (contains(index, start, end, m1)) {
    return true;
  }
  int m2 = minutes - m1 * ten;
  start = end + gap;
  end = start + COLUMN;
  return contains(index, start, end, m2);
}

private static final int COLUMN = 4;
private static final int ROW = 7;
private static final List<Set<Integer>> NUMBERS = Arrays.asList(
  Set.of(0, 1, 2, 3, 4, 5, 6, 7, 13, 14, 20, 21, 22, 23, 24, 25, 26, 27), // 0
  Set.of(21, 22, 23, 24, 25, 26, 27), // 1
  Set.of(0, 3, 4, 5, 6, 7, 10, 13, 14, 17, 20, 21, 22, 23, 24, 27), // 2
  Set.of(0, 3, 6, 7, 10, 13, 14, 17, 20, 21, 22, 23, 24, 25, 26, 27), // 3
  Set.of(0, 1, 2, 3, 10, 17, 21, 22, 23, 24, 25, 26, 27), // 4
  Set.of(0, 1, 2, 3, 6, 7, 10, 13, 14, 17, 20, 21, 24, 25, 26, 27), // 5
  Set.of(0, 1, 2, 3, 4, 5, 6, 7, 10, 13, 14, 17, 20, 21, 24, 25, 26, 27), // 6
  Set.of(0, 1, 2, 3, 7, 14, 21, 22, 23, 24, 25, 26, 27), // 7
  Set.of(0, 1, 2, 3, 4, 5, 6, 7, 10, 13, 14, 17, 20, 21, 22, 23, 24, 25, 26, 27), // 8
  Set.of(0, 1, 2, 3, 6, 7, 10, 13, 14, 17, 20, 21, 22, 23, 24, 25, 26, 27)); // 9

Code(alternative pattern)

private static final int COLUMN = 5;
private static final int ROW = 7;
private static final List> NUMBERS = Arrays.asList(
    Set.of(1, 2, 3, 4, 5, 7, 9, 13, 14, 17, 20, 21, 25, 27, 29, 30, 31, 32, 33), // 0
    Set.of(8, 13, 14, 15, 16, 17, 18, 19, 20, 27), // 1
    Set.of(1, 6, 7, 12, 13, 14, 18, 20, 21, 24, 27, 29, 30, 34), // 2
    Set.of(0, 5, 7, 13, 14, 17, 20, 21, 23, 24, 27, 28, 29, 32, 33), // 3
    Set.of(3, 4, 9, 11, 15, 18, 21, 22, 23, 24, 25, 26, 27, 32), // 4
    Set.of(0, 1, 2, 5, 7, 9, 13, 14, 16, 20, 21, 23, 27, 28, 31, 32, 33), // 5
    Set.of(1, 2, 3, 4, 5, 7, 10, 13, 14, 17, 20, 21, 24, 27, 29, 32, 33), // 6
    Set.of(0, 7, 11, 12, 13, 14, 17, 21, 23, 28, 29), // 7
    Set.of(1, 2, 4, 5, 7, 10, 13, 14, 17, 20, 21, 24, 27, 29, 30, 32, 33), // 8
    Set.of(1, 2, 5, 7, 10, 13, 14, 17, 20, 21, 24, 27, 29, 30, 31, 32, 33)); // 9

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

2022/06/30

Change the height of a drop-down list of JComboBox by mouse dragging

Code

JPopupMenu popup = new JPopupMenu();
popup.setBorder(BorderFactory.createEmptyBorder());
popup.setPopupSize(240, 120);

JLabel bottom = new JLabel("", new DotIcon(), SwingConstants.CENTER);
MouseInputListener rwl = new ResizeWindowListener(popup);
bottom.addMouseListener(rwl);
bottom.addMouseMotionListener(rwl);
bottom.setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
bottom.setOpaque(true);
bottom.setBackground(new Color(0xE0_E0_E0));
bottom.setFocusable(false);

JPanel resizePanel = new JPanel(new BorderLayout());
resizePanel.add(scroll);
resizePanel.add(bottom, BorderLayout.SOUTH);
resizePanel.add(Box.createHorizontalStrut(240), BorderLayout.NORTH);
resizePanel.setBorder(BorderFactory.createLineBorder(new Color(0x64_64_64)));

JPopupMenu popup = new JPopupMenu();
popup.add(resizePanel);

// ...
class ResizeWindowListener extends MouseInputAdapter {
  private final Rectangle rect = new Rectangle();
  private final JPopupMenu popup;
  private final Point startPt = new Point();
  private final Dimension startDim = new Dimension();

  protected ResizeWindowListener(JPopupMenu popup) {
    this.popup = popup;
  }

  @Override public void mousePressed(MouseEvent e) {
    rect.setSize(popup.getSize());
    startDim.setSize(popup.getSize());
    startPt.setLocation(e.getComponent().getLocationOnScreen());
  }

  @Override public void mouseDragged(MouseEvent e) {
    rect.height = startDim.height + e.getLocationOnScreen().y - startPt.y;
    popup.setPreferredSize(rect.getSize());
    Window w = SwingUtilities.getWindowAncestor(popup);
    if (w != null && w.getType() == Window.Type.POPUP) {
      // Popup$HeavyWeightWindow
      w.setSize(rect.width, rect.height);
    } else {
      // Popup$LightWeightWindow
      popup.pack();
    }
  }
}

References

2022/01/31

Fix the JToolTip display of JList cells to correspond to after mouse wheel rotation

Code

JList<String> list1 = new JList<String>(model) {
  @Override public void updateUI() {
    super.updateUI();
    setCellRenderer(new TooltipListCellRenderer<>());
  }
};
JScrollPane scroll1 = new JScrollPane(list1);
scroll1.addMouseWheelListener(e -> {
  JScrollPane scrollPane = (JScrollPane) e.getComponent();
  Component view = scrollPane.getViewport().getView();
  MouseEvent event = SwingUtilities.convertMouseEvent(scrollPane, e, view);
  ToolTipManager.sharedInstance().mouseMoved(event);
});

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

2019/06/28

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

2018/06/29

Move items by copying and pasting between JLists

Code

class ListPopupMenu extends JPopupMenu {
  private final JMenuItem cutItem;
  private final JMenuItem copyItem;
  protected ListPopupMenu(JList<?> list) {
    super();
    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    TransferHandler handler = list.getTransferHandler();
    cutItem = add("cut");
    cutItem.addActionListener(e -> {
      handler.exportToClipboard(list, clipboard, TransferHandler.MOVE);
    });
    copyItem = add("copy");
    copyItem.addActionListener(e -> {
      handler.exportToClipboard(list, clipboard, TransferHandler.COPY);
    });
    add("paste").addActionListener(e -> {
      handler.importData(list, clipboard.getContents(null));
    });
    addSeparator();
    add("clearSelection").addActionListener(e -> list.clearSelection());
  }
  @Override public void show(Component c, int x, int y) {
    if (c instanceof JList) {
      boolean isSelected = !((JList<?>) c).isSelectionEmpty();
      cutItem.setEnabled(isSelected);
      copyItem.setEnabled(isSelected);
      super.show(c, x, y);
    }
  }
}

// ...
private static int getIndex(TransferHandler.TransferSupport info) {
  JList<?> target = (JList<?>) info.getComponent();
  int index; // = dl.getIndex();
  if (info.isDrop()) { // Mouse Drag & Drop
    System.out.println("Mouse Drag & Drop");
    TransferHandler.DropLocation tdl = info.getDropLocation();
    if (tdl instanceof JList.DropLocation) {
      index = ((JList.DropLocation) tdl).getIndex();
    } else {
      index = target.getSelectedIndex();
    }
  } else { // Keyboard Copy & Paste
    index = target.getSelectedIndex();
  }
  DefaultListModel<?> listModel = (DefaultListModel<?>) target.getModel();
  // boolean insert = dl.isInsert();
  int max = listModel.getSize();
  // int index = dl.getIndex();
  index = index < 0 ? max : index; // If it is out of range, it is appended to the end
  index = Math.min(index, max);
  return index;
}

References

2018/04/26

Move large numbers of items to another JList as fast as possible

Code

private static <E> void move1(JList<E> from, JList<E> to) {
  ListSelectionModel sm = from.getSelectionModel();
  int[] selectedIndices = from.getSelectedIndices();

  DefaultListModel<E> fromModel = (DefaultListModel<E>) from.getModel();
  DefaultListModel<E> toModel = (DefaultListModel<E>) to.getModel();
  List<E> unselectedValues = new ArrayList<>();
  for (int i = 0; i < fromModel.getSize(); i++) {
    if (!sm.isSelectedIndex(i)) {
      unselectedValues.add(fromModel.getElementAt(i));
    }
  }
  if (selectedIndices.length > 0) {
    for (int i: selectedIndices) {
      toModel.addElement(fromModel.get(i));
    }
    fromModel.clear();
    // unselectedValues.forEach(fromModel::addElement);
    DefaultListModel<E> model = new DefaultListModel<>();
    unselectedValues.forEach(model::addElement);
    from.setModel(model);
  }
}

References

2018/03/29

Create Week Calendar in JList and display contribution activity heatmap

Code

JList<Contribution> weekList = new JList<Contribution>() {
  @Override public void updateUI() {
    setCellRenderer(null);
    super.updateUI();
    setLayoutOrientation(JList.VERTICAL_WRAP);
    setVisibleRowCount(DayOfWeek.values().length); // ensure 7 rows in the list
    setFixedCellWidth(size.width);
    setFixedCellHeight(size.height);
    setCellRenderer(new ContributionListRenderer());
    getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
    setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
  }
};
// ...
private class ContributionListRenderer implements ListCellRenderer<Contribution> {
  private final Icon emptyIcon = new ColorIcon(Color.WHITE);
  private final ListCellRenderer<? super Contribution> renderer
    = new DefaultListCellRenderer();
  @Override public Component getListCellRendererComponent(
      JList<? extends Contribution> list, Contribution value, int index,
      boolean isSelected, boolean cellHasFocus) {
    JLabel l = (JLabel) renderer.getListCellRendererComponent(
        list, null, index, isSelected, cellHasFocus);
    if (value.date.isAfter(currentLocalDate)) {
      l.setIcon(emptyIcon);
      l.setToolTipText(null);
    } else {
      l.setIcon(activityIcons.get(value.activity));
      String actTxt = value.activity == 0 ? "No" : Objects.toString(value.activity);
      l.setToolTipText(actTxt + " contribution on " + value.date.toString());
    }
    return l;
  }
}

References

2018/02/26

Create a range selectable calendar with JList

Code

public final LocalDate realLocalDate = LocalDate.now();
public LocalDate currentLocalDate;
public final Dimension size = new Dimension(40, 26);

// ...
JLabel yearMonthLabel = new JLabel("", SwingConstants.CENTER);
JList<LocalDate> monthList = new JList<LocalDate>(new CalendarViewListModel(realLocalDate)) {
  @Override public void updateUI() {
    setCellRenderer(null);
    super.updateUI();
    setLayoutOrientation(JList.HORIZONTAL_WRAP);
    setVisibleRowCount(CalendarViewListModel.ROW_COUNT); // ensure 6 rows in the list
    setFixedCellWidth(size.width);
    setFixedCellHeight(size.height);
    setCellRenderer(new CalendarListRenderer<>());
    getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
  }
};

// ...
class CalendarViewListModel extends AbstractListModel<LocalDate> {
  public static final int ROW_COUNT = 6;
  private final LocalDate startDate;
  private final WeekFields weekFields = WeekFields.of(Locale.getDefault());
  protected CalendarViewListModel(LocalDate date) {
    super();
    LocalDate firstDayOfMonth = YearMonth.from(date).atDay(1);
    int dowv = firstDayOfMonth.get(weekFields.dayOfWeek()) - 1;
    startDate = firstDayOfMonth.minusDays(dowv);
  }

  @Override public int getSize() {
    return DayOfWeek.values().length * ROW_COUNT;
  }

  @Override public LocalDate getElementAt(int index) {
    return startDate.plusDays(index);
  }
}

References

2017/04/28

Disable any items in JList

Code

JList<String> list = new JList<>(model);
list.setCellRenderer(new DefaultListCellRenderer() {
  @Override public Component getListCellRendererComponent(
      JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
    Component c;
    if (disableIndexSet.contains(index)) {
      c = super.getListCellRendererComponent(list, value, index, false, false);
      c.setEnabled(false);
    } else {
      c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
    }
    return c;
  }
});

ActionMap am = list.getActionMap();
am.put("selectNextRow", new AbstractAction() {
  @Override public void actionPerformed(ActionEvent ae) {
    for (int i = list.getSelectedIndex() + 1; i < list.getModel().getSize(); i++) {
      if (!disableIndexSet.contains(Integer.valueOf(i))) {
        list.setSelectedIndex(i);
        break;
      }
    }
  }
});

References

2016/12/27

Change the shape of JToolTip to balloon

Code

class BalloonToolTip extends JToolTip {
  private HierarchyListener listener;
  @Override public void updateUI() {
    removeHierarchyListener(listener);
    super.updateUI();
    listener = e -> {
      Component c = e.getComponent();
      boolean b = (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0;
      if (b && c.isShowing()) {
        Window w = SwingUtilities.getWindowAncestor(c);
        if (w != null && w.getType() == Window.Type.POPUP) {
          // Popup$HeavyWeightWindow
          w.setBackground(new Color(0x0, true));
        }
      }
    };
    addHierarchyListener(listener);
    setOpaque(false);
    setBorder(BorderFactory.createEmptyBorder(8, 5, 0, 5));
  }

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

  @Override public 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.setColor(getForeground());
    g2.draw(s);
    g2.dispose();
    super.paintComponent(g);
  }

  private Shape makeBalloonShape() {
    Insets i = getInsets();
    int w = getWidth() - 1;
    int h = getHeight() - 1;
    int v = i.top / 2;
    Polygon triangle = new Polygon();
    triangle.addPoint(i.left + v + v, 0);
    triangle.addPoint(i.left + v, v);
    triangle.addPoint(i.left + v + v + v, v);
    Area area = new Area(new RoundRectangle2D.Float(
        0, v, w, h - i.bottom - v, i.top, i.top));
    area.add(new Area(triangle));
    return area;
  }
}

//...
JList<String> list = new JList<String>(model) {
  @Override public JToolTip createToolTip() {
    JToolTip tip = new BalloonToolTip();
    tip.setComponent(this);
    return tip;
  }
};

References

2016/11/24

Filtering JList items by Regex

Code

DefaultListModel<ListItem> model = new DefaultListModel<>();
JList<ListItem> list = new JList<ListItem>(model) {
  @Override public void updateUI() {
    setSelectionForeground(null);
    setSelectionBackground(null);
    setCellRenderer(null);
    super.updateUI();
    setLayoutOrientation(JList.HORIZONTAL_WRAP);
    setVisibleRowCount(0);
    setFixedCellWidth(82);
    setFixedCellHeight(64);
    setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
    setCellRenderer(new ListItemListCellRenderer<ListItem>());
    getSelectionModel().setSelectionMode(
        ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
  }
};
private Optional<Pattern> getPattern() {
  try {
    return Optional.ofNullable(field.getText())
                   .filter(s -> !s.isEmpty())
                   .map(Pattern::compile);
  } catch (PatternSyntaxException ex) {
    return Optional.empty();
  }
}
private void filter() {
  getPattern().ifPresent(pattern -> {
    List<ListItem> selected = list.getSelectedValuesList();
    model.clear();
    Stream.of(defaultModel)
          .filter(item -> pattern.matcher(item.title).find())
          .forEach(model::addElement);
    for (ListItem item : selected) {
      int i = model.indexOf(item);
      list.addSelectionInterval(i, i);
    }
  });
}

References

2015/08/13

Create a custom drag ghost image

Code

class ListItemTransferHandler extends TransferHandler {
  @Override public int getSourceActions(JComponent c) {
    System.out.println("getSourceActions");
    if (!(c instanceof JList)) {
      return TransferHandler.NONE;
    }
    JList<?> source = (JList<?>) c;
    Point pt;
    if (compact) {
      int w = source.getFixedCellWidth();
      int h = source.getFixedCellHeight() - 20; // TODO: 20 ???
      setDragImage(createCompactDragImage(source, w, h));
      pt = new Point(w / 2, h);
    } else {
      setDragImage(createDragImage(source));
      pt = c.getMousePosition();
    }
    if (pt != null) {
      setDragImageOffset(pt);
    }
    return TransferHandler.MOVE; // TransferHandler.COPY_OR_MOVE;
  }

  private static BufferedImage createDragImage(JList source) {
    int w = source.getWidth();
    int h = source.getHeight();
    BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
    Graphics g = bi.getGraphics();
    DefaultListCellRenderer renderer =
      (DefaultListCellRenderer) source.getCellRenderer();
    for (int i : source.getSelectedIndices()) {
      Component c = renderer.getListCellRendererComponent(
          source, source.getModel().getElementAt(i), i, false, false);
      Rectangle rect = source.getCellBounds(i, i);
      SwingUtilities.paintComponent(g, c, source, rect);
    }
    g.dispose();
    return bi;
  }

  private BufferedImage createCompactDragImage(JList source, int w, int h) {
    BufferedImage br = null;
    if (w > 0 && h > 0) {
      br = source.getGraphicsConfiguration().createCompatibleImage(
          w, h, Transparency.TRANSLUCENT);
    } else {
      return null;
    }
    int[] selectedIndices = source.getSelectedIndices();
    int length = selectedIndices.length;
    Graphics g = br.getGraphics();
    DefaultListCellRenderer renderer =
      (DefaultListCellRenderer) source.getCellRenderer();
    int idx = selectedIndices[0];
    Object valueAt = source.getModel().getElementAt(idx);
    Component c = renderer.getListCellRendererComponent(
        source, valueAt, idx, false, false);
    Rectangle rect = source.getCellBounds(idx, idx);
    SwingUtilities.paintComponent(g, c, source, 0, 0, rect.width, rect.height);
    if (length > 1) {
      LABEL.setText(String.valueOf(length));
      Dimension d = LABEL.getPreferredSize();
      SwingUtilities.paintComponent(
          g, LABEL, source, (w - d.width) / 2, (h - d.height) / 2, d.width, d.height);
    }
    g.dispose();
    br.coerceData(true);
    return br;
  }
// ...

References

2013/10/15

Scrollable list of JCheckBoxes

Code

JList

class CheckBoxCellRenderer<E extends CheckBoxNode> extends JCheckBox
    implements ListCellRenderer<E>, MouseListener, MouseMotionListener {
  private int rollOverRowIndex = -1;

  @Override public Component getListCellRendererComponent(
        JList<? extends E> list, E value, int index,
        boolean isSelected, boolean cellHasFocus) {
    this.setOpaque(true);
    if (isSelected) {
      this.setBackground(list.getSelectionBackground());
      this.setForeground(list.getSelectionForeground());
    } else {
      this.setBackground(list.getBackground());
      this.setForeground(list.getForeground());
    }
    this.setSelected(value.selected);
    this.getModel().setRollover(index == rollOverRowIndex);
    this.setText(value.text);
    return this;
  }

  @Override public void mouseExited(MouseEvent e) {
    if (rollOverRowIndex >= 0) {
      JList<?> l = (JList<?>) e.getComponent();
      l.repaint(l.getCellBounds(rollOverRowIndex, rollOverRowIndex));
      rollOverRowIndex = -1;
    }
  }

  @Override public void mouseClicked(MouseEvent e) {
    if (e.getButton() == MouseEvent.BUTTON1) {
      JList<?> l = (JList<?>) e.getComponent();
      Point p = e.getPoint();
      int index  = l.locationToIndex(p);
      if (index >= 0) {
        @SuppressWarnings("unchecked")
        DefaultListModel<CheckBoxNode> m =
            (DefaultListModel<CheckBoxNode>) l.getModel();
        CheckBoxNode n = m.get(index);
        m.set(index, new CheckBoxNode(n.text, !n.selected));
        l.repaint(l.getCellBounds(index, index));
      }
    }
  }

  @Override public void mouseMoved(MouseEvent e) {
    JList<?> l = (JList<?>) e.getComponent();
    int index = l.locationToIndex(e.getPoint());
    if (index != rollOverRowIndex) {
      rollOverRowIndex = index;
      l.repaint();
    }
  }

  @Override public void mouseEntered(MouseEvent e)  {
    /* not needed */
  }

  @Override public void mousePressed(MouseEvent e)  {
    /* not needed */
  }

  @Override public void mouseReleased(MouseEvent e) {
    /* not needed */
  }

  @Override public void mouseDragged(MouseEvent e)  {
    /* not needed */
  }
}

// ...
JList list1 = new JList(model) {
  private CheckBoxCellRenderer renderer;
  @Override public void updateUI() {
    setForeground(null);
    setBackground(null);
    setSelectionForeground(null);
    setSelectionBackground(null);
    if (renderer != null) {
      removeMouseListener(renderer);
      removeMouseMotionListener(renderer);
    }
    super.updateUI();
    renderer = new CheckBoxCellRenderer();
    setCellRenderer(renderer);
    addMouseListener(renderer);
    addMouseMotionListener(renderer);
  }

  // @see SwingUtilities2.pointOutsidePrefSize(...)
  private boolean pointOutsidePrefSize(Point p) {
    int index = locationToIndex(p);
    DefaultListModel m = (DefaultListModel) getModel();
    CheckBoxNode n = (CheckBoxNode) m.get(index);
    Component c = getCellRenderer().getListCellRendererComponent(
                    this, n, index, false, false);
    // c.doLayout();
    Dimension d = c.getPreferredSize();
    Rectangle rect = getCellBounds(index, index);
    rect.width = d.width;
    return index < 0 || !rect.contains(p);
  }

  @Override protected void processMouseEvent(MouseEvent e) {
    if (!pointOutsidePrefSize(e.getPoint())) {
      super.processMouseEvent(e);
    }
  }

  @Override protected void processMouseMotionEvent(MouseEvent e) {
    if (!pointOutsidePrefSize(e.getPoint())) {
      super.processMouseMotionEvent(e);
    } else {
      e = new MouseEvent(
        (Component) e.getSource(), MouseEvent.MOUSE_EXITED, e.getWhen(),
        e.getModifiers(), e.getX(), e.getY(),
        e.getXOnScreen(), e.getYOnScreen(),
        e.getClickCount(), e.isPopupTrigger(), MouseEvent.NOBUTTON);
      super.processMouseEvent(e);
    }
  }
};

JTree

class CheckBoxNodeRenderer extends JCheckBox implements TreeCellRenderer {
  private TreeCellRenderer renderer = new DefaultTreeCellRenderer();
  @Override public Component getTreeCellRendererComponent(
    JTree tree, Object value, boolean selected, boolean expanded,
    boolean leaf, int row, boolean hasFocus) {
    if (leaf && value instanceof DefaultMutableTreeNode) {
      this.setOpaque(false);
      Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
      if (userObject instanceof CheckBoxNode) {
        CheckBoxNode node = (CheckBoxNode) userObject;
        this.setText(node.text);
        this.setSelected(node.selected);
      }
      return this;
    }
    return renderer.getTreeCellRendererComponent(
             tree, value, selected, expanded, leaf, row, hasFocus);
  }
}

class CheckBoxNodeEditor extends JCheckBox implements TreeCellEditor {
  private final JTree tree;
  public CheckBoxNodeEditor(JTree tree) {
    super();
    this.tree = tree;
    setOpaque(false);
    setFocusable(false);
    addActionListener(new ActionListener() {
      @Override public void actionPerformed(ActionEvent e) {
        stopCellEditing();
      }
    });
  }

  @Override public Component getTreeCellEditorComponent(
    JTree tree, Object value, boolean isSelected, boolean expanded,
    boolean leaf, int row) {
    if (leaf && value instanceof DefaultMutableTreeNode) {
      Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
      if (userObject instanceof CheckBoxNode) {
        this.setSelected(((CheckBoxNode) userObject).selected);
      } else {
        this.setSelected(false);
      }
      this.setText(value.toString());
    }
    return this;
  }

  @Override public Object getCellEditorValue() {
    return new CheckBoxNode(getText(), isSelected());
  }

  @Override public boolean isCellEditable(EventObject e) {
    return (e instanceof MouseEvent);
  }

  // Copied from AbstractCellEditor
  // protected EventListenerList listenerList = new EventListenerList();
  // transient protected ChangeEvent changeEvent = null;
  @Override public boolean shouldSelectCell(EventObject anEvent) {
    // ...

References

2012/10/29

Delete button in a JComboBox popup menu items

Code

class CellButtonsMouseListener extends MouseAdapter {
  @Override public void mouseMoved(MouseEvent e) {
    JList<?> list = (JList<?>) e.getComponent();
    Point pt = e.getPoint();
    int index = list.locationToIndex(pt);
    ButtonsRenderer<?> renderer = (ButtonsRenderer<?>) list.getCellRenderer();
    renderer.rolloverIndex = Objects.nonNull(getButton(list, pt, index)) ? index : -1;
    list.repaint();
  }

  @Override public void mousePressed(MouseEvent e) {
    e.getComponent().repaint();
  }

  @Override public void mouseReleased(MouseEvent e) {
    JList<?> list = (JList<?>) e.getComponent();
    Point pt = e.getPoint();
    int index = list.locationToIndex(pt);
    if (index >= 0) {
      JButton button = getButton(list, pt, index);
      if (Objects.nonNull(button)) {
        button.doClick();
      }
    }
    ((ButtonsRenderer<?>) list.getCellRenderer()).rolloverIndex = -1;
    list.repaint();
  }

  @Override public void mouseExited(MouseEvent e) {
    JList<?> list = (JList<?>) e.getComponent();
    ((ButtonsRenderer<?>) list.getCellRenderer()).rolloverIndex = -1;
  }

  private static <E> JButton getButton(JList<E> list, Point pt, int index) {
    E proto = list.getPrototypeCellValue();
    Component c = list.getCellRenderer().getListCellRendererComponent(
        list, proto, index, false, false);
    Rectangle r = list.getCellBounds(index, index);
    c.setBounds(r);
    // c.doLayout(); // may be needed for other layout managers (eg. FlowLayout)
    pt.translate(-r.x, -r.y);
    return Optional.ofNullable(SwingUtilities.getDeepestComponentAt(c, pt.x, pt.y))
        .filter(JButton.class::isInstance).map(JButton.class::cast).orElse(null);
  }
}

References