SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Swing再入門

Swing再入門 マウスで選択可能なマルチフォントテキストの描画

第5回 マルチフォントの描画と応用例

テキストをマウスで選択する

 さて、マルチフォントのテキスト描画ができるようになると、いろいろと面白い使い方ができるようになります。一つの利用例として、「マウス操作で選択できるテキストの描画」というのを考えてみましょう。

 「選択できる」といっても、表示されているテキストが実際に選択できるわけではありません。選択されているように「見える」ということです。マウスポインタの位置をチェックし、テキストの一部の色を変えるなどすれば、その部分だけが選択されているように見えますね?

 今回は、いろいろとやるべきことがありますので、まずはサンプルを見てください。これもG2Panelとして実装させています。

頭の方にimport文を追加し、G2Panelを書き換える
// import文を追加
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;

/* Sample クラス部分は省略 */

class G2Panel extends JPanel {
    private static final long serialVersionUID = 1L;
    private float nx,ny,mx1,my1,mx2,my2;
    private String msg,selstr;
    private Font font;

    public G2Panel() {
        super();
        nx = 20;
        ny = 50;
        msg = "Welcome to Java 2D."; // 表示するテキスト
        selstr = "";
        font = new Font("Dialog",Font.BOLD,24);
        addMouseListener(new MyMouseAdapter());
        addMouseMotionListener(new MyMouseMotionAdapter());
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;
        AffineTransform tf1 = g2.getTransform();
        FontRenderContext fc1 = g2.getFontRenderContext();
        TextLayout tl1 = new TextLayout(msg,font,fc1);
        g2.translate(nx,ny);
        g2.setPaint(new Color(220,255,220));
        g2.fill(tl1.getBounds());
        g2.setPaint(Color.blue);
        tl1.draw(g2, 0, 0);
        // テキストモデルの文字の位置を調べる
        TextHitInfo hi1 = tl1.hitTestChar(mx1 - nx,my1);
          // translate幅だけ補正
        TextHitInfo hi2 = tl1.hitTestChar(mx2 - nx,my2);
          // translate幅だけ補正
        
        Rectangle2D bounds = tl1.getBounds();
        Point point = new Point((int)(mx2 - nx),(int)(my2 - ny));
        
        int start = hi1.getInsertionIndex();
        int end = hi2.getInsertionIndex();
        // テキスト領域内にポインタがあるかチェック
        if (bounds.contains(point)){
            if (start == end) {
                // キャレットの描画
                g2.setPaint(Color.red);
                g2.draw(tl1.getCaretShape(hi1));
            } else {
                // 選択テキストの領域を選択色で塗りつぶす
                g2.setPaint(new Color(255,125,125,100));
                g2.fill(tl1.getLogicalHighlightShape(start,end));
                // 選択されている部分をselstrに設定
                selstr = msg.substring(start,end);
                // とりあえず、selstrをクリップボードにコピー
                Clipboard clipboard =
                    Toolkit.getDefaultToolkit().getSystemClipboard();
                StringSelection selection =
                    new StringSelection(selstr);
                clipboard.setContents(selection, null);
            }
        }
    }

    class MyMouseAdapter extends MouseAdapter {
        // マウスプレス時の処理
        public void mousePressed(MouseEvent ev) {
            mx1 = ev.getX();
            my1 = ev.getY();
            mx2 = mx1;
            my2 = my1;
            repaint();
        }
        // マウスリリース時の処理
        public void mouseReleased(MouseEvent ev) {
            mx2 = ev.getX();
            my2 = ev.getY();
            repaint();
        }
    }

    class MyMouseMotionAdapter extends MouseMotionAdapter {
        // ドラッグ中の処理
        public void mouseDragged(MouseEvent ev) {
            mx2 = ev.getX();
            my2 = ev.getY();
            repaint();
        }
    }
}

 ここでは、変数msgに設定してあるテキストを表示し、マウスでドラッグすることでその一部分を選択(されているように見せることが)できます。また、選択されたテキストの利用例として、選択されている部分がリアルタイムにクリップボードにコピーされるようにしておきました。マウスでテキストを選択し、他のアプリケーション(テキストエディタなど)に切り替えてペーストしてみると、ちゃんと選択された部分のテキストがペーストされます。

選択されたテキストの描画処理

 ここでは、MouseAdapterMouseMotionAdapterを実装して、「マウスをドラッグする」という操作のときの、ドラッグ開始位置と終了位置(あるいはドラッグ中の現時点の位置)を変数mx1,my1,mx2,my2に取り込む操作をしています。そして位置を変数に取り込んだら、すぐさまrepaintして表示を更新します。

 実際に「選択されている」ように見える描画は、paintComponentで行っています。TextLayoutを使ってテキストの描画をした後、まず選択部分のテキストに関する情報を調べます。

TextHitInfo hi1 = tl1.hitTestChar(mx1 - nx,my1);
TextHitInfo hi2 = tl1.hitTestChar(mx2 - nx,my2);

 このTextHitInfoというのは、テキストの位置に関する情報のクラスです。TextLayouthitTestCharというメソッドを使い、引数で指定した位置にあるテキストに関するTextHitInfoインスタンスを取得します。これで、マウスのドラッグ開始位置と終了位置のテキストに関するTextHitInfoが得られます。

Rectangle2D bounds = tl1.getBounds();
Point point = new Point((int)(mx2 - nx),(int)(my2 - ny));

 続いて、TextLayoutからテキストのRectangle2Dを取得し、現在のマウスポインタの位置からPointを作成します。これで、このPointRectangle2Dの領域内にあるかどうかをチェックし、中にあれば、テキストの領域内を選択しているとみなし、選択部分の描画を行います。

 では、選択されたテキストに関する描画を行いましょう。まずは、開始位置と終了位置が同じテキスト位置であった場合です。これはテキストが選択されていないということになりますから、インサーションポイントだけを描画します。

int start = hi1.getInsertionIndex();
int end = hi2.getInsertionIndex();
if (start == end) {
    g2.setPaint(Color.red);
    g2.draw(tl1.getCaretShape(hi1));

 先に作成したTextHitInfogetInsertionIndexというメソッドでドラッグの開始位置と終了位置の文字のインデックス番号を調べ、両者が同じならばキャレットだけを描きます。これは、TextLayoutgetCaretShapeを使います。引数にTextHitInfoインスタンスを指定すれば、その位置のインサーションポイントとなるShapeを得ることができます。このShapeを、Garaphics2Ddrawで描画すれば、キャレットが描ける、というわけです。

 もし、ドラッグ開始位置と終了位置が違っている場合には、テキストが選択されているということになりますからテキストが選択された状態に見えるような描画を行います。これはgetCaretShapeではなく、TextLayoutgetLogicalHighlightShapeというメソッドを使います。

g2.fill(tl1.getLogicalHighlightShape(start,end));

 これで、開始位置から終了位置までのテキスト部分を囲む矩形のシェイプが得られます。これをGraphics2Dfillで描画することで、その部分を塗りつぶすことができます。これが「テキストを選択してハイライトしている表示」となるわけです。

 これで、選択されている描画はできました。今回は単に上から半透明の色でテキストを塗りつぶしているだけですが、より正確に表現したいのであれば、選択されたテキスト部分の背景色アトリビュートを変更し、テキストを再描画すればよいでしょう。

 最後に、選択されているテキスト部分を変数selstrに収めてやります。現在選択されているテキストを変数selstrに取り出しておきます。

selstr = msg.substring(start,end);

 必要に応じてこの値を利用すれば、「選択されているテキスト」をいろいろと扱えるようになるでしょう。ここでは、一例としてクリップボードにコピーするようにしてみましたが、他にもいろいろと利用の方法はありそうですね。各自で考えてみましょう。

まとめ

 今回はマルチフォントの描画とその応用例について説明しました。マルチフォントの描画というのは、普段、見たことのないクラスがいくつも登場してきますので、慣れない内はかなり面倒そうに感じることでしょう。ただ、やっていることの多くは非常に定型的な処理ですから、その手順さえ分かってしまえば、割と機械的に処理していくことができます。

 また、テキストの選択では、「描画するテキスト」と「位置の情報」との間のやり取りについての理解が重要となります。マウスの位置情報からその場所にある文字を割り出したり、といった処理ですね。このあたりも、見慣れないクラスとメソッドのオンパレードですから、一読してもよく分からない人も多いかもしれません。実際にサンプルを動かしてみて、1つ1つのメソッドの役割をよく考えるようにしてください。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
Swing再入門連載記事一覧

もっと読む

この記事の著者

掌田 津耶乃(ショウダ ツヤノ)

三文ライター&三流プログラマ。主にビギナーに向けたプログラミング関連の執筆を中心に活動している。※現在、入門ドキュメントサイト「libro」、カード型学習サイト「CARD.tuyano.com」を公開...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/2598 2008/06/19 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー