前稿までで、vi エディタとしての枠組と、i h j k l Esc コマンドのみを実装した。
他にも実装しなくてはいけない vi コマンドは数限りなくあるし、カーソル移動の問題もあるし、
画面描画も手直ししなくてはいけないし、Windows アプリらしくメニューやツールバーも配置したい。
で、それらは放置プレイしておいて、本稿ではIMEまわりの機能を実装する。
vi コマンドはASCIIの範囲のみを使用するので、
コマンドモードでIMEをONにしても意味がなく、IME ON の状態とコマンドモードは排他的にしておくのがよい。
お風呂に入るときは服を脱ぐが、服を脱いだまま外出してしまっては怪しい人と認定されてしまう。
服を脱いだ状態と外出という行為は排他的なわけだ。
したがって、服を脱いだら自動的にお風呂場に移動してくれれば、冬場に体が冷えてしまうこともなく便利である。
というわけで、コマンドモードでIMEがONされた場合は、挿入モードへ自動的に遷移するようにしてみる。
んで、いろいろ調べてみたのだが、QtでIMEがONされたことを検出する方法は無いようだった。
そこで、QApplication の派生クラスを作り、 bool QCoreApplication::winEventFilter ( MSG * msg, long * result )
をリインプリメントし、Windows イベントをフックしてIMEのON・OFFが変更されたことを検出したら imeOpenStatusChanged()
シグナルをエミット(発行)することにする。
1: class WinApplication : public QApplication
2: {
3: Q_OBJECT
4:
5: public:
6: WinApplication(int & argc, char ** argv);
7: ~WinApplication();
8:
9: protected:
10: bool winEventFilter ( MSG * msg, long * result );
11:
12: signals:
13: void imeOpenStatusChanged();
14: };
1: bool WinApplication::winEventFilter ( MSG * msg, long * result )
2: {
3: if( msg->message == WM_IME_NOTIFY ) {
4: DWORD dwCommand = (DWORD) msg->wParam;
5: if( dwCommand == IMN_SETOPENSTATUS )
6: emit imeOpenStatusChanged();
7: }
8: return false;
9: }
あとは、コマンドモードで imeOpenStatusChanged() シグナルを受けたら挿入モードに遷移するだけだ。
1: int main(int argc, char *argv[])
2: {
3: WinApplication app(argc, argv);
4: MainWindow w;
5: w.show();
6: QObject::connect(&app, SIGNAL(imeOpenStatusChanged()), &w, SLOT(onImeOpenStatusChanged()));
7: return app.exec();
8: }
1: void MainWindow::onImeOpenStatusChanged()
2: {
3: m_viEngine->onImeOpenStatusChanged();
4: }
1: void ViEngine::onImeOpenStatusChanged()
2: {
3: if( mode() == CMD && !m_noInsModeAtImeOpenStatus )
4: setMode(INSERT);
5: }
bool m_noInsModeAtImeOpenStatus は、本稿の後半でコマンドモードに遷移した時にIMEをOFFにするのだが、 その時にも imeOpenStatusChanged() シグナルが発行されるので、その時に再度挿入モードに遷移しないようにするためのメンバ変数。
モード遷移時に以下のように設定しておく。
1: void ViEngine::setMode(Mode mode)
2: {
3: if( mode != m_mode ) {
4: m_mode = mode;
5: m_noInsModeAtImeOpenStatus = true;
6: emit modeChanged(mode);
7: m_noInsModeAtImeOpenStatus = false;
8: }
9: }
今度は逆に、外出しようとする時に裸だったら自動的に服を着るがごとく、 コマンドモードに遷移した場合はIMEを自動的にOFFするようにしてみる。
IME をOFFにするには、下記の様に ImmSetOpenStatus() Win32 API を使用する。 このAPIは引数にウィンドウハンドルを使用するが、ViEngine からはウィンドウハンドルを取得できないので、 MainWindow の onModeChanged(Mode mode) で処理することにした。
なお、ImmSetOpenStatus() を使った場合は、リンクするライブラリに Imm32.lib を追加しておく必要があるので注意。
1: #include <windows.h>
2: .....
3: void MainWindow::onModeChanged(Mode mode)
4: {
5: QString text;
6: switch( mode ) {
7: case CMD: {
8: HWND hwnd = window()->winId();
9: HIMC hC = ImmGetContext(hwnd);
10: ImmSetOpenStatus(hC, FALSE); // IME OFF
11: text = "CMD";
12: break;
13: }
14: .....
15: }
以上でコマンドモードに関連するIME制御の実装は終わりだ。
本稿に示したコードは Windows 依存である。当然ながら Mac/Linux では動作しないので注意。
本稿のソースコードは以下からDLできます。※ VS2008 でプロジェクトを作成しています。
http://vivi.dyndns.org/dist2/qvi-007-src.zip