Lazarus等で使えるSynEditの話です
単語であればGetWordAtRowColを使えばよいのですが
ちょっと特殊な事をしたい場合の対策法です
というか GetWordAtRowCol や GetWordBoundsAtRowColは現行のLazarus 内(ver 1.0と表示される)では多バイト文字で取得位置がズレるようですから。
たぶん GetWordBoundsAtRowColに XY.X:=PhysicalToLogicalCol(LINE.XY.Y-1,XY.X): をいれたらよさそうに思うけど、ちょっと怖い。
protectedメンバーを無理やり使う
GetWordAtRowColの動作を見ると FTheLinesViewを見ていますが、これはPrivateです。どうしようもありません。 しかしFTheLinesViewはTextViewというプロパティで参照出ます。 当然使いたいのですが、これがprotectedなのです。
そこで public にしただけのクラスを
type TMySynEdit = class(TSynEdit) public property TextView; end;
と実装(implementation)に作って無理やり呼び出します。
実際のコード例
type
TMySynEdit = class(TSynEdit)
public
property TextView;
end;
{マウス座標の文字が "hoge"ならhogeを返す}
function GetBraketWordInSynEdit(const ed: TSynEdit; mouse: TPoint): String;
var
ps: TPoint;
sx, ex: Integer;
s: String;
begin
Result := '';
ps := ed.PixelsToRowColumn(mouse); //画面上の文字位置を得る
s := TMySynEdit(ed).TextView[ps.Y - 1];//その行の文字を得る
if s = '' then exit;
if length(s) < 3 then exit;//次の処理が重いので軽くするために
sx := ed.PhysicalToLogicalCol(s, ps.Y - 1, ps.X);//画面上の位置のbyte番目を得る
if sx > length(s) then exit;
if not (s[sx] in ['_', '0'..'9', 'a'..'z', 'A'..'Z']) then exit;
ex := sx;
Dec(sx);
while s[sx] <> '"' do begin
Dec(sx);
if sx < 1 then exit;
end;
Inc(ex);
while s[ex] <> '"' do begin
Inc(ex);
if s[ex] = #0 then exit;
end;
Result := copy(s, sx + 1, ex - sx - 1);
end;
呼び出し方
//SynEditのOnMouseMoveイベントにて
procedure TForm1.SynEdit1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
s: String;
ps: TPoint;
begin
sr.X := X;
sr.Y := y;
s := GetBraketWordInSynEdit(SynMemo, sr);
//この後に sを使う処理を描く
今回の失敗談
AnsiStringであればバイト位置と表示位置は固定幅フォントなら一致するので楽でした。
でもLazarusの内部文字はUTF8です
UTF8だと日本語文字の多くは2byte以上になってしまいます。可変長ですし、外国語だと文字幅も半角になったりします。LazarusのIDEも他のエディタでは全角なのに半角になったりする文字もあります。ややこしそうだ。という事で、
最初はPhysicalToLogicalColを呼ばなくてもAnsiStringに代入すれば日本語だけなら問題ないだろ程度に考えていたのですが、代入しても中身はUTF8だし、UTF8toAnsiとかを呼び出しても変換されない。WideStringにしてからAnsiStringに代入してみたりいろいろやってもダメ。
遠回りでした。