Blog | Tag | Local | Guest | Login | Write |  RSS
Packing Widget _ Box Packing


안녕하세요.
저번시간에는 아주 간단하지만 중요한 이야기를 했었죠? 저번시간에 이야기했던 Packing에 대해서 좀더 자세히 공부해 볼까 합니다. 우리가 어떤 어플리케이션을 개발하면서, 하나의 윈도우에 하나 이상의 버튼을 놓으려 할 것입니다. 우리의 첫번째 hello world 예제는 하나의 widget만 썼었죠. 그러나 우리가 하나의 윈도우에 더많은 widget을 놓으려 할 때, 어떻게 그들이 놓일 위치를 제어해야 할까? 여기서 바로 "packing"이란 것이 등장한죠!!

1. 박스 packing의 원리
대부분의 은 앞서의 예제에서처럼(저번주 강의) 박스를 만드는 것으로 이루어집니다. 이들은 우리의 widget을 수평 혹은 수직 방향으로 패킹해 넣을 수 있는, 보이지 않는 widget 컨테이너들이었죠. 수평 박스로의 패킹 widget인 경우, object는 호출 하는 방식에 따라 수평으로 왼쪽에서 오른쪽으로 혹은 오른쪽에서 왼쪽으로 삽입 됩니다. 수직 박스에서는 반대로 수직으로 삽입됩니다. 우리는 원하는 효과를 내기 위해 다른 박스들의 안팎에서 어떻게라도 조합해서 사용할 수 있겠죠?
새로운 수평박스를 만들기 위해 우리는 gtk_hbox_new()를, 그리고 수직박스를 위해서는 gtk_vbox_new()를 이용합니다. gtk_box_pack_start()와 gtk_box_pack_end ()는 이런 컨테이너의 내부에 object들을 위치시키기 위해 사용합니다. gtk_box_ pack_start()함수는 수직박스에서는 위에서 아래쪽으로, 그리고 수평박스에서는 왼쪽에서 오른쪽으로 패킹할 것입니다. 그리고 gtk_box_pack_end()는 이와 반대 방향으로 패킹합니다. 이 함수들을 이용함으로써 우리는 오른쪽 또는 왼쪽으로 widget을 정렬할 수 있고, 원하는 효과를 낼 수 있습니다. 우리는 대부분의 예제에서 gtk_box_pack_start()를 이용할 것입니다. Object는 또다른 컨테이너이거나 widget이 될 수 있습니다. 그리고 사실, 많은 widget들은 실제로 버튼을 포함하고 있는 widget 이지만, 우리는 보통 버튼 안의 라벨만을 이용할 것입니다.
이런 함수호출로써, GTK는 우리가 widget을 놓을 위치를 알게되고 따라서 자동적으로 크기를 조절한다든지 또다른 매력적인 일들을 할 수 있게 됩니다. 또한 우리의 widget이 어떻게 패킹되어야 하느냐에 따른 수많은 옵션들도 있습니다. 우리가 상상하듯이, 이런 방식은 widget을 놓고 만드는 데 있어서 상당한 유연성 을 제공해 줍니다. 매우 기특한 녀석이죠? ㅋㅋ 

2. Box에 대해서 좀 더 알아볼까요?
이런 유연성 때문에, GTK에서 박스를 패킹하는 것은 처음엔 혼란스러울지 모릅니다. 많은 옵션들이 있으며, 그들이 어떻게 서로 꿰어 맞춰지는지 바로 알 수는 없을 것입니다. 그러나 결국, 우리는 다섯 가지의 기본적인 스타일을 가지게 됩니다. 


각의 줄은 몇 개의 버튼을 가지고 있는 하나의 수평박스(hbox)를 포함합니다. 함수호출 gtk_box_pack은 이 수평박스에 각각의 버튼을 패킹하는 것을 단축한 것입니다. 각각의 버튼은 이 수평박스에 같은 방법으로 패킹됩니다.
이것은 gtk_box_pack_start함수의 선언이다.

void gtk_box_pack_start (GtkBox    *box,
                         GtkWidget *child,
                         gint       expand,
                         gint       fill,
                         gint       padding);

첫번째 인자는 object를 패킹할 박스고 두번째는 그 object입니다. Object는 여기서 모두 버튼이 될 것이고, 따라서 우리는 박스안에 버튼들을 패킹하게 됩니다.
gtk_box_pack_start() 또는 gtk_box_pack_end()에서의 expand라는 인자가 TRUE 일 때, widget은 여백공간을 가득 채우며 박스에 들어가게 될 것입니다. 그리고 그것이 FALSE라면 widget은 적절히 여백을 두게 된다. 이 expand를 FALSE로 두면 우리는widget의 좌우 정렬을 결정할 수 있습니다. 그렇지 않으면 그들은 박스에 가득차서 gtk_box_pack_start 또는 gtk_box_pack_end 어느 쪽을 이용하든지 같은 효과를 가지게 됩니다.
인자 fill은 TRUE일 때 object 자신의 여백공간을 제어합니다. 그리고 FALSE라면 object 자신의 여백공간을 두지 않습니다. 이것은 expand 인자가 TRUE일 때만 효과가 있어요.

새로운 수평박스를 만들 때는 이런 함수가 있습니다.
GtkWidget * gtk_hbox_new (gint homogeneous,
                          gint spacing);

여기서의 인자 homogeneous는 박스 안의 각 object들이 같은 크기를 가지도록 제어합니다. 즉 수평박스일 경우엔 같은 너비, 수직박스일 경우엔 같은 높이이겠죠? 이것이 세팅되면, gtk_box_pack함수의 expand 인자는 언제나 TRUE가 됩니다.

3. 패킹에 대한 예제 프로그램
/* packbox.c */

#include "gtk/gtk.h"

void
delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
{
  gtk_main_quit ();
}

/* Button_label들로 이루어진 hbox를 만듭니다.  */
GtkWidget *make_box (gint homogeneous, gint spacing,
                     gint expand, gint fill, gint padding)
{
  GtkWidget *box;
  GtkWidget *button;
  char padstr[80];

  /* 적당한 homogenous와 spacing을 가진 hbox를 만듭니다. */
  box = gtk_hbox_new (homogeneous, spacing);

  /* 적절히 세팅된 버튼들을 만듭니다. */
  button = gtk_button_new_with_label ("gtk_box_pack");
  gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
  gtk_widget_show (button);

  button = gtk_button_new_with_label ("(box,");
  gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
  gtk_widget_show (button);

  button = gtk_button_new_with_label ("button,");
  gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
  gtk_widget_show (button);

  /* expand의 값에 따르는 라벨을 가진 한 버튼을 만듭니다. */
  if (expand == TRUE)
    button = gtk_button_new_with_label ("TRUE,");
  else
    button = gtk_button_new_with_label ("FALSE,");

  gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
  gtk_widget_show (button);

    button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,");
  gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
  gtk_widget_show (button);

  sprintf (padstr, "%d);", padding);

  button = gtk_button_new_with_label (padstr);
  gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
  gtk_widget_show (button);

  return box;
}

int
main (int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *button;
  GtkWidget *box1;
  GtkWidget *box2;
  GtkWidget *separator;
  GtkWidget *label;
  GtkWidget *quitbox;
  int which;

  gtk_init (&argc, &argv);

  if (argc != 2) {
    fprintf (stderr, "usage: packbox num, where num is 1, 2, 3.\n");

    /* GTK를 끝내는 부분이며, exit status는 1입니다. */
    gtk_exit (1);
  }

  which = atoi (argv[1]);

    /* 윈도우를 만듭니다. */
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

  /* Main 윈도에 destroy 시그널을 연결시켜 줘야 합니다.  이것은 제대로 된 동작을 위해 매우 중요한 것입니다. */
  gtk_signal_connect (GTK_OBJECT (window), "delete_event",
                      GTK_SIGNAL_FUNC (delete_event), NULL);
  gtk_container_border_width (GTK_CONTAINER (window), 10);

  /* 우리는 수평박스들을 패킹해 넣을 수직박스(vbox)를 만듭니다.저번시간에도 말씀 드렸듯이, 스택 구조를 생각하면 될 것입니다. */
  box1 = gtk_vbox_new (FALSE, 0);

  switch (which) {
  case 1:
        /* 새로운 라벨을 만듭니다. */
        label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");

        /* 라벨들을 왼쪽으로 정렬시킵니다. */
        gtk_misc_set_alignment (GTK_MISC (label), 0, 0);

        /* 라벨을 수직박스(vbox box1)에 패킹합니다.  */
        gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);

        /* 라벨을 보여줍니다. */
        gtk_widget_show (label);

        /* make_box 함수를 적절한 인자로써 호출합니다. */
        box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);

        box2 = make_box (FALSE, 0, TRUE, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);

        box2 = make_box (FALSE, 0, TRUE, TRUE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);

        /* 하나의 separator를 만듭니다 */
        separator = gtk_hseparator_new ();

        /* separator를 vbox 안으로 패킹합니다.  이들 각각의 widget은 vbox 안으로 패킹되므로, 수직 방향으로 쌓일 것입니다. */
        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);

        /* 또다른 라벨을 만들어 그것을 보여줍니다. */
        label = gtk_label_new ("gtk_hbox_new (TRUE, 0);");
        gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
        gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
        gtk_widget_show (label);

        /* 각 인자는 homogeneous, spacing, expand, fill, padding입니다. */
        box2 = make_box (TRUE, 0, TRUE, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);

        box2 = make_box (TRUE, 0, TRUE, TRUE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);

        /* 또다른 separator */
        separator = gtk_hseparator_new ();

        /* gtk_box_pack_start 의 마지막 3가지 인자들은 expand, fill, padding 입니다. */
        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);

        break;

    case 2:

        /* 라벨을 새로 만든다. box1은 main()의 시작부분에서 만들어진대로 vbox이다. */
        label = gtk_label_new ("gtk_hbox_new (FALSE, 10);");
        gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
        gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
        gtk_widget_show (label);

        box2 = make_box (FALSE, 10, TRUE, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);

        box2 = make_box (FALSE, 10, TRUE, TRUE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);

        separator = gtk_hseparator_new ();
        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);

        label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
        gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
        gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
        gtk_widget_show (label);

        box2 = make_box (FALSE, 0, TRUE, FALSE, 10);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);

        box2 = make_box (FALSE, 0, TRUE, TRUE, 10);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);

        separator = gtk_hseparator_new ();
        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);
        break;

    case 3:
        /* 이것은 gtk_box_pack_end()를 이용하여 widget을 오른쪽 정렬하는 걸 보여줍니다.  먼저, 앞에서처럼 새로운 박스를 하나 만듭니다. */
        box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
        /* 라벨을 하나 만듭니다. */
        label = gtk_label_new ("end");
        /* 그것을 gtk_box_pack_end()로써 패킹하므로, make_box()로 만들어진
         * hbox의 오른쪽으로 놓여지게 됩니다.
        gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0);
        /* 라벨을 보입니다. */
        gtk_widget_show (label);

        /* box2를 box1 안으로 packing합니다. */
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);

        /* bottom 쪽을 위한 separator. */
        separator = gtk_hseparator_new ();

        /* 이것은 400픽셀의 너비에 5픽셀의 높이(두께)로 separator를 세팅합니다. 이것은 우리가 만든 hbox가 또한 400픽셀의 너비이기 때문이고, "end" 라벨은 hbox의 다른 라벨들과 구분될(separated)것입니다.  그렇지 않으면, hbox 내부의 모든 widget들은 가능한만큼 서로 빽빽히 붙어서 패킹될 것입니다. */
        gtk_widget_set_usize (separator, 400, 5);

        /* main()함수의 시작부분에서 만들어진 vbox(box1)으로 separator를 패킹합니다. */
        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);
    }

    /* 또다른 hbox를 만듭니다. */
    quitbox = gtk_hbox_new (FALSE, 0);

    /* 우리의 quit 버튼입니다. */
    button = gtk_button_new_with_label ("Quit");

    /* 윈도를 파괴하기 시그널을 세팅합니다.  이것은 위에서 정의된 우리의 시그널 핸들러에 의해 포착될, "destroy"시그널을 윈도로 보내줍니다. */
    gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
                               GTK_SIGNAL_FUNC (gtk_widget_destroy),
                               GTK_OBJECT (window));

    /* quitbox로 버튼을 패킹합니다.  gtk_box_pack_start의 마지막 세 인자는 expand, fill, padding입니다. */
    gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0);
    /* vbox(box1) 안으로 quitbox를 패킹합니다. */
    gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0);

    /* 우리의 모든 widget을 포함하게 된 이 vbox를, main윈도로 패킹. */
    gtk_container_add (GTK_CONTAINER (window), box1);

    /* 그리고 남아있는 모든 것을 보여줍니다. */
    gtk_widget_show (button);
    gtk_widget_show (quitbox);

    gtk_widget_show (box1);
    /* 마지막에 윈도를 보여줘서 모든 것이 한번에 튀어나오며 보입니다. */
    gtk_widget_show (window);
    gtk_main ();

    /* gtk_main_quit()을 호출했다면 제어는 이곳으로 오게되고,  gtk_exit()를 호출하면 그렇지 않습니다. */

    return 0;
}


역시나도 재미있는것 같아요 ㅋ 저는 인터넷에서 boxpacking 예제를 참고하였어요 ^ ^ 보고 따라하기식으로 하면서 몇가지 추가하고 바꿨더니 위의 화면과는 조금 다른 것이 나오더라구요 ^ ^ 오늘 강의에 사용한 예제를 제대로 알고있다면 몇가지 바꿔보는건 매우 쉬운듯 해요 ^  ^ 아아아 ㅋㅋ 뭔가 긴 것 같지만, 반복적인 소스들, ㅋ (아! 간단하구나?)라는 생각이 들정도 ㅋ
점점 GTK+ 매력에 빠져들고 계신지요? ㅋㅋㅋㅋㅋ