前稿までで、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