FoundationsofGTKDevelopment:Chapter 02

From 흡혈양파의 번역工房
Jump to navigation Jump to search
제 2 장 자신의 첫 GTK+ 애플리케이션

자신의 첫 GTK+ 애플리케이션

제 1장에서는 그래픽 애플리케이션 개발자로서 자신의 GTK+ 라이브러리에서 어떤 것들을 이용할 수 있는지에 대한 개요를 제시하였다. 이번 장에서는 자신만의 GTK+ 애플리케이션을 작성하는 방법을 학습할 것이다.


간단한 예제로 시작하지만 이번 장에서는 중요한 개념들을 많이 제시하고 있다. 향후 본인이 작성하는 GTK+ 애플리케이션마다 의존하게 될 주제를 다룰 것이다. 따라서 여느 장과 마찬가지로 다음으로 넘어가기 전에 아래의 여러 페이지에 걸쳐 제시된 개념을 이해하도록 한다.


이번 장에서 학습하게 될 내용은 다음과 같다.

  • 모든 GTK+ 애플리케이션에서 요하는 기본 함수 호출
  • GCC를 이용해 GTK+ 코드를 컴파일하는 방법
  • GTK+ 위젯 시스템의 객체 지향적 특성
  • 자신의 애플리케이션에서 시그널, 콜백, 이벤트가 하는 역할
  • Pango Text Markup Lanaguage를 이용해 텍스트 스타일을 변경하는 방법
  • 본 장에 제시된 위젯에 제공되는 다른 다양하고 유용한 함수들
  • GtkButton 위젯을 이용해 클릭 가능한 GtkLabel을 만드는 방법
  • GObject 메서드를 이용해 객체의 프로퍼티를 얻고 설정하는 방법


Hello World

필자가 지금까지 읽은 프로그래밍 저서는 하나같이 "Hello World" 애플리케이션을 예로 시작한다. 그리고 필자도 그 전통을 깨고 싶지는 않다.


예제를 바로 시작하기 전에 알아두어야 할 점이 있는데, 모든 예제에 대한 소스 코드는 본 책의 웹 사이트 www.gtkbook.com에서 다운로드가 가능하다는 것이다. 본 장의 후반부에 제시된 방법을 이용해 각 예제를 컴파일하거나 패키지의 base 폴더에 발견되는 지침을 따라도 된다.


리스팅 2-1은 이번 책에서 처음으로 소개하는 가장 간단한 GTK+ 애플리케이션이다. 이는 GTK+를 초기화하고, 창을 생성하여 사용자에게 표시하며, 프로그램이 종료될 때까지 기다린다. 이것은 매우 기본적이지만 당신이 생성하는 모든 GTK+ 애플리케이션마다 가져야 하는 기본 코드를 보여준다!


Gtkd note.png 리스팅 2-1의 애플리케이션은 애플리케이션의 종료 방법은 제공하지 않는다. 창의 모서리에 X를 클릭하면 창은 닫히지만 애플리케이션은 여전히 실행될 것이다. 따라서 애플리케이션을 강제로 나가려면 terminal 창에서 Ctrl+C를 눌러야 할 것이다.


리스팅 2-1. World 와 인사하기 (helloworld.c)

#include <gtk/gtk.h>

int main (int argc,
        char *argv[])
{
    GtkWidget *window;

    /* Initialize GTK+ and all of its supporting libraries. */
    gtk_init (&argc, &argv);

    /* Create a new window, give it a title and display it to the user. */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Hello World");
    gtk_widget_show (window);

    /* Hand control over to the main loop. */
    gtk_main ();
    return 0;
}


위의 내용에서 <gtk/gtk.h> 파일은 GTK+ 에서 이용 가능한 모든 위젯, 변수, 함수, 구조를 포함할 뿐만 아니라 <glib/glib.h>와 <gdk/gdk.h>와 같이 GTK+가 의존하는 다른 라이브러리로부터의 헤더 파일도 포함한다. 자신의 애플리케이션들 중 대부분은 <gtk/gtk.h>가 GTK+ 개발에 포함시켜야 할 유일한 GTK+ 헤더 파일에 해당하겠지만 좀 더 향상된 애플리케이션의 경우라면 더 많은 대상을 포함시켜야 할 것이다.


리스팅 2-1은 GTK+를 이용해 생성할 수 있는 가장 간단한 애플리케이션에 속한다. 이는 기본 너비와 높이가 200 픽셀인 최상위 수준 GtkWindow 위젯을 생성한다. 애플리케이션을 시작한 terminal에서 죽이는(kill) 방법 말고는 애플리케이션을 나갈 방법이 없다. 필요 시 시그널을 이용해 애플리케이션을 나가는 방법은 다음 예제에서 학습할 것이다.


이 예제는 간단한 편이지만 당신이 생성하는 모든 GTK+ 애플리케이션에 필요한 기본사항을 보여준다. "Hello World" 애플리케이션을 이해하기 위한 첫 단계는 main() 함수의 내용을 살펴보는 것이다.


GTK+ 초기화하기

GTK+ 라이브러리의 초기화는 대부분 애플리케이션에서 매우 간단하게 이루어진다. gtk_init()를 호출하면 모든 초기화 작업이 자동으로 실행될 것이다.


GTK+ 환경의 설정부터 시작하는데, 이 과정에는 GDK 디스플레이를 얻고 GLib 메인 이벤트 루프와 기본 시그널 처리를 준비하는 과정이 포함된다. gtk_init()가 필요 이상으로 작업한다면 gdk_init() 또는 g_main_loop_new()와 같이 적은 수의 함수를 호출하는 작은 initialization 함수를 본인이 직접 생성하는 방법도 택할 수 있으나 대부분의 애플리케이션에서는 그럴 필요가 없다.


오픈 소스 라이브러리를 이용 시 가장 큰 장점 중 하나는 어떻게 일이 진행되는지 코드를 직접 읽을 수 있다는 데에 있다. GTK+ 소스를 손쉽게 확인하여 gtk_init()가 호출하는 모든 내용을 확인하고 자신의 애플리케이션이 무엇을 실행해야 하는지를 선택할 수 있다. 하지만 각 라이브러리가 어떻게 사용되고 상호 연관되는지에 대해 더 학습하기 전에는 우선 gtk_init()를 사용하도록 한다.


표준 main() 인자 매개변수인 argc와 argv를 gtk_init()로 전달하였음을 눈치챘을 것이다. GTK+ 초기화 함수는 모든 인자를 파싱하고 그것이 인식하는 것은 무엇이든 제거한다. 그것이 사용하는 매개변수는 모두 리스트에서 제거될 것이므로 gtk_init()를 호출한 다음에 인자 파싱은 개발자가 직접 실행해야 한다. 즉, 매개변수의 표준 리스트는 개발자 당사자가 추가로 작업할 필요 없이 모든 GTK+ 애플리케이션이 전달 및 파싱할 수 있다는 뜻이다.


GTK+ 라이브러리에 다른 함수를 호출하기 전에 먼저 gtk_init()를 호출하는 것이 중요하다. 이를 어길 경우 개발자의 애플리케이션은 적절하게 기능하지 못해 결국 충돌할 가능성이 크다.


gtk_init() 함수가 만일 GUI를 초기화할 수 없거나 해결할 수 없는 큰 문제에 직면할 경우 애플리케이션을 종료시킬 것이다. GUI 초기화가 실패할 때 본인의 애플리케이션이 텍스트 인터페이스에 의존하길 원한다면 gtk_init_check()를 이용해야 한다.

gboolean gtk_init_check (int *argc,
        char ***argv);


초기화가 실패하면 FALSE가 리턴된다. 성공한다면 gtk_init_check()는 TRUE를 리턴할 것이다. 해당 함수는 의지할 텍스트 인터페이스가 있을 경우에만 사용하길 바란다!


위젯 계층구조

위젯 계층구조는 GTK+를 학습할 때 가장 중요한 논의 주제들 중 하나라고 생각한다. 이해하기 힘든 주제는 아니지만 이것이 없다면 오늘날 위젯은 존재할 수 없었을 것이다.


주제를 이해하기 위해 새로운 GtkWindow 객체를 생성하는 데에 사용되는 함수 gtk_window_new()를 살펴볼 것이다. 아래 행을 통해 우리는 새 GtkWindow 객체를 생성하길 원하는데 gtk_window_new()는 GtkWidget를 향하는 포인터를 리턴함을 눈치챌 것이다. 이는 GTK+ 에서 모든 위젯은 사실상 GtkWidget 자체이기 때문이다.

GtkWidget* gtk_window_new (GtkWindowType type);


GTK+ 의 위젯은 GObject 계층구조 시스템을 이용하므로, 이미 존재하는 위젯으로부터 새 위젯을 파생하는 것이 가능하다. 자식 위젯은 사실상 그들의 조상(ancestor) 위젯의 implementation이기 때문에 그들의 부모, 조부 위젯 등으로부터 프로퍼티, 함수, 시그널을 상속받는다.


GTK+의 위젯 계층구조는 단일 상속된 시스템으로, 각 자식은 하나의 직계 부모만 가질 수 있다는 의미다. 이는 모든 위젯이 구현하는 단순한 선형적 관계를 생성한다. 개발자 고유의 자식 위젯을 파생시키는 방법은 제 11장에서 학습할 것이다. 그때까지 우리는 위젯 계층구조를 이용해 상속된 메서드와 프로터리를 활용할 것이다.


그림 2-2에는 GtkWindow 클래스의 위젯 계층구조에 대한 단순한 개요가 설명되어 있다.

그림 2-1. GtkWindow의 위젯 계층구조


그림 2-1은 처음에는 복잡해 보이지만 이해가 쉽도록 클래스 타입을 하나씩 살펴보도록 하자. GObject는 GTK+와 Pango를 포함해 그것을 기반으로 하는 모든 라이브러리에 대해 공통 속성을 제공하는 기본적인 타입이다. 이는 그로부터 파생된 객체들이 구성, 소멸, 참조, 참조해제되도록 허용한다. 또 시그널 시스템과 객체 프로퍼티 함수를 제공하기도 한다. G_OBJECT()를 이용해 객체를 GObject로서 형변환(cast)할 수 있다. GObject가 아닌 객체나 GObject로부터 파생되지 않은 객체를 G_OBJECT()로 형변환할 경우 GLib는 임계 오류를 던지고 형변환은 실패할 것이다. 이는 다른 어떤 GTK+ 형변환 함수와도 발생할 것이다.


GInitiallyUnowned는 프로그래머가 접근해선 절대로 안 되는데, 그 모든 멤버들이 private하기 때문이다. 이는 참조가 floating할 수 있도록 존재한다. floating 참조는 누구도 소유하지 않은 참조다.

  • GtkObject는 모든 GTK+ 객체의 기반 클래스이다. GTK+ 2.0에서는 모든 객체의 절대적 기반 클래스로서 대체되었지만 GtkObject는 GtkAdjustment와 같은 비위젯(nonwidget) 클래스의 이전 기종 호환성을 위해 유지되었다. GTK_OBJECT()를 이용해 객체를 GtkObject로서 형변환할 수 있다.
  • GtkWidget은 모든 GTK+ 위젯에 대한 추상적 기반 클래스다. 이는 모든 위젯이 필요로 하는 스타일 프로퍼티와 표준 함수를 소개한다. 모든 위젯을 GtkWidget로서 보관하는 것이 표준 관행이며 이는 리스팅 2-1에서 확인할 수 있다. 따라서 객체의 형변환에 GTK_WIDGET()을 사용해야 하는 경우는 드물 것이다.
  • GtkContainer는 하나 또는 그 이상의 위젯을 포함하는 데에 사용되는 추상적 클래스다. 이것이 없이는 창에 다른 어떤 위젯도 추가할 수가 없기 때문에 매우 중요한 구조다. 따라서 제 3장에서는 이 클래스로부터 파생된 위젯을 중점으로 다루겠다. GTK_CONTAINER()를 이용해 객체를 GtkContainer로서 형변환할 수 있다.
  • GtkBin은 또 다른 추상적 클래스로서, 위젯이 하나의 자식만 포함하도록 허용한다. 이는 코드를 재생성할 필요 없이 다수의 위젯이 해당 기능을 갖도록 허용한다. GTK_BIN()을 이용해 객체를 GtkBin으로서 형변환할 수 있다.
  • GtkWindow는 리스팅 2-1에서 살펴본 표준 window 객체다. GTK_WINDOW()를 이용해 객체의 형변환이 가능하다.


본문에 실린 모든 위젯은 비슷한 위젯 계층구조를 사용할 것이다. API 문서가 있다면 자신이 사용하는 위젯의 계층구조를 참조할 수 있으므로 유용하다. API 문서를 라이브러리와 함께 설치하지 않았다면 www.gtk.org/api 에서 이용할 수 있다.


현재로선 객체를 어떻게 형변환하고 어떤 기본 추상적 타입이 사용되는지만 알아도 충분하다. 제 11장에서는 자신만의 위젯을 생성하는 방법을 학습할 것이다. 그 때 GObject 시스템이 하는 일도 자세히 알아볼 것이다.


GTK+ Windows

리스팅 2-1의 코드는 기본 너비와 높이가 200 픽셀로 설정된 GtkWindow 객체를 생성한다. 기본 크기를 200으로 선택한 이유는 너비와 높이가 0 픽셀인 창은 크기 조정이 불가하기 때문이다. 제목 표시줄과 창 테두리는 총 크기에 포함되어 있으므로 사실상 창의 작업 영역은 200 x 200 픽셀보다 작다는 사실을 주목해야 한다.


GTK_WINDOW_TOPLEVEL을 gtk_window_new()로 전달하였다. 이는 GTK+에게 새로운 최상위 수준 창을 생성하라고 말한다. 최상위 수준 창은 윈도 관리자 decorations를 사용하고, 테두리 프레임을 가지며, 윈도 관리자가 그들을 위치시킬 수 있도록 허용한다. 따라서 개발자는 창의 위치와 관련해 절대적인 제어를 "갖지 않으며" 그러한 가정도 해선 안 된다.

GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);


GTK+ 가 제어하는 것과 윈도 관리자가 제어하는 것을 구별하는 것이 중요하다. 최상위 수준 위젯의 크기와 위치에 대한 제안과 요청을 보낼 수는 있다. 하지만 이러한 기능의 최종적 제어는 윈도 관리자에게 있다.


한편 GTK_WINDOW_POPUP을 이용해 팝업 창을 생성할 수도 있지만 GTK+에서는 오해의 소지가 있는 이름이다. 팝업 창은 툴팁이나 메뉴와 같이 창으로 간주하지 않는 대상에 사용된다.


팝업 창은 윈도 관리자가 무시하므로 decorations 또는 테두리 프레임이 없다. 팝업 창을 최소화하거나 최대화하는 방법도 없는데, 윈도 관리자가 그에 대해 알지 못하기 때문이다. 크기조정 그립(grip)이 표시되지 않고, 기본 키 바인딩은 작동하지 않을 것이다.


GTK_WINDOW_TOPLEVEL과 GTK_WINDOW_POPUP은 GtkWindowType 목록(enumeration)에서 이용 가능한 유일한 요소들이다. 대부분의 경우 타당한 이유가 있지 않는 한 GTK_WINDOW_TOPLEVEL을 사용할 것이다.


Gtkd note.png 창에서 윈도 관리자 decorations만 끄고자 할 때는 GTK_WINDOW_POPUP을 사용해선 안 된다. 창의 decorations를 끄려면 그 대신 gtk_window_set_decorated(GtkWindow *window, gboolean show)를 사용해야 한다.


아래 함수는 제목 표시줄과 작업표시줄로 "Hello World!"를 창의 제목으로 표시할 것을 요청한다. gtk_window_set_title()은 GtkWindow 객체를 그 첫 번째 매개변수로서 요하기 때문에 GTK_WINDOW() 함수를 이용해 창의 형변환을 실행해야 한다.

void gtk_window_set_title (GtkWindow *window,
        const gchar *title);


gtk_window_set_title()의 두 번째 매개변수는 창이 표시하게 될 제목이다. 이는 gchar이라 불리는 GLib의 char 구현을 이용한다. gchar*로 열거된 매개변수가 보이면 const char*도 수용할 것인데, gchar*는 표준 C 문자열 객체의 typedef로서 정의되기 때문이다.


이번 절에서 마지막으로 살펴볼 흥미로운 함수는 gtk_widget_show()인데, 이는 명시된 위젯을 visible로 설정할 것을 GTK+로 알린다. gtk_widget_show()를 호출한 직후엔 위젯이 표시되지 않을 수도 있는데, 화면으로 가져오기 전에 GTK+는 모든 처리가 완료될 때까지 위젯을 대기시키기(queue) 때문이다.


gtk_widget_show()는 그것이 호출되는 위젯만 표시할 것이라는 사실을 주목하는 것이 중요하다. 위젯이 이미 visible로 설정되지 않은 자식들을 가진 경우 자식들은 화면에 표시되지 않을 것이다. 또 위젯의 부모가 visible로 설정되지 않은 경우 부모는 화면에 표시되지 않을 것이다. 대신 그 부모도 visible로 설정될 때까지 대기할 것이다.


위젯의 표시 뿐만 아니라 gtk_widget_hide()를 이용해 사용자의 뷰에서 위젯을 숨기는 것도 가능하다.

void gtk_widget_hide (GtkWidget *widget);


이는 뷰에서 모든 자식 위젯을 숨기겠지만 주의해서 사용해야 한다. 해당 함수는 명시된 위젯을 hidden으로 설정할 뿐이다. 후에 위젯을 표시하면 그 자식들은 hidden으로 표시된 적이 없기 때문에 visible 상태일 것이다. 다수의 위젯을 한 번에 표시하고 숨기는 방법을 학습 시 이러한 구별이 중요해질 것이다.


메인 루프 함수

초기화가 완료되고 필요한 시그널이 GTK+ 애플리케이션에서 연결되면 GTK+ 메인 루프가 제어를 담당하고 이벤트 처리를 시작하길 원할 때가 올 것이다. 이를 위해 gtk_main()을 호출할 것인데, 해당 호출은 당신이 gtk_main_quit() 를 호출하거나 애플리케이션이 종료될 때까지 계속 실행된다. 이것은 main()에서 호출되는 마지막 GTK+ 함수여야 한다.


gtk_main()을 호출한 후에는 콜백 함수가 초기화되기 전까지는 프로그램의 제어를 다시 얻기가 불가능하다. GTK+에서 시그널과 콜백 함수는 버튼 클릭, 비동기식 입-출력 이벤트, 프로그램 가능 timeout 등의 사용자 액션에 의해 트리거된다. 다음 예제에서는 시그널, 이벤트, 콜백 함수를 살펴봄으로써 시작할 것이다.


Gtkd note.png 특정 시간 간격으로 호출되는 함수의 생성도 가능하며 이를 timeout이라 부른다. 콜백 함수의 또 다른 타입으로 idle 함수라는 것이 있는데, 이는 운영체제가 다른 작업의 처리로 바쁘지 않을 때 호출된다. 이러한 두 가지 기능은 GLib의 일부이며 제 6장에서 상세히 다루겠다.


이런 몇 가지 경우를 제외하곤 애플리케이션의 제어는 gtk_main()이 호출되고 나면 시그널, timeout 함수, 그 외 다양한 콜백 함수에 의해 관리된다. 본 장의 후반부에서는 자신의 애플리케이션에서 시그널과 콜백을 사용하는 방법을 살펴볼 것이다.


GCC와 pkg-config를 이용해 컴파일하기

이제 리스팅 2-1이 어떻게 작동하는지 이해했으니 코드를 실행 파일(executable)로 컴파일할 때가 되었다. 이를 위해 terminal에서 아래 명령을 실행한다.

gcc -Wall -g helloworld.c -o helloworld `pkg-config --cflags gtk+-2.0` \
    `pkg-config --libs gtk+-2.0`


해당 명령은 libglade도 필요로 하는 제 10장의 예제들만 제외하고 모든 예제에서 사용 가능하다. 필자는Linux에서 표준 C 컴파일러인 GCC 컴파일러를 사용하기로 결정했는데, 대부분의 C와 C++ 컴파일러도 작동할 것이다. 단, 다른 컴파일러를 사용할 것이라면 문서의 참조가 필요할 것이다.


앞의 컴파일 명령은 제공되는 여러 옵션을 이용해 파싱된다. -Wall 옵션은 모든 타입의 컴파일러 경고를 가능하게 한다. 항상 바람직한 것은 아니지만 GTK+로 프로그래밍을 시작할 때 단순한 프로그래밍 오류를 감지하는 데 도움이 되기도 한다. 디버깅은 -g를 이용해 가능하므로, GDB 또는 자신이 선택한 디버거를 이용해 컴파일된 애플리케이션을 사용할 수 있을 것이다.


다음 명령 집합인 helloworld.c -o helloworld는 명시된 파일을 컴파일하고 이를 helloworld라는 실행 파일로 출력한다. GCC에 의한 컴파일에 대해 하나 또는 그 이상의 소스 파일을 명시할 수 있다.


Gtkd caution.png 컴파일 명령에 사용된 기울어진 작은 따옴표 부호는 역따옴표로, 대부분 키보다 상단 좌측 모서리 쪽의 키에서 찾을 수 있다. 이것은 따옴표 사이의 명령은 나머지 행이 실행되기 전에 출력에 의해 실행 및 대체되어야 함을 단말기로 알려준다.


GCC 컴파일러에 더해 명시된 라이브러리나 경로의 리스트를 리턴하는 pkg-config 애플리케이션도 사용할 필요가 있다.


첫 번째 예인 pkg-config --cflags gtk+-2.0은 컴파일러의 include 경로로 디렉터리명을 리턴한다. 이는 컴파일러에서 GTK+ 헤더 파일을 이용 가능하도록 확보할 것이다. 자신의 단말기에서 pkg-config --cflags gtk+-2.0을 실행하면 컴파일러로 출력되는 내용의 예를 살펴볼 수 있을 것이다.


두 번째 호출인 pkg-config --libs gtk+-2.0은 링커가 사용하는 명령행으로 옵션을 추가하는데, 실행 파일로 연결하는 데에 필요한 라이브러리 리스트와 디렉터리 경로 확장자가 포함된다. 표준 Linux 환경에서 리턴되는 라이브러리는 아래와 같다.

  • GTK+(1lgtk): 그래픽 위젯
  • GDK(-lgdk): 표준 그래픽 렌더링 라이브러리
  • GdkPixbuf(-lgdk_pixbuf): 클라이언트 측 이미지 조작
  • Pango(-lpango): 글꼴 렌더링과 출력
  • GObject(-lgobject): 객체 지향의 타입 시스템
  • GModule(-lgmodule): 동적으로 로딩되는 라이브러리
  • GLib(-lglib): 데이터 타입과 유틸리티 함수
  • Xlib(-lX11): X Windows System 프로토콜 라이브러리
  • Xext(-1Xext): X 확장 라이브러리 루틴
  • GNU math library(-lm): GNU 라이브러리로서, GTK+는 여기서 비롯된 다수의 루틴을 사용


위에서 확인할 수 있듯이 pkg-config는 개발자가 수동으로 GTK+ 애플리케이션을 컴파일할 때마다 긴 includes 및 라이브러리 리스트의 하드코딩을 피하는 편리한 방법을 제공한다.


리스팅 2-1은 GTK+를 이용해 생성할 수 있는 가장 간단한 애플리케이션에 해당한다. 그림 2-2와 같이 너비와 높이가 200 픽셀인 최상위 수준의 GtkWindow 위젯이 생성된다.

그림 2-2. 기본 크기로 생성된 Hello World 창


창은 제목 표시줄의 오른쪽에 표준 X를 포함하고 있지만 X를 클릭 시 창이 사라지는 효과만 나타남을 눈치챌 것이다. 애플리케이션은 계속해서 이벤트를 기다리며, Ctrl+C를 누를 때까지 제어는 시작 단말기(launching terminal)로 돌아가지 않을 것이다. 시그널을 이용해 shutdown 콜백을 구현하는 방법을 다음 예제에서 살펴보겠다.


"Hello World" 확장하기

개발자가 작성하는 모든 GTK+ 애플리케이션은 리스팅 2-1에 표시된 함수 호출을 필요로 하지만 예제 자체는 그다지 유용하지 못하다. 이제 시작하는 방법을 알았으니 좀 더 멋지게 세상에 "hello"를 외쳐보자.


리스팅 2-2는 "Hello World" 애플리케이션을 두 가지 방법으로 확장한다. 첫째, 콜백 함수를 윈도우 시그널로 연결하여 애플리케이션이 스스로 종료할 수 있다. 둘째, 이 예제는 GtkContainer 구조를 도입하여 위젯이 하나 또는 이상의 다른 위젯을 포함할 수 있도록 해준다.


리스팅 2-2. World 와 다시 인사하기 (helloworld2.c)

#include <gtk/gtk.h>

static void destroy (GtkWidget*, gpointer);
static gboolean delete_event (GtkWidget*, GdkEvent*, gpointer);

int main (int argc,
        char *argv[])
{
    GtkWidget *window, *label;

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Hello World!");
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    gtk_widget_set_size_request (window, 200, 100);

    /* Connect the main window to the destroy and delete-event signals. */
    g_signal_connect (G_OBJECT (window), "destroy",
        G_CALLBACK (destroy), NULL);
    g_signal_connect (G_OBJECT (window), "delete_event",
        G_CALLBACK (delete_event), NULL);

    /* Create a new GtkLabel widget that is selectable. */
    label = gtk_label_new ("Hello World");
    gtk_label_set_selectable (GTK_LABEL (label), TRUE);

    /* Add the label as a child widget of the window. */
    gtk_container_add (GTK_CONTAINER (window), label);
    gtk_widget_show_all (window);

    gtk_main ();
    return 0;
}

/* Stop the GTK+ main loop function when the window is destroyed. */
static void
destroy (GtkWidget *window,
        gpointer data)
{
    gtk_main_quit ();
}

/* Return FALSE to destroy the widget. By returning TRUE, you can cancel
 * a delete-event. This can be used to confirm quitting the application. */
static gboolean
delete_event (GtkWidget *window,
        GdkEvent *event,
        gpointer data)
{
    return FALSE;
}


그림 2-3은 리스팅 2-2이 실행되는 모습의 스크린샷이다. 이는 GtkWindow가 포함하는 GtkLabel을 표시한다. 이제 해당 예제에서 표시된 새 기능을 살펴보자.

그림 2-3. 확장된 Hello World 창


GtkLabel 위젯

리스팅 2-2에서 GtkLable이라 불리는 새로운 타입의 위젯이 생성되었다. 이름이 암시하듯 GtkLabel 위젯은 보통 다른 위젯에 라벨을 붙이는 데에 사용된다. 하지만 편집 불가하거나, 포맷팅되거나, 래핑된 텍스트의 큰 블록을 생성하는 등의 작업에 사용되기도 한다.


gtk_label_new()를 호출함으로써 새 라벨 위젯을 생성할 수도 있다. gtk_label_new()에 NULL을 전달하는 것은 빈 문자열을 전달하는 것과 동일하다. 이런 경우 어떠한 텍스트도 없이 라벨만 표시될 것이다.

GtkWidget* gtk_label_new (const gchar *str);


사용자가 키보드나 마우스를 이용해 일반적인 GtkLabel을 편집하기란 (즉, 프로그래머의 추가 작업 없이 편집하기란) 불가능하지만 gtk_label_set_selectable()을 이용하면 사용자가 텍스트를 선택하고 복사할 수 있도록 해줄 것이다. 위젯은 커서 포커스를 허용 가능하므로, 라벨과 다른 위젯들 간에 Tab 키를 이용해 이동할 수 있다.

void gtk_label_set_selectable (GtkLabel *label,
        gboolean selectable);


라벨을 선택하는 기능은 기본적으로 꺼져 있는데, 이러한 기능은 사용자가 특정 정보를 계속 유지해야 할 필요가 있을 때에만 사용되어야 하기 때문이다. 가령 오류 메시지는 selectable(선택 가능하게) 설정되어야만 웹 브라우저와 같은 다른 애플리케이션으로 쉽게 복사가 가능하다.


GtkLabel 의 텍스트는 당신이 생성 시 명시한 텍스트 문자열과 동일한 상태로 유지될 필요가 없다. gtk_label_set_text()를 이용해 쉽게 변경할 수 있다. 연상기호(mnemonics)는 물론이고 라벨이 최근에 포함한 텍스트는 무엇이든 오버라이드될 것이다.

void gtk_label_set_text (GtkLabel *label,
        const gchar *str);


Gtkd note.png 연상기호는 사용자가 누르면 특정 유형의 액션을 실행하는 키의 조합이다. GtkLabel에 연상기호를 추가하면 키를 누를 시 지정된 위젯의 활성화가 가능하다.


현재 라벨에 의해 표시된 문자열은 gtk_label_get_text()를 이용해 검색할 수 있다. 리턴된 문자열은 markup이나 연상기호에 대한 정보는 포함하지 않을 것이다. 라벨은 또 이를 내부적으로 사용하기 때문에 리턴된 문자열은 절대 수정해선 안 된다!


마지막으로 알아야 하는 GtkLabel 메서드는 gtk_label_set_markup()으로, 표시된 텍스트에 대한 커스텀 스타일을 정의하도록 해준다. Pango Text Markup Language가 제공하는 태그의 수는 많은데, 본 서적의 뒷부분에 실린 부록 C에서 찾을 수 있겠다.

void gtk_label_set_markup (GtkLabel *label,
        const gchar *str);


Pango Text Markup Language는 두 가지 타입의 스타일 메서드를 제공한다. <span> 태그는 글꼴 타입, 크기, 선굵기, 전경색 등과 같은 속성들과 함께 사용 가능하다. 또 감싼 텍스트를 볼드체, 고정폭 간격, 이탤릭체로 만드는 <b>, <tt>, <i>와 같은 다양한 태그도 제공한다.


컨테이너 위젯과 레이아웃

이번 장에서 처음으로 소개한 예제를 다시 상기하면, GtkWindow 구조는 GtkContainer로부터 간접적으로 파생된다. 즉, GtkWindow는 GtkContainer이고, GtkContainer의 함수, 시그널, 프로퍼티를 모두 상속받는다.


gtk_container_add()를 이용하면 위젯을 컨테이너의 자식으로서 추가할 수 있다. 컨테이너가 이제 위젯의 부모가 되는 것이다. 해당 컨테이너, 그리고 포함된 관계를 설명하는 데에 "부모와 자식"이란 용어가 자주 사용되는데, 부모는 포함하는 위젯이고 자식은 부모 안에 포함된다.

void gtk_container_add (GtkContainer *container,
        GtkWidget *child);


아쉽게도 이러한 용어는 오해를 불러일으키곤 하는데 그 이유는 GTK+가 모든 면에서 객체 지향적이기 때문이다. 이러한 이유로 인해 GTK+를 사용하고 논할 때는 "부모"와 "자식"이 사용되는 맥락을 인지해야만 한다. 해당 용어들은 컨테이너 위젯 관계를 이야기할 때와 위젯 파생(derivation) 관계를 논할 때 모두 사용된다.


GtkContainer 클래스의 목적은 부모 위젯이 하나 또는 그 이상의 자식을 갖도록 허용하는 데에 있다. GtkWindow는 GtkBin이라 불리는 컨테이너 타입에서 파생된다. GtkBin은 부모가 하나의 자식만 갖도록 한다. 따라서 컨테이너로서 창은 직접적으로 하나의 자식만 포함하도록 제한한다. 다행히 하나의 자식은 그 자체가 조금 더 복잡한 컨테이너 위젯이 될 수 있으므로 또 하나 이상의 자식 위젯을 포함할 수도 있다.


예제로 소개한 창은 더 이상 기본 크기 200 x 200 픽셀이 아니며 사각형 비율(square aspect ratio)이 유지되지 않음을 주목해야 한다. 이것은 GTK+가 보통 자동적이고 다이나믹하게 크기가 조정되는 레이아웃 시스템을 사용하기 때문이다. 이렇게 다이나믹한 크기 조정의 원인은 컨테이너 객체 존재의 이면에서 발생한다. 크기 조정 체계는 컨테이너 위젯을 다루는 다음 장에서 세부적으로 논할 것이다.


우리가 생성한 창은 GtkContainer이므로 gtk_container_set_border_width() 함수를 이용해 창의 안쪽 테두리 주위로 10 픽셀 테두리를 위치시킬 수 있다. 테두리는 자식 위젯의 사면에 모두 설정된다.

void gtk_container_set_border_width (GtkContainer *container,
        guint border_width);


레이아웃 관리자는 테두리를 추가할 필요 없이 GtkLabel 위젯의 기본 크기로 창을 축소시킬 수 있도록 해준다. 리스팅 2-1에서 창은 200 픽셀 너비와 100 픽셀 높이로 설정되었다. 이 크기라면 대부분의 시스템에서 라벨 주위에 10 픽셀 이상의 테두리가 표시될 것이다. 테두리는 사용자로 하여금 테두리와 위젯이 할당한 크기보다 작게 창을 조정하지 못하도록 한다.


그리고 나서 창에 gtk_widget_show_all()을 호출한다. 해당 함수는 창, 그의 자식, 자식의 자식 등을 재귀적으로 그린다. 해당 함수가 없다면 자식 위젯마다 gtk_widget_show()를 호출해야 할 것이다. 대신 gtk_widget_show_all()을 이용하면 GTK+는 화면에 각 위젯이 모두 나타날 때까지 하나씩 표시하는 일을 알아서 해준다.

void gtk_widget_show_all (GtkWidget *widget);


비재귀적 gtk_widget_show()와 같이, 부모가 visible로 설정되지 않은 위젯에서 위의 함수를 호출할 경우 표시되지 않을 것이다. 위젯은 그 부모가 visible로 설정될 때까지 대기할 것이다.


GTK+는 gtk_widget_hide_all()을 제공하기도 하는데, 이는 명시된 위젯과 그 모든 자식을 숨겨진 상태로 설정할 것이다. 컨테이너가 숨겨지면 포함된 위젯들도 표시되지 않으므로, 포함하는 객체에서 gtk_widget_hide()를 호출하면 gtk_widget_hide_all()을 호출할 때와 동일한 효과를 보이는데, 둘 다 컨테이너와 그 자식들을 모두 숨길 것이기 때문이다. 하지만 둘 사이에는 중요한 차이점이 있다. gtk_widget_hide()를 호출하면 하나의 위젯에만 visible 프로퍼티를 FALSE로 설정하는 반면 gtk_widget_hide_all()을 호출하면 전달된 위젯 상의 프로퍼티를 비롯해 그에 포함된 모든 위젯에서 프로퍼티를 재귀적으로 변경한다.

void gtk_widget_hide_all (GtkWidget *widget);


gtk_widget_show()와 gtk_widget_show_all() 함수 집합은 같은 관계를 갖는다. 따라서 같은 위젯에서 gtk_widget_hide_all()을 사용하되 gtk_widget_show()를 호출할 경우 그 자식들은 모두 invisible 상태로 유지될 것이다.


컨테이너 위젯과 애플리케이션 레이아웃 관리는 다음 장에서 상세히 다룰 것이다. GtkContainer를 이해하는 데 필요한 정보는 리스팅 2-2에서 충분히 싣고 있으니 시그널과 콜백 함수를 계속하겠다.


시그널과 콜백

GTK+는 시그널과 콜백 함수에 의존하는 시스템이다. 시그널은 사용자가 어떤 액션을 실행했다는 사실을 개발자의 애플리케이션에게 전하는 알림이다. 개발자는 시그널이 방출되면 함수를 실행하도록 GTK+에게 알릴 수 있다. 이를 콜백 함수라고 부른다.


Gtkd caution.png GTK+ 시그널은 POSIX 시그널과 동일하지 않다! GTK+의 시그널은 X Windows System으로부터 이벤트에 의해 전파된다. 각 시그널은 별개의 메서드를 제공하고, 이러한 두 가지 타입의 시그널은 교대(interchangeably)해서 사용해선 안 된다.


사용자 인터페이스를 초기화하고 나면 제어는 gtk_main() 함수로 주어지고, 해당 함수는 시그널이 방출될 때까지 sleep 상태로 유지된다. 이 시점에서 제어는 콜백 함수라고 불리는 다른 함수로 전달된다.


프로그래머로서 당신은 gtk_main()을 호출하기 전에 그들의 콜백 함수로 시그널을 연결한다. 콜백 함수는 액션이 발생하여 시그널이 방출될 때나, 개발자가 명시적으로 시그널을 방출할 때 호출될 것이다. 시그널이 절대 방출되지 못하도록 막는 기능도 있다.


Gtkd note.png 자신의 애플리케이션에서 시그널은 언제든 연결할 수 있다. 예를 들어, 새로운 시그널은 콜백 함수 내부에서 연결 가능하다. 하지만 gtk_main()을 호출하기 전에 임무에 결정적인(mission-critical) 콜백을 초기화하도록 한다.


함수와 마찬가지로 시그널에도 많은 유형이 있으며, 부모 구조로부터 상속된다. hide 또는 grab-focus 등 모든 위젯에 일반적인 시그널도 많이 존재하며, GtkRadioButton 시그널 group-changed와 같이 위젯에 특정적인 시그널도 있다. 어떤 경우든 클래스로부터 파생된 위젯은 그의 모든 조상이 이용할 수 있는 시그널이라면 그들도 이용할 수 있다.


시그널 연결하기

처음으로 직면하게 될 시그널의 예제는 리스팅 2-2에 소개되어 있다. GtkWindow는 destroy() 콜백 함수로 연결되어 있었다. 해당 함수는 destroy 시그널이 방출될 때 호출될 것이다.

g_signal_connect (G_OBJECT (window), "destroy",
        G_CALLBACK (destroy), NULL);


GTK+는 위젯에 gtk_widget_destroy()가 호출되거나 delete_event() 콜백 함수로부터 FALSE가 리턴되면 destroy 시그널을 방출한다. API 문서를 참조한다면 destroy 시그널이 GtkObject 클래스에 속함을 확인할 수 있을 것이다. 즉, GTK+ 내 모든 클래스는 시그널을 상속받고, 개발자는 어떤 GTK+ 구조든 소멸되면 그러한 사실을 통지받을 것이다.


모든 g_signal_connect() 호출에는 4개의 매개변수가 있다. 첫 번째는 시그널을 감시해야 하는 위젯이다. 그 다음 본인이 추적하길 원하는 시그널의 이름을 명시한다. 각 위젯마다 가능한 시그널이 여러 개 존재하는데, 모두 API 문서에서 찾을 수 있다. 각 위젯은 구조적으로 사실상 각 조상들의 구현에 해당하기 때문에 위젯은 그 조상들의 시그널을 마음대로 사용할 수 있음을 기억하라. 부모 클래스를 참조하려면 API에서 "객체 계층구조(Object Hierarchy)" 부분을 이용할 수 있겠다.

gulong g_signal_connect (gpointer object,
        const gchar *signal_name,
        GCallback handler,
        gpointer data);


시그널명을 입력할 때 밑줄과 대시 문자는 서로 바꿔 사용할 수 있다. 해당 문자들은 동일한 문자로 파싱될 것이기 때문에 둘 중 무엇을 이용하는지는 별 차이가 없다. 필자는 본문에서 밑줄 문자로 통일해 사용할 것이다.


g_signal_connect()에서 세 번째 매개변수는 시그널이 방출되면 호출될 콜백 함수로서, G_CALLBACK()을 이용해 형변환된다. 콜백 함수의 포맷은 각 구체적인 시그널의 함수 프로타타입 요구조건에 따라 좌우된다. 콜백 함수의 예는 다음 절에서 소개할 것이다.


g_signal_connect()에서 마지막 매개변수는 콜백 함수로 포인터를 전송하도록 해준다. 리스팅 2-2에서 우리는 NULL을 전달하였기 때문에 포인터가 void였지만, 콜백 함수로 GtkLabel을 전달하는 상황을 가정해보자.


g_signal_connect() 의 예제에서 GtkLabel은 gpointer로서 형변환되어 후에 콜백 함수로 전달될 것이다. gpointer는 그저 void 포인터의 타입 정의에 해당한다. 이는 콜백 함수에서 다시 형변환(recast)할 수 있지만 g_signal_connect()는 gpointer 타입을 필요로 한다.

g_signal_connect (G_OBJECT (window), "destroy",
        G_CALLBACK (destroy),
        (gpointer) label);


g_signal_connect()에 대한 리턴값은 시그널의 핸들러 식별자이다. 이는 g_signal_handler_block(), g_signal_handler_unblock(), g_signal_handler_disconnect()와 함께 사용 가능하다. 이러한 함수들은 각각 콜백 함수가 호출되지 못하도록 막고, 콜백 함수를 재활성화하며, 메모리로부터 시그널 핸들러를 제거한다. 이에 관한 추가 정보는 API 문서에서 찾을 수 있다.


콜백 함수

g_signal_connect()에 명시된 콜백 함수는 그것이 연결된 시그널이 위젯에서 방출되면 호출될 것이다. 다음 절에서 다루게 될 이벤트를 제외한 모든 시그널의 경우 콜백 함수는 아래와 같은 형식으로 되어 있다.

static void
callback_function (GtkWidget *widget,
        ... /* Other Possible Arguments */ ...,
        gpointer data);


각 시그널에 대한 콜백 함수의 포맷 예제는 API 문서에서 찾을 수 있지만 이는 일반적인 포맷이다. 첫 번째 매개변수는 g_signal_connect()에서 발생한 객체지만, 언제나 시그널이 생성된 위젯 타입으로서 형변환되어야 한다는 점에서 차이가 있다. 위젯이 파생된 위젯 타입으로 접근해야 한다면 내장된 형변환 함수를 이용할 수도 있다.


언제나 그런 것은 아니지만 중간에 나타날 수 있는 다른 인자들도 존재한다. 이러한 매개변수들의 경우 개발자가 이용하는 시그널에 관한 문서를 참조해야 한다.


자신의 콜백 함수에서 마지막 매개변수는 g_signal_connect()의 마지막 매개변수에 해당한다. 데이터는 void 포인터로서 전달되기 때문에 자신이 형변환을 원하는 데이터 타입을 콜백 함수의 마지막 매개변수로서 위치시킬 수 있다. 가령 GtkLabel을 g_signal_connect()의 마지막 매개변수로서 전달했다고 가정해보자.

static void
destroy (GtkWidget *window,
        GtkLabel *label)


해당 예제에서 우리는 객체가 GtkLabel 타입이라고 확신했기 때문에 GtkLabel을 콜백 함수의 마지막 매개변수로서 사용했다. 이는 gpointer로부터 객체를 바람직한 데이터 타입으로 다시 형변환(recast)하는 수고를 덜어줄 것이다.


제 11장에서는 커스텀 위젯을 생성하는 방법을 학습하는 동시 자신만의 시그널을 생성하는 방법을 다룰 것이다.


시그널의 방출과 중단

이벤트로 넘어가기 전에 시그널에 관해 알아야 할 흥미로운 함수가 2개 있다. g_signal_emit_by_name()을 이용하면 텍스트명을 이용해 객체에서 시그널을 방출할 수 있다. 시그널 식별자를 이용해 시그널을 방출할 수도 있지만 시그널의 이름으로 접근하게 될 가능성이 훨씬 크다. 시그널 식별자가 있다면 g_signal_emit()를 이용해 시그널을 식별할 수 있다.

void g_signal_emit_by_name (gponter instance,
        const gchar *signal_name,
        ...);


g_signal_emit_by_name()의 마지막 매개변수들은 리턴값을 보관하는 위치와 시그널로 전달되어야 하는 매개변수의 리스트이다. void 함수일 경우 리턴값은 문제 없이 무시할 수 있다.


g_signal_stop_emission_by_name()을 이용하면 현재 방출되고 있는 시그널을 중단하는 것이 가능하다. 이는 코드가 실행하는 어떤 액션으로 인해 방출되는 시그널을 임의적으로 비활성화하도록 해준다.

void g_signal_stop_emission_by_name (gpointer instance,
        const gchar *signal_name);


이벤트

이벤트는 X Windows System에 의해 방출되는 특수 타입의 시그널이다. 이러한 시그널들은 처음에 X Window System에 의해 방출되고, 이후 윈도 관리자로부터 개발자의 애플리케이션으로 전송되어 GLib이 제공하는 시그널 시스템에 의해 해석된다. 예를 들어 destroy 시그널은 위젯에서 방출되지만 delete-event 이벤트는 위젯의 기본이 되는 GdkWindow에서 먼저 인식한 후 위젯의 시그널로서 방출된다.


처음으로 만나게 될 이벤트의 예는 리스팅 2-2의 delete-event이다. delete-event 시그널은 사용자가 창 닫기를 시도할 때 방출된다. 창은 제목 표시줄에 위치한 닫기 버튼을 클릭하거나, 작업표시줄에서 팝업 메뉴 항목을 이용하거나, 윈도 관리자가 제공하는 다른 방법들을 이용해 닫을 수 있다.


이벤트를 콜백 함수로 연결하는 것은 다른 GTK+ 시그널과 마찬가지로 g_signal_connect()의 사용을 통해 이루어진다. 하지만 당신의 콜백 함수는 약간 다른 방식으로 준비할 것이다.

static gboolean
callback_function (GtkWidget *widget,
        GdkEvent *event,
        gpointer data);


콜백 함수에서 첫 번째 차이점은 gboolean 리턴값이다. 이벤트 콜백으로부터 TRUE가 리턴되면 GTK+는 이벤트가 이미 처리되었으며 계속되지 않을 것이라고 가정한다. FALSE를 리턴하면 개발자는 GTK+에게 계속해서 이벤트를 처리하라고 알리는 셈이다. FALSE는 함수에 대한 기본값이므로 대부분의 경우 delete-event 시그널을 사용할 필요가 없다. 이는 기본 시그널 핸들러를 오버라이드하고자 할 경우에만 유용하다.


예를 들어, 많은 애플리케이션에서 당신은 프로그램이 꺼졌는지 확인하길 원할 것이다. 아래 코드를 이용하면 사용자가 애플리케이션을 끄고 싶지 않을 경우 애플리케이션의 꺼짐을 방지할 수 있다.

static gboolean
delete_event (GtkWidget *window,
        GdkEvent *event,
        gpointer data)
{
    gboolean answer = /* Ask the user if exiting is desired. */

    if (answer)
        return FALSE;
    else
        return TRUE;
}


delete-event 콜백 함수에서 FALSE를 리턴하면 gtk_widget_destroy()가 위젯에서 자동으로 호출된다. 앞에서 언급한 바와 같이 이 시그널은 자동으로 액션을 계속할 것이므로, 기본값의 오버라이드를 원치 않는 한 따로 연결할 필요가 없다.


뿐만 아니라 콜백 함수는 GdkEvent 매개변수를 포함한다. GdkEvent는 GdkEventType 목록과 이용 가능한 모든 이벤트 구조에 대한 C 공용체(union)다. 우선 GdkEventType 목록을 살펴보자.


이벤트 타입

GdkEventType 목록은 이용할 수 있는 이벤트 타입의 리스트를 제공한다. 무슨 일이 일어났는지 항상 알 수는 없기 때문에 이러한 이벤트 타입을 이용하면 발생한 이벤트의 타입을 알아낼 수 있다.


가령 button-press-event 시그널을 위젯으로 연결할 경우 시그널의 콜백 함수를 실행시킬 수 있는 이벤트 타입에는 GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, GDK_3BUTTON_PRESS 세 가지가 있다. 이를 두 번 또는 세 번 클릭하면 GDK_BUTTON_PRESS를 두 번째 이벤트로서 방출하므로 이벤트 타입을 구별할 줄 알아야 한다.


부록 B에서는 개발자가 이용 가능한 이벤트의 전체 리스트를 확인할 수 있다. 이는 g_signal_connect()로 전달된 시그널명, GdkEventType 목록 값, 이벤트의 설명을 보여준다.


리스팅 2-2에 실린 delete-event 콜백 함수를 살펴보자. delete-event가 GDK_DELETE 타입이라는 사실을 이미 알고 있지만 잠시만 모른다고 가정하자. 이는 아래 조건문을 이용해 간단히 검사할 수 있다.

static gboolean
delete_event (GtkWidget *window,
        GdkEvent *event,
        gpointer data)
{
    if (event->type == GDK_DELETE)
        return FALSE;

    return TRUE;
}


위의 예제에서 이벤트 타입이 GDK_DELETE라면 FALSE가 리턴되고 gtk_widget_destroy()가 위젯 상에서 호출될 것이다. 그 외의 경우 TRUE가 리턴되고 추가 액션은 취하지 않는다.


특정 이벤트 구조 이용하기

때로는 어떤 이벤트 타입이 방출되었는지 이미 알고 있는 경우가 있다. 아래 예제에서는 key-press-event가 항상 방출될 것을 알고 있다.

g_signal_connect (G_OBJECT (widget), "key-press-event"
        G_CALLBACK (key_press), NULL);


이런 경우 이벤트 타입은 항상 GDK_KEY_PRESS가 될 것이며 콜백 함수도 그와 같이 선언될 수 있다고 가정하는 것이 안정하겠다.

static gboolean
key_press (GtkWidget *widget,
        GdkEventKey *event,
        gpointer data)


이벤트 타입이 GDK_KEY_PRESS라는 사실을 이미 알고 있기 때문에 GdkEvent 내의 모든 구조로 접근할 필요가 없을 것이다. 콜백 함수에서 GdkEvent 를 대신해 사용 가능한 GdkEventKey 만 이용할 수 있을 것이다. 이벤트는 이미 GdkEventKey로서 형변환되었으므로 해당 구조 내의 요소로만 직접 접근이 가능할 것이다.

typedef struct
{
    GdkEventType type; // GDK_KEY_PRESS or GDK_KEY_RELEASE
    GdkWindow *window; // The window that received the event
    gint8 send_event; // TRUE if the event used XSendEvent
    guint32 time; // The length of the event in milliseconds
    guint state; // The state of Control, Shift, and Alt
    guint keyval; // The key that was pressed <gdk/gdkkeysyms.h>
    gint length; // The length of string
    gchar *string; // A string approximating the entered text
    guint16 hardware_keycode; // Raw code of the key that was pressed or released
    guint8 group; // The keyboard group
    guint is_modifier : 1; // Whether hardware_keycode was mapped (since 2.10)
} GdkEventKey;


GdkEventKey 구조에는 유용한 프로퍼티가 많이 존재하며 본 저서에 걸쳐 사용될 것이다. 어느 시점에서는 API 문서에서 GdkEvent 구조를 어느 정도 살펴보는 것이 유용할 것이다. 본문에서는 GdkEventKey와 GdkEventButton을 포함해 가장 중요한 구조를 몇 가지 다룰 것이다.


모든 이벤트 구조에서 이용 가능한 유일한 변수는 이벤트 타입으로서, 이는 발생한 이벤트의 타입을 정의한다. 올바르지 않은 방식으로 처리되는 것을 막기 위해서는 항상 이벤트 타입을 확인하는 것이 좋겠다.


그 외 GTK+ 함수

더 많은 예제를 살펴보기 전에 다른 장들을 학습하거나 자신만의 GTK+ 애플리케이션을 설치할 때 편리하게 사용할 수 있는 함수를 몇 가지 집중해서 살펴보고자 한다.

GtkWidget 함수

GtkWidget 구조는 어떤 위젯과도 사용 가능한 유용한 함수를 많이 포함한다. 이번 절에서는 개발자의 수많은 애플리케이션에서 필요로 하게 될 몇 가지 함수를 간략하게 살펴보겠다.


객체 상에서 gtk_widget_destroy()를 명시적으로 호출하면 위젯을 소멸시키는 것이 가능하다. gtk_widget_destroy()를 호출하면 참조 계수(reference count)를 위젯과 그 모든 자식들에게 재귀적으로 떨어뜨린다(drop on). 따라서 위젯과 그 자식들은 소멸되고, 모든 메모리는 해제된다.

void gtk_widget_destroy (GtkWidget *widget);


일반적으로 위의 함수는 최상위 위젯에서만 호출된다. 보통은 대화창을 소멸시키고, 애플리케이션을 중단하는 메뉴 항목을 구현할 때에만 사용된다. 본 장에서 다음으로 소개할 예제에서는 버튼을 클릭하면 애플리케이션이 중단되도록 사용하였다.


위젯의 최소 크기를 설정 시에는 gtk_widget_set_size_request()를 이용할 수 있다. 이는 위젯을 강제로 정상 크기보다 작거나 크게 만들 것이다. 하지만 기능할 수 없거나 화면에 그려지지 않을 정도로 작게 조정되지는 않을 것이다.

void gtk_widget_set_size_request (GtkWidget *widget,
        gint width,
        gint height);


어떤 매개변수로든 -1를 전달하면 개발자는 GTK+에게 위젯의 본래(natural) 크기를 사용하도록 알린다는 뜻이며, 개발자가 만일 커스텀 크기를 정의하지 않았다면 일반적으로 위젯에 할당되는 크기를 사용하도록 알리는 것이다. 위젯을 본래 크기로 리셋할 수도 있다.


위젯의 높이나 너비는 1 픽셀 미만으로 설정할 수 없지만 매개변수 중 하나로 0을 전달할 경우 GTK+는 위젯을 가능한 한 작게 만들 것이다. 다시 말하지만, 기능을 할 수 없거나 화면에 그려지지 않을 정도로 작게 조정되진 않을 것이다.


국제화로 인해 위젯의 크기를 설정 시 위험이 발생한다. 개발자의 컴퓨터에선 텍스트가 너무 크게 보이는 반면 애플리케이션에 독일어 번역을 사용하는 컴퓨터에서는 텍스트에 비해 위젯이 너무 작거나 크게 나타날 수도 있다. 테마 또한 위젯의 크기 조정과 관련된 문제를 제시하는데, 위젯은 테마에 따라 서로 다른 크기로 기본 설정되기 때문이다. 따라서 대부분의 경우는 GTK+가 창과 위젯의 크기를 선택하도록 허용하는 방법이 최선이겠다.


위젯이 강제로 키보드 포커스를 잡도록 하기 위해서는 gtk_widget_grab_focus()를 이용할 수 있다. 이는 키보드 상호작용을 처리할 수 있는 위젯 상에서만 작동할 것이다. gtk_widget_grab_focus()의 사용 예로, 검색 툴바가 Firefox에 나타날 때 텍스트 엔트리로 커서를 전송하는 경우를 들 수 있다. 선택 가능한 GtkLabel에 포커스를 주는 데에도 사용될 수 있겠다.

void gtk_widget_grab_focus (GtkWidget *widget);


종종 우리는 위젯을 비활성화(inactive)로 설정하길 원할 것이다. gtk_widget_set_sensitive()를 호출하면 명시된 위젯과 그 자식들이 비활성화/활성화된다. 위젯을 비활성화로 설정하면 사용자는 위젯과 상호작용할 수 없을 것이다. 대부분의 위젯은 비활성화로 설정 시 회색으로 표시될 것이다.

void gtk_widget_set_sensitive (GtkWidget *widget,
        gboolean sensitive);


위젯과 그 자식들을 재활성화시키고 싶다면 동일한 위젯 상에서 해당 함수를 호출하기만 하면 된다. 자식들은 그 부모 위젯의 감도(sensitivity)의 영향을 받지만 자신들의 프로퍼티를 변경하는 대신 부모의 설정을 반영할 뿐이다.


GtkWindow 함수

GtkWindow 구조의 두 가지 사용 예를 살펴보았다. 그리고 창의 안쪽 테두리와 창의 자식 사이에 테두리 패딩(border padding)을 추가하는 방법도 학습하였다. 뿐만 아니라 창의 제목을 설정하고 자식 위젯을 추가하는 방법도 배웠다. 이제 창의 맞춤설정(customize)을 가능하게 하는 함수를 몇 가지 살펴보도록 한다.


모든 창은 기본적으로 크기 조정이 가능하다. 대부분의 애플리케이션에서는 바람직한 일인데, 각 사용자가 선호하는 크기가 다를 것이기 때문이다. 하지만 특정한 이유가 있다면 사용자가 창의 크기를 조정하지 못하도록 gtk_window_set_resizable()을 이용할 수 있겠다.

void gtk_window_set_resizable (GtkWindow *window,
        gboolean resizable);


Gtkd caution.png 크기 조정의 기능은 윈도 관리자가 제어하므로 모든 경우에서 환영받는 일은 아니라는 사실을 주목해야 한다.


바로 위에 실린 주의사항은 요점을 상기시킨다. GTK+가 하는 일은 대부분 윈도 관리자가 제공한 기능과 상호작용하는 일이다. 이 때문에 윈도우 설정이라고 해서 모든 윈도 관리자에서 따르리라는 법은 없다. 개발자의 설정은 힌트에 불과하므로 이후에 사용되거나 무시되거나 둘 중 하나에 해당하기 때문이다. GTK+로 애플리케이션을 개발 시에는 개발자의 요청이 수락되기도, 거부되기도 한다는 점을 유념해야 한다.


GtkWindow의 기본 크기는 gtk_window_set_default_size()로 설정 가능하지만 해당 함수를 이용 시에는 조심해야 할 점이 몇 가지 있다. 창의 최소 크기가 당신이 명시한 크기보다 큰 경우 GTK+는 해당 함수를 무시할 것이다. 개발자가 이전에 더 큰 크기의 요청을 설정한 경우에도 무시될 것이다.

void gtk_window_set_default_size (GtkWindow *window,
        gint width,
        gint height);


gtk_widget_set_size_request()와 달리 gtk_window_set_default_size()는 창의 처음 크기만 설정할 뿐, 사용자가 크기를 더 크게 또는 작게 조정할 수는 없게 만든다. 높이 또는 너비 매개변수를 0으로 설정할 경우 창의 높이나 너비가 가능한 최소 크기로 설정될 것이다. 매개변수 중 하나로 -1을 전달할 경우 창은 본래 크기로 설정될 것이다.


gtk_window_move()를 이용하면 윈도 관리자에게 창을 명시된 위치로 이동시킬 것을 "요청"(request)할 수 있다. 하지만 윈도 관리자는 이러한 요청을 무시하기도 한다. 윈도 관리자로부터 액션을 요하는 "요청(request)" 함수라면 모두 마찬가지다.

void gtk_window_move (GtkWindow *window,
        gint x,
        gint y);


화면에서 창의 크기는 기본적으로 화면의 좌측 상단 모서리를 기준으로 계산되지만 gtk_window_set_gravity()를 이용하면 기준을 변경할 수 있다.

void gtk_window_set_gravity (GtkWindow *window,
        GdkGravity gravity);


해당 함수는 위젯의 gravity를 정의하는데 이는 레이아웃 계산이 (0,0)을 고려하게 될 것이라는 의미다. GdkGravity 목록에 가능한 값으로는 GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH, GDK_GRAVITY_NORTH_EAST, GDK_GRAVITY_WEST, GDK_GRAVITY_CENTER, GDK_GRAVITY_EAST, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_SOUTH, GDK_GRAVITY_SOUTH_EAST, GDK_GRAVITY_STATIC가 있다.


North, south, east, west는 화면에서 상단, 하단, 우측, 좌측의 변(edge)을 나타낸다. 이들은 다수의 gravity 타입을 구성하는 데에 사용된다. GDK_GRAVITY_STATIC은 창 자체의 좌측 상단 모서리를 나타내며 창 decorations는 무시한다.


애플리케이션이 하나 이상의 창을 가진 경우 gtk_window_set_transient_for()를 이용해 하나의 창을 부모로 설정할 수 있다. 이로 인해 윈도 관리자는 부모 위젯 위에 자식을 중앙 정렬하거나 하나의 특정 창을 다른 창들보다 항상 위에 표시하게 만들 수 있다. 다중 창이나 일시적(transient) 관계에 대한 개념은 대화상자를 논하면서 제 5장에서 살펴볼 것이다.

void gtk_window_set_transient_for (GtkWindow *window,
        GtkWindow *parent);


창의 작업 표시줄이나 제목 표시줄에 표시될 아이콘은 gtk_window_set_icon_from_file()을 이용해 설정할 수 있다. 아이콘의 크기는 중요하지 않은데, 알맞은 크기가 알려지면 조정될 것이기 때문이다. 그래야만 최상의 질로 크기가 조정된 아이콘이 가능해진다.

gboolean gtk_window_set_icon_from_file (GtkWindow *window,
        const gchar *filename,
        GError **err); // NULL


아이콘이 성공적으로 로딩되어 설정되면 TRUE가 리턴된다. 따라서 아이콘 로딩이 왜 실패했는지에 대한 세부적인 정보를 원하지 않는 한 현재로선 NULL을 세 번째 매개변수로 전달하는 편이 안전하다. GError 구조는 제 4장에서 논할 것이다.


보류 이벤트 처리

이따금씩 애플리케이션에서 모든 보류(pending) 이벤트를 처리하길 원할 때가 있다. 이는 처리에 오랜 시간이 소요되는 코드 조각을 실행 중인 경우 매우 유용하다. 애플리케이션이 멈춘(freeze) 것처럼 보일 것인데, 다른 프로세스가 CPU를 차지하는 경우 위젯이 그려지지 않기 때문이다. 가령 필자가 생성한 통합 개발 환경인 OpenLDev에서는 build 명령이 처리되는 동안 필자가 사용자 인터페이스를 업데이트해야 한다. 그렇지 않으면 창이 잠기면서 build가 완료될 때까진 어떤 build 출력도 표시되지 않을 것이다.


아래 루프는 이러한 문제에 대한 해답이 되는데, 신규 GTK+ 프로그래머들이 제기할 법한 수많은 질문에 해답이 되기도 한다.

while (gtk_events_pending ())
    gtk_main_iteration ();


루프는 gtk_main_iteration()을 호출하는데 이는 자신의 애플리케이션에 대한 첫 번째 보류 이벤트를 처리할 것이다. 이는 gtk_events_pending()이 TRUE를 리턴하여, 처리되어야 할 이벤트가 기다리고 있는지 알려주는 동안에도 계속된다.


루프는 freezing 문제를 해결하는 데에 쉬운 해답이 되지만 문제를 피할 수 있는 코딩 전략들을 사용하는 편이 더 나을 것이다. 예를 들어, 처리가 필요한 더 중요한 액션이 없을 때에만 함수를 호출하기 위해서는 idle 함수를 사용할 수 있는데, 이는 제 6장에서 다룰 것이다.


버튼

GtkButton 위젯은 특별한 타입의 컨테이너로서 그 자식을 클릭 가능한 개체로 바꾼다. 해당 위젯은 하나의 자식만 보유할 수 있다. 하지만 자식 위젯은 자체가 컨테이너가 될 수 있으므로 버튼은 이론적으로 수많은 자식을 가진 조상(ancestor)이 될 수 있다. 이는 버튼이 라벨과 이미지를 동시에 보유하도록 허용한다.


GtkButton 위젯의 목적은 자식을 클릭 가능하게 만드는 것이기 때문에 버튼이 활성화되었다는 알림을 받으려면 항상 clicked 시그널을 사용해야 한다. 바로 다음 예제에서 해당 시그널을 사용해보겠다.


GtkButton 위젯은 GtkLabel을 이용해 새 버튼을 자식으로서 생성하는 gtk_button_new_with_label()을 이용해 주로 초기화된다. 빈 GtkButton를 생성하고 후에 자신만의 자식을 추가하고 싶다면 gtk_button_new()를 사용할 수 있지만 대부분의 경우 이것을 사용하고 싶지 않을 것이다.


그림 2-4는 연상기호 기능을 가진 버튼을 보여준다. 연상기호 라벨은 밑줄이 쳐진 문자로 인식할 수 있다. 아래 버튼의 경우 Alt+C를 누르면 버튼이 클릭될 것이다.

그림 2-4. 연상기호 라벨이 있는 GtkButton 위젯


gtk_button_new_with_mnemonic() 함수는 연상기호 라벨을 지워하는 새 버튼을 초기화할 것이다. 사용자가 명시된 문자 키와 함께 Alt 키를 누르면 버튼이 활성화될 것이다. 가속기(accelerator)는 사전 정의된 액션을 활성화하는 데에 사용할 수 있는 키 또는 키의 집합에 해당한다.


Gtkd note.png 일정 타입의 사용자 인터페이스를 제공하는 위젯에서 연상기호 옵션이 이용 가능하다면 그러한 기능을 활용할 것을 권한다. 키보드 단축키가 없더라도 일부 사용자는 마우스 대신 키보드를 사용해 사용자 인터페이스를 살펴보는 편을 선호한다.


리스팅 2-3은 clicked 시그널을 이용하는 GtkButton의 단순한 예제다. 버튼을 누르면 창이 소멸되고 애플리케이션이 중단될 것이다. 해당 예제의 버튼도 연상기호와 키보드 가속기 기능을 이용한다. 이 예제의 스크린샷은 그림 2-4에 실려 있다.


리스팅 2-3. GtkButton 위젯 (buttons.c)

#include <gtk/gtk.h>

static void destroy (GtkWidget*, gpointer);

int main (int argc,
        char *argv[])
{
    GtkWidget *window, *button;

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Buttons");
    gtk_container_set_border_width (GTK_CONTAINER (window), 25);
    gtk_widget_set_size_request (window, 200, 100);

    g_signal_connect (G_OBJECT (window), "destroy",
        G_CALLBACK (destroy), NULL);

    /* Create a new button that has a mnemonic key of Alt+C. */
    button = gtk_button_new_with_mnemonic ("_Close");
    gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);

    /* Connect the button to the clicked signal. The callback function recieves the
    * window followed by the button because the arguments are swapped. */
    g_signal_connect_swapped (G_OBJECT (button), "clicked",
        G_CALLBACK (gtk_widget_destroy),
        (gpointer) window);

    gtk_container_add (GTK_CONTAINER (window), button);
    gtk_widget_show_all (window);

    gtk_main ();
    return 0;
}
/* Stop the GTK+ main loop function. */
static void
destroy (GtkWidget *window,
        gpointer data)
{
    gtk_main_quit ();
}


리스팅 2-3에서 버튼이 클릭되면 메인 창에서 gtk_widget_destroy()가 호출된다. 이는 매우 간단한 예제지만 대부분의 애플리케이션에서 유용하게 사용된다. GNOME Human Interface Guidelines는 htt://developer.gnome.org/projects/gup/hig 에서 확인하고 다운로드할 수 있는데, 개인설정 대화상자는 설정이 변경된 직후 설정을 적용시켜야 한다고 명시된다.


따라서 개인설정 대화상자를 설정하면 하나의 버튼만 필요할 가능성이 높다. 버튼을 포함하고 변경내용을 저장하는 창을 소멸시키는 것이 버튼의 목적일 것이다.


버튼을 생성하고 나면 gkt_button_set_relief()를 이용해 GtkButton 주위에 특정 양감(relief) 정도를 추가할 수 있다. 양감은 주위 위젯으로부터 버튼을 구별시키는 3-D 타입의 테두리다. GtkReliefStyle 목록 값은 아래를 준수한다.

  • GTK_RELIEF_NORMAL: 버튼의 모든 변 주위에 양감을 추가한다.
  • GTK_RELIEF_HALF: 버튼의 절반에만 양감을 추가한다.
  • GTK_RELIEF_NONE: 버튼 주위에 양감을 추가하지 않는다.


리스팅 2-3은 새로운 시그널 연결 함수인 g_signal_connect_swapped()를 소개한다. 해당 함수는 콜백 함수가 실행될 때 데이터 매개변수와 시그널이 방출되는 객체의 위치를 바꾼다(swap).

g_signal_connect_swapped (G_OBJECT (button), "clicked",
        G_CALLBACK (gtk_widget_destroy),
        (gpointer) window);


이는 콜백 함수에서 gtk_widget_destroy()의 사용을 허용하여 gtk_widget_destroy (window)를 호출할 것이다. 콜백 함수가 하나의 매개변수만 수신한다면 객체는 무시될 것이다.


위젯 프로퍼티

GObject는 프로퍼티 시스템을 제공하여 위젯이 그 사용자와 어떻게 상호작용하는지와 화면에 어떻게 그려지는지를 맞춤설정하도록 해준다. 이번 절에서는 스타일, 리소스 파일, GObject의 프로퍼티 시스템을 사용하는 방법을 학습할 것이다.


GObject 클래스로부터 파생된 모든 클래스는 원하는 수만큼 프로퍼티를 설치할 수 있다. GTK+에서 이러한 프로퍼티는 위젯이 어떻게 행동할 것인지에 관한 정보를 저장한다. 가령 GtkButton은 버튼이 사용하는 양감 스타일을 정의하는 relief라는 프로퍼티를 갖고 있다.


아래 코드에서는 g_object_get()을 이용해 버튼의 relief 프로퍼티가 보관한 현재 값을 검색한다. 해당 함수는 NULL로 종료되는 프로퍼티와 변수의 리스트를 수락하여 리턴 값을 보관한다.

g_object_get (button, "relief", &value, NULL);


각 객체에는 수많은 프로퍼티가 있기 때문에 본문에 전체 리스트를 싣지는 않을 것이다. 특정 위젯에 이용 가능한 프로퍼티에 관한 정보는 API 문서를 참조해야 한다.


위젯 프로퍼티 설정하기

g_object_set()를 이용하면 프로퍼티에 대한 새 값을 쉽게 설정할 수 있다. 예를 들어, 버튼의 relief 프로퍼티는 GTK_RELIEF_NORMAL로 설정되었다.

g_object_set (button, "relief", GTK_RELIEF_NORMAL, NULL);


각 위젯의 많은 프로퍼티를 설정 및 검색하도록 함수가 제공된다. 하지만 모든 프로퍼티가 그러한 선택을 할 수 있는 것은 아니다. 이러한 함수들은 제 8장에서 GtkTreeView 위젯을 학습할 때 매우 유용해지는데, 그 장에서 사용되는 많은 객체들은 어떤 프로퍼티에 대해서도 get 또는 set 함수를 제공하지 않기 때문이다.


GObject의 notify 시그널을 이용해 특정 프로퍼티를 감시하는 수도 있다. 프로퍼티를 notify::property-name 시그널로 연결하면 감시가 가능하다. 리스팅 2-4의 예제는 relief 프로퍼티가 변경되면 property_changed()를 호출한다.


리스팅 2-4. Notify 프로퍼티 사용하기

g_signal_connect (G_OBJECT (button), "notify::relief",
        G_CALLBACK (property_changed), NULL);

...

static void
property_changed (GObject *button,
        GParamSpec *property,
        gpointer data)
{
    /* Handle the property change ... */
}


Gtkd caution.png 시그널명을 입력 시에는 대시나 밑줄의 사용이 모두 허용되지만 notify 시그널을 사용 시에는 항상 대시를 사용해야 한다. 가령, GtkWidget의 can-focus 프로퍼티를 감시하고 싶다면 notify::can_focus 는 허용되지 않는다는 말이다! notify는 시그널명이므로 can-focus는 위젯 프로퍼티명이다.


콜백 함수는 GParamSpec이라는 새로운 타입의 객체를 수신하는데, 이것은 프로퍼티에서 무엇이 변경되었는지에 대한 정보를 보유한다. 현재로서는 property->name을 이용해 변경된 프로퍼티명을 검색할 수 있다는 사실만 알면 된다. GParamSpec 구조에 관해서는 제 11장에서 자신만의 커스텀 위젯에 프로퍼티를 추가하는 방법을 학습할 때 더 자세히 알아볼 것이다.


모든 GObject는 프로퍼티 시스템에 더해 문자열 리스트를 포인터 리스트와 연관시키는 테이블을 갖고 있다. 이는 쉽게 접근할 수 있는 데이터를 객체로 추가할 수 있도록 해주므로 시그널 핸들러에게 추가 정보를 전달해야 하는 경우 특히 유용하다. 객체에 새 데이터 필드를 추가하려면 g_object_set_data()를 호출하기만 하면 된다. 해당 함수는 data를 가리키는 데에 사용될 유일한 문자열을 수락한다. 동일한 키 이름으로 된 연관(association)이 이미 존재하는 경우, 새 데이터가 기존 데이터를 대체할 것이다.

void g_object_set_data (GObject *object,
        const gchar *key,
        gpointer data);


데이터로 접근해야 한다면 key와 연관된 포인터를 리턴하는 g_object_get_data()를 호출할 수 있다. g_signal_connect()를 이용해 임의의 데이터 조각을 전달하는 대신 위의 데이터 전달 방법을 사용해야 한다.


자신의 이해도 시험하기

제 2장에서는 창과 라벨 위젯에 관해 학습하였다. 이제 지식을 실제로 사용해볼 때가 되었다. 아래 두 개의 연습문제를 통해 당신은 GTK+ 애플리케이션의 구조, 시그널, GObject 프로퍼티 시스템에 대한 본인의 지식을 활용할 것이다.


연습문제 2-1. 이벤트와 프로퍼티 사용하기

이 연습문제는 본 장의 첫 부분에 실린 두 예제를 확장시켜 스스로를 소멸시키는 능력이 있는 GtkWindow를 생성할 것이다. 우선 개발자의 이름을 창의 제목으로 설정해야 한다. 그리고 개발자의 성을 기본 텍스트 문자열로 가진, 선택 가능한 GtkLabel 을 창의 자식으로 추가해야 한다.


그 외에 창의 크기가 조정이 불가하고, 최소 크기는 300 x 100 픽셀이어야 한다는 특성이 있다. 이러한 작업을 실행하기 위한 함수는 본 장의 내용에서 찾아볼 수 있다.


다음으로, API 문서를 살펴보고 창에 key-press-event 시그널을 연결하라. key-press-event 콜백 함수에서 창 제목과 라벨 텍스트를 바꿔본다. 예를 들어, 콜백 함수를 처음으로 호출하면 창 제목은 당신의 성으로 설정되고 라벨은 당신의 이름으로 설정되어야 한다.


아래 함수가 도움이 될 것이다.

gint g_ascii_strcasecmp (const gchar *str1, const gchar *str2);


g_ascii_strcasesmp() 에서 두 개의 문자열이 동일하면 0이 리턴된다. str1이 str2보다 작으면 음수가 리턴된다. 그 외의 경우 양수가 리턴된다.


연습문제 2-1을 완료했다면 부록 F에서 해답에 대한 설명을 찾을 수 있으며, www.gtkbook.com에서 해답의 전체 소스 코드를 다운로드할 수 있다.


연습문제 2-2. GObject 프로퍼티 시스템

이번에는 연습문제 2-1을 확장시키되 창의 제목, 높이, 너비는 GObject에서 제공하는 함수를 이용해 설정할 것이다. 또 콜백 함수 내에서 창 제목과 라벨 텍스트를 수반하는 모든 연산은 GObject가 제공하는 함수를 이용해 실행되어야 한다. 게다가 notify 시그널을 이용해 창의 제목을 감시해야 한다. 제목이 변경되면 terminal 출력을 통해 사용자에게 통지해야 한다.


힌트: GLib가 제공하는 함수 g_message()를 이용해 terminal로 메시지를 출력할 수 있다. 해당 함수는 printf()가 지원하는 것과 동일한 포맷팅을 준수한다.


두 연습문제를 완료했다면 이제 다음 장으로 넘어가 컨테이너 위젯을 다룰 준비가 되었다. 컨테이너 위젯은 메인 창이 하나 이상의 위젯을 포함하도록 허용하는데, 이번 장에 실린 모든 예제가 이에 해당한다.


하지만 다음으로 넘어가기 전에 GTK+로 개발하기의 내용을 보완할 수 있는 www.gtkbook.com에 대해 알아야겠다. 해당 웹 사이트는 다운로드, 추가 GTK+ 정보의 링크, C refresher 지침서, API 문서 등을 포함한다. 본문을 읽으면서 웹 사이트를 참고한다면 GTK+를 학습 시 도움이 될 것이다.


요약

이번 장에서는 가장 기본적인 GTK+ 위젯과 애플리케이션에 대해 학습하였다. 첫 번째 애플리케이션은 "Hello World" 예제로, 모든 GTK+ 애플리케이션이 필요로 하는 기본 호출을 보였는데 이러한 호출은 다음과 같다.

  • gtk_init()를 이용해 GTK+ 초기화하기
  • 최상위 수준의 GtkWindow 생성하기
  • GtkWindow 표시하기
  • gtk_main()을 이용해 메인 루프로 이동하기


두 번째 예제에서는 GTK+ 애플리케이션 내에서 시그널, 이벤트, 콜백 함수의 목적에 대해 배웠다. GtkContainer 구조는 GtkWindow와 연관된 것으로 소개되었다. 또 GObject 라이브러리에 의해 구현되는 위젯 계층구조 시스템의 목적을 살펴보았다.


다음으로 GtkWidget, GtkWindow, GtkLabel에 연관된 유용한 함수들을 살펴보았다. 그 중 다수는 본 저서를 통해 사용될 것이다. 두 연습문제에서 사실 몇 가지 함수들을 실제로 사용해야 했을 것이다.


또 마지막 예제를 통해서는 GtkButton 위젯을 소개하였다. GtkButton은 자식 위젯을 클릭 가능한 버튼으로 만드는 컨테이너 타입이다. 이를 이용해 라벨, 연상기호, 또는 임의의 위젯을 표시할 수 있다. 버튼은 제 4장에서 더 상세히 다루겠다.


다음 장에서는 GtkContainer 구조에 대해, 그리고 이를 마음대로 광범위한 컨테이너 위젯 집합에 관계시키는 방법을 학습할 것이다.


Notes