glib, gtk 팝업 사전 만들기 5화
전편에서 UI 설계한 거를 좀 바꿔야 겠다.
GtkToolbar 를 없애고 아래처럼 단순하게~
+---------- GtkWindow ---------+
| | +- GtkScrolledWindow --+ | |
| | | +------------------+ | | |
| | | | | | | |
| | | | GtkTextView | | | |
| | | | | | | |
| | | +------------------+ | | |
| | +----------------------+ | |
| +--------------------------+ |
+------------------------------+
Makefile 에 아래처럼 -lgdbm_compat 이거 넣어주고
TARGET_DEPS_LIBS = `pkg-config --libs glib-2.0 gobject-2.0 gtk+-3.0` -lgdbm_compat
configure 에 아래처럼 libgdbm_compat.so.4 파일이 존재하는지 검사하는 코드를 넣을 것.
코드 자체는 GPL 에 종속되지는 않지만 최종 바이너리가 GPL 에 링킹되기 때문에 바이너리 배포하면
소스코드를 GPL 로 공개당해야 하는 거임. 주의바람!
나중에 이걸 응용할 때에는 BSD 계열의 dbm 구현체나 다른 방식이긴 하지만 sqlite 로 교체하는 것이
좋음. 내가 여기서 작성한 문서 및 소스코드는 GPL 이 아님.
원저자이기 때문에 예외둘 수 있고, GPL 코드를 사용한 것이 아니기 때문에 내 맘대로 할 수 있음.
GPL 링킹한 바이너리 배포하는 것도 아니구!! 이거는 그냥 튜토리얼 문서임^^
참고로 FreeBSD 에 있는 dbm 구현체는 BSD 라이선스임. 리눅스 나뽀 ㅋㅋ
if ! pkg-config --exists glib-2.0
; then errmsg+
=" glib-2.0"; fi
if ! pkg-config --exists gobject-2.0
; then errmsg+
=" gobject-2.0"; fi
if ! pkg-config --exists gtk+-3.0
; then errmsg+
=" gtk+-3.0"; fi
if [ ! -f /usr/lib/libgdbm_compat.so.4
] &&
[ ! -f /usr/lib/x86_64-linux-gnu/libgdbm_compat.so.4
]
then errmsg+
=" libgdbm_compat.so.4"
fi
그래서 tiandict.c 코드는 아래처럼 됨.
아래에서 유심히 봐야할 부분은 g_signal_connect() 로 콜백함수 연결해놓는 부분하고,
gtk_container_add (GTK_CONTAINER (dict->window), sw); 이런 식으로 컨테이너 안에
넣는다는 점. dict->window 가 GtkWindow 인데, GtkContainer 을 상속받은 거거든.
gtk_window_add() 이런 함수 없음. 정상적인 OOP 언어라면 이런 식으로 하는게 맞는데,
아무래도 c 로 하다보니, GTK_CONTAINER (dict->window) 이렇게 형변환을 하구,
gtk_container_add (GTK_CONTAINER (dict->window), sw); 이렇게 sw 를 컨테이너에 넣음.
이런 점이 OOP 언어를 쓰던 사람이 보기에는 불편할 수도 있겠네.
#include <stdlib.h>
#include <gmodule.h>
#include <gtk/gtk.h>
#include <ndbm.h>
#include <fcntl.h>
typedef struct _Tiandict
{
DBM
*dbm
;
GtkWidget
*window
;
GtkWidget
*editor
;
GtkClipboard
*clipboard
;
gulong handler_id
;
} Tiandict
;
void
on_owner_change
(GtkClipboard
*clipboard
,
GdkEvent
*event
,
Tiandict
*dict
)
{
GdkWindow
*gdk_window
;
GdkSeat
*seat
;
GdkDevice
*device
;
gchar
*text
;
gint x
;
gint y
;
text
= gtk_clipboard_wait_for_text
(clipboard
);
if (text
== NULL)
return;
gtk_window_set_title
(GTK_WINDOW
(dict
->window
), text
);
gdk_window
= gtk_widget_get_window
(GTK_WIDGET
(dict
->window
));
seat
= gdk_display_get_default_seat
(gdk_display_get_default
());
device
= gdk_seat_get_pointer
(seat
);
if (gdk_window
&& device
)
{
gdk_device_get_position
(device
, NULL, &x
, &y
);
gtk_window_move
(GTK_WINDOW
(dict
->window
), x
+ 10, y
+ 15);
gtk_window_set_keep_above
(GTK_WINDOW
(dict
->window
), TRUE
);
gtk_window_set_keep_above
(GTK_WINDOW
(dict
->window
), FALSE
);
}
datum key
;
datum content
;
key
.dptr
= text
;
key
.dsize
= strlen
(text
) + 1;
content
= dbm_fetch
(dict
->dbm
, key
);
if (dbm_error
(dict
->dbm
))
perror
(NULL);
GtkTextBuffer
*buffer
;
buffer
= gtk_text_view_get_buffer
(GTK_TEXT_VIEW
(dict
->editor
));
if (content
.dptr
)
gtk_text_buffer_set_text
(buffer
, content
.dptr
, -1);
else
gtk_text_buffer_set_text
(buffer
, "찾는 단어가 없습니다.", -1);
g_free
(text
);
}
gboolean
on_enter_notify_event
(GtkWidget
*widget
,
GdkEvent
*event
,
Tiandict
*dict
)
{
if (dict
->handler_id
)
{
g_signal_handler_disconnect
(dict
->clipboard
, dict
->handler_id
);
dict
->handler_id
= 0;
}
return FALSE
;
}
gboolean
on_leave_notify_event
(GtkWidget
*widget
,
GdkEvent
*event
,
Tiandict
*dict
)
{
if (!dict
->handler_id
)
dict
->handler_id
= g_signal_connect
(dict
->clipboard
, "owner-change",
(GCallback
) on_owner_change
, dict
);
return FALSE
;
}
static void
on_destroy
(GtkWidget
*widget
,
gpointer user_data
)
{
gtk_main_quit
();
}
Tiandict
*tian_dict_new
()
{
Tiandict
*dict
;
GtkWidget
*sw
;
dict
= g_slice_new
(Tiandict
);
dict
->dbm
= dbm_open
("dict.db", O_RDWR
| O_CREAT
| O_SYNC
,
S_IRUSR
| S_IWUSR
);
gtk_init
(NULL, NULL);
dict
->editor
= gtk_text_view_new
();
sw
= gtk_scrolled_window_new
(NULL, NULL);
gtk_container_add
(GTK_CONTAINER
(sw
), dict
->editor
);
dict
->window
= gtk_window_new
(GTK_WINDOW_TOPLEVEL
);
gtk_window_set_decorated
(GTK_WINDOW
(dict
->window
), FALSE
);
gtk_window_set_default_size
(GTK_WINDOW
(dict
->window
), 350, 216);
gtk_window_set_position
(GTK_WINDOW
(dict
->window
), GTK_WIN_POS_CENTER
);
gtk_window_set_gravity
(GTK_WINDOW
(dict
->window
), GDK_GRAVITY_CENTER
);
gtk_window_set_title
(GTK_WINDOW
(dict
->window
), "Tiandict");
gtk_container_add
(GTK_CONTAINER
(dict
->window
), sw
);
dict
->clipboard
= gtk_clipboard_get
(GDK_SELECTION_PRIMARY
);
dict
->handler_id
= g_signal_connect
(dict
->clipboard
, "owner-change",
(GCallback
) on_owner_change
, dict
);
g_signal_connect
(dict
->window
, "destroy", (GCallback
) on_destroy
, NULL);
g_signal_connect
(dict
->window
, "enter-notify-event", (GCallback
) on_enter_notify_event
, dict
);
g_signal_connect
(dict
->window
, "leave-notify-event", (GCallback
) on_leave_notify_event
, dict
);
gtk_widget_show_all
(dict
->window
);
return dict
;
}
void tian_dict_free
(Tiandict
*dict
)
{
dbm_close
(dict
->dbm
);
g_slice_free
(Tiandict
, dict
);
}
int main
(int argc
, char **argv
)
{
Tiandict
*dict
;
dict
= tian_dict_new
();
gtk_main
();
tian_dict_free
(dict
);
return EXIT_SUCCESS
;
}
보면 알겠지만, GUI 프로그래밍이라는 게 별거 없음. 자바 swing, swt 랑 하는 방식이 똑같음.
Qt 는 GTK 랑 더 유사함. wxWidgets 라는 거는 리눅스에서는 GTK wrapper 임.
GUI 로 뭔가를 만드려면 위에 보듯, 마우스로 클릭했을 때, 그게 PRIMARY Atom 에 저장된다는 걸
알아야겠지. 그리고 이벤트가 발생할 텐데 그 이벤트에 맞게 적절한 처리를 하는게 중요하지.
다른 GUI 프로그램도 마찬가지겠지만, api 문서 열심히 찾아서 쓰는게 일이지.
어디에서 차이가 나느냐...
사용자분들이 느낄 때, 편한가 불편한가. 이런 점.
그리고 트랜스미션(transmission) 이런 걸 만든다고 할 때, GUI 툴킷보다는 소켓, 통신 이런 걸
알아야 만들 수 있는거거덩. 소켓에서도 이벤트가 발생함. GUI 툴킷에서 제공하는 이벤트 루프에
잘 조화되게 해야겠지. glib 에서는 g_socket_create_source() 이런 함수를 제공하니까,
GMainLoop 에서 조화롭게 돌아갈 수 있지. GtkMain 은 GMainLoop 를 사용했으니 GTK 와도 조화롭게
어울릴 수 있음.
사전을 만든다고 하면, GUI 프로그래밍이 문제가 아니라 결국엔 데이터가 문제지.
그리고 편의성에 대해 생각해보자구.
이 프로그램은, toolbar 를 창 상단에 위치시키면 단순성이라는 제작 의도를 벗어나고,
그러면 프로그램을 어떻게 종료해야 하나. drag 할 때, 한글 칠 때도 클립보드(PRIMARY Atom)가
간혹 변경되는지 사전창이 따라다니는데, 헉 불편해~~~
거거를 끌 수 있는 옵션이라던가 메뉴를 제공하면 좋겠지.
appindicator(패널 알림 아이콘) 이라는게 있지. 그걸 사용하면 이런 고민거리가 확 날아가버림.
데스크탑에 알림을 보내는 방법도 있는데,
g_notification_new(), g_application_send_notification() 이런 함수가 있어서
데스크탑에 알림을 보내는 방법이 있음. 저런 걸 이용해서 사용자 편의성을 개선시키면 좋음.
appindicator 라는 라이브러리와 GNotification 은 알아두면 좋음.
GUI Toolkit 자체를 만들기 어려워 그렇지,
GUI 는 배우기 쉬움. api 문서 보고 그냥 만드면 되는 거임.
터미널에서 gtk3-demo 라고 치면,
데모 프로그램 돌아가고 거의 모든 위젯을 볼 수 있음. 소스코드도 제공하고.
gtk 굉장히 좋음 다른언어로도 많이 래핑됨 많이많이쓰셈