내맘대로 Widget 배치 (GtkFixed)

1. GtkContainer

GtkContainer가 뭐라구요? 네 맞습니다! 바로 다른 Widget을 포함하는 Widget을 위한 기본 Class이다. 아이구 똑똑들 하셔라~ (재미 없나요? -_-;) 지긋지긋하게 닳고 닳도록 들었다. 지놈은 실제 구현된것도 별로 없지만 GUI를 위한 기본 모태가 되는 아주 중요한 놈이다. 이는 추상적인 클래스로서 혼자서는 별다른 일을 하지 못하지만 GtkBin, GtkTable, GtkFixed등이 GtkContainer를 상속받는다. 강좌가 진행되며 잘 기억이 안나는건 다시한번 찾아봐주는 센스를 기대해본다. 'Gtk에서의 화면구성과 GtkContainer'와 '여러개의 Widget 배치 (GtkTable)'에 있다.

2. GtkTable 을 이용한 양식화된 배치

양식화된 배치

전장에서 GtkTable에 관해 말해보았다. 양식화된 형태로 먼저 Table을 구성하고 Widget을 원하는 Cell에 포함시켜 화면 표시를 위한 정보를 구성하는것이다. GtkBin을 상속받은 GtkWindow나 GtkButton등과 크게 다른점은 여러개의 Widget을 배치한다는것이다. 또 뒤에나올 GtkFixed와 다른점은 양식화된 형태로 Widget을 배치한다는 것이다. 이것은 또다른 큰 차이를 낳게 된다. 단순히 양식화된 형태에 widget을 넣는냐 내맘대로 마구 배치하느냐의 차이만 있는것은 아니라 상위 Container의 크기변경과도 관계가 있다. 이는 다음장으로 예정되어있는 "resize에서의 GtkTable과 GtkFixed"에서 자세히 설명해주도록 하겠다.

3. GtkFixed 를 이용하여 내맘대로 배치

후후후 드디어 화면 배치의 마지막 요소(내가 하게될 강좌에 한하여서이다.)인 GtkFixed에 대해서 알아볼 시간이 왔다.

여러개의 Widget을 포함

GtkFixed도 GtkContainer를 상속받는다. Widget을 포함 하는 Widget이라는 얘기이다. GtkTable처럼 여러개의 Widget을 포함 시킬 수 있다.

고정된 위치와 고정된 크기

GtkFixed의 가장 큰 특징은 내맘대로 배치할 수 있다는 것이다. 이는 실행중에도 마찬가지다 초기에 셋팅할때뿐만 아니라 실행중에도 내마음대로 위치를 바꾸고 크기를 조정할 수 있다. 내맘대로 배치 할 수 있다는것이 의미하는것이 무엇일까? 바로 Fixed라는 이름에서도 알 수 있듯이 GtkFixed내에 픽셀단위로 고정된 위치와 고정된 크기로 포함시킨다.

보이는 바와 같이 여러개의 Widget을 원하는 위치, 원하는 크기로 배치한다.

GtkFixed에 포함된 Widget은 저절로 변하지 않는다.

GtkTable은 자신의 크기 변하면 포함된 Widget들도 확대, 축소, 채우기의 설정에 따라 위치와 크기가 저절로 조정된다. 이와는 달리 GtkFixed는 원하는 위치와 원하는 크기로 포함시키고 GtkFixed의 크기가 변하더라도 포함된 Widget들은 위치와 크기가 그대로이다. 다만 이것은 GtkFixed 자신의 변화에 따라 포함된 Widget이 저절로 변하지 않는다는 것이지 GtkFixed내에 포함된 Widget들은 전혀 변경할 수 없다는 것이 아니다. 오히려 언제든 내가 원하는 위치에 내가 원하는 크기로 변경하여 배치시킬 수 있으며 이는 실행중에도 즉시 반영된다.

GtkTable의 경우 위의 그림과 같이 GtkTable의 크기가 늘어날경우 내부에 포함된 Widget들도 함께 늘어난다. 물론 무조건 늘어나는건 아니다. 확장, 축소, 채우기 등의 옵션에 의해 변한다. 아래의 그림을 보자.

GtkTable내에 포함된 Widget도 아무런 옵션을 주지 않은경우 크기가 변해도 위와같이 사이즈가 변하지 않는다.

혹시나 해서 한가지 말해줄것이 있다. 위의 경우처럼 GtkWindow 자체에 어떠한 Widget을 포함시킨경우 (여기서는 GtkTable) Window가 늘어나면 같이 늘어난다. 이것은 GtkBin의 기본속성이다. Container자체를 가득 채우게 되는것이다. 이는 옵션같은것으로 조정되는 성질의 것이 아니다. GtkBin을 상속받은 다른 Widget들에 다른 Widget을 포함시킬때도 모두 마찬가지이다.

GtkFixed의 크기가 늘어난 경우이다. 하지만 내부의 Widget들은 모두 그자리에 그 크기를 유지하고 있다. 이것은 옵션같은것으로 조정되는 성질의 것이 아니다.

위치의 이동과 크기의 변경

GtkFixed에 포함된 Widget은 GtkFixed의 크기가 변하더라도 저절로는 조정되지 않는다고 하였다. GtkFixed를 기준으로 위치와 크기가 고정되어 있다. 그럼 한번 셋팅하면 그만이라는 얘기인가? 설마~ 그렇다면 우리는 좌절할 수 밖에 없는 상황이 온다. (했던 얘기 또하고 했던 얘기 또하고 지겹겠지만 그냥 들었으면 한다 ㅡ.ㅡ) GtkFixed의 변화에 의해 포함된 Widget들이 저절로 조정되어지지 않을뿐 우리는 언제든 마음껏 Widget을의 위치와 크기를 변경할 수 있다. 역시나 내맘대로 이다.

Widget들은 서로간의 위치에 영향을 주지 않는다.

말 그대로 이다. Widget은 GtkFixed내에 어디에든 배치할 수 있다. Widget이 있는 영역에 다른 Widget을 배치한다면 그 위에 덮어져 자리하게 된다. GtkBin이나 GtkTable처럼 하나의 영역(GtkTable의 경우 Cell)에 하나의 Widget만 배치할 수 있는것이 아니다. GtkFixed내라면 어디든 상관없다. 심지어는 그 위치에 다른 Widget이 자리잡고 있어도 상관없다. 곂쳐지는 부분을 덮어쓸 뿐이다. GtkFixed는 커다란 하나의 영역만이 존재하고 그 내부에는 자유롭게 Widget을 포함시킬 수 있다.

'button1'과 'button2'가 일부 겹쳐져있다. 다른 Widget과 무관하게 어디든 위치를 정할 수 있다.

위치는 GtkFixed를 기준으로한 상대적인 위치

혹시라도 오해하는 사람이 있을까봐 설명을 해둔다. GtkFixed에서 말하는 위치란 GtkFixed를 기준으로한 상대적인 위치이다. 스크린 전체가 아니라 GtkFixed Widget을 기준으로 한다는것이다. 변경되지 않는다는 의미가 Window을 왼쪽으로 이동시켰는데 GtkFixed에 포함되어있던 Widget이 스크린을 기준으로 고자리에 고대로 덩그러니 있다는 얘기는 아니다 -_-; GtkFixed내에 포함된 Widget의 X위치가 0일때는 GtkFixed Widget내에서 0인것이지 화면 전체에서 0은 아니라는것이다. 고정이라고 하니 혹시라도 오해할 사람이 있을까봐 설명한다.

(넘 쓸대없나... 개발을 하려하는 사람중에 그런 사람이야 당연히 없겠지만 볼마우스 시절 컴퓨터를 처음사서 마우스를 허공에 번쩍 들어 움직이더니 컴퓨터가 고장났다는 사람도 있었다. 컴퓨터를 설치해주던 기사의 말을 들어보면 분명 자기가 처음 설치해줄때 쓰는것을 알려줬다고 하였다. 거침없는 오해 정말 무서운 것이다 --;)

GtkWidget* gtk_fixed_new (void);

GtkFixed를 생성하는 함수이다. 필요한 인자는 없다. 그렇다 GtkFixed는 복잡한듯 하지만 상당히 간편하고 유용한 Widget이다. GtkFixed는 단지 Widget을 포함시켜 배치하기 위한 판때기에 불과하다. GtkTable처럼 뭔가 칸이 쳐져있거나 옵션이 있는 것이 아니다. 단순하게 생각하면 된다. 그저 우리는 GtkFixed라는 판때기를 하나 만들고 판때기 위에 맘대로 가져다 붙이면 되는것이다. GtkBin이나 GtkTable처럼 한 영역에 (GtkTable의 경우에는 Cell)하나의 Widget만을 배치할 수 있는것이 아니고 마구 겹쳐넣는것도 된다. Widget을 포함시키는데 다른 Widget의 영향을 받지 않는다는것이다. 단 나중에 포함시킨 위젯이 기본적으로는 위로 온다.

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

Widget을 GtkFixed에 포함시키는 함수이다. fixed는 GtkFixed 자기 자신이고, widget은 포함시킬 widget이다. x는 가로 좌표이고 y는 세로 좌표이다. 흔히 생각하는 x, y좌표를 생각하면 된다. 이 좌표는 GtkFixed를 기준으로 하는 상대적인 좌표이다. x가 0이면 GtkFixed의 맨 왼쪽 첫번째 픽셀인것이지 화면전체 또는 GUI 어플리케이션 전체에서의 위치는 아니다. GtkFixed에서의 위치이다. GtkFixed가 좌우 두개로 나누어진 GtkTable의 오른쪽에 포함되어 들어갈 수도 있는것이고, GtkWindow에 그냥 전체로 자리잡을 수도 있는것이다. GtkFixed이든 GtkTable이든 그외 무엇이든 소수의 일부 Widget을 제외하고는 그저 Widget일 뿐이고 Widget이 가지는 모든 특성을 그대로 가지고 있다. 상대적인 위치임을 설명하기 위해 말이 길었는데 GtkFixed를 기준으로한 상대적인 x, y 좌표라는것만 기억하고 이해하면 무리가 없다.

void gtk_fixed_move (GtkFixed *fixed, GtkWidget *widget, gint x, gint y);

한가지 기억해보자. 위에서 GtkFixed는 언제라도 위치를 변경할 수 있다 하였다. 이함수를 호출하여 GtkFixed내에 이미 포함되어진 Widget의 위치를 이동시키는것이다. 이는 실행중에라도 상관없으며 함수를 호출하게 되면 즉시 반영된다. 한가지 주의할것은 두번째 파라미터인 widget은 'gtk_fixed_put' 함수를 이용하여 이미 포함되어진 widget이어야한다.

GtkFixed내에 'button1'과 'button2'가 포함되어져 있다고 하자. 'button2'의 'clicked' Signal Callback 함수내에 gtk_fixed_move 함수를 호출하여 'widget' 파라미터에 'button1'을 넣어주면 'button1'이 GtkFixed를 기준으로 x와 y에 해당하는 위치로 이동하는것이다. 이는 예제에서 구현하여 확인해 주겠다.

gtk_fixed_move 함수를 호출하면 위와같이 된다. 'button2'를 주목

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

GtkFixed와는 무관하게 GtkWidget에 해당하는 함수이다. GtkWidget의 최소 크기를 변경한다. 말그대로 최소크기이다 GtkTable처럼 포함되어진 Widget의 크기가 유동적인 경우 원래의 목적대로 최소 사이즈의 역할을 한다. GtkTable이 줄어들어도 이 사이즈 밑으로는 줄지 않는다. 그럼 GtkFixed에서는 어떨까? 최소사이즈를 지정하는 것이지만 GtkFixed의 경우 고정된 사이즈이다보니 이 함수를 호출하여 GtkWidget의 크기를 변경하게 되면 이 크기를 유지하게 된다. GtkWidget의 크기를 변경하는 방법은 여러가지가 있지만 이것이 제일 간단하니 일단 이것으로 설명을 하겠다.

이정도 되면 더이상 설명할 필요는 없겠으나 참고로 GTK에서는 화면의 구성요소인 Widget들은 GtkWidget을 상속받으므로 GtkWidget이 하는일은 모두 할 수 있다. GtkButton, GtkLabel, GtkTable, GtkEntry 등등 모든 Widget이 이 함수로 크기가 변한다.

gtk_widget_set_size_request 함수를 호출하면 위와같이 된다. 'button2'를 주목

간단히 정리해보자

gtk_fixed_new : GtkFixed 생성

gtk_fixed_put : GtkFixed내에 Widget을 포함시킨다.

gtk_fixed_move : GtkFixed내에 이미 포함되어진 Widget의 위치를 이동시킨다.

gtk_widget_set_size_request : GtkWidget의 최소크기를 변경한다. GtkFixed는 고정크기이므로 이크기가 유지된다.

4. GtkEntry

한줄의 텍스트를 입력할 수 있는 Widget

우리가 흔히 생각하는 입력 양식 Widget중 하나이다. 단 한줄만 입력할 수 있다. 해당 Widget에 포커스와 커서를 이동시키고 키보드를 쳐서 입력하거나 복사 붙이기 등을 사용한다.

GtkWidget* gtk_entry_new ();

GtkEntry를 생성하는 함수이다. 반환 타입은 GtkWidget의 포인터이다.

const char* gtk_entry_get_text (GtkEntry *entry);

GtkEntry에 입력되어 있는 Text를 반환하는 함수이다. 반환 타입은 char의 포인터이다. (문자열)

void gtk_entry_set_text (GtkEntry *entry, const char *text);

GtkEntry의 Text를 변경하는 함수이다. 특이한 사항은 없고, 'text' 파라미터에 변경할 Text의 char 포인터를 넣어준다. (이것도 문자열)

4. 사용할 Signal

"clicked" : RUN_FIRST

gboolean

cb_button1_clicked (GtkWidget *widget, gpointer data)

{

GtkWidget *button = (GtkWidget *) data;

gtk_widget_set_size_request (button, 50, -1);

}

GtkButton등에서 마우스를 클릭했을때 발생하는 Signal이다. Callback 함수의 형식은 아래와 같다.

void user_function (GtkWidget *widget, gpointer data);

"activate" : RUN_LAST

void

cb_entry_activate (GtkWidget *widget, gpointer data)

{

GtkWidget *label = (GtkWidget *) data;

const gchar *entry_text = gtk_entry_get_text (GTK_ENTRY (widget));

gtk_label_set_text (GTK_LABEL (label), entry_text);

}

GtkEntry에서 키보드의 'Enter'키가 눌러졌을때 발생하는 Signal이다. Callback 함수의 형식은 아래와 같다.

"void user_function (GtkWidget *widget, gpointer data);

GtkEntry외에 다른곳에서도 사용하는지는 기억해내지 못했다. -_-;

5. 지겨운 "Hi 똘츄~"

오늘만들건 요 비슷한거

#include <gtk/gtk.h>

/*

* GtkFixed를 Global로 선언한다.

* Callback 함수에서 여러개의 Widget을 사용할 일이 있어서이다.

* 이는 단순한 예제를 위하여 사용하는것이니 Global은 가급적 삼가토록 한다.

* Callback에서 여러개의 Widget을 가져오는 방법은 후에 여러가지를 알려주겠다.

*/

GtkWidget *fixed = NULL;

// Window의 'destroy' Signal 발생시 호출될 Callback 함수

void

cb_window_destroy (GtkWidget *widget, gpointer data)

{

// Main Event Loop 종료 (프로그램의 종료)

gtk_main_quit ();

}

// Button1의 'clicked' Signal 발생시 호출될 Callback함수

gboolean

cb_button1_clicked (GtkWidget *widget, gpointer data)

{

// g_signal_connect에서 넘어온 사용자 데이터(Button2)를 GtkWidget의 포인터로 타입 캐스팅

GtkWidget *button = (GtkWidget *) data;

/*

* GtkWidget의 크기를 변경하는 함수

* 넓이는 50으로 변경하고 높이는 변경하지 않는다.

* 넓이와 높이의 값이 -1이면 변경하지 않는다

*/

gtk_widget_set_size_request (button, 50, -1);

/*

* 반환값이 없다. default handler는 무조건 호출된다.

*/

}

// Button2의 'clicked' Signal 발생시 호출될 Callback함수

gboolean

cb_button2_clicked (GtkWidget *widget, gpointer data)

{

// g_signal_connect에서 넘어온 사용자 데이터(entry)를 GtkWidget의 포인터로 타입 캐스팅

GtkWidget *entry = (GtkWidget *) data;

/*

* GtkFixed내에 포함된 Widget의 위치를 이동시키는 함수

* fixed는 위에서 선언한 글로벌 변수이다.

*/

gtk_fixed_move (GTK_FIXED (fixed), entry, 10, 50);

// GtkEntry의 Text를 변경하는 함수

gtk_entry_set_text (GTK_ENTRY (entry), "오!! Entry의 텍스트가 바꼈습니다.");

/*

* 반환값이 없다. default handler는 무조건 호출된다.

*/

}

// entry의 'activate' Signal 발생시 호출될 Callback함수

void

cb_entry_activate (GtkWidget *widget, gpointer data)

{

// g_signal_connect에서 넘어온 사용자 데이터(label)를 GtkWidget의 포인터로 타입 캐스팅

GtkWidget *label = (GtkWidget *) data;

// 자기 자신(entry)의 Text를 가져온다.

const gchar *entry_text = gtk_entry_get_text (GTK_ENTRY (widget));

// label의 Text를 entry의 Text롤 변경한다.

gtk_label_set_text (GTK_LABEL (label), entry_text);

/*

* 반환값이 없다. default handler는 무조건 호출된다.

*/

}

int

main (int argc, char *argv[])

{

// 대부분 Widget의 생성후 반환값 형식은 GtkWidget이므로 GtkWidget의 포인터로 변수 선언

GtkWidget *window = NULL;

GtkWidget *label = NULL;

GtkWidget *entry = NULL;

GtkWidget *button1 = NULL;

GtkWidget *button2 = NULL;

gtk_init (&argc, &argv);

// 기본 Window(GtkWindow) 생성

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

/*

* Label(GtkLabel) 생성

*/

label = gtk_label_new ("Hi 똘츄~");

/*

* label의 최소 크기를 변경한다.

* GtkFixed내에 포함되는 Widget들은 왠만하면 지정해주는것이 좋다.

* 높이는 기본크기 사용

*/

gtk_widget_set_size_request(label, 100, -1);

gtk_widget_show (label);

/*

* Entry(GtkEntry) 생성

*/

entry = gtk_entry_new ();

/*

* entry의 최소 크기를 변경한다.

* GtkFixed내에 포함되는 Widget들은 왠만하면 지정해주는것이 좋다.

* 높이는 기본크기 사용

*/

gtk_widget_set_size_request(entry, 150, -1);

gtk_widget_show (entry);

/*

* Button1(GtkButton) 생성

* 생성과 label변경을 한번에 처리할 수 있는 gtk_button_new_with_label 함수를 썼다.

*/

button1 = gtk_button_new_with_label ("Button1");

/*

* Button1의 최소 크기를 변경한다.

* GtkFixed내에 포함되는 Widget들은 왠만하면 지정해주는것이 좋다.

* 높이는 기본크기 사용

*/

gtk_widget_set_size_request(button1, 70, -1);

gtk_widget_show (button1);

/*

* Button2(GtkButton) 생성

* 생성과 label변경을 한번에 처리할 수 있는 gtk_button_new_with_label 함수를 썼다.

*/

button2 = gtk_button_new_with_label ("Button2");

/*

* Button2의 최소 크기를 변경한다.

* GtkFixed내에 포함되는 Widget들은 왠만하면 지정해주는것이 좋다.

*/

gtk_widget_set_size_request(button2, 100, 100);

gtk_widget_show (button2);

/*

* Fixed(GtkFixed) 생성

* fixed변수는 최상단에 Global로 선언

*/

fixed = gtk_fixed_new ();

gtk_widget_show (fixed);

/*

* fixed의 최소 크기를 변경한다.

* gtk_widget_set_size_request는 GtkFixed내에 포함된 Widget에만 국한된것이 아니다.

어느 Widget이든 원하면 지정할 수 있다.

*/

gtk_widget_set_size_request (fixed, 300, 300);

// 기본 Window에 Fixed를 넣어준다.(GtkContainer에 대해서는 이미 설명하였으므로 생략한다)

gtk_container_add (GTK_CONTAINER (window), fixed);

/*

* GtkFixed에 label을 child widget으로 포함시킨다.

* 가로 위치 10 pixel, 세로 위치 10 pixel이다.

*/

gtk_fixed_put (GTK_FIXED (fixed), label, 10, 10);

/*

* GtkFixed에 entry를 child widget으로 포함시킨다.

* 가로 위치 120 pixel, 세로 위치 50 pixel이다.

*/

gtk_fixed_put (GTK_FIXED (fixed), entry, 120, 50);

/*

* GtkFixed에 Button1을 child widget으로 포함시킨다.

* 가로 위치 20 pixel, 세로 위치 150 pixel이다.

*/

gtk_fixed_put (GTK_FIXED (fixed), button1, 20, 150);

/*

* GtkFixed에 Button2을 child widget으로 포함시킨다.

* 가로 위치 70 pixel, 세로 위치 160 pixel이다.

*/

gtk_fixed_put (GTK_FIXED (fixed), button2, 70, 160);

// Signal 연결에 대해서는 전장인 "Signal & Callback & Handler"에서 설명하였다

// 기본 Window의 X 버튼 클릭시 실행할 Callback 함수 연결

g_signal_connect (G_OBJECT (window), "destroy",

G_CALLBACK (cb_window_destroy), NULL);

/*

* entry에서 Enter를 입력하였을때 실행할 Callback 함수 연결

* 사용자 데이터로 마지막 파라미터에 label(포인터)를 넘긴다.

*/

g_signal_connect (G_OBJECT (entry), "activate",

G_CALLBACK (cb_entry_activate), label);

/*

* Button1을 마우스 버튼으로 클릭했을때 실행할 Callback 함수 연결

* 사용자 데이터로 마지막 파라미터에 button2(포인터)를 넘긴다.

*/

g_signal_connect (G_OBJECT (button1), "clicked",

G_CALLBACK (cb_button1_clicked), (gpointer) button2);

/*

* Button2를 마우스 버튼으로 클릭했을때 실행할 Callback 함수 연결

* 사용자 데이터로 마지막 파라미터에 entry(포인터)를 넘긴다.

*/

g_signal_connect (G_OBJECT (button2), "clicked",

G_CALLBACK (cb_button2_clicked), (gpointer) entry);

// Window를 화면에 표시한다.

gtk_widget_show (window);

/*

* Main Event Loop 생성 및 실행

* gtk_main_quit 함수가 호출될때까지 다음 문장으로 진입할 수 없다.

* 실질적으로 이때 화면상에 UI가 표시된다.

*/

gtk_main ();

// gtk_main_quit 함수가 호출되면 여기로 진입하게 된다.

return 0;

}

컴파일

$gcc `pkg-config --cflags --libs gtk+-2.0` hi3.c -o hi3

컴파일에 관한 자세한 내용은 앞에서 설명했으니 생략한다.

실행

$./hi3

이와 유사한 화면이 나타날것이다. 실제로는 조금 다르겠지만 (사실 많이 -_-;)

'button1'을 클릭해보자. 'button2'의 넓이가 줄어들것이다. 'clicked' Signal이 발생하여 g_signal_connect로 연결된 Callback 함수인 'cb_button1_clicked' 함수가 호출된다. Callback 함수 내에 gtk_widget_set_size_request 함수를 호출하여 'button2'의 크기를 변경하도록 구현하였다.

'button2'버튼을 클릭해보자. entry가 왼쪽으로 이동하고 entry의 내용이 변경되는 것을 볼 수 있다. 구구절절한 설명은 button1과 같고 Callback 함수내에 구현된 부분이 다르다. gtk_fixed_move 함수를 호출하여 'entry'의 위치를 이동시키고, gtk_entry_set_text 함수를 호출하여 'entry'의 Text를 변경한다.

'entry'를 마우스로 한번 클릭해보자. 포커스를 이동시키고 커서를 위치하기 위함이다. 이제 키보드에서 'Enter'키를 눌러보자. 'label'의 Text가 바뀌는것을 볼 수 있다. 'activate' Signal이 발생하여 g_signal_connect로 연결된 Callback 함수인 'cb_entry_activate' 함수가 호출된다. Callback 함수 내에 gtk_label_set_text 함수를 호출하여 'label'의 Text를 변경하도록 구현하였다.

GtkFixed는 그다지 어려운것이 아니다. 그냥 고정된 위치와 크기로 Widget을 포함 할 수 있는 Container라고 생각하면 된다.