# 게시판 (C)

WebKitGTK+와 DOM을 사용한 간단한 프로그램.
Shaun McCance
shaunm@gnome.org 2010
Marta Maria Casetti
mmcasetti@gmail.com 2013
조성호
shcho@gnome.org 2017-2019.

# 게시판

      이 지침서를 통해 다음을 배웁니다:

        WebKit으로 웹 페이지를 표시합니다.
        WebKit의 DOM 함수로 웹 페이지 내용을 다룹니다.

      이 따라하기 예제에서는 독자 여러분이 C 프로그래밍 언어에 익숙하며, 위젯을 어떻게 만들어 두고, 콜백 함수를 시그널에 어떻게 연결하는지에 대해  GTK+를 기본적으로 이해하고 있다고 간주합니다. GTK+ 기본을 배우려면 를 참고하십시오.

# 안주타에서 프로젝트 만들기

그놈 플랫폼에는 강력한 웹킷 HTML 프레임워크를 바탕으로 만든 WebKitGTK+가 있습니다. WebKit은 그놈 전반적으로 활용하며, 인터넷의 웹 페이지를 보기만 하는 목적으로 쓰는게 아니라, CSS로 쉽게 스타일을 지정할 수 있는 리치 사용자 인터페이스를 만들 때도 사용합니다.

이 따라하기 지침에서는 WebKit으로 간단한 게시판을 만들어보겠습니다. 게시판에서는 일부 텍스트를 입력할 수 있으며 HTML 메시지 목록을 추가할 수 있습니다. 시작하기 전에 Anjuta에서 프로젝트를 설정해야합니다.

안주타에서 파일새로 만들기 프로젝트를 눌러 새 프로젝트 도우미를 여십시오.

C 탭에서 GTK+ (단순)을 선택하고, 계속을 누르십시오.

기본 정보 페이지에 자세한 내용을 적어나가심시오. 프로젝트 이름은 message-board라고 하십시오. 계속을 누르십시오.

따라하기 지침을 통해 사용자 인터페이스를 직접 만들 예정이므로 사용자 인터페이스에 GtkBuilder 사용 설정을 끄십시오.

안주타 프로그램에서 프로젝트에 WebkitGTK+를 사용한다고 알려야합니다. 프로젝트 옵션 페이지에서, 외부 패키지 설정을 선택하십시오. 계속을 누르십시오. 외부 패키지 설정 페이지에서 webkitgtk-3.0을 선택하십시오.

새 프로젝트 도우미를 끝내고 나면 프로젝트탭 또는 the 파일탭에서 src/main.c 파일을 여십시오 안주타에서는 서식에 일부 기본 GTK+ 코드를 채워넣습니다. WebKit 프로젝트를 만들고 있으니 WebKit 레더를 추가해야합니다. `gtk/gtk.h`줄 다음에 다음 줄을 추가하십시오:

#include <webkit/webkit.h>

여태껏 작성해오던 코드로 모든 부분이 동작하는지 확인하십시오. 빌드프로젝트 빌드 를 누르거나 그냥 ShiftF7키를 누르십시오. 처음 빌드할 때는 몇가지 설정 옵션을 물어봅니다. 그냥 기본 값으로 설정하고 실행을 누르십시오.

이제 프로그램을 실행할 수 있어야합니다. 실행실행을 누르든지 그냥 F3 키를 누르십시오. 빈 창이 나타납니다.

# 창과 웹 뷰 배치

이제 창을 보여줄 수 있으니 WebKit을 다룰 시간입니다. 이 따라하기 안내에 따라 텍스트 항목과 웹 뷰를 만들고 이 요소를 창에 추가합니다. `create_window` 함수를 찾아 다음 코드로 바꾸십시오:

static GtkWidget* create_window (void) { GtkWidget *window, *box, *scroll, *view, *entry;

window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size (GTK_WINDOW (window), 400, 400); gtk_window_set_title (GTK_WINDOW (window), "Message Board"); g_signal_connect (window, "delete-event", G_CALLBACK (gtk_main_quit), NULL);

box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); gtk_container_set_border_width (GTK_CONTAINER (box), 6); gtk_container_add (GTK_CONTAINER (window), box);

entry = gtk_entry_new (); gtk_box_pack_start (GTK_BOX (box), entry, FALSE, FALSE, 0);

scroll = gtk_scrolled_window_new (NULL, NULL); g_object_set (scroll, "shadow-type", GTK_SHADOW_IN, NULL); gtk_box_pack_start (GTK_BOX (box), scroll, TRUE, TRUE, 0);

view = webkit_web_view_new (); gtk_container_add (GTK_CONTAINER (scroll), view); webkit_web_view_load_string (WEBKIT_WEB_VIEW (view), "<html><body></body></html>", "text/html", "UTF-8", NULL);

gtk_widget_show_all (GTK_WIDGET (box)); return window; }

우선 `GtkWindow` 객체를 만들고 제목과 기본 크기를 설정합니다. `gtk_main_quit` 함수를 `delete-event` 시그널에 연결하기도 합니다. `delete-event`시그널은 창을 닫을때 방출합니다. `gtk_main_quit` 함수는 GTK의 알부이며, 프로그램을 끝냅니다.

이제 수직 박스를 만들어 창에 추가했습니다. 창은 오직 단일 하위 위젯을 가질 수 있기 때문에 여러 위젯을 추가하려면 박스를 사용해야합니다. `gtk_box_new`의 두번째 인자는 각 하위 위젯의 (픽셀 단위)간격을 설정하고 다음 줄에는 전체에 6 픽셀 두께의 테두리를 넣습니다.

다음 `GtkEntry` 객체를 만들어 박스에 넣으십시오. `gtk_box_pack_start`의 세번째, 네번째 인자는 박스에 추가 여유 공간이 있을때, 항목이 차지하지 않도록 설정합니다. 네번째 인저에서는 항목 주변의 여백 공간입니다. 지금의 경우 여백 공간을 0으로 설정했는데 박스에서 내부 여백을 처리할 수 있기 때문입니다.

웹 뷰를 추가하기 전에 웹 뷰를 넣을 스크롤 뷰를 우선 넣어야합니다. 스크롤 뷰는 필요한 경우 스크롤 표시줄을 오른편과 하단에 두고 웹 뷰가 화면 전체를 차지하지 않게 막아줍니다. 여기서는 `gtk_box_pack_start`에 `TRUE`와 `TRUE`를 넣어 스크롤 창(웹 뷰!)에서 박스의 추가 여분 공간을 사용합니다.

마지막으로 `WebKitWebView`를 만들고 스크롤 창에 추가했습니다. 그리고 `webkit_web_view_load_string` 함수와 다음 인자를 통해 매우 기본적인 HTML페이지를 웹 뷰에 불러다놓았습니다:

## `WEBKIT_WEB_VIEW (view)`

뷰 자체입니다. `view` 는 `GtkWidget*` 형식이므로 객체를 안전하게 캐스팅하려면 `WEBKIT_WEB_VIEW` 를 사용해야합니다.

## `"<html><body></body></html>"`

여러분이 작성할 수 있는 간단한 HTML 파일입니다.

## `"text/html"`

여러분이 제공한 컨텐트의 MIME 형식입니다. 지금 같은 경우는 플레인 HTML을 사용중입니다.

## `"UTF-8"`

여러분이 제공한 내용의 문자 인코딩 방식입니다. ASCII 문자만 활용했다 히더라도 UTF-8로 지정하시는게 좋습니다. UTF-8은 그놈 플랫폼 이곳저곳에서 기본 인코딩으로 활용합니다.

## `NULL`

기본 URI. 이 간단한 예제에서는 필요하지 않습니다만, 그림을 추가하거나 상대 URI 참고 경로를 추가한다면 file: URI를 제공하는게 좋습니다.

위젯을 추가할 때마다 위젯이 나타나게 `gtk_widget_show` 함수를 호출해야합니다. `GtkBox`와 같은 컨테이너 위젯에서 `gtk_widget_show_all` 함수를 호출하면, GTK+에서 알아서 컨테이너의 어떤 깊은 곳에 있든 다 끄집어내어 자동으로 보여줍니다. 때로는 이벤트에 대한 응답으로 위젯을 동적으로 숨기거나 보이려고 하는 그런 경우에 `gtk_widget_show_all`을 호출하고 싶지 않을 때도 있습니다.

마지막으로 박스의 `gtk_widget_show_all`를 호출해야 합니다. 그렇지 않으면 만들어 둔 어떤 위젯도 볼 수 없습니다(창은 `main` 함수의 `gtk_widget_show` 호출로 나타납니다.)

게시판을 다시 빌드하고 실행하십시오. 텍스트 항목과 웹 뷰가 나타납니다. 텍스트 항목과 웹 뷰는 서로를 알지 못하기 대문에 어떤 일도 일어나지 않습니다.

# 시그널 걸어두기

이제 텍스트 항목에 텍스트를 입력했을때 실제로 게시판에서 어떤 일을 일어나게 하려고 합니다. 콜백 함수를 `entry`의 `activate` 시그널로 연결합니다. GTK+는 사용자가 항목 위치에서 Enter 키를 누를 때마다 `activate` 시그널을 내보냅니다. `entry`와 `view`를 정의한 어디에든 다음 코드를 `create_window`에 넣으십시오:

g_signal_connect (entry, "activate", G_CALLBACK (entry_activate_cb), view);

실제로 `entry_activate_cb`를 정의해야합니다. `create_window` 위 어디에든 다음 처럼 정의하십시오:

static void entry_activate_cb (GtkEntry *entry, WebKitWebView *view) { WebKitDOMDocument *document; WebKitDOMElement *body, *div;

document = webkit_web_view_get_dom_document (view); body = webkit_dom_document_query_selector (document, "body", NULL); div = webkit_dom_document_create_element (document, "div", NULL); webkit_dom_node_set_text_content (WEBKIT_DOM_NODE (div), gtk_entry_get_text (entry), NULL); webkit_dom_node_append_child (WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (div), NULL); gtk_entry_set_text (entry, ""); }

여러분은 우선 `view`에 HTML 문서를 나타내는 `WebKitDOMDocument`를 가져와야 합니다. 웹킷 DOM 클래스와 메서드는 HTML을 살펴보고 다룰 수 있게 하며, JavaScript를 통해 이미 알고 있는 DOM API와 매우 유사하게 다룰 수 있습니다.

문서를 가져온 후에는 `body` 구성요소를 가져와서 `div` 구성요소를 넣을 수 있게 해야합니다. `webkit_dom_document_query_selector` 함수는 CSS 선택자로 문서의 구성 요소를 찾게 해줍니다. 이 방식을 통해 문서를 돌아볼 때 짜증나는 반복문을 작성하지 않게 해줍니다.

다음, 메시지를 넣어둘 새 `div` 구성요소를 만듭니다. 여러분이 만든 모든 구성 요소는 문서에 붙으므로, 구성 요소를 만드는 함수의 첫번째 인자로 `WebKitDOMDocument`를 갖습니다. 그 다음 텍스트 항목의 컨텐트로 구성 요소의 텍스트를 설정합니다. `gtk_entry_get_text` 에서 `const gchar*`를 반환하기에, 결과를 굳이 해제하지 않아도 됩니다.

마지막으로 `div` 구성요소를 body에 추가하고 텍스트 항목을 지워서 새 내용을 입력할 수 있게 하십시오.프로그램을 다시 빌드하고 실행한 후 여러분 자신이 직접 테스트해보십시오.

# CSS로 더 멋지게 만들기

여기서 프로그램의 기능은 완벽하게 동작하지만 썩 매력있는 편은 아닙니다. 화면에 나타나는 메시지를 다른 HTML페이지를 다루어왔던 것처럼 CSS로 장식할 수 있습니다. 일부 CSS를 페이지에 붙여넣는 방법에는 여러가지가 있는데, 처음 HTML 문서에 추가하는 방법, `div` 구성 요소의 `style` 속성에 넣는 방법, DOM API로 프로그래밍 방식으로 넣는 방법이 있습니다.

이 따라하기 지침에서는 웹 뷰에 붙은 `WebKitWebSetting` 객체의 `user-stylesheet-uri` 속성으로 CSS를 붙입니다. 좀 더 완벽한 프로그램에서는 HTML 파일을 저장하고 불러오고 싶을 수도 있습니다. HTML의 실제 스타일 정보를 외부에서 유지한다는건, 굳이 사용자 파일을 건드리지 않아도 프로그램에서 완벽하게 스타일을 바꿀 수 있다는 의미가 되겠습니다. 보통 프로그램과 해당 파일을 같이 설치해야 하겠지만 이 시연 프로그램에서는 하나의 파일에 모두 담아두고 데이터 URI로 꼼수를 쓰겠습니다. 우선 파일 상단 부분에 정적 CSS를 정의하십시오.

static const guchar CSS[] = "body { margin: 0; padding: 0; }\n" "div { " " -webkit-border-radius: 2px;" " background: -webkit-gradient(linear, 0% 100%, 0% 0%," " from(#f1f1f1), to(white));" " border: solid 1px #c6c6c6;" " -webkit-box-shadow: 0px 0px 2px #c6c6c6;" " margin: 12px; padding: 6px;" "}";

이 예제에 들어간 모든 내용은 `body` 구성 요소의 `div` 구성요소입니다. 더 복잡한 HTML을 만든다면 어떤 CSS든 필요합니다. 사실, CSS가 편하다면 여러분이 더 좋아하는 방식대로, 이 코드를 바꿔보시는게 좋습니다.

CSS를 적용하려면, `view` 를 설정한 이후 어디든 `create_window` 함수에서 `user-stylesheet-uri`를 설정하십시오.

tmp = g_base64_encode (CSS, strlen((gchar *) CSS)); css = g_strconcat ("data:text/css;charset=utf-8;base64,", tmp, NULL); g_object_set (webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view)), "user-stylesheet-uri", css, NULL); g_free (css); g_free (tmp);

또한 `create_window` 상단에 `tmp` 와 `css` 변수 선언을 추가했는지 확인하십시오.

gchar *tmp, *css;

data:로 시작하는 데이터 URI와 컨텐트 형식 일부 정보, 데이터 인코딩 방식입니다. 실제 데이터는 콤마 뒤에 따라오는데 이 경우에는 Base64 방식으로 인코딩합니다. http:, ftp:, file: 같은 다른 URI 형식과는 다르게 URI 스킴에는 불러올 파일이 어디있는지 지정하지 않습니다. 대신 파일의 전체 내용을 제시해줍니다.

위 코드는 우선 CSS 정의를 Base64 방식으로 인코딩한 다음 데이터 URI를 만드는 정적 문자열과 합칩니다. `g_strconcat` 함수에서는 여러 문자열 인자를 취하여 합칠 수 있으므로, 마지막 인자 값은 `NULL`로 주어 언제 문자열을 합치는 동작을 멈출 지 알려야합니다. 그리고 스타일 시트를 올바르게 설정하고 나면 잊지 마시고 임시 문자열에 대해 메모리 해제를 반드시 수행하십시오.

프로그램을 다시 빌드하고 실행하십시오. 테두리와 미묘한 백그라운드 그레디언트로 멋지게 장식할 메시지 부분을 제외하고는 섹션의 마지막 부분과 정확히 동일하게 동작합니다.

# 더 알아보기

이 따라하기 지침에서는 GTK+와 웹킷으로 문서를 보여주고 내용을 다루는 기본적인 프로그램을 만드는 방법을 보여줍니다. 실제 프로그램을 만들려면, 프로그램에 좀 더 이것저것 붙여보고 싶을지도 모릅니다. 기능 추가는 여러분이 직접 해보시죠. 몇가지 아이디어를 드리겠습니다:

CSS가 불편하다면 메시지 표시 방식을 바꿔보십시오. CSS는 시작하기 쉬우면서도 다루어 볼수록 점점 강력해집니다. 인터넷에는 다양한 CSS 따라하기 문서가 있으며, 웹에 할 수 있는 모든걸 이 프로그램에서 해볼 수 있습니다.

바로 지금 게시판을 닫는다면 모든 메시지가 증발합니다. 각 게시물을 올린 다음 HTML 문서를 저장해보고 시작할 때 저장한 파일(있을 경우)을 불려와보십시오.

메시지를 오랫동안 유지했다면 언제 게시했는지 궁금해하실겁니다. 각 메시지를 게시할 때 타임 스탬프를 추가하십시오. 아마도 CSS로 모양새를 갖출 수 있도록 다른 클래스 이름을 부여한 추가 하위 `div` 구성요소를 만들고 싶을 수도 있습니다.

이 프로그램은 메시지를 계속 유지합니다. 사용자가 메시지를 지울 수 있게 하는 방법을 생각해보시죠. 아마도 너무 오래됐거나, 일정 갯수가 넘어가면 자동으로 메시지를 사라지게 하고 싶을지도 모릅니다. 아니면 각 메시지에 삭제할 수 있는 링크를 달아둘 수 있습니다. 심지어 메시지에 마우스 단추를 올려두고 오른쪽 단추를 눌렀을 때 단축 메뉴를 띄울 수 있습니다. 이 기능을 구현하려면 웹킷 DOM API를 좀 더 찾아봐야 합니다.