テキストをマウスで選択する
さて、マルチフォントのテキスト描画ができるようになると、いろいろと面白い使い方ができるようになります。一つの利用例として、「マウス操作で選択できるテキストの描画」というのを考えてみましょう。
「選択できる」といっても、表示されているテキストが実際に選択できるわけではありません。選択されているように「見える」ということです。マウスポインタの位置をチェックし、テキストの一部の色を変えるなどすれば、その部分だけが選択されているように見えますね?
今回は、いろいろとやるべきことがありますので、まずはサンプルを見てください。これも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に設定してあるテキストを表示し、マウスでドラッグすることでその一部分を選択(されているように見せることが)できます。また、選択されたテキストの利用例として、選択されている部分がリアルタイムにクリップボードにコピーされるようにしておきました。マウスでテキストを選択し、他のアプリケーション(テキストエディタなど)に切り替えてペーストしてみると、ちゃんと選択された部分のテキストがペーストされます。
選択されたテキストの描画処理
ここでは、MouseAdapterとMouseMotionAdapterを実装して、「マウスをドラッグする」という操作のときの、ドラッグ開始位置と終了位置(あるいはドラッグ中の現時点の位置)を変数mx1,my1,mx2,my2に取り込む操作をしています。そして位置を変数に取り込んだら、すぐさまrepaintして表示を更新します。
実際に「選択されている」ように見える描画は、paintComponentで行っています。TextLayoutを使ってテキストの描画をした後、まず選択部分のテキストに関する情報を調べます。
TextHitInfo hi1 = tl1.hitTestChar(mx1 - nx,my1); TextHitInfo hi2 = tl1.hitTestChar(mx2 - nx,my2);
このTextHitInfoというのは、テキストの位置に関する情報のクラスです。TextLayoutのhitTestCharというメソッドを使い、引数で指定した位置にあるテキストに関するTextHitInfoインスタンスを取得します。これで、マウスのドラッグ開始位置と終了位置のテキストに関するTextHitInfoが得られます。
Rectangle2D bounds = tl1.getBounds(); Point point = new Point((int)(mx2 - nx),(int)(my2 - ny));
続いて、TextLayoutからテキストのRectangle2Dを取得し、現在のマウスポインタの位置からPointを作成します。これで、このPointがRectangle2Dの領域内にあるかどうかをチェックし、中にあれば、テキストの領域内を選択しているとみなし、選択部分の描画を行います。
では、選択されたテキストに関する描画を行いましょう。まずは、開始位置と終了位置が同じテキスト位置であった場合です。これはテキストが選択されていないということになりますから、インサーションポイントだけを描画します。
int start = hi1.getInsertionIndex(); int end = hi2.getInsertionIndex(); if (start == end) { g2.setPaint(Color.red); g2.draw(tl1.getCaretShape(hi1));
先に作成したTextHitInfoのgetInsertionIndexというメソッドでドラッグの開始位置と終了位置の文字のインデックス番号を調べ、両者が同じならばキャレットだけを描きます。これは、TextLayoutのgetCaretShapeを使います。引数にTextHitInfoインスタンスを指定すれば、その位置のインサーションポイントとなるShapeを得ることができます。このShapeを、Garaphics2Dのdrawで描画すれば、キャレットが描ける、というわけです。
もし、ドラッグ開始位置と終了位置が違っている場合には、テキストが選択されているということになりますからテキストが選択された状態に見えるような描画を行います。これはgetCaretShapeではなく、TextLayoutのgetLogicalHighlightShapeというメソッドを使います。
g2.fill(tl1.getLogicalHighlightShape(start,end));
これで、開始位置から終了位置までのテキスト部分を囲む矩形のシェイプが得られます。これをGraphics2Dのfillで描画することで、その部分を塗りつぶすことができます。これが「テキストを選択してハイライトしている表示」となるわけです。
これで、選択されている描画はできました。今回は単に上から半透明の色でテキストを塗りつぶしているだけですが、より正確に表現したいのであれば、選択されたテキスト部分の背景色アトリビュートを変更し、テキストを再描画すればよいでしょう。
最後に、選択されているテキスト部分を変数selstrに収めてやります。現在選択されているテキストを変数selstrに取り出しておきます。
selstr = msg.substring(start,end);
必要に応じてこの値を利用すれば、「選択されているテキスト」をいろいろと扱えるようになるでしょう。ここでは、一例としてクリップボードにコピーするようにしてみましたが、他にもいろいろと利用の方法はありそうですね。各自で考えてみましょう。
まとめ
今回はマルチフォントの描画とその応用例について説明しました。マルチフォントの描画というのは、普段、見たことのないクラスがいくつも登場してきますので、慣れない内はかなり面倒そうに感じることでしょう。ただ、やっていることの多くは非常に定型的な処理ですから、その手順さえ分かってしまえば、割と機械的に処理していくことができます。
また、テキストの選択では、「描画するテキスト」と「位置の情報」との間のやり取りについての理解が重要となります。マウスの位置情報からその場所にある文字を割り出したり、といった処理ですね。このあたりも、見慣れないクラスとメソッドのオンパレードですから、一読してもよく分からない人も多いかもしれません。実際にサンプルを動かしてみて、1つ1つのメソッドの役割をよく考えるようにしてください。
