まずは Qt が用意しているテキストエディタークラスの QPlainTextEdit にコマンドモードを実装してみる。
新規プロジェクトを作成し、QPlainTextEdit の派生クラス ViEditView をプロジェクトに追加する。
1: #include2: 3: class ViEditView : public QPlainTextEdit 4: { 5: Q_OBJECT 6: 7: public: 8: ViEditView(QWidget *parent = 0); 9: ~ViEditView(); 10: };
んで、ViEditView を MainWindow のセントラルウィジットに指定する。
1: #include "ViEditView.h"
2:
3: MainWindow::MainWindow(QWidget *parent, Qt::WFlags flags)
4: : QMainWindow(parent, flags)
5: {
6: m_editor = new ViEditView;
7: setCentralWidget(m_editor);
8: }
たったこれだけで、下図の様な最低限のテキストエディタが出来上がる。
Qt は非常に生産性が高いと言われている。それは、以下の2つの要因から来ていると筆者は考えている。
本稿は後者の実例である。
vi エディタの最大の特徴と言えば、良くも悪くもコマンドモードと挿入モードが存在するということである。
初期状態はコマンドモードで、英数字・記号はカーソル移動、編集などのコマンドとして解釈される。
文字を挿入したい場合は i コマンドなどで挿入モードに遷移しなくてはいけない。
挿入モードを終了し、コマンドモードに戻るには Esc を押す。
例えば“abc”という文字列をカーソル位置に挿入したい時は iabc Esc と打鍵する。
vi をよく知らない人は、 モードを切り替えるために余計なキー(i、Esc)を押す必要があり打鍵数が増えるじゃないか、と思うかもしれない。 しかし、コマンドモードがあるおかげで、Ctrl を多用して左小指を痛めることもないし、 コマンドに割り当てるキーの自由度が増すので、結果的に打鍵数そのものが減り、編集速度も高速になるのだ。
挿入モードで Esc を押したらコマンドモード、コマンドモードで i を押したら挿入モードに遷移するようにしてみよう。
そのためには、まずモード状態を表す enum型を宣言し、そのメンバ変数を宣言し、セッターゲッターを定義する。
Qt ではゲッターは変数名そのもの、セッターは set を付ける流儀なのでそれに従った。
メンバ変数には、MFCからの慣習で m_ を前置している。
1: class ViEditView : public QPlainTextEdit
2: {
3: Q_OBJECT
4:
5: public:
6: enum Mode {
7: CMD = 0,
8: INSERT,
9: };
10:
11: public:
12: ViEditView(QWidget *parent = 0);
13: ~ViEditView();
14:
15: public:
16: Mode mode() const { return m_mode; }
17:
18: public:
19: void setMode(Mode mode) { m_mode = mode; }
20:
21: private:
22: Mode m_mode;
23: };
m_mode はコンストラクタで CMD に初期化しておく。
1: ViEditView::ViEditView(QWidget *parent)
2: : m_mode(CMD)
3: , QPlainTextEdit(parent)
3: {
4: }
Esc または i が押されたらモードを切り替え、コマンドモードではとりあえず i 以外は無視することにする。
そのために keyPressEvent() をリインプリメントする。
keyPressEvent() はその名の通り、キーが押されたときにコールされるイベントハンドラだ。
1: class ViEditView : public QPlainTextEdit
2: {
3: .....
4: protected:
5: void keyPressEvent ( QKeyEvent * event );
6: .....
7: };
さて、keyPressEvent() の処理だが、まず、モードにより処理が大きく分かれる。
今はコマンドモードと挿入モードしか無いので、switch 文で分岐してそれぞれの処理メソッドをコールする。
1: void ViEditView::keyPressEvent ( QKeyEvent * event )
2: {
3: switch( mode() ) {
4: case CMD:
5: cmdModeKeyPressEvent(event); // コマンドモードの場合の処理
6: break;
7: case INSERT:
8: insModeKeyPressEvent(event); // 挿入モードの場合の処理
9: break;
10: }
11: }
コマンドモードの場合、i が押されたら挿入モードに切り替える。
1: void ViEditView::cmdModeKeyPressEvent(QKeyEvent *event)
2: {
3: QString text = event->text();
4: if( text == "i" ) {
5: setMode(INSERT); // i が押されたら挿入モードへ
6: return;
7: }
8: // とりあえず、i 以外のキー入力は無視
9: }
挿入モードの場合、Esc が押されたらコマンドモードに切り替える。
1: void ViEditView::insModeKeyPressEvent(QKeyEvent *event)
2: {
3: if( event->key() == Qt::Key_Escape ) {
4: setMode(CMD); // Esc が押されたらコマンドモードへ
5: return;
6: }
7: QPlainTextEdit::keyPressEvent(event); // Esc キー以外は通常処理
8: }
以上で、QPlainTextEdit に vi のコマンドモードを導入することができた、というわけでござるぞ。
下記の処理を行えば、コマンドモードをサポートすることができるぞ。
本稿のソースコードは以下からDLできます。
http://vivi.dyndns.org/dist2/qvi-001.zip
※ VS2008 でプロジェクトを作成していますが、qvi-001/qvi-001.pro を読みこめば QtCreator でもビルドできます。