Google Tag Manager

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