前へ 次へ
技術文章qvi

前稿までで、vi エディタとしての枠組と、i h j k l Esc コマンドのみを実装した。
他にも実装しなくてはいけない vi コマンドは数限りなくあるし、カーソル移動の問題もあるし、 画面描画も手直ししなくてはいけないし、Windows アプリらしくメニューやツールバーも配置したい。

で、それらは放置プレイしておいて、本稿ではIMEまわりの機能を実装する。

IME ON で挿入モードに

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するようにしてみる。

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

Tweet


前へ 次へ
技術文章qvi