本稿から、QPlainTextEdit 派生クラスの画面描画というQtのディープな世界に足を踏み入れよう。
何故ディープかというと、QPlainTextEdit とそれに関連するクラスの詳細を知っている必要があるからだ。
しかし、恐れる必要はない。QPlainTextEdit とその仲間たちは、付き合ってみれば充分面白くて役にたつ奴らだから。
さて、まずは行番号の表示だ。
行番号を表示する部分を確保する。とりあえず幅は数字8文字分に固定にしておく。
コードは以下の様に、setViewportMargins() で左側のマージンを指定すればOKだ。
1: ViEditView::ViEditView(QWidget *parent)
2: : m_mode(CMD), QPlainTextEdit(parent)
3: {
4: .....
5: m_lineNumberWidth = fontMetrics().width('8') * 6;
6: m_lineNumberAreaWidth = fontMetrics().width('8') * 8;
7: setViewportMargins(m_lineNumberAreaWidth, 0, 0, 0);
8: }
m_lineNumberAreaWidth は行番号表示エリア幅を保持するメンバ変数。 後で出てくるけど m_lineNumberWidth は表示エリア内に表示する行番号表示幅。2文字分マージンを確保している。
これだけで、下図の様にビュー左側に行番号を描画するエリアが確保される。
※ 上記コードは fontMetrics() が変更されないという前提に基づいている。 将来的には設定ダイアログでフォントフェース・サイズを変更可能にするので、あまりよろしくない。 が、その対処はその時に行うことにする。
マージン部分に行番号を描画するために、その場所に QWidget を配置し、paintEvent により処理することにする。
わざわざ QWidget を配置しなくても、ViEditView::paintEvent() で描画すればいいじゃないかと思う人もいるかもしれない。
筆者もそう思った。だがしかし、マージン領域にうまく描画できなかったのだ。それでしょうがなく QWidget を配置することにした。
1: class ViEditView : public QPlainTextEdit
2: {
3: .....
4: protected:
5: bool eventFilter(QObject *obj, QEvent *event);
6: private:
7: QWidget *m_lineNumberArea;
8: int m_lineNumberAreaWidth;
9: int m_lineNumberWidth;
10: };
行番号表示部分ウィジットは QWidget の派生クラスにして、paintEvent() をリインプリメントしてもよいのだが、 わざわざ派生クラスを宣言・実装するのが面倒なので、QWidget オブジェクトにイベントフィルターをインストールすることで、 行番号を描画するようにした。
1: ViEditView::ViEditView(QWidget *parent)
2: : m_mode(CMD), QPlainTextEdit(parent)
3: {
4: .....
5: m_lineNumberArea = new QWidget(this);
6: m_lineNumberArea->installEventFilter(this);
7: }
下記がイベントフィルターの部分。行番号表示ウィジットに対して Paint イベントが送られて来た場合は、 ViEditView::drawLineNumbers() をコールして行番号を表示する。
1: bool ViEditView::eventFilter(QObject *obj, QEvent *event)
2: {
3: if( obj == m_lineNumberArea && event->type() == QEvent::Paint ) {
4: drawLineNumbers();
5: return true;
6: }
7: return false;
8: }
下記が行番号を実際にウィジットに描画している部分。
ウィジットに描画を行うには、それを引数にして QPainter オブジェクトを生成し、 QPainter が用意する各種描画メソッドのお世話になればよい。ここでは行番号テキストを描画すればいいので、 QPainter::drawStaticText ( int left, int top, const QStaticText & staticText ) を使用しているだけだ。
QPlainTextEdit が保持するテキストは改行コードにより分割されて管理されている。そのためのクラスが QTextBlock だ。
垂直スクロール位置を考慮し、画面に表示される最初のブロックは QPlainTextEdit::firstVisibleBlock() で取得することができる。
QTextBlock オブジェクトの表示位置は QPlainTextEdit::blockBoundingGeometry() で、
行番号情報は QTextBlock::blockNumber() で取得できるので、それらの情報を使って行番号表示エリアに行番号を順次表示していく。
表示すべきブロックが尽きるか、ビューの下端を超えてしまったらループ終了だ。
1: void ViEditView::drawLineNumbers()
2: {
3: QPainter painter(m_lineNumberArea);
4: painter.setPen(Qt::black);
5: QRect r = m_lineNumberArea->rect();
6: painter.fillRect(r, Qt::lightGray);
7: const int ht = fontMetrics().height();
8: QTextBlock block = firstVisibleBlock();
9: int lineNumber = block.blockNumber() + 1;
10: while( block.isValid() ) {
11: const int y = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
12: if( y >= r.bottom() ) break;
13: QString number = QString::number(lineNumber);
14: painter.drawText(0, y, m_lineNumberWidth, ht, Qt::AlignRight, number);
15: ++lineNumber;
16: block = block.next();
17: }
18: }
以上で行番号表示できるようになった。あと忘れていけないのは、 メインウィンドウがリサイズされた時に行番号表示ウィジットも適切なサイズにリサイズする処理だ。
下記の様に ViEditView::resizeEvent(QResizeEvent *event) をリインプリメントし、 setGeometry() を呼んでやればよい。
1: void ViEditView::resizeEvent(QResizeEvent *event)
2: {
3: QPlainTextEdit::resizeEvent(event);
4: QRect r = rect();
5: m_lineNumberArea->setGeometry(QRect(r.left(), r.top(), m_lineNumberAreaWidth, r.height()));
6: }
下図に行番号を表示した時のスクリーンショットを示しておく。どうだい、少しはテキストエディタらしい面構えに近づいたじゃろ。
本稿のソースコードは以下からDLできます。※ VS2008 でプロジェクトを作成しています。
http://vivi.dyndns.org/dist2/qvi-008-src.zip