· 6 years ago · Mar 09, 2020, 05:26 PM
1glib, gtk 팝업 사전 만들기 7화
2
36화에 파일 올렸는데, 중간 중간 짤려먹어서 파일만 다시 올려봄.
4이것도 짤리면 말구~
5
6----------
7tiandict.c
8----------
9/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
10
11#include <stdlib.h>
12#include <gmodule.h>
13#include <gtk/gtk.h>
14#include <ndbm.h>
15#include <fcntl.h>
16
17#define TIAN_WIDTH 300
18#define TIAN_HEIGHT 100
19
20typedef struct _Tiandict
21{
22 DBM *dbm;
23 GtkWidget *window;
24 GtkTextBuffer *text_buffer; /* 요거는 GtkTextView 용도 */
25 GtkClipboard *clipboard;
26 gulong handler_id;
27 /* 요거는 offset 맞추기 위한 변수, GUI 프로그래밍하다보면 offset, 좌표 맞추는 일이 자주 발생함. */
28 int titlebar_height;
29} Tiandict;
30
31void
32on_owner_change (GtkClipboard *clipboard,
33 GdkEvent *event,
34 Tiandict *dict)
35{
36 GdkWindow *gdk_window;
37 GdkSeat *seat;
38 GdkDevice *device;
39 gchar *text;
40 gint x;
41 gint y;
42
43 text = gtk_clipboard_wait_for_text (clipboard);
44
45 if (text == NULL)
46 return;
47
48 gtk_window_set_title (GTK_WINDOW (dict->window), text);
49
50 gdk_window = gtk_widget_get_window (GTK_WIDGET (dict->window));
51 seat = gdk_display_get_default_seat (gdk_display_get_default ());
52 device = gdk_seat_get_pointer (seat);
53
54 if (gdk_window && device)
55 {
56 gdk_device_get_position (device, NULL, &x, &y);
57 /* 아래는 적절한 위치로 이동하기 위해 좌표를 좀 조절함 */
58 gtk_window_move (GTK_WINDOW (dict->window), x - 25,
59 y + dict->titlebar_height);
60 gtk_window_set_keep_above (GTK_WINDOW (dict->window), TRUE);
61 gtk_window_set_keep_above (GTK_WINDOW (dict->window), FALSE);
62 }
63
64 /* 아래는 db 에서 데이터 인출해오는 코드, 매우 간단함. */
65 datum key;
66 datum content;
67
68 key.dptr = text;
69 key.dsize = strlen (text) + 1;
70 content = dbm_fetch (dict->dbm, key);
71
72 if (dbm_error (dict->dbm))
73 perror (NULL);
74
75 if (content.dptr)
76 gtk_text_buffer_set_text (dict->text_buffer, content.dptr, -1);
77 else
78 gtk_text_buffer_set_text (dict->text_buffer, "찾는 단어가 없습니다.", -1);
79
80 g_free (text);
81}
82
83gboolean
84on_enter_notify_event (GtkWidget *widget,
85 GdkEvent *event,
86 Tiandict *dict)
87{
88 if (dict->handler_id)
89 {
90 g_signal_handler_disconnect (dict->clipboard, dict->handler_id);
91 dict->handler_id = 0;
92 }
93
94 return FALSE;
95}
96
97gboolean
98on_leave_notify_event (GtkWidget *widget,
99 GdkEvent *event,
100 Tiandict *dict)
101{
102 if (!dict->handler_id)
103 dict->handler_id = g_signal_connect (dict->clipboard, "owner-change",
104 (GCallback) on_owner_change, dict);
105
106 return FALSE;
107}
108
109static void
110on_destroy (GtkWidget *widget,
111 gpointer user_data)
112{
113 gtk_main_quit ();
114}
115
116/* 이 함수는 실제로 저장하는 함수임 */
117static void
118on_save_button_clicked (GtkButton *button,
119 Tiandict *dict)
120{
121 /* 아래 변수는 포인터가 아니잖아. 함수에 포인터로 넘길 때, &iter1 이렇게 함
122 아래처럼 변수를 선언하면 malloc, free 안 써도 됨. 함수 내에서 한번 쓰고 버리는 함수들은
123 이렇게 선언하면 됨 */
124 GtkTextIter iter1;
125 GtkTextIter iter2;
126 gchar *text;
127
128 /* text buffer 에서 문자열 가져오는 작업임. api 문서 보면 다 나옴.
129 문서를 얼마나 빨리 찾냐가 기술임 ㅋㅋㅋㅋ */
130 gtk_text_buffer_get_start_iter (dict->text_buffer, &iter1);
131 gtk_text_buffer_get_end_iter (dict->text_buffer, &iter2);
132 text = gtk_text_buffer_get_text (dict->text_buffer, &iter1, &iter2, TRUE);
133
134 /* text 가 NULL 이면 프로그램 죽음. 그거 방지하려고 넣는 코드.
135 text[0] 이거는 뭐하는 거냐면
136 text = ""; 이렇게 하면 text[0] 여기에 0('\0', NULL) 값 들어있는거임.
137 문자열은 문자열인데 strlen (text) 하면 0 나오는 문자열이거든. 그거 저장할 필요 없잖아 */
138 if (text && text[0])
139 {
140 datum key;
141 datum content;
142
143 /* 마우스로 더블 클릭하면 PRIMARY Atom 에 저장된다고 했잖어. 그걸 GtkClipboard 이라는 걸로
144 인출해오고, 인출 후에 set_title () 했거덩. 그래서 title 로부터 key 되는 단어를 가져오는
145 거임. 별도로 변수 만들기 귀찮거덩. 있는거 그냥 쓰자 */
146 key.dptr = (char *) gtk_window_get_title (GTK_WINDOW (dict->window));
147 /* 문자열은 NULL 바이트까지 저장하는게 나중에 써먹기 좋아. */
148 key.dsize = strlen (key.dptr) + 1;
149
150 content.dptr = text;
151 content.dsize = strlen (content.dptr) + 1;
152 /* 저장하는 함수. 예외처리해야 하는데 귀찮아서 ㅋㅋㅋ */
153 dbm_store (dict->dbm, key, content, DBM_REPLACE);
154 }
155
156 /* text[0] 에 0 들어 있을 경우도 해제되는거임. */
157 if (text) /* if (text != NULL) 이거랑 같은 코드. 일부러 길게 칠 필요 없거덩 */
158 g_free (text);
159}
160
161Tiandict *tian_dict_new ()
162{
163 Tiandict *dict;
164 GtkWidget *sw; /* 이거는 scrolled window, 길어서 그냥 sw */
165 GtkWidget *editor;
166 GtkWidget *titlebar; /* GtkHeader */
167 GtkWidget *save_button; /* GtkButton */
168 gchar *path1, *path2; /* 패스1은 디렉토리 만들 때 써먹을 거임 */
169
170 dict = g_slice_new (Tiandict);
171 path1 = g_build_path (G_DIR_SEPARATOR_S, g_get_user_config_dir (),
172 "tiandict", NULL);
173 path2 = g_build_path (G_DIR_SEPARATOR_S, g_get_user_config_dir (),
174 "tiandict", "dict.db", NULL);
175 g_mkdir_with_parents (path1, 0700); /* 디렉토리 한방에 만드는거 mkdir -p */
176 dict->dbm = dbm_open (path2, O_RDWR | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR);
177
178 if (!dict->dbm)
179 {
180 /* glib 에 g_log, g_info, g_message, g_warning, g_critical, g_debug
181 이런 함수들 있음. 참고로 logging 도 됨 */
182 g_critical ("Can't open %s", path2);
183 exit (EXIT_FAILURE);
184 }
185
186 gtk_init (NULL, NULL);
187
188 /* GtkButton */
189 /* button 은 이렇게 만드는데, icon 이름에,
190 /usr/share/icons/Adwaita/22x22/legacy/document-save.png
191 document-save 요 부분만 넣으면 됨. 각종 사이즈, 각종 theme 별로,
192 document-save 아이콘이 엄청 나게 많음.
193 /usr/share/icons/ 디렉토리에 있는 거니까.. 고민하지 말라구. */
194 save_button = gtk_button_new_from_icon_name ("document-save",
195 GTK_ICON_SIZE_SMALL_TOOLBAR);
196 /* 전편에 설명했지? 버튼 클릭하면 clicked 신호가 발생하는데,
197 on_save_button_clicked 함수 실행하라는 얘기임. user_data 로 dict 넘어가는거구 */
198 g_signal_connect (save_button, "clicked",
199 G_CALLBACK (on_save_button_clicked), dict);
200
201 /* GtkHeaderBar */
202 titlebar = gtk_header_bar_new ();
203 gtk_header_bar_set_decoration_layout (GTK_HEADER_BAR (titlebar),
204 "menu:minimize,close");
205 gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (titlebar), TRUE);
206 /* gtk_container_add (GTK_HEADER_BAR (titlebar), aoeuao) 이런 함수도 있지만,
207 아래처럼 직접 제공하면 그걸 써!!.
208 pack_start, pack_end 함수 많이 보게 될 텐데,
209 pack_start 는 시작부분(왼쪽이겠지?)부터 패킹하라는 얘기. 쳐넣으라는 얘기.
210 pack_end 는 끝에서부터 (오른쪽이겠지?) 패킹하라는 얘기. 쳐넣으라는 얘기.
211 이런 짓을 왜하지? 이러지 말구, 창 최대화하면 그게 layout 에 영향을 주거덩.
212 왜 필요한지 사이즈, 간격 맞추다보면 별 뻘짓을 다하게 될꺼다 흠핫핫핫 ㅋㅋ */
213 gtk_header_bar_pack_start (GTK_HEADER_BAR (titlebar), save_button);
214
215 /* GtkTextView */
216 editor = gtk_text_view_new ();
217 /* 이거는 줄바꿈, 편집기에 이렇거 많이 보이지? */
218 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (editor), GTK_WRAP_WORD_CHAR);
219 /* 이거는 버퍼 얻어오는거. 나중에 해제 시키지 말구 */
220 dict->text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor));
221 gtk_text_buffer_set_text (dict->text_buffer,
222 "티엔 사전입니다. 마우스로 더블 클릭하면 단어에 대한 뜻이 나옵니다. 찾는 단어가 없을 때, "
223 "이 곳에 단어에 대한 의미를 입력후에 위에 제목 줄에 저장 버튼 누르시면 되겠습니다.", -1);
224
225 /* GtkScrolledWindow */
226 /* 옵션 안 주면 스크롤바가 알아서 다 자동으로 됨 */
227 /* 스코롤러 정책 옵션 바꿀려면 devhelp 로 gtk_scrolled 입력하고 검색하면 되구.
228 GUI 프로그래밍은 api 검색 능력이 곧 실력이야. 진짜임!!! */
229 sw = gtk_scrolled_window_new (NULL, NULL);
230 gtk_container_add (GTK_CONTAINER (sw), editor);
231
232 /* GtkWindow */
233 dict->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
234 gtk_window_set_titlebar (GTK_WINDOW (dict->window), titlebar);
235 gtk_window_set_default_size (GTK_WINDOW (dict->window), TIAN_WIDTH, TIAN_HEIGHT);
236 gtk_window_set_position (GTK_WINDOW (dict->window), GTK_WIN_POS_CENTER);
237 gtk_window_set_gravity (GTK_WINDOW (dict->window), GDK_GRAVITY_CENTER);
238 gtk_window_set_title (GTK_WINDOW (dict->window), "Tiandict");
239 gtk_container_add (GTK_CONTAINER (dict->window), sw);
240
241 /* GtkClipboard */
242 dict->clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
243 dict->handler_id = g_signal_connect (dict->clipboard, "owner-change",
244 (GCallback) on_owner_change, dict);
245
246 g_signal_connect (dict->window, "destroy", (GCallback) on_destroy, NULL);
247 g_signal_connect (sw, "enter-notify-event", (GCallback) on_enter_notify_event, dict);
248 g_signal_connect (sw, "leave-notify-event", (GCallback) on_leave_notify_event, dict);
249
250 gtk_widget_show_all (dict->window);
251
252 /* 요거는 진짜 설명이 필요함. 위에 gtk_header_bar_new() 했잖아. 그때는 height 를
253 알 수 없어. 그걸 나중에 allocate 한 후에 알 수 있거든.
254 gtk_widget_show_all gtk_widget_show 한 이후에는 확실히 알 수 있지.
255 그거 모르면 엄청 해멜거다.. ㅋㅋㅋ "draw" 신호가 발생하거든.
256 그 신호에 g_signal_connect 로 연결하고, 콜백에서 아래 함수 실행해도 크기 알 수 있지
257 이거 진짜 노하우야... 잘 기억해둬. 나 옛날에 이거 몰라서 한 두달 삽질했다 !!!!
258 그 옛날이 벌써 4년전이네 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
259 */
260 dict->titlebar_height = gtk_widget_get_allocated_height (titlebar);
261
262 g_free (path1);
263 g_free (path2);
264
265 return dict;
266}
267
268void tian_dict_free (Tiandict *dict)
269{
270 dbm_close (dict->dbm);
271 g_slice_free (Tiandict, dict);
272}
273
274int main (int argc, char **argv)
275{
276 Tiandict *dict;
277
278 dict = tian_dict_new ();
279
280 gtk_main ();
281
282 tian_dict_free (dict);
283
284 return EXIT_SUCCESS;
285}
286
287---------
288configure
289---------
290#!/bin/sh
291
292# default variables
293prefix="/usr/local"
294EXTRA_CFLAGS="-Wall -Werror"
295EXTRA_LDFLAGS="-Wl,-z,relro"
296hardening="-D_FORTIFY_SOURCE=2 -O1"
297
298for i in "$@"
299do
300case $i in
301 --disable-hardening)
302 hardening=""
303 ;;
304 --libdir=*)
305 libdir="${i#*=}"
306 ;;
307 --prefix=*)
308 prefix="${i#*=}"
309 ;;
310 --help|*)
311 echo "Usage: ./configure [OPTION]..."
312 echo " --help Show help messages"
313 echo " --disable-hardening Disable hardening"
314 echo " --libdir=PATH Set PATH to libdir"
315 echo " --prefix=DIRECTORY Set DIRECTORY to prefix"
316 exit 1
317 ;;
318esac
319done
320
321[ -z $libdir ] && libdir="$prefix/lib"
322eval libdir=$libdir
323
324EXTRA_CFLAGS=$EXTRA_CFLAGS" $hardening"
325errmsg=""
326
327# check required packages
328
329if ! pkg-config --exists glib-2.0; then errmsg+=" glib-2.0"; fi
330if ! pkg-config --exists gobject-2.0; then errmsg+=" gobject-2.0"; fi
331if ! pkg-config --exists gtk+-3.0; then errmsg+=" gtk+-3.0"; fi
332if [ ! -f /usr/lib/libgdbm_compat.so.4 ] &&
333 [ ! -f /usr/lib/x86_64-linux-gnu/libgdbm_compat.so.4 ]
334 then errmsg+=" libgdbm_compat.so.4"
335fi
336
337# print error messages
338
339if test -n "$errmsg"; then
340 echo "Not found: $errmsg"
341 exit 1
342fi
343
344# generate config.mk
345
346cat > config.mk <<- EOF
347EXTRA_CFLAGS = $EXTRA_CFLAGS
348EXTRA_LDFLAGS = $EXTRA_LDFLAGS
349VERSION = 2020.03.07
350CC = cc
351top_srcdir = $(pwd)
352prefix = $prefix
353libdir = $libdir
354datadir = $prefix/share
355GETTEXT_PACKAGE = tiandict
356TARGET_LOCALE_DIR = $prefix/share/locale
357MKDIR_P = mkdir -p
358EOF
359
360echo "Generated config.mk"
361echo "----------------------------------------------------------------"
362cat config.mk
363echo "----------------------------------------------------------------"
364
365
366--------
367Makefile
368--------
369include ./config.mk
370
371TARGET = tiandict
372
373TARGET_DEPS_CFLAGS = `pkg-config --cflags glib-2.0 gobject-2.0 gtk+-3.0`
374TARGET_DEPS_LIBS = `pkg-config --libs glib-2.0 gobject-2.0 gtk+-3.0` -lgdbm_compat
375
376SOURCES = tiandict.c
377CFLAGS = \
378 $(TARGET_DEPS_CFLAGS) \
379 $(EXTRA_CFLAGS) \
380 -DVERSION=\"$(VERSION)\" \
381 -DTARGET_LOCALE_DIR=\"$(TARGET_LOCALE_DIR)\"
382
383LDFLAGS = $(TARGET_DEPS_LIBS) $(EXTRA_LDFLAGS)
384
385all: $(TARGET)
386
387$(TARGET): $(SOURCES) Makefile
388 $(CC) $(CFLAGS) $(SOURCES) \
389 -Wl,--as-needed $(LDFLAGS) -o $(TARGET)
390
391clean:
392 rm -f $(TARGET) config.mk
393
394install:
395 install -D -m 755 $(TARGET) -t $(DESTDIR)$(prefix)/bin
396
397uninstall:
398 rm -f $(DESTDIR)$(prefix)/bin/$(TARGET)
399 -rmdir -p $(DESTDIR)$(prefix)/bin