GtkIMContext 사용법에 관한 글
제 1 판 2003 년 9 월 17 일
제 2 판 2003 년 10 월 4 일
제 3 판 2003 년 10 월 27 일
제 4 판 2005 년 4 월 30 일
한글판 johnsonj
제 0 장 서문
§ 라이센스
이 문서 및 프로그램의 저작권은 이와모토 카즈키가 가진다. 이와모토 카즈키의 저작권을 부인할 수 없다.
이 문서 및 프로그램을 변경하거나 수정하여 배포할 수 있다.
이 문서 및 프로그램의 일부 또는 전부를 다른 저작물에 포함할 수 있다.
이 문서 및 프로그램의 내용을 저작권자는 보장하지 않는다.
이 문서 및 프로그램으로 인한 어떠한 문제도 저작권자는 책임지지 않는다.
§ 연락처
Web 사이트 : http : //
www.maid.org/ E 메일 :
i...@maid.org 전화 : 090-9224-6801
§ 전문
GTK +는 문자열 입력을 위해 GtkEntry과 GtkText 등의 창부품이 있다.
문자열을 입력하려면, 이런 창부품을 사용할 수 있다. 대부분의
프로그램에서 이정도 창부품이면 충분하다. 그러나 HTML 편집기나
워드프로세서 등의 소프트웨어는 문자열 입력을 직접 처리해야 한다.
키 누르기 이벤트를 처리하는 것만으로는 부족하고, Input Method에 대해 생각해야
한다. GTK + -2.0에는 GtkIMContext라는 위젯이 있고,
이것을 사용하면 문자열을 정확하게 입력할 수 있다.
이 글에서는 GTK + 응용 프로그램의 문자 입력에 대해 설명한다.
§ 업데이트 내역
2003 년 10 월 4 일
tkng 씨의 의견에 따라 제 1 장 키 입력 이벤트와 2 장 preedit 표시를 변경.
2003 년 10 월 27 일
gtk_im_context_filter_keypress의 전후 처리에 대한 설명을 변경.
키보드 접근 방법에 대한 설명을 추가.
2005 년 4 월 30 일
im0103.c의 GtkItemFactory를 폐기하고, 일반적인 방법으로 메뉴를 구축하도록 변경.
§ 목차
제 0 장 서문
라이센스
연락처
전문
업데이트 내역
목차
제 1 장 키 입력 이벤트
키 입력 이벤트
GtkIMContext 사용
키보드 가속기의 함정
제 2 장 preedit보기
preedit_changed과 preedit_start, preedit_end
preedit보기
gtk_im_context_set_cursor_location
gtk_im_context_set_use_preedit
제 3 장 immodule 전환
팝업 메뉴 만들기
제 4 장 기타
gtk_im_context_reset
retrieve-surrounding
delete-surrounding
제 5 장 부록
GtkIMContext의 요점
GtkIMContext의 문제점
제 1 장 키 입력의 기초
§ 키 입력 이벤트
im0101.c는 키 입력 이벤트를 연결하여 그 내용을 표시하는 프로그램이다.
im0101.c ----------------------------------------------- ------------------------
#include <locale.h>
#include <gtk / gtk.h>
/ ************************************************* *****************************
* *
* 키 입력 이벤트 *
* *
************************************************** **************************** /
static gboolean
signal_key_press (GtkWidget * widget, GdkEventKey * event, gpointer user_data)
{
gint i;
g_print ( "key_press : keyval = % 02X", event-> keyval);
if (event-> state & GDK_SHIFT_MASK)
g_print ( "Shift");
if (event-> state & GDK_CONTROL_MASK)
g_print ( "Ctrl");
if (event-> state & GDK_MOD1_MASK)
g_print ( "Alt");
g_print ( "length = % d", event-> length);
if (event-> length> 0)
{
for (i = 0; event-> string [i]! = '\ 0'; i ++)
g_print ( "% 02X", event-> string [i]);
g_print ( "string = % s", event-> string);
}
g_print ( "\ n");
return TRUE;
}
/ ************************************************* *****************************
* *
* *
* *
************************************************** **************************** /
int
main (int argc, char * argv [])
{
GtkWidget * window;
/ * 초기화 * /
setlocale (LC_ALL, "");
gtk_set_locale ();
gtk_init (& argc, & argv);
/ * 윈도우 * /
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (G_OBJECT (window) "destroy"
G_CALLBACK (gtk_main_quit), NULL);
g_signal_connect (G_OBJECT (window) "key-press-event"
G_CALLBACK (signal_key_press), NULL);
/ * 표시 * /
gtk_widget_show_all (window);
gtk_window_set_policy (GTK_WINDOW (window), TRUE, TRUE, TRUE);
gtk_main ();
return 0;
}
-------------------------------------------------- -----------------------------
키 입력은 이 정도로 충분하다. GdkEventKey 구조의 keyval 멤버의 값을 참조하면, 어떤 키를 눌렀는지 알 수 있다.
그리고 key-press-event 이벤트에서 어떤 키를 놓았는지 알 수 있다. 그러나 문자 또는 문자열이 필요하다면,
이 방법은 GTK + -2.0에서는 맞지 않는다. 또 GdkEventKey 구조의 string 멤버는 신뢰할 수 없다.
UTF-8이 아니고 로칼 인코딩을 사용하기 때문이다.
대신 GTK + -2.0에서 GtkIMContext라는 구조가 있다. 이것으로 문자를 입력해야 한다.
UFT-8이 인코딩이기 때문에 문자와 그 입력 방법 (immodule)을 바꿀 수 있다.
§GtkIMContext 사용
im0102.c는 im0101.c을 개량하고 GtkIMContext를 이용하도록 한 것이다.
im0102.c ----------------------------------------------- ------------------------
#include <locale.h>
#include <gtk / gtk.h>
/ ************************************************* *****************************
* *
* *
* *
************************************************** **************************** /
static void
signal_realize (GtkWidget * widget, GtkIMContext * im_context)
{
gtk_im_context_set_client_window (im_context, widget-> window);
}
static void
signal_unrealize (GtkWidget * widget, GtkIMContext * im_context)
{
gtk_im_context_set_client_window (im_context, NULL);
}
static gboolean
signal_focus_in (GtkWidget * widget, GdkEventFocus * event,
GtkIMContext * im_context)
{
GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
gtk_im_context_focus_in (im_context);
return FALSE;
}
static gboolean
signal_focus_out (GtkWidget * widget, GdkEventFocus * event,
GtkIMContext * im_context)
{
GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
gtk_im_context_focus_out (im_context);
return FALSE;
}
/ ************************************************* *****************************
* *
* 키 입력 이벤트 *
* *
************************************************** **************************** /
static gboolean
signal_key_press (GtkWidget * widget, GdkEventKey * event,
GtkIMContext * im_context)
{
if (gtk_im_context_filter_keypress (im_context, event))
return TRUE;
g_print ( "key_press : keyval = % 02X \ n", event-> keyval);
return TRUE;
}
static gboolean
signal_key_release (GtkWidget * widget, GdkEventKey * event,
GtkIMContext * im_context)
{
if (gtk_im_context_filter_keypress (im_context, event))
return TRUE;
g_print ( "key_release : keyval = % 02X \ n", event-> keyval);
return TRUE;
}
/ ************************************************* *****************************
* *
* GtkIMContext *
* *
************************************************** **************************** /
static void
signal_commit (GtkIMContext * im_context, gchar * arg1, gpointer user_data)
{
g_print ( "commit : % d % s \ n", strlen (arg1) arg1);
}
/ ************************************************* *****************************
* *
* *
* *
************************************************** **************************** /
int
main (int argc, char * argv [])
{
GtkIMContext * im_context;
GtkWidget * window;
/ * 초기화 * /
setlocale (LC_ALL, "");
gtk_set_locale ();
gtk_init (& argc, & argv);
/ * InputMethod * /
im_context = gtk_im_multicontext_new ();
g_signal_connect (im_context "commit"G_CALLBACK (signal_commit), NULL);
/ * 윈도우 * /
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (G_OBJECT (window) "destroy"
G_CALLBACK (gtk_main_quit), NULL);
g_signal_connect (G_OBJECT (window) "key-press-event"
G_CALLBACK (signal_key_press) im_context);
g_signal_connect (G_OBJECT (window) "key-release-event"
G_CALLBACK (signal_key_release) im_context);
g_signal_connect (G_OBJECT (window) "realize"
G_CALLBACK (signal_realize) im_context);
g_signal_connect (G_OBJECT (window) "unrealize"
G_CALLBACK (signal_unrealize) im_context);
g_signal_connect (G_OBJECT (window) "focus-in-event"
G_CALLBACK (signal_focus_in) im_context);
g_signal_connect (G_OBJECT (window) "focus-out-event"
G_CALLBACK (signal_focus_out) im_context);
/ * 표시 * /
gtk_widget_show_all (window);
gtk_window_set_policy (GTK_WINDOW (window), TRUE, TRUE, TRUE);
gtk_main ();
g_object_unref (im_context);
return 0;
}
-------------------------------------------------- -----------------------------
먼저 realize / realize / focus-in-event / focus-out-event이라는 짝이 추가된다.
GtkIMContext를 이용하려면 꼭 필요하다.
key-press-event와 key-release-event는 gtk_im_context_filter_keypress와 함께 일을 한다.
키 이벤트에 대하여 이 API가 TRUE를 반환한다면, GtkIMContext이 처리한 것이고.
그렇지 않은 경우에는 처리되지 않은 것이다.
여기서 TRUE를 반환하면 이벤트를 TRUE로 종료해야 한다.
키 입력은 GtkIMContext에서 사용하기 때문에 응용 프로그램은 키 입력을 사용하지 않는다.
gtk_im_context_filter_keypress 의 결과로 commit 신호가 발생한다.
commit 신호는 입력이 확정된 문자열을 인자로 전달한다.
간단한 문자 입력을 예를 들어 보자.
키보드의 "A"를 누르면 문자 "a"가 입력되고 commit 신호 (인자는 "a")가 발생한다.
문자 입력은 commit 신호에만 허용된다.
GdkEventKey 구조의 string 멤버에 문자가 있지만,
commit 신호가 발생하지 않고 gtk_im_context_filter_keypress는 TRUE를 반환할 가능성이 있다.
그래서 string 멤버의 값을 문자 입력으로 취급하면 안된다.
gtk_im_context_filter_keypress가 FALSE를 반환할지라도
string 멤버의 문자를 문자 입력으로 취급하면 안된다.
FALSE를 반환하면, 그 값을 커서의 이동 등의 문자 입력 이외의 처리에 사용하는 것은 괞찬다.
또 string 멤버에 문자가 없을 때 gtk_im_context_filter_keypress를 호출하면 commit 신호가 발생할 수 있다.
예를 들어 키보드의 A를 눌렀을 때, gtk_im_context_filter_keypress를 호출하면
· TRUE가 반환되면, commit 신호가 발생하고 문자열이 입력된다
· TRUE가 반환되어도 commit 신호가 발생하지 않을 수 있다.
· FALSE가 반환되면 commit 신호는 발생하지 않는다.
리턴키나 탭키 등은 commit 신호의 대상이 되는 문자에 포함되지 않는다.
따라서 반환 및 탭 등을 입력 할 경우, gtk_im_context_filter_keypress 호출후 직접
처리해야 한다. immodule에 따라 리턴키나 탭을 사용하는 경우,
gtk_im_context_filter_keypress가 TRUE를 반환 할 수 있다.
역시 gtk_im_context_filter_keypress의 반환 값이 TRUE인 경우는 처리를 해야 한다.
gtk_im_context_filter_keypress보다 먼저 키 입력을 처리하는 것은 상당히 신중해야 한다.
응용 프로그램 고유의 단축키 등을 키 입력보다 먼저 처리해 버리면
immodule이 필요할 경우 키 입력을 얻을 수 없어서 문자를 입력할 수 없게 되어 버릴 수도 있다.
-------------------------------------------------- -----------------------------
static gboolean
signal_key_press (GtkWidget * widget, GdkEventKey * event,
GtkIMContext * im_context)
{
static gboolean ctrl_x = FALSE;
if ((event-> keyval == GDK_X || event-> keyval == GDK_x)
&& (event-> state & GDK_CONTROL_MASK))
{
ctrl_x = TRUE;
return TRUE;
}
if (ctrl_x)
{
ctrl_x = FALSE;
if (event-> keyval == GDK_2)
{
/ * 윈도우를 분할하는 등의 처리 * /
return TRUE;
}
}
if (gtk_im_context_filter_keypress (im_context, event))
return TRUE;
/ * 다른 키 입력 처리 * /
return TRUE;
}
-------------------------------------------------- -----------------------------
Ctrl + X에 이어 2를 누르면 윈도우를 분할한다. 먼저 처리하는 것은
문제가 있다. 이제 Ctrl + X가 immodule을 통과할 수 없다.
-------------------------------------------------- -----------------------------
static gboolean
signal_key_press (GtkWidget * widget, GdkEventKey * event,
GtkIMContext * im_context)
{
static gboolean ctrl_x = FALSE;
if (! ctrl_x && (event-> keyval == GDK_X || event-> keyval == GDK_x)
&& (event-> state & GDK_CONTROL_MASK))
{
ctrl_x = TRUE;
return TRUE;
}
if (ctrl_x)
{
ctrl_x = FALSE;
if (event-> keyval == GDK_2)
{
/ * 윈도우를 분할하는 등의 처리 * /
return TRUE;
}
}
if (gtk_im_context_filter_keypress (im_context, event))
return TRUE;
/ * 다른 키 입력 처리 * /
return TRUE;
}
-------------------------------------------------- -----------------------------
첫 번째 if 문에 ctrl_x를 넣고 Ctrl + X에 이어 Ctrl + X를 다시 누를 때에는
Ctrl + X를 immodule에 전달하기로 했다.
이것이라면 Ctrl + X를 immodule에 전달할 수 있다.
그러나 동일한 작업을 하는 다른 응용 프로그램이라면 Ctrl + X를 누르면 그것으로 끝나는 반면,
이 응용 프로그램은 Ctrl + X를 두 번 눌러야 한다.
immodule이 제공하는 입력 방법은 그 입력 대상의 문자 입력 방법의 역사와 전통에서 유래한다
(물론 GTK +의 역사보다 훨씬 오래되었고 더 널리 퍼져 있다).
사용자는 현재의 입력 방법에 익숙하다.
응용 프로그램마다 입력이 다르게 되면 매우 불편한 일이다.
-------------------------------------------------- -----------------------------
static gboolean
signal_key_press (GtkWidget * widget, GdkEventKey * event,
GtkIMContext * im_context)
{
static gboolean ctrl_x = FALSE;
if (ctrl_x)
{
ctrl_x = FALSE;
if (event-> keyval == GDK_2)
{
/ * 창 분할 등의 처리 * /
return TRUE;
}
}
if (gtk_im_context_filter_keypress (im_context, event))
return TRUE;
if ((event-> keyval == GDK_X || event-> keyval == GDK_x)
&& (event-> state & GDK_CONTROL_MASK))
{
ctrl_x = TRUE;
return TRUE;
}
/ * 다른 키 입력 처리 * /
return TRUE;
}
-------------------------------------------------- -----------------------------
Ctrl + X의 처리를 gtk_im_context_filter_keypress 이후에 했다.
Ctrl + X는 바로 immodule에 전달된다.
immodule이 Ctrl + X를 처리하지 않은 경우에만 응용 프로그램이 Ctrl + X를 처리하면 된다.
immodule이 항상 Ctrl + X를 처리한다 (gtk_im_context_filter_keypress는 항상 TRUE
를 반환한다).
그러나 immodule은 언제든지 전환할 수 있다는 것을 기억하라.
immodule은 Ctrl + X를 입력할 필요없이 immodule을 바꾸면 끝난다.
(응용 프로그램이 항상 키를 처리하고 버리는 것은 immodule이 어떻게 할 수 없는 일이지만,
immodule이 항상 키를 처리하고 버리는 것은 immodule 전환으로 해결 가능하다)
-------------------------------------------------- -----------------------------
static gboolean
signal_key_press (GtkWidget * widget, GdkEventKey * event,
GtkIMContext * im_context)
{
static gboolean edit = FALSE;
if (! edit)
{
switch (event-> keyval)
{
case GDK_a :
case GDK_i :
edit = TRUE;
break;
case GDK_h :
case GDK_j :
case GDK_k :
case GDK_l :
/ * 커서의 이동 등의 처리 * /
return TRUE;
}
return TRUE;
}
if (gtk_im_context_filter_keypress (im_context, event))
return TRUE;
if (event-> keyval == GDK_Escape
|| (event-> keyval == GDK_c && (event-> state & GDK_CONTROL_MASK)))
{
edit = FALSE;
return TRUE;
}
/ * 다른 키 입력 처리 * /
return TRUE;
}
-------------------------------------------------- -----------------------------
vi의 키 조작 구현이 위와 같다.
실제로는 이 예보다 복잡하게 될 것이다. 구조가 복잡하게 될 경우, 설계에 충분한 주의가 필요하다.
설계를 잘못하면 사용자의 immodule로 입력이 전달되지 않거나, 표준이 아닌 입력 방법을 강요당하게 된다.
gtk_im_context_filter_keypress과 immodule의 관계를 잘 모른다면,
gtk_im_context_filter_keypress 전에 키 입력을 처리하는 디자인은 피하라.
키 이벤트는 즉시 gtk_im_context_filter_keypress에 전달하고
FALSE가 돌아올 경우에만 응용 프로그램이 키 입력 처리를 수행하는 간단한 구조가 좋다.
정리하면 아래와 같다.
· 문자의 입력은 모두 commit 신호로 처리한다
· gtk_im_context_filter_keypress가 TRUE를 돌려주면 즉시 TRUE로 종료
· gtk_im_context_filter_keypress가 FALSE이면 문자 입력 이외의 처리가 가능
· gtk_im_context_filter_keypress보다 먼저 처리할 때 신중하게 할 필요가 있다
§ 키보드 단축키의 함정
im0103.c은 메뉴와 키보드 단축키의 예다.
GTK + 애플리케이션에 이런 구조가 많다.
Ctrl + Q를 누르면 키보드 단축키에 따라 메뉴의 Exit를 선택했을 때와 같은 동작이 이루어진다.
im0103.c ----------------------------------------------- ------------------------
#include <locale.h>
#include <gtk / gtk.h>
/ ************************************************* *****************************
* *
* 메뉴 *
* *
************************************************** **************************** /
void
menu_activate (GtkMenuItem * menuitem, gpointer user_data)
{
g_print ( "menu exit \ n");
}
/ ************************************************* *****************************
* *
* 키 입력 이벤트 *
* *
************************************************** **************************** /
static gboolean
signal_key_press (GtkWidget * widget, GdkEventKey * event, gpointer user_data)
{
gint i;
g_print ( "key_press : keyval = % 02X", event-> keyval);
if (event-> state & GDK_SHIFT_MASK)
g_print ( "Shift");
if (event-> state & GDK_CONTROL_MASK)
g_print ( "Ctrl");
if (event-> state & GDK_MOD1_MASK)
g_print ( "Alt");
g_print ( "length = % d", event-> length);
if (event-> length> 0)
{
for (i = 0; event-> string [i]! = '\ 0'; i ++)
g_print ( "% 02X", event-> string [i]);
g_print ( "string = % s", event-> string);
}
g_print ( "\ n");
return TRUE;
}
/ ************************************************* *****************************
* *
* *
* *
************************************************** **************************** /
int
main (int argc, char * argv [])
{
guint key;
GdkModifierType mods;
GtkAccelGroup * accel_group;
GtkWidget * window * drawing_area * vbox;
GtkWidget * menu_bar * menu_shell * menu_item;
/ * 초기화 * /
setlocale (LC_ALL, "");
gtk_set_locale ();
gtk_init (& argc, & argv);
/ * 윈도우 * /
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (G_OBJECT (window) "destroy"
G_CALLBACK (gtk_main_quit), NULL);
/ * DrawingArea * /
drawing_area = gtk_drawing_area_new ();
g_signal_connect (G_OBJECT (drawing_area) "key-press-event"
G_CALLBACK (signal_key_press), NULL);
gtk_widget_add_events (drawing_area,
GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
GTK_WIDGET_SET_FLAGS (drawing_area, GTK_CAN_FOCUS);
/ * 메뉴 * /
accel_group = gtk_accel_group_new ();
menu_item = gtk_menu_item_new_with_mnemonic ( "E_xit");
gtk_accelerator_parse ( "<Control> q", & key & mods);
gtk_widget_add_accelerator (menu_item "activate", accel_group, key, mods,
GTK_ACCEL_VISIBLE | GTK_ACCEL_LOCKED);
g_signal_connect (G_OBJECT (menu_item) "activate"
G_CALLBACK (menu_activate), NULL);
menu_shell = gtk_menu_new ();
gtk_menu_shell_append (GTK_MENU_SHELL (menu_shell) menu_item);
menu_item = gtk_menu_item_new_with_mnemonic ( "_File");
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item) menu_shell);
menu_bar = gtk_menu_bar_new ();
gtk_menu_shell_append (GTK_MENU_SHELL (menu_bar) menu_item);
gtk_window_add_accel_group (GTK_WINDOW (window) accel_group);
/ * 상자 * /
vbox = gtk_vbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox) menu_bar, FALSE FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox) drawing_area, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER (window) vbox);
/ * 표시 * /
gtk_widget_show_all (window);
gtk_window_set_policy (GTK_WINDOW (window), TRUE, TRUE, TRUE);
gtk_main ();
return 0;
}
-------------------------------------------------- -----------------------------
이렇게 하면 될거 같지만, Ctrl + Q를 눌러도 DrawingArea 키 이벤트가 발생하지 않는다.
gtk_im_context_filter_keypress를 키 이벤트로 호출하고 싶어도
키보드 가속기에서 지정한 키가 먼저 처리되어 버려,
immodule에 도달하는 일은 결코 일어나지 않는다.
즉, 위에서 gtk_im_context_filter_keypress 전에 키를 처리해 버리는 나쁜 예와 동일하다.
키보드 가속기를 평소대로 사용했을 뿐인데 immodule에 관련하여 문제가 일어난다.
직접 gtk_im_context_filter_keypress을 다룬다면 그 취급에 주의하라.
키보드 가속기를 사용하는 것이 목적인데 immodule까지 연루되는 것은 의문이다.
현재로는 immodule을 사용하는 경우, 키보드 단축키를 사용하지 않는 것이 더 나은 선택이다.
제 2 장 preedit보기
§preedit_changed과 preedit_start, preedit_end
im0102.c에서 입력 할 수있게 되었다. 그러나 입력 중인 문자 (preedit)를 알지 못한다.
키 입력과 문자가 완전히 1 대 1로 대응하는 경우, 입력 중인 문자가 없다.
그러나 많은 immodule은 여러 키 입력에 하나 이상의 문자를 입력한다.
예를 들어, 가타가나를 입력하는 경우, k를 입력 한 순간에 문자가 확정되지 않는다.
(commit 신호가 발생하지 않는다). 만약 여기에서 a를 입력하면 가타카나의 '가'가 확정된다.
y를 입력하면 아직 문자는 확정되지 않는다. 그 다음 a를 입력하면 카타카나의 "보정"이 확정된다.
여기에 입력 문자인 k와 y를 표시해야 한다. 응용 프로그램에 기회를 주기 위한 신호이다.
그것이 바로 preedit_changed과 preedit_start, preedit_end이다.
im0102.c는 preedit_changed과 preedit_start, preedit_end의 각 신호의 내용을 표시한다.
im0201.c ----------------------------------------------- ------------------------
/ ************************************************* *****************************
* *
* GtkIMContext *
* *
************************************************** **************************** /
static void
signal_commit (GtkIMContext * im_context, gchar * arg1, gpointer user_data)
{
g_print ( "commit : % d % s \ n", strlen (arg1) arg1);
}
static void
signal_preedit_start (GtkIMContext * im_context, gpointer user_data)
{
g_print ( "preedit_start \ n");
}
static void
signal_preedit_end (GtkIMContext * im_context, gpointer user_data)
{
g_print ( "preedit_end \ n");
}
static void
signal_preedit_changed (GtkIMContext * im_context, gpointer user_data)
{
gchar * str;
gint cursor_pos;
gtk_im_context_get_preedit_string (im_context & str, NULL, & cursor_pos);
g_print ( "preedit_change : % d % d % s \ n", cursor_pos, strlen (str) str);
g_free (str);
}
/ ************************************************* *****************************
* *
* *
* *
************************************************** **************************** /
int
main (int argc, char * argv [])
{
GtkIMContext * im_context;
GtkWidget * window;
/ * 초기화 * /
setlocale (LC_ALL, "");
gtk_set_locale ();
gtk_init (& argc, & argv);
/ * InputMethod * /
im_context = gtk_im_multicontext_new ();
g_signal_connect (im_context "commit"G_CALLBACK (signal_commit), NULL);
g_signal_connect (im_context "preedit_start"
G_CALLBACK (signal_preedit_start), NULL);
g_signal_connect (im_context "preedit_end"
G_CALLBACK (signal_preedit_end), NULL);
g_signal_connect (im_context "preedit_changed"
G_CALLBACK (signal_preedit_changed), NULL);
/ * 윈도우 * /
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (G_OBJECT (window) "destroy"
G_CALLBACK (gtk_main_quit), NULL);
g_signal_connect (G_OBJECT (window) "key-press-event"
G_CALLBACK (signal_key_press) im_context);
g_signal_connect (G_OBJECT (window) "key-release-event"
G_CALLBACK (signal_key_release) im_context);
g_signal_connect (G_OBJECT (window) "realize"
G_CALLBACK (signal_realize) im_context);
g_signal_connect (G_OBJECT (window) "unrealize"
G_CALLBACK (signal_unrealize) im_context);
g_signal_connect (G_OBJECT (window) "focus-in-event"
G_CALLBACK (signal_focus_in) im_context);
g_signal_connect (G_OBJECT (window) "focus-out-event"
G_CALLBACK (signal_focus_out) im_context);
/ * 표시 * /
gtk_widget_show_all (window);
gtk_window_set_policy (GTK_WINDOW (window), TRUE, TRUE, TRUE);
gtk_main ();
g_object_unref (im_context);
return 0;
}
-------------------------------------------------- -----------------------------
preedit에 변화가있을 때 preedit_changed 신호가 발생한다.
preedit이 확정되거나 preedit가 비활성화되어 preedit이 분실된 경우에도 preedit_changed시
시그널이 발생한다. preedit_changed 신호의 인자는 preedit 문자열이 아니므로,
gtk_im_context_get_preedit_string를 호출하여 문자열을 가져와야 한다.
(불필요한 정보는 이 API의 인자에 NULL을 할당한다).
이 API의 cursor_pos 대한 설명을 보면 현재 GTK + 2.2 API Reference에 의하면
조립 문자열 안에서 (바이트 단위로) 커서의 위치를 저장한 장소라고 씌여 있다.
그러나 cursor_pos는 바이트 단위가 아닌 문자 단위이다.
UTF-8로 "한글」의 「한」과 「글」의 사이는 6이지만, 문자 단위로는 2이다.
preedit_start와 preedit_end는 입력이 시작될 때와 끝날 때 발생한다.
xim이라면 XIM이 활성화되었을 때와 해제될 때 각각 신호가 발생한다.
그러나 모든 immodule이 XIM 같은 구조를 가진 것은 아니다.
preedit 문자가없는 상태에서 문자 상태가되었을 때 preedit_start이 발생하고
preedit에 문자가 없어 졌을 때에 preedit_end가 발생할 수 있다.
이것은 각 immodule의 사양에 따라 달라진다
(GTK + 2.2 API 참조서에는 명확한 설명이 없다).
§preedit보기
위의 신호에 따라 preedit를 표시하는 것은 응용 프로그램이 책임진다.
GTK + -2.0 창부품은 (GtkEntry, GtkText)은 preedit의 변화에 따라 커서 위치에 preedit를 삽입한다.
이것은 아주 좋은 방법이지만, preedit의 변화에따라 전체를 변화시킬 필요가 있으며,
시간이 많이 걸리는 작업이다.
간단한 방법으로는 GtkLabel 등의 위젯을 설치하고, 거기에서 볼 수 있다.
그러나 이런 구현은 간단하지만 사용자가 사용하기가 어색하다.
절충안은 커서 위치에 preedit을 표시하는 윈도우를 놓고 거기서 preedit을 표시하는 방법이 있다.
이 방법은 전자만큼 좋은 것은 아니지만, 구현은 비교적 간단하다.
GTK + 이외의 응용 프로그램에서도 많이 볼 수있는 방법이다.
im0202.c ----------------------------------------------- ------------------------
/ ************************************************* *****************************
* *
* GtkIMContext *
* *
************************************************** **************************** /
static void
signal_commit (GtkIMContext * im_context, gchar * arg1, gpointer user_data)
{
g_print ( "commit : % d % s \ n", strlen (arg1) arg1);
}
static void
signal_preedit_changed (GtkIMContext * im_context, GtkWidget * drawing)
{
int width, height;
gchar * str;
gint x, y;
PangoAttrList * attrs;
PangoLayout * layout;
GtkWidget * preedit * window;
GdkRectangle rc;
preedit = gtk_widget_get_parent (drawing);
window = g_object_get_data (G_OBJECT (preedit) "user_data");
gtk_im_context_get_preedit_string (im_context & str & attrs, NULL);
if (strlen (str)> 0)
{
layout = gtk_widget_create_pango_layout (window, str);
pango_layout_set_attributes (layout, attrs);
pango_layout_get_pixel_size (layout & width & height);
g_object_unref (layout);
gdk_window_get_origin (window-> window & x & y);
gtk_window_move (GTK_WINDOW (preedit) x, y);
gtk_window_resize (GTK_WINDOW (preedit), width, height);
gtk_widget_show (preedit);
gtk_widget_queue_draw_area (preedit, 0, 0, width, height);
}
else
{
gtk_widget_hide (preedit);
}
g_free (str);
pango_attr_list_unref (attrs);
}
gboolean signal_expose (GtkWidget * widget,
GdkEventExpose * event, GtkIMContext * im_context)
{
gchar * str;
gint cursor_pos;
GdkGC * gc;
PangoAttrList * attrs;
PangoLayout * layout;
GdkColor color [2] = {{0, 0x0000, 0x0000, 0x0000}
{0, 0xffff, 0xffff, 0xffff}};
gtk_im_context_get_preedit_string (im_context & str & attrs & cursor_pos);
layout = gtk_widget_create_pango_layout (g_object_get_data
(G_OBJECT (gtk_widget_get_parent (widget)) "user_data") str);
pango_layout_set_attributes (layout, attrs);
gc = gdk_gc_new (widget-> window);
gdk_color_alloc (gdk_colormap_get_system () color);
gdk_color_alloc (gdk_colormap_get_system () color + 1);
/ * 배경 * /
gdk_gc_set_foreground (gc, color + 1);
gdk_draw_rectangle (widget-> window, gc, TRUE, event-> area.x, event-> area.y,
event-> area.width, event-> area.height);
/ * 전경 * /
gdk_gc_set_foreground (gc, color);
gdk_gc_set_background (gc, color + 1);
gdk_draw_layout (widget-> window, gc, 0, 0, layout);
gdk_gc_unref (gc);
g_free (str);
pango_attr_list_unref (attrs);
g_object_unref (layout);
return TRUE;
}
/ ************************************************* *****************************
* *
* *
* *
************************************************** **************************** /
int
main (int argc, char * argv [])
{
GtkIMContext * im_context;
GtkWidget * preedit * drawing * window;
/ * 초기화 * /
setlocale (LC_ALL, "");
gtk_set_locale ();
gtk_init (& argc, & argv);
im_context = gtk_im_multicontext_new ();
preedit = gtk_window_new (GTK_WINDOW_POPUP);
drawing = gtk_drawing_area_new ();
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/ * preedit 윈도우 * /
gtk_window_move (GTK_WINDOW (preedit), 0, 0);
gtk_container_add (GTK_CONTAINER (preedit) drawing);
g_object_set_data (G_OBJECT (preedit) "user_data"window);
/ * DrawingArea * /
g_signal_connect (G_OBJECT (drawing) "expose-event"
G_CALLBACK (signal_expose) im_context);
gtk_widget_show (drawing);
/ * InputMethod * /
g_signal_connect (im_context "commit"G_CALLBACK (signal_commit), NULL);
g_signal_connect (im_context "preedit_changed"
G_CALLBACK (signal_preedit_changed) drawing);
/ * 윈도우 * /
g_signal_connect (G_OBJECT (window) "destroy"
G_CALLBACK (gtk_main_quit), NULL);
g_signal_connect (G_OBJECT (window) "key-press-event"
G_CALLBACK (signal_key_press) im_context);
g_signal_connect (G_OBJECT (window) "key-release-event"
G_CALLBACK (signal_key_release) im_context);
g_signal_connect (G_OBJECT (window) "realize"
G_CALLBACK (signal_realize) im_context);
g_signal_connect (G_OBJECT (window) "unrealize"
G_CALLBACK (signal_unrealize) im_context);
g_signal_connect (G_OBJECT (window) "focus-in-event"
G_CALLBACK (signal_focus_in) im_context);
g_signal_connect (G_OBJECT (window) "focus-out-event"
G_CALLBACK (signal_focus_out) im_context);
/ * 표시 * /
gtk_widget_show_all (window);
gtk_window_set_policy (GTK_WINDOW (window), TRUE, TRUE, TRUE);
gtk_main ();
g_object_unref (im_context);
return 0;
}
-------------------------------------------------- -----------------------------
preedit 문자열이 있다면 팝업창을 열고 거기에 preedit 보기를 한다.
문자열이 없어지면 preedit을 숨긴다.
preedit_start과 preedit_end에서 gtk_widget_show 또는 gtk_widget_hide를 호출하는 것이 좋아 보이지만,
preedit_start과 preedit_end는 preedit 문자열의 유무에 따라 발생하는 것이 아니다
(immodule 따라서는 문자열의 유무로 발생하기도 한다).
imxim의 경우 Shitf + Space로 변환이 활성화 / 비활성화 될 때 preedit_start / preedit_end가 발생한다.
팝업 창은 preedit 문자열에 따라 필요한 최소의 크기로 한다.
gtk_im_context_get_preedit_string에서 Pango 문자의 속성을 취득할 수 있으므로, 이것도 이용한다.
§gtk_im_context_set_cursor_location
immodule 따라 변환 후보와 입력 모드를 표시할 수 있다.
예를 들어 일본어를 입력한다면 영숫자(키에 해당하는 문자를 그대로 입력)와
한자 변환 같은 입력 모드가있을 수 있다. 이 입력 모드는 키와 메뉴 등으로 전환한다
(immodule에 따라) 현재의 입력 모드를 표시하여 사용자에게 알려 주어야 한다.
또한 일본어 입력은 예를 들어 로마자로 "iwamoto"를 입력하면 preedit는 "이
와모토"가 되어, 이것의 변환을 시도하고 변환 후보로 "iwamoto ','이와 모토"
"이와 모토 ','이와 모토 ','이와 모토 '등이 있을 수 있다. 그 중에서 사용자가 선택한다
(선택하면 preedit 문자열은 없다. commit이 발생한다.).
immodule은 사용자가 선택할 변환 후보를 보여주어야 한다.
보여 줄 위치를 결정하기 위해 immodule은 커서의 위치를 필요로 한다.
커서란 마우스 커서가 아니라, 문자를 입력할 위치이다.
immodule 위의 변환 후보와 입력 모드를 표시하기 위해 커서의 좌표가 필요하다.
이 좌표를 전달하면 커서 근처에 immodule 변환 후보와 입력 모드를 표시하는 팝업 창을 표시 할 수 있다.
변환 후보와 상태가 커서 가까이 있으면 사용자는 관점을 이동하지 않아도 된다.
(특히 변환 후보가 커서에서 멀리 떨어져 표시되면 불편하다. )
반대로 immodule이 팝업창으로 커서 위치를 덮어 버리면 사용자는 매우 곤란하다.
팝업 창이 커서를 가리지 않도록 커서 위치를 immodule에게 알려 주어야 한다.
위의 변환 후보와 입력 모드라는 것은 일례에 지나지 않는다.
immodule에따라 변환 후보와 입력 모드 이외의 아무 것도 볼 수 없는 경우도 있다.
변환 후보와 입력 모드를 커서와 관계없는 위치에 표시 할 수 있다.
커서 위치는 immodule 클라이언트 창(gtk_im_context_set_client_window에서 지정한 창)의
좌상으로부터의 상대 좌표이다.
x, y 좌표는 커서 위치의 왼쪽 좌표, 폭 (width)은 0, height는 높이가 된다.
일반적으로 클라이언트 윈도우의 왼쪽에서 오른쪽, y에서 y + height 범위와 겹치지
않도록 팝업 창이 나타난다.
변환 후보가 팝업 윈도우의 왼쪽 좌표 (x, y + height)에 나타날 것으로 예상한다.
커서 아래에 충분한 영역이 없으면 변환 후보의 팝업 창의 왼쪽 하단 좌표가 (x, y-1)가 될 수도 있다.
(GTK + -2.2의 imxim는 기대를 배신하며 대부분의 immodule 팝업 창을 표시하지 않는다.
그러나 미래와 미지의 immodule을 위해서도 응용 프로그램은
gtk_im_context_set_cursor_location를 구현해야 한다. )
im0203.c는 커서(표시되지 않음)를 이동하고 동시에
gtk_im_context_set_cursor_location를 부른다. 또한 PageUp / PageDown 높이를 변경한다.
동시에 Shift 키를 누를 때마다 10 픽셀 씩 변경한다.
im0203.c ----------------------------------------------- ------------------------
/ ************************************************* *****************************
* *
* 키 입력 이벤트 *
* *
************************************************** **************************** /
static gboolean
signal_key_press (GtkWidget * widget, GdkEventKey * event,
GtkIMContext * im_context)
{
gint n;
GdkRectangle * area;
if (gtk_im_context_filter_keypress (im_context, event))
return TRUE;
area = g_object_get_data (G_OBJECT (widget) "user_data");
n = (event-> state & GDK_SHIFT_MASK) == 0? 1 : 10;
switch (event-> keyval)
{
case GDK_Home : area-> x = area-> y = 0; area-> height = 10; break;
case GDK_Left : area-> x - = n; break;
case GDK_Right : area-> x + = n; break;
case GDK_Up : area-> y - = n; break;
case GDK_Down : area-> y + = n; break;
case GDK_Page_Up : area-> height + = n; break;
case GDK_Page_Down : area-> height = MAX (area-> height - n, 1); break;
default :
g_print ( "key_press : keyval = % 02X \ n", event-> keyval);
return TRUE;
}
gtk_im_context_set_cursor_location (im_context, area);
g_print ( "key_press : keyval = % 02X, cursor = % d % d % d \ n",
event-> keyval, area-> x, area-> y, area-> height);
return TRUE;
}
static gboolean
signal_key_release (GtkWidget * widget, GdkEventKey * event,
GtkIMContext * im_context)
{
if (gtk_im_context_filter_keypress (im_context, event))
return TRUE;
g_print ( "key_release : keyval = % 02X \ n", event-> keyval);
return TRUE;
}
/ ************************************************* *****************************
* *
* GtkIMContext *
* *
************************************************** **************************** /
static void
signal_commit (GtkIMContext * im_context, gchar * arg1, gpointer user_data)
{
g_print ( "commit : % d % s \ n", strlen (arg1) arg1);
}
static void
signal_preedit_changed (GtkIMContext * im_context, GtkWidget * drawing)
{
int width, height;
gchar * str;
gint x, y;
PangoAttrList * attrs;
PangoLayout * layout;
GtkWidget * preedit * window;
GdkRectangle rc * area;
preedit = gtk_widget_get_parent (drawing);
window = g_object_get_data (G_OBJECT (preedit) "user_data");
area = g_object_get_data (G_OBJECT (window) "user_data");
gtk_im_context_get_preedit_string (im_context & str & attrs, NULL);
if (strlen (str)> 0)
{
layout = gtk_widget_create_pango_layout (window, str);
pango_layout_set_attributes (layout, attrs);
pango_layout_get_pixel_size (layout & width & height);
g_object_unref (layout);
gdk_window_get_origin (window-> window & x & y);
gtk_window_move (GTK_WINDOW (preedit) x + area-> x, y + area-> y);
gtk_window_resize (GTK_WINDOW (preedit), width, height);
gtk_widget_show (preedit);
gtk_widget_queue_draw_area (preedit, 0, 0, width, height);
}
else
{
gtk_widget_hide (preedit);
}
g_free (str);
pango_attr_list_unref (attrs);
}
/ ************************************************* *****************************
* *
* *
* *
************************************************** **************************** /
int
main (int argc, char * argv [])
{
GdkRectangle area;
GtkIMContext * im_context;
GtkWidget * preedit * drawing * window;
/ * 초기화 * /
setlocale (LC_ALL, "");
gtk_set_locale ();
gtk_init (& argc, & argv);
/ * 커서 위치 * /
area.x = area.y = area.width = 0;
area.height = 10;
im_context = gtk_im_multicontext_new ();
preedit = gtk_window_new (GTK_WINDOW_POPUP);
drawing = gtk_drawing_area_new ();
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/ * preedit 윈도우 * /
gtk_window_move (GTK_WINDOW (preedit), 0, 0);
gtk_container_add (GTK_CONTAINER (preedit) drawing);
g_object_set_data (G_OBJECT (preedit) "user_data"window);
/ * DrawingArea * /
g_signal_connect (G_OBJECT (drawing) "expose-event"
G_CALLBACK (signal_expose) im_context);
gtk_widget_show (drawing);
/ * InputMethod * /
gtk_im_context_set_cursor_location (im_context & area);
g_signal_connect (im_context "commit"G_CALLBACK (signal_commit), NULL);
g_signal_connect (im_context "preedit_changed"
G_CALLBACK (signal_preedit_changed) drawing);
/ * 윈도우 * /
g_object_set_data (G_OBJECT (window) "user_data", & area);
g_signal_connect (G_OBJECT (window) "destroy"
G_CALLBACK (gtk_main_quit), NULL);
g_signal_connect (G_OBJECT (window) "key-press-event"
G_CALLBACK (signal_key_press) im_context);
g_signal_connect (G_OBJECT (window) "key-release-event"
G_CALLBACK (signal_key_release) im_context);
g_signal_connect (G_OBJECT (window) "realize"
G_CALLBACK (signal_realize) im_context);
g_signal_connect (G_OBJECT (window) "unrealize"
G_CALLBACK (signal_unrealize) im_context);
g_signal_connect (G_OBJECT (window) "focus-in-event"
G_CALLBACK (signal_focus_in) im_context);
g_signal_connect (G_OBJECT (window) "focus-out-event"
G_CALLBACK (signal_focus_out) im_context);
/ * 표시 * /
gtk_widget_show_all (window);
gtk_window_set_policy (GTK_WINDOW (window), TRUE, TRUE, TRUE);
gtk_main ();
g_object_unref (im_context);
return 0;
}
-------------------------------------------------- -----------------------------
§gtk_im_context_set_use_preedit
결론부터 말하면 현재 gtk_im_context_set_use_preedit는 사용할 수 없다.
이 API로 use_preedit를 FALSE로하면 "preedit보기"에서 설명한 preedit
표시를 immodule 측이 대신해 준다. preedit의 표시는 use_preedit가 TRUE라면
어플리케이션 측이 책임지고, FALSE이면 immodule 측이 책임진다.
기본적으로 use_preedit은 TRUE이다. use_preedit를 FALSE로하면
immodule이 커서 위치에 필요에 따라 팝업 창을 만들고 preedit 문자열을 보여주고 대기할 수 있다.
이 점을 이용하면 응용 프로그램의 작성이 쉽다.
그러나 이 기능은 GTK + -2.2에서는 사용할 수 없다.
use_preedit가 FALSE 상태인 immodule은 별로 없다.
대부분의 immodule에 구현되어 있지 않다.
use_preedit을 FALSE로 한 경우 preedit에 문자열이 있어도 preedit는 표시되지 않고
use_preedit가 TRUE의 경우와 같은 동작을 한다.
만일 구현했다 하더라도 immodule 측의 preedit의 표시에 기대할 수 없다.
예를 들어 imxim이 갖춘 기능은 "preedit을 표시"하는 정도의 품질이다.
imxim을 평소 이용하지 않고, 가끔 imxim으로 입력한다면 견딜 수 있을지
모르지만, 상용할 만큼의 품질이 따르지 않는다.
immodule 측의 대응도 진행되고 있지만, 먼 미래의 일이므로 응용
프로그램 측에서 preedit를 표시하는 것이 좋다.
제 3 장 immodule 전환
§ 팝업 메뉴 만들기
immodule의 전환은 팝업 메뉴에 의해 실행된다.
gtk_im_multicontext_append_menuitems라는 API가있어, 이것을 호출하면
GtkMenuShell에 GtkMenuItem를 추가한다.
현재 선택되어있는 immodule에 메뉴가 붙어 있다.
이 GtkMenuItem이 선택되면 immodule이 교체된다.
gtk_im_multicontext_append_menuitems를 호출하는 위치에 주의하라.
너무 일찍 호출하면 선택된 메뉴가 올바르게 표시되지 않는다.
im0301.c ----------------------------------------------- ------------------------
/ ************************************************* *****************************
* *
* 키 / 마우스 입력 이벤트 *
* *
************************************************** **************************** /
static gboolean
signal_key_press (GtkWidget * widget, GdkEventKey * event,
GtkIMContext * im_context)
{
if (gtk_im_context_filter_keypress (im_context, event))
return TRUE;
g_print ( "key_press : keyval = % 02X \ n", event-> keyval);
return TRUE;
}
static gboolean
signal_key_release (GtkWidget * widget, GdkEventKey * event,
GtkIMContext * im_context)
{
if (gtk_im_context_filter_keypress (im_context, event))
return TRUE;
g_print ( "key_release : keyval = % 02X \ n", event-> keyval);
return TRUE;
}
static gboolean
signal_button_press (GtkWidget * widget,
GdkEventButton * event, GtkWidget * menu_popup)
{
if (event-> type == GDK_BUTTON_PRESS && event-> button == 3)
{
/ * 오른쪽 클릭 * /
gtk_menu_popup (GTK_MENU (menu_popup)
NULL, NULL, NULL, NULL, event-> button, event-> time);
return TRUE;
}
return FALSE;
}
/ ************************************************* *****************************
* *
* *
* *
************************************************** **************************** /
int
main (int argc, char * argv [])
{
GtkIMContext * im_context;
GtkWidget * preedit * drawing * window * menu_popup;
/ * 초기화 * /
setlocale (LC_ALL, "");
gtk_set_locale ();
gtk_init (& argc, & argv);
im_context = gtk_im_multicontext_new ();
preedit = gtk_window_new (GTK_WINDOW_POPUP);
drawing = gtk_drawing_area_new ();
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
menu_popup = gtk_menu_new ();
/ * preedit 윈도우 * /
gtk_window_move (GTK_WINDOW (preedit), 0, 0);
gtk_container_add (GTK_CONTAINER (preedit) drawing);
g_object_set_data (G_OBJECT (preedit) "user_data"window);
/ * DrawingArea * /
g_signal_connect (G_OBJECT (drawing) "expose-event"
G_CALLBACK (signal_expose) im_context);
gtk_widget_show (drawing);
/ * InputMethod * /
g_signal_connect (im_context "commit"G_CALLBACK (signal_commit), NULL);
g_signal_connect (im_context "preedit_changed"
G_CALLBACK (signal_preedit_changed) drawing);
/ * 윈도우 * /
g_signal_connect (G_OBJECT (window) "destroy"
G_CALLBACK (gtk_main_quit), NULL);
g_signal_connect (G_OBJECT (window) "key-press-event"
G_CALLBACK (signal_key_press) im_context);
g_signal_connect (G_OBJECT (window) "key-release-event"
G_CALLBACK (signal_key_release) im_context);
g_signal_connect (G_OBJECT (window) "realize"
G_CALLBACK (signal_realize) im_context);
g_signal_connect (G_OBJECT (window) "unrealize"
G_CALLBACK (signal_unrealize) im_context);
g_signal_connect (G_OBJECT (window) "focus-in-event"
G_CALLBACK (signal_focus_in) im_context);
g_signal_connect (G_OBJECT (window) "focus-out-event"
G_CALLBACK (signal_focus_out) im_context);
g_signal_connect (G_OBJECT (window) "button-press-event"
G_CALLBACK (signal_button_press) menu_popup);
gtk_widget_add_events (window, GDK_BUTTON_PRESS_MASK);
/ * 표시 * /
gtk_widget_show_all (window);
gtk_window_set_policy (GTK_WINDOW (window), TRUE, TRUE, TRUE);
/ * 메뉴 * /
gtk_im_multicontext_append_menuitems (GTK_IM_MULTICONTEXT (im_context)
GTK_MENU_SHELL (menu_popup));
gtk_main ();
gtk_widget_destroy (menu_popup);
g_object_unref (im_context);
return 0;
}
-------------------------------------------------- -----------------------------
immodule의 전환은 해당 프로세스에서만 사용한다. gtk_im_multicontext_new
로 여러 실체를 만든 경우, 다른 실체도 변경되지만 다른 프로세스에는 영향을 주지 않는다.
또한 프로세스의 종료까지 변경은 유효하다.
지금 현재 어떤 immodule이 선택되어 있는지 알 방법은 팝업 메뉴 뿐이다.
immodule을 전환하는 방법도 팝업 메뉴 뿐이다.
제 4 장 기타
§gtk_im_context_reset
이 API를 호출하면 immodule를 초기화 할 수 있다.
구체적으로 어떻게 동작할지는 immodule에 따라 다르다.
일반적으로 preedit에 있는 문자열이 모두 삭제된다.
현재 입력 모드는 변하지 않는다.
예를 들어 preedit가 "이와 모토"에서 입력 모드가 히라카나면
gtk_im_context_reset 의해 preedit의 "이와모토"는 없앨 수 있겠지만
입력 모드는 카타카나 그대로이다.
일반적으로 이 API는 커서가 이동했을 때와 상황이 변할 때 등에 호출한다.
그것은 응용 프로그램이 무엇을 하는지에 달려 있다.
GtkEntry에서 예를 들어 마우스 왼쪽 버튼을 누르면 이 API를 부른다.
응용 프로그램은 필요하다면 이것을 호출하지 않아도 된다.
§retrieve-surrounding
현재 retrieve-surrounding 신호를 발생시키는 immodule는 없다. 구체적인
소스 코드도 없고 사양도 불분명하다. 여기에 쓴 것은 GTK + -2.2에서 필자가 추측한 것이다.
현재 GTK + 2.2 API Reference에는 태국어를 예로 든다. 태국어라면 입력을위해
커서 주변의 문자열이 필요할지도 모르겠다. (필자는 태국어를 모른다)
retrieve-surrounding 신호는 immodule이 커서 주변의 문자가 필요할 때 발생한다.
이 신호가 발생하면 응용 프로그램은 gtk_im_context_set_surrounding를 호출한다.
immodule 따라 입력 문자를 결정하기 위해 문자가 삽입되는 위치,
앞뒤의 문자가 필요한 경우가 있을 수 있다.
또한 주위의 문자를 알게 되면 변환 후보를 더 잘 선택할 수 있을 것이다.
예를 들어, 일본어로는 "tensai"를 입력하고 그것을 변환하는 경우, "천재" "하늘
오 ','천재 ','천재 ','권리 '등이 있을 수 있다. 이때 커서 주위에 '이와 모토 카즈키 "라는 문자열이 있다면,
"천재 "를 첫번째 변환 후보로 보여줄 수 있다.
물론 주위의 문자열을 얻을 수 없어도 변환 후보의 결정은 가능하다.
그 경우 immodule은 다른 기준에 따라 변환 후보를 선택한다
(예를 들어 과거의 변환 후보의 선택 횟수 라든지 등).
GtkEntry의 구현은 아래와 같이 되어 있다.
-------------------------------------------------- -----------------------------
static gboolean
gtk_entry_retrieve_surrounding_cb (GtkIMContext * context,
GtkEntry * entry)
{
gtk_im_context_set_surrounding (context,
entry-> text,
entry-> n_bytes,
g_utf8_offset_to_pointer (entry-> text, entry-> current_pos) - entry-> text);
return TRUE;
}
-------------------------------------------------- -----------------------------
gtk_im_context_set_surrounding 인자는 바이트 단위이다. 예를 들어 "한글"
문자열에서 "한"과 "글"사이에 커서가 있다면 다음과 같다
gtk_im_context_set_surrounding (context, "한글", 6, 4);
§delete-surrounding
현재 delete-surrounding 신호를 발생시키는 immodule는 없다. 구체적인 소스
코드도없고 사양도 불분명하다. 여기에 쓴 것은 GTK + 구현에서 필자가 나름대로 추측한 것이다.
delete-surrounding 신호는 커서 주위의 문자를 제거할 필요가 있을 때 발생한다.
현재 GTK + 2.2 API Reference에 이 신호는 다음과 같이 적혀 있다.
gboolean user_function (GtkIMContext * imcontext,
gint arg1,
gint arg2,
gpointer user_data);
더 적절하게 쓴다면 다음과 같이 된다.
gboolean user_function (GtkIMContext * imcontext,
gint offset,
gint n_chars,
gpointer user_data);
offset과 n_chars는 바이트 단위가 아닌 문자 단위이다.
이것을 억지로 한국어에 적용한다면, 예를 들어 이미 "한글"이라는 문자열에서
커서가 "한"와 "글"사이에 있을 때 "゛"가 입력 된다면 offset은 -1, n_chars은 1이다.
그리고 commit 신호로 "스"가 입력된다.
(immodule는 "한글"이라는 문자열이 있는지와 커서의 위치는
retrieve-surrounding 신호로 알고 있다)
이것을 억지로 알파벳에 적응한다면, 이미 'A'라는 문자가있을 때
"^"가 입력 된다면 'A'가 삭제되고 대신 'A'에 '^'가 붙은 문자가
commit된다.
GtkEntry의 구현은 아래와 같이 되어 있다.
-------------------------------------------------- -----------------------------
static gboolean
gtk_entry_delete_surrounding_cb (GtkIMContext * slave,
gint offset,
gint n_chars,
GtkEntry * entry)
{
gtk_editable_delete_text (GTK_EDITABLE (entry)
entry-> current_pos + offset,
entry-> current_pos + offset + n_chars);
return TRUE;
}
-------------------------------------------------- -----------------------------
제 5 장 부록
§GtkIMContext의 요점
아래는 필자가 이전에 메일링 리스트에 투고한 것을 편집한 글이다.
우선 아래 기록을 읽으면 GtkIMContext의 요점은 파악할 수 있을 것이다.
보통 GTK + 응용 프로그램 수준에서 GtkIMContext를 직접 취급할 것은 아니라고 생각된다.
하지만 응용 프로그램 수준에서 볼 수 있는 한계는 GtkIMContext이다.
그 아래 일부 immodule이 있고 immodule은 자체에서 끝날 수도 있고 XIM을 부를 수도 있다.
immodule은 전환이 가능하다. XIM 수준에서 IM의 전환은 없지만 (필자가 모를 수도 있다 ...)
Canna를 위한 immodule과 FreeWnn을 위한 immodule이 있고, Canna과 FreeWnn를 전환할 수 있다.
마찬가지로 cWnn 라든지 kWnn, ATOK 등에 해당하는 immodule을 만들면, 그 역시도 전환할 수 있다.
immodule이 완전히 정비되어 있는 것은 아니고 개척의 여지가 있다.
괜찮은 문서도 없고, 소스를 뜯어보면서 앞으로 스스로 연구해야 한다.
gtk_im_multicontext_append_menuitems에서 immodule 전환을위한 GtkMenuItem을
GtkMenuShell 생성할 수 있다.
응용 프로그램은 key-press-event와 key-release-event 이벤트 처리를 위해
gtk_im_context_filter_keypress를 부를 필요가 있다. TRUE를 반환하면 키 입력은
GtkIMContext으로 처리된 것이다. FALSE이면 처리되지 않은 것이다.
알파벳 한 글자도 gtk_im_context_filter_keypress로 처리된다.
gtk_im_context_set_use_preedit를 TRUE (기본값)로 하면, 입력이 확정되지
없는 문자열은 응용 프로그램 측이 preedit-changed 신호에 따라 스스로 표시해야 한다. gtk_im_context_get_preedit_string으로 문자열을 얻을 수 있다.
FALSE이면 응용 프로그램 측 책임이 아니라 GtkIMContext 측에서 적당히 표시해 준다.
각 immodule이 처리해 주겠지만.
(그 품질은 그저 그렇기 때문에 차라리 TRUE로 설정하고 스스로 그리는 것이 좋다)
확정했다면, commit 신호가 발생한다. 일본어 입력뿐만 아니라 알파벳
문자도 commit 신호이다.
surrounding은 주위의 문자이다. 일본어는 아마 필요하지 않을 것이다.
그러나 강력한 InputMethod라면 주위의 문자에서 최적의 변환 후보를 선택하는데 이것을 이용한다.
또는 언어에 따라 주위의 문자가 현재 입력을 결정하는 데 필요할 수
있다. GTK + -2.2 참조 설명서에서는 태국어를 예로 든다.
예를 들어, "イワモトカスキ"라는 문자열이 "ス"와 "キ"사이에 캐럿이 있다면
gtk_im_context_set_surrounding (context "イワモトカスキ", -1, 18); 이다.
주위의 문자가 필요하다면 retrieve-surrounding 신호가 발생한다.
delete-surrounding 신호, 예를 들어 이미 "イワモトカスキ"라는 단어가 있고,
커서가 "ス"와 "キ"사이에 "゛"을 입력했다면, arg1은 -1, arg2는 1이 될수 있다.
그런 다음 commit 신호로 "ス"가 확정된다.
억지로 일본어로 해석하면 이런 것이라고 생각한다. 태국어는 또 다른 처리가 필요하다고 생각한다
(과연 그런가?). 안타깝게도, 필자는 태국어를 모르므로 무엇이라고 단정할 수 없다.
또한, 위의 필자의 GtkIMContext에 대한 인식에 잘못이 있을 수 있다.
§GtkIMContext의 문제점
immodule을 전환하는데 gtk_im_multicontext_append_menuitems에서 메뉴를 확인하는
방법이 없는 것은 안타까운 일이다. Windows의 Alt + Shift처럼 키로 전환하거나
다른 응용 프로그램에 적응 수단을 구현할 수 있는 여지가 있으면 좋겠다.
게다가 immodule의 선택은 특정 GtkIMContext의 실체에만 유효할 뿐만 아니라
응용 프로그램에 따라 다르다. 그런데도 GtkIMContext의 실체에서 메뉴를 생성하는 사양은 이상하다.
이것은 GtkIMContext에 국한하면 문제가 없지만 문자열의 위치 값을 지정할
경우 단위로 문자와 바이트가 혼합되어 있다. 1 문자가 항상 1 바이트라면 문제가 없겠지만.
사실, 특히 GtkIMContext을 필요로 하는 환경에서는 한 문자가 1 바이트인 경우는 없다.
일관성 없는 API의 사양 때문에 프로그래머가 많은 부담을 진다.
현재 preedit보기는 응용 프로그램이 수행할지 immodule이 수행할지 선택의 여지가 없다.
실제로 preedit보기는 누구든지 결국 필자가 제 2 장에서 설명한 샘플과 같은 모양이 될 것이다.
그래서 이것은 GTK + 측에서 구현해 주면, 서로 부담을 덜 수 있다. 또한 일관된 사용자 인터페이스
스를 제공 할 수 있다. (물론, 거기에 의지하지 않고 응용 프로그램이나 immodule이 독자적으로
대응하는 것도 좋은 방법이다)
API도 여러가지로 부족하다. 예를 들어 현재의 변환 후보 목록을 얻을 API와 현재 상태
(로마자 카나 라든지 숫자 라든지)를 얻는 API가 있어도 좋을 것이다.