/* input-gui.c -- gui-based input method module. Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 National Institute of Advanced Industrial Science and Technology (AIST) Registration Number H15PRO112 This file is part of the m17n library. The m17n library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The m17n library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the m17n library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /***en @addtogroup m17nInputMethodWin @brief Input method support on window systems. The input driver @c minput_gui_driver is provided for internal input methods that is useful on window systems. It displays preedit text and status text at the inputting spot. See the documentation of @c minput_gui_driver for more details. In the m17n-X library, the foreign input method of name @c Mxim is provided. It uses XIM (X Input Method) as a background input engine. The symbol @c Mxim has a property @c Minput_driver whose value is a pointer to the input driver @c minput_xim_driver. See the documentation of @c minput_xim_driver for more details. */ /***ja @addtogroup m17nInputMethodWin @brief ウィンドウシステム上の入力メソッドのサポート. 入力ドライバ @c minput_gui_driver は、 ウィンドウシステム上で用いられる内部入力メソッド用のドライバである。 このドライバは入力スポットに preedit テキストと status テキストを表示する。詳細については @c minput_gui_driver の説明を参照のこと。 m17n-X ライブラリは、@c Mxim と言う名前を持つ外部入力メソッドを提供している。これは XIM (X Input Method) をバックグラウンドの入力エンジンとして利用する。シンボル @c Mxim は @c Minput_driver というプロパティを持っており、その値は入力ドライバ @c minput_xim_driver へのポインタである。 詳細については @c minput_xim_driver の説明を参照のこと。 */ /*=*/ #if !defined (FOR_DOXYGEN) || defined (DOXYGEN_INTERNAL_MODULE) /*** @addtogroup m17nInternal @{ */ #include #include #include "config.h" #include "m17n-gui.h" #include "m17n-misc.h" #include "internal.h" #include "internal-gui.h" #include "input.h" typedef struct { MDrawWindow win; MDrawMetric geometry; MDrawControl control; int mapped; } MInputGUIWinInfo; typedef struct { MInputContextInfo *ic_info; MFrame *frame; /* .x and .y are not used. */ MInputGUIWinInfo client; /* In the following members, is relative to . */ MInputGUIWinInfo focus; MInputGUIWinInfo preedit; MInputGUIWinInfo status; MInputGUIWinInfo candidates; } MInputGUIContextInfo; static MFace *status_face; static MFaceBoxProp face_box_prop; static int win_create_ic (MInputContext *ic) { MInputGUIContextInfo *win_ic_info; MInputGUIArgIC *win_info = (MInputGUIArgIC *) ic->arg; MFrame *frame = win_info->frame; if ((*minput_default_driver.create_ic) (ic) < 0) return -1; MSTRUCT_CALLOC (win_ic_info, MERROR_IM); win_ic_info->ic_info = (MInputContextInfo *) ic->info; win_ic_info->frame = frame; win_ic_info->client.win = win_info->client; (*frame->driver->window_geometry) (frame, win_info->client, win_info->client, &win_ic_info->client.geometry); win_ic_info->focus.win = win_info->focus; (*frame->driver->window_geometry) (frame, win_info->focus, win_info->client, &win_ic_info->focus.geometry); win_ic_info->preedit.win = (*frame->driver->create_window) (frame, win_info->client); win_ic_info->preedit.control.two_dimensional = 1; win_ic_info->preedit.control.as_image = 0; win_ic_info->preedit.control.with_cursor = 1; win_ic_info->preedit.control.cursor_width = 1; win_ic_info->preedit.control.enable_bidi = 1; win_ic_info->preedit.geometry.x = -1; win_ic_info->preedit.geometry.y = -1; win_ic_info->status.win = (*frame->driver->create_window) (frame, win_info->client); win_ic_info->status.control.as_image = 1; win_ic_info->status.control.enable_bidi = 1; win_ic_info->candidates.win = (*frame->driver->create_window) (frame, win_info->client); win_ic_info->candidates.control.as_image = 1; ic->info = win_ic_info; return 0; } static void win_destroy_ic (MInputContext *ic) { MInputGUIContextInfo *win_ic_info = (MInputGUIContextInfo *) ic->info; MInputContextInfo *ic_info = (MInputContextInfo *) win_ic_info->ic_info; MFrame *frame = win_ic_info->frame; (*frame->driver->destroy_window) (frame, win_ic_info->preedit.win); (*frame->driver->destroy_window) (frame, win_ic_info->status.win); (*frame->driver->destroy_window) (frame, win_ic_info->candidates.win); ic->info = ic_info; (*minput_default_driver.destroy_ic) (ic); free (win_ic_info); } static int win_filter (MInputContext *ic, MSymbol key, void *arg) { MInputGUIContextInfo *win_ic_info = (MInputGUIContextInfo *) ic->info; int ret; if (! ic || ! ic->active) return 0; if (key == Mnil && arg) { key = minput_event_to_key (win_ic_info->frame, arg); if (key == Mnil) return 1; } ic->info = win_ic_info->ic_info; ret = (*minput_default_driver.filter) (ic, key, arg); win_ic_info->ic_info = (MInputContextInfo *) ic->info; ic->info = win_ic_info; return ret; } static void adjust_window_and_draw (MFrame *frame, MInputContext *ic, MText *mt, int type) { MInputGUIContextInfo *win_ic_info = (MInputGUIContextInfo *) ic->info; MDrawControl *control; MDrawWindow win; MDrawMetric *geometry, physical, logical; int xoff = win_ic_info->focus.geometry.x; int yoff = win_ic_info->focus.geometry.y; int x0, x1, y0, y1; int len = mtext_nchars (mt); if (type == 0) { win = win_ic_info->preedit.win; control = &win_ic_info->preedit.control; geometry = &win_ic_info->preedit.geometry; len++; } else if (type == 1) { win = win_ic_info->status.win; control = &win_ic_info->status.control; geometry = &win_ic_info->status.geometry; } else { win = win_ic_info->candidates.win; control = &win_ic_info->candidates.control; geometry = &win_ic_info->candidates.geometry; } mdraw_text_extents (frame, mt, 0, len, control, &physical, &logical, NULL); x0 = physical.x, x1 = x0 + physical.width; y0 = physical.y, y1 = y0 + physical.height; if (x0 > logical.x) x0 = logical.x; if (x1 < logical.x + logical.width) x1 = logical.x + logical.width; if (y0 > logical.y) y0 = logical.y; if (y1 < logical.y + logical.height) y1 = logical.y + logical.height; physical.width = x1 - x0; physical.height = y1 - y0; physical.x = xoff + ic->spot.x; if (physical.x + physical.width > win_ic_info->client.geometry.width) physical.x = win_ic_info->client.geometry.width - physical.width; if (type == 0) { if (len <= 1) { physical.height = physical.width = 1; physical.x = physical.y = -1; } else { if (y0 > - ic->spot.ascent) { physical.height += y0 + ic->spot.ascent; y0 = - ic->spot.ascent; } if (y1 < ic->spot.descent) { physical.height += ic->spot.descent - y1; } physical.y = yoff + ic->spot.y + y0; } } else if (type == 1) { physical.y = yoff + ic->spot.y + ic->spot.descent + 2; if (physical.y + physical.height > win_ic_info->client.geometry.height && yoff + ic->spot.y - ic->spot.ascent - 2 - physical.height >= 0) physical.y = yoff + ic->spot.y - ic->spot.ascent - 2 - physical.height; } else { if (win_ic_info->status.mapped) { /* We assume that status is already drawn. */ if (win_ic_info->status.geometry.y < yoff + ic->spot.y) /* As there was no lower room for status, candidates must also be drawn upper. */ physical.y = win_ic_info->status.geometry.y - 1 - physical.height; else { /* There was a lower room for status. */ physical.y = (win_ic_info->status.geometry.y + win_ic_info->status.geometry.height + 1); if (physical.y + physical.height > win_ic_info->client.geometry.height) /* But not for candidates. */ physical.y = (yoff + ic->spot.y - ic->spot.ascent - 1 - physical.height); } } else { physical.y = yoff + ic->spot.y + ic->spot.descent + 2; if ((physical.y + physical.height > win_ic_info->client.geometry.height) && (yoff + ic->spot.y - ic->spot.ascent - 2 - physical.height >= 0)) physical.y = (yoff + ic->spot.y - ic->spot.ascent - 2 - physical.height); } } (*frame->driver->adjust_window) (frame, win, geometry, &physical); mdraw_text_with_control (frame, win, -x0, -y0, mt, 0, len, control); } static void win_callback (MInputContext *ic, MSymbol command) { MInputGUIContextInfo *win_ic_info = (MInputGUIContextInfo *) ic->info; MFrame *frame = win_ic_info->frame; if (command == Minput_preedit_draw) { MText *mt; MFace *face = mface (); if (! win_ic_info->preedit.mapped) { (*frame->driver->map_window) (frame, win_ic_info->preedit.win); win_ic_info->preedit.mapped = 1; } win_ic_info->preedit.control.cursor_pos = ic->cursor_pos; if (ic->spot.fontsize) mface_put_prop (face, Msize, (void *) ic->spot.fontsize); mface_merge (face, mface_underline); mtext_push_prop (ic->preedit, 0, mtext_nchars (ic->preedit), Mface, face); M17N_OBJECT_UNREF (face); if (ic->im->language != Mnil) mtext_put_prop (ic->preedit, 0, mtext_nchars (ic->preedit), Mlanguage, ic->im->language); if (ic->candidate_list && ic->candidate_show) mtext_push_prop (ic->preedit, ic->candidate_from, ic->candidate_to, Mface, mface_reverse_video); if (mtext_nchars (ic->produced) == 0) mt = ic->preedit; else { mt = mtext_dup (ic->produced); mtext_cat (mt, ic->preedit); win_ic_info->preedit.control.cursor_pos += mtext_nchars (ic->produced); } adjust_window_and_draw (frame, ic, mt, 0); if (ic->candidate_list) mtext_pop_prop (ic->preedit, 0, mtext_nchars (ic->preedit), Mface); mtext_pop_prop (ic->preedit, 0, mtext_nchars (ic->preedit), Mface); if (mtext_nchars (ic->produced) != 0) M17N_OBJECT_UNREF (mt); } else if (command == Minput_status_draw) { if (! win_ic_info->client.win) return; mtext_put_prop (ic->status, 0, mtext_nchars (ic->status), Mface, status_face); if (ic->im->language != Mnil) mtext_put_prop (ic->status, 0, mtext_nchars (ic->status), Mlanguage, ic->im->language); adjust_window_and_draw (frame, ic, ic->status, 1); } else if (command == Minput_candidates_draw) { MPlist *group; MText *mt; int i, len; int from, to; if (! ic->candidate_list || ! ic->candidate_show) { if (win_ic_info->candidates.mapped) { (*frame->driver->unmap_window) (frame, win_ic_info->candidates.win); win_ic_info->candidates.mapped = 0; } return; } if (! win_ic_info->candidates.mapped) { (*frame->driver->map_window) (frame, win_ic_info->candidates.win); win_ic_info->candidates.mapped = 1; } i = 0; group = ic->candidate_list; while (1) { if (mplist_key (group) == Mtext) len = mtext_len (mplist_value (group)); else len = mplist_length (mplist_value (group)); if (i + len > ic->candidate_index) break; i += len; group = mplist_next (group); } mt = mtext (); if (mplist_key (group) == Mtext) { MText *candidates = (MText *) mplist_value (group); from = (ic->candidate_index - i) * 2 + 1; to = from + 1; for (i = 0; i < len; i++) { mtext_cat_char (mt, ' '); mtext_cat_char (mt, mtext_ref_char (candidates, i)); } } else { MPlist *pl; for (pl = (MPlist *) mplist_value (group); i < ic->candidate_index && mplist_key (pl) != Mnil; i++, pl = mplist_next (pl)) { mtext_cat_char (mt, ' '); mtext_cat (mt, (MText *) mplist_value (pl)); } from = mtext_nchars (mt) + 1; to = from + mtext_nchars ((MText *) mplist_value (pl)); for (; mplist_key (pl) != Mnil; pl = mplist_next (pl)) { mtext_cat_char (mt, ' '); mtext_cat (mt, (MText *) mplist_value (pl)); } } mtext_cat_char (mt, ' '); mtext_push_prop (mt, 0, mtext_nchars (mt), Mface, status_face); mtext_push_prop (mt, from, to, Mface, mface_reverse_video); if (ic->im->language != Mnil) mtext_put_prop (mt, 0, mtext_nchars (mt), Mlanguage, ic->im->language); adjust_window_and_draw (frame, ic, mt, 2); M17N_OBJECT_UNREF (mt); } else if (command == Minput_set_spot) { minput_callback (ic, Minput_preedit_draw); minput_callback (ic, Minput_status_draw); minput_callback (ic, Minput_candidates_draw); } else if (command == Minput_toggle) { if (ic->active) { minput_callback (ic, Minput_preedit_done); minput_callback (ic, Minput_status_done); minput_callback (ic, Minput_candidates_done); } else { minput_callback (ic, Minput_preedit_start); minput_callback (ic, Minput_status_start); minput_callback (ic, Minput_candidates_start); } } else if (command == Minput_preedit_start) { } else if (command == Minput_preedit_done) { if (win_ic_info->preedit.mapped) { (*frame->driver->unmap_window) (frame, win_ic_info->preedit.win); win_ic_info->preedit.mapped = 0; } } else if (command == Minput_status_start) { if (! win_ic_info->status.mapped) { (*frame->driver->map_window) (frame, win_ic_info->status.win); win_ic_info->status.mapped = 1; } } else if (command == Minput_status_done) { if (win_ic_info->status.mapped) { (*frame->driver->unmap_window) (frame, win_ic_info->status.win); win_ic_info->status.mapped = 0; } } else if (command == Minput_candidates_start) { if (! win_ic_info->candidates.mapped) { (*frame->driver->map_window) (frame, win_ic_info->candidates.win); win_ic_info->candidates.mapped = 1; } } else if (command == Minput_candidates_done) { if (win_ic_info->candidates.mapped) { (*frame->driver->unmap_window) (frame, win_ic_info->candidates.win); win_ic_info->candidates.mapped = 0; } } else if (command == Minput_reset) { MInputCallbackFunc func; if (minput_default_driver.callback_list && (func = ((MInputCallbackFunc) mplist_get_func (minput_default_driver.callback_list, Minput_reset)))) { MInputContextInfo *ic_info = (MInputContextInfo *) win_ic_info->ic_info; ic->info = ic_info; (func) (ic, Minput_reset); ic->info = win_ic_info; } if (ic->preedit_changed) minput_callback (ic, Minput_preedit_draw); if (ic->status_changed) minput_callback (ic, Minput_status_draw); if (ic->candidates_changed) minput_callback (ic, Minput_candidates_draw); } } static int win_lookup (MInputContext *ic, MSymbol key, void *arg, MText *mt) { MInputGUIContextInfo *win_ic_info = (MInputGUIContextInfo *) ic->info; MInputContextInfo *ic_info = (MInputContextInfo *) win_ic_info->ic_info; int ret; ic->info = ic_info; ret = (*minput_default_driver.lookup) (ic, key, arg, mt); ic->info = win_ic_info; return ret; } int minput__win_init () { minput_gui_driver = minput_default_driver; minput_gui_driver.create_ic = win_create_ic; minput_gui_driver.destroy_ic = win_destroy_ic; minput_gui_driver.filter = win_filter; minput_gui_driver.lookup = win_lookup; { MPlist *plist = mplist (); minput_gui_driver.callback_list = plist; mplist_put_func (plist, Minput_preedit_start, M17N_FUNC (win_callback)); mplist_put_func (plist, Minput_preedit_draw, M17N_FUNC (win_callback)); mplist_put_func (plist, Minput_preedit_done, M17N_FUNC (win_callback)); mplist_put_func (plist, Minput_status_start, M17N_FUNC (win_callback)); mplist_put_func (plist, Minput_status_draw, M17N_FUNC (win_callback)); mplist_put_func (plist, Minput_status_done, M17N_FUNC (win_callback)); mplist_put_func (plist, Minput_candidates_start, M17N_FUNC (win_callback)); mplist_put_func (plist, Minput_candidates_draw, M17N_FUNC (win_callback)); mplist_put_func (plist, Minput_candidates_done, M17N_FUNC (win_callback)); mplist_put_func (plist, Minput_set_spot, M17N_FUNC (win_callback)); mplist_put_func (plist, Minput_toggle, M17N_FUNC (win_callback)); mplist_put_func (plist, Minput_reset, M17N_FUNC (win_callback)); } #if 0 /* This will make the caller of minput_method_open() pazzled. */ minput_driver = &minput_gui_driver; #endif face_box_prop.width = 1; face_box_prop.color_top = face_box_prop.color_left = face_box_prop.color_bottom = face_box_prop.color_right = msymbol ("black"); face_box_prop.inner_hmargin = face_box_prop.inner_vmargin = 2; face_box_prop.outer_hmargin = face_box_prop.outer_vmargin = 1; status_face = mface (); mface_put_prop (status_face, Mbox, &face_box_prop); return 0; } void minput__win_fini () { M17N_OBJECT_UNREF (status_face); if (minput_gui_driver.callback_list) { M17N_OBJECT_UNREF (minput_gui_driver.callback_list); minput_gui_driver.callback_list = NULL; } } /*** @} */ #endif /* !FOR_DOXYGEN || DOXYGEN_INTERNAL_MODULE */ /* External API */ /*** @addtogroup m17nInputMethodWin */ /*** @{ */ /*=*/ /***en @brief Input driver for internal input methods on window systems. The input driver @c minput_gui_driver is for internal input methods to be used on window systems. It creates sub-windows for a preedit text and a status text, and displays them at the input spot set by the function minput_set_spot (). The macro M17N_INIT () set the variable @c minput_driver to the pointer to this driver so that all internal input methods use it. Therefore, unless @c minput_driver is changed from the default, the driver dependent arguments to the functions whose name begin with minput_ must are treated as follows. The argument $ARG of the function minput_open_im () is ignored. The argument $ARG of the function minput_create_ic () must be a pointer to the structure @c MInputGUIArgIC. See the documentation of @c MInputGUIArgIC for more details. If the argument $KEY of function minput_filter () is @c Mnil, the argument $ARG must be a pointer to the object of type @c XEvent. In that case, $KEY is generated from $ARG. The argument $ARG of the function minput_lookup () must be the same one as that of the function minput_filter (). */ /***ja @brief ウィンドウシステムの内部入力メソッド用入力ドライバ. 入力ドライバ @c minput_gui_driver は、ウィンドウシステム上で用いられる入力メソッド用ドライバである。 このドライバは、関数 minput_set_spot () によって設定された入力スポットに preedit テキスト用のサブウィンドウと status テキスト用のサブウィンドウを作り、それぞれを表示する。 マクロ M17N_INIT () は変数 @c minput_driver をこのドライバへのポインタに設定し、全ての内部入力メソッドがこのドライバを使うようにする。 したがって、@c minput_driver がデフォルト値のままであれば、minput_ で始まる名前を持つ関数の引数のうちドライバ依存のものは以下のようになる。 関数 minput_open_im () の引数 $ARG は無視される。 関数 minput_create_ic () の引数 $ARG は構造体 @c MInputGUIArgIC へのポインタでなくてはならない。詳細については @c MInputGUIArgIC の説明を参照のこと。 関数 minput_filter () の引数 $ARG が @c Mnil の場合、 $ARG は @c XEvent 型のオブジェクトへのポインタでなくてはならない。この場合 $KEY は $ARG から生成される。 関数 minput_lookup () の引数 $ARG は関数 minput_filter () の引数 $ARG と同じでなくてはならない。 */ MInputDriver minput_gui_driver; /*=*/ /***en @brief Symbol of the name "xim". The variable Mxim is a symbol of name "xim". It is a name of the input method driver #minput_xim_driver. */ /***ja @brief "xim"を名前として持つシンボル . 変数 Mxim は"xim"を名前として持つシンボルである。"xim" は入力メソッドドライバ #minput_xim_driver の名前である。 */ MSymbol Mxim; /*=*/ /***en @brief Convert an event to an input key. The minput_event_to_key () function returns the input key corresponding to event $EVENT on $FRAME by a window system dependent manner. In the m17n-X library, $EVENT must be a pointer to the structure @c XKeyEvent, and it is handled as below. At first, the keysym name of $EVENT is acquired by the function @c XKeysymToString. Then, the name is modified as below. If the name is one of "a" .. "z" and $EVENT has a Shift modifier, the name is converted to "A" .. "Z" respectively, and the Shift modifier is cleared. If the name is one byte length and $EVENT has a Control modifier, the byte is bitwise anded by 0x1F and the Control modifier is cleared. If $EVENT still has modifiers, the name is preceded by "S-" (Shift), "C-" (Control), "M-" (Meta), "A-" (Alt), "G-" (AltGr), "s-" (Super), and "H-" (Hyper) in this order. For instance, if the keysym name is "a" and the event has Shift, Meta, and Hyper modifiers, the resulting name is "M-H-A". At last, a symbol who has the name is returned. */ /***ja @brief イベントを入力キーに変換する. 関数 minput_event_to_key () は、$FRAME のイベント $EVENT に対応する入力キーを返す。ここでの「対応」はウィンドウシステムに依存する。 m17n-X ライブラリの場合には、$EVENT は 構造体 @c XKeyEvent へのポインタであり、次のように処理される。 まず、関数 @c XKeysymToString によって、$EVENT の keysym 名を取得し、次いで以下の変更を加える。 名前が "a" .. "z" のいずれかであって $EVENT に Shift モディファイアがあれば、名前はそれぞれ "A" .. "Z" に変換され、Shift モディファイアは取り除かれる。 名前が1バイト長で $EVENT に Control モディファイアがあれば、名前と 0x1F とをビット単位で and 演算し、Control モディファイアは取り除かれる。 それでも $EVENT にまだモディファイアがあれば、名前の前にそれぞれ "S-" (Shift), "C-" (Control), "M-" (Meta), "A-" (Alt), , "G-" (AltGr), "s-" (Super), "H-" (Hyper)がこの順番で付く。 たとえば、keysym 名が "a" でイベントが Shift, Meta, and Hyper モディファイアを持てば、得られる名前は "M-H-A" である。 最後にその名前を持つシンボルを返す。*/ MSymbol minput_event_to_key (MFrame *frame, void *event) { int modifiers; MSymbol key; char *name, *str; M_CHECK_READABLE (frame, MERROR_IM, Mnil); key = (*frame->driver->parse_event) (frame, event, &modifiers); if (! modifiers) return key; name = msymbol_name (key); str = alloca (strlen (name) + 2 * 8 + 1); str[0] = '\0'; if (modifiers & MINPUT_KEY_SHIFT_MODIFIER) strcat (str, "S-"); if (modifiers & MINPUT_KEY_CONTROL_MODIFIER) strcat (str, "C-"); if (modifiers & MINPUT_KEY_META_MODIFIER) strcat (str, "M-"); if (modifiers & MINPUT_KEY_ALT_MODIFIER) strcat (str, "A-"); if (modifiers & MINPUT_KEY_ALTGR_MODIFIER) strcat (str, "G-"); if (modifiers & MINPUT_KEY_SUPER_MODIFIER) strcat (str, "s-"); if (modifiers & MINPUT_KEY_HYPER_MODIFIER) strcat (str, "H-"); strcat (str, name); return msymbol (str); } /*** @} */ /* Local Variables: coding: euc-japan End: */