LECTURE 12 XIM による日本語入力

○ はじめに

LECTURE 5 で説明した文字列描画と LECTURE 10 で説明したキーボードイベントを使えば、英数字のようなシングルバイト文字をキーボードから入力して表示するアプリケーションを作成することができます( テキストフィールド、etc. )。しかし、これだけでは日本語文字列を入力することはできません。日本語のようなマルチバイト文字列を入力として受け取るには、別の方法を使わなければなりません。

ここでは XIM ( X Input Method protocol ) を用いて日本語文字列(マルチバイト文字列)を入力する方法について説明していきます。

○ 日本語入力メソッド/アプリケーション/ユーザ間のやりとり

通常 Linux® ( Unix ) で日本語を入力する場合は、まず kinput2 などの漢字変換入力プログラム ( IM ) を起動し、キーボードから漢字変換前の日本語文字列を入力、変換を行い最終的に入力文字列を確定するという手順となります。この時の IM とアプリケーションのやりとりは以下のようになります。

  1. アプリケーションはキー入力を IM に渡す
  2. 入力メソッドは受け取ったキー入力を受け取り、前編集文字列を表示する
  3. 変換指示を受け取ると、IM は漢字変換サーバ(通常は別プロセス)とやりとりし、 変換中の表示文字列を受け取って表示する
  4. 入力文字列が確定されると、IM は確定文字列をアプリケーションに渡しアプリ ケーションがこれを表示する

つまり、日本語入力を行うためには IM とのやりとりを実装しなければならないのです。

IM とのやりとりには、Kinput2 プロトコルのように IM 独自のプロトコルも存在しますが、ここでは X ウィンドウシステムのリリース 5 から 用意された XIM ( X Input Method protocol ) という国際化テキスト入力のためのプロトコルに従います。

IM とユーザとのやりとりに関しては、次のスクリーン領域および対話形式が XIM 仕様として定義されています。

スクリーン領域
領域名 説明
状態表示領域 入力メソッドの内部状態を表示するための出力領域
前編集領域 変換確定前の入力文字列を表示するための領域
補助領域 入力メソッドが必要とする一時ウィンドウ

対話形式
形式名 説明
ルートウィンドウ型 入力メソッドはデータをアプリケーションとは別個のウィンドウ (ルートウィンドウの子ウィンドウ)に表示する
off-the-spot 型 前編集中のデータをアプリケーションウィンドウのある領域に表示する。 領域はアプリケーションの方で指定する
over-the-spot 型 前編集中のデータを、現在の挿入点に重ねて置かれた独立ウィンドウに表示する
on-the-spot 型 入力メソッドは、前編集中のデータを表示するようにアプリケーションに指示する (この場合、前編集データを表示するのはアプリケーションとなる)

アプリケーション側は上の対話形式の中から一つを選び、それに必要な前編集領域および状態表示領域を用意しなければいけません。on-the-spot 型 についてはさらに入力メソッドが呼び出すコールバック関数を用意する必要があります。

本ページでは、ルートウィンドウ型および off-the-spot 型について取り挙げます。over-the-spot 型および on-the-spot 型に関してはみなさんの方で勉強してみてください(単に僕の勉強不足です。すみませんm(__)m )。

○ サンプルプログラム ( ルートウィンドウ型 )

ルートウィンドウ型という対話形式で日本語文字列を入力、ウィンドウに表示するサンプルプログラム xim_root.c です。はじめに のページから、全てのサンプルプログラムを固めた tar ボールをダウンロードすることもできます。

実行して仮名漢字変換用の IM を起動すると、下の様に仮名漢字変換入力用のウィンドウが表示されます。このウィンドウは、ルートウィンドウの子ウィンドウとなります。このウィンドウで文字列を入力、変換を確定するとその確定文字列が上のウィンドウに表示されます。

012xim_root_window.jpg
012xim_root_im.jpg

--- xim_root.c ---

    1	/*
    2	 * ルートウィンドウ型(別ウィンドウに仮名漢字変換文字列
    3	 * を表示するタイプ)のサンプルプログラム 
    4	 */
    5	
    6	#include <X11/Xlib.h>
    7	#include <X11/Xlocale.h>
    8	#include <X11/Xutil.h>
    9	#include <X11/keysym.h>
   10	#include <stdio.h>
   11	
   12	
   13	#define BORDER  2    /* ボーダの幅 */
   14	#define COL     30   /* 30列 */
   15	#define ROW     20   /* 20行 */
   16	
   17	static Display*          dpy;          /* ディスプレイ */
   18	static int               screen;       /* スクリーン */
   19	static Window            root;         /* ルートウィンドウ */
   20	static Window            win;          /* 表示するウィンドウ */
   21	static int               win_w, win_h; /* ウィンドウの幅と高さ */
   22	static unsigned long     black, white; /* 黒、白のピクセル値 */
   23	static GC                gc;           /* グラフィックスコンテキスト */
   24	static XFontSet          fs;           /* フォントセット */
   25	static int               font_w;       /* 文字幅 */
   26	static int               font_h;       /* 文字高 */
   27	static int               ascent;       /* 文字のアセント */
   28	static int               descent;      /* 文字のディセント */
   29	static XIM               im;           /* インプットメソッド */
   30	static XIC               ic;           /* インプットコンテキスト */
   31	static Atom              protocol;     /* ウィンドウマネージャ用変数 */
   32	
   33	
   34	static int  xim_setlocale();
   35	static void xim_init();
   36	static void xim_create_fontset();
   37	static void xim_create_window();
   38	static int  xim_init_inputmethod();
   39	static void xim_event_loop();
   40	static void xim_draw( XEvent* event );
   41	static void xim_close();
   42	
   43	
   44	int 
   45	main( void )
   46	{
   47		/* インプットメソッドが必要とするイベント */
   48		unsigned long fevent;
   49	
   50		if ( xim_setlocale() == -1 ) {
   51			return 1;
   52		}
   53	
   54		xim_init();
   55		xim_create_fontset();
   56		xim_create_window();
   57	
   58		/* インプットメソッドの初期化 */
   59		if ( xim_init_inputmethod() == -1 ) {
   60			return 1;
   61		}
   62	
   63		/* ウィンドウ破壊イベントを受け取る */
   64		protocol = XInternAtom( dpy, "WM_DELETE_WINDOW", True );
   65		XSetWMProtocols( dpy, win, &protocol, 1 );
   66	
   67		/* インプットメソッドが必要とするイベントを問い合わせる */
   68		XGetICValues( ic, XNFilterEvents, &fevent, NULL );
   69	
   70		XSelectInput( dpy, win, 
   71		      ExposureMask | KeyPressMask | FocusChangeMask | fevent );
   72		XMapWindow( dpy, win );
   73	
   74		xim_event_loop();
   75		xim_close();
   76	
   77		return 0;
   78	}
   79	
   80	
   81	static int 
   82	xim_setlocale( void )
   83	{
   84		if ( setlocale( LC_CTYPE, "" ) == NULL ) {
   85			printf( "Can't set locale.\n" );
   86			return -1;
   87		}
   88	
   89		if ( ! XSupportsLocale() ) {
   90			printf( "Current locale is not supported\n" );
   91			return -1;
   92		}
   93	
   94		/* ユーザ指定のモディファイアを連結する */
   95		if ( XSetLocaleModifiers( "" ) == NULL ) {
   96			printf( "Can't set locale modifiers\n" );
   97			return -1;
   98		}
   99	
  100		return 0;
  101	}
  102	
  103	
  104	static void 
  105	xim_init( void )
  106	{
  107		dpy = XOpenDisplay( "" );
  108	
  109		root   = DefaultRootWindow( dpy );
  110		screen = DefaultScreen( dpy );
  111		white  = WhitePixel( dpy, screen );
  112		black  = BlackPixel( dpy, screen );
  113	}
  114	
  115	
  116	static void 
  117	xim_create_fontset( void )
  118	{
  119		XFontSetExtents*  fs_extents;
  120		int    missing_count;
  121		char** missing_list;
  122		char*  def_string;
  123	
  124		
  125		fs = XCreateFontSet( dpy, "-*-fixed-medium-r-normal--14-*-*-*", 
  126			     &missing_list, &missing_count, &def_string );
  127	
  128		XFreeStringList( missing_list );   
  129	
  130		/* フォント情報の取得 */
  131		fs_extents = XExtentsOfFontSet(fs);
  132	
  133		font_w     = fs_extents->max_logical_extent.width;
  134		font_h     = fs_extents->max_logical_extent.height;
  135		ascent     = - fs_extents->max_logical_extent.y;
  136		descent    = fs_extents->max_logical_extent.height - ascent;
  137	
  138		/* ウィンドウ幅と高さを設定 */
  139		win_w = font_w * COL;
  140		win_h = font_h * ROW;
  141	}
  142	
  143	
  144	static void 
  145	xim_create_window( void )
  146	{
  147		win = XCreateSimpleWindow( dpy, root,
  148			   0, 0, win_w, win_h, BORDER, black, white );
  149	
  150		gc = XCreateGC( dpy, win, 0, NULL );
  151		XSetBackground( dpy, gc, white );
  152		XSetForeground( dpy, gc, black );
  153	}
  154	
  155	
  156	static int 
  157	xim_init_inputmethod( void )
  158	{
  159		/* インプットメソッドをオープンする */
  160		if ( ( im = XOpenIM( dpy, NULL, NULL, NULL ) ) == NULL ) {
  161			printf( "Couldn't open input method.\n" );
  162			return -1;
  163		}
  164	
  165	
  166		/* インプットコンテキストの作成 */
  167		ic = XCreateIC( im,
  168				XNInputStyle, 
  169				XIMPreeditNothing | XIMStatusNothing,
  170				XNClientWindow, 
  171				win,
  172				NULL );
  173	
  174		if ( ic == NULL ) {
  175			printf( "Couldn't create input context.\n" );
  176			XCloseIM( im );
  177			return -1;
  178		}
  179	
  180		return 0;
  181	}
  182	
  183	
  184	static void 
  185	xim_event_loop( void )
  186	{
  187		XEvent event;
  188	
  189		while( 1 ) {
  190			XNextEvent( dpy, &event );
  191	
  192			/* インプットメソッドにイベントを渡す */
  193			if( XFilterEvent( &event, ( Window )NULL ) == True ) {
  194				/* イベントはフィルタリングされた */
  195				continue;
  196			}
  197	
  198			switch( event.type ) {
  199				/* 
  200				 * Expose イベントを用意していないので、ウィンドウが
  201				 * 隠れた場合などには描画内容は失われる。
  202				 */
  203				case KeyPress :
  204					xim_draw( &event );
  205					break;
  206	
  207				case FocusIn :
  208					XSetICFocus( ic );
  209					break;
  210	
  211				case FocusOut :
  212					XUnsetICFocus( ic );
  213					break;
  214	
  215				case ClientMessage:
  216					if ( event.xclient.data.l[0] == protocol ) {
  217						return;
  218					}
  219		      
  220					break;
  221			}
  222		}
  223	}
  224	
  225	
  226	static void
  227	xim_draw( XEvent* event )
  228	{
  229		Status status;
  230		KeySym keysym;
  231		int cnt = 0;
  232	
  233		/* COL 列の 2 バイト文字分の領域を 1 行の最大文字数とする */
  234		char buffer[ COL * 2 ];
  235	
  236		static int row = 0;
  237		static int col = 0;
  238	
  239	
  240		/* 入力文字列の取得 */
  241		cnt = XmbLookupString( ic, ( XKeyPressedEvent* )event,
  242				       buffer, sizeof( buffer ), &keysym, &status );
  243	
  244		/* 
  245		 * この改行処理を下の if 文より先に行わないと、そちらにイベントを
  246		 * 拾われて改行処理が行われないので注意 
  247		 */
  248		if ( status == XLookupBoth && keysym == XK_Return ) {
  249			/* 最下段に到達したら、先頭行に戻る */
  250			if ( ++ row >= ROW ) {
  251				row = 0; 
  252			}
  253			col = 0;
  254			
  255			/* 新しい行をクリアする */
  256			XClearArea( dpy, win, 0, font_h * row, win_w, font_h, False );
  257		}
  258		else if ( status == XLookupChars || status == XLookupBoth ) {
  259			/* 
  260			 * 入力文字(文字列)の描画 
  261			 *
  262			 * 描画原点の x 座標を決める際に、font_w / 2 という計算を
  263			 * しているが、これは 1 バイト文字の幅に相当する。日本語
  264			 * 文字(文字列)が入力された場合には 2 バイトずつ char の
  265			 * 値を受け取るので、現在の col に font_w / 2 をかければ
  266			 * 次に描画すべき描画原点の x 座標を決定できる。
  267			 */
  268			XmbDrawString( dpy, win, fs, gc,
  269				       ( font_w / 2 ) * col, font_h * row + ascent,
  270				       buffer, cnt );
  271	
  272			col += cnt;
  273		}
  274	
  275		/* 
  276		 * status が XBufferOverflow の場合は、用意したバッファに文字列
  277		 * を格納しきれなかったということなので、この場合は充分なバッファ
  278		 * を用意した上で再度 XmbLookupString() を実行する必要がある。
  279		 */
  280	}
  281	
  282	
  283	static void 
  284	xim_close( void )
  285	{
  286		printf( "Exit application.\n" );
  287	
  288		XDestroyIC( ic );
  289		XCloseIM( im );
  290		XFreeGC( dpy, gc );
  291		XDestroyWindow( dpy, win );
  292		XCloseDisplay( dpy );
  293	}
  294	
  295	
  296	/* End of xim_root.c */

○ サンプルプログラム (ルートウィンドウ型) の説明

それでは、サンプルプログラムについて説明していきます。

  • 13 〜 15 行目

    ここではウィンドウサイズを定義するのではなく、20 行、30 列の文字列を表示できる領域とし てウィンドウの大きさを定義しています。なおこの数値は全角文字の表示数を表しています。

  • 17 〜 31 行目

    xctrl.c と同様に処理を関数単位で切り分けているため、関数間で共有する変数を static 変数 として宣言しています。25、26 行の font_w、font_h はそれぞれ文字幅、文字高さを格納する変数 です。

    文字を描画する際に指定する位置座標は、文字に設定されている描画原点です。ところでこの描 画原点は、font_w および font_h で表される長方形の頂点ではありません (そういう場合もあるか もしれませんが)。ascent および descent はそれぞれ、文字高さのうち描画原点より上 / 下 の部 分の値を指します。

    XIM 型変数の im および XIC 型の変数 ic は、それぞれ IM とやりとりするために必要な情報 を格納するためのものです。

  • 44 〜 78 行目 ( main() )

    xim_root.c の main 関数です。おおまかな処理は、

    • ロケールの確立 ( xim_setlocale() )
    • 初期処理 ( xim_init() )
    • フォントセットの生成、およびフォント情報の取得 ( xim_create_fontset() )
    • ウィンドウ / GC の作成 ( xim_create_window() )
    • IM の初期化 ( xim_init_inputmethod() )
    • ウィンドウ破壊イベントを受け取るための、WM_PROTOCOLS プロパティの設定
    • IM が必要とするイベントの問い合わせ ( XGetICValues() )
    • イベントの選択 ( XSelectInput() )
    • ウィンドウのマップ ( XMapWindow() )
    • イベントループ ( xim_event_loop() )
    • 終了処理 ( xim_close() )

    となります。ウィンドウ破壊イベントに関しては、LECTURE 13 ウィンドウマネージャとのやりとりの時に詳しく説明しますので、ここではそういう処理を やっているんだとくらいに思っていて下さい。

    IM は自分が必要とするイベントをアプリケーションから受け取り、イベントに応じて動作します。 そのためには、アプリケーションは XSelectInput() でイベントマスクを設定する際に、IM が必 要とする全てのイベントも設定しなければなりません。IM が必要とするイベントを知るには XGetICValues() を使って IC に設定された属性を取得します。

      char* XGetICValues( XIC ic, ... );
      ... : XIC の属性値を取得するための可変長引数リストを指定
    
    

    XGetICValues() の可変長引数には、取得したい属性の名前と取得した属性値を格納するための変 数へのポインタを指定します。引数の終わりには NULL を設定します。ここでは IM が必要とす るイベントマスクを取得するために、XNFilterEvents を指定して unsigned long 型の変数 fevent を格納先のバッファとして指定しています。

    エラーが発生しなかった場合、XGetICValues() は返り値として NULL を返します。何らかのエラ ーが発生した場合には、設定できなかった最初の引数の名前が返されます。

  • 81 〜 101 行目 ( xim_setlocale() )

    LECTURE 11 で説明したロケール関連の処理に加えて、 XSetLocaleModifiers() という関数を実行しています。

      char* XSetLocaleModifiers( char* modifier_list );
      modifier_list : モディファイアを指定
    
    

    引数を "" とすることで、環境変数 XMODIFIERS に "@im=kinput2" のように設定された IM の 指定を読み込むことができます ( kinput2 を使用する場合 )。"@im=kinput2" という表現につ いては、XOpenIM() の説明の際に再度ふれます。

  • 116 〜 141 行目 ( xim_create_fontset() )

    LECTURE 11 で説明したフォント作成処理に加え、 XExtentsOfFontSet() を使ってフォント幅などのフォント情報を取得しています。指定したフォ ントセットで描画される文字のうち、最大の領域を占める文字についての情報を XFontSetExtents 型構造体変数として返します。

      XFontSetExtents* XExtentsOfFontSet( XFontSet font_set );
    
      typedef struct {
          XRectangle max_ink_extent;
          XRectangle max_logical_extent;
      } XFontSetExtents;
    
    

    max_logical_extent の方は、描画した文字が完全に囲まれる長方形領域の情報が格納されます。 max_locical_extent.x および max_logical_extent.y には、それぞれ描画文字の描画原点から 見た左上角の位置が格納されます。

    これらを使って、フォント幅および高さを取得しています。また、 ( - max_logical_extent.y ) が ascent ( 画面上方向が y 軸の正方向であるため)、 font_h - ascent が descent となります。最後に フォント幅、高さと文字表示数を使ってウィ ンドウのサイズを決定しています。

  • 156 〜 181 行目 ( xim_init_inputmethod() )

    IM の通信するための初期処理を行っています。IM によってマルチバイト文字列を入力する場 合には、まず IM に接続しなければなりません。これには XOpenIM() を使います。

      XIM XOpenIM( Display* dpy, XrmDatabase db, char* res_name, char* res_class );
    
      db        : IM がリソースの検索に使うリソースデータベース
      res_name  : アプリケーションの完全なリソース名
      res_class : アプリケーションの完全なクラス名
    
    

    この関数を実行すると、現在のロケールに適する IM との接続が確立されその接続のハンドル として XIM 型の変数が返されます。IM との接続に失敗した場合は、NULL が返ります。なお、 XIM 型の内容は公開されていません。引数は IM がリソースを検索する際に使用するためのも のですが、xim_root.c では全て NULL としています( 勉強不足のため、NULL だとどうなるの か、どういう場合に NULL 以外を指定するのかについてはまだ僕は分かってませんm(__)m )。

    XOpenIM() がオープンするデフォルトの IM はロケールで決定されますが、各ロケールに対し て複数の入力メソッドがあることも考えられます。このため、デフォルトの IM を設定するた めに "im" という名前のロケールモディファイアが用意されています。前述のように環境変数 XMODIFIERS に "@im=input_method_name" と設定することで、希望の入力メソッドが使用され ます。

    XOpenIM() によって IM との接続が確立されましたが、IM に対する領域や対話形式の設定、お よびテキスト入出力そのものを行うには、IM のオープンに引き続いて入力コンテキスト( IC ) を作成しなければなりません。IC は、GC が描画に関する属性情報を管理しているのと同様に 国際化テキストの入出力に関する属性情報を管理しています。IC の属性によって IM の挙動や 外観が決定されます。

    X 入力コンテキスト ( XIC ) を作成するには XCreateIC() を使います。作成に失敗した場合 は NULL が返ります。

      XIC XCreateIC( XIM im, ... );
    
      im  : XOpenIM によって接続が確立された IM のハンドラ
      ... : XIC の属性を設定するための可変長引数リスト
    
    

    可変長引数リストには属性名と属性値の組を設定し、リストの最後には NULL を設定します。 xim_root.c では、XNInputStyle という属性に対して XIMPreeditNothing および XIMStatusNothing という属性値を、XNClientWindow という属性に対して表示のウィンドウを 指定していることになります。

    ここに指定できる属性名およびその内容について、いくつか下表に示します。

    定数名 説明
    XNInputStyle 入力コンテキストに対して入力メソッドが使用する対話型を指定する ( XIMStyle )。 ここに XIMPreeditNothing および XIMStatusNothing を指定するとルートウィンドウ型の対話形式となる。
    XNFontSet 入力メソッドが前編集や状態表示ウィンドウでの文字列描画に使用する フォントセットを指定する ( XFontSet )。
    XNClientWindow 入力メソッドが前編集領域および状態表示領域として使用するウィンドウを指定する ( Window )。
    XNForeground 入力メソッドが使用する前景ピクセル値を指定する( unsigned long )
    XNBackground 入力メソッドが使用する背景ピクセル値を指定する( unsigned long )

  • 184 〜 223 行目 ( xim_event_loop() )

    主処理を行うイベントループです。XNextEvent() を使ってイベントを読み込んだ後、イベン トの種類によって処理を振り分ける前に XFilterEvent() という関数を実行しています。

      Bool XFilterEvent( XEvent* event, Window win );
    
      event : フィルタするイベントを指定
      win   : フィルタが適用されるウィンドウを指定
    
    

    IM はキーストローク以外の X イベントを受け取る必要があります( 領域の書き換えのためな ど色々)。そのため、アプリケーション側で処理する前に全てのイベントを IM に渡さなければ なりません。このために XFilterEvent() を使います。

    この関数が True を返した場合、イベントは IM によって処理されたのでアプリケーション側 ではこのイベントの処理を行ってはいけません。False を返した場合に限って、イベントの種 類によって処理を振り分けます。

    • KeyPress イベント時

      226 〜 280 行目の xim_draw() で、入力文字の描画処理を行っています。

      KeyPress イベントを受け取った場合、何のキーが押されたのかを判定する必要があります。 英数字の場合は XLookupString() を使いますが、日本語のようなマルチバイト入力の場合は XmbLookupString() を使います。

          int XmbLookupString( XIC              ic, 
                               XKeyPressedEvent event, 
                               char*            buffer_return, 
                               int              bytes_buffer, 
                               KeySym*          keysym_return, 
                               Status* status_return 
                             );
      
          buffer_return : IM から、構成された(変換後の)マルチバイト文字列が返される
          bytes_buffer  : リターンバッファの容量を指定
          keysym_return : NULL でない場合は、イベントから計算された KeySym が返される
          status_return : 返却情報の種類を示す値が返される
      
      

      buffer_return に格納された文字列のバイト数が返ります。なお、status_return に格納さ れる値は下表の通りです。

      定数名 説明
      XLookupBothKeySym と文字列の両方が返された
      XLookupChars入力メソッドにより入力文字列が構成されている
      XLookupKeySym文字列ではなく KeySym が返されている
      XLookupNone正しい入力が今のところ構成されていない
      XBufferOverflow返されるべき入力文字列がバッファの大きさよりも長い。 この場合、関数の返り値が必要なバイト数となるので、 その大きさのバッファを確保して再読み込みを行う

      xim_root.c では、改行が入力された場合 ( XK_Return という KeySym と改行文字が返され るため、status_return は XLookupBoth となる ) には文字列の表示位置を下にずらしてそ の行を背景色でクリア ( XClearArea() ) しています。また、半角英数字が入力された場合 ( status_return は XLookupBoth ) やマルチバイト文字列が入力された場合( status_return は XLookupChars ) には、現在の表示位置を計算して XmbDrawString() を使って文字列を描 画しています。

      なお、xim_root.c では status_return が XBufferOverflow である場合を扱っていません が、この場合は用意したバッファに文字列を格納しきれなかったということなので、充分な バッファを用意した上で再度 XmbLookupString() を実行する必要があります。

    • FocusIn / FocusOut イベント時

      FocusIn / FocusOut イベントによってウィンドウのフォーカス状態を得たら、それに合わせ て IC のフォーカスを設定する必要があります。そうすることで、IM がフォーカス状態に応 じた編集領域の描画などを行うことが可能となるからです。

      IC のフォーカス状態は XSetICFocus() / XUnsetICFocus() を使います。

          void XSetICFocus( XIC ic );
          void XUnsetICFocus( XIC ic );
      
      

    • ClientMesage イベント時

      LECTURE 13 で説明しますが、ウィンドウマネージャから ウィンドウ破壊要求を受け取った場合は、終了処理を行います。

  • 283 〜 293 行目 ( xim_close() )

    終了処理を行います。IC の解放処理用の XDestroyIC()、IM の解放処理用の XCloseIM() が 新しく出てきています。

      XDestroyIC( XIC ic );
      XCloseIM( XIM im );
    
    

以上でルートウィンドウ型サンプルプログラムの説明は終りです。

○ サンプルプログラム ( off-the-spot 型 )

ルートウィンドウ型のコードの一部を書き換えるだけで、簡単に off-the-spot 型のコードが得られます。xim_root.c を書き換えて off-the-spot 型にした xim_offspot.c について、書き換えられた関数 ( xim_create_font_set()、xim_init_inputmethod() ) のみを示します。

off-the-spot 型の場合、仮名漢字変換入力領域がクライアントウィンドウの一部に確保されます。

012xim_offspot.jpg

--- xim_offspot.c ( 一部 ) ---

...

static void 
xim_create_fontset( void )
{
    XFontSetExtents*  fs_extents;
    int    missing_count;
    char** missing_list;
    char*  def_string;

    fs = XCreateFontSet( dpy, "-*-fixed-medium-r-normal--14-*-*-*", 
                 &missing_list, &missing_count, &def_string );

    XFreeStringList( missing_list );   

    /* フォント情報の取得 */
    fs_extents = XExtentsOfFontSet(fs);

    font_w  = fs_extents->max_logical_extent.width;
    font_h  = fs_extents->max_logical_extent.height;
    ascent  = - fs_extents->max_logical_extent.y;
    descent = fs_extents->max_logical_extent.height - ascent;

    /* 
     * ウィンドウ幅と高さを設定 
     * IM の状態領域および前編集領域を確保するため、
     * xim_root.c より 2 行分高さを広げる 
     */
    win_w = font_w * COL;
    win_h = font_h * ( ROW + 2 );
}

...

static int 
xim_init_inputmethod( void )
{
    /* 前編集領域および状態領域を指す矩形 */
    XRectangle    preedit_rect, status_rect;

    /* 前編集領域および状態領域の属性を指定するためのリスト */
    XVaNestedList preedit_attr, status_attr;

    /* インプットメソッドをオープンする */
    if ( ( im = XOpenIM( dpy, NULL, NULL, NULL ) ) == NULL ) {
        printf( "Couldn't open input method.\n" );
        return -1;
    }

    /* 前編集領域属性の設定 */
    preedit_rect.x      = 0;
    preedit_rect.y      = font_h * ROW;
    preedit_rect.width  = win_w;
    preedit_rect.height = font_h;

    preedit_attr  = XVaCreateNestedList( 
        ( int )NULL, XNArea, &preedit_rect, XNFontSet, fs, NULL );

    status_rect.x       = 0;
    status_rect.y       = font_h * ( ROW + 1 );
    status_rect.width   = win_w;
    status_rect.height  = font_h;

    status_attr = XVaCreateNestedList( 
        ( int )NULL, XNArea, &status_rect, XNFontSet, fs, NULL );

    /* インプットコンテキストの作成 */
    ic = XCreateIC( im,
            XNInputStyle,        
            XIMPreeditArea | XIMStatusArea,
            XNClientWindow,      
            win,
            XNPreeditAttributes, 
            preedit_attr,
            XNStatusAttributes,  
            status_attr,
            NULL );

    XFree( preedit_attr );
    XFree( status_attr );

    if ( ic == NULL ) {
        printf( "Couldn't create input context.\n" );
        XCloseIM( im );
        return -1;
    }

    return 0;
}
...

○ off-the-spot 型のルートウィンドウ型との違い

off-the-spot 型の実装がルートウィンドウ型と異なる点は、前編集領域および状態領域についての情報を設定すること、またその 2 つの領域を考慮してウィンドウのサイズを決定しなければならないということです。

まず xim_create_window() のウィンドウサイズを計算している部分で、高さを 2 行分大きくしています。これは、前編集領域および状態領域の 2 行分を増やしたことに相当します。次に xim_init_inputmethod() において、前編集領域および状態領域がウィンドウのどの領域を使用するかを XRectangle 型変数に設定した後に、XVaCreateNestedList() を使って属性リストを作成しています。

XVaNestedList XVaCreateNestedList( int dummy, ... );

dummy : 実際には使用されないダミーの引数を指定
...   : 可変長引数リストの指定

(*)XVaNestedList に関しては
   typedef void* XVaNestedList;
   として定義されている。

可変長リストを使用して、領域を設定した XRectangle 型変数を XNArea 属性に、作成したフォントセットを XNFontSet 属性に設定しています。

また XCreateIC を使って IC を作成するに際して、XNInputStyle 属性に XIMPreeditArea および XIMStatusArea の論理和を設定します。そして新しく出てきた XNPreeditAttributes および XNStatusAttributes 属性に、XVaCreateNestedList を使って作った属性リストをそれぞれ設定します。なお、不要になった属性リストは XFree() を使って解放します。

XFree( void* data );

以上の手続きを行うだけで、ルートウィンドウ型から簡単に off-the-spot 型を作成することができます。