要旨:
属性つき文字列を扱うテキストエディタにはテキストボックス内のフォント属性を設定・操作する機能、
属性付き文字列をテキストボックスの境界で折り返して行に割り当てる機能が必要である。
|
使用する主なJava SEのクラス:java.awt.event.MouseListener, MouseMotionListener, MouseEvent
|
このページで>説明するクラス:
LineBreaker, Line,
StringTokenizerEx, CaretPosition
|
1. LineBreaker
戻る=>page top
1.1 概要
java.awt.font.LineBreakMeasurerを使って、属性つき文字列を表示領域の幅に収まるよう複数行に割り付ける。
LineBreakMeasurerは"\n" (改行)があるとうまく処理できないので、"\n"
(改行)処理専用のメソッドを用意している。
行のテキスト表現はjava.awt.font.TextLayout。複数行になるのでTextLayoutの配列で表す。
また各行が、画面のどこに表示するかを示す位置情報をPoint2D[]の配列に格納する。
:
テキストインデックスとLineBreaker実行後のインデックス
つぎの変数で表す。
◦ textIndex - テキストインデックス。
◦ lineindex - LineBreaker実行後に決まる行インデックス。
◦ columnIndex - LineBreaker実行後に決まる行内のテキストインデックス。
: 改行の扱い
1行目の最後は改行が入っている。すなわち、textLayout[0]内のテキストは、
”MILITARY Jokes\n”。ここでもう一回改行を押すと、textLayout[1]には”\n”だけが入ることになる。
図(a) TextLayoutオブジェクト
|
図(b) TextLayoutのパラメータ>
|
図 1.1 LineBreaker
1.2 API
public class LineBreaker
フィールド |
説明
|
textIterator
|
private AttributedCharacterIterator textIterator
表示テキスト
|
textBox |
private TextBox textBox
このオブジェクトを使うTextBoxオブジェクト |
textArea
|
private Rectangle2D textArea
テキスト表示範囲
|
textAlign
|
private int textAlign
テキストの水平方向配置。左寄り/中央/右寄りで整数値は0/1/2。
|
lineSpace
|
private int lineSpace
行間隔
|
lineList
|
private ArrayList lineList
Lineオブジェクトを格納するリスト。
Lineオブジェクトに各行のテキスト情報、TextLayout情報、TextLayoutの画面位置情報などテキストの割付に関する全ての情報を格納する。
|
widthLimit
|
private int widthLimit
textAreaの幅が0のとき、文字の折り返し幅をこの値にする。
|
メソッド
|
説明
|
コンストラクタ |
public LineBreaker(){} |
setData
|
public void setData(AttributedCharacterIterator textIterator, TextBox textBox)
・textBoxをフィールド変数に設定する。
また textBoxのデータを参照して、フィールド変数textArea, textAlign, lineSpaceに値を設定する。
・textIterator がnullでなければ、createMultipleLinesメソッドでLineオブジェクトを作成しArrayListに出力する。
|
createMultipleLines
|
private void createMultipleLines()
・createLineBreakPositions メソッドを呼ぶ。
行の分割位置を出力。分割位置は先頭からの文字数で表す。
・各行のTextLayoutを格納するtextLayouts (TextLayout[])を作成。
AttributedStringUtil.getTextLayoutStringメソッドでAttributedStringを取得し、TextLayout[])を作成する。
: TextLayoutオブジェクトに格納された"\n" (Line feed)
"\n" (Line feed)をTextLayoutに格納してTextLayout.drawメソッドで表示(TextBox.drawText)しても、何も表示されずテキスト選択(TextBox.setCaretPositionOrSelection)もできない。このため"\n" (Line feed)を" " (スペース)または"↲"で置き換えて表示する。
AttributedStringUtil, CommittedTextContainerなど属性つき文字列を処理するクラスでは、"\n" (Line feed)をそのまま扱い表示だけ他の文字で置き換えるようにするため、このメソッドでで処理する。
・各行のStringデータをlayoutStrings[]に格納。
・createTextLayoutPositionsメソッドを呼ぶ。
各TextLayoutの表示位置を配列textLayoutPositionsに出力。
配列textLayoutPositionsのX座標はTextLayoutの左端、Y座標はTextlayoutのbaseline位置(上図参照)。
・Lineオブジェクト作成
breakPositions, textLayoutPositions, textLayouts, layoutStringsから各行のLineオブジェクトを作成してフィールド変数LineListに追加。
|
createLineBreakPositions
|
private int[] createLineBreakPositions(AttributedCharacterIterator textIterator,
float formatWidth)
行の分割位置を文字数で出力。出力データの配列数は「作成される行数+1」でbreakPositions[0]=0。
・StringTokenizerEx:Line Feed(\n)で文字列を分割。
・Line Feed(\n)で分割された文字列に対しLineBreakMeasurerを適用して複数行に分割。
|
createTextLayout
Positions
|
private Point2D[] createTextLayoutPositions(TextLayout[] layout)
・TextAreaの左上隅点から配置する。
・TextLayoutのAscent, Descentを取得してY値を更新
(注)X位置はTextLayoutの左端、Y位置はTextlayoutのbaseline位置。
|
createTextLayoutBounds
|
private Rectangle2D[] createTextLayoutBounds(TextLayout[] layout, Point2D[]
textLayoutPositions)
TextLayoutを囲むRectangle2Dを作成
|
getString
|
public String getString(CharacterIterator iterator, int begin, int end)
フィールド変数textIteratorから[begin, end]区間のStringデータを返す。
|
getTextLayouts
|
public TextLayout[] getTextLayouts()
各行に対応するTextLayoutを配列で返す。
TextLayoutの数が0(表示テキストがない)の場合、長さ0の配列またはnullを返す。
|
getTextLayout
|
public TextLayout getTextLayout(int lineIndex)
lineIndexで指定された行のTextLayoutを返す。
TextLayoutがない場合、またはindex<0の場合、nullを返す。
|
getTextLayoutStrings
|
public String[] getTextLayoutStrings()
各TextLayoutに含まれるテキストをStringの配列で返す。
表示テキストがない場合、長さ0の配列またはnullを返す。
|
getTextLayoutString
|
public String getTextLayoutString(int lineIndex)
lineIndex番目のTextLayoutに含まれるテキストをStringで返す。
lineIndex<0の場合、表示テキストがない場合はnullを返す。
|
getPreceedingString
|
private String getPreceedingString(int lineIndex, int columnIndex)
lineIndexが指すTextLayout内のcolumnIndex番目より手前のテキストを返す。
(lineIndex, columnIndex) => 図5.1
|
getPreceedingCharacter
|
private String getPreceedingCharacter(int lineIndex, int columnIndex)
lineIndexが指すTextLayout内のcolumnIndex番目の手前の1文字を返す。
|
getTextLayoutPositions
|
public Point2D[] getTextLayoutPositions()
各行のTextLayoutの画面上での位置を配列で返す。
X位置はTextLayoutの左端、Y位置はTextlayoutのbaseline位置。
表示テキストがない場合、長さ0の配列またはnullを返す。
=>図5.1 TextLayoutのパラメータ
|
getBounds
|
public Rectangle2D[] getBounds()
各行のTextlayoutを囲む矩形を配列で返す。
TextlayoutのgetBoundsメソッドで取り出す矩形のx,yは0であるが、ここで返す矩形はcreateTextLayoutPositionsメソッドで決めた、画面上での位置を反映している。
表示テキストがない場合、長さ0の配列またはnullを返す。
|
getBounds
|
public Rectangle2D getBounds(int lineIndex)
lineIndexで指定したTextLayoutを囲む矩形を返す。
lineIndex<0の場合、TextLayoutがない場合はnullを返す。
|
getCharacterCount
|
public int getCharacterCount()
フィールド変数textIteratorの文字数を返す。
|
getCharacterCount
|
public int getCharacterCount(int lineIndex)
lineIndexで指定したTextLayout内の文字数を返す。
|
getCharacterCount
|
public int getCharacterCount(int lineIndex, int columnIndex)
テキストの先頭からlineIndexのTextLayout内のcolumnIndexまでの文字数を返す。
(lineIndex, columnIndex) => 図5.1
|
getLineColumnIndices |
public int[] getLineColumnIndices(int textIndex)
textIndexを変換してlineIndexとcolumnIndexを返す。int配列の戻り値indicesは、indices[0]がlineIndex、indices[1]がcolumnIndexである。 |
getTextIndex |
public int getTextIndex(int lineIndex, int columnIndex)
line index (lineIndex) とcolumn index (columnIndex)を変換してテキストインデックス(textIndex)を返す。
(lineIndex, columnIndex) => 図5.1 |
updateCaretPosition
|
public CaretPosition updateCaretPosition(int textIndex)
廃止。CaretPosition.updateCaretPositionを使用する。
引数:
textIndex - テキストインデックス.
戻り値:
CaretPosition オブジェクト.
Processing:
このメソッドは上記のgetLineColumnIndices メソッドを使えば簡単に実現できる。
|
getCaretPosition
|
public CaretPosition getCaretPosition(int lineIndex, columnIndex)
廃止。CaretPosition.getCaretPositionを使用する。
引数:
lineIndex - 行数で、getTextLayoutsで取り出すTextLayoutの配列の番号。
columnIndex - lineIndexが指すTextLayout内での文字位置。
処理:
lineIndex、columnIndexから、その手前の文字数をカウントして動機の取れたCretPositionを返す。
TextBox. getCaretPositionAtMouseから呼ばれる。
参照=>CaretPositionの役割
(注) columnIndex>0で直前の文字が\n(改行)ならば\nの前のキャレット位置を返す。
|
getOffsetCaretPosition
|
public CaretPosition getOffsetCaretPosition(CaretPosition caretPosition,
int offset)
廃止。CaretPosition.columnOffsetを使用する。
引数:
caretPosition - 現在のテキストカーソル(Caret)に位置を表す CaretPositionオブジェクト。
offset - 文字数でのオフセット量。マイナス値を指定してもよい。
戻り値:
オフセット位置のCaretPositionオブジェクト。
処理:
引数のcaretPositionの位置から、 offsetの長さのテキストを追加した場合に、追加テキストの後の位置を新しいCaretPositionとして返す。下の同名メソッドを使う。
offsetはプラス値、マイナス値のどちらでも良いが、オフセットした位置がLineBreakerが保持するテキストの範囲を超えることはできない。
このメソッドはTextBox.drawCaretにおいてcomposedText(未確定テキスト)がある場合にcaretの位置を求めるのに使われる。
またTextBox.keyPressedメソッドでHome、Endキーを押してキャレットを左右に移動するために使用する。
上下に移動するにはgetLineOffsetCaretPositionメソッドを使う。
|
getOffsetCaretPosition
|
public CaretPosition getOffsetCaretPosition(int lineIndex, int columnIndex,
int offset)
廃止。CaretPosition.offsetCaretPositionを使用する。
引数:
lineIndex - 行インデックス (lineIndex=0,1, 2,....)
columnIndex - カラムインデックス
(columnIndex=1, 2,....)
offset - 文字数でのオフセット量。マイナス値を指定してもよい。
戻り値:
オフセット位置のCaretPositionオブジェクト。
処理:
TextLayout番号、TextLayout内位置を指定しこれに文字数offset(マイナス値も可)を加えたCaretPositionを返す。
updateCaretPositionメソッドで計算する。
|
getLineOffsetCaret
Position
|
public CaretPosition getLineOffsetCaretPosition(CaretPosition caretPosition,
int lineOffset)
廃止。CaretPosition.lineOffsetを使用する。
引数:
caretPosition - 現在のテキストカーソル(Caret)に位置を表す CaretPositionオブジェクト。
lineOffset - 行数でのオフセット量。マイナス値を指定してもよい。
戻り値: オフセット位置のCaretPositionオブジェクト。
処理:
引数のcaretPositionの位置から、上または下へ一行ずらしたCaretPositionを返す。
lineOffset<0の時は上の行へ、lineOffset>0は下の行へ移す。移った行の文字数が少ない場合、キャレットの位置は、その行の一番後ろになる。但し、最後の文字が\n(改行)の場合は、\nの前の位置とする。
このメソッドはTextBox.keyPressedメソッドでUp、Downキーを押してキャレットを上下に移動するために使用する。
|
getCaretRectangle
|
public Rectangle getCaretRectangle(CaretPosition caretPosition, int offset)
引数:
caretPosition - 現在のキャレット(Caret、テキストカーソル)の位置を表す
CaretPositionオブジェクト。
offset - 文字数でのオフセット値。マイナス値を指定してもよい。
戻り値:
オフセットを加えた位置に表示するキャレット図形(Rectangle)を返す。
処理:
このメソッドは、TextBoxのgetTextLocation
とdrawCaretから呼ばれる。
オフセット値は、composedText(未確定テキスト)が存在し、その後ろにキャレット(テキストカーソル)に
表示するときに使われる。
(a)textIteratorがnullのとき、以下の処理を実行。
このケースはTextBoxにテキストが存在しない場合、つまり新規のTextBoxオブジェクトを作成した場合や、
テキストがすべて削除された場合に起きる(下図ケース(a))。
この場合、参考となる属性付き文字列(Java Class AttributedString)がないので、特にキャレット図形の高さが決まらない。
そこで空白の属性付き文字列と、それを格納する一時的なTextLayoutを作成し、
そのTextLayoutからascent、descent(Figure 1.1 (b))を取得してキャレット図形の高さを決める。
結論としてキャレット図形の左上隅点は、textAreaの左上隅点、
幅は0、高さは(ascent+descent)となる。
(b)引数caretPositionがテキストのある行の内側を指している場合、次の処理を行う。
これは未確定テキストと
確定テキストの
少なくとも一方が表示されているケースである(ケース(b))。
この場合、キャレット図形をTextLayoutクラス(Java API)のgetCaretShapesメソッドを使い、次のように取得する。
Shape[] carets=textLayout.getCaretShapes(index);
そしてキャレット図形(矩形)を戻り値carets[0]を参照して作成する。
(c)引数caretPositionがテキストのある行の終端を指している場合、次の処理を行う。
この場合、キャレットを次の行の先頭に表示する(ケース(c))。
(b)と同様な方法でキャレット図形を取得し、その位置を次の行の先頭に変更する。
これはascent、descenと
lineSpace値から計算できる。
|
|
|
ケース(a) |
ケース(b) |
ケース(c) |
図 キャレットの矩形(赤マーク) |
|
printTextLayoutPositions
|
public void printTextLayoutPositions()
各行のTextLayoutの位置をプリントする。
|
printTextLayouts
|
public void printTextLayouts()
各行のTextLayoutの文字数、テキストなどをプリントする。
|
printBounds
|
public void printBounds()
各行のTextLayoutを囲む矩形をプリントする。
|
rangeCheck |
private void rangeCheck(int index, int indexMax) throws RangeException
index>indexMaxならばRangeExceptionをスローする。
次のrangeErrorでエラーを検知したときに、e.printStackTrace()を呼び出すのが目的。 |
textRangeError |
private boolean textRangeError(int textIndex, String errMessage)
textIndex>(textIteratorの文字数)ならばエラーメッセージとStackTraceを出力する。 |
lineRangeError |
private boolean lineRangeError(int lineIndex, String errMessage)
lineIndex>this.lineList.size()ならばエラーメッセージとStackTraceを出力する。 |
lineColumnRangeError |
private boolean lineColumnRangeError(int lineIndex, int columnIndex, String
errMessage)
lineIndex>this.lineList.size()またはcolumnIndex>columMaxならばエラーメッセージとStackTraceを出力する。
: columnIndexはlineIndexが指す行の文字インデックス。columMaxはその行の文字数。 |
1.3 RangeException - LineBreakerの内部クラス
コード |
class RangeException extends Exception {
RangeException() {}
public String toString() {
return "range Exception";
}
}
|
2. Lineクラス
戻る=>page top
public class Line
LineBreakerで作成される各行を表すクラス。
フィールド
|
説明
|
startCharPosition
|
int startCharPosition
LineBreakerが保持する表示テキストに部分区間がこの行に割り付けられている。
部分区間の表示テキストでの開始位置。
|
endCharPosition
|
int endCharPosition
部分区間の表示テキストでの終了位置。
|
textLayoutPosition
|
Point2D textLayoutPosition
TextLayoutの左端ベースライン位置。
|
textLayout
|
TextLayout textLayout
この行のTextLayout。
|
textLayoutString
|
String textLayoutString
この行のテキスト。
|
メソッド
|
説明
|
コンストラクタ
|
public Line(int startCharPosition, int endCharPosition, Point2D textLayoutPosition,
TextLayout textLayout, String textLayoutString)
引数をフィールド変数に設定したオブジェクトを作成する。
|
getStartCharPosition
|
public int getStartCharPosition()
フィールド変数startCharPositionを返す。
|
getEndCharPosition
|
public int getEndCharPosition()
フィールド変数endCharPositionを返す。
|
getTextLayoutPosition
|
public Point2D getTextLayoutPosition()
フィールド変数textLayoutPositionを返す。
|
getTextLayout
|
public TextLayout getTextLayout()
フィールド変数textLayoutを返す。
|
getTextLayoutString
|
public String getTextLayoutString()
フィールド変数textLayoutStringを返す。
|
3. StringTokenizerExクラス
戻る=>page top
public class StringTokenizerEx
java.util.StringTokenizerと同様な機能を果たすクラス。
区切り文字に\n(改行)を指定すると、\nの後ろで文字列を分割する。
LineBreakerで使われる。
フィールド
|
説明
|
str
|
String str:文字列。
|
delim
|
String delim:区切り文字。
|
index
|
int index:文字列のindex。
|
メソッド
|
説明
|
コンストラクタ
|
public StringTokenizerEx(String str, String delim)
文字列と区切り文字を指定してオブジェクトを作成する。
|
hasMoreTokens
|
public boolean hasMoreTokens()
トークンがまだあるかどうかを返す。
|
nextToken
|
public String nextToken()
次のトークンを返す。
|
4. CaretPositionクラス
戻る=>page top
4.1 CaretPositionの使い方
(1)テキストカーソル(キャレット)の位置
テキストカーソルの位置は次の2通りの形式で表す。
=> 図5.1
(a)表示文字列での位置(一次元的な位置)
1文字目の前のキャレット位置を0、後を1とし文字に番号をふる。この一次元的な番号付けでのキャレット位置をtextIndexと書く。
(b)行番号とその行での位置(二次元的な位置)
行番号をlineIndexで表す(lineIndex=0,1,2...)。行内でのテキスト位置をcolumIndexで表す(columnIndex=0,1,2...)。1文字目の前の位置が0。
(2)textIndexと(lineIndex, columnIndex)の間の変換
(lineIndex, columnIndex)とtextIndexは、TextLayoutの先頭位置と末尾の位置を除いては1:1に対応するので変換できる。
但し、この変換に必要なTextLayoutの配列のデータを保持しているのは
LineBreakerオブジェクトなので、
変換は常にLineBreakerを参照して行う。
4.2 API
public class CaretPosition
フィールド
|
説明
|
lineIndex
|
int lineIndex
TexyLayout配列の要素番号(-1は無効を表す)。
|
columnIndex
|
int columIndex
TexyLayoutでのキャレット位置(-1は無効を表す)。
|
textIndex
|
int textIndex
文字列でのキャレット位置(-1は無効を表す)。
|
lineFeedOption |
boolean lineFeedOption
lineFeedOption=trueならば、\n(改行)の後のテキストカーソル(キャレット)位置は、次の行の先頭に位置ずける。
String str="";
if(lineFeedOption&&columnIndex>0){
str=lineBreaker.getPreceedingCharacter(lineIndex, columnIndex);
if(str.equals("\n")){
lineIndex++;
columnIndex=0;
}
}
: lineFeedOption
通常のテキストカーソル(キャレット)の処理ではlineFeedOption=trueを指定する。これはEnterキーを押すと改行され、次の行の先頭にテキストカーソルが表示される通常のワードプロセッサーと同様である。
またテキストの選択範囲の先頭文字の前と最後文字の後に2個のCaretPositionオブジェクトを使う場合には、lineFeedOption=falseを指定する。
=> TextBox.selStart, TextBox.selEnd
|
メソッド
|
説明
|
コンストラクタ
|
public CaretPosition(int lineIndex, int columnIndex)
new CaretPosition(-1, -1), new CaretPosition(0, 0)など自明なCaretPositionオブジェクトを作成するのに使う。
(textindex, lineIndex, lineColumnIndex)を整合性をもって設定するためには、staticメソッドCaretPosition.getCaretPositionを使う。
|
コンストラクタ
|
public CaretPosition(int lineIndex, int columnIndex, boolean lineFeedOption)
同上
|
コンストラクタ
|
public CaretPosition(int lineIndex, int columnIndex, int textIndex )
同上
|
コンストラクタ
|
public CaretPosition(int lineIndex, int columnIndex, int textIndex, boolean lineFeedOption)
同上
|
isValid |
public boolean isValid()
lineindex>=0, columnIndex>=0, textIndex>=0ならばtrueを返す。 |
getLineIndex
|
public int getLineIndex()
lineIndexを返す。
|
getColumnIndex
|
public int getColumnIndex()
positionInLineを返す。
|
getTextIndex
|
public int getTextIndex()
positionInTextを返す。
|
resetPosition |
public void resetPosition()
すべてのフィールド変数に0をセットする。 |
updateCaretPosition |
public void updateCaretPosition(int newTextIndex, LineBreaker lineBreaker, String callFrom)
引数:
newTextIndex - テキストインデックス.
lineBreaker - LineBreaker オブジェクト
callFrom - デバッグ用
処理:
このメソッドは、newTextIndexを与えて、フィールド変数lineIndex, columIndexを更新するもので、
LineBreaker.getLineColumnIndices メソッドを使う。
|
columnOffset |
public void columnOffset(int offset, LineBreaker lineBreaker)
引数:
offset - 水平方向オフセット量
lineBreaker - LineBreaker オブジェクト
処理:
このメソッドは矢印キーでテキストカーソルを左右に動かした場合にTextBox.keyPressedから呼ばれる。
下のoffsetCaretpositionをコールして処理する。
|
offsetCaretPosition |
private void offsetCaretPosition(int lineIndex, int columnIndex, int offset, LineBreaker lineBreaker)
引数:
lineIndex - 行インデックス
columIndex - カラムインデックス
offset - オフセット量
lineBreaker - LineBreaker オブジェクト
=> 図5.1, 5.1 CaretPositionの使い方
処理:
このメソッドは上のcolumnOffsetメソッドと下のlineOffsetメソッドから呼ばれる。
テキスト先頭から(lineIndex, columIndex)までの文字数をLineBreaker.getCharacterCountメソッドでカウントし、オフセット分を加え新しいフィールド変数textIndexの値を計算し、updateCaretPositionメソッドで更新する。
|
lineOffset |
public void lineOffset(int lineOffset, LineBreaker lineBreaker)
引数:
lineOffset - 垂直方向オフセット量
lineBreaker - LineBreaker オブジェクト
処理:
このメソッドは矢印キーでテキストカーソルを上下に動かした場合にTextBox.keyPressedから呼ばれる。
上のoffsetCaretpositionをコールして処理する。
|
toString
|
public String toString()
キャレット位置の文字列表現を返す。
|
getCaretPosition (static) |
public static CaretPosition getCaretPosition(int textIndex, boolean lineFeedOption,
LineBreaker lineBreaker, String callFrom)
引数:
textIndex - テキストインデックス.
lineFeedOption - 改行オプション(フィールド変数lineFeedOption参照)
lineBreaker - LineBreaker オブジェクト
callFrom - 呼び出し元のクラス、メソッド名称(デバッグ用)
戻り値:
CaretPosition - CaretPosition オブジェクト.
処理:
このメソッドは、textIndexからlineIndex, columIndexを計算して新しいCaretPositionオブジェクトを作成する。
LineBreaker.getLineColumnIndices メソッドを使う。
|
getCaretPosition (static) |
public static CaretPosition getCaretPosition(int lineIndex, int columnIndex,
boolean lineFeedOption, LineBreaker lineBreaker, String callFrom)
引数:
lineIndex - 行インデックス
columIndex - カラムインデックス
lineFeedOption - 改行オプション(フィールド変数lineFeedOption参照)
lineBreaker - LineBreaker オブジェクト
callFrom - 呼び出し元のクラス、メソッド名称(デバッグ用)
=> 図5.1, 5.1 CaretPositionの使い方
戻り値:
CaretPosition - CaretPosition オブジェクト.
処理:
このメソッドは、lineIndex, columIndexからtextIndexを計算して新しいCaretPositionオブジェクトを作成する。
LineBreaker.getTextIndex メソッドを使う。
|
|