SUBJECT :국제화된 Motif 프로그램을 위한 기초 안내서



\Copyright(1992, 한국과학기술원 GUI 컨소시엄 프로젝트)
\Copyright  (1993, 현대전자 산업 주식회사)

시작하면서

이 안내서는 Motif 프로그래밍을 해본 경험이
있는 사람이 국제화된 Motif 프로그램을 시작하는데
도움을 주고자 쓰여졌다. 따라서 이 안내서는
Motif나 C 프로그램에 경험이 있는 사람들을
가정한다.

\section*{책의 내용}

Motif 1.2에서는 국제화된 응용프로그램이
작성될 수 있도록 상당한 변화가 있었다. 대부분의
변화는 텍스트 위짓이나 텍스트 필드
위짓에 있으며 Compound String 부분에서는
X11R5에서 제공하는 폰트집합을 지원하기 위한
변화가 있다. 또한 input method를
지원하기 위한 툴킷 레이어의 라이브러리가
지원된다.  (국제화와 관련하여 Motif 1.2에서의
자세한 정보를 원하는 분은 Implementer's
Guide를 참조하기 바란다.)

그러나 실제로 이러한 변화는 응용프로그램의
입장에서는 크게 신경쓰지 않아도 된다. 즉,
기존의 Motif 1.1에 기반을 둔 응용프로그램이
큰 변화없이 Motif 1.2 환경으로 옮아갈 수
있다는 것을 의미한다.
이 안내서에서는 응용프로그래머가
Motif 1.2에서 어느 특정 언어를 구현하고자
할 때 알아야 하는 내용을 몇장에 나누어서 다루고 있다.

이 안내서의 내용은 다음과 같이 꾸며져 있다.

2 장  Motif의 국제화에 대한 설명을 하고 있다.
3 장  로케일의 지정에 관하여 설명한다.
4 장  Compound String을 사용하는 방법에 대해서 설명한다.
5 장  텍스트 위짓에 대해서 설명하고 있다.
6 장  하나의 완전한 프로그램의 예를 보인다.

\chapter 2 {Motif의 국제화}

\section{국제화와 지역화의 필요성}

컴퓨터가 세계 여러 나라에 보급됨에 따라,
프로그램을 만드는 사람들은 자신의
프로그램에 다른나라의 언어를 지원하기
위하여 한국용, 일본용, 독일용 등 여러개의
버젼을 만들어 왔다. 그러나 이 작업은
같은 프로그램일지라도 다른 언어를
지원하기 위하여 소스코드를 변경해야 되기
때문에 각각의 버젼들이 호환성이 없을
뿐 아니라 소프트웨어의 개발에 이중
삼중의 노력이 들게 된다.

국제화는 프로그램을 어느 특정 언어나
지역적 특수성에 국한되지 않도록 만드는 것이다.
즉, 각각의 언어에서 필요한 요소를
소스의 변경없이 지원할 수 있도록 하는
구조를 만드는 것을 말한다. 지역화는 이렇게
국제화된 프로그램에 실제로 특정
언어나 지역적 특수성을 지원할 수 있도록
데이타베이스나 서비스를 제공해준다.

\section{국제화된 Motif의 구조}

텍스트 입력은 XmIm 라이브러리를
통하여 X11R5에서 제공하는 기능을 이용하여
받아들이게 되고 보완된 Compound
String 기능을 이용하여 주로 메뉴의 라벨이나
메세지등에서 다른언어를 지원할 수
있도록 한다.


\chapter 3 {로케일의 지정}

\section{setlocale}

국제화된 응용프로그램은 로케일을 정의하는
것부터 시작하게 된다. setlocale()은
POSIX에 정의된 로케일 정의 함수이다.
형식은 다음과 같다.

    #include 

    char *setlocale(category, locale)
        int category;
        char *locale;

    category  로케일을 정할 범주를 말한다.
              LC_CTYPE, LC_COLLATE, LC_CTIME,
              LC_NUMERIC, LC_MONETARY, LC_MESSAGES
                같은 여러 범주가 있다.
    locale    로케일을 지정하는 문자열이다.
              만약 ``''을 지정하면 category와
                같은 이름의 환경변수에서 값을 가져온다.
                그 이름의 환경변수가
                없으면 LANG이라는 환경변수에서 그 값을
                가져온다.

실제로 프로그램에서는 다음과 같이 쓰는 경우가 많다.

    setlocale(LC_CTYPE, "");


여기서 LC_CTYPE은 문자의 변환과 분류에
영향을 주는 로케일 범주로 국제화된
Motif는 주로 이 로케일 범주의 영향을
받는다. 값으로 ``''을 지정하였으므로 환경변수
LC_CTYPE에서 값을 가져온다. 만약 LC_CTYPE이
정의되지 않았으면 환경변수 LANG에서
그 값을 가져온다.

\chapter 4 {Compound String의 사용}

국제화된 응용프로그램을 만들 경우 보통 두개 이상의
문자집합을 만들어 사용하게 된다
(예를 들면, 한글과 영어).
Motif 1.2에서는 compound string의 하나의
세그먼트가 여러개의 문자집합으로
되어있는 텍스트를 지원한다. 즉, compound
string의 하나의 세그먼트는
자신과 같은 charset의 tag를 지닌 폰트
집합으로 그려진다.
예를 들어 "현대전자 산업 주식회사 S/W R & D 
Center"라는 문자열을 라벨 위짓의
라벨 스트링으로 사용하고자 할 경우,
먼저 문자열을 compound string으로 변환하고,
이를 그릴 때 사용하는 폰트 리스트를
만드는 두 단계의 작업을 통해서 이루어진다.

\section{Compound String의 생성}

텍스트 위짓을 제외하고 위짓에 텍스트
데이타를 그리는 위짓들은 (예를 들면,
라벨 위짓) XmString이라고 하는 데이타
타입을 필요로 한다.  따라서 사용자는
일반적인 스트링을 먼저 XmString으로
변환시켜줘야 한다.   일반적인 C 스트링을
XmString으로 변환하기 위해서 보통
XmStringCreate()이라는
함수를 사용하는데 그 형식은 다음과 같다.

    #include 

    XmString XmStringCreate(text, charset)
        char *text;
        XmStringCharSet charset;

실제 프로그램의 예를 들면 다음과 같다.

    XmString xmString;

    xmString = XmStringCreate("현대전자 S/W 연구소 중대형 개발실'',
                    ``koreanEUC'');
    .....
    XmStringFree(xmString);

이렇게 하여 만들어진 스트링은 리소스
XmNlabelString의 값으로 지정하게된다.
여기서 만들어지는 세그먼트의 charset은
이 세그먼트를 실제로 그릴 때 사용하는
폰트 집합을 지정하는 폰트 리스트에서의 해당
폰트 집합의 charset과 같아야 한다.
즉, 다음 단계에서 생성되는 폰트
리스트에서 ``koreanEUC''라는 tag를
가진 폰트 집합에 의하여 위에서 만든
compound string이 그려진다.

\section{폰트 리스트의 생성}

실제로 위에서 만든 XmString의 세그먼트를
그리는데 사용되는 폰트 리스트를 만드는
과정이다.
먼저 폰트집합을 XCreateFontSet()을 이용하여
만들고 이를 이용하여 하나의
폰트 리스트 엔츠리를 만든다. 이때 사용하는
함수는 XmFontListEntryCreate()인데
그 형식은 다음과 같다.

    #include 

    XmFontListEntry XmFontListEntryCreate(tag, type, font)
        char *tag;
        XmFontType type;
        XtPointer font;

여기서 tag는 실제로 이 폰트집합을
써서 그리고자하는 텍스트 세그먼트의
tag와 같아야 한다.  이렇게 만들어진
폰트 리스트 엔츠리를 위짓이 사용하는
폰트 리스트에 넣는 과정이 필요한데
이는 XmFontListAppendEntry()라는
함수를 통하여 이루어진다.

    #include 

    XmFontList XmFontListAppendEntry(list, entry)
        XmFontList list;
        XmFontListEntry entry;

이렇게 하여 만들어진 폰트 리스트는 위짓의
리소스 XmNfontList에 지정하게 된다.
이러한 과정의 실제 프로그램의 예를 들면
다음과 같다.

#define FONTSET
"*-clean-bold-r-*--16-*,*-myeongjo-bold-r-*--16-*-ksc5601.1987-1"

    XFontSet fontSet;
    int missing_charset_count;
    char **missing_charset_list;
    char *def_string;
    XmFontListEntry flEntry;
    XmFontList fontList;

    fontSet = XCreateFontSet(display, FONTSET, &missing_charset_list,
            &missing_charset_count, &def_string);
    flEntry = XmFontListEntryCreate("koreanEUC", XmFONT_IS_FONTSET, fontSet);
    fontList = XmFontListAppendEntry(fontList, flEntry);

    n = 0;
    XtSetArg(args[n], XmNfontList, fontList); n++;
    XtSetValues(widget, args, n);

\section{리소스 파일의 사용}

XmFONTLIST_DEFAULT_TAG라는 tag를 가지는
compound string은 실제로 응용프로그램이
소스안에 하드코딩을 하지 않더라도 리소스
파일에 지정할 수 있다. 마찬가지로
같은 tag를 가지는 폰트 리스트도 리소스 파일에
지정할 수 있다.
위에서 나타난 코드는 실제로 다음과 같이
리소스 파일에 표현하여도 같은 효과를
갖는다.

widget name.labelString:    현대전자 S/W 연구소 중대형 개발실
widget name.fontList:
*-clean-bold-r-*--16-*;*-myeongjo-bold-r-*--16-ksc5601.1987-1:

\chapter 5 {텍스트 위짓}

Motif1.2에서는 국제화된 텍스트 위짓을 제공한다.
이는 국제화된 텍스트 위짓이 실제로 한글이나
일본어 등을 바꾸지 않고도 응용을 지원할
수 있다는 것을 의미한다. 텍스트 위짓의
동작은 사용자가 환경변수를 정하면 그에 따라
동작하기 때문에 응용프로그램은 기존의
API를 그대로 사용하여 한글이 입력되는 것을 지원할 수 있다.

만약 응용프로그램이 내부코드로서 wide character를 쓰고자 하면
새로 정의된 텍스트 위짓의
wide character public interface를 사용할 수 있다.
자세한 내용은 Reference Manual을 참조하기 바란다.

텍스트 위짓을 사용할때 한가지 알아야 될 것은
사용자나 응용프로그래머가 텍스트 위짓이 사용하는
input method나 input style을 지정할 수 있다는 것이다.
즉, 다음과 같은 두가지 리소스가 정의될 수 있다.

    XmNinputMethod
        Type:   String
        Default:    NULL
        Access: CSG

이 리소스는 input method의 로케일 변형자를 지정할 때 쓰인다.

    XmNpreeditType
        Type:   String
        Default:    "OnTheSpot,OverTheSpot,OffTheSpot,Root"
        Access: CSG

이 리소스는 입력 스타일을 지정할 때 쓰인다.

결국 사용자나 응용프로그래머가 OffTheSpot 방식으로 입력을 받고자 할 경우
아래와 같이 리소스 파일에 지정하면 된다.

    *preeditType: OffTheSpot

Motif의 Text[Field] Widget을 사용할 때 주의할 점은 만약 Application에서
여러개의 Text[Filed] Widget을 만들어 이곳 저곳에서 입력을 할 경우 
다른 Text[Field] Widget이나 완전히 다른 Widget으로 옮기기 전에 반드시 
입력상태를 영문으로 맞추어 놓아야 한다는 것이다.


\chapter 6 {완전한 예}

이 장에서는 그 동안 따로따로 설명했던
것들을 묶어서 완전히 실행되는
프로그램의 리스트와 그에 따른 리소스
파일을 보여줌으로써 독자 여러분의 이해를
돕고자 한다. 아래의 예에서는 앞에서
설명되지 않은 부분이 많은데
이는 기본적으로 Motif 1.1의 프로그램
스타일과 같기 때문에 여기서 추가로
설명하지 않는다.

/*
 * sample.c:
 *  Sample program
 */

#include 
#include 
#include 
#include 
#include 
#include 

#define FONTSET
"*-clean-bold-r-*--16-*,*-myeongjo-bold-r-*--16-*-ksc5601.1987-1"

void Quit(widget, client_data, call_data)
    Widget widget;
    XtPointer client_data;
    XtPointer call_data;
{   /* Quit */
    XtCloseDisplay(XtDisplay(widget));
    exit(0);
}   /* Quit */

Widget CreateQuitButton(parent, name, fontList)
    Widget parent;
    char *name;
    XmFontList fontList;
{   /* CreateQuitButton */
    Widget quit;
    XmString quitString;
    Arg args[10];
    int n;


    quitString = XmStringCreate(``끝내기'', ``koreanEUC'');
    n = 0;
    XtSetArg(args[n], XmNlabelString, quitString); n++;
    XtSetArg(args[n], XmNfontList, fontList); n++;
    quit = XtCreateManagedWidget(name, xmPushButtonWidgetClass, parent,
                    args, n);
    XtAddCallback(quit, XmNactivateCallback, Quit, NULL);
    XmStringFree(quitString);

    return(quit);
}   /* CreateQuitButton */

Widget CreateLabelWidget(parent, name, fontList)
    Widget parent;
    char *name;
    XmFontList fontList;
{   /* CreateLabelWidget */
    Widget label;
    XmString xmString;
    Arg args[10];
    int n;


    /* 라벨로 사용될 Compound String을 만든다. */\\
    xmString = XmStringCreate(``현대전자 S/W 연구소 '', ``koreanEUC'');
    n = 0;

    XtSetArg(args[n], XmNlabelString, xmString); n++;
    XtSetArg(args[n], XmNfontList, fontList); n++;
    label = XtCreateManagedWidget(name, xmLabelWidgetClass, parent, args, n);
    XmStringFree(xmString);
    return(label);
}   /* CreateLabelWidget */

Widget CreateTextFieldWidget(parent, name)
    Widget parent;
    char *name;
{   /* CreateTextFieldWidget */
    Widget textF;

    textF = XtCreateManagedWidget(name, xmTextFieldWidgetClass, parent, NULL,
0);
    return(textF);
}   /* CreateTextFieldWidget */

main(argc, argv)
    int argc;
    char *argv[];
{   /* main */
    Widget topLevel, rowColumn, quitB, label, textF;
    XtAppContext appCons;
    Arg args[10];
    int n;
    XFontSet fontSet;
    int missing_charset_count;
    char **missing_charset_list;
    char *def_string;
    XmFontList fontList;
    XmFontListEntry flEntry;


    /* 로케일의 지정 */
    setlocale(LC_CTYPE, "");

    topLevel = XtAppInitialize(&appCons, "Sample", NULL, 0, &argc, argv,
                    NULL, NULL, 0);         +
    n = 0;
    XtSetArg(args[n], XmNallowShellResize, True); n++;
    XtSetValues(topLevel, args, n);

    n = 0;
    XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
    rowColumn = XtCreateManagedWidget("rowColumn", xmRowColumnWidgetClass,
                    topLevel, args, n);


    /* 폰트 리스트를 만든다. */

    fontSet = XCreateFontSet(XtDisplay(topLevel), FONTSET,
&missing_charset_list,
            &missing_charset_count, &def_string);
    flEntry = XmFontListEntryCreate("koreanEUC", XmFONT_IS_FONTSET, fontSet);
    fontList = XmFontListAppendEntry((XmFontList) NULL, flEntry);

    quitB = CreateQuitButton(rowColumn, "quitB", fontList);
    label = CreateLabelWidget(rowColumn, "label", fontList);
    textF = CreateTextFieldWidget(rowColumn, "textF");

    XtRealizeWidget(topLevel);
    XtAppMainLoop(appCons);
}   /* main */


다음은 위의 프로그램을 실행시키기 위한 샘플 리소스 파일의 내용이다.

sample*XmTextField.fontList:
*-clean-bold-r-*--16-*;*-myeongjo-bold-r-*--16-*-ksc5601.1987-1





















     < X11R5/Motif-1.2 설치 요령 >

 1. X11R5와 Motif-1.2의 실행 화일들의 크기는 약 120MB 정도이므로 이 만큼의 Disk
Space를
 마련한다. (X-Terminal용 font인 Super-X를 지우면 약 80MB) 2. /usr file system에 이 정도의 disk space가 있을 경우에는 이 Package를 System Directory에 Install한다.  # cd / # tar xvfp /dev/rst0
 3. 다른 file system으로 install할 경우(space 가 있는 directory를 $TARGET 이라
면)
     # cd $TARGET
     # tar xvfp /dev/rst0
     # ls -F
          usr/
     # cd usr ;  ls -F
          bin/	include/	lib/	local/
 4. 위 3.의 경우에는 system directory로 symbolic link를 시켜준다.
     # ln -s $TARGET/usr/bin/X11   /usr/bin/X11
     # ln -s $TARGET/usr/include/Xm   /usr/include/Xm
     # ln -s $TARGET/usr/include/X11   /usr/include/X11
     # ln -s $TARGET/usr/include/Mrm   /usr/include/Mrm
     # ln -s $TARGET/usr/include/uil   /usr/include/uil
     # ln -s $TARGET/usr/lib/X11   /usr/lib/X11
 5. X11R5와 Motif에 관련된 라이브러리를 system directory로 move시킨다.
     # cp -r $TARGET/usr/local   /usr  (한자 사전)
     # mv $TARGET/usr/lib/lib*   /usr/lib
 6. move시킨 라이브러리를 정돈한다.
     # ranlib  /usr/lib/libX*.a  /usr/lib/libX*sa*  /usr/lib/liboldX*.a
     /usr/lib/libphigs.a
 7. 위와 같이 설치한 후에는 현대 전자로 hostid를 알려주어 이에 해당하는 serial
number를 받아
  file에 저장한다.
     # hostid
          xxxxxxxx
     위 값을  S/W 프라자로 알려주면 serial number를 곧바로 받는다.
     만약 serial number가 yyyyyy 라면
     # cd /usr/lib/X11
     # vi License.dat
          yyyyyy값을 입력한다. (다른 key가 들어가면 안된다)
      실제로 위 number는 hexa값으로 나타난다.
 8. install한 X11R5와  Motif가 잘 작동하도록 Rebooting한다.
     # /etc/reboot
 9. 이제부터 X11R5와 Motif-1.2를 사용할 수 있다.
     # xinit
10. 한글화된 응용 프로그램(에: hterm)을 실행 시키기 전에는 반드시 한글 입력
기(is)를
     실행시켜야 한다.
11. 한글 입력기의 Motif Version은 mis이다.
12. /usr/lib/X11/korean에 있는 system.mwmrc와 그 밑에 있는 app-defaults/Mwm을
사용자의 $HOME
     밑의 .mwmrc와  Mwm으로 각각 copy 하여 사용자의 기호에 따라 mwm을 바꾸어
     사용할 수 있다.




 <참고>
   Super-X terminal에서 위의 Product를 실행시키고자 할 때.

  우선 X-Terminal이 서버를 download받고 있는 host를 찾는다.
 그런후에, 그 host에서 Super-X라는 서버가 있는 PATH를 찾아서 Super-X아래에 있는
fonts로
 간다. 그리고, 거기에 있는 misc라는 directory를 misc.org로 move시켜 backup 받은
후, 위에서 받은
 X11R5/Motif-1.2 실행 화일중에 usr/lib/X11/fonts밑에 있는 Super-X.misc를
Super-X/fonts아래의
 misc로 rename시켜 copy하거나, symbolic link시킨다.

{국제화된 X 윈도우 시스템 프로그램을 위한 기초 안내서}

한국과학기술원 인공지능연구센터 GUI Consortium Project}
\Copyright{1992, GUI Consortium Project}
\Copyright(1993,  Hyundai Electronics Industries. Co., Ltd)

\chapter*{시작하면서}

이 책은 X 윈도우 프로그램을 해 보신 경험이 있는 분이 국제화된
X 윈도우 프로그램을 시작할 때 도움을 드리려고 쓰여졌습니다.
따라서 이 책을 읽으시는 분은 X 윈도우나 C 프로그램에 익숙해야
합니다.

\section*{책의 내용}

이 책의 내용은 다음과 같이 꾸며져 있습니다.
1 장  X 윈도우 시스템의 국제화에 관련하여 설명을 드립니다.
2 장  입력 서버와 연결하는 방법에 대하여 설명을 드립니다.
3 장  입력 서버로부터 입력을 받는 방법을 설명합니다.
4 장  입력 서버의 환경을 바꾸는 방법을 설명합니다.
5 장  출력하는 방법에 대하여 설명드립니다.

\chapter{X 윈도우의 국제화}

\section{국제화와 지역화의 필요성}

컴퓨터가 세계 여러 나라에 보급됨에 따라, 프로그램을 만드는 사람들은
한 프로그램을 한국용, 미국용, 일본용 등 각 지역에 맞게 여러 개를
만들게 되었습니다. 그러나 이 작업은 매우 어려운데 그 이유는 지구상에서
컴퓨터를 쓰는 나라가 수 십 개가 넘으므로 같은 프로그램을 수 십 번
고쳐야 하기 때문입니다. 그래서 나온 생각이 국제화와 지역화라는 개념입니다.

국제화란 프로그램을 어떤 특정한 지역에 국한되지 않도록 만드는
것을 말합니다. 지역화란 국제화된 프로그램을 각 지역에 맞게 만드는 작업을
말합니다. 이 때 국제화된 프로그램은 수정되지 않습니다. 예로 날짜를
표시하는 프로그램을 생각해 봅시다. 국제화되지 않은 프로그램에서는
날짜를 바로 어떤 지역에 맞는 형식으로 표시합니다. 그러나 국제화된
프로그램에서는 그 지역에서 날짜를 표시하는 형식에 대한 정보를 가져와서
그 형식에 맞게 출력합니다.

국제화된 X 윈도우 시스템에서는 응용프로그램은 지역에 관계없이 만들어지고
그 지역에 특별한 입력은 입력 서버가 출력은 국제화된 X 라이브러리가 하게
됩니다. 여기서 I18N은 Internationalization의 약자입니다. (I와 n사이에
글자가 18 개입니다.) 마찬가지로 L10N은 Localization의 약자입니다.

\chapter{XIM 과의 연결}

이 장에서는 응용프로그램이 제일 먼저 해야 할 일인 입력 서버와의 연결을
하는 법에 대해서 알아 보겠습니다. 간략하게 해야 할 작업을 나열하면
다음과 같습니다.

\item setlocale
\item XOpenDisplay
\item XOpenIM
\item XCreateWindow
\item XCreateFontSet
\item XCreateIC

이 중에서 XOpenDisplay 와 XCreateWindow 는 보통의 X 윈도우 프로그램에서도
하는 작업이므로 여기서는 설명하지 않겠습니다. 자세한 내용은 다른 책을
참조하십시요.

\section{setlocale}

국제화된 X 윈도우 시스템은 내부적으로 locale(로케일) 이라고 하는 정보를 가지고
있는데, 이 정보는 프로그램이 어떤 지역에 특별한 처리를 해야 할 경우---문자열을
입력받는다거나 출력하는 경우---에 사용됩니다. 따라서 모든 프로그램은
로케일을 정하는 것부터 시작해야 합니다. setlocale() 함수는 이
로케일을 결정하는 함수입니다. 형식은 다음과 같습니다.

#include 

char *setlocale(category, locale)
int category;
char *locale;

category  로케일을 정할 범주를 말합니다. LC_CTYPE, LC_COLLATE, LC_CTIME,
           LC_NUMERIC, LC_MONETARY, LC_MESSAGES 같은 여러 범주가
       있습니다.
locale  로케일을 지정하는 문자열입니다. 만약  "" 을 지정하면
       category와 같은 이름의 환경변수에서 값을 가져옵니다.
       그 이름의 환경변수가 없으면 LANG이라는 환경변수에서 값을
       가져옵니다.

실제로 프로그램에서는 다음과 같이 쓰는 경우가 많습니다.

     setlocale(LC_CTYPE, "");

여기서 LC_CTYPE 은 문자의 변환과 분류에 영향을 주는 로케일 범주로
국제화된 X 윈도우 시스템은 주로 이 로케일 범주의 영향을 받습니다.
값으로는 "" 을 지정하였으므로 환경변수 LC_CTYPE에서 값을
가져옵니다. 만약 LC_CTYPE이 없으면 LANG이라는 환경변수에서 값을
가져옵니다.

\section{XOpenIM}

프로그램이 입력 서버로부터 입력을 받기 위해서는 입력 서버와 연결을
해야 합니다. 이 일을 해주는 함수가 XOpenIM입니다. 형식은 다음과 같습니다.

#include 

XIM XOpenIM(display, rdb, res_name, res_class)
Display* display;
XrmDatabase rdb;
char* res_name;
char* res_class;

display     XOpenDisplay에서 얻은 디스플레이입니다.
rdb         리소스 데이타 베이스입니다.
res_name    리소스 이름입니다.
res_class   리소스 클래스입니다.

실제의 프로그램 예를 보면 다음과 같게 됩니다.

     XIM im;
     Display *display;

     display = XOpenDisplay("");
     im = XOpenIM(display, NULL, NULL, NULL);
     if (im == NULL) {
         fprintf(stderr, "can't open IM\n");
         exit(1);
     }

위의 작업을 한 후에는 보통의 X 프로그램과 같이 창 만드는 작업을
하면 됩니다.

\section{XCreateFontSet}

국제화된 프로그램을 짜게 되면 보통 두 개 이상의 문자집합을 사용하게
됩니다. (예를 들면 한글과 영문) 그런데 프로그램에서 다음과 같은
문자열을 출력한다고 할 경우,

현대전자 산업주식회사  Software R & D Center

응용프로그램에서 일일이 어느 부분이 한글이고 어느 부분이 영문인지
가려서 출력한다는 것은 상당히 귀찮은 작업입니다. 이런 작업을 쉽게
하기 위해서 국제화된 X 프로그램에서는 편의를 위해서 폰트집합이라는
것을 제공합니다.

실제로 폰트집합을 만드는 부분을 보이면 다음과 같습니다.

     #define DEFFONTLIST
"*-clean-bold-r-*-c-80-iso8859-1,*-myungjo-bold-r-*-c-160-ksc5601.1987-1"

     int missing_charset_count;
     char **missing_charset_list;
     char *def_string;
     XFontSet fontset;

     fontset = XCreateFontSet(display, DEFFONTLIST,
                   &missing_charset_list,
                   &missing_charset_count, &def_string);

여기서 쓸려고 하는 폰트는 두가지 즉 clean-bold와 명조입니다.
(폰트이름에 대해서는 자세하게 설명하지 않겠습니다. 다른 책을 참조하십시요.)
자기가 쓸려고 하는 폰트들을 `,' 로구분하여 나열하는 것을 알수 있습니다.
XCreateFontSet을 부른 후에는 쓸려고 하는 폰트들중에서 빠진 것의
갯수가 missing_charset_count에, 빠진 폰트들의 이름이
missing_charset_list에 돌려져서 옵니다. def_string은 빠진 것 대신
쓰일 폰트의 이름입니다.

\section{XCreateIC}

\subsection{입력 스타일}

폰트집합을 만든 후에는 응용프로그램이 입력을 어떤 방식으로 받을 것인지를
결정해야 합니다. 응용프로그램이 입력을 받는 방식에는 다음과 같은 4 가지
방법이 있습니다.

On-the-spot 방식
      사용자가 한 글자를 쳐 넣을 때마다 매번 입력을 받는 방식
Over-the-spot 방식
      사용자가 쳐 넣는 글자가 모아져서 응용프로그램에 전달되는
      방식. 지금 모아지는 글자는 커서의 오른 쪽에 그려지게 됩니다.
Off-the-spot 방식
      사용자가 쳐 넣는 글자가 모아져서 응용프로그램에 전달되는
      방식. 지금 모아지고 있는 글자들이 윈도우의 아래에 나타나는
      것이 Over-the-spot과 다릅니다.
Root-window 방식
      Off-the-spot 방식과 비슷하나 지금 모아지고 있는 글자가
      전체화면의 아래에 나타나게 됩니다.

위의 4 방식은 다음과 같은 비트 마스크들의 조합으로 표현됩니다.

     #define OnTheSpot   (XIMPreeditCallbacks|XIMStatusArea)
     #define OffTheSpot  (XIMPreeditArea|XIMStatusArea)
     #define OverTheSpot (XIMPreeditPosition|XIMStatusArea)
     #define RootWindow  (XIMPreeditNothing|XIMStatusNothing)

여기서 보면 OnTheSpot은 프리에디트 콜백과 상태 지역이 있고,
OffTheSpot은 프리에디트 지역과 상태 지역, OverTheSpot은 프리에디트
위치와 상태 지역, RootWindow는 프리에디트 상태지역 둘 다 없는
것을 나타냅니다.

위의 방식중 몇가지가 입력 서버에서 제공되는지 알아보기 위해서는
XGetIMValues라는 함수를 씁니다. 아래 프로그램에서는 우리가 원하는
입력방식이 Over-the-spot인 것으로 되어 있습니다.

     XIMStyles *ximstyles;
     XIMStyle style;

     style = OverTheSpot;
     XGetIMValues(im, XNQueryInputStyle, &ximstyles, NULL);
     for (i = 0; i < ximstyles->count_styles; i++)
         if (ximstyles->supported_styles[i] == style)
             break;
     if (i == ximstyles->count_styles) {
         fprintf(stderr, "style not supported\n");
         exit(1);
     }

\subsection{XVaCreateNestedList}

입력 서버가 자신이 원하는 입력 방식을 제공하는 것을 확인한 후에는
입력 문맥을 만드는 작업을 합니다. 입력 문맥을 만드는 함수는 XCreateIC인데,
이 함수를 부를 때 프리 에디트 창에 대한 정보와 상태 지역에 대한 정보를 넘겨
주어야합니다. 이 정보는 XVaNestedList라는 자료구조를 통해 전달해 줍니다.
XVaNestedList를 만드는 함수는 XVaCreateNestedList입니다.

실제의 예를 보이면 다음과 같이 됩니다.

     XPoint overspot;
     XVaNestedList list, preeditlist;

     overspot.x = 0;
     overspot.y = 0;
     list = XVaCreateNestedList(0,
                XNSpotLocation, &overspot,
                XNForeground, fore,
                XNBackground, back,
                XNFontSet, fontset,
                NULL);
     preedit_list = XVaCreateNestedList(0,
                XNPreeditAttributes, list,
                NULL);

XVaCreateNestedList의 제일 처음의 인자 0은 그냥 보내주는 것으로
실행에 아무 영향을 미치지 않습니다. SpotLocation은 프리 에디트
창이 나타날 위치를 말합니다. 여기서는 그냥 (0, 0)으로 되어 있습니다.
이 정보는 프로그램 실행중에 계속 변하게 되므로 초기값으론 무엇을
주어도 별 상관이 없습니다.
Foreground는 프리에디트 창의 전경색, Background는 프리에디트 창의
배경색입니다. 그리고 FontSet은 프리에디트 창에서 쓰일 폰트 집합입니다.

상태 지역에 대해서는 다음과 같이 정보를 만들어 둡니다.

     list = XVaCreateNestedList(0,
             XNForeground, fore,
             XNBackground, back,
             XNFontSet, fontset,
             NULL);
     status_list = XVaCreateNestedList(0,
             XNStatusAttributes, list,
             NULL);

여기까지 한 후에는 XCreateIC를 부르면 됩니다.

     XIC ic;

     ic = XCreateIC(im, XNInputStyle, style,
                        XNClientWindow, win,
                        XNFocusWindow, win,
                        XNVaNestedList, preedit_list,
                        XNVaNestedList, status_list,
                        NULL);

여기서 ClientWindow와 FocusWindow는 입력 서버로 부터 입력을 받을
윈도우를 말합니다.

\section{On-the-spot인 경우}

입력 스타일이 on-the-spot일 경우에는 입력 서버에서 콜백들을 불러주므로
이 콜백들을 등록해야 합니다. 이 콜백에 관한 정보는 XIMCallback이라는
자료구조를 통해서 기술됩니다. 실제의 예를 보이면 다음과 같습니다.

     static XIMCallback PreeditStartCallbackStruct = {
         NULL, (XIMProc) PreeditStartCallback
     };
     static XIMCallback PreeditDoneCallbackStruct = {
         NULL, (XIMProc) PreeditDoneCallback
     };
     static XIMCallback PreeditDrawCallbackStruct = {
         NULL, (XIMProc) PreeditDrawCallback
     };
    static  XIMCallback PreeditCaretCallbackStruct = {
         NULL, (XIMProc) PreeditCaretCallback
     };

     if (style & XIMPreeditCallbacks) { /* on-the-spot */
         list = XVaCreateNestedList(0,
                 XNPreeditStartCallback, &PreeditStartCallbackStruct,
                 XNPreeditDoneCallback, &PreeditDoneCallbackStruct,
                 XNPreeditDrawCallback, &PreeditDrawCallbackStruct,
                 XNPreeditCaretCallback, &PreeditCaretCallbackStruct,
                 NULL);
         preedit_list = XVaCreateNestedList(0,
                 XNPreeditAttributes, list,
                 NULL);
     }
     else {
         ...  /* non on-the-spot */
     }

위에서 preedit_list를 만드는 부분을 제외한 나머지(상태 영역 부분)는 앞에서
설명한대로 하시면 됩니다.
PreeditStartCallback, PreeditDrawCallback 등은 콜백 함수의 이름으로 실제
이 함수가 어떻게 구성되는지에 대해서는 입력을 다루는 장에서 설명하겠습니다.

\chapter{입력 받기}

입력에 관련된 함수는 XFilterEvent와 XmbLookupString, XSetICFocus,
XUnsetICFocus 입니다.

\section{XFilterEvent}

XFilterEvent는 XNextEvent를 부른 다음에 반드시 실행시켜 주어야만
합니다.(일본에서 구현한 방식에서는 XmbLookupString에서 자동으로 하게
되어 있습니다.).
그럼으로서 이벤트가 입력서버에 전달되어 입력서버가 제대로 동작하게 됩니다.
XFilterEvent의 형식은 다음과 같습니다.

Bool XFilterEvent(event, window)
XEvent* event;
Window window;

event         XNextEvent에서 받은 이벤트입니다.
window         그 이벤트가 갈 윈도우입니다.
돌려주는 값   만약 True를 돌려받으면 이 이벤트는 입력서버에서
               처리했다는 뜻입니다. 응용프로그램에서는 처리를 하면
                 안됩니다. 만약 False를 돌려 받으면 이것은 입력서버에서
                 처리하지 않은 이벤트입니다. 응용프로그램에서 처리해야
                 합니다.

window인자에는 0을 주어도 됩니다. 그럼 event 자료구조
안의 window를 이용하게 됩니다. 실제의 예를 보면 다음과 같습니다.

     XEvent event;

     while (1) {
         XNextEvent(display, &event);
         if (XFilterEvent(&event, win) == True)
             continue;
         switch (event.type) {
             ...
         }
     }

\section{XmbLookupString}

보통의 X 윈도우 프로그램에서는 KeyPress 이벤트를 받으면
XLookupString을 불러서 이벤트에서 스트링으로 변환을 하게 됩니다.
국제화된 X 윈도우 프로그램에서는 XLookupString 대신에 XmbLookupString
을 불러주어야만 합니다. 이 함수가 쓰이는 예를 보이면 다음과 같이
됩니다.

     KeySym keysym;
     char buf[MAX_BUF];
     Status status;
     int len;

     len = XmbLookupString(ic, &event, buf, MAX_BUF, &keysym, &status);
     if (len > 0) {
         ...
     }
    else {
         ...
     }

여기서 len이 0보다 크면 문자열이 입력된 것이므로 문자열을 처리하면
됩니다. len이 0보다 크지 않으면 문자열이 될 수 없는 키(예를 들면 화살표 키)가
입력된 것이므로 keysym을 보고 이에 맞는 처리를 하면 됩니다.

\section{XSetICFocus, XUnsetICFocus}

응용프로그램의 창이 입력 포커스를 받을 때와 잃어 버릴 때마다
XSetICFocus, XUnsetICFocus를 불러 주어야 합니다.
실제의 예를 보면

      switch (event.type) {
      ...
      case EnterNotify:
          XSetICFocus(ic);
          ...
          break;
      case LeaveNotify:
          XUnsetICFocus(ic);
          ...
          break;
      ...
      }

여기서는 마우스가 창에 들어오면 XSetICFocus 창에서 나가면
XUnsetICFocus를 불러 주고 있습니다. 실제 프로그램에서는
FocusOut 이벤트와 FocusIn 이벤트일 때도 처리해 주는 것이
좋겠습니다.

\section{On-the-spot일 경우}

On-the-spot일 경우에는 매 글자가 눌려질 때 마다 콜백이 불려지므로
조금 복잡하게 됩니다. 전체적인 순서를 간단하게 설명하면 다음과 같습니다.

사용자가 shift+space등을 눌러  한글 모드를 시작하면
입력서버는 이 때 PreeditStartCallback을 부릅니다. 이 때 응용프로그램은
프리 에디트가 시작됐음을 알고 이에 대한 준비를 해야 합니다.
계속해서 사용자가 한글 문자열을 입력하게 되면 입력서버는 PreeditDrawCallback을
불러 줍니다. 이 때 응용프로그램이 각 문자를 그리게 됩니다. 사용자가
문자열을 입력하는 도중에 화살표키등을 사용해서 캐럿을 움직이게 되면
입력서버는 PreeditCaretCallback을 불러줍니다. 이 때 응용프로그램은
알맞게 캐럿을 움직여 주어야 합니다. 마지막으로 사용자가 shift+space를 다시
입력하면 입력서버는 PreeditDoneCallback을 부릅니다. 이 때 응용프로그램은
프리에디트가 끝났음을 알고 이에 대응하는 작업을 합니다.

위와 같은 시나리오 상에서 각각에 대해서 좀더 자세히 알아 보도록 하겠습니다.

\subsection{XIMProc}

입력서버에서 부르는 콜백 함수들은 다음과 같은 형식을 갖고 있습니다.

fucntion(ic, client_data, call_data)
XIC ic;
XPointer client_data;
XPointer call_data;

ic            XCreateIC에서 생긴 ic입니다.
client_data  XIMCallback의 첫번째 멤버입니다.
call_data    각 콜백마다 달라지는 입력서버에서 주는 정보입니다.

여기서 client_data는 XIMCallback 자료구조의 첫번째 멤버로서
응용프로그램의 필요에 따라서 받고 싶은 자료를 미리 정해두면됩니다.

\subsection{PreeditStartCallback}

PreeditStartCallback은 사용자가 프리에디트를 시작하면 불리는 함수입니다.
call_data는 NULL입니다. 응용프로그램은 이 함수가 불려지면 프리에디트를
시작하기에 필요한 작업을 합니다. 예를 들자면 버퍼의 초기화등을 들 수
있겠습니다.

\subsection{PreeditDrawCallback}

PreeditDrawCallback은 프리에디트가 시작된 후 사용자가 키를 입력할 때마다
불려지는 함수입니다. call_data에는 XIMPreeditDrawCallbackStruct란 자료
구조가 들어옵니다. 이 자료구조는 다음과 같은 모양을 하고 있습니다.

typedef struct _XIMPreeditDrawCallbackStruct{
        int caret;
        int chg_first;
        int chg_length;
        XIMText *text;
} XIMPreeditDrawCallbackStruct;

여기서 caret은 지금 커서의 위치, chg_first는 바꾸어야 할 스트링의
처음을 나타내는 인덱스, chg_length는 바꾸어야 할 문자열의 길이,
text는 바꿔질 문자열에 대한 정보를 갖고 있습니다.

XIMText는 다음과 같은 모양으로 되어 있습니다.

typedef struct _XIMText{
        unsigned short length;
        XIMFeedback *feedback;
        Bool encoding_is_wchar;
        union {
               char *multi_byte;
               wchar_t *wide_char;
        } string;
} XIMText;

여기서 length는 바꿔질 문자열의 길이 feedback은 이 문자열을 어떻게
표시할 것인가에 대한 정보로 현재의 구현에서는 XIMReverse로 되어 있습니다.
encoding_is_wchar는 다음 멤버인 string이 wchar로 인코딩되어 있는지
multi_byte로 되어 있는지에 대한 정보입니다. 현재의 구현에서는 항상
False입니다. 즉 다음의 string멤버는 항상 multi_byte형식입니다.

이해를 돕기 위해서 사용자가 다음과 같은 문자열을 입력할 경우 불려지는
콜백들과 call_data를 설명해 보겠습니다.

shift+space  ㅎㅏ shift+space

shift+space  1 PreeditStartCallback이 불려집니다.
ㅎ           2 PreeditDrawCallback이 불려집니다.
              call_data에 오는 정보는 다음과 같습니다.
         caret = 1, chg_first = 0, chg_length = 0,
         text$rightarrow$length = 2, text$rightarrow$string.multi_byte
              = ``ㅎ''
         이 정보를 해석하면 버퍼의 0 번째 문자부터
         길이 0의 문자열을 길이 2의 ``ㅎ''자란 문자열로
         바꾸고(즉, ``ㅎ''을 삽입하고) 캐럿을 1번째 문자뒤
          즉 ``ㅎ''자 뒤에 그리라는 뜻입니다.
ㅏ           3 PreeditDrawCallback이 불려집니다.
              call_data에 오는 정보는 다음과 같습니다.
             caret = 1, chg_first = 0, chg_length = 1,
         text$rightarrow$length = 2, text$rightarrow$string.multi_byte
              = ``하''
         이 정보를 해석하면 버퍼의 0 번째 문자부터 길이
              1의 문자열을 길이 2의 문자열 ``하''로 바꾸고
              (즉, ``ㅎ''을 ``하''로 바꾸고) 캐럿을 1번째 문자 뒤
          즉 ``하''자 뒤에 그리라는 뜻입니다.
shift+space  4 PreeditDoneCallback이 불려집니다.

여기서 주의해야 할 것은 chg_first와 chg\length의 값은 한글 영문에
관계없이 한 글자를 1로 치는 것이고 (예로 ``한글abc''는 5)
 text$rightarrow$length는 한글은 2, 영문은 1로 치는 것입니다.
(예로 ``한글abc''는 7)

\subsection{PreeditCaretCallback}

PreeditCaretCallback은 사용자가 프리에디트 도중 화살표키를 누르면
불려집니다. 이 함수의 call_data는 XIMPreeditCaretCallbackStruct인데
다음과 같이 선언되어 있습니다.

typedef struct _XIMPreeditCaretCallbackStruct {
        int position;
        XIMCaretDirection direction;
        XIMCaretStyle style;
} XIMPreeditCaretCallbackStruct;

여기서 position은 현재의 caret의 위치를, direction은 캐럿이 어느 방향으로
움직였는가를 style은 캐럿을 어떤 모양으로 그릴 것인지를 말합니다.
현재의 구현에서는 direction은 XIMForwardChar와 XIMBackwardChar 두 가지
뿐입니다.

이 함수는 캐럿을 새로운 위치에 그린후에 새로운 캐럿의 위치를
돌려주어야만 합니다.

\subsection{PreeditDoneCallback}

PreeditDoneCallback은 사용자가 프리에디트를 시작하면 불리는 함수입니다.
call_data는 NULL입니다. 응용프로그램은 이 함수가 불려지면 프리에디트를
마치는 작업을 합니다.

주의할 점은 on-the-spot인 경우에 preedit이 끝나고 난 뒤 preedit 동안
입력된 문자들을 모아서 하나의 문자열로 입력서버가 돌려주므로 이에
대비해야 합니다. 예를 들면 문서 편집기같은 응용프로그램의 경우
preedit 동안 PreeditDrawCallback에서 이미 문자들을 버퍼에 삽입하도록
프로그램을 만드는 경우가 있는데 이 경우 preedit이 끝나고 난 뒤 그 동안
입력된던 문자열이 오므로 이 것을 또 한번 버퍼에 삽입하게 되는 수가 있습니다.
이에 대한 해결책으로는 PreeditDoneCallback이 불리고 난뒤에 오는 KeyPress
이벤트는 무시하도록 하는 것도 한 방법입니다. 또는 PreeditDrawCallback에서
문자들을 그리기만 하고 버퍼에 삽입하지는 않다가 KeyPress가 왔을 때
삽입하는 수도 있겠습니다.

\chapter{환경 바꾸기}

Over-the-spot일 경우에는 입력 서버에게 현재 커서가 어디에 있는지를
가르쳐 주어야 입력 서버가 제 위치에 preedit 창을 만들 수가 있습니다.
이것은 XSetICValues란 함수를 이용하여 하게 됩니다.

    over_spot.x = 0;
    over_spot.y = 20;
    list = XVaCreateNestedList(0,
               XNSpotLocation, &over_spot,
               NULL);
    preedit_list = XVaCreatedNestedList(0,
               XNPreeditAttributes, list,
               NULL);
    XSetICValues(ic, XNVaNestedList, preedit_list, NULL);

위와 같이 하게 되면 입력서버가 다음에 preedit 창을 그릴 때
(0,20) 위치에 그리게 됩니다. 이와 같은 작업은 매번 커서가 움직일 때마다
해 주면 되겠습니다.

\chapter{출력 하기}

문자열을 출력하는 함수는 XmbDrawString과 XmbDrawImageString으로
이 것은 보통 X 윈도우 프로그램의 XDrawString과 XDrawImageString에
대응됩니다. XmbDrawString과 XmbDrawImageString은 인자로 font_set을
하나 더 전달하는 것이 다릅니다. 실제의 예를 보이면

XmbDrawString(display, win, font_set, gc, 0, 20, "가나다라 abc", 12);

이 경우 ``가나다라 abc''라는 문자열을 (0, 20) 위치에 그려 주게 됩니다.


     ※ 참고 : 국제화를 기초로 짜여진 모든 프로그램들은 입력서버가 이미
          실행중인 후에야 비로소 실행될 수 있습니다.
          (예 : is (입력 서버) 를 실행시킨후 , hterm ( 한글 xterm) 을 실행시킬
          수
          있음)