Zaurusでエディタを作ろう~ 3
第3回。まずは画面表示概要(強調表示用編)
さて、最近のツールキットは総じて便利機能満載なので、簡単なテキストエディタならホントに数分で出来てしまうようなクラスが最初から用意されています。
Zaurus搭載のQtopia1.5にも今となっては古くなってしまった感が否めないですが、ご多聞に漏れずQMultiLineEditというエディタ向けのクラスがあります。
こいつに入出力用のダイアログでもくっつけてあげればWindowsのメモ帳程度の簡単なエディタとしてはほぼ完成です。
まぁ、PC用のエディタでコーディングしてる人がメモ帳だけでやってるとは到底思えません。
IDE組み込みの専用エディタを使ってるか、自分が気に入ったエディタをカスタマイズして使ってる人が大半だと思います。
Zaurusではコンソール用のemacsやvimが便利だったりしますが、Qt用では正規表現による強調文字列やTab、全角スペース等の記号が表示できるZEditorを自分は使用していました。
ただ、ZEditorは真っ当なエディタのため、自分が欲しがってるトンがった仕様は実装されていません。
そんなこんなでコアのエディタクラス(メニューとかタブとかボタンとか省いた部分)ですけど基本はZEditorを踏襲させてもらって、これをカスタマイズしていきます。
ブロックコメント、対括弧表示、行マーク、単語単位認識と、色々追加していくのもありますけど、まずはZEditorとの共通部分を確認しながら実装していっちゃいましょう。
ZEditorはQMultiLineEditとQMainWindowを継承したクラスを実装しています。表示周りの中心はQMultiLineEditの継承であるQpeEditorクラス。
こいつのpaintCell()がキモで、そことpaintEvent()内で表示系の殆どの描画を行っています。Satoshiさんご本人によるとpaintCell()内では5段階のプロセスを行っており、それぞれキャラクタ表示、Tab、CRマーク表示、行番号表示、折り返し、予約語の表示、カーソル表示とのこと。
もうちょっと細かく見るとキャラクタ、CR,EOF,Tab,全角スペース記号、行番号、1バイト文字、予約語、コメント、選択文字、バーティカルライン、Jumpマーク、カーソル表示ってな感じですかね。
このpaintCell()は文章編集、カーソルの点滅時などのイベントで呼ばれて行単位で描画するイベントメソッドなんで、特定の行の文字に色を着けたり改行記号を描画したりってのには都合のいいメソッドってわけです。QMultiLineEditの継承元であるQTableViewに純粋仮想メソッドとして実装されてます。
親のQMultiLineEditで実装してあるのはキャラクタ、折り返し記号、選択文字、カーソルの表示だけなので、ZEditorがいかに色々追加実装してるかが分かりますね。
ところが、Zaurus(っていうかQtopia)は描画が滅法重いです。色々やってて気づいたのですが、このZaurus君、実は描画が無ければ裏で結構重い処理(ソートとかリストへの挿入、文字列検索とか)をさせても意外と頑張って処理しちゃってくれます。この辺はさすが416Mhzって所でしょうか。X68kと比べると単純計算で40倍以上ですからねぇ…。クロック数だけだと。
ですので、極力「描かない」ロジックを組むことでそこそこ速度低下を抑えることができそうです。この後色々機能を組み込んでいくと思われるので端折れる部分はザックリやっていくつもりです。そこは勢いで。
描画頻度的に、通常キャラクタ、行番号、予約語、コメント辺りが重そうです。それと自分はコーディングでは殆ど使わない「折り返し」処理をとりあえず保留しておいて、処理速度に余裕があったら後で実装、ということにしちゃいました。
前置き(!?)が長くなりましたが、今回はその中で予約語に色を着けて表示する強調表示の仕組みを解説してみます。Web上にも色々解説してるサイトとかありますけど、ここではQMultiLineEditに対して如何に軽い処理で実装できるか…、をテーマにしてみたいと思います。
自前で行バッファとか管理した方(QMultiLineEditをスクラッチ)が実装が簡単になる処理とか拡張し易いとかあるのですが、そうすると他(編集処理とか)が結構面倒になるので今回は避けて通る事にします。
そのうちやってはみたいのですが…。
で、ZEditorでの文字描画処理は行単位で 通常キャラクタ描画 → コメントを検索して描画 → 予約語を検索して描画 という流れで行っていて、これはこのままでも良かったのですが、予約語のセット数を柔軟にするためにデータの管理方法を変更したいのと、コメントも予約語の1つとして処理したらちょとは軽くなるかなっていう漠然とした理由でコメント、予約語を検索 → 通常キャラクタ描画 → 先に検索したコメント、予約語を描画という流れに変更しました。
同系統の処理をまとめて行うようになるのでいくらかキャッシュが効いてくれるかな…という淡い期待も込めてみます。
そのデータ構造ですが、予約語検索ルール用にConfigColorSyntax、実際の文字列を検索した予約語やコメントの情報を保持しておくColorSyntax、色情報のColorSetの3つの構造体を利用します。
struct ConfigColorSyntax { QStringList syntax; // 予約語リスト bool onlyWord; // 単語単位のみ bool caseSensitive; // 英大文字小文字区別 bool onlyInTag; // タグ内のみ有効 int colorIndex; // ColorSetリストのインデックス int useRegExp; // 0:not 1:QRegExp 2:oniguruma union { QRegExpOni* regExpOni; QRegExp* regExp; }; }; struct ColorSyntax { int start; // 文字列先頭からの文字数 int length; // 対象の文字数 int color; // 色セットインデックス ColorSyntax( int s, int l, int c ) : start(s), length(l), color(c) {}; }; typedef QValueList<ColorSyntax> ColorSyntaxList; struct ColorSet { QColor fgColor; // 前景色 QColor bgColor; // 背景色 bool isUseFgColor; // 前景色使用フラグ bool isUseBgColor; // 背景色使用フラグ }; // インスタンス変数として定義 // QValueList<ConfigColorSyntax> _confSyntax; // QArray<ColorSet>_colSet;
paintCell()への引数として渡されるrowとstringShown()で描画すべき文字列が取得できますので、ConfigColorSyntaxに設定した予約語の検索ルールに従ってその文字列を走査し、結果をColorSyntaxのリスト(ColorSyntaxList)としてリストアップ。
この走査をConfigColorSyntaxの数(_confSyntax.count())だけ繰返します。
QString str = stringShown( row ); // その行の文字列 ColorSyntaxList stxList; // 強調文字列ステータスリストを用意 // 強調文字列の検出と登録 for ( QStringListIterator it = _confSyntax[x].syntax.begin(); it != _confSyntax[x].syntax.end(); ++it ) { int offset = 0; while ( true ) { // 文字列strのオフセットoffsetから予約語*itを検索 // してそのindexをresultとして返す int result = str.find( (*it), offset, _confSyntax[x].caseSensitive ); if ( result == -1 ) // 見つからなかったら次の予約語 break; // 見つかったらリストに追加 stxList.append( ColorSyntax( result, (*it).length(), _confSyntax[x].colorIndex ) ); // 見つかった文字分だけオフセットを移動 offset = result + (*it).length(); } }
最後に通常色で行の文字列全体を描画してから、強調文字列をColorSyntaxListに従ってまとめて上書き描画します。
また、リストに登録する際にソートや衝突チェックとかしてたりするのですが、長くなるのでここでは省略。
// 通常文字列の描画 p.setPen( cg.text() ); p.drawText( 0, 0, cellWidth(), cellHeight(), ExpandTabs, str ); // 強調文字列の描画 ColorSyntaxList::Iterator it; for ( it = stxList.begin(); it != stxList.end(); ++it ) { p.setPen( _colSet[(*it).color].fgColor ); p.drawText( (*it).start * font().width(), y, cellWidth(), cellHeight(), ExpandTabs, str.mid( (*it).start, (*it).length ) ); }
と、まぁこんな感じで実装してみました。ん~、エライ長くなりましたねw ブログでやることじゃないのかも…^^; 読んでくれた人、お疲れ様でした。
でも、効果はあるのか、正規表現を使わない単語単位の検索で400単語位(Qt/C/C++クラス、メソッド系)検索してもそれほど重くならずに普通に表示出来てます。いい感じです。
正規表現使い出すとまた重くなるのでしょうが、その辺は使い分けでどうにかできたらと思ってます。
さて、この連載は続けちゃっていいのでしょうか…w
#PREタグ使うと横幅が足りないですね…。まぁ、雰囲気と流れだけでもw コピペすれば全部見れますが…。
| 固定リンク
この記事へのコメントは終了しました。
コメント