お次はカーソルを自前で描画するようにしてみる。 モード明示で、挿入モードとコマンドモードでテキストカーソル幅を変えてみたが、 以下の問題があった。
本稿は上記の解決編だ。
まずは自分でテキストカーソルを描画する前に、QPlainTextEdit がテキストカーソルを描画しないようにする。 そのためには、コンストラクタで setCursorWidth(0) をコールして、カーソル幅をゼロにすればOKだ。
1: ViEditView::ViEditView(QWidget *parent)
2: : m_mode(CMD), QPlainTextEdit(parent)
3: {
4: .....
5: setCursorWidth(0); // QPlainTextEdit が用意しているカーソルは非表示
6: }
これまでは ViEditView::onCursorPositionChanged() でテキストカーソル位置文字の幅を setCursorWidth(int) に渡していたが、 テキストカーソル幅を保持するメンバ変数を追加し、そこに文字幅を保存するように修正する。
1: class ViEditView : public QPlainTextEdit
2: {
3: .....
4: private:
5: int m_cursorWidth; // テキストカーソル表示幅
6: uint m_tickCount; // テキストカーソル点滅用に使用する変数
7: };
1: void ViEditView::onCursorPositionChanged()
2: {
3: if( mode() == CMD ) {
4: .....
5: //setCursorWidth(wd);
6: m_cursorWidth = wd;
7: } else {
8: //setCursorWidth(1);
9: m_cursorWidth = 1;
10: }
11: }
ここまで準備ができれば、あとは paintEvent() で描画するだけだ。
1: void ViEditView::paintEvent(QPaintEvent * event)
2: {
3: drawCursor();
4: QPlainTextEdit::paintEvent(event);
5: }
6: void ViEditView::drawCursor()
7: {
8: QRect r = cursorRect();
9: r.setWidth(m_cursorWidth);
10: if( mode() == CMD )
11: r = QRect(r.left(), r.top() + r.height()/2, r.width(), r.height()/2);
12: QPainter painter(viewport());
13: painter.fillRect(r, Qt::red);
14: }
実際にカーソルを描画するメソッドを drawCursor() というそのまんまの名前にし、基底クラスの paintEvent() よりも先にコールするようにした。 これはカーソル矩形を描画した後にカーソル位置テキストを描画することで、カーソル位置文字を判別可能にするためだ。
カーソル描画メソッドの中では QPlainTextEdit::cursorRect() でカーソル位置を取得し、 コマンドモードの時は高さを半分にし、保存しておいた文字幅で描画している。
以上で下図の様にテキストカーソルが意図したように描画されるようになった。
しかし残念なことに、コマンドモードでカーソルを左に移動すると下図の様にテキストカーソルのゴミが表示されるようになってしまった。
いろいろ調査した結果、インバリデート(実際に再描画される)領域が正しくなくて、ゴミが残ってしまうのだと思われるのだが、
現時点では対処する方法がわからなかった。
本稿はカーソル問題の解決編だったはずなのだが、新たな問題が生まれてしまった。
次稿で、テキストそのものも自前描画する予定なので、その時に(hopefully)対処できるはずだ。
好みもあるとは思うが、おいら的にはテキストカーソルはやっぱり点滅していた方がいいので、点滅するようにしてみる。
点滅を実装する方法はいくつかあるが、本稿では、QPlainTextEdit がカーソル点滅のために paintEvent() をコールしてくれているので、 QElapsedTimer を使って一定時間ごとに描画・非描画を切り替えることにしてみる。
1: class ViEditView : public QPlainTextEdit
2: {
3: .....
4: private:
5: qint64 m_tickCount; // タイマー基準値
6: QElapsedTimer *m_timer; // タイマーオブジェクト
7: };
1: void ViEditView::paintEvent(QPaintEvent * event)
2: {
3: const int blinkPeriod = 1200; // 点滅周期、単位:ms
4: qint64 tc = m_timer->elapsed() - m_tickCount;
5: if( tc % blinkPeriod < blinkPeriod / 2 )
6: drawCursor();
7: QPlainTextEdit::paintEvent(event);
8: }
9: void ViEditView::setMode(Mode mode)
10: {
11: m_tickCount = m_timer->elapsed();
12: .....
13: }
以上でカーソル点滅の出来上がりだ。QPlainTextEdit の再描画周期と ViEditView のカーソル点滅周期がずれているので、 点滅周期がちょと変動気味だが、これもそのうち対処する。問題先延ばしだ。
本稿のソースコードは以下からDLできます。※ VS2008 でプロジェクトを作成しています。
http://vivi.dyndns.org/dist2/qvi-009-src.zip