FoundationsofGTKDevelopment:Chapter 09
- 제 9 장 메뉴와 툴바
메뉴와 툴바
이번 장에서는 팝업 메뉴, 메뉴 바, 툴바를 생성하는 방법을 가르칠 것이다. 먼저 이러한 위젯들을 각각 수동으로 생성하는 방법을 학습한 다음 위젯의 구성 방법을 배워볼 것이다. 그러면 메뉴와 툴바가 의존하는 개념을 모두 확실히 이해할 수 있을 것이다.
각 위젯을 이해하고 나면 커스텀 XML 파일을 통해 메뉴와 툴바를 동적으로 생성하도록 해주는 GtkUIManager를 소개할 것이다. 각 사용자 인터페이스 파일이 로딩되고, 해당하는 action 객체로 각 요소가 적용되어 그것이 어떻게 표시되고 행동할 것인지 항목으로 알려준다.
이번 장에서 학습하게 될 내용은 다음과 같다.
- 팝업 메뉴, 메뉴 바, 툴바를 생성하는 방법
- 키보드 가속기를 메뉴 항목으로 적용하는 방법
- GtkStatusBar 위젯이 무엇이며 이것을 이용해 메뉴 항목에 관한 추가 정보를 사용자에게 제공하는 방법
- GTK+가 제공하는 메뉴 및 툴바 항목의 타입
- UI 파일을 이용해 메뉴와 툴바를 동적으로 생성하는 방법
- GtkIconFactory를 이용해 커스텀 스톡 항목을 생성하는 방법
팝업 메뉴
이번 장은 팝업 메뉴를 생성하는 방법을 학습하면서 시작할 것이다. 팝업 메뉴는 사용자가 특정 위젯 위에서 마우스를 이리저리 움직이다가 오른쪽 마우스를 클릭하면 표시되는 GtkMenu 위젯이다. GtkEntry 와 GtkTextView와 같은 일부 위젯에는 이미 기본적으로 팝업 메뉴가 빌드되어 있다.
기본적으로 하나의 팝업 메뉴를 제공하는 위젯의 팝업 메뉴를 변경하고 싶다면 팝업 콜백 함수에서 제공되는 GtkMenu 위젯을 편집해야 한다. 예를 들면, GtkEntry 와 GtkTextView 모두 populate-popup 시그널을 가져야 하는데, 해당 시그널은 표시될 GtkMenu 를 수신한다. 이러한 메뉴는 사용자에게 표시하기 전에 개발자가 원하는 방식대로 편집이 가능하다.
팝업 메뉴 생성하기
대부분의 위젯에서 개발자는 자신만의 팝업 메뉴를 생성해야 할 것이다. 이번 절에서는 팝업 메뉴를 GtkProgressBar 위젯으로 제공하는 방법을 학습할 것이다. 우리가 구현할 팝업 메뉴를 그림 9-1에 실었다.
세 개의 팝업 메뉴 항목을 이용해 진행 막대를 펄싱(pulse)하고, 100% 완료로 설정한 다음 삭제하였다. 리스팅 9-1에 이벤트 박스가 진행 막대를 포함하고 있음을 눈치챌 것이다. GtkProgressBar는 GtkLabel과 마찬가지로 GDK 이벤트를 스스로 감지할 수 없으므로 이벤트 박스를 이용해 button-press-event 시그널을 포착해야 한다.
리스팅 9-1. 간단한 팝업 메뉴 (popupmenus.c)
#include <gtk/gtk.h>
static void create_popup_menu (GtkWidget*, GtkWidget*);
static void pulse_activated (GtkMenuItem*, GtkProgressBar*);
static void clear_activated (GtkMenuItem*, GtkProgressBar*);
static void fill_activated (GtkMenuItem*, GtkProgressBar*);
static gboolean button_press_event (GtkWidget*, GdkEventButton*, GtkWidget*);
int main (int argc,
char *argv[])
{
GtkWidget *window, *progress, *eventbox, *menu;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Pop-up Menus");
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
gtk_widget_set_size_request (window, 250, -1);
/* Create all of the necessary widgets and initialize the pop-up menu. */
menu = gtk_menu_new ();
eventbox = gtk_event_box_new ();
progress = gtk_progress_bar_new ();
gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress), "Nothing Yet Happened");
create_popup_menu (menu, progress);
gtk_progress_bar_set_pulse_step (GTK_PROGRESS_BAR (progress), 0.05);
gtk_event_box_set_above_child (GTK_EVENT_BOX (eventbox), FALSE);
g_signal_connect (G_OBJECT (eventbox), "button_press_event",
G_CALLBACK (button_press_event), menu);
gtk_container_add (GTK_CONTAINER (eventbox), progress);
gtk_container_add (GTK_CONTAINER (window), eventbox);
gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK);
gtk_widget_realize (eventbox);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
/* Create the pop-up menu and attach it to the progress bar. This will make sure
* that the accelerators will work from application load. */
static void
create_popup_menu (GtkWidget *menu,
GtkWidget *progress)
{
GtkWidget *pulse, *fill, *clear, *separator;
pulse = gtk_menu_item_new_with_label ("Pulse Progress");
fill = gtk_menu_item_new_with_label ("Set as Complete");
clear = gtk_menu_item_new_with_label ("Clear Progress");
separator = gtk_separator_menu_item_new ();
g_signal_connect (G_OBJECT (pulse), "activate",
G_CALLBACK (pulse_activated), progress);
g_signal_connect (G_OBJECT (fill), "activate",
G_CALLBACK (fill_activated), progress);
g_signal_connect (G_OBJECT (clear), "activate",
G_CALLBACK (clear_activated), progress);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), pulse);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), separator);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), fill);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), clear);
gtk_menu_attach_to_widget (GTK_MENU (menu), progress, NULL);
gtk_widget_show_all (menu);
}
대부분의 경우 개발자는 button-press-event를 이용해 사용자가 언제 팝업 메뉴를 표시하고자 하는지를 감지하고자 할 것이다. 이는 오른쪽 마우스 버튼이 클릭되었는지 여부를 확인하도록 해준다. 오른쪽 마우스 버튼이 클릭되면 GdkEventButton의 button member는 3이 될 것이다.
하지만 GtkWidget은 popup-menu 시그널을 제공하기도 하는데, 이는 사용자가 팝업 메뉴를 활성화하기 위해 내장된 키 가속기를 누르면 활성화된다. 대부분의 사용자는 팝업 메뉴를 활성화하는 데에 마우스를 이용할 것이기 때문에 GTK+ 애플리케이션에선 해당하지 않겠다. 그럼에도 불구하고 이러한 시그널도 처리하고 싶다면 두 개의 콜백 함수에 의해 호출되는 팝업 메뉴를 표시하는 세 번째 함수를 생성해야 한다.
새로운 메뉴는 gtk_menu_new()를 이용해 생성된다. 메뉴는 초기 내용이 없이 초기화되므로 메뉴 항목을 생성하는 것이 다음 단계가 된다.
이번 절에서 다룰 내용은 두 가지 타입의 메뉴 항목이다. 첫 번째는 메뉴 항목의 다른 모든 타입에 대한 기반 클래스인 GtkMenuItem이다. GtkMenuItem: gtk_menu_item_new(), gtk_menu_item_new_with_label(), gtk_menu_item_new_with_mnemonic()을 대상으로 세 가지 초기화 함수가 제공된다.
GtkWidget* gtk_menu_item_new_with_label (const gchar *label);
대부분의 경우 내용이 없는 메뉴 항목은 그다지 쓸모가 없기 때문에 gtk_menu_item_new()를 사용할 필요가 없을 것이다. 이 함수를 이용해 메뉴 항목을 초기화할 경우 세부 내용을 GTK+가 처리하도록 허용하는 대신 개발자가 코드에서 메뉴의 모든 측면을 구성해야 할 것이다.
메뉴 항목 니모닉은 키보드 가속기와는 다르다. 니모닉은 메뉴에 포커스가 있을 때 사용자가 Alt와 적절한 니모닉 키를 누를 때 메뉴 항목이 활성화된다. 키보드 가속기는 커스텀 키의 조합으로, 키 조합을 누르면 콜백 함수가 실행된다. 메뉴에 대한 키보드 가속기는 다음 절에서 학습할 것이다.
나머지 타입의 메뉴 항목은 GtkSeparatorMenuItem로, 그 위치에 일반적인 구분자를 위치시킨다. 새로운 구분자 메뉴 항목을 생성하려면 gtk_separator_menu_item_new()를 이용할 수 있다.
구분자는 메뉴 항목을 그룹으로 조직하여 사용자가 적절한 항목을 쉽게 찾을 수 있도록 해주기 때문에 메뉴 구조를 디자인할 때 매우 중요하다. 가령 File 메뉴에서 메뉴 항목은 종종 파일 열기, 파일 저장하기, 파일 인쇄하기, 애플리케이션 닫기 등의 그룹으로 조직된다. 그룹 사이에 구분자가 없이 메뉴 항목이 열거되는 경우는 드룹다 (예: 최근 열린 파일의 리스트는 구분자 없이 표시될 수도 있다). 대부분은 개발자가 비슷한 메뉴 항목을 그룹화하고 그룹들 사이에 구분자를 위치시킨다.
메뉴 항목이 생성되고 나면 activate 시그널로 각 메뉴 항목을 연결해야 하는데, 해당 시그널은 사용자가 항목을 선택하면 발생한다. 이 방법이 아니라면, 주어진 메뉴 항목의 하위메뉴가 표시되면 추가적으로 발생하는 activate-item 시그널을 이용하는 수도 있다. 메뉴 항목이 하위메뉴로 확장되지 않는 한 두 가지 시그널에 별 차이는 없다.
activate와 activate-item 콜백 함수는 각각 GtkMenuItem 위젯을 수신하는데, 이러한 위젯은 당신이 함수로 전달해야 하는 데이터와 액션을 시작한다. 리스팅 9-2에서는 세 개의 메뉴 항목 콜백 함수가 제공된다. 이들은 진행 막대를 펄싱하고 100% 완료로 채운 후 모든 경과를 삭제한다.
이제 모든 메뉴 항목을 생성했으니 메뉴로 추가해야 한다. GtkMenu는 GtkMenuShell에서 파생되는데, 이는 하위메뉴와 메뉴 항목을 포함하고 표시하는 추상적 기반 클래스다. 메뉴 항목은 gtk_menu_shell_append()를 이용해 메뉴 셸로 추가 가능하다. 해당 함수는 각 항목을 메뉴 셸의 끝에 추가한다.
void gtk_menu_shell_append (GtkMenuShell *menu_shell,
GtkWidget *child);
뿐만 아니라 gtk_menu_shell_prepend() 또는 gtk_menu_shell_insert()를 이용해 메뉴 항목을 메뉴의 시작 부분으로 추가하거나 임의의 위치로 삽입할 수 있다. gtk_menu_shell_insert()가 수락하는 위치는 0의 색인으로 시작된다.
GtkMenu의 자식들을 모두 표시되도록(visible) 설정하고 나면 gtk_menu_attach_to_widget()을 호출하여 팝업 메뉴가 특정 위젯과 연관시킬 수 있다. 이 함수는 팝업 메뉴와 그것이 추가될 위젯을 수락한다.
void gtk_menu_attach_to_widget (GtkMenu *menu,
GtkWidget *attach_widget,
GtkMenuDetachFunc detacher);
gtk_menu_attach_widget()의 마지막 매개변수는 위젯으로부터 메뉴가 분리되면 특정 함수를 호출하는 데에 사용할 수 있는 GtkMenuDetachFunc를 수락한다.
팝업 메뉴 콜백 함수
필요한 위젯을 생성하고 나면 리스팅 9-2에 표시된 button-press-event 시그널을 처리해야 한다. 이 예제에서는 진행 막대에서 오른쪽 마우스를 클릭할 때마다 팝업 메뉴가 표시된다.
리스팅 9-2. 간단한 팝업 메뉴에 대한 콜백 함수 (popupmenus.c)
static gboolean
button_press_event (GtkWidget *eventbox,
GdkEventButton *event,
GtkWidget *menu)
{
if ((event->button == 3) && (event->type == GDK_BUTTON_PRESS))
{
gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
event->button, event->time);
return TRUE;
}
return FALSE;
}
static void
pulse_activated (GtkMenuItem *item,
GtkProgressBar *progress)
{
gtk_progress_bar_pulse (progress);
gtk_progress_bar_set_text (progress, "Pulse!");
}
static void
fill_activated (GtkMenuItem *item,
GtkProgressBar *progress)
{
gtk_progress_bar_set_fraction (progress, 1.0);
gtk_progress_bar_set_text (progress, "One Hundred Percent");
}
static void
clear_activated (GtkMenuItem *item,
GtkProgressBar *progress)
{
gtk_progress_bar_set_fraction (progress, 0.0);
gtk_progress_bar_set_text (progress, "Reset to Zero");
}
리스팅 9-2의 button-press-event 콜백 함수에서 gtk_menu_popup()을 이용하면 메뉴를 화면으로 표시할 수 있다.
void gtk_menu_popup (GtkMenu *menu,
GtkWidget *parent_menu_shell,
GtkWidget *parent_menu_item,
GtkMenuPositionFunc func,
gpointer func_data,
guint button,
guint32 event_time);
리스팅 9-2에서는 이벤트가 발생한 시간과 (event->time) 이벤트를 야기한 마우스 버튼을 (event->button) 제외한 모든 매개변수가 NULL로 설정되었다. 버튼 이외의 것이 팝업 메뉴를 활성화하였다면 버튼 매개변수에 0을 제공해야 한다.
액션이 popup-menu 시그널에 의해 호출된다면 이벤트 시간을 이용할 수 없을 것이다. 이런 경우 gtk_get_current_event_time()을 이용할 수 있다. 이 함수는 현재 이벤트의 타임스탬프를 리턴하고, 현재 이벤트가 없을 경우 GDK_CURRENT_TIME을 리턴한다.
parent_menu_shell, parent_menu_item, func, func_data는 보통 NULL로 설정되는데, 이들은 메뉴가 메뉴 바 구조의 일부일 때 사용되기 때문이다. parent_menu_shell 위젯은 팝업 초기화를 야기한 항목을 포함하는 메뉴 셸(menu shell)이다. 아니면 팝업 초기화를 야기한 메뉴 항목에 해당하는 parent_menu_item을 제공하는 방법도 있다.
GtkMenuPositionFunc는 화면에서 메뉴를 그려야 하는 위치를 결정하는 함수다. 이는 func_data를 마지막 선택적 매개변수로 수락한다. 앞서 언급했듯이 이러한 함수들은 애플리케이션에서 사용되는 경우가 드물기 때문에 안전하게 NULL로 설정이 가능하다. 우리 예제에서는 팝업 메뉴가 이미 진행 막대와 연관되어 있기 때문에 올바른 위치에 그려질 것이다.
키보드 가속기
메뉴를 생성할 때 해야 할 가장 중요한 일들 중 하나는 바로 키보드 가속기를 설정하는 일이다. 키보드 가속기는 키의 조합으로, Ctrl이나 Shift와 같은 수식키(modifier) 하나 또는 그 이상을 하나의 가속기 키와 조합하여 만들어진다.
리스팅 9-3은 진행 막대 팝업 메뉴 애플리케이션을 확장하여 키보드 가속기를 메뉴 항목으로 추가한다. 진행 막대는 사용자가 Ctrl+P를 누르면 펄싱되고, Ctrl+F를 누르면 채워지며, Ctrl+C를 누르면 삭제된다.
리스팅 9-3. 가속기를 메뉴 항목으로 추가하기 (accelerators.c)
static void
create_popup_menu (GtkWidget *menu,
GtkWidget *window,
GtkWidget *progress)
{
GtkWidget *pulse, *fill, *clear, *separator;
GtkAccelGroup *group;
/* Create a keyboard accelerator group for the application. */
group = gtk_accel_group_new ();
gtk_window_add_accel_group (GTK_WINDOW (window), group);
gtk_menu_set_accel_group (GTK_MENU (menu), group);
pulse = gtk_menu_item_new_with_label ("Pulse Progress");
fill = gtk_menu_item_new_with_label ("Set as Complete");
clear = gtk_menu_item_new_with_label ("Clear Progress");
separator = gtk_separator_menu_item_new ();
/* Add the necessary keyboard accelerators. */
gtk_widget_add_accelerator (pulse, "activate", group, GDK_P,
GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
gtk_widget_add_accelerator (fill, "activate", group, GDK_F,
GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
gtk_widget_add_accelerator (clear, "activate", group, GDK_C,
GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
g_signal_connect (G_OBJECT (pulse), "activate",
G_CALLBACK (pulse_activated), progress);
g_signal_connect (G_OBJECT (fill), "activate",
G_CALLBACK (fill_activated), progress);
g_signal_connect (G_OBJECT (clear), "activate",
G_CALLBACK (clear_activated), progress);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), pulse);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), separator);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), fill);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), clear);
gtk_menu_attach_to_widget (GTK_MENU (menu), progress, NULL);
gtk_widget_show_all (menu);
}
키보드 가속기는 GtkAccelGroup의 인스턴스로 저장된다. 자신의 애플리케이션에서 가속기를 구현하기 위해서는 gtk_accel_group_new()를 이용해 새로운 가속기 그룹을 생성해야 한다. 해당 가속기 그룹은 메뉴가 효과를 나타내게 될 GtkWindow 로 추가되어야 한다. 그리고 가속기를 이용하는 어떤 메뉴로든 연관되어야 한다. 리스팅 9-3에서 이러한 과정은 gtk_window_add_accel_group()과 gtk_menu_set_accel_group()을 이용해 GtkAccelGroup을 생성한 직후 실행되었다.
GtkAccelMap을 이용해 수동으로 키보드 가속기를 생성하는 방법도 가능하지만 대부분의 사례에서는 gtk_widget_add_accelerator()만으로 필요한 기능을 모두 제공할 것이다. 이 방법을 이용 시 유일한 문제점은 사용자가 런타임 시 이 함수가 생성한 키보드 가속기를 변경할 수 없다는 데에 있다.
void gtk_widget_add_accelerator (GtkWidget *widget,
const gchar *signal_name,
GtkAccelGroup *group,
guint accel_key,
GdkModifierType mods,
GtkAccelFlags flags);
가속기를 위젯으로 추가하기 위해서는 gtk_widget_add_accelerator()를 이용하면 되는데, 이는 사용자가 키 조합을 누르면 위젯에서 signal_name이 명시한 시그널을 발생시킬 것이다. 개발자는 자신의 가속기 그룹을 함수로 명시해야 하는데, 이는 앞서 언급했듯이 창과 메뉴에 연관되어야 한다.
가속기 키와 하나 또는 그 이상의 수식키를 결합하면 완전한 키 조합이 형성된다. 이용 가능한 가속기 키 목록은 <gdk/gdkkeysyms.h>에서 이용 가능하다. 이 헤더 파일은 <gtk/gtk.h>에 포함되어 있지 않으므로 명시적으로 포함되어야 한다. 수식키는 GdkModifierType 열거에서 명시한다. 가장 자주 사용되는 수식키는 GDK_SHIFT_LOCK, GDK_CONTROL_MASK, GDK_MOD1_MASK로, 각각 Shift, Ctrl, Alt 키에 해당한다.
일부 사례에서는 키 코드를 다룰 때 동일한 액션에 대해 여러 키를 제공해야 하므로 주의를 기울여야 한다. 가령 숫자 1 키를 포착하고 싶다면 GDK_1과 GDK_KP_1을 주의해야 하는데, 키보드 상단에 있는 1 키와 숫자 키패드의 1에 해당하기 때문이다.
gtk_widget_add_accelerator()의 마지막 매개변수는 가속기 플래그다. GtkAccelFlags 열거에는 세 개의 플래그가 정의되어 있다. GDK_ACCEL_VISIBLE이 설정되면 가속기가 라벨에 표시될 것이다. GTK_ACCEL_LOCKED는 사용자가 가속기를 수정하지 못하도록 제한할 것이다. GTK_ACCEL_MASK는 위젯 가속기에 대한 두 개의 플래그를 모두 설정할 것이다.
상태 바 힌트
메인 창 하단에 주로 표시되는 GtkStatusbar 위젯은 애플리케이션에서 어떤 일이 일어나고 있는지에 대한 추가 정보를 제공하는 데에 사용된다. 상태 바는 메뉴와 같이 이용할 때도 매우 유용한데, 마우스를 메뉴 항목에서 왔다갔다하면 그 기능에 대한 정보를 사용자에게 추가로 제공하기 때문이다. 상태 바의 스크린샷은 그림 9-2에서 확인할 수 있다.
상태 바 위젯
상태 바는 한 번에 하나의 메시지만 표시할 수 있지만 위젯은 사실상 메시지 스택을 보관한다. 현재 표시되는 메시지는 스택에서 가장 상단에 위치한다. 스택에서 메시지 하나를 꺼내면 바로 이전의 메시지가 표시된다. 상단에서 하나를 꺼낸 후 스택에 더 이상 문자열이 없다면 상대 바에는 어떤 메시지도 표시되지 않는다.
새로운 상태 바 위젯은 gtk_statusbar_new()를 이용해 생성된다. 이는 메시지 스택이 비어 있는 새로운 GtkStatusbar 위젯을 생성할 것이다. 새로운 상태 바의 스택에 메시지를 추가하거나 그로부터 제거하기 위해서는 그 전에 먼저 gtk_status_bar_get_context_id()를 이용해 컨텍스트 식별자를 검색해야 한다.
guint gtk_statusbar_get_context_id (GtkStatusBar *statusbar,
const gchar *description);
컨텍스트 식별자는 컨텍스트 설명 문자열과 연관된 부호가 없는 유일한 정수다. 이 식별자는 특정 타입으로 된 메시지에 모두 사용 가능하며, 개발자가 스택에 메세지를 분류할 수 있도록 해준다.
가령 상태 바가 하이퍼링크와 IP 주소를 보유할 것이라면 개발자는 "URL"과 "IP"라는 문자열로부터 두 개의 컨텍스트를 생성할 수 있겠다. 스택으로 메시지를 넣거나 스택으로부터 메시지를 꺼낼 때는 컨텍스트 식별자를 명시해야 한다. 그래야만 애플리케이션에서 서로 구분된 부분들이 서로 영향을 미치지 않고 상태 바 메시지로 메시지를 넣고 꺼낼 수 있다.
메시지 범주별로 다른 컨텍스트 식별자를 이용하는 것이 중요하다. 애플리케이션 일부분이 사용자에게 메시지를 전송하려는 동안 다른 부분에서는 그 고유의 메시지를 삭제하려 한다면 스택에서 올바르지 않은 메시지를 꺼내고 싶진 않을 것이다!
컨텍스트 식별자를 생성한 후에는 gtk_statusbar_push()를 이용해 상태 바의 상단으로 메시지를 추가할 수 있다. 이 함수는 방금 추가된 문자열에 대한 유일한 메시지 식별자를 리턴한다. 이 식별자는 후에 메시지의 위치와 상관없이 스택에서 메시지를 제거 시 사용할 수 있다.
guint gtk_statusbar_push (GtkStatusBar *statusbar,
guint context_id,
const gchar *message);
스택에서 메시지를 제거하는 방법에는 두 가지가 있다. 특정 컨텍스트 ID에 해당하는 메시지를 스택의 상단에서 제거하고자 한다면 gtk_statusbar_pop()을 이용한다. 이 함수는 context_id의 컨텍스트 식별자를 이용해 상태 바의 스택에서 최상위(highest) 메시지를 제거할 것이다.
void gtk_statusbar_pop (GtkStatusBar *statusbar,
guint context_id);
gtk_statusbar_remove()를 이용하면 상태 바의 메시지 스택에서 특정 메시지를 제거하는 것이 가능하다. 이를 사용하기 위해서는 메시지의 컨텍스트 식별자와 제거하고자 하는 메시지의 메시지 식별자를 제공해야 하는데, 후자는 메시지가 추가되었을 때 gtk_statusbar_push()가 리턴한 값이다.
void gtk_statusbar_remove (GtkStatusBar *statusbar,
guint context_id,
guint message_id);
GtkStatusbar에는 has-resize-grip이라는 하나의 프로퍼티만 있는데, 이는 창의 크기조정을 위해 상태 바의 모서리에 그래픽을 위치시킬 것이다. 사용자는 크기조정 그립(resize grip)을 잡고 드래그하여 부모 창의 크기를 조정할 수 있다. gtk_statusbar_set_has_resize_grip()이라는 내장된 함수를 이용하면 이 프로퍼티를 설정할 수 있다.
메뉴 항목 정보
상태 바의 한 가지 유용한 역할로, 마우스 커서를 메뉴 항목 위에서 왔다갔다하면 메뉴 항목에 대한 추가 정보를 사용자에게 제공한다는 점을 들 수 있다. 이 예는 앞 절에 실린 그림 9-2에 표시하였는데, 그림 9-4의 진행 막대 팝업 애플리케이션의 스크린샷에 해당한다.
상태 바 힌트를 구현하려면 GtkWidget의 enter-notify-event 와 leave-notify-event 시그널에 각 메뉴 항목을 연결해야 한다. 리스팅 9-4는 앞에서 학습한 진행 막대 팝업 메뉴 애플리케이션인데, 마우스 커서를 메뉴 항목 위에서 이리저리 움직이면 상태 바 힌트가 제공되도록 만들었다.
리스팅 9-4. 메뉴 항목에 대한 추가 정보 표시하기 (statusbarhints.c)
static void
create_popup_menu (GtkWidget *menu,
GtkWidget *progress,
GtkWidget *statusbar)
{
GtkWidget *pulse, *fill, *clear, *separator;
pulse = gtk_menu_item_new_with_label ("Pulse Progress");
fill = gtk_menu_item_new_with_label ("Set as Complete");
clear = gtk_menu_item_new_with_label ("Clear Progress");
separator = gtk_separator_menu_item_new ();
g_signal_connect (G_OBJECT (pulse), "activate",
G_CALLBACK (pulse_activated), progress);
g_signal_connect (G_OBJECT (fill), "activate",
G_CALLBACK (fill_activated), progress);
g_signal_connect (G_OBJECT (clear), "activate",
G_CALLBACK (clear_activated), progress);
/* Connect signals to each menu item for status bar messages. */
g_signal_connect (G_OBJECT (pulse), "enter-notify-event",
G_CALLBACK (statusbar_hint), statusbar);
g_signal_connect (G_OBJECT (pulse), "leave-notify-event",
G_CALLBACK (statusbar_hint), statusbar);
g_signal_connect (G_OBJECT (fill), "enter-notify-event",
G_CALLBACK (statusbar_hint), statusbar);
g_signal_connect (G_OBJECT (fill), "leave-notify-event",
G_CALLBACK (statusbar_hint), statusbar);
g_signal_connect (G_OBJECT (clear), "enter-notify-event",
G_CALLBACK (statusbar_hint), statusbar);
g_signal_connect (G_OBJECT (clear), "leave-notify-event",
G_CALLBACK (statusbar_hint), statusbar);
g_object_set_data (G_OBJECT (pulse), "menuhint",
(gpointer) "Pulse the progress bar one step.");
g_object_set_data (G_OBJECT (fill), "menuhint",
(gpointer) "Set the progress bar to 100%.");
g_object_set_data (G_OBJECT (clear), "menuhint",
(gpointer) "Clear the progress bar to 0%.");
gtk_menu_shell_append (GTK_MENU_SHELL (menu), pulse);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), separator);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), fill);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), clear);
gtk_menu_attach_to_widget (GTK_MENU (menu), progress, NULL);
gtk_widget_show_all (menu);
}
/* Add or remove a status bar menu hint, depending on whether this function
* is initialized by a proximity-in-event or proximity-out-event. */
static gboolean
statusbar_hint (GtkMenuItem *menuitem,
GdkEventProximity *event,
GtkStatusbar *statusbar)
{
gchar *hint;
guint id = gtk_statusbar_get_context_id (statusbar, "MenuItemHints");
if (event->type == GDK_ENTER_NOTIFY)
{
hint = (gchar*) g_object_get_data (G_OBJECT (menuitem), "menuhint");
gtk_statusbar_push (statusbar, id, hint);
}
else if (event->type == GDK_LEAVE_NOTIFY)
gtk_statusbar_pop (statusbar, id);
return FALSE;
}
상태 바 힌트를 구현할 때는 먼저 어떤 시그널이 필요한지 알아내야 한다. 마우스 커서를 메뉴 항목에 가져다 대면 상태 바에 메시지를 추가하고, 마우스를 치우면 메시지를 삭제하길 원한다고 치자. 이 설명을 감안하면 enter-notify-event와 leave-notify-event가 좋은 선택이다.
두 가지 시그널을 이용 시 한 가지 장점은 하나의 콜백 함수만 필요하다는 점인데, 각 시그널에 대한 프로토타입이 GdkEventProximity 객체를 수신하기 때문이다. 이 객체에서 우리는 GDK_ENTER_NOTIFY와 GDK_LEAVE_NOTIFY 이벤트를 구별할 수 있다. 우리는 이벤트가 방출되면 실행될 작업을 향상시키고 싶을 뿐 GTK+가 이벤트를 처리하는 일은 원치 않기 때문에 콜백 함수에서 FALSE를 리턴하길 원할 것이다.
statusbar_hint() 콜백 함수에서 먼저 메뉴 항목 메시지에 대한 컨텍스트 식별자를 검색해야 한다. 애플리케이션이 문자열을 기억하는 한 개발자가 원하는 문자열을 이용할 수 있다. 리스팅 9-4에서는 상태 바로 추가된 모든 메뉴 항목 메시지를 설명하도록 "MenuItemHints"라는 문자열이 사용되었다. 애플리케이션의 다른 부분에서 상태 바를 사용한 경우, 다른 컨텍스트 식별자를 이용하면 메뉴 항목 힌트는 그대로 유지될 것이다.
guint id = gtk_statusbar_get_context_id (statusbar, "MenuItemHints");
이벤트 타입이 GDK_ENTER_NOTIFY라면 메시지를 사용자에게 표시할 필요가 있다. create_popup_menu() 함수에서 데이터 매개변수가 "menuhint"라고 불리는 항목 메뉴로 추가되었다. 이는 메뉴 항목이 실행하는 일을 좀 더 자세히 설명한 것으로, 사용자에게 표시될 것이다.
hint = (gchar*) g_object_get_data (G_OBJECT (menuitem), "menuhint");
gtk_statusbar_push (statusbar, id, hint);
이후 gtk_statusbar_push()를 이용하면 메시지를 상태 바의 "MenuItemHints" 컨텍스트 식별자로 추가할 수 있다. 해당 메시지는 스택의 상단에 위치되여 사용자에게 표시된다. 사용자 인터페이스는 변경내용을 즉시 반영해야 하므로 해당 함수를 호출한 다음에는 모든 GTK+ 이벤트의 처리를 고려해야 한다.
그렇지만 이벤트 타입이 GDK_LEAVE_NOTIFY라면 동일한 컨텍스트 식별자로 마지막에 추가된 메뉴 항목 메시지를 제거해야 한다. 가장 최근의 메시지는 gtk_statusbar_pop()을 이용해 스택에서 제거할 수 있다.
메뉴 항목
지금까지는 라벨과 구분자 메뉴 항목을 표시하는 평평한(flat) 메뉴에 대해 배워보았다. 기존의 메뉴 항목으로 하위메뉴를 추가하는 것도 가능하다. GTK+는 다른 GtkMenuItem 객체도 많이 제공한다. 그림 9-3은 이미지, 체크 메뉴, 라디오 메뉴 항목과 함께 하위메뉴를 포함하는 팝업 메뉴를 보여준다.
하위메뉴
GTK+에서 하위 메뉴는 구분된 메뉴 항목 위젯의 타입에 의해 생성되는 것이 아니라 gtk_menu_item_set_submenu()를 호출함으로써 생성된다. 이 함수는 gtk_menu_attach_to_widget()을 호출하여 메뉴 항목에 하위메뉴를 추가하고 메뉴 항목 옆에 화살표를 배치하여 이제 하위메뉴가 있음을 나타낸다. 메뉴 항목에 이미 하위메뉴가 있다면 주어진 GtkMenu 위젯으로 대체될 것이다.
void gtk_menu_item_set_submenu (GtkMenuItem *menuitem,
GtkWidget *submenu);
하위메뉴는 체계적인 메뉴 구조를 어지럽힐 수 있는 매우 구체적인 옵션으로 구성된 리스트에 이용 시 매우 유용하다. 하위메뉴를 이용할 때는 GtkMenuItem 위젯이 제공하는 activate-item 시그널을 이용할 수 있는데, 이 시그널은 메뉴 항목이 하위메뉴를 표시하면 발생할 것이다.
GtkMenuItem과 메뉴 항목 구분자 외에도 세 가지 타입의 메뉴 항목 객체, 즉 이미지, 체크 메뉴, 라디오 메뉴 항목이 있는데 이를 나머지 절에서 다루도록 하겠다.
이미지 메뉴 항목
GtkImageMenuItem은 메뉴 항목 라벨의 왼쪽에 작은 이미지가 표시된다는 점만 제외하면 그 부모 클래스인 GtkMenuItem과 매우 비슷하다. 새로운 이미지 메뉴 항목을 생성하는 데에는 네 가지 함수가 제공된다.
첫 번째 함수인 gtk_image_menu_item_new()는 빈 라벨과 연관된 이미지 없이 새로운 GtkImageMenuItem 객체를 생성한다. 메뉴 항목의 image 프로퍼티를 이용하면 메뉴 항목이 표시하는 이미지를 설정할 수 있다.
GtkWidget* gtk_image_menu_item_new ();
두 번째로 gtk_image_menu_item_new_from_stock()을 이용하면 스톡 식별자로부터 새로운 이미지 메뉴 항목을 생성할 수 있다. 이 함수는 stock_id와 연관된 이미지와 라벨을 가진 새로운 GtkImageMenuItem 객체를 생성한다. 이 함수는 부록 D에 열거된 스톡 식별자 문자열을 수락한다.
GtkWidget* gtk_image_menu_item_new_from_stock (const gchar *stock_id,
GtkAccelGroup *accel_group);
위 함수의 두 번째 매개변수는 가속기 그룹을 수락하는데, 이는 스톡 항목의 기본 가속기로 설정될 것이다. 리스팅 9-3에서처럼 메뉴 항목에 대한 키보드 가속기를 수동으로 설정하고 싶다면 이 매개변수에 대해 NULL로 명시해야 한다.
세 번째로는 gtk_image_menu_item_new_with_label()을 이용하면 처음에 라벨만 존재하는 새로운 GtkImageMenuItem 객체를 생성할 수 있다. 이후 image 프로퍼티를 이용해 이미지 위젯을 추가하면 된다. GTK+는 위젯의 image 프로퍼티를 편집하도록 해주는 gtk_image_menu_item_set_image()라는 함수도 제공한다.
GtkWidget* gtk_image_menu_item_new_with_label (const gchar *label);
GTK+는 네 번째로 gtk_image_menu_item_new_with_mnemonic()이란 함수를 제공하는데, 니모닉 라벨과 함께 이미지 메뉴를 생성할 것이다. 앞의 함수와 마찬가지로 메뉴 항목이 생성되고 나면 image 프로퍼티를 별도로 설정해야 할 것이다.
체크 메뉴 항목
GtkCheckMenuItem은 Boolean active 프로퍼티가 TURE인지 FALSE인지에 따라 라벨 외에 체크 부호를 표시할 메뉴 항목을 생성하도록 해준다. 이는 사용자가 옵션의 활성화 여부를 확인할 수 있도록 허용한다.
GtkMenuItem과 마찬가지로 세 가지 초기화 함수, gtk_check_menu_item_new(), gtk_check_item_new_with_label(), gtk_check_menu_item_new_with_mnemonic()가 제공된다. 이러한 함수들은 각각 라벨 없이 GtkCheckMenuItem 객체만 생성, 초기 라벨과 함께 객체를 생성, 니모닉 라벨과 함께 객체를 생성한다.
GtkWidget* gtk_check_menu_item_new ();
GtkWidget* gtk_check_menu_item_new_with_label (const gchar *label);
GtkWidget* gtk_chcek_menu_item_new_with_mnemonic (const gchar *label);
앞서 언급했듯이 체크 메뉴 항목의 현재 상태는 위젯의 active 프로퍼티가 보유한다. GTK+는 두 가지 함수, gtk_menu_item_set_active()와 gtk_check_menu_item_get_active()를 제공하여 active 값을 검색한다.
모든 체크 버튼 위젯과 마찬가지로 toggled 시그널을 이용할 수 있는데, 이 시그널은 사용자가 메뉴 항목의 상태를 토글할 때 발생한다. 체크 버튼 상태는 GTK+가 업데이트를 하기 때문에 시그널은 변경된 값을 반영하도록 애플리케이션의 업데이트를 허용하는 데에 그친다.
GtkCheckMenuItem은 메뉴 항목의 inconsistent 프로퍼티를 수정하는 데 사용되는 gtk_check_menu_item_set_inconsistent()를 제공하기도 한다. 프로퍼티를 TRUE로 설정하면 체크 메뉴 항목은 활성화도 아니고 비활성화도 아닌 세 번째 상태, "in between(중간)" 상태를 표시할 것이다. 이를 이용하면 사용자에게 아직 설정되지 않은 선택을 내려야 한다거나 선택의 다른 부분들에 대한 프로퍼티가 설정/설정해제되었음을 표시할 수 있다.
라디오 메뉴 항목
GtkRadioMenuItem은 GtkCheckMenuItem에서 파생된 위젯이다. 체크 메뉴 항목의 draw-as-radio 프로퍼티를 TRUE로 설정하면 이 위젯은 체크 버튼 대신 라디오 버튼으로 렌더링된다. 라디오 메뉴 항목은 일반 라디오 버튼과 동일한 방식으로 작동한다.
첫 번째 라디오 버튼은 아래 함수들 중 하나를 이용해 생성되어야 한다. 라디오 버튼 그룹을 NULL로 설정 가능한데, 첫 번째 요소를 참조함으로써 필요한 요소들을 그룹으로 추가할 필요가 없기 때문이다. 이러한 함수들은 각각 빈 메뉴 항목, 라벨이 있는 메뉴 항목, 니모닉이 있는 메뉴 항목을 생성한다.
GtkWidget* gtk_radio_menu_item_new (GSList *group);
GtkWidget* gtk_radio_menu_item_new_with_label (GSList *group,
const gchar *text);
GtkWidget* gtk_radio_menu_item_new_with_mnemonic (GSList *group,
const gchar *text);
그 외 다른 라디오 메뉴 항목들은 모두 다음 세 가지 함수들 중 하나를 이용해 생성되어야 하는데, 먼저 group 과 연관된 라디오 버튼 그룹으로 항목을 추가할 것이다. 이 함수들은 각각 빈 메뉴 항목, 라벨이 있는 메뉴 항목, 니모닉이 있는 메뉴 항목을 생성한다.
GtkWidget* gtk_radio_menu_item_new_from_widget (GtkRadioMenuItem *group);
GtkWidget* gtk_radio_menu_item_new_from_widget_with_label (GtkRadioMenuItem *group,
const gchar *text);
GtkWidget* gtk_radio_menu_item_new_from_widget_with_mnemonic
(GtkRadioMenuItem *group,
const gchar *text);
메뉴 바
GtkMenuBar는 다수의 팝업 메뉴를 수평 또는 수직 행으로 구성하는 위젯이다. 각 루트 요소는 하위메뉴로 나뉘는 GtkMenuItem이다. GtkMenuBar의 인스턴스는 주로 메인 애플리케이션 창의 상단을 따라 표시되고 애플리케이션이 제공하는 기능으로 접근하도록 해준다. 메뉴 바의 예제는 그림 9-4에 소개하겠다.
리스팅 9-5에서는 File, Edit, Help 메뉴가 있는 GtkMenuBar 위젯을 생성한다. 각 메뉴는 사실상 하위메뉴가 있는 GtkMenuItem이다. 이후 다수의 메뉴 항목이 각 하위메뉴로 추가된다.
리스팅 9-5. 메뉴 그룹 생성하기 (menubars.c)
#include <gtk/gtk.h>
int main (int argc,
char *argv[])
{
GtkWidget *window, *menubar, *file, *edit, *help, *filemenu, *editmenu, *helpmenu;
GtkWidget *new, *open, *cut, *copy, *paste, *contents, *about;
GtkAccelGroup *group;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Menu Bars");
gtk_widget_set_size_request (window, 250, -1);
group = gtk_accel_group_new ();
menubar = gtk_menu_bar_new ();
file = gtk_menu_item_new_with_label ("File");
edit = gtk_menu_item_new_with_label ("Edit");
help = gtk_menu_item_new_with_label ("Help");
filemenu = gtk_menu_new ();
editmenu = gtk_menu_new ();
helpmenu = gtk_menu_new ();
gtk_menu_item_set_submenu (GTK_MENU_ITEM (file), filemenu);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (edit), editmenu);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (help), helpmenu);
gtk_menu_shell_append (GTK_MENU_SHELL (menubar), file);
gtk_menu_shell_append (GTK_MENU_SHELL (menubar), edit);
gtk_menu_shell_append (GTK_MENU_SHELL (menubar), help);
/* Create the File menu content. */
new = gtk_image_menu_item_new_from_stock (GTK_STOCK_NEW, group);
open = gtk_image_menu_item_new_from_stock (GTK_STOCK_OPEN, group);
gtk_menu_shell_append (GTK_MENU_SHELL (filemenu), new);
gtk_menu_shell_append (GTK_MENU_SHELL (filemenu), open);
/* Create the Edit menu content. */
cut = gtk_image_menu_item_new_from_stock (GTK_STOCK_CUT, group);
copy = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, group);
paste = gtk_image_menu_item_new_from_stock (GTK_STOCK_PASTE, group);
gtk_menu_shell_append (GTK_MENU_SHELL (editmenu), cut);
gtk_menu_shell_append (GTK_MENU_SHELL (editmenu), copy);
gtk_menu_shell_append (GTK_MENU_SHELL (editmenu), paste);
/* Create the Help menu content. */
contents = gtk_image_menu_item_new_from_stock (GTK_STOCK_HELP, group);
about = gtk_image_menu_item_new_from_stock (GTK_STOCK_ABOUT, group);
gtk_menu_shell_append (GTK_MENU_SHELL (helpmenu), contents);
gtk_menu_shell_append (GTK_MENU_SHELL (helpmenu), about);
gtk_container_add (GTK_CONTAINER (window), menubar);
gtk_window_add_accel_group (GTK_WINDOW (window), group);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
새로운 GtkMenuBar 위젯은 gtk_menu_bar_new()를 이용해 생성된다. 이는 개발자가 내용을 추가할 수 있는 빈 메뉴 셸을 생성할 것이다.
메뉴 바를 생성한 후에는 gtk_menu_bar_set_pack_direction()을 이용해 메뉴 바 항목의 패킹 방향을 정의할 수 있다. pack_direction 프로퍼티에 대한 값은 GtkPackDirection 열거에서 정의되며, GTK_PACK_DIRECTION_LTR, GTK_PACK_DIRECTION_RTL, GTK_PACK_DIRECTION_TTB, GTK_PACK_DIRECTION_BTT를 포함한다. 이들은 메뉴 항목을 각각 좌측에서 우측으로, 우측에서 좌측으로, 상단에서 하단으로, 하단에서 상단으로 패킹할 것이다. 자식 위젯은 기본적으로 좌측에서 우측으로 패킹된다.
GtkMenuBar는 메뉴 바 자식의 메뉴 항목이 패킹되는 방향을 설정하는 child-pack-direction이라는 프로퍼티도 제공한다. 다시 말하자면 하위메뉴 항목이 패킹되는 방법을 제어한다. 이 프로퍼티에 대한 값은 GtkPackDirection 열거에 의해 정의된다.
메뉴 바의 각 자식 항목은 사실 GtkMenuItem 위젯에 해당한다. GtkMenuBar는 GtkMenuShell에서 파생되기 때문에 아래와 같이 gtk_menu_shell_append()를 이용해 항목을 바로 추가하면 된다.
gtk_menu_shell_append (GTK_MENU_SHELL (menubar), file);
메뉴 바의 시작 부분이나 임의의 위치에 항목을 추가하려면 각각 gtk_menu_shell_prepend()와 gtk_menu_shell_insert()를 이용하면 된다.
각 루트 메뉴 항목으로 하위메뉴를 추가하려면 gtk_menu_item_set_submenu()를 호출해야 한다. 각 하위메뉴는 팝업 메뉴와 동일한 방식으로 생성되는 GtkMenu 위젯이다. GTK+는 필요 시 사용자에게 하위메뉴를 표시하는 일을 처리한다.
gtk_menu_item_set_submenu (GTK_MENU_ITEM (file), filemenu);
툴바
GtkToolbar는 수평 또는 수직 행에 다수의 위젯을 보유하는 컨테이너 타입이다. 이는 약간의 수고만으로 많은 수의 위젯을 쉽게 맞춤설정하도록 해주는 것이 목적이다. 일반적으로 툴바에는 툴버튼이 있어서 텍스트 문자열과 함께 이미지를 표시할 수 있도록 해준다. 하지만 툴바는 사실상 어떤 타입의 위젯이든 보유할 수 있다. 네 개의 툴 버튼과 하나의 구분자를 보유하는 툴바를 그림 9-5에 소개하겠다.
리스팅 9-6에서는 간단한 툴바가 생성되어 수평 행에 다섯 개의 툴 항목을 표시한다. 각 툴바 항목은 아이콘과 항목의 목적을 설명하는 라벨을 표시한다. 툴바는 메뉴에 맞지 않는 툴바 항목으로 접근을 제공하는 화살표도 표시하도록 설정되었다.
이 예제에서 GtkEntry 위젯으로 자르기, 복사하기, 붙여넣기, 모두 선택하기 기능을 제공하기 위해 툴바가 사용되었다. main() 함수는 GtkEntry 위로 패킹하는 툴바를 생성한다. 이후 이는 create_toolbar()를 호출하여 툴바를 툴 항목으로 채우고 필요한 시그널을 연결한다.
리스팅 9-6. GtkToolbar 위젯 생성하기 (toolbars.c)
static void select_all (GtkEditable*);
/* Create a toolbar with Cut, Copy, Paste and Select All toolbar items. */
static void
create_toolbar (GtkWidget *toolbar,
GtkWidget *entry)
{
GtkToolItem *cut, *copy, *paste, *selectall, *separator;
cut = gtk_tool_button_new_from_stock (GTK_STOCK_CUT);
copy = gtk_tool_button_new_from_stock (GTK_STOCK_COPY);
paste = gtk_tool_button_new_from_stock (GTK_STOCK_PASTE);
selectall = gtk_tool_button_new_from_stock (GTK_STOCK_SELECT_ALL);
separator = gtk_separator_tool_item_new ();
gtk_toolbar_set_show_arrow (GTK_TOOLBAR (toolbar), TRUE);
gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), cut, 0);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), copy, 1);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), paste, 2);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), separator, 3);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), selectall, 4);
g_signal_connect_swapped (G_OBJECT (cut), "clicked",
G_CALLBACK (gtk_editable_cut_clipboard), entry);
g_signal_connect_swapped (G_OBJECT (copy), "clicked",
G_CALLBACK (gtk_editable_copy_clipboard), entry);
g_signal_connect_swapped (G_OBJECT (paste), "clicked",
G_CALLBACK (gtk_editable_paste_clipboard), entry);
g_signal_connect_swapped (G_OBJECT (selectall), "clicked",
G_CALLBACK (select_all), entry);
}
/* Select all of the text in the GtkEditable. */
static void
select_all (GtkEditable *entry)
{
gtk_editable_select_region (entry, 0, -1);
}
새 툴바는 gtk_toolbar_new()를 이용해 생성되는데, 이 함수는 리스팅 9-6에 표시된 create_toolbar() 함수보다 일찍 호출되었다. 이는 개발자가 툴 버튼을 추가할 수 있는 빈 GtkToolbar 위젯을 생성한다.
GtkToolbar는 툴바에 맞지 않는 항목으로 접근하는 기능, 버튼 스타일, 방향 등을 포함해 그것이 어떻게 상호작용하고 표시되는지 맞춤설정하도록 수많은 프로퍼티를 제공한다.
공간이 충분하지 않아 툴바 항목을 모두 툴바에 표시할 수 없는 경우 gtk_toolbar_set_show_arrow()를 TRUE로 설정하면 오버플로(overflow) 메뉴가 표시될 것이다. 모든 항목을 툴바로 표시할 수 있다면 뷰에서 화살표가 숨겨질 것이다.
void gtk_toolbar_set_show_arrow (GtkToolbar *toolbar,
gboolean show_arrow);
또 다른 GtkToolbar 프로퍼티는 모든 메뉴 항목이 표시되는 스타일로, gtk_toolbar_set_style()로 설정된다. 이 프로퍼티는 테마로 오버라이드가 가능하므로 gtk_toolbar_unset_style()을 호출함으로써 기본 스타일을 이용해야 한다. GtkToolbarStyle 열거에는 네 가지의 툴바 스타일이 정의된다.
- GTK_TOOLBAR_ICONS: 툴바의 각 툴 버튼마다 아이콘만 표시한다.
- GTK_TOOLBAR_TEXT: 툴바의 각 툴 버튼마다 라벨만 표시한다.
- GTK_TOOLBAR_BOTH: 각 툴 버튼마다 아이콘과 라벨을 표시하고, 아이콘이 라벨 위에 위치한다.
- GTK_TOOLBAR_BOTH_HORIZ: 각 툴 버튼마다 아이콘과 라벨을 표시하고, 아이콘이 라벨의 좌측에 위치한다. 툴 항목의 라벨 텍스트는 항목의 important 프로퍼티가 TRUE로 설정될 때에만 표시될 것이다.
툴바의 또 다른 중요한 프로퍼티로 방향(orientation)이 있는데, 이는 gtk_toolbar_set_orientation()을 이용해 설정된다. GtkOrientation은 두 가지 값, GTK_ORIENTATION_HORIZONTAL과 GTK_ORIENTATION_VERTICAL을 정의하는데, 툴바를 수평(기본값)이나 수직으로 만들 때 사용할 수 있다.
툴바 항목
리스팅 9-6은 세 가지 중요한 툴 항목 타입, 즉 GtkToolItem, GtkToolButton, GtkSeparatorToolItem을 소개한다. 모든 툴 버튼은 모든 툴 항목이 사용하는 기본 프로퍼티를 보유한 GtkToolItem 클래스에서 파생된다.
GTK_TOOLBAR_BOTH_HORIZ 스타일을 이용 중이라면 GtkToolItem에 설치된 필수 프로퍼티는 is-important 설정이 된다. 이 프로퍼티가 TRUE로 설정되어야만 툴바 항목의 라벨 텍스트가 이 스타일로 표시될 것이다.
메뉴와 마찬가지로 구분자 툴 항목은 GtkSeparatorToolItem에서 제공되고 gtk_separator_tool_item_new()를 이용해 생성된다. 구분자 툴 항목에는 draw라는 프로퍼티가 있는데, 이를 TRUE로 설정하면 구분자가 그려진다. draw를 FALSE로 설정하면 시각적 구분자 없이 그 위치에 패딩을 위치시킬 것이다.
GtkSeparatorToolItem의 expand 프로퍼티를 TRUE로 설정하고 draw 프로퍼티를 FALSE로 설정하면 구분자 다음의 모든 항목을 툴바 끝까지 강제로 늘일 것이다(force).
대부분의 툴바 항목은 GtkToolButton 타입이다. GtkToolButton은 gtk_tool_button_new_from_stock()을 포함해 다수의 초기화 함수를 제공한다. 이 함수는 스톡 식별자를 수식하는데, GTK+ 2.10에서 이용 가능한 스톡 항목의 목록은 부록 D에서 찾을 수 있다. 대부분의 초기화 함수와 달리 이 메서드는 GtkWidget 객체 대신 GtkToolItem 객체를 리턴한다.
위의 방법 대신 gtk_tool_button_new()를 이용해 커스텀 아이콘과 라벨로 된 GtkToolButton을 생성하는 수도 있다. 이러한 프로퍼티는 각각 NULL로 설정 가능하다.
GtkToolItem* gtk_tool_button_new (GtkWidget *icon,
const gchar* label);
라벨, 스톡 식별자, 아이콘은 초기화 후에 각각 gtk_tool_button_set_label(), gtk_tool_button_set_stock_id(), gtk_tool_button_set_icon_widget() 을 이용하면 수동으로 설정이 가능하다. 이러한 함수들은 툴 버튼의 label, stock-id, icon-widget 프로퍼티로 접근성을 제공한다.
게다가 gtk_tool_button_set_label_widget()을 이용하면 툴 버튼의 기본 GtkLabel 위젯 대신 자신만의 위젯을 정의하여 사용할 수도 있다. 이는 엔트리나 콤보 박스와 같은 임의의 위젯을 툴 버튼으로 내포하는 것을 허용할 것이다. 이 프로퍼티가 NULL로 설정되면 기본 라벨이 사용될 것이다.
void gtk_tool_button_set_label_widget (GtkToolButton *button,
GtkWidget *label_widget);
툴바 항목을 생성하고 나면 gtk_toolbar_insert()를 이용해 각 GtkToolItem을 툴바로 삽입할 수 있다. 초기화 함수가 GtkWidget을 리턴하지는 않으므로 GtkToolItem을 캐스팅할 필요가 없다.
void gtk_toolbar_insert (GtkToolbar *toolbar,
GtkToolItem *item,
gint pos);
gtk_toolbar_insert()의 세 번째 매개변수는 항목을 툴바로 삽입하기 위한 위치를 수락한다. 툴 버튼 위치는 0부터 색인된다. 음수로 된 위치는 툴바의 끝에 추가될 것이다.
토글 툴 버튼
GtkToggleToolButton은 GtkToolButton에서 파생되어서 초기화 기능과 토글 기능만 스스로 구현한다. 토글 툴 버튼은 GtkToggleButton 위젯의 기능을 툴바 항목의 형태로 제공한다. 이는 사용자가 옵션의 설정 여부를 눈으로 확인할 수 있도록 해준다.
토글 툴 버튼은 active 프로퍼티가 TRUE로 설정되면 누름 상태로(depressed) 유지되는 툴 버튼이다. toggled 시그널을 이용하면 토글 버튼의 상태가 변경 시 알림을 수신할 수 있다.
새로운 GtkToggleToolButton을 생성하는 방법에는 두 가지가 있다. 첫 번째로 gtk_toggle_tool_button_new()를 이용하면 빈 툴 버튼이 생성된다. 이후 GtkToolButton이 제공하는 함수를 이용해 라벨과 이미지를 사용할 수 있다.
GtkToolItem* gtk_toggle_tool_button_new ();
GtkToolItem* gtk_toggle_tool_button_new_from_stock (const gchar *stock_id);
두 번째로 gtk_toggle_tool_button_new_from_stock()을 이용하면 스톡 식별자와 연관된 이미지와 라벨이 있는 툴 버튼이 생성될 것이다. 스톡 식별자가 발견되지 않으면 이미지와 라벨은 잘못된(error) 스톡 항목으로 설정될 것이다.
라디오 툴 버튼
GtkRadioToolButton은 GtkToggleToolButton에서 파생되므로 active 프로퍼티와 toggled 시그널을 상속받는다. 따라서 위젯은 새로운 라디오 툴 버튼을 생성하여 라디오 그룹으로 추가하는 방법만 제공하면 된다.
첫 번째 라디오 툴 버튼은 gtk_radio_tool_button_new() 또는 gtk_radio_tool_button_new_from_stock()을 이용해 생성되어야 하는데, 이 때 라디오 그룹은 NULL로 설정된다. 그러면 라디오 툴 버튼에 대한 초기의 기본 라디오 그룹이 생성될 것이다.
GtkToolItem* gtk_radio_tool_button_new (GSList *group);
GtkToolItem* gtk_radio_tool_button_new_from_stock (GSList *group,
const gchar *stock_id);
GtkRadioToolButton은 필요 시 라디오 툴 버튼의 라벨을 설정하는 데 사용 가능한 함수 및 프로퍼티를 제공하는 GtkToolButton로부터 함수를 상속받는다.
모든 필수 요소는 gtk_radio_tool_button_from_widget() 또는 gtk_radio_tool_button_new_with_stock_from_widget()을 이용해 생성되어야 한다. 그룹을 첫 번째 라디오 툴 버튼으로 설정하면 동일한 그룹에 추가된 모든 필수 항목들을 추가하게 될 것이다.
GtkToolItem* gtk_radio_tool_button_new_from_widget (GtkRadioToolButton *group);
GtkToolItem* gtk_radio_tool_button_new_with_stock_from_widget
(GtkRadioToolButton *group,
const gchar *stock_id);
GtkRadioToolButton은 group이라는 프로퍼티 하나만 제공하는데, 이는 라디오 그룹에 속한 또 다른 라디오 툴 버튼이다. 이 버튼은 모든 라디오 버튼을 함께 연결하도록 해주어 한 번에 하나의 버튼만 선택되도록 확보한다.
메뉴 툴 버튼
GtkToolButton에서 파생된 GtkMenuToolButton은 메뉴를 툴 버튼으로 붙이도록 해준다. 위젯은 연관된 메뉴로 접근성을 제공하는 라벨과 이미지 옆에 화살표를 위치시킨다. 가령 GtkMenuToolButton 을 이용해 최근 열었던 파일의 리스트를 GTK_STOCK_OPEN 툴바 버튼에 추가할 수 있겠다. 그림 9-6은 이러한 목적으로 사용된 메뉴 툴 버튼의 스크린샷이다.
리스팅 9-7은 메뉴 툴 버튼을 구현하는 방법을 보여준다. 실제 툴 버튼은 다른 GtkToolButton과 유사한 방식으로 생성되지만 GtkMenuToolButton 위젯으로 메뉴를 부착하는 단계가 추가된다는 점에 차이가 있다.
리스팅 9-7. GtkMenuToolButton 이용하기
GtkToolItem *open;
GtkWidget *recent;
recent = gtk_menu_new ();
/* Add a number of menu items where each corresponds to one recent file. */
open = gtk_menu_tool_button_new_from_stock (GTK_STOCK_OPEN);
gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (open), GTK_MENU (recent));
리스팅 9-7에서는 gtk_menu_tool_button_new_from_stock()을 이용해 기본 스톡 아이콘과 라벨로 된 메뉴 툴 버튼이 생성되었다. 이 함수는 스톡 식별자를 수락하고, 적절한 라벨과 아이콘에 적용할 것이다.
위의 방법 대신 gtk_menu_tool_button_new()를 이용해 메뉴 툴 버튼을 생성하는 것도 가능한데, 이 함수는 아이콘 위젯과 라벨 텍스트를 수락한다. 이러한 프로퍼티들을 추후에 GtkToolButton 프로퍼티의 도움을 받아 설정하고자 한다면 해당 프로퍼티를 NULL로 설정해도 좋다.
GtkToolItem* gtk_menu_tool_button_new (GtkWidget *icon,
const gchar *label);
GtkMenuToolButton을 유일하게 만드는 것은 툴 버튼의 우측에 있는 화살표가 사용자에게 메뉴로 접근성을 제공한다는 점이다. 툴 버튼의 메뉴는 gtk_menu_tool_button_set_menu()를 이용해 설정하거나 menu 프로퍼티를 GtkMenu 위젯으로 설정하면 된다. 화살표를 클릭하면 사용자에게 해당 메뉴가 표시된다.
동적인 메뉴 생성
모든 메뉴와 툴바 항목을 수동으로 생성하는 것도 가능하지만 그럴 경우 공간을 많이 차지하고 코딩에 필요 이상의 시간을 소모하게 만들기도 한다. 메뉴와 툴바의 생성을 자동화하기 위해 GTK+는 XML 파일로부터 메뉴를 동적으로 생성할 수 있도록 한다.
UI 파일 생성하기
사용자 인터페이스 파일은 XML 포맷으로 구성된다. 모든 내용은 <ui>와 </ui> 태그 사이에 포함된다. 당신이 생성하는 동적 UI의 한 가지 타입으로는 리스팅 9-8에 표시된 <menubar>를 이용한 GtkMenuBar를 들 수 있겠다.
리스팅 9-8. 메뉴 UI 파일 (menu.ui)
<ui>
<menubar name="MenuBar">
<menu name="FileMenu" action="File">
<menuitem name="FileOpen" action="Open"/>
<menuitem name="FileSave" action="Save"/>
<separator/>
<menuitem name="FileQuit" action="Quit"/>
</menu>
<menu name="EditMenu" action="Edit">
<menuitem name="EditCut" action="Cut"/>
<menuitem name="EditCopy" action="Copy"/>
<menuitem name="EditPaste" action="Paste"/>
<separator/>
<menuitem name="EditSelectAll" action="SelectAll"/>
<menuitem name="EditDeselect" action="Deselect"/>
</menu>
<menu name="HelpMenu" action="Help">
<menuitem name="HelpContents" action="Contents"/>
<menuitem name="HelpAbout" action="About"/>
</menu>
</menubar>
</ui>
꼭 필요한 것은 아니지만 모든 menubar, menu, menuitem에는 name 속성을 추가해야 한다. name 속성은 실제 위젯으로 접근하는 데 사용할 수 있다. name이 명시되지 않으면 "action" 필드를 이용해 위젯으로 접근이 가능하다.
<menubar>마다 원하는 수만큼 <menu> 자식을 가질 수 있다. 두 가지 태그 모두 일반적인 XML 규칙에 따라 닫아야 한다. 태그에 닫는 태그가 없다면 (예: <menuitem/>) 태그의 끝에 사선(/)을 위치시켜 태그가 끝이 났음을 파서가 알아차리도록 해야 한다.
action 속성은 최고 수준의 위젯과 구분자를 제외한 모든 요소들에게 적용된다. GtkAction 객체를 각 요소로 연관시키기 위해 UI 파일을 로딩하면 GtkUIManager는 action 속성을 이용한다. GtkAction은 항목이 어떻게 그려지는지, 콜백 함수를 호출해야 하는지, 그리고 때에 따라서는 언제 항목이 활성화되는지에 대한 정보를 보유한다.
구분자는 <separator/> 태그를 이용해 메뉴 내에 위치된다. 일반적인 GtkSeparatorMenuItem이 추가될 것이므로 구분자에 대한 이름이나 액션 정보를 제공하지 않아도 된다.
메뉴 바 뿐만 아니라 리스팅 9-9에 표시된 바와 같이 <toolbar> 태그를 이용해 UI 파일에 툴바도 생성할 수 있다.
리스팅 9-9. 툴바 UI 파일 (toolbar.ui)
<ui>
<toolbar name="Toolbar">
<toolitem name="FileOpen" action="Open"/>
<toolitem name="FileSave" action="Save"/>
<separator/>
<toolitem name="EditCut" action="Cut"/>
<toolitem name="EditCopy" action="Copy"/>
<toolitem name="EditPaste" action="Paste"/>
<separator/>
<toolitem name="EditSelectAll" action="SelectAll"/>
<toolitem name="EditDeselect" action="Deselect"/>
<separator/>
<toolitem name="HelpContents" action="Contents"/>
<toolitem name="HelpAbout" action="About"/>
</toolbar>
</ui>
각 툴바는 원하는 수만큼 <toolitem> 요소를 포함할 수 있다. 툴 항목은 메뉴 항목과 동일한 방식으로 "action"과 선택적 "name"으로 명시된다. 구분된 UI 파일 내 요소들에 대해 동일한 "name"을 사용할 수는 있지만 가령 툴바와 메뉴 바가 동일한 파일에 있는 경우라면 동일한 이름을 사용해선 안 된다.
하지만 다중 요소에 대해 동일한 "action"을 사용할 수는 있다. 이런 경우 각 요소가 동일하게 그려지고 동일한 콜백 함수로 연결될 것이다. 이 때 장점은 각 항목 타입마다 하나의 GtkAction만 정의해도 된다는 점이다. 가령, 리스팅 9-8에서 9-10까지 예제에서는 UI 파일 내 Cut 요소에 대해 동일한 "action"을 사용할 것이다.
툴바, 메뉴 바, 팝업 메뉴는 구분된 UI 파일로 나뉘었지만 하나의 파일에 원하는 수만큼 위젯을 포함시킬 수 있다. 파일의 전체 내용이 <ui>와 </ui> 태그 사이에 포함되어야 한다는 한 가지 조건이 있다.
툴바와 메뉴 바 외에도 리스팅 9-10에서 확인할 수 있듯이 UI 파일에 팝업 메뉴를 정의하는 것도 가능하다. 리스팅 9-8, 9-9, 9-10에 반복되는 액션이 실행됨을 주목하라. 반복 액션은 "action"의 인스턴스마다 객체를 정의하는 대신 하나의 GtkAction 객체만 정의하도록 해준다.
리스팅 9-10. 팝업 UI 파일 (popup.ui)
<ui>
<popup name="EntryPopup">
<menuitem name="EditCut" action="Cut"/>
<menuitem name="EditCopy" action="Copy"/>
<menuitem name="EditPaste" action="Paste"/>
<separator/>
<menuitem name="EditSelectAll" action="SelectAll"/>
<menuitem name="EditDeselect" action="Deselect"/>
</popup>
</ui>
UI 파일이 마지막으로 지원하는 최고 수준의 위젯은 팝업 메뉴로, <popup> 태그로 표시된다. 팝업 메뉴는 일반 메뉴와 같아서 <menuitem> 요소를 여전히 자식으로 사용할 수 있을 것이다.
UI 파일 로딩하기
UI 파일을 생성한 후에는 애플리케이션에 로딩시켜 필요한 위젯을 검색해야 한다. 이를 실행하기 위해서는 GtkActionGroup과 GtkUIManager가 제공하는 기능을 활용할 필요가 있다.
GtkActionGroup은 이름, 스톡 식별자, 라벨, 키보드 가속기, 툴팁, 콜백 함수로 이루어진 항목의 집합이다. 각 액션의 이름을 UI 요소로 연관시키기 위해서는 UI 파일로부터 action 매개변수로 이름을 설정할 수 있다.
GtkUIManager는 하나 또는 그 이상의 사용자 인터페이스 정의를 동적으로 로딩하도록 허용하는 객체다. 이는 연관된 액션 그룹을 바탕으로 가속기 그룹을 자동으로 생성하여 개발자가 UI 파일로부터 name 매개변수를 기반으로 위젯을 참조하도록 해준다.
리스팅 9-11에서는 GtkUIManager를 이용해 리스팅 9-8과 9-9의 UI 파일로부터 메뉴 바와 툴바를 로딩하였다. 그 결과 애플리케이션은 그림 9-7에 표시된 모습과 같다.
애플리케이션 내 각 메뉴와 툴 항목은 빈 콜백 함수로 연결되어 있는데, 이 예제는 UI 정의로부터 메뉴와 툴바를 동적으로 로딩하는 방법만 보여주는 데 목적이 있기 때문이다. 실제 내용이 있는 콜백 함수는 이번 장에 실린 두 연습문제를 통해 구현할 것이다.
리스팅 9-11. GtkUIManager를 이용해 메뉴 로딩하기 (uimanager.c)
#include <gtk/gtk.h>
/* All of the menu item callback functions have a GtkMenuItem parameter, and
* receive the same gpointer value. There is only one callback function shown
* since all of the rest will be formatted in the same manner. */
static void open (GtkMenuItem *menuitem, gpointer data);
#define NUM_ENTRIES 13
static GtkActionEntry entries[] =
{
{ "File", NULL, "_File", NULL, NULL, NULL },
{ "Open", GTK_STOCK_OPEN, NULL, NULL,
"Open an existing file", G_CALLBACK (open) },
{ "Save", GTK_STOCK_SAVE, NULL, NULL,
"Save the document to a file", G_CALLBACK (save) },
{ "Quit", GTK_STOCK_QUIT, NULL, NULL,
"Quit the application", G_CALLBACK (quit) },
{ "Edit", NULL, "_Edit", NULL, NULL, NULL },
{ "Cut", GTK_STOCK_CUT, NULL, NULL,
"Cut the selection to the clipboard", G_CALLBACK (cut) },
{ "Copy", GTK_STOCK_COPY, NULL, NULL,
"Copy the selection to the clipboard", G_CALLBACK (copy) },
{ "Paste", GTK_STOCK_PASTE, NULL, NULL,
"Paste text from the clipboard", G_CALLBACK (paste) },
{ "SelectAll", GTK_STOCK_SELECT_ALL, NULL, NULL,
"Select all of the text", G_CALLBACK (selectall) },
{ "Deselect", NULL, "_Deselect", "<control>d",
"Deselect all of the text", G_CALLBACK (deselect) },
{ "Help", NULL, "_Help", NULL, NULL, NULL },
{ "Contents", GTK_STOCK_HELP, NULL, NULL,
"Get help on using the application", G_CALLBACK (help) },
{ "About", GTK_STOCK_ABOUT, NULL, NULL,
"More information about the application", G_CALLBACK (about) }
};
int main (int argc,
char *argv[])
{
GtkWidget *window, *menubar, *toolbar, *vbox;
GtkActionGroup *group;
GtkUIManager *uimanager;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "UI Manager");
gtk_widget_set_size_request (window, 250, -1);
/* Create a new action group and add all of the actions to it. */
group = gtk_action_group_new ("MainActionGroup");
gtk_action_group_add_actions (group, entries, NUM_ENTRIES, NULL);
/* Create a new UI manager and build the menu bar and toolbar. */
uimanager = gtk_ui_manager_new ();
gtk_ui_manager_insert_action_group (uimanager, group, 0);
gtk_ui_manager_add_ui_from_file (uimanager, "menu.ui", NULL);
gtk_ui_manager_add_ui_from_file (uimanager, "toolbar.ui", NULL);
/* Retrieve the necessary widgets and associate accelerators. */
menubar = gtk_ui_manager_get_widget (uimanager, "/MenuBar");
toolbar = gtk_ui_manager_get_widget (uimanager, "/Toolbar");
gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_ICONS);
gtk_window_add_accel_group (GTK_WINDOW (window),
gtk_ui_manager_get_accel_group (uimanager));
vbox = gtk_vbox_new (FALSE, 0);
gtk_box_pack_start_defaults (GTK_BOX (vbox), menubar);
gtk_box_pack_start_defaults (GTK_BOX (vbox), toolbar);
gtk_container_add (GTK_CONTAINER (window), vbox);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
메뉴와 툴바를 동적으로 로딩하기 위해 GtkUIManager를 사용할 때 가장 먼저 해야 할 일은 액션의 집합체(array)를 생성하는 것이다. 모든 GtkAction, GtkToggleAction, GtkRadioAction 객체를 수동으로 생성하는 방법도 있지만 훨씬 더 간단한 방법이 있다.
GtkActionEntry는 액션명, 스톡 식별자, 라벨, 가속기, 툴팁, 콜백 함수를 보유하는 구조체다. GtkActionEntry 구조체의 내용은 아래의 코드 조각에서 확인할 수 있다.
typedef struct
{
const gchar *name;
const gchar *stock_id;
const gchar *label;
const gchar *accelerator;
const gchar *tooltip;
GCallback callback;
} GtkActionEntry;
액션명 문자열이 사용되기 위해서는 UI 정의에서 툴 항목이나 메뉴의 action 속성과 이름이 같아야 한다. 액션명을 제외한 어떤 속성이든 필요하지 않으면 NULL로 설정해도 안전하다. 스톡 식별자를 명시할 경우, 기본값이 오버라이드되는 것을 원하지 않는 한 라벨이나 가속기를 명시할 필요가 없다.
키보드 가속기는 그 값을 설명하는 문자열로 명시된다. 허용되는 키보드 가속기로는 "<Control>a", "<Shift><Control>x", "F3" 등이 있다. 일부 수식키를 간략하게 사용하는 수도 있다. 가령 Control 키는 "<Ctrl>" 또는 "<Ctl>"로 참조할 수 있다. 간략하게 말하자면, 가속기는 gtk_accelerator_parse()를 이용해 파싱될 수 있는 형태여야 한다.
액션 리스트를 생성한 후에는 gtk_action_group_new()를 이용해 모든 액션을 보유하게 될 새로운 GtkActionGroup을 생성해야 한다. 이 함수로 정의된 이름은 액션과 키 바인딩을 연관시킬 때 사용될 것이다.
gtk_action_group_add_actions()를 호출하면 GtkActionEntry 객체의 배열을 GtkActionGroup으로 추가할 수 있다. 이 함수는 엔트리의 배열, 엔트리 개수, 각 콜백 함수로 전달될 선택적 데이터 매개변수를 수락한다.
void gtk_action_group_add_actions (GtkActionGroup *group,
const GtkActionEntry *entries,
guint n_entries,
gpointer data);
각 콜백 함수로 다른 데이터 매개변수를 전달해야 한다면 각 GtkAction을 수동으로 생성하여 gtk_action_group_add_action() 또는 gtk_action_group_add_action_with_accel()을 이용해 그룹으로 추가해야 할 것이다.
다음 단계는 gtk_ui_manager_new()를 이용해 GtkUIManager를 생성하는 것이다. 이 객체는 UI 정의를 로딩하여 각 항목을 그에 해당하는 GtkAction으로 연결하는 데 사용될 것이다. 이후 개발자는 gtk_ui_manager_insert_action_group()을 이용해 자신의 모든 액션 그룹을 GtkUIManager로 추가해야 한다. 이 함수는 그룹으로부터 모든 액션을 UI 관리자에게 추가할 것이다. 이후 적절한 위젯을 생성하기 위해 UI 정의에서 액션을 요소로 매치할 수 있을 것이다.
void gtk_ui_manager_insert_action_group (GtkUIManager *uimanager,
GtkActionGroup *group,
gint pos);
이 함수의 세 번째 매개변수는 UI 관리자 내에서 액션 그룹의 위치를 나타내는 정수다. 그룹 내에 더 낮은 위치에 동일한 이름으로 된 액션이 있다면 이것이 높은 위치의 액션보다 우선한다.
다음으로 gtk_ui_manager_add_ui_from_file()을 이용해 원하는 수만큼 UI 파일을 로딩하길 원할 것이다. 리스팅 9-11에서는 실행 파일과 관련해 menu.ui 와 toolbar.ui 파일이 로딩되었다. 이 함수의 세 번째 매개변수는 선택적 GError 객체다.
guint gtk_ui_manager_add_ui_from_file (GtkUIManager *uimanager,
const gchar *filename,
GError **error);
이 정의는 각 파일의 내용을 로딩할 것이다. 이후 각 요소는 액션 그룹으로부터 추가된 객체와 매치될 것이다. UI 관리자는 추후 UI 정의에 따라 적절한 위젯을 모두 생성할 것이다. 액션이 존재하지 않으면 오류가 terminal로 출력될 것이다.
UI 관리자가 위젯을 생성하고 난 후에 이름 매개변수가 존재하지 않으면 개발자는 아래 코드와 같이 이름 경로나 액션을 기반으로 위젯을 로딩할 수 있다. 두 가지 최상위 수준 위젯인 메뉴 바와 툴바는 "/MenuBar"와 "/Toolbar"에서 찾을 수 있다. 이들은 gtk_ui_manager_get_widget()을 이용해 로딩된다.
GtkWidget* gtk_ui_manager_get_widget (GtkUIManager *self,
const gchar *path);
경로가 필요하면 어떤 위젯이든 절대 경로를 부여해야만 한다. 절대 경로에서는 <ui> 요소가 누락된다. 이후 경로는 각 항목의 name 속성을 이용해 빌드된다. 가령, 메뉴 바에서 GTK_STOCK_OPEN 요소로 접근하기 위해 gtk_ui_manager_get_widget()을 호출하면 "/MenuBar/FileMenu/FileOpen" 메뉴 항목이 리턴될 것이다.
그 외 액션 타입
스톡 이미지와 키보드 가속기가 있는 메뉴와 툴바 항목도 좋지만 GtkUIManager와 함께 토글 버튼과 라디오 버튼을 이용하는 것은 어떨까? GTK+는 이를 목적으로 GtkToggleActionEntry와 GtkRadioActionEntry를 제공한다. GtkToggleActionEntry의 내용은 다음과 같다.
typedef struct
{
const gchar *name;
const gchar *stock_id;
const gchar *label;
const gchar *accelerator;
const gchar *tooltip;
GCallback callback;
gboolean is_active;
} GtkToggleActionEntry;
UI 정의를 사용 시 한 가지 장점은 실제 정의(action definition)에서는 애플리케이션에서 액션이 어떻게 구현될 것인지 전혀 알지 못한다는 데에 있다. 이 때문에 사용자는 각 액션이 어떻게 구현될 것인지 알지 않고도 메뉴 구조를 재디자인할 수 있다.
GtkActionEntry 외에도 GTK+는 토글 메뉴 또는 툴 항목을 생성하는 GtkToggleActionEntry를 제공한다. 이 구조체는 한 가지 추가 member인 is_active를 포함하는데, 이는 버튼이 처음에 활성화(active)로 설정될 것인지를 정의한다.
GtkToggleActionEntry 객체의 배열을 추가하는 작업은 개발자가 gtk_action_group_add_toggle_actions()를 사용해야 한다는 사실만 제외하면 일반 액션을 추가하는 과정과 비슷하다. 이 함수는 GtkToggleActionEntry 객체의 배열, 배열 내 액션의 개수, 모든 콜백 함수로 전달될 포인터를 수락한다.
void gtk_action_group_add_toggle_actions (GtkActionGroup *group,
const GtkToggleActionEntry *entries,
guint num_entries,
gpointer data);
또 GtkRadioActionEntry는 라디오 액션의 그룹을 생성하도록 해준다. value 라는 member는 특정 라디오 메뉴 항목이나 라디오 툴 버튼을 활성화하는 데 사용할 수 있는 유일한 정수다.
typedef struct
{
const gchar *name;
const gchar *stock_id;
const gchar *label;
const gchar *accelerator;
const gchar *tooltip;
gint value;
} GtkRadioActionEntry;
라디오 액션은 gtk_action_group_add_radio_actions()의 호출을 통해 액션 그룹으로 추가되며, 이 함수를 호출하면 라디오 버튼을 모두 그룹화할 것이다. 함수는 개발자가 두 개의 추가 매개변수를 명시해야 한다는 점만 제외하면 gtk_action_group_add_toggle_actions()와 동일하게 작동한다.
void gtk_action_group_add_radio_actions (GtkActionGroup *group,
const GtkRadioActionEntry *entries,
guint num_entries,
gint value,
GCallback on_change,
gpointer data);
value 매개변수는 액션에 할당된 식별자로, 초기에 활성화할 수도 있고 기본적으로 모두 비활성화하려면 -1로 설정해야 한다. 콜백 함수 on_change()는 changed 시그널이 라디오 버튼에서 발생하면 호출된다.
플레이스홀더
UI 파일을 생성할 때는 추후 메뉴에 다른 메뉴 항목을 추가할 수 있는 위치를 표시해두길 원할지도 모른다. 가령, File 메뉴에 최신 파일 목록을 추가하고 싶다고 가정하면 개발자는 목록에 얼마나 많은 파일이 포함될 것인지 알지 못할 것이다.
이러한 경우를 위해 GTK+는 <placeholder> 태그를 제공한다. 코드의 그 다음 줄에 <placeholder> 태그가 정의되어 File 메뉴에서 최신 파일 메뉴 항목을 추가할 수 있는 위치를 표시하는 데 사용할 수 있다.
<placeholder name="FileRecentFiles"/>
애플리케이션 내에서 플레이스홀더 위치에 새로운 사용자 인터페이스 정보를 추가하려면 gtk_ui_manager_add_ui()를 사용할 수 있다. 이 함수는 먼저 gtk_ui_manager_new_merge_id()를 호출해 리턴된 부호가 없는 유일한 정수를 수락한다. 개발자는 위젯을 사용자 인터페이스로 추가할 때마다 새로운 통합(merge) 식별자를 검색해야 할 것이다.
void gtk_ui_manager_add_ui (GtkUIManager *uimanager,
guint merge_id,
const gchar *path,
const gchar *name,
const gchar *action,
GtkUIManagerItemType type,
gboolean top);
gtk_ui_manager_add_ui()의 그 다음 매개변수는 새로운 항목이 추가되어야 하는 지점을 나타내는 경로로, 플레이스홀더에 대한 경로인 "/MenuBar/File/FileRecentFiles"가 될 것이다. 이후 개발자는 새로운 위젯에 대한 이름과 액션을 명시하고, 추가되는 UI 항목의 타입을 명시해야 한다. UI 항목의 타입은 아래와 같이 GtkUIManagerItemType 열거 옵션에서 정의된다.
- GTK_UI_MANAGER_AUTO: GTK+는 어떤 타입의 위젯이 추가될 것인지 결정할 것이다.
- GTK_UI_MANAGER_MENUBAR: GtkMenuBar 위젯을 추가한다. 플레이스홀더의 위치는 <ui> 태그의 직계 자식이어야 한다.
- GTK_UI_MANAGER_MENU: GtkMenu를 최상위 수준의 위젯의 자식으로 추가한다.
- GTK_UI_MANAGER_TOOLBAR: GtkMenuBar를 추가한다. 플레이스홀더의 위치는 <ui> 태그의 직계 자식이어야 한다.
- GTK_UI_MANAGER_PLACEHOLDER: 사용자 인터페이스 내 어떤 위치로든 추가 가능한 새로운 플레이스홀더를 추가한다.
- GTK_UI_MANAGER_POPUP: GtkMenuBar를 추가한다. 이는 플레이스홀더가 <ui> 태그의 직계 자식으로 위치하도록 요구한다.
- GTK_UI_MANAGER_MENUITEM: GtkMenuItem을 최상위 수준 위젯의 자식으로 추가한다.
- GTK_UI_MANAGER_TOOLITEM: GtkToolItem을 최상위 수준의 GtkToolbar 위젯의 자식으로 추가한다.
- GTK_UI_MANAGER_SEPARATOR: 구분자를 타입과 상관없이 최상위 수준 위젯으로 추가한다.
- GTK_UI_MANAGER_ACCELERATOR: 키보드 가속기를 메뉴 또는 툴바로 추가한다.
gtk_ui_manager_add_ui()의 마지막 매개변수는 Boolean 변수로, 주어진 경로와 관련해 새로운 UI 요소를 위치시킨다. 이를 TRUE로 설정하면 UI 요소는 경로 앞에 삽입된다. 그 외의 경우는 경로 뒤에 삽입된다.
커스텀 스톡 항목
바로 앞 절에서 GtkActionEntry는 스톡 식별자를 수락하여 이미지를 항목으로 추가함을 눈치챌 것이다. 이 때문에 언젠가는 개발자가 비표준 메뉴와 툴바 항목에 사용하도록 자신만의 커스텀 스톡 아이콘을 생성해야 할 것이다. 새로운 스톡 항목은 세 가지 객체, GtkIconSource, GtkIconSet, GtkIconFactory를 이용해 생성된다. 가장 아래부터 차례대로 살펴보자.
GtkIconSource는 GdkPixbuf 또는 이미지 파일명을 보유하는 객체다. 이는 이미지의 한 가지 변형체(variant)를 보유하도록 되어 있다. 예를 들어, 이미지가 활성화될 때와 비활성화되었을 때 다르게 표시되어야 한다면 각 상태별로 하나씩, 총 여러 개의 아이콘 소스를 갖고 있어야 한다. 아이콘 크기, 언어, 또는 아이콘 상태별로 다수의 아이콘 소스가 필요할 것이다.
다수의 아이콘 소스는 GtkIconSet를 이용해 조직되는데, 이는 하나의 스톡 이미지에 해당하는 모든 GtkIconSource 객체를 보유한다. 개발자의 아이콘 집합에 하나의 이미지만 포함하는 경우가 종종 있다. 드물긴 하지만 이런 경우 gtk_icon_set_new_from_pixbuf()를 이용하면 아이콘 소스의 생성 단계를 건너뛸 수 있다.
GtkIconSet* gtk_icon_set_new_from_pixbuf (GdkPixbuf *pixbuf);
필요한 아이콘 집합을 모두 생성하고 나면 특정 테마에 대한 모든 스톡 항목을 조직하는 데 사용되는 GtkIconFactory로 아이콘 집합이 추가된다. 아이콘 팩토리(icon factory)는 GTK+가 스톡 항목을 검색할 수 있도록 전역 리스트(global list)로 추가된다.
이번 절에서는 여러 개의 새로운 스톡 항목이 생성될 것이다. 그림 9-8은 리스팅 9-12에서 생성되는 새로운 스톡 항목의 스크린샷에 해당한다.
리스팅 9-12에서는 "check-list", "calculator", "screenshot", "cpu", "desktop"라는 다섯 개의 새로운 스톡 항목이 생성되었다. 이후 각 새로운 스톡 항목으로부터 툴바 항목이 생성되어 화면에 표시된다.
리스팅 9-12. GtkIconFactory 이용하기 (iconfactory.c)
#include <gtk/gtk.h>
#define ICON_LOCATION "/path/to/icons/"
typedef struct
{
gchar *location;
gchar *stock_id;
gchar *label;
} NewStockIcon;
const NewStockIcon list[] =
{
{ ICON_LOCATION"checklist.png", "check-list", "Check _List" },
{ ICON_LOCATION"calculator.png", "calculator", "_Calculator" },
{ ICON_LOCATION"camera.png", "screenshot", "_Screenshots" },
{ ICON_LOCATION"cpu.png", "cpu", "CPU _Info" },
{ ICON_LOCATION"desktop.png", "desktop", "View _Desktop" },
{ NULL, NULL, NULL }
};
static void add_stock_icon (GtkIconFactory*, gchar*, gchar*);
int main (int argc,
char *argv[])
{
GtkWidget *window, *toolbar;
GtkIconFactory *factory;
gint i = 0;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Icon Factory");
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
factory = gtk_icon_factory_new ();
toolbar = gtk_toolbar_new ();
/* Loop through the list of items and add new stock items. */
while (list[i].location != NULL)
{
GtkToolItem *item;
add_stock_icon (factory, list[i].location, list[i].stock_id);
item = gtk_tool_button_new_from_stock (list[i].stock_id);
gtk_tool_button_set_label (GTK_TOOL_BUTTON (item), list[i].label);
gtk_tool_button_set_use_underline (GTK_TOOL_BUTTON (item), TRUE);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, i);
i++;
}
gtk_icon_factory_add_default (factory);
gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH);
gtk_toolbar_set_show_arrow (GTK_TOOLBAR (toolbar), FALSE);
gtk_container_add (GTK_CONTAINER (window), toolbar);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
/* Add a new stock icon from the given location and with the given stock id. */
static void
add_stock_icon (GtkIconFactory *factory,
gchar *location,
gchar *stock_id)
{
GtkIconSource *source;
GtkIconSet *set;
source = gtk_icon_source_new ();
set = gtk_icon_set_new ();
gtk_icon_source_set_filename (source, location);
gtk_icon_set_add_source (set, source);
gtk_icon_factory_add (factory, stock_id, set);
}
새로운 아이콘 팩토리, 소스, 또는 집합을 생성하는 일은 gtk_icon_factory_new(), gtk_icon_source_new(), gtk_icon_set_new()를 호출하는 것만큼이나 간단하다. 각 함수는 현재 상태에선 쓸모가 없는 빈 객체를 생성한다.
리스팅 9-12에서 아이콘 소스는 gtk_icon_source_set_filename()을 통해 명시된 파일명에서 찾을 수 있는 이미지로 초기화된다. 이 방법이 아니라면 gtk_icon_source_set_pixbuf()를 이용해 GtkPixbuf 객체로부터 아이콘 소스를 생성할 수도 있다.
리스팅 9-12에서는 각 스톡 항목마다 하나의 아이콘 소스만 필요했기 때문에 추가 맞춤설정은 필요 없다. 하지만 gtk_icon_source_set_size()를 이용해 아이콘이 구체적인 크기로 표시되도록 설정하는 것은 가능하다. 이 함수는 애플리케이션이 구체적인 크기를 필요로 한다면 이 아이콘만 사용할 것을 GTK+에게 알린다.
void gtk_icon_source_set_size (GtkIconSource *source,
GtkIconSize size);
아이콘 소스 크기를 설정해야 할 경우, gtk_icon_source_set_size_wildcarded()로 FALSE를 전달하지 않는 한 어떤 효과도 나타나지 않을 것이다. 그 외의 경우 아이콘 소스는 모든 크기에 사용될 것이다. 아이콘 상태에서도 마찬가진데, 설정은 gtk_icon_source_set_state_wildcarded()를 이용해 해제되어야 한다.
뿐만 아니라 개발자는 GtkIconState에서 정의된 구체적인 상태에 있는 동안 아이콘이 표시되도록 정의할 수 있다. gtk_icon_source_set_state()를 이용하면 열거에서 정의된 다섯 가지 상태에 모두 아이콘을 정의하도록 확보하길 원할 것이다.
void gtk_icon_source_set_state (GtkIconSource *source,
GtkIconState state);
아이콘 소스를 생성하고 나면 gtk_icon_set_add_source()를 이용해 소스를 모두 아이콘 집합으로 추가해야 한다. 이 함수는 GtkIconSet을 비롯해 추가될 아이콘 소스를 수락한다.
void gtk_icon_set_add_source (GtkIconSet *iconset,
const GtkIconSource *source)
아이콘 소스에서 어떤 와일드카드든 설정을 해제할 경우, 가능한 상태나 크기마다 스톡 아이콘을 정의하도록 확보해야 할 것이다. 이는 주로 와일드카드로 표시된 크기와 상태로 된 하나의 아이콘 소스를 추가함으로써 이루어진다. 구체적인 아이콘이 더 있다면 그것이 사용될 것이다. 적절한 아이콘을 찾을 수 없으면 와일드카드 아이콘이 사용될 것이다. 이 와일드카드 이미지는 상황에 따라 밝아지거나 어떤 방식으로든 변경된다.
다음으로 gtk_icon_factory_add()를 이용해 각 GtkIconSet을 아이콘 팩토리로 추가해야 한다. 이 함수는 아이콘을 참조하는 데 사용될 스톡 식별자를 수락한다. 보통은 "myapp-iconname"으로 명명하길 원할 것인데, 여기서 "myapp"은 개발자의 애플리케이션명으로 대체되겠다.
void gtk_icon_factory_add (GtkIconFactory *factory,
const gchar *stock_id,
GtkIconSet *iconset);
스톡 식별자가 이미 존재한다면 새 항목이 기존의 것을 대체하므로 자신의 애플리케이션명을 스톡 식별자에 사용하면 기본 스톡 항목의 오버라이드를 피할 수 있다.
개발자의 아이콘 팩토리에 추가된 스톡 항목은 gtk_icon_factory_add_default()를 이용해 아이콘 팩토리의 전역적 리스트로 추가하기 전에는 이용할 수가 없다. 보통은 기본 아이콘을 포함하는 그래픽 라이브러리마다 구분된 아이콘 팩토리가 존재할 것이다.
자신의 이해도 시험하기
아래 두 개의 연습문제는 이번 장에 걸쳐 학습한 메뉴와 툴바에 대한 개요를 제공한다.
연습문제를 해결하는 동시 기본적으로 팝업 메뉴를 지원하지 않는 다른 위젯들을 이용해 팝업 메뉴를 생성해볼 수도 있다. 연습문제를 해결하고 나면 기본 항목 대신에 사용되는 자신만의 스톡 아이콘을 생성하여 확장할 수도 있겠다.
연습문제 9-1. 툴바
제 7장에서 GtkTextView 위젯을 이용해 간단한 텍스트 에디터를 생성한 바 있다. 이번 연습문제에서는 애플리케이션을 확장시켜 GtkButton 위젯으로 찬 수직 막대 대신 액션을 위한 툴바를 제공해보라.
수동으로 툴바의 생성도 가능하지만 대부분 애플리케이션에서는 GtkUIManager를 이용해 툴바를 생성하길 원할 것이다. 따라서 이 방법을 이용하라. 내장된 스톡 항목을 활용하거나 GtkIconFactory를 이용해 자신만의 스톡 항목을 생성하라.
때로는 애플리케이션이 툴바를 핸들 박스의 자식으로 제공하는 편이 더 도움이 되기도 한다. 자신의 텍스트 에디터에 이를 적용하여 툴바를 텍스트 뷰 위에 위치시켜라. 그리고 텍스트로 된 디스크립터는 모든 툴 버튼의 아래에 표시되도록 툴바를 만들어라.
첫 번째 연습문제는 자신만의 툴바를 빌드하는 방식을 가르쳐준다. 또 GtkHandleBox 컨테이너의 사용 방법도 보여준다. 다음 연습문제에서는 메뉴 바가 있는 Text Editor 애플리케이션을 재구현할 것이다.
연습문제 9-2. 메뉴 바
연습문제 9-1에서와 같은 애플리케이션을 구현하되 이번에는 메뉴 바를 이용하라. GtkUIManager를 계속해서 이용해야 하지만 메뉴가 GtkHandleBox에 포함될 이유는 없다.
툴팁은 메뉴 항목에 자동으로 표시되지 않으므로 각 항목에 대한 추가 정보는 상태 바를 이용해 제공하라. 메뉴 바는 File과 Edit, 두 개의 메뉴를 포함해야 한다. File 메뉴에서 Quit 메뉴 항목도 제공하도록 한다.
요약
이번 장에서는 메뉴, 툴바, 메뉴 바를 생성하는 두 가지 방법을 학습하였다. 첫 번째 방법은 수동적인 방식으로, 조금 더 까다로웠지만 필요한 위젯을 모두 소개할 기회를 가졌다.
첫 번째 예제를 통해서는 기본 메뉴 항목을 이용해 진행 막대에 대한 팝업 메뉴를 구현하는 방법을 설명했다. 이 예제를 확장시켜 GtkStatusbar 위젯을 통해 키보드 가속기와 추가 정보를 사용자에게 제공하고자 하였다. 하위메뉴뿐 아니라 이미지, 토글, 라디오 메뉴 항목에 대해서도 학습하였다.
그 다음 절에서는 하위메뉴가 있는 메뉴 항목을 구현하여 GtkMenuShell을 이용해 메뉴 바를 구현하는 방법을 보였다. 이러한 메뉴 바는 수평이나 수직으로, 그리고 전방향이나(forward) 후방향으로(backward) 표시가 가능하다.
툴바는 버튼으로 이루어진 단순한 수평 또는 수직 리스트다. 각 버튼은 아이콘과 라벨 텍스트를 포함한다. 지금까지 추가로 세 가지 타입의 툴바 버튼, 즉 토글, 라디오 버튼, 추가 메뉴가 있는 툴 버튼을 학습하였다.
그리고 많은 작업을 거친 후에 동적으로 로딩 가능한 메뉴를 생성하는 방법을 학습했다. 각 메뉴나 툴바는 UI 정의 파일에서 보유하는데, 이러한 파일은 GtkUIManager 클래스에 의해 로딩된다. UI 관리자는 각 객체를 적절한 액션과 연관시키고, UI 정의에 따라 위젯을 생성한다.
마지막으로 학습한 내용은 자신만의 커스텀 스톡 아이콘을 생성하는 방법이다. 액션의 집합체는 스톡 식별자로 하여금 아이콘을 액션으로 추가하도록 요구하기 때문에 자신만의 아이콘을 생성할 필요가 있겠다.
다음 장에서는 코딩에서 잠시 물러나 Glade 사용자 인터페이스 빌더(Glade User Interface Builder)를 이용해 그래픽 사용자 인터페이스를 디자인하는 주제를 다루도록 하겠다. 이러한 애플리케이션을 시작하면 동적으로 로딩 가능한 사용자 인터페이스 XML 파일을 생성한다. 그리고 나서 프로그램적으로 Libglad를 이용해 이러한 파일을 처리하는 방법을 학습할 것이다.