お次はカーソルを自前で描画するようにしてみる。 モード明示で、挿入モードとコマンドモードでテキストカーソル幅を変えてみたが、 以下の問題があった。
本稿は上記の解決編だ。
まずは自分でテキストカーソルを描画する前に、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