Signal & Callback & Handler

이 기사를 쓰기 위해 루나씨를 이틀간 너무 귀찮게했다. 이게 다 delete_event 때문이다 --; 나의 잠시간의 헷갈림으로 인해 안드로메다에 다녀왔다. 너무 많이 알려하지 말길 바란다.

1. Signal에 Callback 함수 연결

'g_signal_connect'와 'g_signal_connect_after'는 Signal에 사용자 정의 handler인 Callback 함수를 연결해준다.

이미 Event와 Signal, 그리고 Main Event Loop에 대해 간략히 알아보았다. 그때 Event가 생겨 Signal이 발생하면 Callback 함수를 호출한다 하였는데 바로 이 Callback 함수를 원하는 Signal에 연결하는것이다. 연결된 Callback 함수는 해당 Signal이 발생할때마다 호출된다.

Signal의 종류에 따라 Callback 함수의 형식은 이미 정해져있다. 해당 Callback 함수를 형식에 맞추어 작성하고 g_signal_connect 함수를 호출할때 Callback 함수의 포인터를 파라미터로 넘겨준다.

g_signal_connect(instance, detailed_signal, c_handler, data)

void callback_destroy (GtkWidget *widget, gpointer data {

gtk_main_quit ();

}

...

GtkWidget *window;

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

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

...

"destroy"는 Widget이 제거될때 발생하는 Signal이다. 형식은 위에서 보는 바와 같이 "void user_function (GtkWidget *widget, gpointer data)"이다.

default handler

Signal이 발생할때 우리가 g_signal_connect로연결한 Callback 함수만 호출하고 끝나는것이 아니다. 사용자 정의 handler라고 할 수 있는 Callback 함수를 호출하기도 하고 기본적으로 처리해야할 것들이 정의된 default handler를 호출하기도 한다. Signal이 발생했을때 기본적으로 처리해야할 작업이 default handler에 정의되어있는 것이다.

앞서 우리는 Signal이 발생했을때 호출하도록 g_signal_connect로 Callback 함수를 연결할 수 있는것을 보았다. 이것과는 별개로 Signal이 발생했을때 Widget이 해야할 가장 기본적인 기능들이 있다. GtkButton의 경우 마우스로 버튼을 누르게 되면 버튼이 눌러진 모양이 되어야한다.

Widget의 형태에 따라 기본적으로 해야할 작업이 정의 되어 있고, Signal 발생시 Callback 함수와 함께 호출되는것이 바로 default handler이다.

default handler는 같은 Signal이라고 해서 다 똑같은 것은 아니다. GtkWidget에서는 Signal을 등록만 하고 default handler는 지정하지 않는 경우가 있다. 'button-press-event'가 그러하다. Widget을 마우스의 버튼으로 눌렀을때 GtkButton이 기본적으로 해야할일과 GtkEntry가 기본적으로 해야할일은 다르다. GtkButton은 버튼이 눌러진 모양으로 변해야할 것이며, GtkEntry는... (너무 길다 ㅡ.ㅡ 'gtkentry.c'의 'gtk_entry_button_press' 함수를 참고하기 바란다.) 에헴 뭐 그렇다 --; 중요한것은 Signal은 같지만 default handler는 Widget마다 특성에 따라 다르게 구현될 수 있다는 것이다.

마지막으로 default handler도 반환값이 있으면 Signal 발생에 의한 handler 제어에 영향을 준다. 'button-press-event'같은 경우 default handler는 TRUE를 반환하여, 'g_signal_connect_after'로 Callback 함수를 연결해 놓으면 호출되지 않는다. 자세한 내용은 아래의 내용들을 모두 보고나면 이해가 될것이다.

g_signal_connect와 g_signal_connect_after의 차이점

Signal에는 Flag가 있다. 이는 GTK 내부에 Signal마다 미리 정의해 놓았다. Signal의 Flag에 따른 handler 호출 절차를 알아두게 되면 어플리케이션의 섬세한 제어를 할 수 있다. Signal마다 어떤 Flag를 사용하는지는 API에 보면 각 Widget설명의 Signal 항목에 잘 나와있다. 그럼 아래를 한번 보자.

typedef enum {

G_SIGNAL_RUN_FIRST = 1 << 0,

G_SIGNAL_RUN_LAST = 1 << 1,

G_SIGNAL_RUN_CLEANUP = 1 << 2,

G_SIGNAL_NO_RECURSE = 1 << 3,

G_SIGNAL_DETAILED = 1 << 4,

G_SIGNAL_ACTION = 1 << 5,

G_SIGNAL_NO_HOOKS = 1 << 6

} GSignalFlags;

이것이 Signal Flag의 종류이다. 'G_SIGNAL_RUN_FIRST'와 'G_SIGNAL_RUN_LAST'만 설명하겠다.

'G_SIGNAL_RUN_FIRST'로 지정된 Signal은 Callback 함수보다 default handler를 먼저 호출한다. default handler 호출, 그다음 Callback 함수 호출이다. 반대로 'G_SIGNAL_RUN_LAST'는 Callback 함수가 호출되고 난 후 default handler가 호출된다. 단 여기에 예외사항이 있다. 바로 g_signal_connect_after이다. g_signal_connect_after로 연결된 Callback 함수는 FIRST이던 LAST이던 default handler 함수가 호출된 후에 Callback 함수가 호출된다.

이제 두개의 차이를 어느정도 이해 할 것이다. g_signal_connect로 연결된 Callback 함수는 FIRST냐 LAST에 따라 default handler보다 먼저 호출 될 수도 나중에 호출 될 수도 있지만 g_signal_connect_after는 default handler보다 나중에 호출된다.

다음은 GObject API에 있는 내용이다. (Signal은 GObject 소속이다.)

RUN_FIRST: if the G_SIGNAL_RUN_FIRST flag was used during signal registration and if there exist a class_closure for this signal, the class_closure is invoked. Jump to EMISSION_HOOK state.

EMISSION_HOOK: if any emission hook was added to the signal, they are invoked from first to last added. Accumulate return values and jump to HANDLER_RUN_FIRST state.

HANDLER_RUN_FIRST: if any closure were connected with the g_signal_connect family of functions, and if they are not blocked (with the g_signal_handler_block family of functions) they are run here, from first to last connected. Jump to RUN_LAST state.

RUN_LAST: if the G_SIGNAL_RUN_LAST flag was set during registration and if a class_closure was set, it is invoked here. Jump to HANDLER_RUN_LAST state.

HANDLER_RUN_LAST: if any closure were connected with the g_signal_connect_after family of functions, if they were not invoked during HANDLER_RUN_FIRST and if they are not blocked, they are run here, from first to last connected. Jump to RUN_CLEANUP state.

RUN_CLEANUP: if the G_SIGNAL_RUN_CLEANUP flag was set during registration and if a class_closure was set, it is invoked here. Signal emission is completed here.

Callback 함수의 반환값

Callback 함수의 형식 중에 반환값이 있는것이 있다. 그중 자주 사용하는 반환값의 형식은 gboolean 이고 이것은 handler 호출의 과정을 중단시킬 수 있다.

위에서 말했듯 Signal이 발생했을때 handler가 하나만 호출되는것이 아니다. 여러개의 handler가 조건에 따라 순차적으로 호출되는데 이때 handler의 반환값이 TRUE냐 FALSE냐에 따라 계속 진행할 것인지, 멈출것인지가 결정된다. Callback 함수의 예제들을 보면 대부분 FALSE인 것을 알 수 있다. 그렇다 FALSE일 경우에는 계속해서 진행하고 TRUE면 진행을 멈추어 그 이후에 호출해야할 handler는 호출되지 않는다.

예를 들어보자면 'button_press_event' Signal의 Callback 함수에 반환값을 TRUE로 주게 되면, 그 이후에 호출되어야할 default handler가 호출되지 않아, 버튼이 눌러진 모양으로 변하지 않는다. 다음에 나올 "Hi 똘츄~"예제를 이용해 확인해 주도록 하겠다.

위에서도 말했지만 default handler도 반환값이 TRUE인 경우에는 그 다음 handler가 호출되지 않는다.