前へ 次へ
技術文章qvi

お次はカーソルを自前で描画するようにしてみる。 モード明示で、挿入モードとコマンドモードでテキストカーソル幅を変えてみたが、 以下の問題があった。

本稿は上記の解決編だ。

カーソル自前描画

まずは自分でテキストカーソルを描画する前に、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

Tweet


前へ 次へ
技術文章qvi