FoundationsofGTKDevelopment:Chapter 10
- 제 10 장 동적 사용자 인터페이스
동적 사용자 인터페이스
지금쯤이면 GTK+와 그것이 지원하는 라이브러리에 대해 충분히 학습하고, 이를 이용해 어느 정도 복잡한 애플리케이션을 생성할 수 있게 되었을 것이다. 하지만 이러한 애플리케이션에 사용될 위젯과 행위를 생성하고 설정하기 위한 코드를 모두 수동으로 작성하기란 지루하기 십상이다.
Glade 사용자 인터페이스 빌더는 자신의 UI를 그래픽하게 디자인하도록 허용함으로써 그러한 코드를 모두 작성하지 않아도 되도록 해준다. 이는 GTK+의 위젯 라이브러리뿐 아니라 GNOME 라이브러리에서 다양한 위젯을 지원한다. 사용자 인터페이스는 XML 파일로 저장되어 애플리케이션의 사용자 인터페이스를 동적으로 빌드할 때 사용할 수 있다.
이번 장의 마지막 부분에서는 XML 파일을 동적으로 로딩하는 데 사용 가능한 Libglade를 다룬다. Libglade는 필요한 위젯을 모두 생성하고, Glade에서 정의된 어떤 시그널이든 연결할 수 있도록 해준다.
이번 장에서는 본문을 작성할 당시 최신 Glade 버전의 사용자 인터페이스를 다룬다. 향후엔 바뀔 수도 있겠지만 이번 장에 제시된 지침에서 약간만 변경될 것이라고 장담한다. 또한 GTK+ 향후 버전에서는 Libglade가 GtkBuilder 객체로서 GTK+로 이동될 것이다. 이런 일이 발생한다면 지침을 비롯해 변경에 관한 추가 정보는 본 저서의 웹 사이트에 공지될 것이다.
이번 장에서는 다음과 같은 내용을 학습할 것이다.
- 그래픽 사용자 인터페이스(GUIs)를 디자인할 때 유념해야 할 사항
- Glade를 이용해 커스텀 그래픽 사용자 인터페이스를 디자인하는 방법
- Libglade를 이용해 Glade 사용자 인터페이스를 동적으로 로딩하는 방법
사용자 인터페이스 디자인=
이번 장에서는 Glade3와 Libglade를 이용해 동적인 사용자 인터페이스를 구현하는 방법을 학습할 것이다. 하지만 그래픽 사용자 인터페이스를 디자인할 때 유념해야 하는 개념을 먼저 학습하는 편이 신중한 처사겠다. 이러한 개념들은 추후에 사용자의 혼란과 당혹감을 피하는 데 도움이 될 것이다.
개발자는 자신이 디자인한 애플리케이션이니 그 사용 방법을 잘 알겠지만 사용자가 애플리케이션을 활용하려면 개발자가 최대한으로 도움을 주어야 한다는 사실을 인식해야 한다. 전문가든 초보자든 각 사용자는 최소한의 학습곡선을 따라 애플리케이션을 이용할 수 있어야 한다. 그렇긴 하지만 앞으로 소개할 절들은 개발자가 이러한 수준의 직관을 얻는 데 도움이 되는 많은 팁과 디자인 결정들을 포함할 것이다. 애플리케이션의 유지보수성(maintainability)도 향상시킬 것이다.
사용자 알기
사용자 인터페이스를 디자인할 때 고려해야 할 가장 중요한 대상은 바로 인터페이스를 이용하는 사람이다. 현재 작업에 모두 숙련된 이들인가, 아니면 다른 사람들보다 도움이 더 필요한가? 그들이 익숙하게 이용하는 인터페이스를 본따서 만들 수 있는가, 아니면 완전히 새롭게 만들 것인가?
개발자가 범하는 가장 큰 실수 중 하나는 사용자들의 기술 수준을 무분별하게 일반화하는 데에서 발생한다. 본인에게는 애플리케이션을 설계하는 방식이 합리적으로 보이겠지만 그건 본인이 설계했기 때문이다. 사용자에게는 애플리케이션의 사용에 관련된 사전 지식이 없다는 사실을 이해해야만 한다.
혼동을 피하기 위해서는 시간을 들여 유사한 애플리케이션을 연구해보고, 성공적으로 보이는 디자인과 문제를 야기하는 점들을 기록하라. 가령 GNOME 데스크톱 애플리케이션에서 사용되는 애플리케이션을 만들고 있는 경우, GNOME 휴먼 인터페이스 가이드라인(GNOME Human Interface Guidelines)을 확인한다면 이를 준수하는 다른 애플리케이션에 사용된 디자인을 사용하는 데 도움이 될 것이다. GNOME 휴먼 인터페이스 가이드라인은 http://developer.gnome.org/ 에서 찾을 수 있다.
사용자 인터페이스를 디자인 시 고려해야 하는 또 다른 사항은 접근성이다. 사용자에게 시각적 문제가 있어 애플리케이션을 사용하지 못하는 수도 있다. 접근성 툴킷(Accessibility Toolkit)은 GTK+ 애플리케이션을 스크린 리더와 호환이 되도록 만들어주는 기능을 많이 제공하고 있다. GTK+는 또 테마에 많이 의존하기 때문에 가능한 한 글꼴을 설정하거나 사용자에게 글꼴을 변경할 방법을 제공하는 것을 피해야 한다.
언어 또한 사용자 인터페이스를 디자인 시 중요한 고려사항이다. 우선 개발자는 항상 사용자에게 익숙한 용어를 이용해야 한다. 가령, 공학 애플리케이션에서는 수학적 용어를 마음껏 사용해도 좋지만 웹 브라우저에서는 삼가야 한다.
많은 애플리케이션들이 유명세를 타고 나면 다른 언어로 번역되는데, 다른 문화에서 불쾌하게 여기는 단어나 이미지를 이용할 경우 문제를 야기할 수도 있음을 명심한다.
디자인을 간단하게 유지하기
애플리케이션의 사용 대상을 이해했다면 이제 효과적인 사용자 인터페이스를 디자인하기가 훨씬 수월해지긴 하지만 인터페이스가 너무 어렵거나 어수선하다면 여전히 문제에 직면할 것이다. 화면에 표시되는 위젯의 수는 언제나 적당한 개수로 제한하도록 하라.
예를 들어, 사용자에게 여러 개의 선택권을 제시하고 하나만 선택하도록 만들어야 한다면 라디오 버튼을 이용하길 원할 것이다. 하지만 GtkComboBox를 이용한다면 필요한 위젯의 수를 엄청나게 감소시킬 것이다.
GtkNotebook 컨테이너는 비슷한 옵션 그룹을 그룹화하는 데 매우 유용하게 사용되어 페이지를 어지럽게 만드는 일을 막을 수 있다. 많은 애플리케이션에서는 서로 연관되거나 의존하는 위젯들을 개인설정 대화상자(preferences dialog)로 그룹화하는 데에 이 위젯을 이용한다.
메뉴 레이아웃도 항상 실용적인 방식으로 이루어지는 것은 아니기 때문에 문제를 야기하는 영역이 된다. 가능하다면 File, Edit, View, Help, Format, Window와 같은 표준 메뉴를 이용해야 한다. 이러한 메뉴들은 컴퓨팅에 경험이 있는 사용자들에게 익숙하므로 이러한 메뉴를 예상할 것이다. 따라서 이러한 메뉴에는 표준 항목들이 포함되어 있어야 한다. 가령 File 메뉴는 파일을 조작, 인쇄하고 애플리케이션을 종료하기 위한 항목을 포함해야 한다. 특정 항목을 어디에 위치시켜야 할지 확실치 않다면 다른 애플리케이션에서 어떻게 배치하는지 알아볼 필요가 있겠다.
반복적인 작업 또는 사용자가 자주 실행하는 작업은 언제나 빠르고 쉽게 만들어야 한다. 이를 위한 방법에는 여러 가지가 있다. 그 중에서도 많은 액션에 대해 키보드 가속기를 제공하는 것이 가장 중요한데, 키보드에서 Ctrl+O를 누르는 편이 File 메뉴나 Open 메뉴 항목을 클릭하는 것보다 훨씬 빠르기 때문이다.
가능하면 자르기에는 Ctrl+X를, 무언가 새로운 대상을 생성할 때는 Ctrl+N과 같은 표준 키보드 가속기를 이용하도록 한다. 이는 애플리케이션 사용자의 초기 학습 곡선 폭을 상당히 줄여줄 것이다. 사실 일부 키보드 가속기는 많은 위젯에 이미 내장되어 있는데, 가령 텍스트 위젯에서 선택된 내용을 자르는 데에는 Ctrl+X가 내장되어 있다.
사용자가 키보드 가속기에 익숙해지기까지는 시간이 어느 정도 소요될 수 있기 때문에 반복적인 옵션에는 툴바가 매우 유용하다. 하지만 툴바에 위치시키는 항목 개수의 균형을 맞출 필요가 있겠다. 툴바가 너무 복잡해지면 사용자를 놀라게 하거나 혼란스럽게 만들고, 툴바에 항목이 너무 없으면 쓸모가 없게 된다. 사용자가 원할 법한 항목이 툴바에 많이 표시되어 있다면 사용자가 툴바를 스스로 맞춤설정하도록 허용하는 편이 낫겠다.
일관성을 유지하라
일관성은 그래픽 사용자 인터페이스를 디자인할 때 핵심이 되며, GTK+는 매우 쉽게 이를 가능하게 한다. 가장 먼저 GTK+는 많은 스톡 항목을 제공하는데, 이들은 해당하는 장소에서 자체적으로 제공하는 항목에 유용하게 사용되어야 한다. 사용자는 스톡 항목의 아이콘에는 이미 익숙할 것이므로 사용 방법을 숙지했을 것이다.
스톡 항목은 올바르게 사용하지 않으면 위험성이 매우 크다. 처음부터 의도하지 않은 액션에 스톡 항목을 절대 이용해선 안 된다. 가령, GTK_STOCK_REMOVE가 "마이너스 부호"처럼 생겼다고 해서 빼기 연산에 사용해서는 안 된다는 말이다. 아이콘은 사용자의 테마에 의해 정의되며 항상 당신이 추측하는 모양은 아니다.
테마에 관해서는 가능한 한 테마가 제공하는 설정에 의존해야 한다. 그러면 자신의 애플리케이션 내에서 뿐만 아니라 전체 데스크톱 환경에서 일관된 모양이 유지된다. 테마는 데스크톱의 모든 애플리케이션에 적용되므로 애플리케이션은 사용자가 실행하는 대부분의 애플리케이션과도 일관될 것이다.
드물지만 사용자의 테마가 제공하는 기본값(defaults)에서 벗어나야 하는 경우, 항상 사용자에게 설정을 변경하거나 시스템 기본값을 사용할 수 있는 방도를 제공해야 한다. 이는 글꼴과 색상을 다룰 때 특히 중요한데, 개발자가 변경한 내용으로 인해 일부 테마와 함께 애플리케이션을 이용할 수 없는 결과가 발생하기도 하기 때문이다.
일관성을 유지할 때 또 다른 장점으로 사용자가 애플리케이션을 훨씬 빠르게 학습한다는 점을 들 수 있다. 사용자는 다수의 디자인을 학습하는 대신 하나만 학습해도 충분할 것이다. 애플리케이션과 추가 대화상자에 일관된 레이아웃을 이용하지 않으면 사용자는 새로운 창마다 새로운 모험을 감수해야 할 것이다.
사용자를 계속 개입시켜라
애플리케이션이 오랜 시간 응답하지 않는 것만큼 사용자를 재빠르게 애플리케이션으로부터 떨어뜨리는 요인은 없다. 대부분의 컴퓨터 사용자는 한두 개의 버그에 익숙해져 있지만 애플리케이션이 정보를 처리하면서 장시간 반응하지 않은 채로 유지된다면 사용자는 포기하고 말 것이다.
이를 피하기 위한 해결책이 두 가지 있다. 첫 번째는 애플리케이션을 좀 더 효율적으로 만드는 것이다. 하지만 애플리케이션에 문제가 없거나 애플리케이션을 좀 더 효율적으로 만들 방도가 없다면 진행 막대를 이용해야 한다. 진행 막대는 애플리케이션이 아직도 작동하고 있음을 사용자에게 알려준다. 진행 막대의 업데이트를 확실히 하라! 프로세스의 소요 시간을 알지 못한다면 진행 막대의 펄싱을 통해 사용자에게 프로세스의 진행 상태에 대해 알려주는 메시지를 제공하는 것도 좋은 방법이다.
뿐만 아니라 제 2장에서 배운 아래의 루프도 기억해 두어라.
while (gtk_events_pending ())
gtk_main_iteration ();
해당 루프는 프로세서가 다른 작업을 처리하는 동안에도 사용자 인터페이스가 업데이트되도록 확보할 것이다. CPU 집약적 프로세스가 처리되는 동안 사용자 인터페이스를 업데이트하지 않으면 애플리케이션은 프로세스가 완료될 때까지 사용자에게 반응하지 않을지도 모른다!
액션이 실행될 때 사용자에게 피드백도 제공해야 한다. 문서가 저장되고 있다면 개발자는 unmodified(변경되지 않음)라고 표시하거나 상태 바에 메시지를 표시해야 한다. 액션이 실행될 때 사용자에게 피드백을 제공하지 않으면 액션이 실행되지 않았다고 가정할 수도 있다.
메시지 대화상자는 피드백을 제공하는 매우 유용한 방법이지만 필요 시에만 사용되어야 한다. 메시지 대화상자가 너무 자주 나타나면 사용자가 당황할 수도 있으므로 중요한 오류나 경고는 필요 시에만 보고되어야 한다.
누구든 실수는 하기 마련이다
전문가든 초보자든 우리 모두 실수는 하기 마련이다. 따라서 우리는 사용자를 항상 용서해야만 한다. 모두들 한두 번쯤은 올바르지 않은 버튼을 눌러 장시간 작업한 내용을 날려버린 경험이 있을 것이다. 적절하게 설계된 애플리케이션에서는 이런 일이 절대로 발생해선 안 된다.
사용자가 쉽게 실행취소할 수 없는 기본 액션의 경우, 개발자는 액션을 되돌리는(undo) 기능을 제공해야 한다. 가령 앞에서 살펴본 Grocery List 애플리케이션에서 항목을 제거한다거나 텍스트 뷰에서 텍스트를 이동하는 일이 이러한 기본 액션에 해당하겠다.
실행취소가 불가한 액션의 경우, 확인 대화상자를 항상 제공해야 한다. 이는 액션을 되돌릴 수 없음을 명시적으로 알리고 사용자에게 계속하길 원하는지 질문해야 한다. 저장하지 않은 변경내용이 담긴 문서가 있을 때 애플리케이션을 닫을 것인지 사용자에게 항상 물어보아야 하는 경우를 예로 들 수 있겠다. 사람들은 다년간 소프트웨어를 사용해왔고, 실행을 되돌릴 수 없는 액션에 대해서는 확인 대화상자가 표시될 것이라고 기대한다.
Glade 사용자 인터페이스 빌더
GUI 툴킷의 성패를 좌우하는 한 가지 요인으로는 애플리케이션을 재빠르고 효율적으로 활용할 수 있는 능력을 들 수 있겠다. 사용자 인터페이스는 애플리케이션의 성공에 극히 중요하지만 개발 과정에서 가장 소모적인 측면이 되어선 안 된다.
Glade는 개발자가 그래픽 사용자 인터페이스를 재빠르고 효율적으로 디자인하여 코드의 다른 측면으로 넘어갈 수 있도록 해주는 툴이다. 사용자 인터페이스는 위젯 구조체, 각 위젯의 프로퍼티, 그리고 개발자가 각각에 연관시킨 시그널 핸들러를 설명하는 XML 파일로 저장된다. 후에 Libglade는 사용자 인터페이스를 로딩하여 애플리케이션 로드에 동적으로 빌드하는 데에 사용할 수 있다. 이는 애플리케이션을 재컴파일하지 않고 사용자 인터페이스의 외관을 변경하도록 해준다.
Glade의 기존 버전들은 사용자 인터페이스를 XML 파일로 저장하는 대신 소스 코드를 생성하도록 해주었다. 이 방법은 개발자가 자신의 사용자 인터페이스를 변경하고자 할 때 관리가 까다로워지기 때문에 더 이상 사용하지 않는다. 따라서 이번 장에 소개된 방법을 따르도록 한다.
Glade의 장단점부터 먼저 인식할 필요가 있겠다. Glade는 애플리케이션의 사용자 인터페이스를 디자인하고, 자신의 코드에 구현된 콜백 함수와 연관될 시그널을 설정하며, 공통된 위젯 프로퍼티를 처리하는 데 사용할 수 있다. 하지만 Glade는 코드 에디터나 통합 개발 환경은 아니다. 그것이 출력하는 파일은 애플리케이션에 의해 로딩되어야 하며, 개발자는 자신의 코드에 콜백 함수를 모두 구현해야만 한다. Glade는 애플리케이션의 사용자 인터페이스를 초기화하고 시그널을 연결하는 과정을 간소화하도록 만들어졌다.
본문에서 사용된 버전인 Glade 3은 현재 통합 개발 환경을 허용하는데, 이를 사용자 인터페이스로 포함시킨 Anjuta를 예로 들 수 있겠다. 이러한 IDEs는 GTK+ 애플리케이션을 효율적으로 이용하는 완전한 해답을 제공한다.
사용자 인터페이스는 XML 파일로 저장되므로 언어와 무관하다는 점 또한 Glade의 또 다른 장점으로 들 수 있다. Libglade가 제공한 기능을 래핑한 언어라면 사용자 인터페이스를 로딩할 수 있다. 즉, 개발자가 선택한 프로그래밍 언어와 상관없이 동일한 그래픽 사용자 인터페이스 디자이너를 이용할 수 있다는 말이다.
이번 장의 나머지 부분을 계속하기 전에 먼저 Glade, Libglade, 그리고 본인 운영체제의 패키지 관리자(package manager)에서 Libglade용 개발 패키지를 설치해야 한다. 아니면 http://glade.gnome.org/ 에서 소스를 다운로드한 후 컴파일해도 된다.
뒷부분을 읽는 동안 지침을 따라 애플리케이션을 설치하도록 한다. 그러면 Glade 3 애플리케이션에 대해 학습하여 책의 안내에 따라 충분히 실습할 수 있을 것이다.
Glade 인터페이스
처음으로 Glade를 시작하면 메인 창, 위젯 팔레트, 위젯 프로퍼티 에디터, 3개의 창이 표시될 것이다. 그림 10-1은 browser.glade 파일에서 열린 프로젝트와 함께 Glade 애플리케이션의 메인 창의 스크린샷을 표시한 그림이다.
메인 창은 Glade 프로젝트 관리를 가능하게 한다. Projects 메뉴는 최근에 연 프로젝트의 리스트를 표시하여 프로젝트 간 전환을 가능하게 한다. 메인 창에는 위젯 트리 뷰도 포함되어 있어서 포커스가 있는 프로젝트의 위젯 포함을 보여준다.
위젯 트리 뷰는 프로젝트 내의 부모-자식 컨테이너 관계를 표시한다. 다수의 최상위 수준 위젯도 가능하다. 하지만 그림 10-1에서 browser.glade 프로젝트의 최상위 수준 위젯은 window가 유일하다.
이 창에서는 프로젝트 옵션을 명시하고, 프로젝트를 저장하며, 기존 프로젝트를 로딩할 것이다. 창의 메뉴에는 액션의 실행취소(undo)/재실행(redo)과 같이 프로젝트를 작업 시 도움이 되는 옵션들도 다수 제공한다.
Glade 3 대신 Glade 2를 이용해 작업하기로 결정했다면 자주 저장하는 습관을 들여라. 오래된 Glade 버전에서는 액션의 실행취소/재실행 지원이 구현되지 않는데, 한 번 마우스를 잘못 클릭해서 장시간 작업한 내용을 덮어쓰는 일이 발생한다면 얼마나 좌절하겠는가!
Glade 3를 시작하면 표시되는 두 번째 창은 위젯 팔레트(widget palette)로, 애플리케이션을 디자인할 때 이용할 수 있는 위젯을 모두 열거한다. 위젯 팔레트의 스크린샷을 그림 10-2에 소개하였다.
기본적으로 위젯의 네 가지 범주가 표시되는데, 최상위 수준 위젯, 컨테이너, 제어와 표시에 사용되는 위젯, 더 이상 사용하지 않는 위젯이 되겠다. GTK+ Obsolete 목록에 있는 위젯은 너무 오래되고 잘 사용되지 않으므로 추후 배포판에선 제거될 수도 있기 때문에 새 애플리케이션에 사용하지 않도록 한다.
위젯의 기본 범주에 더해 추가 위젯 라이브러리를 포함하는 다른 범주들도 찾을 수 있겠다. 이러한 위젯들은 GNOME 라이브러리나 다른 커스텀 위젯 라이브러리에 추가되기도 한다.
View 메뉴를 이용해 위젯 팔레트의 레이아웃을 변경할 수 있다. 그림 10-2는 아이콘과 텍스트를 모두 표시하도록 설정된 위젯 팔레트를 보여준다. 하지만 본인이 익숙한 스타일에 따라 텍스트만 표시하거나 아이콘만 표시할 수도 있다.
최상위 수준의 위젯을 새로 생성하려면 Toplevels 섹션에서 원하는 위젯의 아이콘을 클릭하기만 하면 된다. 그러면 새로운 최상위 수준의 위젯이 표시되어 메인 창의 위젯 트리로 추가된다. 최상위 수준이 아닌 위젯을 추가하려면, 원하는 위젯의 아이콘을 먼저 클릭한 후 위젯을 위치시키고자 하는 장소에 마우스를 클릭한다. 최상위 수준이 아닌 위젯을 사용자 인터페이스로 추가하려면 컨테이너 위젯 내의 빈 셀을 클릭해야 한다.
창 생성하기
이번 장에서는 Glade와 Libglade를 이용해 간단한 파일 브라우저 애플리케이션을 생성해볼 것이다. 먼저 File▶New를 이용해 새 프로젝트를 생성하거나, 애플리케이션이 로딩될 때 생성된 빈 프로젝트를 이용한다. 추후에 이 지침서를 다시 읽는다면 File▶Open을 이용해 기존 프로젝트를 열 수도 있다.
빈 프로젝트가 생기면 위젯 팔레트의 Window 아이콘을 클릭하여 새로운 최상위 수준의 GtkWindow를 생성한다. 새 창에는 그림 10-3에 표시된 바와 같이 위젯의 내부에 그물(mesh) 모양 패턴이 보일 것이다. 이 패턴은 컨테이너에서 자식 위젯을 추가할 수 있는 영역을 지정한다. 위젯 팔레트에서 최상위 수준이 아닌 위젯을 선택했다면 이 영역을 클릭하여 위젯을 컨테이너로 추가해야 한다. 최상위 수준이 아닌 위젯을 추가 시에는 이 방법을 이용한다.
최상위 수준의 창을 생성한 다음에는 그림 10-4와 같이 위젯 Properties 창의 내용이 변경되었음을 눈치챌 것이다. 이 창에서는 Glade에서 지원되는 각 위젯의 모든 프로퍼티를 맞춤설정이 가능하다.
Glade는 다수의 위젯 프로퍼티를 편집할 수 있도록 해주지만 일부 액션은 단순히 코드에서 실행되어야 한다. 따라서 본문에서 학습한 모든 내용을 Glade로 대체하겠다는 생각은 하지 않는 것이 좋다. 대부분의 애플리케이션에서는 여전히 GTK+를 이용해 개발을 할 것이다.
그림 10-4에 표시된 위젯 Properties 창에는 다양한 옵션으로 찬 5개의 탭이 있다. General 탭은 현재 선택된 위젯 타입에 특정적인 기본 옵션을 제공한다. 예를 들어, GtkWindow 위젯은 창의 타입, 제목, 크기조정 기능, 기본 크기 등을 명시하도록 해준다.
Name 필드는 그림 10-4에서 스크롤이 있는 창 범위 밖으로 스크롤되어 위젯에 유일한 이름을 부여하는 데에 사용된다. Glade는 현재 프로젝트에 유일한 이름을 각 위젯으로 자동 할당하겠지만 이는 일반적인 이름이다. 애플리케이션으로부터 위젯을 참조할 계획이라면 의미가 있는 이름을 부여해야 한다. treeview1, treeview2, treeview3로 명명된 GtkTreeView 위젯이 3개가 로딩되면 얼마나 혼동되겠는가!
Packing 탭은 부모 위젯의 크기가 변경되면 위젯이 어떻게 반응할 것인지에 대한 기본 정보를 제공하는데, 확장하기(expanding)와 채우기(filling)를 예로 들 수 있겠다. 공통 프로퍼티는 GtkWidget가 제공하는 프로퍼티로, 모든 위젯에 이용 가능하다. 따라서 크기 요청(size request)을 이 탭에서 제공할 수 있겠다.
Glade를 이용해 처음 작업한다면 프로퍼티는 부모 컨테이너가 아니라 자식 컨테이너에 의해 설정되기 때문에 패킹 옵션이 그다지 직관적이지 못할지도 모른다. 가령, GtkVBox의 자식에 대한 패킹 옵션은 부모 컨테이너가 아니라 자식 컨테이너의 Packing 탭에서 제공될 것이다.
Signals 탭은 각 위젯에 대한 시그널을 정의하도록 해주는데, 이러한 시그널은 Libglade가 연결하게 될 것이다. 마지막으로 장애인 부호로 표시된 Accessibility 탭은 장애의 지원 시 사용되는 옵션을 제공한다.
이 책에서 처음에 제시한 예제를 다시 상기해보면, 빈 GtkWindow 위젯은 위젯의 생성 방법을 설명할 때를 빼곤 쓸모가 없음을 기억할 것이다. 이 애플리케이션에서 파일 브라우저는 메인 창에 패킹된 여러 개의 위젯을 필요로 하므로 수직 박스 컨테이너를 추가하는 것이 다음 단계가 되겠다. 팔레트에서 Vertical Box 위젯을 선택한 다음에 창의 그리드 패턴 내부를 클릭하면 GtkVBox 위젯이 창으로 추가된다. 여기까지 완료되면 그림 10-5에 표시된 그림과 같은 대화상자가 뜨면서 GtkVBox가 보유한 항목의 개수를 묻는다.
기본적으로 자식 위젯을 보유하기 위해 세 개의 셀이 생성되지만 셀의 개수는 원하는 항목의 개수로 변경할 수 있으며 항상 0보다 커야 한다. 필요로 하는 자식 위젯의 개수가 기본값이 되기 때문에 OK 버튼을 클릭하면 된다.
컨테이너가 보유하게 될 위젯의 개수가 확실치 않더라도 염려하지 않아도 된다. 위젯의 Properties 창의 General 탭에서 셀을 추가하거나 삭제하는 것이 가능하기 때문이다. 이후 Packing 탭 아래 포함된 상자에서 위젯의 위치를 변경할 수 있다. Libglade를 이용해 사용자 인터페이스를 빌드하고 나서도 개발자의 코드를 이용해 편집이 가능하다!
수직 박스를 추가하고 나면 세 개의 빈 컨테이너에 그물 패턴이 보일 것인데, Properties 창과 위젯 트리 뷰에서 무엇이 변경되었는지 확인하라. 이 그물 패턴에 우리는 툴바, 주소창(address bar), 트리 뷰를 추가할 것이다.
툴바 추가하기
핸들 박스로 툴바를 추가하기 위해 툴바를 생성할 때는 주로 사용자가 원할 때 창에서 툴바를 제거할 수 있도록 만드는 것이 바람직하겠다. 이를 위해선 위젯 팔레트에서 Handle Box 항목을 선택하고 젤 위에 위치한 GtkVBox 셀을 클릭해야 한다. 후에 이와 같은 방식으로 툴바 위젯을 핸들 박스로 추가할 수 있다.
앞 장에서 소개한 방식대로 툴바를 생성하길 원한다면 핸들 박스를 생성하되 그곳에 자식 위젯을 추가해선 안 된다. 애플리케이션의 코드를 작성하면서 프로그램적으로 툴바를 핸들 박스로 추가하면 된다.
툴바 위젯을 선택하면 위젯의 Properties 창의 좌측 하단 모서리에 Edit 버튼이 생김을 눈치챌 것이다. 이 버튼을 클릭하면 그림 10-6과 같은 툴바 에디터가 열릴 것이다. 이 툴바 에디터는 툴바를 구성하는 새로운 툴 항목들을 생성한다. 이러한 툴 항목에 대한 콜백 함수는 모두 개발자가 자신의 코드에서 구현해야 한다.
툴바 에디터는 지원되는 항목이라면 모두 툴바로 추가하도록 해준다. 새로운 항목을 추가하려면 Add 버튼만 클릭하면 된다. 버튼을 클릭하면 일반적인 툴 버튼이 삽입되지만 항목의 타입은 후에 변경이 가능하다. 우측에 보면 새로운 툴 항목에 해당하는 옵션이 많이 존재한다.
새로운 툴 버튼을 추가하고 나면 Type 콤보 박스에서 옵션을 선택하여 위젯의 타입을 선택한다. 콤보 박스에 포함된 툴바 항목의 타입은 일반적인 툴 버튼으로, 이미지, 라벨, 토글, 라디오 버튼, 메뉴 툴 버튼, 툴 항목, 구분자를 포함한다. 새로운 타입을 선택하면 대화상자는 선택된 타입에 대한 프로퍼티를 편집하는 창으로 즉시 변경된다.
예를 들어, 그림 10-6에서 선택된 툴 버튼의 타입은 GtkMenuToolButton 에 해당한다. 모든 툴바 항목은 툴바가 수평적이거나 수직적일 때 툴바 항목의 표시 여부를 결정하는 옵션을 제공한다. 따라서 개발자는 수직 방향일 때는 툴바 항목을 숨기고, 툴바가 수평 방향이면 사용자에게 표시하도록 설정할 수가 있다.
메뉴 툴 버튼은 툴 항목에 표시할 라벨과 이미지도 선택하도록 해준다. 이미지는 Image Type 콤보 박스에서 자신이 선택한 옵션에 따라 스톡 이미지, 기존에 있던 이미지 파일, 커스텀 아이콘 테마의 식별자 중에서 하나가 선택된다.
툴바 에디터의 하단을 따라서는 시그널을 각 툴 버튼으로 연결하도록 해주는 트리 뷰가 보일 것이다. Glade는 시그널명과 개발자가 툴바 항목에 부여한 이름을 바탕으로 선택하도록 다수의 명명된 콜백 함수를 제공한다. 자신만의 커스텀 콜백 함수명을 입력할 수도 있다. Libglade를 통해 각 콜백 함수를 전달하도록 데이터를 명시하는 것도 가능하므로 "User data" 매개변수를 빈칸으로 두어도 된다. 그림 10-6에서는 on_back_clicked()라는 이름에 의해 명명된 콜백 함수가 GtkToolButton의 clicked 시그널로 연결되었다.
Libglade를 이용해 사용자 인터페이스를 로딩할 때는 Glade 파일에 정의된 콜백 함수와 자신의 코드에 포함된 콜백 함수를 연결하는 방법이 두 가지가 있다. 개별적 콜백 함수를 수동으로 연결하고 싶을 때는 이름이 유일하다는 조건 하에서 시그널 핸들러를 원하는대로 명명해도 좋다. 하지만 Libglade에서는 개발자의 실행 파일에서 적절한 심볼을 찾도록 GModule을 이용함으로써 모든 시그널을 자동으로 연결하는 함수가 제공된다. 이 기능을 이용하려면 개발자가 Glade에서 정의한 콜백 함수명이 코드에 포함된 함수명과 매치해야만 한다!
툴바의 편집이 끝나면 핸들 박스는 항상 창의 높이에서 정확히 1/3만큼 차지함을 눈치챌 것인데, 기본적으로 위젯이 확장되어 공간을 채우도록 설정되기 때문이다. 그림 10-7에서 보이는 바와 같이 핸들 박스의 expand 프로퍼티의 설정을 해제하길 원할 것이다.
제 3장에서 expand 와 fill 프로퍼티가 GtkBox 위젯의 자식 위젯들에게 어떤 액션을 실행하는지 설명하면서 제공한 표를 기억해보자. Glade는 이것이 어떻게 위젯에게 영향을 미치는지 더 잘 이해하도록 여러 가지 패킹 옵션을 실험하기에 최적의 기회를 제공한다. 따라서 시간을 들여 여러 가지 패킹 옵션을 실험해보도록 한다!
Packing 탭은 패킹이 박스의 시작 부분과 끝 부분 중 어디서 시작되는지, 즉 위젯 주위의 패킹을 결정하고 컨테이너 내에서 위젯의 위치를 결정하기 위한 옵션을 제공한다. 이러한 프로퍼티들은 gtk_box_pack_start()와 friends를 이용해 GtkBox로 자식 위젯을 추가할 때 사용한 것과 동일한 설정(settings)에 해당한다.
툴바를 완료하고 패킹 개인설정을 수정하고 나면 애플리케이션은 그림 10-8의 모습과 같을 것이다. 창의 변을 드래그하면 창의 크기가 수직으로 확장되지만 핸들 박스는 추가 공간을 채우도록 더 이상 확장되지 않음을 주목한다!
그림 10-8에 표시된 툴바는 사용자의 브라우징 히스토리를 통해 전방향과 후방향으로 이동하는 데 사용되는 두 개의 메뉴 툴 버튼을 포함한다. 부모 디렉터리로 이동, 현재 뷰의 새로고침, 파일 제거, 홈 디렉터리로 이동, 파일 정보 보기에 사용되는 툴 버튼들도 존재한다. 이러한 툴 버튼 각각은 애플리케이션의 코드에 개발자가 구현해야 하는 콜백 함수로 연결되어 있다.
파일 브라우저 완료하기
파일 브라우저를 생성하기 위한 다음 단계는 사용자에게 현재 위치를 표시하고 새 위치를 입력하도록 해주는 주소창을 생성하는 것이다. 따라서 그림 10-9와 같이 세 개의 위젯이 있는 수평 박스가 필요하다. 세 개의 위젯은 GtkEntry 위젯에 보유된 내용을 설명하는 라벨, 현재 위치를 보유하는 GtkEntry 위젯, 그리고 누르면 그 위치로 이동하는 버튼이 해당한다.
버튼에 단순히 GTK_STOCK_JUMP_TO 스톡 항목을 사용할 수도 있지만 버튼을 컨테이너로 사용하는 방법을 보이겠다. 먼저 Properties 대화상자에서 버튼 타입을 컨테이너로 변경해야 한다. 그러면 빈 컨테이너 그물 패턴이 버튼의 내용으로 표시될 것이다.
그림 10-9에서는 버튼을 생성하기 위해 두 개의 자식 위젯이 있는 GtkHBox가 버튼으로 추가되었는데, GtkImage 위젯은 GTK_STOCK_JUMP_TO 스톡 이미지로 설정되고, GtkLabel 위젯은 "Go"라고 말한다.
주소창의 또 다른 중요한 측면으로 Current Location GtkLabel 위젯이 있는데, 이는 볼드체(bold)로 설정된다. 제 2장에서 Pango Text Markup Language에 대해 학습한 바 있다. 그림 10-10에서처럼 라벨의 개인설정 대화상자에서 "Use markup"을 선택하면 라벨 내용에 Pango Text Markup Language 태그를 이용할 수 있을 것이다. 해당 옵션을 선택하지 않으면 마크업 태그(markup tag)가 라벨에서 텍스트로 렌더링될 것이다.
마크업 프로퍼티 아래에서 "Use underline" 프로퍼티를 설정함으로써 니모닉 라벨을 활성화할 수가 있다. 그림 10-10에서는 니모닉 라벨이 꺼져 있기 때문에 밑줄 문자는 텍스트로 표시될 것이다.
마지막 단계는 GtkScrolledWindow 위젯을 수직 박스의 마지막 셀로 추가하고, GtkTreeView 위젯을 그 컨테이너로 추가하는 것이다. 완료된 파일 브라우저 사용자 인터페이스를 그림 10-11에 표시하였다. 하지만 Glade에서 애플리케이션의 편집은 아직 끝나지 않았다.
내용 변경하기
파일 브라우저의 디자인은 끝났지만 창의 하단을 따라 GtkStatusBar 위젯을 포함시키기로 결정하였다! 사용자 인터페이스의 내용을 변경하는 일은 약간 까다로우므로 이번 절에서 몇 가지 도전적인 액션을 실행하는 방법을 자세히 가르칠 것이다.
상태 바를 추가하기 위한 첫 번째 단계는 메인 GtkVBox 위젯이 포함하는 자식 위젯의 개수를 늘이는 것이다. 이를 위해선 위젯 트리 뷰에서 수직 박스를 선택한다. Properties 창에서는 General 탭의 "Number of items" 프로퍼티를 이용해 자식의 개수를 증가시킬 수 있다. 이를 실행하면 상태 바 위젯을 추가할 수 있는 새로운 빈 공간이 수직 박스의 끝에 추가된다.
수직 또는 수평 박스의 자식을 재정렬해야 하는 경우 먼저 이동하고자 하는 위젯을 선택해야 한다. 이어서 Properties 창의 Packing 탭 아래에서 스핀 버튼의 값을 변경시켜 새로운 위치를 선택할 수 있다. 스핀 버튼의 값을 변경하면 자식 위젯이 새로운 위치로 이동하는 것을 확인할 수 있다. 주위를 둘러싼 자식 위젯들의 위치는 변경내용을 반영하도록 자동으로 조정된다.
다른 위젯이 이미 추가된 위치로 컨테이너를 넣기로 결정한 경우에도 문제가 발생할 수 있다. 가령 파일 브라우저 애플리케이션에서 스크롤이 있는 창 대신 수평적 패인을 위치시키기로 결정했다고 가정하자. 먼저 메인 창의 위젯 트리 뷰에서 위젯을 선택하고 Ctrl+X를 눌러 제거해야 한다. 그리고 나면 수평적 패인을 추가할 수 있는 빈 박스가 표시될 것이다. 그 다음 스크롤이 있는 창을 위치시킬 패인을 선택한 후 Ctrl+V를 누른다.
Glade 2를 이용 시 사용 중인 사용자 인터페이스를 변경하는 일은 주의해서 다뤄야 하는 주제에 해당하는데, 바로 실행취소와 재실행 액션을 지원하지 않기 때문이다. 액션의 실행을 취소할 수 없기 때문에 최상위 수준의 위젯을 실수로 제거하는 실수를 저질러 여러 시간을 들여 작업한 내용이 손실되기가 쉽다. 현재 Glade 3에서는 실행취소/재실행을 지원하므로 그다지 염려하지 않아도 된다.
위젯 시그널
애플리케이션에서 마지막 단계는 모든 위젯을 위한 시그널을 준비시키는 것이다. 그림 10-12에서는 Go 버튼에 대한 위젯 프로퍼티의 Signals 탭을 표시하였다. GtkButton 위젯은 clicked 시그널로 연결되어, 시그널이 발생 시 on_button_clicked()를 호출한다.
clicked 시그널 외에도 개발자는 몇 가지 시그널로 연결해야 한다. 구분자를 제외한 각 툴 항목은 GtkToolButton의 clicked 시그널로 연결되어야 한다. 또 GtkEntry도 activate로 연결해야 하는데, 이는 엔트리에 포커스가 있을 때 사용자가 Enter 키를 누르면 발생한다.
이 애플리케이션은 Glade 3을 이용해 애플리케이션을 디자인하는 방법을 보여주기 위한 목적으로 만든 간단한 파일 브라우저의 디자인에 불과한다. 단순한 디자인을 넘어서기 위해 애플리케이션에 필요한 코드는 제 13장에서 구현할 것이다.
트리 뷰의 경우에는 개발자가 row-activated로 연결해야 한다. 행이 활성화되면 파일에 관한 추가 정보가 사용자에게 표시되거나 선택된 디렉터리를 사용자가 훑어볼 것이다. 위젯을 비롯해 그 시그널과 콜백 함수의 리스트를 표 10-1에서 제공하였으니 예제를 손쉽게 따라해 볼 수 있을 것이다.
위젯 | 설명 | 시그널 | 콜백 함수 |
GtkButton | Go 버튼 | clicked | go_on_clicked() |
GtkEntry | Location 엔트리 | activate | on_location_activate() |
GtkMenuToolButton | 뒤로 | clicked | on_back_clicked() |
GtkMenuToolButton | 앞으로 | clicked | on_forward_clicked() |
GtkToolButton | 위로 | clicked | on_up_clicked |
GtkToolButton | 새로고침(refresh) | clicked | on_refresh_clicked() |
GtkToolButton | 홈 | clicked | on_home_clicked() |
GtkToolButton | 삭제 | clicked | on_delete_clicked() |
GtkToolButton | 정보 | clicked | on_info_clicked() |
GtkTreeView | 파일 브라우저 | row-activated | on_raw_activated |
GtkWindow | 메인 창 | destroy | gtk_main_quit() |
표 10-1. 위젯 시그널 |
메뉴 생성하기
툴바 외에 Glade 3을 이용해 메뉴 또한 생성이 가능하다. 그림 10-13은 메뉴 바 에디터를 담은 그림인데 툴바 에디터와 상당히 비슷하다. 일반적인 메뉴 항목을 비롯해 이미지, 체크 버튼, 라디오 버튼, 구분자가 렌더링된 항목을 지원한다.
메뉴를 생성하는 방법 세 가지를 배웠으니 그 중에서 어떤 것이 최고의 방법인지에 대한 질문이 발생한다. 각 방법마다 장·단점이 있기 마련이니 하나씩 살펴보도록 하겠다.
가장 먼저 메뉴를 수동으로 생성하고 각 객체를 자신의 요구대로 맞추는 방법을 살펴보았다. 이 방법은 조그마한 메뉴를 이용할 때 좋은데, 코드가 공간을 많이 차지하지 않고 구현이 전적으로 한 곳에 위치하기 때문이다. 하지만 메뉴의 크기가 커지고 기본 항목 이상을 포함하게 되면 코드의 유지가 번거로워지고 공간도 많이 차지하게 된다.
다음으로 UI 정의와 함께 GtkUIManager를 이용해 메뉴를 동적으로 생성하는 방법을 학습하였다. 이 방법은 좁은 공간에 많은 수의 액션을 정의할 수 있기 때문에 메뉴의 생성 과정을 간소화한다. 뿐만 아니라 메뉴가 UI 정의로부터 구성되기 때문에 사용자가 메뉴를 극도로 간단하게 편집할 수 있도록 허용한다. 애플리케이션의 디자인 시 Glade를 사용하지 않을 경우 선호할만한 메뉴 생성 방법이겠다.
Glade도 매우 매력적인 메뉴 생성 방법을 제공하는데, 처음에 디자인된 후 관리가 매우 간단하기 때문이다. Libglade가 메뉴를 알아서 구성해주기 때문에 메뉴를 생성하기 위한 코드가 필요 없는 방법이기도 하다. 하지만 이 방법에는 한 가지 문제가 있는데, UI 파일을 이용할 때처럼 사용자가 메뉴와 툴바의 레이아웃을 변경하도록 허용하는 것이 쉽지 않다는 점이다.
쉽게 이용할 수 있는 방법을 하나 꼽자면, 수직 박스 또는 메인 창의 자식으로 이용하는 컨테이너가 무엇이든 그 끝에 개발자의 위젯을 모두 패킹하는 방법이다. 그러면 애플리케이션이 로딩될 때 gtk_box_pack_start()를 이용해 GtkUIManager가 생성한 메뉴를 단순히 창으로 패킹할 수 있다. 그럼에도 불구하고 메뉴의 맞춤설정을 사용자에게 허용하지 않아도 된다면 Glade를 통해 모든 메뉴를 생성해도 괜찮겠다.
이제 사용자 인터페이스의 생성이 끝났으니 project.glade 파일로 저장이 가능한데, 여기서 project는 원하는 이름으로 변경해도 좋다. 파일은 애플리케이션의 위치를 기준으로 상대 경로 또는 절대 경로로부터 로딩이 가능하다.
Libglade 이용하기
Glade에서 애플리케이션을 디자인한 후에는 Libglade를 이용해 사용자 인터페이스를 로딩해야 한다. 이 라이브러리는 Glade 사용자 인터페이스를 파싱하고 런타임 시 필요한 모든 위젯을 생성하는 데에 사용된다.
Libglade는 XML 파일로부터 로딩되는 사용자 인터페이스를 생성 및 보유하는 데 사용되는 GladeXML 객체를 제공한다. 뿐만 아니라 Glade 파일에 추가된 시그널을 개발자의 애플리케이션에 포함된 콜백 함수로 연결할 때에도 사용된다.
Libglade의 또 다른 장점은 초기화 중에만 오버헤드가 추가된다는 점인데, 이는 코드에서 직접 생성된 인터페이스에 비하면 무시해도 좋을 만큼 적다. 초기화가 완료되면 애플리케이션에 추가된 오버헤드는 사실상 없다고 보면 된다. 예를 들어, GladeXML은 개발자의 코드와 동일한 방식으로 시그널 핸들러를 내부적으로 연결하기 때문에 추가 처리를 요하지 않을 것이다.
Libglade는 모든 위젯 초기화를 처리하고 레이아웃은 Glade 3에 이미 디자인되어 있으므로 코드 베이스의 길이를 상당히 줄일 수 있다. 가령 리스팅 10-1에서 만약 모든 것을 수동으로 코딩(hand-code)한다면 코드의 길이는 엄청나게 길어질 것이다.
리스팅 10-1. 사용자 인터페이스 로딩하기 (browser.c)
#include <gtk/gtk.h>
#include <glade/glade.h>
void on_back_clicked (GtkToolButton*);
void on_forward_clicked (GtkToolButton*);
void on_up_clicked (GtkToolButton*);
void on_refresh_clicked (GtkToolButton*);
void on_delete_clicked (GtkToolButton*);
void on_home_clicked (GtkToolButton*);
void on_info_clicked (GtkToolButton*);
void on_go_clicked (GtkButton*);
void on_location_activate (GtkEntry*);
void on_row_activated (GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*);
int main (int argc,
char *argv[])
{
GtkWidget *window;
GladeXML *xml;
gtk_init (&argc, &argv);
xml = glade_xml_new ("browser.glade", NULL, NULL);
window = glade_xml_get_widget (xml, "window");
glade_xml_signal_autoconnect (xml);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
리스팅 10-1에 표시된 코드는 GTK+와 그것이 지원하는 라이브러리만 이용해서 컴파일하진 않을 것이다. 아래와 같은 컴파일 명령에 Libglade를 포함시켜야 할 것이다.
gcc -export-dynamic browser.c -o browser `pkg-config --cflags --libs gtk+-2.0` \
`pkg-config --cflags --libs libglade-2.0`
주요 실행 파일이나 어떤 비공유 라이브러리의 시그널을 자동으로 연결하고 싶다면 링커에 -export-dynamic을 전달해야 할 것이다. 그렇지 않으면 GModule이 코드 검사(introspection)를 위해 애플리케이션을 열어야 하는데 열 수 없는 상황이 발생하여 어떤 시그널도 연결되지 못할 것이다!
사용자 인터페이스 로딩하기
Glade 사용자 인터페이스는 glade_xml_new()를 이용해 로딩된다. 이는 개발자가 호출하게 될 첫 번째 GladeXML 함수지만, gtk_init() 다음에 호출되어야 한다. 이 함수는 XML 파일이 제공하는 사용자 인터페이스를 파싱하고, 필요한 위젯을 모두 생성하며, 번역에 사용되는 기능을 제공한다.
GladeXML* glade_xml_new (const char *glade_file_name,
const char *root_widget,
const char *translation_domain);
위의 함수는 세 개의 문자열을 수락한다. 첫 번째는 Glade 사용자 인터페이스 파일의 위치를 나타낸다. 상대 경로와 절대 경로가 모두 가능하지만 실행 파일이 사용자의 시스템에서 다른 대안적인 위치에 설치가 가능하므로 가능하면 절대 경로를 사용하도록 한다. 절대 경로는 주로 컴파일 도중에 GNU Autotools와 같은 애플리케이션에 의해 정의된다. 상대 경로는 현재 작업 디렉터리를 기준으로 결정되는데, 처음에는 실행 파일의 위치로 설정되지만 g_chdir()를 이용해 변경이 가능하다.
glade_xml_new()가 수락하는 두 번째 매개변수는 루트 노드의 위젯명이다. 개발자는 GladeXML 객체만 특정 위젯과 그 자식 위젯을 로딩하도록 위젯명을 명시할 수 있다. 이 매개변수로 NULL을 전달하면 GladeXML은 파일 내 모든 위젯을 로딩할 것이다.
glade_xml_new()를 호출할 때마다 사용자 인터페이스의 새로운 버전이 빌드될 것이다. 이 때문에 어느 시점에서든 애플리케이션에서 위젯을 소멸시키고 싶은 경우를 제외하고는 UI마다 함수를 한 번만 호출해야 한다.
마지막으로 개발자는 위젯에 대한 번역(translation) 도메인을 제공할 수 있으며, 이를 제공 시 번역이 가능하다고 표시된 위젯이 모두 처리될 것이다. 애플리케이션이 번역을 지원하지 않으면 해당 매개변수에 NULL을 전달하는 것이 안전하겠다.
새로운 GladeXML 객체를 생성함으로써 사용자 인터페이스를 초기화했다면 이제 glade_xml_get_widget()을 이용해 위젯을 검색하는 것이 가능하다. 이 함수는 이미 인스턴스화된 위젯을 리턴하는데, 이는 Glade에서 개발자가 부여한 이름으로 참조된다.
GtkWidget* glade_xml_get_widget (GladeXML *xml,
const char *name);
glade_xml_get_widget()에서 리턴된 위젯에는 개발자가 Glade에서 설정한 프로퍼티가 모두 준비되어 있다. 이 위젯은 GTK+에서 제공된 함수를 이용해 애플리케이션에서 생성한 여느 다른 GtkWidget과 마찬가지로 이용이 가능하다. 이것이 바로 Libglade의 주요 장점 중 하나에 해당하는데, 사용자 인터페이스를 설정하는 데에 모두 단조로운 코드를 제공하지 않아도 되고 애플리케이션의 흥미로운 측면들을 재빠르게 개발할 수 있다는 점이 그것이다.
또 다른 유용한 함수로 glade_xml_get_widget_prefix()가 있는데, 이는 주어진 문자열과 동일한 접두사를 가진 위젯의 리스트를 검색하도록 해준다. 위젯의 타입이나 그것이 속한 창에 따라 위젯을 모두 명명한다면 이 함수는 매우 유용하게 작용한다.
GList* glade_xml_get_widget_prefix (GladeXML *xml,
const char *name);
시그널 연결하기
애플리케이션을 사용할 준비를 시키기 위한 다음 단계는 Glade에서 생성한 시그널 핸들러를 연결하는 것이다. 리스팅 10-1에서는 glade_xml_signal_autoconnect()를 이용해 한 번에 모든 시그널을 연결하였다.
void glade_xml_signal_autoconnect (GladeXML *xml);
시그널을 자동으로 연결하기 위해 Libglade는 GModule의 NULL 버전을 여는데, 이는 애플리케이션의 심볼 테이블(symbol table)로 접근성을 제공할 것이다. 이후 함수는 동일한 시그널 핸들러명으로 된 함수를 찾고자 시도하는데, 즉 Glade 파일 내 이름이 개발자의 애플리케이션에 포함된 콜백 함수의 이름과 매치해야 한다는 뜻이다. 이 함수는 사용자의 시스템에서 GModule이 지원될 때에만 작동할 것이다.
시그널을 연결하는 또 다른 방법은 glade_xml_signal_autoconnect_full()을 이용하는 것이다. 이 함수는 개발자를 위해 모든 시그널을 알아서 연결해주는 콜백 함수를 제공하도록 해준다. 이를 통해 개발자가 필요한 맞춤설정이 무엇이든 개발자가 제공할 수 있다.
void glade_xml_signal_connect_full (GladeXML *self,
const gchar *handler_name,
GladeXMLConnectFunc connect_func,
gpointer data);
리스팅 10-2는 glade_xml_signal_autoconnect()에서와 동일한 방식으로 GModule을 이용하는 간단한 GladeXMLConnectFunc를 제시한다. 이 함수는 Glade 사용자 인터페이스 파일 내의 시그널마다 호출된다.
리스팅 10-2. 시그널 자동연결하기
static void
connect_func (const gchar *callback_name,
GObject *object,
const gchar *signal_name,
const gchar *signal_data,
GObject *connect_object,
gboolean connect_after,
gpointer data)
{
static GModule *module_self = NULL;
gpointer handler_func;
module_self = g_module_open (NULL, 0);
g_assert (module_self != NULL);
if (g_module_symbol (module_self, callback_name, &handler_func))
{
if (connect_object && connect_after)
g_signal_connect_object (object, signal_name, handler_func,
connect_object, G_CONNECT_AFTER);
else if (connect_object && !connect_after)
g_signal_connect_object (object, signal_name, handler_func,
connect_object, G_CONNECT_SWAPPED);
else if (!connect_object && connect_after)
g_signal_connect_after (object, signal_name, handler_func, data);
else
g_signal_connect (object, signal_name, handler_func, data);
}
else
g_warning ("The callback function could not be found: %s", callback_name);
}
GModule의 사용법은 이미 알고 있을 것이니 리스팅 10-2에 실린 코드를 이해할 것이다. 하지만 리스팅 10-2에서 시그널의 연결에 새로운 함수가 두 개 사용되었는데 이를 잠시 설명해보겠다.
g_signal_connect()를 이용해 시그널을 연결할 때는 개발자의 콜백 함수가 표준 콜백 함수보다 먼저 실행될 것이다. 따라서 일부 사례에서는 표준 콜백을 자신의 코드로 오버라이드하는 것을 허용한다. 이 방법 대신 콜백 함수가 표준 콜백 이후에 실행되길 원한다면 g_signal_connect_after()를 이용해 시그널을 연결할 수 있다.
gulong g_signal_connect_after (gpointer object,
const gchar *signal_name,
GCallback handler,
gpointer data);
시그널을 연결 시 또 다른 유용한 함수로 g_signal_connect_object()가 있지만 이는 콜백 함수가 실행되는 동안에도 네 번째 매개변수에 제공된 GObject가 유지되도록 확보하기 위해 참조 계수를 임시적으로 증가시킨다. 또 object 대신 gobject가 콜백 함수로 전송된다.
gulong g_signal_connect_object (gpointer object,
const gchar *signal_name,
GCallback handler,
gpointer gobject,
GConnectFlags flags);
g_signal_connect_object() 함수는 아래의 GConnectFlags 열거에서 플래그를 수락한다.
- G_CONNECT_AFTER: 이 플래그가 설정되면 콜백 함수는 기본 시그널 핸들러 다음에 호출된다. g_signal_connect_after()를 호출하는 것과 비슷하다.
- G_CONNECT_SWAPPED: 이 플래그가 설정되면 시그널 데이터가 첫 번째 매개변수로 콜백 함수에 전송된 후 GObject가 초기화된다. 이는 g_signal_connect_swapped()를 호출하는 것과 비슷하다.
G_CONNECT_SWAPPED가 처음에는 유용하게 보일지도 모르지만 사용자 데이터 매개변수에서만 함수를 호출할 때 사용되기도 한다. 가령, GTK_STOCK_QUIT 메뉴 항목을 연결할 때 이 플래그를 사용할 경우, 이를 gtk_widget_destroy()로 연결하여 메인 창을 사용자 데이터로서 전달할 수 있다. 이런 경우 메뉴 항목이 활성화되면 메인 창이 소멸되는 결과를 야기할 것이다.
시그널을 자동으로 연결하는 방법 외에 glade_xml_signal_connect()를 이용해 연결하는 방법도 있다. 이 때 개발자는 Glade로 전달한 시그널 핸들러를 명시해야 한다. 이 함수를 이용 시 장점은 Glade 시그널 핸들러와 실제 함수명이 같을 필요가 없다는 데에 있다. 이 함수는 편의를 위한 함수이며, 위젯을 검색하여 g_signal_connect()를 호출하는 것과 동일한 효과를 갖는다.
void glade_xml_signal_connect (GladeXML *xml,
const char *signal_name,
GCallback callback_func);
glade_xml_signal_connect()를 이용 시 문제는 콜백 함수로 데이터를 전달할 수 없다는 것이다. 이를 수정하기 위해 glade_xml_signal_connect_data()를 이용할 수 있는데, 이 함수는 콜백 함수로 전달할 데이터 매개변수를 명시하도록 해준다.
void glade_xml_signal_connect_data (GladeXML *xml,
const char *signal_name,
GCallback callback_func,
gpointer data);
각 함수마다 다른 데이터 매개변수를 전달해야 하는 경우를 제외하면 단순히 Libglade가 모든 시그널을 자동으로 연결하도록 허용하는 편이 훨씬 편리하다. 하지만 자동연결은 GModule을 지원하는 시스템에서만 작동하므로 한 번에 하나의 시그널을 연결하는 방법도 익혀둘 것을 잊지 말라!
자신의 이해도 시험하기
이번 장에 실린 연습문제는 특히 숙련된 GTK+ 개발자가 되는 데에 중요하다. 규모가 큰 애플리케이션의 모든 측면을 프로그램적으로 디자인하는 데에는 시간이 너무 많이 소요되므로 실용적이지 못하다.
대신 Glade를 이용해 사용자 인터페이스를 디자인하고, Libglade의 도움을 받아 그 디자인을 로딩하고 시그널을 연결해야 한다. 이를 통해 개발자는 자신의 애플리케이션에서 그래픽적인 측면을 재빠르게 완료하고 애플리케이션을 작동시키는 백엔드 코드(backend code)에 집중할 수 있을 것이다.
연습문제 10-1. Glade 텍스트 에디터
이번 연습문제에서는 연습문제 9-1의 텍스트 에디터를 Glade에서 구현하라. 텍스트 에디터 내의 툴바는 전적으로 Glade에서 구현되어야 한다.
앞 장의 연습문제 해답을 갖고 있다면 코딩 작업을 많이 요하지 않을 것이다. 연습문제 9-1의 해답은 본 서적의 웹사이트 www.gtkbook.com에서도 찾을 수 있다. 이번 연습문제는 Glade 3에 익숙해지고 여러 위젯 프로퍼티를 시험해볼 기회를 제공할 것이다.
툴바가 있는 애플리케이션을 디자인하고 나면 메뉴 바를 추가하는 작업으로 쉽게 전환이 가능하다. 규모가 큰 애플리케이션에서는 사용자에게 두 가지 옵션을 모두 제공해야 할 것이다. 아래 연습문제에서는 텍스트 에디터 애플리케이션에 메뉴 바를 추가해본다.
연습문제 10-2. 메뉴가 있는 Glade 텍스트 에디터
연습문제 9-2에서는 메뉴 바가 있는 텍스트 에디터를 구현해보았다. 이번에는 그 애플리케이션을 확장시키되 Glade와 Libglade를 이용한다. 첫째, Glade와 Libglade를 함께 활용할 수 있도록 GtkUIManager를 이용해 메뉴를 구현하라. 둘째, Glade에서 다시 메뉴를 구현하라.
앞의 연습문제와 마찬가지로 연습문제 9-2에 대한 해답은 http://www.gtkbook.com 에서 찾을 수 있다. 콜백의 코딩은 앞 장에서 이미 실습한 내용이므로 해답을 다운로드 시 해당 부분은 건너뛰어도 좋다.
요약
이번 장에서는 코딩에서 잠시 벗어나 그래픽 사용자 인터페이스를 디자인할 때 고려해야 할 문제들을 살펴보았다. 짧게 말하자면, 개발자는 항상 애플리케이션의 사용자를 잊지 말아야 한다. 사용자에게서 예상되는 바를 알고, 애플리케이션의 모든 측면에서 그들의 요구를 충족시켜야 한다.
다음으로 Glade 3을 이용해 그래픽 사용자 인터페이스를 디자인하는 방법을 학습하였다. GUI 툴킷의 사용을 고려할 때는 애플리케이션의 그래픽 측면을 빠르게 활용하는 기능이 꼭 필요하며, GTK+는 Glade를 통해 이 기능을 제공한다.
Glade는 위젯 프로퍼티, 레이아웃, 시그널 핸들러를 포함해 사용자 인터페이스의 모든 측면을 디자인하도록 해준다. 사용자 인터페이스는 읽을 수 있는 XML 파일로 저장되어 애플리케이션의 구조를 설명하는 데에 사용된다.
Glade 3에서 애플리케이션을 디자인하고 난 후에는 Libglade를 이용해 사용자 인터페이스를 동적으로 로딩할 수 있다. 이 라이브러리는 Glade 사용자 인터페이스를 파싱하고 런타임 시 필요한 모든 위젯을 생성하는 데 사용이 가능하다. 뿐만 아니라 Glade에서 선언된 시그널 핸들러를 애플리케이션에 포함된 콜백 함수로 연결하기 위한 함수들도 제공한다.
다음 장에서는 코딩으로 다시 돌아가 GObject 시스템의 복잡성을 면밀하게 살펴볼 것이다. 독자는 새로운 위젯과 클래스의 상속을 통해 자신만의 GObject 클래스를 생성하는 방법과 처음부터 위젯을 생성하는 방법을 학습하게 될 것이다.