概要

TitledBorderのタイトルをダブルクリックすると、GlassPaneに配置したJTextFieldをその上に表示して文字列を編集可能にします。

サンプルコード

class EditableTitledBorder extends TitledBorder implements MouseListener {
  protected final Container glassPane = new EditorGlassPane();
  protected final JTextField editor = new JTextField();
  protected final JLabel renderer = new JLabel();
  protected final Rectangle rect = new Rectangle();
  protected Component comp;
  protected final Action startEditing = new AbstractAction() {
    @Override public void actionPerformed(ActionEvent e) {
      if (comp instanceof JComponent) {
        Optional.ofNullable(((JComponent) comp).getRootPane())
        .ifPresent(r -> r.setGlassPane(glassPane));
      }
      glassPane.add(editor);
      glassPane.setVisible(true);

      Point p = SwingUtilities.convertPoint(comp, rect.getLocation(), glassPane);
      rect.setLocation(p);
      rect.grow(2, 2);
      editor.setBounds(rect);
      editor.setText(getTitle());
      editor.selectAll();
      editor.requestFocusInWindow();
    }
  };
  protected final Action cancelEditing = new AbstractAction() {
    @Override public void actionPerformed(ActionEvent e) {
      glassPane.setVisible(false);
    }
  };
  protected final Action renameTitle = new AbstractAction() {
    @Override public void actionPerformed(ActionEvent e) {
      if (!editor.getText().trim().isEmpty()) {
        setTitle(editor.getText());
      }
      glassPane.setVisible(false);
    }
  };
  // ...
View in GitHub: Java, Kotlin

解説

上記のサンプルでは、TitledBorderのタイトルをマウスでダブルクリックするとGlassPaneに配置したJTextFieldを重ねて表示されてタイトル文字列が編集可能になります。

  • タイトル文字列をダブルクリックで編集開始
  • JTextFieldにフォーカスがある状態でEnterキーを入力すると編集完了
  • JTextFieldにフォーカスがある状態でESCキーで編集をキャンセル

  • TitledBorderのタイトルはJLabelを使用して描画されているがprivateなのでその位置やサイズの取得が不可
  • 以下のようにTitledBorder#paintBorder(...)メソッドからJLabelの位置とサイズを計算しているコードを抜き出してその領域を取得
  • 所得した領域内でダブルクリックされたら編集を開始しJTextFieldの表示位置とサイズをその領域に合わせてGlassPane上に表示
private Rectangle getTitleBounds(Component c, int x, int y, int width, int height) {
  String title = getTitle();
  if (Objects.nonNull(title) && !title.isEmpty()) {
    Border border = getBorder();
    int edge = border instanceof TitledBorder ? 0 : EDGE_SPACING;
    JLabel label = getLabel(c);
    Dimension size = label.getPreferredSize();
    Insets insets = makeBorderInsets(border, c, new Insets(0, 0, 0, 0));

    int labelY = y;
    int labelH = size.height;
    int position = getTitlePosition();
    switch (position) {
    case ABOVE_TOP:
      insets.left = 0;
      insets.right = 0;
      break;
    case TOP:
      insets.top = edge + insets.top / 2 - labelH / 2;
      if (insets.top >= edge) {
        labelY += insets.top;
      }
      break;
    case BELOW_TOP:
      labelY += insets.top + edge;
      break;
    case ABOVE_BOTTOM:
      labelY += height - labelH - insets.bottom - edge;
      break;
    case BOTTOM:
      labelY += height - labelH;
      insets.bottom = edge + (insets.bottom - labelH) / 2;
      if (insets.bottom >= edge) {
        labelY -= insets.bottom;
      }
      break;
    case BELOW_BOTTOM:
      insets.left = 0;
      insets.right = 0;
      labelY += height - labelH;
      break;
    default:
      break;
    }
    insets.left += edge + TEXT_INSET_H;
    insets.right += edge + TEXT_INSET_H;

    int labelX = x;
    int labelW = width - insets.left - insets.right;
    if (labelW > size.width) {
      labelW = size.width;
    }
    switch (getJustification(c)) {
    case LEFT:
      labelX += insets.left;
      break;
    case RIGHT:
      labelX += width - insets.right - labelW;
      break;
    case CENTER:
      labelX += (width - labelW) / 2;
      break;
    default:
      break;
    }
    return new Rectangle(labelX, labelY, labelW, labelH);
  }
  return new Rectangle();
}

参考リンク

コメント