概要

JTextAreaの行番号を表示するコンポーネントを作成し、これを対象となるJTextAreaと同じJScrollPaneRowHeaderViewに設定します。

サンプルコード

class LineNumberView extends JComponent {
  private static final int MARGIN = 5;
  private final JTextArea textArea;
  private final FontMetrics fontMetrics;
  //private final int topInset;
  private final int fontAscent;
  private final int fontHeight;
  private final int fontDescent;
  private final int fontLeading;

  public LineNumberView(JTextArea textArea) {
    this.textArea = textArea;
    Font font   = textArea.getFont();
    fontMetrics = getFontMetrics(font);
    fontHeight  = fontMetrics.getHeight();
    fontAscent  = fontMetrics.getAscent();
    fontDescent = fontMetrics.getDescent();
    fontLeading = fontMetrics.getLeading();
    //topInset  = textArea.getInsets().top;

    textArea.getDocument().addDocumentListener(new DocumentListener() {
      @Override public void insertUpdate(DocumentEvent e) {
        repaint();
      }
      @Override public void removeUpdate(DocumentEvent e) {
        repaint();
      }
      @Override public void changedUpdate(DocumentEvent e) {}
    });
    textArea.addComponentListener(new ComponentAdapter() {
      @Override public void componentResized(ComponentEvent e) {
        revalidate();
        repaint();
      }
    });
    Insets i = textArea.getInsets();
    setBorder(BorderFactory.createCompoundBorder(
      BorderFactory.createMatteBorder(0, 0, 0, 1, Color.GRAY),
      BorderFactory.createEmptyBorder(i.top, MARGIN, i.bottom, MARGIN - 1)));
    setOpaque(true);
    setBackground(Color.WHITE);
    setFont(font);
  }
  private int getComponentWidth() {
    Document doc  = textArea.getDocument();
    Element root  = doc.getDefaultRootElement();
    int lineCount = root.getElementIndex(doc.getLength());
    int maxDigits = Math.max(3, String.valueOf(lineCount).length());
    Insets i = getBorder().getBorderInsets(this);
    return maxDigits * fontMetrics.stringWidth("0") + i.left + i.right;
    //return 48;
  }
  private int getLineAtPoint(int y) {
    Element root = textArea.getDocument().getDefaultRootElement();
    int pos = textArea.viewToModel(new Point(0, y));
    return root.getElementIndex(pos);
  }
  @Override public Dimension getPreferredSize() {
    return new Dimension(getComponentWidth(), textArea.getHeight());
  }
  @Override protected void paintComponent(Graphics g) {
    g.setColor(getBackground());
    Rectangle clip = g.getClipBounds();
    g.fillRect(clip.x, clip.y, clip.width, clip.height);

    g.setColor(getForeground());
    int base  = clip.y;
    int start = getLineAtPoint(base);
    int end   = getLineAtPoint(base + clip.height);
    int y     = start * fontHeight;
    int rmg   = getBorder().getBorderInsets(this).right;
    for (int i = start; i <= end; i++) {
      String text = String.valueOf(i + 1);
      int x = getComponentWidth() - rmg - fontMetrics.stringWidth(text);
      y += fontAscent;
      g.drawString(text, x, y);
      y += fontDescent + fontLeading;
    }
  }
}
view all

解説

Swing (Archive) - Advice for editor gutter implementation...を参考にして、JTextAreaに行番号を表示しています。

上記のサンプルで使用するJTextAreaは、使用するフォントや余白などは変更不可で、各行の高さは最後まで一定であると想定しています。


  • JTextPaneでの行番号表示
    • 折り返しても表示は前の行を継続
    • 折り返された行にも行番号を表示
      • Swing - line number in jtextpane
      • EditorKitを使って行番号を表示し、JTextPaneで折り返された行でも、表示に従って行番号を割り当てる

参考リンク

コメント

  • すごいね!私はこのような資料を探しています、どうも ありがとうございます。 -- CK
    • どういたしまして。 -- aterai
  • こちらのソースは非常に参考になります。現在趣味でエディタを作成しているのですが、JTextPaneDocumentJTextAreaに与えてカーソル位置を取得しています。JTextPaneで行番号、列番号を正確に取得できないものでしょうか? -- shusen
  • どうもです。JTextPaneだと、デフォルトの行の折り返しをどう扱うかで、行番号の表示が異なります。 -- aterai
    • リンクなどを本文に移動。 -- aterai
  • ご返答ありがとうございます。現在はこちらにある折り返し抑制のソースを利用させていただいているので、EditorKitを利用する分を試してみたいと思います。 -- shusen
  • EditorKitを利用した分をコピペして、とりあえずそのままコンパイル・実行してみました。ここでおかしいことがありまして、コマンドプロンプトから起動すると文字が挿入された行のみ行番号が表示されるのですが、自作エディタからProcessBuilderProcess"java Test"となるように呼び出すと、未入力の行も含めて全ての行番号が表示されます。コレはプロンプトの実効環境がおかしいのでしょうか? -- shusen
  • すみません、自己解決しました(たぶん)。ランタイムの問題で、jre1.6.0_01以下だと上記の状態になりました。jre1.6.0_02jre1.6.0_03ではちゃんと表示されました。 -- shusen
    • 情報ありがとうございます。直接は関係のない話ですが、バージョンかぁと何気にダウンロードサイトに行ったら、 サーバー落ちてるっぽいですねorz あとで見たらダウンロードのページがすこし変更されているみたいなので、単に更新中だっただけ?みたいです。 -- aterai
  • ありがとう。参考になりました。 -- ミルコ・マグカップ
  • ところで、これってJTextAreaとフォントを同じにしないとgetComponentWidth()で適切な幅が得られないと思います。 -- ミルコ・マグカップ
  • getComponentWidth()JTextAreaのフォントを基に幅を算出しているけど、実際に描画されるフォントはデフォルトのフォントだから -- ミルコ・マグカップ
    • こんばんは。「JTextAreaのフォントの幅...」は、確かにそうですね。この幅は行番号の表示幅とその右寄せにしか使ってないので、いっそ固定にしてもいいかもしれません。 -- aterai
    • LineNumberViewsetFontで同じフォントを使用するように修正しました(ミルコさん、ありがとうございました)。ついでにJTextAreaのフォントよりRowHeaderView(行番号表示用)のフォントサイズがすこし小さい場合などでも問題ないように、ベースラインを真面目に揃えて描画するよう修正しました。 -- aterai