まずは 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 でもビルドできます。