FoundationsofGTKDevelopment:Chapter 13
- 제 13 장 전부 합치기
전부 합치기
총 12 장에 걸쳐 GTK+와 그에 관련된 기술을 이용해 할 수 있는 모든 것에 대해 심도 있는 관점을 제공하였다. 이번 장에서는 이렇게 얻은 지식을 적용하여 몇 가지 애플리케이션을 빌드해볼 것이다.
이번 장에서는 5개의 완전한 애플리케이션, 즉 제 10장에서 디자인한 파일 브라우저를 비롯해 계산기, 핑 유틸리티(ping utility), 행맨(Hangman) 게임, 캘린더를 소개할 것이다. 하지만 예제에 해당하는 소스 코드는 이번 장에 실지 않았다. 각 코드는 http://www.gtkbook.com 에서 다운로드가 가능하다.
마지막으로 개발자가 GTK+ 지식을 확장할 수 있도록 다른 학습 자원에 관한 조언을 제공하면서 본 장을 마무리하겠다.
파일 브라우저
제 10장에서 개발자는 Glade에서 파일 브라우저 애플리케이션을 위한 사용자 인터페이스를 구현한 바 있다. 사용자 인터페이스는 이후 Libglade를 이용해 동적으로 로딩되었고, glade_xml_signal_autoconnect()를 이용해 모든 시그널이 자동으로 연결되었다.
10장의 뒷부분에서 콜백 함수를 제 13장에서 구현하겠다고 언급한 바가 있는데 지금이 바로 그 때다. 그림 13-1은 파일 브라우저 애플리케이션을 처음 시작했을 때 모습으로, 루트 폴더를 표시하고 있다.
이 애플리케이션에서 특히 흥미로운 것은 실제 파일 브라우징 기능이다. 이 기능들은 연습문제 8-1에 실린 기능과 매우 유사하다. 연습문제 8-1에서는 사용자의 파일 시스템을 여기저기 살펴볼 수 있는 GtkTreeView 위젯을 이용해 간단한 애플리케이션을 생성하였다. 파일 브라우저의 현재 위치는 연결 리스트에 저장되는데, 이 리스트로부터 전체 경로를 빌드할 수 있다. 리스트 내 각 노드는 경로의 일부분으로, 디렉터리 구분자가 각 문자열 사이에 위치하여 전체 경로가 빌드된다. GtkEntry 위젯 또한 제공되어 사용자가 키보드로 경로를 편집할 수 있도록 해준다.
파일 시스템의 검색은 몇 가지 다른 방식을 이용해 이루어진다. 앞서 언급했듯이 위치는 주소 표시줄에 입력할 수 있지만 위치에 대한 검증(validity)은 GtkEntry 위젯이 활성화될 때 확인되어야 한다. 이 방법 외에도 Back, Forward, Up, Home 툴바 버튼을 이용해 각각 브라우징 히스토리를 검색하고, 부모 디렉터리로 이동하며, 홈 디렉터리로 이동할 수 있다. 마지막으로 GtkTreeView의 row-activated 시그널은 선택된 디렉터리로 사용자를 이동시키거나 선택된 파일에 관한 정보를 확인할 수 있도록 해준다.
GtkStatusBar 위젯은 창의 하단면을 따라 위치된다. 이는 현재 디렉터리 내 총 항목의 개수와 이러한 항목의 전체 크기에 대한 정보를 추적한다. 이 예제를 비롯해 나머지 4개의 애플리케이션에 대한 소스는 모두 http://www.gtkbook.com 에서 이용할 수 있다.
계산기
계산기는 대부분의 GUI 프로그래밍 서적에서 구현되는 간단한 애플리케이션이다. 이 예제를 소개하는 이유는 계산기를 구현하는 것이 얼마나 쉬운지 증명하기 위해서다. 애플리케이션의 스크린샷은 그림 13-2에서 확인할 수 있다.
위의 계산기 애플리케이션은 Glade에서 디자인하였기 때문에 코드를 전혀 사용하지 않고 사용자 인터페이스가 완성되었다. 그 다음 glade_xml_signal_autoconnect()를 이용해 모든 버튼을 시그널로 연결하였다. 이 예제의 위젯 대부분은 GtkButton 위젯이므로 clicked와 destroy 시그널만으로 충분했다.
계산기는 사용자가 선택적 10진 소수점(optional decimal point)으로 된 수를 입력하고, 네 개의 기본 연산(덧셈, 뺄셈, 곱셈, 나눗셈)을 실행하며, 숫자를 부정(negate)하고, 제곱근과 지수(exponent)를 계산할 수 있도록 해준다. 필요한 콜백 함수의 개수를 줄이기 위해 모든 수와 소수 자리가 num_clicked()라는 시그널 콜백 함수로 연결되었고, 4개의 기본 연산과 거듭제곱 연산(power operation)이 다른 함수로 연결되었다. 이에 따라 이러한 연산 그룹들이 작동하기 위해서는 매우 유사한 코드를 필요로 한다는 사실을 이용할 수 있었다.
숫자 또는 소수 자리 버튼을 클릭하면 문자가 현재 값의 뒤에 추가되는데, 숫자는 10자리수로 길이가 제한된다. 연산 버튼을 클릭하면 연산이 실행되고 새로운 값이 저장된다. 연산 버튼은 또한 사용자가 숫자 또는 소수 자리를 누르면 새로운 숫자가 시작되어야 함을 계산기에게 알리기 위해 clear_flag라는 플래그를 설정한다.
거듭제곱 연산은 다른 연산들과 그룹화되지 않는데, sqrt_clicked() 콜백 함수에 표시된 값에서 즉시 실행되기 때문이다. 이를 위해서는 GtkEntry 위젯에 표시된 현재 값을 취하고 숫자의 거듭제곱을 계산한 후 GtkEntry 위젯으로 결과를 다시 저장해야 한다. 마찬가지로 부정(negate) 연산도 즉시 효과를 발휘한다. 반대로 지수 연산을 클릭하면 거듭제곱(power)을 요청한다.
행맨
그림 3-3에 실린 행맨 애플리케이션은 앞 장에서 학습한 GtkDrawingArea 위젯을 이용한다. 문자, 테두리, 현재 퍼즐은 GtkDrawingArea 위젯에 그려진다. 다음으로 각각에 알파벳 문자가 표시된 버튼의 테이블이 창의 하단면을 따라 위치된다. 각 버튼을 클릭하면 사용자와 상호작용을 할 수 없게 된다.
그리기 영역에 관해 기억해야 할 한 가지 중요한 점은 위젯을 expose-event 시그널로 연결하길 원할 것이란 점이다. 사용자가 창의 크기를 조정하거나, 포커스를 변경하거나, 창을 이동할 때마다 위젯의 내용은 삭제될(clear) 것이다. 행맨 애플리케이션에서는 expose-event 가 발생할 때마다 그리기 영역이 재설정되어야 한다.
이 콜백 함수에서는 gdk_draw_lines()를 이용해 여러 개의 점으로부터 창 주위에 테두리를 그렸다. 다음으로 gtk_draw_polygon()을 이용해 점의 집합으로부터 스캐폴딩(scaffolding)을 그렸다. 해답을 참고해보면 이 함수의 세 번째 매개변수가 TRUE로 설정되어 위젯의 스타일에 명시된 색상으로 다각형이 칠해짐을 눈치챌 것이다.
이후 현재 퍼즐 문자열이 PangoLayout으로 추가되는데, 아직 발견되지 않은 문자열은 모두 공백 문자로 설정된다. 퍼즐의 크기는 현재 퍼즐에 따라 다르기 때문에 퍼즐의 너비에 따라 테두리의 중심을 기준으로 정렬된다. 레이아웃은 gdk_draw_layout()을 이용해 그리기 영역의 GdkWindow로 그려진다.
마지막으로 다시 gdk_draw_line()과 gdk_draw_polygon()을 이용해 실제 문자를 그렸는데, 무엇을 그리는지는 사용자의 오답 횟수에 따라 좌우된다.
문자를 클릭하면 콜백 함수는 현재 퍼즐을 순환하면서 발견된 문자를 확인한다. 매치하는 내용이 발견되지 않으면 행맨에 추가물이 그려진다. 상태가 5에 도달하면 사용자가 지는 게임이다. 해답을 모두 기입하면 사용자가 이긴다.
핑 유틸리티
제 6장에서는 파이프를 통해 애플리케이션과 통신하기 위해 GIOChannel을 이용하는 법을 학습하였다. 핑 유틸리티 애플리케이션은 그림 13-4에서 확인할 수 있는데, 이는 애플리케이션이 중단될 때까지 계속적으로 혹은 특정 횟수만큼 주소로 통신 상태를 테스트(ping)하도록 해준다.
이 애플리케이션에서는 특정 주소로 애플리케이션의 통신 상태를 테스트하는 예를 보이기 위해 g_spawn_async_with_pipes()를 이용하였다. 이 함수가 수신한 셸 명령은 올바른 포맷이 되도록 g_shell_parse_argv()를 이용해 파싱되었다. Ping 버튼 또한 비활성화되어 사용자가 자식 프로세스의 다중 인스턴스를 실행하지 못하도록 하였다.
자식 프로세스를 띄운 후에는 출력 파이프를 이용해 읽기 데이터용 파이프를 관찰하는 새로운 GIOChannel 객체를 생성하였다. 데이터는 읽을 준비가 되면 각 핑에 대한 통계를 GtkTreeView 위젯에 표시할 수 있도록 파싱되었다. 이러한 과정은 사용자가 명시된 회수만큼 지속되기도 하고, 사용자가 자식 프로세스를 중지시킬 때까지 지속되기도 한다.
자식 프로세스가 실행되면 Stop 버튼이 활성화되어 사용자는 자식 프로세스가 완료되기 전에 프로세스를 죽일 수 있다. 이 함수는 자식 프로세스를 강제로 닫는 아래와 같은 kill() 의 인스턴스를 호출할 뿐이다.
kill (pid, SIGINT);
프로세스를 죽이면 파이프가 소멸되어 watch 함수에서 GIOChannel가 중단(shut down)되는 결과를 야기한다. 이는 다음 자식 프로세스에 대해 동일한 GIOChannel 객체를 재사용할 수 있도록 보장한다.
캘린더
이번 장의 마지막 애플리케이션에서는 사용자를 위한 이벤트를 구성하는 캘린더를 생성해볼 것이다. 이는 GtkCalendar 위젯을 이용해 사용자가 일자와 GtkTreeView를 훑어보고 주어진 일자에 해당하는 이벤트를 표시하도록 해준다. 그림 13-5에 이러한 캘린더 애플리케이션을 표시하였다.
캘린더 애플리케이션의 생성 시 사용된 코드 대부분은 매우 익숙할 것인데, 앞 장에 걸쳐 소개한 함수를 사용하기 때문이다. 익숙한 함수 외에도 이 코드에서는 GLib가 제공한 XML 파서를 이용해 XML 파일로 저장된 캘린더 파일을 열었다. 하나의 이벤트를 포함하는 캘린더 파일의 예제는 리스팅 13-1에서 확인할 수 있다.
리스팅 13-3. 캘린더 파일 (test.cal)
<calendar>
<event>
<name>Release of the Book</name>
<location>Everywhere</location>
<day>16</day>
<month>3</month>
<year>2007</year>
<start>All Day</start>
<end></end>
</event>
</calendar>
새로운 캘린더는 New 툴바 버튼을 클릭하면 생성되는데, 캘린더 파일명과 위치를 요청할 것이다. 캘린더는 개발자가 이벤트를 추가하거나 제거할 때마다 저장되므로 Save 버튼은 별도로 제공되지 않는다. 이미 존재하는 캘린더는 Open 툴바 버튼을 눌러서 열 수 있다.
마크업 파서 함수
캘린더를 열기 위해 이 애플리케이션은 GLib의 XML 파서를 이용해 파일의 내용을 검색한다. 해당 파서는 사용법이 매우 쉽고 기본 XML 파일을 지원한다. 파서를 사용하기 위해 가장 먼저 해야 할 일은 새로운 GMarkupParser 구조체를 정의하는 것이다. 이 구조체에는 5개의 함수가 포함되어 있는데, 하나씩 다뤄보도록 하겠다. 이 함수들 중 error()만 제외하고 NULL로 설정이 가능하다.
첫 번째 함수인 start_element()는 <calendar> 혹은 <event>와 같은 태그를 열 때마다 호출된다. 이 함수는 속성 이름과 값으로 된 배열과 함께 태그 요소명을 수신한다. 또 시작 요소를 구별하고, 적절한 때에 속성을 검사하기도 한다. 캘린더 애플리케이션에서는 이 함수를 이용해 다음 이벤트를 위해 이전 이벤트에서 저장된 임시 데이터를 모두 해제하고 백지 상태로 만든다.
void (*start_element) (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer data,
GError **error);
다음으로 소개할 함수 end_element()는 </calendar> 혹은 </event>와 같은 닫는 태그(close tag)마다 호출된다. <tag/>처럼 닫는 태그가 없는 태그에도 호출된다. 앞에 소개한 함수와 마찬가지로 태그명을 수락한다. 캘린더 애플리케이션에서는 </event> 태그에 도달할 경우 전역 트리로 이벤트를 추가하는 데에 사용된다.
void (*end_element) (GMarkupParseContext *context,
const gchar *element_name,
gpointer data,
GError **error);
세 번째로 text() 함수는 start_element()와 end_element() 호출 사이에서 발견되는 데이터에 대해 호출된다. 이 함수는 텍스트 길이를 비롯해 두 개의 태그 사이에 위치한 텍스트를 수락한다. text 문자열은 NULL로 끝나지 않음을 주목해야 한다! 캘린더 애플리케이션에서 이 함수는 이벤트의 내용을 읽기 위해 사용된다.
void (*text) (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer data,
GError **error);
text() 함수는 문자열을 포함하는 태그에 대해 호출될 뿐만 아니라 다른 태그를 호출하는 태그에 대해서도 호출된다. 따라서 이 함수에는 공백으로 채워진 text 매개변수와 새로운 행 문자가 포함될 것이다! g_markup_parse_context_get_element()를 이용해 문자열을 포함하는 태그를 검색할 수 있다.
passthrough() 함수는 캘린더 애플리케이션에서 사용되지 않는다. 이 함수는 XML이 아니면서 현재 자체의 위치를 유지해야 하는 요소에 대해 호출되는데, 주석을 예로 들 수 있겟다. text()와 마찬가지로 passthrough_text 문자열은 NULL로 끝나지 않는다!
void (*passthrough) (GMarkupParseContext *context,
const gchar *passthrough_text,
gsize text_len,
gpointer data,
GError **error);
마지막으로 파싱 시 오류가 발생할 때마다 error() 함수가 호출된다. 이 함수는 유일하게 GMarkupParser에서 설정되어야 한다.
void (*error) (GMarkupParseContext *context,
GError *error,
gpointer data);
XML 파일 파싱하기
XML 텍스트는 사실상 GMarkupParseContext 객체를 이용해 파싱된다. 새로운 파서는 g_markup_parse_context_new()를 이용해 생성된다.
GMarkupParseContext* g_markup_parse_context_new (const GMarkupParser *parser,
GMarkupParseFlags flags,
gpointer data,
GDestroyNotify user_data_dnotify);
이 함수는 먼저 XML 파일 내의 요소에 대해 호출될 함수를 포함하는 GMarkupParser 객체를 수락한다. 이후 GMarkupParseFlags가 정의한 플래그를 수락한다. 이용할 수 있는 플래그는 하나밖에 없는데, 바로 G_MARKUP_TREAT_CDATA_AS_TEXT다. 이 플래그가 설정되면 CDATA로 표시된 섹션들이 passthrough() 대신 text()의 구현부로 전달될 것이다. g_markup_parse_context_new()의 마지막 두 매개변수는 데이터를 GMarkupParser 함수로 전달하고, 파서가 해제되면 호출할 함수를 제공한다.
XML은 g_market_parse_context_parse()를 이용해 파싱된다. 이 함수를 호출하면 제공된 텍스트 문자열에서 모든 태그를 훑어보고 적절한 GMarkupParser 함수를 호출한다. 파싱이 성공하면 이 함수는 TRUE를 리턴할 것이다.
gboolean g_markup_parse_context_parse (GMarkupParseContext *context,
const gchar *text,
gssize text_len,
GError **error);
파스 컨텍스트의 사용이 끝나면 g_markup_parse_context_free()를 호출해야 한다. 단, 개발자의 GMarkupParse 함수에서 이 함수를 호출할 수는 없다.
기타 자원들
축하한다! 이로써 본 서적을 모두 끝마침과 동시 복잡한 GTK+ 애플리케이션을 개발하고 관리하기에 충분한 지식을 습득하였을 것이다. 하지만 지금부터 무엇을 해야 할 것인지 궁금한 이들이 있을 것이다. 스스로 애플리케이션을 개발하면서 필수적인 라이브러리와 자원들이 많이 존재한다.
본문에서 언급하였듯 첫 번째 자원은 이 책의 공식 웹 사이트로, http://www.gtkbook.com 에서 찾을 수 있다. 이 사이트에는 GTK+ 개발자들을 위한 온라인 자원의 링크는 물론이고 본문에서 다루지 않은 주제들에 대한 지침서도 포함되어 있다. GTK+ 애플리케이션의 개발 시 이 사이트의 내용을 참고한다면 많은 도움이 될 것이다.
또 다른 훌륭한 자원으로 GTK+ 웹 사이트를 들 수 있는데, http://www.gtk.org 에서 이용할 수 있다. 이 사이트는 메일링 리스트, 다운로드, GTK+의 버그 추적에 관한 정보를 포함한다. 최신 문서도 찾을 수 있을 것이다. GNOME 개발자의 홈 페이지인 http://developer.gnome.org 또한 학습에 도움이 될 것이다.
GTK+와 그것이 지원하는 라이브러리 외에도 GNOME용 애플리케이션을 개발하면서 계속 사용하게 될 라이브러리들이 다수 있다. 아래 리스트는 이러한 라이브러리들 중 몇 가지를 예로 들어 그 용도를 간략하게 설명하는데, 이러한 라이브러리에 관한 상세한 정보는 http://developer.gnome.org 에서 찾을 수 있다.
- Libgnome: 주로 Libgnomeui와 함께 배포되며, 최신 GTK+ 위젯의 기능을 확장하는 위젯과 객체를 다수 제공한다. 최신 배포판에서는 이러한 라이브러리로부터 가장 자주 사용되는 위젯들을 GTK+로 통합하였다. 예를 들어, Libgnomeprint와 Libgnomeprintui는 현재 GTK+에서 인쇄 지원으로 구현되고 있다.
- GConf: GNOME에서 애플리케이션에 대한 다양한 설정과 데스크톱 환경 자체를 저장하기 위해 사용되는 시스템이다. GConf 라이브러리를 이용하면 애플리케이션에 대한 여러 종류의 설정을 저장하고 변경내용을 감시하여 애플리케이션을 즉시 업데이트할 수 있다. 애플리케이션의 다수의 인스턴스가 동시에 실행될 때 특히 유용하다.
- GnomeVFS: GNOME 가상 파일 시스템을 줄인 말로, 이 라이브러리는 로컬 또는 원격 파일을 읽고, 쓰고, 실행하기 위한 추상적 레이어에 불과하다. MIME 파일 타입을 처리하고 특정 파일의 MIME 타입을 검색하는 데에도 사용할 수 있다.
- ORBit: 코드가 컴퓨터 간 프로그램 호출을 만들 수 있도록 허용하는 데에 사용되는 공통 객체 요구 매개자 구조(CORBA)와 호환되는 라이브러리다. 다수의 프로그래밍 언어가 함께 작동할 수 있도록 표준 정의를 이용한다.
- Libart: GnomeCanvas와 같은 다양한 위젯을 렌더링하는 데에 사용되는 벡터 기반의 그래픽 라이브러리다. GNOME 데스크톱 환경을 위한 복잡한 그리기 액션을 처리한다.
- Bonobo: 복합 문서의 모델링을 위해 GNOME이 사용하는 CROBRA 기반의 라이브러리 집합이다.
VTE: GNOME에서 다수의 애플리케이션이 사용하는 단말 에뮬레이터 위젯이다. Terminal을 어떤 애플리케이션으로든 GtkWidget으로 포함시키도록 해준다.
요약
총 13개 장을 통해 GTK+와 그것이 지원하는 라이브러리에 관련된 대다수 부분에 익숙해졌을 것이다. 이렇게 습득한 지식을 이용하면 다수의 플랫폼에서 실행되는 애플리케이션을 위한 그래픽 사용자 인터페이스를 구현할 수 있을 것이다.
본 서적은 독자에게 GTK+를 철저하게 이해시키는 데에 목적을 두었으며, 애플리케이션을 개발하는 동안 이 책이 귀중한 자원이 되길 바란다. 뒤에 실린 다섯 편의 부록은 API 문서에서 항상 면밀하게 다루지는 않는 주제들과 관련해 꼭 필요한 참조 문서로, 전문가가 되더라도 유용하게 사용할 수 있을 것이다. 6번째 부록은 연습문제를 완료하는 방법에 관한 팁과 함께 해답에 대한 간략한 설명을 제공한다.
이제 필요한 지식은 모두 겸비했으니 훌륭한 그래픽 애플리케이션 개발자가 될 때까지 계속해서 연습하고 실습하라. 홀로서기를 계속하는 데에 필요한 것은 모두 갖췄다. 필자가 이 책을 집필하면서 느낀 즐거움을 똑같이 느꼈길 바란다!