懸案だったテキストカーソルゴミ表示事件が片付いたので、次は EOF, 改行マークを表示して、よりテキストエディタっぽくしてみる。
QPlainTextEdit 派生クラスの描画を好きに変えるには paintEvent() をリインプリメントすればいいのだが、
基底クラスの処理を省くとIME入力時のオンザスポット表示がうまくいかないなどの副作用がある。
もちろん必要な処理をすべて自分で記述してやればいいのだが、既に存在しているコードと同じものをわざわざ自分で記述するのは
時間の面でも安全性の面でも好ましくない。
かと言って Qt のソースを流用しちゃうと、おいらが記述したコードが大嫌いで不自由な LGPL に汚染されてしまう。
それは何としても避けたい。
んで、QPlainTextEdit の文字表示は QTextLayout が担っていて、draw() メソッドなどが仮想関数になっていないので、
一部の機能をリライトするのが結構難しいのだ。
存在する機能はそのままにして、簡単に EOF や 改行やタブや全角空白を認識しやすいように表示するにはどうしたらいいのかと、 思い悩んでいたのだが、簡単な方法を寝てる時に思いついた。
要は、paintEvent() の中で、EOF 位置座標をなんらかの方法で取得し、そこにEOFマークを描画すればいいのだ。
EOF 位置座標を取得するには、テキストカーソルを文章末尾に移動し、
QRect QPlainTextEdit::cursorRect ( const QTextCursor &) const
を呼んであげればいいのだ。なんとストレートで簡単明瞭ではないか。
何故今までこの方法に気がつかなかったか我ながら不思議なくらいだ。
1: void ViEditView::paintEvent(QPaintEvent * event) 2: { 3: ..... 4: setCursorWidth(0); 5: QPlainTextEdit::paintEvent(event); 6: setCursorWidth(m_cursorWidth); 7: 8: QTextCursor cur = textCursor(); 9: cur.movePosition(QTextCursor::End); // EOF 位置に移動 10: QRect r = cursorRect(cur); // EOF 位置の座標をゲット 11: QPainter painter(viewport()); 12: painter.setPen(Qt::blue); 13: painter.drawText(QPointF(r.left(), r.bottom()), "[EOF]"); // EOF マークを描画 14: ..... 15: }
というわけで、EOFマークを表示するためのソースコードは上記のようになる。
8行目でテキストカーソルオブジェクトをコピーし、9行目でEOFの位置に移動している。 このようなことを行うと画面上のカーソルが本当に移動してしまうのではないかと心配する人もいるかもしれないが、 コピーしたテキストカーソルオブジェクトを移動しているだけなので、画面には反映されない。 画面に反映させたい場合は setTextCursor(const QTextCursor &) を呼んであげるとよい。
※ パフォーマンス的にはEOFマーク表示座標が画面外の場合は QPainter の準備などを行わない方が良いかもしれない。 が、そこまで気にする必要もないだろう。
この方法が素晴らしいのは、下図の様に IME入力中にオンザスポットになるのはもちろん、 変換途中でも変換ウィンドウの下の文字列をちゃんと右に避けてくれるという点だ。 延々と文句を言ってきた気がするが、やっぱりQPlainTextEdit は素晴らしいぞ。
※ 上図を眺めていまさらながら気がついたのだが、変換候補ウィンドウが上にズレて、変換文字列を隠してしまっているのは問題だ。
今度こそ上部余白の分だけズレているように思えるが、後でちゃんと調査して対処する。
EOF と同様の手法で改行マークも描画してみよう。
firstVisibleBlock() で、画面最上部の QTextBlock オブジェクトを取得し、それが尽きるか画面下部を超えるまでループを回す。
block.position() でブロック先頭位置(文書先頭からのオフセット値)を取得し、それを QTextCursor に設定し、
movePosition(QTextCursor::EndOfBlock) でブロック末尾に移動。
あとは EOF の時と同じ様に cursorRect() で位置を取得し、そこに改行マークを描画するだけだ。
コードは下記の様になる。
1: void ViEditView::paintEvent(QPaintEvent * event) 2: { 3: ..... 4: const int bottom = viewport()->rect().height(); 5: QPainter painter(viewport()); 6: painter.setPen(Qt::blue); 7: QTextCursor cur = textCursor(); 8: cur.movePosition(QTextCursor::End); 9: const int posEOF = cur.position(); 10: QTextBlock block = firstVisibleBlock(); 11: while( block.isValid() ) { 12: cur.setPosition(block.position()); // ブロック先頭位置 13: cur.movePosition(QTextCursor::EndOfBlock); // ブロック末尾移動 14: if( cur.position() == posEOF ) // 最後に改行がなく、EOF だった場合 15: break; 16: QRect r = cursorRect(cur); 17: if( r.top() >= bottom ) break; // 画面外の場合 18: painter.drawText(QPointF(r.left(), r.bottom()), "←"); 19: block = block.next(); 20: } 21: cur.movePosition(QTextCursor::End); 22: QRect r = cursorRect(cur); 23: painter.drawText(QPointF(r.left(), r.bottom()), "[EOF]"); 24: ..... 25: }
画面ショットを下図に示す。現在は手抜きで、改行マークに全角文字の“←”を使用しているのでビジュアル的にはいまいちだが、 改行マークが描画されたメリットは大きい。