FoundationsofGTKDevelopment:Chapter 03

From 흡혈양파의 번역工房
Jump to navigation Jump to search
제 3 장 컨테이너 위젯

컨테이너 위젯

제 2장을 통해 개발자가 생성하는 GTK+ 애플리케이션마다 필요로 하는 기본적인 것들을 제시하였다. 그 외에 시그널, 이벤트, 콜백 함수, GtkLabel 위젯, GtkButton 위젯, GtkContainer 클래스에 대한 개념도 소개하였다.


이번 장에서는 컨테이너 위젯의 두 가지 타입을 다룰 것인데, decorator와 레이아웃 컨테이너가 그것이다. 그 다음으로 박스, 노트북, 핸들 박스, 이벤트 박스를 포함해 많은 중요한 컨테이너 위젯에 대한 지식을 습득할 것이다.


마지막으로 다루게 될 위젯인 GtkEventBox가 없다면 위젯은 GDK 이벤트를 활용할 수 없을 것이다.


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

  • GtkContainer 클래스와 그 자식 클래스(descendents)의 목적
  • 박스, 패인(pane), 테이블을 포함해 레이아웃 컨테이너를 사용하는 방법
  • 고정 컨테이너의 사용 시 장·단점
  • 여러 페이지로 된 노트북 컨테이너를 생성하는 방법
  • 이벤트 박스를 이용해 모든 위젯에게 이벤트를 제공하는 방법


GtkContainer

GtkContainer 클래스는 앞의 절에서 간략하게 다룬 바 있지만 심도 있게 살펴보기 위해서는 우선 숙련된 GTK+ 개발자가 되어야 한다. 따라서 이번 절에서는 해당 추상 클래스의 모든 중요한 측면들을 다룬다.


컨테이너 클래스의 주요 목적은 부모 위젯이 하나 또는 그 이상의 자식들을 포함하도록 허용하는 데에 있다. GTK+에는 두 가지 타입의 컨테이너 위젯이 있는데, 하나는 자식들과 decorator들을 배치하는 데에 사용되는 것이고 나머지 하나는 위치지정을 넘어서 특정 종류의 기능까지 자식에게 추가하는 것이다.


Decorator 컨테이너

제 2장에서는 GtkBin에서 파생된 위젯 GtkWindow를 소개하였다. GtkBin은 하나의 자식 위젯만 보유하는 기능을 가진 컨테이너 클래스 타입이다. 해당 클래스에서 파생된 위젯을 decorator 컨테이너라고 부르는데, 자식 위젯에게 특정 타입의 기능을 추가하기 때문이다.


가령 GtkWindow는 최상위 수준의 위젯에 위치시킬 수 있는 추가 기능을 자식 위젯에게 제공한다. decorator의 예제로는 자식 위젯의 주변에 프레임을 그리는 GtkFrame 위젯, 자식을 클릭 가능한 버튼으로 만드는 GtkButton, 사용자에게 자식을 숨기고 표시하는 GtkExpander가 있다. 이러한 위젯들은 모두 자식 위젯을 추가할 때 gtk_container_add()를 사용한다.


GtkBin은 gtk_bin_get_child()라는 하나의 함수만 제공하는데, 이것은 컨테이너의 자식 위젯에 대한 포인터를 검색하도록 해준다. GtkBin 클래스의 실제 목적은 하나의 자식 위젯만 요구하는 모든 서브클래스들이 파생될 수 있는 실체화 가능한(instantiable) 위젯을 제공한다. 이것은 공통 기반에 사용되는 중심(central) 클래스다.

GtkWidget* gtk_bin_get_child (GtkBin *bin);


GtkBin으로부터 파생된 위젯으로는 창(windows), 정렬(alignments), 프레임, 버튼, 항목, 콤보 박스, 이벤트 박스, 익스펜더(expanders), 핸들 박스, 스크롤을 이용한 창, 툴 항목이 있다. 이러한 컨테이너들 중 다수는 이번 장과 이후 여러 장에서 다룰 것이다.


레이아웃 컨테이너

GTK+가 제공하는 또 다른 컨테이너 위젯 타입은 레이아웃 컨테이너라고 불린다. 이는 다수의 위젯을 배열하는 데에 사용된다. 레이아웃 컨테이너는 GtkContainer에서 직접 파생된다는 사실을 특징으로 한다.


이름에서 짐작할 수 있듯이 레이아웃 컨테이너는 사용자의 개인설정, 개발자의 명령어, 내장된 규칙에 따라 그 자식들을 올바로 배열하는 데에 목적이 있다. 사용자 개인설정에는 테마 및 글꼴 개인설정의 사용도 포함된다. 이러한 값을 오버라이드할 수도 있지만 대부분의 경우 사용자의 개인설정을 존중하도록 한다. 모든 컨테이너 위젯에 적용되는 크기조정(resizing) 규칙도 있는데, 이는 다음 절에서 다루겠다.


레이아웃 컨테이너로는 박스, 고정 컨테이너, 패인(paned) 위젯, 아이콘 뷰, 레이아웃, 메뉴 셸(menu shell), 노트북, 소켓, 테이블, 텍스트 뷰, 툴바, 트리 뷰가 있다. 이들 중 대부분은 이번 장을 통해, 나머지는 다른 장에서 다룰 것이다. 본문에서 찾을 수 없는 정보는 API 문서를 참고하면 될 것이다.


자식 위젯의 크기조정

컨테이너는 자식 위젯을 배열하고 꾸미는 것 외에 자식 위젯의 크기도 조정해야 한다. 크기조정은 두 가지 단계로 진행되는데, size requisition과 size allocation이 그것이다. 짧게 말하자면, 두 가지 단계는 위젯에 이용 가능한 크기를 협상한다. 따라서 이는 위젯과 그 부모들과 자식들 간에 이루어지는 재귀적 통신 과정으로 볼 수 있겠다.


Size requisition은 자식 위젯의 바람직한 크기를 의미한다. 프로세스는 최상위 수준 위젯에서 시작하고, 자식 위젯들에게 선호하는 크기를 질문한다. 자식들은 또 그들의 자식들에게 질문하는 식으로 마지막 자식들로 도달할 때까지 지속된다.


이 시점에서 마지막 자식은 화면에 올바로 표시되기 위해 필요한 공간과 프로그래머가 요청한 크기를 기반으로 자신이 원하는 크기를 결정한다. 예를 들어 GtkLabel 위젯은 그 텍스트를 화면에 완전히 표시하기에 충분한 공간을 요청하고, 개발자가 만일 그보다 더 큰 크기를 요청하였다면 더 많은 공간을 요청할 것이다.


이후 자식 위젯은 조상 위젯들에게 그 크기를 전달하는데 이는 최상위 수준의 위젯이 자식의 requisition을 바탕으로 필요한 공간을 수신할 때까지 지속된다.

typedef struct
{
    gint width;
    gint height;
} GtkRequisition;


각 위젯은 GtkRequisition 객체에 너비와 높이 값으로서 자신의 크기 개인설정(size preferences)을 보관한다. requisition은 요청에 불과하므로 부모 위젯은 이 값은 굳이 이행할 필요가 없음을 명심한다.


최상위 수준 위젯이 원하는 공간의 양을 스스로 결정하면 size allocation이 시작된다. 최상위 수준 위젯을 nonresizable(크기조정 불가)로 설정했다면 위젯의 크기는 절대로 조정되지 않을 것이며, 어떤 액션도 발생하지 않고 requisition은 무시될 것이다. 그 외의 경우는 최상위 수준 위젯이 스스로 바람직한 크기로 조정할 것이다. 이후 이용 가능한 공간을 자식 위젯으로 전달할 것이다. 이러한 프로세스는 모든 위젯이 스스로 크기를 조정할 때까지 반복된다.

typedef struct
{
    gint x;
    gint y;
    gint width;
    gint height;
} GtkAllocation;


모든 위젯에 대한 size allocation은 각 자식 위젯마다 GtkAllocation 구조의 하나의 인스턴스에 보관된다. 이러한 구조는 gtk_widget_size_allocate()를 이용해 크기조정을 위해 자식 위젯으로 전달된다. 해당 함수는 프로그래머에 의해 직접 호출되기도 하지만 대부분의 경우는 권하지 않는다.


대개의 경우 자식 위젯에게 자신들이 요청한 공간이 제공되지만 그렇지 않은 특정 상황들이 있다. 가령 최상위 수준의 위젯의 크기 조정이 불가한 경우 requisition은 고려되지 않을 것이다.


반대로 위젯이 부모에 의해 크기가 할당된 경우 위젯은 새로운 크기의 자신을 다시 그리는 수 밖에 없다. 따라서 gtk_widget_size_allocate()를 호출할 때는 주의해야 한다. 대부분의 경우 위젯의 크기조정 시 gtk_widget_set_size_request()가 최선의 선택이다.


컨테이너 시그널

GtkContainer 클래스는 현재 네 가지 시그널, add, check_resize, remove, set_focus_child를 제공한다.

  • add: 자식 위젯이 컨테이너로 추가 또는 패킹된다. 당신이 gtk_container_add()를 명시적으로 호출하지 않고 위젯의 내장된 패키징 함수를 사용하더라도 해당 시그널은 방출될 것이다.
  • check_resize: 추가 액션을 취하기 전에 컨테이너가 자식 위젯의 크기조정이 필요한지 검사한다.
  • remove: 컨테이너로부터 자식 위젯이 제거된다.
  • set_focus_child: 컨테이너의 자식 위젯이 윈도 관리자로부터 포커스를 받는다.


이제 GtkContainer 클래스의 목적을 알게 되었으니 컨테이너 위젯의 다른 타입으로 넘어갈 것이다. 창, 특히 GtkBin 위젯의 타입에 관해 이미 학습한 바 있으니 GtkBox라 불리는 레이아웃 컨테이너를 살펴볼 것이다.


수평 및 수직 박스

GtkBox는 1차원의 직사각형 영역에 다수의 자식 위젯을 넣을 수 있도록 해주는 추상 컨테이너 위젯이다. 박스에는 두 가지 유형이 있는데, GtkVBox는 자식 위젯을 하나의 열에 패킹하고, GtkHBox는 하나의 행에 패킹한다.


Gtkd note.png 본 서적의 나머지 부분에 실린코드 리스팅은 각 절에서 중요한 텍스트의 부분만 포함할 것이다. 따라서 전체 예제를 확인하기 위해서는 소스 코드를 다운로드 해야 할 것이다. 가령, destroy 콜백 함수는 지금쯤이면 사용법을 익혔을 것이기 때문에 이후 예제에서는 포함하지 않을 것이다. 하지만 www.gtkbook.com에서 그 소스 코드를 다운로드할 수 있다.


리스팅 3-1. 기본 패키징의 수직 박스 (boxes.c)

#include <gtk/gtk.h>

#define NUM_NAMES 4
const gchar* names[] = { "Andrew", "Joe", "Samantha", "Jonathan" };

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

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Boxes");
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    gtk_widget_set_size_request (window, 200, -1);

    vbox = gtk_vbox_new (TRUE, 5);

    /* Add four buttons to the vertical box. */
    for (i = 0; < NUM_NAMES; i++)
    {
        GtkWidget *button = gtk_button_new_with_label (names[i]);
        gtk_box_pack_start_defaults (GTK_BOX (vbox), button);

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

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

    gtk_main ();
    return 0;
}


리스팅 3-1은 GtkBox 위젯의 간단한 모습이다. 애플리케이션의 그래픽 출력은 그림 3-1에 표시되어 있다. 이름의 시작 위치는 다르게 패킹되었으나 배열에 추가된 순서대로 표시되었음을 명심한다.

그림 3-1. 시작 위치부터 패킹된 수직 박스


리스팅 3-1을 분석해보면 GtkVBow와 GtkHBox 위젯이 동일한 함수 집합을 사용함을 눈치챌 것인데, 이는 둘 다 GtkBox 클래스로부터 파생되기 때문이다. 각 함수의 매개변수는 동일하지만, 수직 박스는 gtk_vbox_new()를 이용해 생성되고, 수평 박스는 gtk_hbox_new()를 이용해 생성된다는 점이 유일하게 다르다.


어떤 위젯에서든 마찬가지로 객체를 사용하기 전에 GtkVBox를 초기화할 필요가 있다. gtk_vbox_new()에서 첫 번째 매개변수는 박스 내 모든 자식이 동질적인지(homogeneous) 여부를 나타낸다. TRUE로 설정되면 모든 위젯에 들어맞는 최소의 공간이 모든 자식 위젯에게 주어질 것이다.

GtkWidget* gtk_vbox_new (gboolean homogeneous,
        gint spacing);


두 번째 매개변수는 각 자식 위젯과 그 주변 위젯 간 공간의 기본 픽셀 값을 명시한다. 박스가 동일한 간격으로 설정되지 않을 경우 자식 위젯이 추가되면서 각 셀마다 해당 값이 변경될 수 있다.


리스팅 3-1에서 GtkBox 위젯으로 우선 라벨이 추가되고 나면 더 이상 라벨로 접근할 필요가 없으므로 애플리케이션은 객체마다 별도의 포인터를 보관하지 않는다. 부모 위젯이 소멸되면 자동으로 제거될 것이다. 이후 각 버튼은 패킹(packing)이라 불리는 방법을 이용해 박스로 추가된다.


gtk_box_pack_start_defaults()를 이용해 박스로 위젯을 추가함으로써 자식 위젯은 자동으로 설정된 세 개의 프로퍼티를 가지는데, Expanding은 TRUE로 설정되어 박스로 할당된 추가 공간을 셀(cell)에 자동으로 제공할 것이다. 이러한 공간은 공간을 요청한 모든 셀에게 균등하게 배분된다. fill 프로퍼티도 TRUE로 설정되는데, 이는 위젯이 패딩(padding)을 이용해 채우는 대신 제공된 추가 공간으로 확장될 것을 의미한다. 마지막으로 셀과 그 주변 위젯 사이에 위치한 패딩의 양은 0 픽셀로 설정된다.

void gtk_box_pack_start_defaults (GtkBox *box,
        GtkWidget *widget);


패킹 박스는 함수의 명명 규칙 때문에 이해하기가 약간 힘들다. 손쉽게 이해하려면 패킹이 시작되는 장소와 관련시키는 방법을 이용한다. 시작 위치에서 패킹할 경우 첫 번째 자식은 박스의 가장 상단이나 좌측에 나타나는 방식으로 자식 위젯들이 추가될 것이다. 끝 위치에서 패킹하면 첫 번째 자식은 박스의 하단이나 우측에 표시될 것이다.


다시 말해, 시작에 대한 참조 위치는 당신이 위젯을 추가하면서 이동한다. 위젯을 끝 위치로 추가해도 동일한 프로세스가 발생한다. 따라서 요소를 역순으로 추가하려면 gtk_box_end() 또는 gtk_box_pack_end_defaults()를 이용해야 한다. 이와 관련된 예제는 리스팅 3-2에 발췌된 코드에서 확인할 수 있다.


리스팅 3-2. 패킹 매개변수 명시하기 (boxes2.c)

vbox = gtk_vbox_new (TRUE, 5);

/* Add four buttons to the vertical box, packing at the end. */
for (i = 0; i < NUM_NAMES; i++)
{
    GtkWidget *button = gtk_button_new_with_label (names[i]);
    gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 5);

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


그림 3-2는 리스팅 3-2의 그래픽 출력을 나타낸다. 끝에서부터 각 위젯을 패킹하였으므로 역순으로 표시되었다. 박스의 끝에서 패킹이 시작되었고, 각 자식 위젯은 이전(previous) 위젯보다 먼저 패킹되었다. 패킹 함수의 시작과 끝에 호출을 마음대로 배치해도 좋다. GTK+는 두 참조 위치를 모두 파악한다.

그림 3-2. 끝 위치부터 패킹된 수직 박스


expanding, filling, spacing에 기본값을 사용하고 싶지 않다면 gtk_box_pack_end() 또는 gtk_box_pack_start()를 이용해 각 패킹 프로퍼티에 다른 값을 명시할 수 있다.


expand 프로퍼티를 TRUE로 설정하면 셀이 확장되어 위젯이 필요로 하는 박스로 할당된 추가 공간을 차지할 것이다. fill 프로퍼티를 TRUE로 설정하면 위젯 자체가 확장되어 셀에 이용 가능한 추가 공간을 채울 것이다. 표 3-1은 expand와 fill 프로퍼티의 가능한 조합을 간략하게 설명한다.

expand fill 결과
TRUE TRUE 셀이 확장되어 박스로 할당된 추가 공간을 차지하고, 자식 위젯은 그 공간을 채우도록 확장될 것이다.
TRUE FALSE 셀이 확장되어 추가 공간을 차지하지만 위젯은 확장되지 않을 것이다. 대신 추가 공간은 비어 있을 것이다.
FALSE TRUE 셀과 위젯 모두 추가 공간을 채우기 위해 확장되지 않을 것이다. 두 가지 프로퍼티를 모두 FALSE로 설정할 때도 마찬가지다.
FALSE FALSE 셀과 위젯 모두 추가 공간을 채우기 위해 확장되지 않을 것이다. 창의 크기를 조정하더라도 셀은 스스로 크기를 조정하지 않을 것이다.
표 3-1. expand 프로퍼티와 fill 프로퍼티


앞의 gtk_box_pack_end() 호출에서 각 셀은 셀과 그 주변 셀들 사이의 간격을 5 픽셀로 하도록 요청을 받는다. 또한 표 3-1에 따르면, 셀과 그 자식 위젯 중 어떤 것도 박스가 제공한 추가 공간을 차지하기 위해 확장되지는 않을 것이다.

void gtk_box_pack_end (GtkBox *box,
        GtkWidget *child,
        gboolean expand,
        gboolean fill,
        guint padding);


Gtkd note.png 다른 그래픽 툴킷을 이용해 프로그래밍할 경우 GTK+가 제공하는 크기 협상(size negotiation) 시스템이 이상하게 보일지도 모르겠다. 하지만 그 장점은 빠르게 학습할 수 있을 것이다. 사용자 인터페이스를 변경할 경우 프로그래머가 모든 것을 프로그램적으로 위치를 지정하도록 요구하는 대신 GTK+가 자동으로 크기 조정을 처리한다. 이는 GTK+를 학습하는 동안 큰 장점이 될 것이다.


GtkBox 위젯 내 요소들의 순서를 사용자에게 보여주기 전에 finalize를 시도하는 동안 gtk_box_reorder_child()를 이용하면 박스 내에 자식 위젯들의 재정렬이 가능하다.

void gtk_box_reorder_child (GtkBox *box,
        GtkWidget *child,
        gint position);


이 함수를 이용해 자식 위젯을 GtkBox 안에 새로운 위치로 이동시킬 수 있다. GtkBox 컨테이너 내부의 첫 번째 위젯의 위치는 0부터 색인된다. position 값을 -1 또는 자식의 개수보다 큰 값으로 명시할 경우 박스의 마지막에 위젯이 위치될 것이다.


수평 및 수직적 패인

GtkPaned 는 정확히 두 개의 위젯을 보유하는 특수 형태의 컨테이너 위젯이다. 크기조정 막대가 두 위젯 사이에 위치하는 형태로, 사용자는 막대를 일정 방향으로 드래그하여 두 개의 위젯의 크기를 조정할 수 있다. 막대가 움직이면 사용자 상호작용 또는 프로그램 호출에 의해 두 개의 위젯 중 하나는 크기가 줄어들고 하나는 늘어난다.


패인(paned) 위젯에는 두 가지 타입이 있는데, 하나는 수평적 크기조정을 위한 GtkHPaned이고 나머지는 수직적 크기조정을 위한 GtkVPaned다. 박스와 마찬가지로 수평 및 수직 패인 클래스는 위젯을 생성하기 위한 함수만 제공한다. 그 외의 기능은 GtkPaned라는 공통된 부모 클래스에서 정의된다. 리스팅 3-3은 두 개의 GtkButton 위젯을 수평적 패인의 자식으로서 위치시키는 간단한 예를 보여준다.


리스팅 3-3. 수평적 패인(panes.c)

#include <gtk/gtk.h>

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

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Panes");

    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    gtk_widget_set_size_request (window, 225, 150);

    hpaned = gtk_hpaned_new ();
    button1 = gtk_button_new_with_label ("Resize");
    button2 = gtk_button_new_with_label ("Me!");

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

    /* Pack both buttons as the two children of the GtkHPaned widget. */
    gtk_paned_add1 (GTK_PANED (hpaned), button1);
    gtk_paned_add2 (GTK_PANED (hpaned), button2);

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

    gtk_main ();
    return 0;
}


그림 3-3에서 볼 수 있듯이 GtkHPaned 위젯은 두 개의 자식 위젯 사이에 수직막대를 위치시킨다. 막대를 드래그하면 하나의 위젯은 줄어들고 나머지 위젯은 늘어난다. 사실 막대를 이동시켜 하나의 자식을 사용자로부터 완전히 완전히 숨길 수도 있다. gtk_paned_pack1()과 gtk_paned_pack2()를 이용해 이를 방지하는 법을 학습할 것이다.

그림 3-3. 리스팅 3-3의 그래픽 출력


리스팅 3-3에서 우리는 gtk_hpaned_new()를 이용해 GtkHPaned 객체를 생성하였다. 수직 패인 위젯을 대신 사용하고자 한다면 gtk_vpaned_new()를 호출하면 된다. 그러면 패인 위젯의 타입과 상관없이 GtkPaned 함수들이 작동할 것이다.


GtkPaned은 두 개의 자식 위젯만 처리할 수 있기 때문에 GTK+는 자식 위젯마다 패킹을 위한 함수를 제공한다. 아래 예제에서는 gtk_paned_add1()과 gtk_paned_add2()를 이용해 두 자식을 모두 hpaned로 추가하였다. 해당 함수들은 GtkPaned 위젯의 resize와 shrink 프로퍼티에 기본값을 사용한다.

gtk_paned_add1 (GTK_PANED (hpaned), label1);
gtk_paned_add2 (GTK_PANED (hpaned), label2);


앞의 gtk_paned_add1()과 gtk_paned_add2() 호출은 리스팅 3-3에서 발췌한 것으로, 아래와 동일하다.

gtk_paned_pack1 (GTK_PANED (hpaned), label1, FALSE, TRUE);
gtk_paned_pack2 (GTK_PANED (hpaned), label2, TRUE, TRUE);


gtk_paned_pack1()과 gtk_paned_pack2()에서 세 번째 매개변수는 패인의 크기가 조정되면 자식 위젯이 확장되어야 하는지 여부를 명시한다. FALSE로 설정하면 이용 가능한 영역을 얼마나 크게 만들든 자식 위젯은 확장되지 않을 것이다.


마지막 매개변수는 자식을 그것의 size requisition보다 작게 만들 수 있는지를 명시한다. 대부분의 경우 TRUE로 설정하여 크기조정 막대를 드래그하면 위젯이 사용자로부터 완전히 숨길 수 있길 원할 것이다. 사용자가 이러한 일을 하지 못하도록 방지하려면 네 번째 매개변수를 FALSE로 설정하라. 표 3-2는 resize 와 shrink 프로퍼티가 어떻게 밀접하게 연관되는지를 보여준다.

resize shrink 결과
TRUE TRUE 패인의 크기가 조정되면 위젯은 이용할 수 있는 공간을 모두 차지할 것이며, 사용자는 위젯을 그 size requisition보다 작게 만들 수 있을 것이다.
TRUE FALSE 패인의 크기가 조정되면 위젯은 이용할 수 있는 공간을 모두 차지할 것이지만, 위젯이 이용 가능한 공간은 위젯의 size requisition보다 크거나 같아야 한다.
FALSE TRUE 위젯은 패인에 이용 가능한 추가 공간을 차지하기 위해 스스로 크기를 조정하지 않을 것이지만 사용자는 위젯을 그 size requisition보다 작게 만들 수 있을 것이다.
FALSE FALSE 위젯은 패인에 이용 가능한 추가 공간을 차지하기 위해 스스로 크기를 조정하지 않을 것이며 위젯이 이용 가능한 공간은 그 size requisition보다 크거나 같아야 한다.
표 3-2. resize와 shrink 프로퍼티


gtk_paned_set_position()을 이용하면 크기조정 막대의 정확한 위치를 쉽게 설정할 수 있다. 위치는 컨테이너 위젯의 상단 또는 좌측면을 기준으로 픽셀로 계산된다. 개발자가 막대의 위치를 0으로 설정하였는데 위젯이 수축(shrinking)을 허용하는 경우 막대는 상단 또는 좌측 끝까지 이동할 것이다.

void gtk_paned_set_position (GtkPaned *paned,
        gint position);


대부분 애플리케이션은 크기조정 막대의 위치를 기억하는 편을 선호할 것인데, 그래야만 사용자가 다음에 애플리케이션을 로딩했을 때 동일한 위치로 복구할 수 있기 때문이다. 크기조정 막대의 현재 위치는 gtk_paned_get_position()을 이용해 검색할 수 있다.

gint gtk_paned_get_position (GtkPaned *paned);


GtkPaned는 많은 시그널을 제공하지만 가장 유용한 시그널 중에서 크기조정 막대가 언제 이동되었는지 알려주는 move-handle을 들 수 있겠다. 크기조정 막대의 위치를 기억하길 원하는 경우 해당 시그널은 언제 새 값을 검색해야 하는지 알려줄 것이다. GtkPaned 시그널의 전체 리스트는 부록 B에서 찾을 수 있다.


테이블

지금까지 필자가 다룬 레이아웃 컨테이너 위젯들은 모두 자식 위젯들을 1 차원으로 패킹하도록 허용하였다. 하지만 GtkTable 위젯은 2차원 공간으로 자식 위젯을 패킹하도록 해준다.


GtkHBox와 GtkVBox 위젯을 여러 개 사용하는 대신 GtkTable 위젯을 사용하면 근접한 행과 열에 위치한 자식 위젯들이 자동으로 서로 정렬된다는 장점이 있으나, 박스 내부의 박스는 예외다. 하지만 모든 위젯을 항상 이러한 방식으로 정렬하고 싶지는 않을 것이기 때문에 이러한 장점이 때로는 단점이 되기도 한다.


그림 3-4에는 세 개의 위젯을 포함하는 단순한 테이블이 표시되어 있다. 하나의 라벨이 두 개의 열로 이어짐을 주목하라. 테이블은 영역(region)이 직사각형에 해당하는 한 하나의 위젯이 다수의 열/행으로 이어지는 것을 허용한다.

그림 3-4. 다중 열로 이어지는 라벨 위젯을 포함하는 테이블


리스팅 3-4는 그림 3-4에 표시된 GtkTable을 생성하여 두 개의 GtkLabel 위젯과 하나의 GtkEntry 위젯을 2x2 영역으로 삽입한다 (GtkEntry의 사용법은 제 4장에서 학습하겠지만 여기서 잠깐 맛보기로 살펴보겠다).


리스팅 3-4. GtkTable가 표시하는 이름 (tables.c)

#include <gtk/gtk.h>

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

    gtk_init (&argc,&argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Tables");
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    gtk_widget_set_size_request (window, 150, 100);

    table = gtk_table_new (2, 2, TRUE);
    label = gtk_label_new ("Enter the following information ...");
    label2 = gtk_label_new ("Name: ");
    name = gtk_enrty_new ();

    /*Attach the two labels and entry widget to their parent container. */
    gtk_table_attach (GTK_TABLE (table), label, 0, 2, 0, 1,
        GTK_EXPAND, GTK_SHRINK, 0, 0);
    gtk_table_attach (GTK_TABLE (table), label2, 0, 1, 1, 2,
        GTK_EXPAND, GTK_SHRINK, 0, 0);
    gtk_table_attach (GTK_TABLE (table), name, 1, 2, 1, 2,
        GTK_EXPAND, GTK_SHRINK, 0, 0);

    /* Add five pixels of spacing between every row and every column. */
    gtk_table_set_row_spacings (GTK_TABLE (table), 5);
    gtk_table_set_col_spacings (GTK_TABLE (table), 5);

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

    gtk_main ();
    return 0;
}


테이블 패킹

gtk_table_new()를 이용해 테이블을 생성할 때는 열의 수, 행의 수, 테이블 셀이 동질한지를 명시해야 한다.

GtkWidget* gtk_table_new (guint rows,
        guint columns,
        gboolean homogeneous);


행과 열의 수는 테이블을 생성한 후에 gtk_table_resize()를 이용해 변경이 가능하지만 가능하면 초기에 올바른 숫자를 사용해야만 사용자 입장에서 혼동을 피할 수 있다. 전적으로 필요한 경우가 아닌데도 사용자 인터페이스를 고의적으로 변경하는 버릇은 개발자 역시 원치 않을 것이다.

void gtk_table_resize (GtkTable *table,
        guint rows,
        guint columns);


gtk_table_set_homogeneous() 함수 또한 테이블의 생성 이후 동질적인 프로퍼티를 리셋하는 데 사용 가능하지만 이 또한 처음에 바람직한 값을 사용해야 한다. 초기 사용자 인터페이스가 설정된 후에는 사용자에게 크기조정의 제어가 넘어간다.

void gtk_table_set_homogeneous (GtkTable *table,
        gboolean homogeneous);


새 위젯의 패킹은 gtk_table_attach()를 이용해 실행된다. 두 번째 매개변수 child는 개발자가 테이블로 추가하는 자식 위젯을 참조한다.

void gtk_table_attach (GtkTable *table,
        GtkWidget *child,
        guint left,
        guint right,
        guint top,
        guint bottom,
        GtkAttachOptions xoptions,
        GtkAttachOptions yoptions,
        guint xpadding,
        guint ypadding);


left, right, top, bottom 변수들은 테이블 내에서 자식 위젯이 있어야 하는 위치를 설명한다. 가령 리스팅 3-4에서 첫 번째 GtkLabel은 아래 명령을 이용해 추가된다.

gtk_table_attach (GTK_TABLE (table), label, 0, 2, 0, 1,
        GTK_EXPAND, GTK_SHRINK 0, 0);


GtkLabel 위젯은 테이블의 첫 열과 첫 행에 바로 붙는데, x 좌표가 추가된 다음 y 좌표가 추가되기 때문이다. 이후 하단에 두 번째 행과 우측의 세 번째 열에 붙는다. 리스팅 3-4의 예제에 표현된 패킹을 그림 3-5에 표시하였다.

그림 3-5. 테이블 패킹


두 개의 열을 선택하면 라벨이 붙은 제로 색인(zero-indexed)의 열 부착점(column attach point)이 3개가 생길 것이다. 두 개의 열이 있는 경우 행 부착점에도 마찬가지로 적용된다.


앞서 언급하였듯 위젯이 다수의 셀에 걸친 경우 직사각형 영역을 차지해야 한다. 위젯은 (0, 1, 0, 2)를 이용해 두 개의 행과 하나의 열에 걸치거나, (0, 2, 0, 2)를 이용해 전체 테이블에 걸칠 수도 있다. 부착점을 명시하는 순서는 x 좌표가 먼저 오고 그 다음에 y 좌표가 따라옴을 기억하는 편이 가장 좋다. 부착점을 명시하고 나면 가로와 세로 방향에 대한 부착(attach) 옵션을 제공할 필요가 있다. 우리 예제에서 자식 위젯들은 x 방향으로 확장하고 y 방향으로 축소하도록 설정되었다. GtkAttachOptions 목록(enumeration)에는 3 개의 값이 있다.

  • GTK_EXPAND: 위젯은 테이블에 의해 위젯으로 할당된 추가 공간을 차지해야 한다. 이러한 공간은 해당 옵션을 명시하는 모든 자식 위젯들 간 균등하게 할당되어야 한다.
  • GTK_SHRINK: 위젯은 렌더링에 충분한 공간만 차지하도록 줄어들어야 한다. 보통 다른 위젯들이 이러한 공간을 차지할 수 있도록 만들기 위해 사용된다.
  • GTK_FILL: 추가 공간을 패딩으로 채우는 대신 할당된 공간을 모두 위젯이 채워야 한다.


bitwise 또는 연산자(operator)를 이용함으로써 다수의 부착점 매개변수를 제공하는 것이 가능하다. 예를 들어 GTK_EXPANDㅣGTK_FILL을 이용하면 패딩을 추가하는 대신 자식 위젯이 추가 공간을 차지하고 채울 것이다.


gtk_table_attach()의 마지막 두 매개변수는 그 자식 위젯과 주변 셀들 사이에 추가되어야 하는 수평 및 수직 패딩의 픽셀을 명시한다.

void gtk_table_attach_defaults (GtkTable *table,
        GtkWidget *child,
        guint left,
        guint right,
        guint top,
        guint bottom);


박스와 마찬가지로 자식 위젯을 추가할 때는 전체 매개변수 집합을 명시하지 않아도 된다. 부착 및 패딩 옵션을 명시하지 않고 gtk_table_attach_defaults()를 이용해 자식을 추가할 수도 있다. 이러한 함수를 이용할 때는 각 부착 옵션마다 GTK_EXPANDㅣGTK_FILL이 사용될 것이며, 패딩은 전혀 추가되지 않을 것이다.


테이블 간격

gtk_table_attach()를 이용하면 행이나 열의 간격을 명시할 수 있지만 GTK+는 자식 위젯을 추가한 후 이러한 수치를 변경하도록 네 가지 방법을 제공한다.


테이블에서 열마다 간격을 설정하고 싶다면 gtk_table_set_col_spacings()를 이용하면 된다. 해당 함수는 리스팅 3-4에서 5 픽셀 간격을 추가하는 데에 사용되었다. GTK+는 행 사이에 패딩을 추가하도록 gtk_table_set_row_spacings()도 제공한다. 이러한 함수들은 테이블의 기존 설정을 모두 오버라이드할 것이다.

void gtk_table_set_col_spacings (GtkTable *table,
        guint spacing);


gtk_table_set_col_spacing() 또는 gtk_table_set_row_spacing()은 각각 하나의 특정 열과 행의 간격을 설정한다. 이러한 함수들은 자식 위젯과 그 주위 간격을 위젯의 좌우 또는 위아래에 추가한다.

void gtk_table_set_col_spacing (GtkTable *table,
        guint column,
        guint spacing);


고정 컨테이너

GtkFixed 위젯은 위젯을 픽셀별로 위치시키도록 해주는 타입의 레이아웃 컨테이너다. 해당 위젯을 사용 시에는 많은 문제가 발생할 수 있지만 단점을 확인하기 전에 우선 간단한 예를 살펴보자.


리스팅 3-5는 두 개의 버튼을 포함하는 GtkFixed 위젯을 생성하는데, 버튼은 위젯의 상단 좌측 모서리를 기준으로 각각 (0,0)과 (20,30)에서 찾을 수 있다.


리스팅 3-5. 정확한 위치 명시하기 (fixed.c)

#include <gtk/gtk.h>

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

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Fixed");
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);

    fixed = gtk_fixed_new ();
    button1 = gtk_button_new_with_label ("Pixel by pixel ...");
    button2 = gtk_button_new_with_label ("you choose my fate.");

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

    /* Place two buttons on the GtkFixed container. */
    gtk_fixed_put (GTK_FIXED (fixed), button1, 0, 0);
    gtk_fixed_put (GTK_FIXED (fixed), button2, 20, 30);

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

    gtk_main ();
    return 0;
}


gtk_fixed_new()로 초기화되는 GtkFixed 위젯은 특정 위치에 특정 크기로 된 위젯을 위치시키도록 해준다. gtk_fixed_put()을 이용하면 명시된 가로 및 세로 위치에 위젯을 놓을 수 있다.

void gtk_fixed_put (GtkFixed *fixed,
        GtkWidget *child,
        gint x,
        gint y);


고정 컨테이너의 상단 좌측 모서리는 위치 (0,0)으로 참조된다. 위치나 위젯에 대한 실제 위치는 양의 공간에서만 명시할 수 있다. 고정 컨테이너는 자체적으로 크기를 변경할 것이므로 모든 위젯은 완전히 표시된다.


GtkFixed 컨테이너 내에서 위젯을 위치시킨 후 이동해야 하는 경우 gtk_fixed_move()를 이용할 수 있다. 이미 위치된 위젯이 겹치지 않도록 주의할 필요가 있겠다. 위젯이 겹치면 GtkFixed 위젯은 알림을 제공하지 않고 대신 창의 렌더링을 시도할 것이므로 그 결과를 예측할 수 없다.

void gtk_fixed_move (GtkFixed *fixed,
        GtkWidget *child,
        gint x_position,
        gint y_position);


이는 GtkFixed 위젯을 사용 시 불가피한 문제를 야기한다. 첫 번째는 사용자가 원하는 테마를 원하는 대로 사용한다는 점이다. 이는 개발자가 명시적으로 글꼴을 설정하지 않는 한 사용자의 머신에 표시되는 텍스트의 크기가 개발자의 머신에 표시된 텍스트의 크기와 다를 수 있음을 의미한다. 위젯의 크기는 사용자 테마에 따라 다양하다. 따라서 잘못된 정렬(misalignment)과 겹침(overlap)을 야기할 수 있다. 이는 리스팅 3-5의 두 개의 스크린샷을 표시하는 그림 3-6에서 묘사하고 있는데, 하나는 글꼴 크기가 작고 나머지 하나는 크다.

그림 3-6. GtkFixed 컨테이너 내에서 글꼴 크기 차이로 인해 야기된 문제


겹침을 피하기 위해 텍스트의 크기와 글꼴을 명시적으로 설정할 수 있지만 대부분의 경우 권하지 않는다. 저시력 사용자를 위해 접근성 옵션이 제공된다. 개발자가 글꼴을 변경하면 일부 사용자는 화면에서 텍스트를 읽을 수 없을 것이다.


GtkFixed 를 사용 시 발생 가능한 또 다른 문제는 애플리케이션을 다른 언어로 해석할 때 발생한다. 사용자 인터페이스가 영어로는 괜찮아 보일지 모르지만 다른 언어로 표시된 문자열은 너비가 일정하지 않아 문제를 표시하기도 한다. 게다가 히브리어와 아랍어와 같이 오른쪽으로 왼쪽으로 읽는 언어는 GtkFixed 위젯을 이용해 적절하게 반영할 수가 없다. 이런 경우 GtkBox 또는 GtkTable과 같은 다양한 크기의 컨테이너를 사용하는 것이 최선의 방법이다.


마지막으로, GtkFixed 컨테이너를 사용하면 자신의 그래픽 인터페이스에 위젯을 추가하고 그로부터 제거하는 일이 번거롭다. 사용자 인터페이스를 변경하면 자신의 모든 위젯의 위치를 변경해야 할 것이다. 위젯이 많은 애플리케이션을 갖고 있는 경우 이는 장기간 관리 문제를 제시한다.


다른 한편으로는 테이블, 박스, 그 외 자동으로 포맷팅되는 다양한 컨테이너가 있다. 사용자 인터페이스에서 위젯을 추가하거나 제거해야 하는 경우 셀로 추가 및 제거하기가 쉽다. 이는 관리를 훨씬 효율적으로 만들기 때문에 큰 애플리케이션에서 고려할 사항이다.


따라서 위에서 제시한 문제들이 자신의 애플리케이션에 해가 되지 않을 것을 확신할 수 없다면 GtkFixed 대신 다양한 크기의 컨테이너를 사용해야 한다. GtkFixed 컨테이너는 적절한 상황이 발생할 경우 이용할 수 있음을 알리기 위해 제시되었다. 설사 사용하기에 적절한 상황이라 하더라도 유연한 컨테이너를 사용하는 편이 언제나 더 나은 해결책이자 적절한 작업 처리 방식이 된다.


익스펜더

GtkExpander 컨테이너는 하나의 자식만 처리할 수 있다. 자식 위젯은 익스펜더의 라벨 왼쪽에 삼각형을 클릭하여 표시/숨김이 가능하다. 해당 액션의 적용 전후 스크린샷을 그림 3-7에서 확인할 수 있다.

그림 3-7. GtkExpander 컨테이너


리스팅 3-6은 그림 3-7을 생성하기 위해 사용되었다. 해당 예제는 가장 중요한 GtkExpander 메서드를 소개한다.


리스팅 3-6. 위젯 표시하기/숨기기 (expanders.c)

#include <gtk/gtk.h>

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

    gtk_init (&argc, &argv);

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

    expander = gtk_expander_new_with_mnemonic ("Click _Me For More!");
    label = gtk_label_new ("Hide me or show me, \nthat is your choice.");

    gtk_container_add (GTK_CONTAINER (expander), label);
    gtk_expander_set_expanded (GTK_EXPANDER (expander), TRUE);
    gtk_container_add (GTK_CONTAINER (window), expander);

    gtk_widget_show_all (window);

    gtk_main ();
    return 0;
}


리스팅 3-6은 GtkExpander를 초기화하기 위해 gtk_expander_new_with_mnemonic()을 사용한다. 해당 함수의 initialization 함수에 밑줄을 사용하면 키보드 가속기가 생성될 것이다. 가령, 리스팅 3-6에서는 사용자가 키보드에 Alt+M을 누를 때마다 위젯이 활성화될 것이다. GtkExpander 위젯을 활성화하면 현재 상태에 따라 위젯이 확장 또는 축소될 것이다.


Gtkd tip.png 연상기호는 라벨을 표시하는 거의 모든 위젯에서 이용 가능하다. 이용 가능한 곳에서는 항상 이 기능을 사용해야 하는데, 일부 사용자들은 키보드를 이용해 애플리케이션을 훑어보는 편을 선호하기 때문이다.


익스펜더 라벨에 밑줄 문자를 포함시키길 원한다면 두 번째 밑줄과 함께 맨 앞에 붙여야 한다. 연상기호 기능을 활용하고 싶지 않다면 gtk_expander_new()를 이용해 표준 문자열을 라벨로 하여 GtkExpander를 초기화할 수 있지만 사용자에게 연상기호를 옵션으로 제공하는 것은 언제든 훌륭한 생각이다. 일반 익스펜더 라벨에서 밑줄 문자는 파싱되진 않지만 다른 문자로서 처리될 것이다.


GtkExpander 위젯 자체는 GtkBin에서 파생되므로 하나의 자식 위젯만 가질 수 있음을 의미한다. 하나의 자식을 보유하는 여느 컨테이너 위젯과 마찬가지로 자식 위젯을 추가하려면 gtk_container_add()가 필요하다.


리스팅 3-6에서 필자는 자식 위젯을 기본적으로 visible로 설정하고자 하므로 GtkExpander 위젯을 확장 가능하도록 설정하였다. GtkExpander 컨테이너의 자식 위젯은 gtk_expander_set_expanded()를 호출함으로써 표시/숨김이 가능하다.

void gtk_expander_set_expanded (GtkExpander *expander,
        gboolean expanded);


기본적으로 GTK+는 익스펜더 라벨과 자식 위젯 사이에 간격을 추가하지 않는다. 간격을 픽셀로 추가하려면 gtk_expander_set_spacing()을 이용해 패딩을 추가할 수 있다.

void gtk_expander_set_spacing (GtkExpander *expander,
        gint spacing);


핸들 박스

GtkHandleBox 위젯은 또 다른 타입의 GtkBin 컨테이너로, 그 자식들을 마우스로 드래그하여 부모 창에서 제거할 수 있도록 한다.


자식 위젯은 제거되면 decorations가 없는 고유의 창에 위치한다. 위젯이 본래 위치한 곳에는 ghost가 위치한다. 창에 다른 위젯들이 존재한다면 가능할 경우 그들이 빈 공간을 채우도록 크기가 조정될 것이다.


위젯은 보통 툴바와 다른 툴킷 디스플레이를 포함하도록 사용된다. GtkHandleBox 위젯의 예를 그림 3-8에 표시하였다. 이는 창에 추가된 후 제거되는 핸들 박스를 보여준다. 핸들 박스는 본래 위치를 이용해 정렬함으로써 다시 부착할 수 있다.

그림 3-8. 부착된 후 분리된 핸들 박스


리스팅 3-7에서는 GtkLabel 자식을 포함하는 GtkHandleBox 위젯을 생성한다. 해당 예제는 GtkHandleBox 클래스를 통해 이용 가능한 모든 프로퍼티를 보여준다.


리스팅 3-7. 분리 가능한 위젯 (handleboxes.c)

#include <gtk/gtk.h>

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

    gtk_init (&argc,&argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Handle Box");
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    gtk_widget_set_size_request (window, 200, 100);

    handle = gtk_handle_box_new ();
    label = gtk_label_new ("Detach Me");

    /* Add a shadow to the handle box, set the handle position on the left and
    * set the snap edge to the top of the widget. */
    gtk_handle_box_set_shadow_type (GTK_HANDLE_BOX (handle), GTK_SHADOW_IN);
    gtk_handle_box_set_handle_position (GTK_HANDLE_BOX (handle), GTK_POS_LEFT);
    gtk_handle_box_set_snap_edge (GTK_HANDLE_BOX (handle), GTK_POS_TOP);

    gtk_container_add (GTK_CONTAINER (handle), label);
    gtk_container_add (GTK_CONTAINER (window), handle);
    gtk_widget_show_all (window);

    gtk_main ();
    return 0;
}


GtkHandleBox 위젯을 생성할 때는 핸들과 스냅 에지(snap edge)가 어디에 위치할 것인지 결정해야 한다. 핸들은 GtkHandleBox 자식을 그 부모로부터 분리(detach)하기 위해 개발자가 붙잡는 자식 위젯의 면(side)에 있는 영역이다. 핸들 박스를 부모 위젯으로부터 분리하면 본래 위치에 얇은 ghost가 그려진다.


gtk_handle_box_set_handle_position() 함수는 핸들의 위치를 설정하는 데에 사용된다. GtkPositionType 목록에는 핸들의 위치와 관련해 네 가지 옵션을 제공한다. 핸들 위치의 기본값은 GTK_POS_LEFT로 설정되지만 이는 GTK_POS_RIGHT, GTK_POS_TOP, GTK_POS_BOTTOM을 이용해 어떤 면이든 위치시킬 수 있다.

void gtk_handle_box_set_handle_position (GtkHandleBox *handle_box,
        GtkPositionType position);


핸들 위치를 기반으로 GTK+는 스냅 에지의 위치를 선택하는데, 이 곳에서 핸들 박스는 그 부모로 재부착하기 위해 스스로를 변경한다. 스냅 에지는 분리된 후에 ghost가 표시되는 곳이다.


gtk_handle_box_set_snap_edge()를 이용해 스냅 에지에 대한 새 GtkPositionType 값을 명시할 수 있다. 사용자의 혼동을 피하기 위해서는 핸들과 관련된 스냅 에지를 위치시키는 장소에 주의를 기울이는 것이 중요하겠다.

void gtk_handle_box_set_snap_edge (GtkHandleBox *handle_box,
        GtkPositionType position);


예를 들어, 핸들 박스가 GtkVBox 위젯의 위에 있고 핸들이 왼쪽 면에 있을 경우 스냅 에지 위치는 GTK_POS_TOP으로 설정해야 한다. 그래야만 ghost가 크기를 조정할 필요 없이 스냅 에지와 같은 장소에 위치할 수 있다.


GtkHandleBox는 gtk_handle_box_set_shadow_type()이라는 함수도 제공하여 자식 위젯 주변에 위치시킬 테두리 타입을 설정하도록 해준다. GtkShadowType 목록에 대한 값은 아래를 따른다.

  • GTK_SHADOW_NONE: 자식 위젯 주변에 어떤 테두리도 위치시키지 않을 것이다.
  • GTK_SHADOW_IN: 테두리가 안쪽으로 치우칠 것이다(skewed inwards).
  • GTK_SHADOW_OUT: 버튼처럼 테두리가 바깥쪽으로 치우칠 것이다.
  • GTK_SHADOW_ETCHED_IN: 테두리가 들어간 3-D 모양일 것이다.
  • GTK_SHADOW_ETCHED_OUT: 테두리가 튀어나온 3-D 모양일 것이다.


노트북

GtkNotebook 위젯은 자식 위젯을 다수의 페이지에 조직한다. 사용자는 위젯의 한쪽 변에 나타나는 탭을 클릭하여 해당 페이지를 전환할 수 있다.

탭은 기본값으로 상단에 표시되지만 당신이 탭이 표시될 위치를 명시할 수도 있다. 탭을 모두 숨기는 것도 가능하다. 그림 3-9는 리스팅 3-8의 코드로 생성된 두 개의 탭이 있는 GtkNotebook 위젯을 보여준다.

그림 3-9. 두 페이지가 있는 노트북 컨테이너


노트북 컨테이너를 생성할 때는 탭마다 탭 라벨 위젯과 자식 위젯을 명시해야 한다. 탭은 앞이나 뒤에서 추가, 삽입, 재정렬, 제거된다.


리스팅 3-8. 다수의 페이지가 있는 컨테이너 (notebooks.c)

#include <gtk/gtk.h>

static void switch_page (GtkButton*, GtkNotebook*);

int main (int argc,
        char *argv[])
{
    GtkWidget *windiw, *notebook;
    GtkWidget *label1, *label2, *child1, *child2;

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Notebook");
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    gtk_widget_set_size_request (window, 250, 100);

    notebook = gtk_notebook_new ();
    label1 = gtk_label_new ("Page One");
    label2 = gtk_label_new ("Page Two");
    child1 = gtk_label_new ("Go to page 2 to find the answer.");
    child2 = gtk_label_new ("Go to page 1 to find the answer.");

    /* Notice that two widgets were connected to the same callback function! */
    g_signal_connect (G_OBJECT (chile1), "clicked",
        G_CALLBACK (switch_page),
        (gpointer) notebook);
    g_signal_connect (G_OBJECT (child2), "clicked",
        G_CALLBACK (switch_page),
        (gpointer) notebook);

    /* Append to pages to the notebook container. */
    gtk_notebook_append_page (GTK_NOTEBOOK (notebook), child1, label1);
    gtk_notebook_append_page (GTK_NOTEBOOK (notebook), child2, label2);

    gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_BOTTOM);

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

    gtk_main ();
    return 0;
}

/* Switch to the next or previous GtkNotebook page. */
static void
switch_page (GtkButton *button,
        GtkNotebook *notebook)
{
    gint page = gtk_notebook_get_current_page (notebook);

    if (page == 0)
        gtk_notebook-set_current_page (notebook, 1);
    else
        gtk_notebook_set_current_page (notebook, 0);
}


GtkNotebook을 생성한 후에는 그곳에 탭을 추가하고 나서야 유용해진다. 탭 리스트의 끝이나 시작 부분에 탭을 추가하려면 각각 gtk_notebook_append_page() 또는 gtk_notebook_prepend_page()를 이용할 수 있다. 각 함수는 자식 위젯인 GtkNotebook와 함께 아래와 같이 탭에 표시되어야 할 위젯을 수락한다.

gint gtk_notebook_append_page (GtkNotebook *notebook,
        GtkWidget *child,
        sGtkWidget *tab_label);


Gtkd tip.png 탭 라벨이 꼭 GtkLabel 위젯일 필요는 없다. 가령, 라벨과 닫기 버튼을 포함하는 GtkHBox 위젯을 사용할 수도 있다. 이는 버튼이나 이미지와 같이 다른 유용한 위젯을 탭 라벨로 내포하도록 허용한다.


각 노트북 페이지는 하나의 자식 위젯만 표시할 수 있다. 하지만 각 자식 위젯은 다른 컨테이너가 될 수도 있으므로 각 페이지는 다수의 위젯을 표시할 수 있다. 사실 GtkNotebook을 다른 GtkNotebook 탭의 자식 위젯으로서 사용하는 것이 가능하다.


Gtkd caution.png 노트북 내부에 노트북을 위치시키는 것도 가능하지만 사용자가 쉽게 혼란에 빠질 수 있으므로 주의를 기울여 실행해야 한다. 꼭 실행해야 한다면 자식 노트북의 탭은 부모 탭과 다른 노트북 면(side)에 위치시키도록 하라. 그래야만 어떤 탭이 어떤 노트북에 속하는지 알아낼 수 있을 것이다.


특정 위치에 탭을 삽입하고자 한다면 gtk_notebook_insert_page()를 사용할 수 있다. 해당 함수는 탭의 정수 위치를 명시하도록 허용할 것이다. 삽입된 탭 다음에 위치한 모든 탭의 색인은 1씩 증가할 것이다.

gint gtk_notebook_insert_page (GtkNotebook *notebook,
        GtkWidget *child,
        GtkWidget *tab_label,
        gint position);


GtkNotebook으로 탭을 추가하는 데에 사용된 세 가지 함수 모두 당신이 추가한 탭의 정수 위치를 리턴하고, 액션이 실패하면 -1를 리턴할 것이다.


GtkNotebook 프로퍼티

리스팅 3-8에서 GtkNotebook에 대해 tab-position 프로퍼티가 설정되었는데, 이는 아래 호출을 통해 이루어진다.

void gtk_notebook_set_tab_pos (GtkNotebook *notebook,
        GtkPositionType position);


탭 위치는 GtkHandleBox의 핸들 및 스냅 에지 위치를 설정하는 데에 사용했던 GtkPositionType 목록을 이용하여 gtk_notebook_tab_pos()에서 설정이 가능하다. 여기에는 GTK_POS_TOP, GTK_POS_BOTTOM, GTK_POST_LEFT, GTK_POS_RIGHT가 포함된다.


노트북은 사용자에게 여러 옵션을 제공하길 원하지만 이러한 옵션들을 여러 단계로 표시하길 원할 때 유용하다. 각 탭에 옵션 몇 가지를 위치시키고 gtk_notebook-set_show_tabs()를 이용해 탭을 숨기면 사용자 옵션을 왔다갔다 선택할 수 있다. 이러한 개념의 예로 자신의 운영체제를 통해 확인 가능한 수많은 마법사(wizard)를 들 수 있는데, GtkAssistant 위젯이 제공하는 기능과 비슷하겠다.

void gtk_notebook_set_show_tabs (GtkNotebook *notebook,
        gboolean show_tabs);


어느 시점이 되면 GtkNotebook은 할당된 공간에서 탭을 보관할 공간이 부족하게 될 것이다. 이러한 문제를 해결하기 위해 gtk_notebook_set_scrollable()을 이용해 노트북 탭을 스크롤 가능하게 설정할 수 있다.

void gtk_notebook_set_scrollable (GtkNotebook *notebook,
        gboolean scrollable);


해당 프로퍼티는 탭을 강제로 사용자에게서 숨길 것이다. 사용자가 탭의 리스트를 스크롤할 수 있도록 화살표가 제공된다. 탭은 하나의 행이나 열에만 표시될 것이기 때문에 꼭 필요하다.


창의 크기를 조정하여 모든 탭이 표시되지 않으면 탭은 스크롤이 가능하게 만들어질 것이다. 또 탭을 모두 그릴 수 없을 만큼 글꼴의 크기가 커도 스크롤이 발생할 것이다. 탭이 할당된 공간 이상을 차지할 가능성이 조금이라도 있다면 해당 프로퍼티는 항상 TRUE로 설정해야 한다.


탭 연산

GTK+는 이미 존재하는 탭과 상호작용할 수 있도록 다수의 함수를 제공한다. 이를 학습하기 전에 우선 이러한 함수들 중 대부분은 change-current-page 시그널을 방출하게 만든다는 사실을 인지하는 것이 유용하다. 해당 시그널은 포커스를 받은 현재 탭이 변경될 때 방출된다.


탭을 추가할 수 있다면 탭을 제거하는 방법도 분명 존재해야 한다. gtn_notebook_remove_page()를 이용해 탭의 색인 참조를 기반으로 탭을 제거할 수 있다. 위젯을 GtkNotebook으로 추가하기 전에 참조 계수를 증가시키지 않았다면 해당 함수는 마지막 참조를 해제(release)하고 자식 위젯을 소멸시킬 것이다.

void gtk_notebook_remove_page (GtkNotebook *notebook,
        gint page_number);


gtk_notebook_reorder_child()를 호출하여 탭을 수동으로 재정렬할 수도 있다. 이동시키고자 하는 페이지의 자식 위젯과 그것을 이동시킬 위치를 명시해야만 한다. 탭의 개수보다 크거나 음의 숫자를 명시할 경우 탭은 리스트의 끝으로 이동할 것이다.

void gtk_notebook_reorder_child (GtkNotebook *notebook,
        GtkWidget *child,
        gint position);


현재 페이지를 변경하기 위해 제공하는 방법에는 세 가지가 있다. 확인하길 원하는 페이지의 구체적인 색인을 알고 있다면 gtk_notebook_set_current_page()를 이용해 해당 페이지도 이동할 수 있다.

void gtk_notebook_set_current_page (GtkNotebook *notebook,
        gint page_number);


때로는 gtk_notebook_next_page() 또는 gtk_notebook_prev_page()를 호출하여 다음 탭이나 이전 탭으로 전환하길 원할 때도 있다. 두 함수 중 하나로 호출하였는데 현재 탭이 0 보다 작아지거나 현재 탭의 개수보다 많아지는 경우 아무 일도 발생하지 않고 호출은 무시될 것이다.


어떤 페이지로 이동할 것인지 결정할 때는 현재 페이지와 총 탭의 개수를 아는 것이 도움이 되곤 한다. 이러한 값은 각각 gtk_notebook_get_current_page() 와 gtk_notebook_get_n_pages()를 이용해 얻을 수 있다.


이벤트 박스

GtkLabel을 포함해 다양한 위젯들은 연관된 GDK 창을 갖는다는 이유로 GDK 이벤트에 응답하지 않는다. 이러한 문제를 해결하기 위해 GTK+는 GtkEvenBox라고 불리는 컨테이너 위젯을 제공한다. 이벤트 박스는 객체에 대한 GDK 창을 제공함으로써 자식 위젯에 대한 이벤트를 포착(catch)한다.


리스팅 3-9는 이벤트 박스를 이용해 button-press-event 시그널을 GtkLabel로 연결한다. 라벨 내 텍스트는 라벨을 더블 클릭할 때 현재 위치를 기반으로 변경된다. 한 번만 클릭하면 시그널은 방출될지언정 눈에 보이는 변화는 발생하지 않는다.


리스팅 3-9. GtkLabel로 이벤트 추가하기 (eventboxes.c)

#include <gtk/gtk.h>

static gboolean button_pressed (GtkWidget*, GdkEventButton*, GtkLabel*);

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

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Event Box");
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    gtk_widget_set_size_request (window, 200, 50);

    eventbox = gtk_event_box_new ();
    label = gtk_label_new ("Double-Click Me!");

    /* Set the order in which widgets will receive notification of events. */
    gtk_event_box_set_above_child (GTK_EVENT_BOX (eventbox), FALSE);

    g_signal_connect (G_OBJECT (eventbox), "button_press_event",
        G_CALLBACK
 (button_pressed), (gpointer) label);

    gtk_container_add (GTK_CONTAINER (eventbox), label);
    gtk_container_add (GTK_CONTAINER (window), eventbox);

    /* Allow the event box to catch button presses, realize the widget, and set the
    * cursor that will be displayed when the mouse is over the event box. */
    gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK);
    gtk_widget_realize (eventbox);
    gdk_window_set_cursor (eventbox->window, gdk_cursor_new (GDK_HAND1));
    gtk_widget_show_all (window);

    gtk_main ();
    return 0;
}

/* This is called every time a button-press event occurs on the GtkEventBox. */
static gboolean
button_pressed (GtkWidget *eventbox,
        GdkEventButton *event,
        GtkLabel *label)
{
    if (event->type == GDK_2BUTTON_PRESS)
    {
        const gchar *text = gtk_label_get_text (label);

        if (text[0] == 'D')
            gtk_label_set_text (label, "I Was Double-Clicked!");
        else
            gtk_label_set_text (label, "Double-Click Me Again!");
    }
    return FALSE;
}


이벤트 박스를 이용할 때는 이벤트 박스의 GdkWindow 가 그 자식 창의 위에 위치할 것인지 아래에 위치할 것인지 결정할 필요가 있다. 이벤트 박스 창이 위에 있는 경우 이벤트 박스 내의 모든 이벤트가 이벤트 박스 위로 이동할 것이다. 창이 아래에 있다면 자식 위젯의 창에 있는 이벤트들이 먼저 해당 위젯으로 이동한 후 그 부모로 이동할 것이다.


Gtkd note.png 창의 위치를 아래(below)로 설정하면 이벤트는 자식 위젯으로 먼저 이동한다. 하지만 이는 연관된 GDK 창을 가진 위젯에 한한다. 자식이 GtkLabel 위젯인 경우 스스로 이벤트를 감지하는 능력이 없다. 따라서 리스팅 3-9에서는 창의 위치를 위로 설정하든 아래로 설정하든 상관없다.


이벤트 박스의 위치는 gtk_event_box_set_above_child()를 이용해 그 자식 위젯의 위나 아래로 이동할 수 있다. 해당 프로퍼티의 기본값은 모든 이벤트 박스에 대해 FALSE로 설정된다. 즉, 모든 이벤트는 시그널이 처음 방출된 위젯에 의해 처리될 것이란 뜻이다. 그리고 위젯이 완료되고 나면 이벤트는 그 부모로 전달될 것이다.

void gtk_event_box_set_above_child (GtkEventBox *event_box,
        gboolean above_child);


다음은, 이벤트 마스크를 이벤트 박스로 추가하여 위젯이 수신하게 될 이벤트 타입을 알 수 있도록 해야 한다. 이벤트 마스크를 명시하는 GdkEventMask 목록의 값은 표 3-3에 실려 있다. GdkEvenMask 값에 대한 bitwise 값을 하나 이상 설정해야 하는 경우 그 값을 gtk_widget_set_events()로 전달할 수 있다.

설명
GDK_EXPOSURE_MASK 위젯이 노출될 때 이벤트를 수락한다.
GDK_POINTER_MOTION_MASK 모든 포인터 모션 이벤트를 수락한다.
GDK_POINTER_MOTION_HINT_MASK GDK_MOTION_NOTIFY 이벤트 개수를 제한하여 마우스가 움직일 때마다 방출되지 않도록 한다.
GDK_BUTTON_MOTION_MASK 아무 버튼이나 누르는 동안 포인터 모션 이벤트를 수락한다.
GDK_BUTTON1_MOTION_MASK 버튼 1을 누르는 동안 포인터 모션 이벤트를 수락한다.
GDK_BUTTON2_MOTION_MASK 버튼 2을 누르는 동안 포인터 모션 이벤트를 수락한다.
GDK_BUTTON3_MOTION_MASK 버튼 3을 누르는 동안 포인터 모션 이벤트를 수락한다.
GDK_BUTTON_PRESS_MASK 마우스 버튼 누름 이벤트를 수락한다.
GDK_BUTTON_RELEASE_MASK 마우스 버튼 누름해제 이벤트를 수락한다.
GDK_KEY_PRESS_MASK 키보드에서 키 누름 이벤트를 수락한다.
GDK_KEY_RELEASE_MASK 키보드에서 키 누름해제 이벤트를 수락한다.
GDK_ENTER_NOTIFY_MASK 창 근처에 들어갈 때 방출되는 이벤트를 수락한다.
GDK_LEAVE_NOTIFY_MASK 창 근처에서 벗어날 때 방출되는 이벤트를 수락한다.
GDK_FOCUS_CHANGE_MASK 포커스 이벤트의 변경을 수락한다.
GDK_STRUCTURE_MASK 창 설정의 내용이 변경될 때 방출되는 이벤트를 수락한다.
GDK_PROPERTY_CHANGE_MASK 객체 프로퍼티에 대한 변경을 수락한다.
GDK_VISIBILITY_NOTIFY_MASK 시각성(visibility) 이벤트에 대한 변경을 수락한다.
GDK_PROXIMITY_IN_MASK 마우스 커서가 위젯의 근처로 들어갈 때 방출되는 이벤트를 수락한다.
GDK_PROXIMITY_OUT_MASK 마우스 커서가 위젯의 근처에서 벗어날 때 방출되는 이벤트를 수락한다.
GDK_SUBSTRUCTURE_MASK 자식 창의 설정을 변경하는 이벤트를 수락한다.
GDK_SCROLL_MASK 모든 스크롤 이벤트를 수락한다.
GDK_ALL_EVENTS_MASK 모든 타입의 이벤트를 수락한다.
표 3-3. GdkEventMask 값


위젯에서는 gtk_widget_realize()를 호출하기 전에 gtk_widget_set_events()를 호출"해야만" 한다. 위젯이 이미 GTK+에 의해 실현(realized)된 경우 대신 gtk_widget_add_events()를 이용해 이벤트 마스크를 추가할 수 있다.


gtk_widget_realize()를 호출하기 전에는 개발자의 GtkEventBox는 아직 연관된 GdkWindow나 다른 GDK 위젯 자원을 갖고 있지 않다. 보통은 부모 위젯이 실현될 때 실현(realization)이 발생하지만 이벤트 박스는 예외다. 위젯에서 gtk_widget_show()를 호출하면 자동으로 GTK+에 의해 실현된다. 이벤트 박스는 개발자가 gtk_widget_show_all()을 호출할 때 실현되지 않는 것이 정상인데, 이는 invisible 상태로 설정되기 때문이다. 이벤트 박스에서 gtk_widget_realize()를 호출한다면 이러한 문제를 쉽게 해결할 수 있을 것이다.


개발자는 이벤트 박스를 실현 시 이벤트 박스가 자식으로서 최상위 수준 위젯에 이미 추가되어 있도록 확보해야 하는데, 그렇지 않을 경우 작동하지 않을 것이다. 그 이유는 개발자가 위젯을 실현하면 그 조상 위젯들도 자동으로 실현될 것이기 때문이다. 조상들이 없다면 GTK+는 만족하지 않을 것이며, 실현은 실패할 것이다.


이벤트 박스가 실현되고 나면 연관된 GdkWindow를 가질 것이다. GdkWindow는 화면에서 위젯이 그려지는 직사각형 영역을 나타내는 클래스다. 이것은 제목 표시줄 등이 포함된 최상위 수준의 창을 참조하는 GtkWindow와는 다르다. GtkWindow는 자식 위젯마다 하나씩, 수많은 GdkWindow 객체를 포함할 것이다. 이들은 화면에 위젯을 그리는 데에 사용된다.


GtkLabel 위젯을 클릭할 수 있도록 허용하는 김에 마우스를 라벨 위에서 왔다 갔다하면 마우스가 손모양(hand)으로 변하도록 만들어도 괜찮은데 이는 gdk_window_set_cursor()와 gdk_cursor_new()를 이용해 실행한다. GDK에서 이용 가능한 커서 타입에는 여러 가지가 있다. 이용 가능한 커서의 전체 리스트를 확인하려면 API 문서에서 GdkCursorType 목록을 살펴본다.

gdk_window_set_cursor (eventbox->window, gdk_cursor_new (GDK_HAND1));


Gtkd note.png GtkWidget 구조에는 다수의 public member가 포함된다. 그 중 하나는 창(window)으로, 주어진 위젯과 연관된 GdkWindow에 해당한다. 앞의 코드에서 새 커서는 이벤트 박스의 GdkWindow와 연관되어 있었다.


자신의 이해도 시험하기

본 장에서는 GTK+에 포함된 여러 컨테이너 위젯을 소개하였다. 아래 실린 두 연습문제는 새롭게 학습한 몇 가지 위젯을 실습하도록 도와준다.


연습문제 3-1. 다수의 컨테이너 사용하기

컨테이너의 중요한 특성 중 하나는 각 컨테이너가 다른 컨테이너들을 보유할 수 있다는 점이다. 이를 명확히 이해하기 위해 이번 예제에서는 수많은 컨테이너를 이용하게 될 것이다. 메인 창은 GtkNotebook과 함께 하단에 두 개의 버튼을 표시할 것이다.


노트북은 4개의 페이지를 가져야 한다. 각 노트북 페이지는 다음 페이지로 이동하는 GtkButton을 포함해야 한다(마지막 페이지의 GtkButton은 첫 페이지를 래핑해야 한다.)


창의 하단 부분을 따라 두 개의 버튼을 생성한다. 첫 번째 버튼은 GtkNotebook에서 이전 페이지로 이동하며 필요 시 마지막 페이지로 래핑한다. 두 번째 버튼을 클릭하면 창을 닫고 애플리케이션을 나가야 한다.


연습문제 3-1은 구현하기에 간단한 애플리케이션이지만 몇 가지 주요점을 설명한다. 첫째, 이것은 GtkVBox와 GtkHBox의 유용성을 보여주고, 어떻게 이들을 함께 사용하여 복잡한 사용자 인터페이스를 생성하는지 보여준다.


GtkTable을 이용해 창의 직계 자손으로 구현하여 위와 동일한 애플리케이션을 구현하는 방법도 있지만 수평 박스를 이용해 하단에 버튼을 정렬하는 편이 훨씬 수월하다. 버튼들은 박스 끝에 패킹되어 있어 박스의 우측면을 따라 정렬되고, 박스를 이용해 구현하는 편이 훨씬 수월하다는 사실을 눈치챌 것이다.


컨테이너는 다른 컨테이너를 포함할 수 있고 또 그래야 한다는 사실을 확인하였을 것이다. 가령 연습문제 3-1에서 GtkWindow는 GtkVBox를 포함하고, 이는 또 GtkHBox와 GtkNotebook을 포함한다. 이러한 구조는 애플리케이션의 크기가 커지면서 점점 더 복잡해지기도 한다.


연습문제 3-1을 완료했다면 연습문제 3-2로 넘어가라. 다음 문제에서는 수직 박스 대신 패인(paned) 컨테이너를 사용하게 될 것이다.


연습문제 3-2. 좀 더 복잡한 컨테이너

이번 연습문제에서는 연습문제 3-1에서 작성한 코드를 확장할 것이다. 버튼으로 된 수평 박스와 노트북을 포함하기 위해 GtkVBox를 사용하는 대신 GtkVPaned 위젯을 생성하라.


이러한 변경 외에도 GtkNotebook 탭을 숨겨 사용자가 버튼을 누르지 않고서는 페이지 간 전환을 할 수 없도록 해야 한다. 이런 경우 페이지가 언제 변경되는지 본인은 알지 못할 것이다. 따라서 GtkNotebook 페이지에 있는 각 버튼은 그 고유의 익스펜더에 의해 포함되어야 한다. 익스펜더 라벨은 노트북 페이지 간 구별을 허용한다.


연습문제 3-2를 완료했다면 GtkBox, GtkPaned, GtkNotebook, GtkExpander를 이용해 연습할 것인데, 이들은 본 저서의 나머지 부분에서 계속 사용될 중요한 컨테이너들이다.


다음 장으로 넘어가기 전에, 이번 장에서 다루었지만 연습문제 3-1과 3-2에 필요하지 않았던 컨테이너 몇 가지를 시험해 볼 수도 있을 것이다. 추후 소개될 장들은 앞에서 소개한 정보를 검토하지 "않을 것이기" 때문에 그러한 실습을 통해 모든 컨테이너의 사용을 연습할 수 있다.


요약

이번 장에서는 두 가지 타입의 컨테이너 위젯, 즉 decorators와 레이아웃 컨테이너를 학습하였다. 본문에서 다룬 decorator 타입으로는 익스펜더, 핸들 박스, 이벤트 박스가 있다. 본문에서 다룬 레이아웃 컨테이너의 타입은 박스, 패인, 테이블, 고정 컨테이너, 노트북이 포함된다.


GtkLabel 이외에도 GDK 이벤트를 처리할 수 없는 위젯들이 있기 때문에 이벤트 박스 컨테이너는 후에 여러 장에서 살펴볼 것이다. 이는 해당 위젯들을 학습할 때 명시될 것이다. 본 장에서 다룬 컨테이너 대부분은 뒤에 여러 장에서 볼 수 있을 것이다.


해당 컨테이너들은 GTK+ 애플리케이션 개발에 필요하긴 하지만 컨테이너에 GtkLabel과 GtkButton 위젯을 표시하는 것만으로는 대부분의 애플리케이션에서 별로 유용하지 (또는 흥미롭지) 않다. 이러한 형태의 애플리케이션은 기본 사용자 상호작용을 넘어 그 이상을 제공하는 데에 거의 수고를 들이지 않는다.


따라서 다음 장에서는 사용자와 상호작용하도록 해주는 위젯을 학습할 것이다. 이러한 위젯으로는 버튼, 토글, 텍스트 엔트리, 스핀 버튼이 있다.


앞서 언급하였듯 제 4장으로 넘어가기 전에 컨테이너 위젯을 먼저 이해해야 한다. 뒤에 소개할 본문에서는 가장 중요한 컨테이너 위젯을 비롯해 이번 장에서 다룬 여러 개념들을 올바로 이해하고 있다고 가정할 것이다.


Notes