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