Статья
Версия для печати
Обсудить на форуме
Основы GTK+ (часть 2)



В первой части мы учились ловить события и создавать виджеты. В этой части мы посмотрим, как создать меню и создадим небольшой текстовый редактор.
Приступим. Для создания меню нам в большинстве случаем понадобятся следующие функции:
Код:
GtkWidget *gtk_menu_bar_new( void );
GtkWidget *gtk_menu_new( void );
GtkWidget *gtk_menu_item_new( void );

GtkWidget *gtk_menu_item_new_with_label( const char *label );

GtkWidget *gtk_menu_item_new_with_mnemnonic( const char *label );

void gtk_menu_item_set_submenu(
GtkMenuItem *menu_item,
GtkWidget *submenu );

void gtk_menu_bar_append(
GtkMenuBar *menu_bar,
GtkWidget *menu_item );

void gtk_menu_item_right_justify( GtkMenuItem *menu_item );

Расскажу о них по порядку.

Код:
GtkWidget *gtk_menu_bar_new( void );
Создает меню бар куда собственно и крепится наше меню.

Код:
GtkWidget *gtk_menu_new( void );
Создает меню в которое инкапсулируются остальные элементы меню.

Код:
GtkWidget *gtk_menu_item_new( void );
GtkWidget *gtk_menu_item_new_with_label( const char *label );
GtkWidget *gtk_menu_item_new_with_mnemnonic( const char *label );
Создают элементы меню. С первой функцией всё понятно просто элемент без видимой метки. Вторая с видимой меткой, а треть я подчеркиванием мнемоники.
Пока ничего сложного и интересного.

Код:
void gtk_menu_item_set_submenu(
GtkMenuItem *menu_item,
GtkWidget *submenu );
Привязываем  к элементу меню подменю.

Код:
void gtk_menu_bar_append(
GtkMenuBar *menu_bar,
GtkWidget *menu_item );
Далее крепим элемент меню к меню бару.

Код:
void gtk_menu_item_right_justify( GtkMenuItem *menu_item );

Если очень хочется можно элемент меню выровнять в меню баре по правому краю.
При нажатии на элемент меню вызывается событие activate .
Для создания сложных меню с минимальным вызовом функций существует фабрика элементов, но об этом в другой раз.
Пример кода из нашей программы:
Код:
/* Меню бар. */
menu_bar = gtk_menu_bar_new ();

/* Пакуем меню бар в коробку. И показываем его всем. */
gtk_box_pack_start (GTK_BOX (ver_box), menu_bar, FALSE, TRUE, 0);
gtk_widget_show (menu_bar);

/* Собственно меню "Файл". */
file_menu = gtk_menu_new();

/* Создаем элементы меню "Файл". */
open_item = gtk_menu_item_new_with_label ("Open");
save_item = gtk_menu_item_new_with_label ("Save");
save_as_item = gtk_menu_item_new_with_label ("Save as");
quit_item = gtk_menu_item_new_with_label ("Quit");

/* Пакуем элементы меню "Файл" в меню "Файл". */
gtk_menu_shell_append (GTK_MENU_SHELL (file_menu), open_item);
gtk_menu_shell_append (GTK_MENU_SHELL (file_menu), save_item);
gtk_menu_shell_append (GTK_MENU_SHELL (file_menu), save_as_item);
gtk_menu_shell_append (GTK_MENU_SHELL (file_menu), quit_item);

/* Подключаем к ним события. */
g_signal_connect (G_OBJECT (open_item), "activate",
G_CALLBACK (open_event), NULL);

g_signal_connect (G_OBJECT (save_item), "activate",
G_CALLBACK (save_event), NULL);

g_signal_connect (G_OBJECT (save_as_item), "activate",
G_CALLBACK (save_event),
(gpointer) "as");

g_signal_connect_swapped (G_OBJECT (quit_item), "activate",
G_CALLBACK (gtk_main_quit),
G_OBJECT(window));

/* Показываем элементы меню "Файл". */
gtk_widget_show(open_item);
gtk_widget_show(save_item);
gtk_widget_show(save_as_item);
gtk_widget_show(quit_item);

/* Создаем верхний элемент меню "Файл". */
file_item = gtk_menu_item_new_with_label ("File");
gtk_widget_show (file_item);

/* Подключаем к верхниму элементу меню. А его в меню бару. */
gtk_menu_item_set_submenu (GTK_MENU_ITEM (file_item), file_menu);
gtk_menu_bar_append (GTK_MENU_BAR(menu_bar), file_item);

Так же в программе мы использовали диалог открытия/создания(выбора) файла.

(КАРТИНКА)

Код:
GtkWidget *gtk_file_selection_new( const gchar *title );
Создает диалог выбора файла с заголовком  title.

Код:
void gtk_file_selection_set_filename(
GtkFileSelection *filesel,
const gchar *filename );
Устанавливаем название файла в диалоге например при нажатии "Сохранить как".

Код:
gchar *gtk_file_selection_get_filename( GtkFileSelection *filesel );
Берем из диалога имя файла.

Кроме того диалог содержит следующие элементы к которым тоже можно обращатся:
Код:
dir_list
file_list
selection_entry
selection_text
main_vbox
ok_button
cancel_button
help_button

Пример использования из программы:

Код:
GtkWidget *file_open;

file_open = gtk_file_selection_new ("Save file as...");

/* Подключаем обработчик нажатия кнопки "Ok" в диалоге. */
g_signal_connect (
G_OBJECT(GTK_FILE_SELECTION(file_open)->ok_button),
"clicked", G_CALLBACK(save_ok_button_event),
NULL);

g_signal_connect_swapped (
G_OBJECT (GTK_FILE_SELECTION(file_open)->ok_button),
"clicked", G_CALLBACK(gtk_widget_destroy),
G_OBJECT (file_open));

g_signal_connect_swapped (
G_OBJECT (GTK_FILE_SELECTION(file_open)->cancel_button),
"clicked", G_CALLBACK(gtk_widget_destroy),
G_OBJECT (file_open));

gtk_widget_show (file_open);

Ну и самое недокументированное это text_view. Это некоторый аналог RichEdit из Windows только возможностей у text_view гораздо больше. Опишу только, то, что сам делал. Вот небольшой кусок кода использованного при создании  text_view:

Код:
/* Создаем окно с прокруткой. */
scroll_window = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (
GTK_SCROLLED_WINDOW (scroll_window),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);

/* Создаем окно редактирования. */
text_view = gtk_text_view_new ();

/* Вытаскием из редактора буфер. */
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));

/* Создаем тэг для текста. Моноширный, жирный и крупный. */
gtk_text_buffer_create_tag (
buffer, "monospace_big_bold",
"family", "monospace",
"size", 20 * PANGO_SCALE,
"weight", PANGO_WEIGHT_BOLD,
NULL);

/* Засовываем текстовое окно в окно с прокруткой. */
gtk_container_add (GTK_CONTAINER (scroll_window), text_view);
gtk_widget_show (text_view);

Что проиходит в этом коде? Думаю здесь понятно:

Код:
scroll_window = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (
GTK_SCROLLED_WINDOW (scroll_window),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);

Создаем окно с прокруткой и устанавливаем ему политику для ползунков, а именно отображаться по необходимости. Далее создаем text_view и грабим с него буфер. А зачем? А затем, что мы работаем с текстовым буфером, а не с окном . К тому же его можно подсунуть другому text_view в этом случае они будут отображать состояние одного буфера. Делается вот так:
Код:
text_view2 = gtk_text_view_new (buffer);

Функция gtk_text_buffer_create_tag создает тэг для текста. Т.е. стиль отображения текста. Далее запихиваем text_view в окно с прокруткой.

С этим надеюсь понятно возникает вопрос, а как нам текст из файла запихать в редактор. Делаем это так
Код:
gtk_text_buffer_insert_with_tags_by_name (
buffer,&iter,cbuffer,rval,
"monospace_big_bold", NULL);
или так:
Код:
gtk_text_buffer_insert (buffer, &iter, cbuffer, rval);
Параметры:

  • buffer - буфер текста;
  • iter - итератор определяет позицию в тексте;
  • cbuffer - текст gchar *;
  • rval - количество символов для вставки, если -1, то вся строка. Должна заканчиваться нулём;
  • "monospace_big_bold" - название тега;
  • NULL - говорит, что тегов больше не будет.

В случае gtk_text_buffer_insert - используется тэг установленный для данной позиции.

Теперь возьмем текст из text_view:
Код:
gtk_text_buffer_get_bounds (buffer, &start, &end);
cbuffer = gtk_text_buffer_get_text (buffer, &start, &end, TRUE);
Первой строкой берем начало и конец буфера. Второй сохраняем текст буфера в gchar *. TRUE означает, что грабит скрытые символы.

И напоследок. Весь текст в text_view в UTF-8 вот так. Так, что перед записью не английских символов в буфер убедитесь, что они в UTF-8. Вот что получилось.

(КАРТИНКА)



Листинг CoolEdit.c:

Код:
#include <gtk/gtk.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>

GtkWidget *window; // Собственно окно.
GtkWidget *scroll_window; // Другое окно с прокруткой для редактора.
GtkWidget *ver_box; // Вертикальная коробка.
GtkWidget *text_view; // Наш виджет для текста.
GtkWidget *file_open; // Диалог открытия/сохранения файла.
GtkWidget *menu_bar; // И так ясно.

GtkWidget *file_menu; // Отсюда элементы меню.
GtkWidget *file_item;
GtkWidget *open_item;
GtkWidget *save_item;
GtkWidget *save_as_item;
GtkWidget *quit_item;

GtkWidget *help_menu;
GtkWidget *help_top_item;
GtkWidget *help_item;
GtkWidget *about_item; // Всё элементы меню кончились.

GtkTextBuffer *buffer; // Текстовый буфер для редактора текста
// его можно подсунуть еще кому нибудь
// и редактировать один буфер в двух и более окнах
// одновременно.
gchar *file_name = NULL; // Храним имя файла мало ли понадобится.
int workfile = -1; // Дескриптор открытого файла.

// Закрывает главное окно в принципе
void destroy(GtkWidget *widget, gpointer data)
{ // не нужна.
gtk_main_quit();
}

// Пишем всякую чушь по событию
void hello(GtkWidget *widget, gpointer data)
{
g_print ("%s was pressedn", (char *) data);
}

// Обратываем попытку
// Закрытия окна нажатием "крестика" в заголовке
gint delete_event( GtkWidget *widget, GdkEvent *event, gpointer data)
{
g_print ("delete event occurredn"); 
return FALSE;
}

/* Обрабатываем нажатие "Ok" в диалоге выбора файла для открытия. */
gint open_ok_button_event(
GtkWidget *widget,
GdkEvent *event,
gpointer data)
{
int rval;
void *cbuffer = calloc(512,sizeof(char));
GtkTextIter iter; // Итератор текстового буфера
pid_t child_pid;

if (file_name != NULL)  g_free(file_name);

/* Грабим имя файла из диалога. */
file_name = gtk_file_selection_get_filename (
GTK_FILE_SELECTION (file_open));

/* Сообщаем чего награбили. */
g_print ("Selected file %sn", file_name);

/* Открываем файл. */
workfile = open (file_name, O_RDWR);
if(workfile == -1) {
g_print ("Error open %sn", file_name);
return FALSE;
}

/* Устанавливаем итератор на начало текста. */
gtk_text_buffer_get_start_iter (buffer, &iter);

/* Читаем из файла пишем в буфер. */
while ((rval = read(workfile, cbuffer, 512)) != 0)
{
gtk_text_buffer_insert_with_tags_by_name (
buffer, &iter, cbuffer, rval,
"monospace_big_bold",
NULL);
}

free (cbuffer);
return FALSE;
}

/* Обрабатываем нажатие кнопки "Открыть" меню. */
gint open_event( GtkWidget *widget, GdkEvent *event, gpointer data)
{
g_print ("Open file widget call.n");

/* Открываем диалог. */
file_open = gtk_file_selection_new ("Open file...");

/* Подключаем обработчик нажатия кнопки "Ok" в диалоге. */
g_signal_connect (
G_OBJECT(GTK_FILE_SELECTION(file_open)->ok_button),
"clicked", G_CALLBACK(open_ok_button_event),
NULL);

g_signal_connect_swapped (
G_OBJECT (GTK_FILE_SELECTION(file_open)->ok_button),
"clicked", G_CALLBACK(gtk_widget_destroy),
G_OBJECT (file_open));

g_signal_connect_swapped (
G_OBJECT (GTK_FILE_SELECTION(file_open)->cancel_button),
"clicked", G_CALLBACK(gtk_widget_destroy),
G_OBJECT (file_open));

gtk_widget_show (file_open);

return FALSE;
}

/* Обрабатываем нажатие кнопки "Ok" в диалоге "Сохранить как". */
gint save_ok_button_event(
GtkWidget *widget,
GdkEvent *event,
gpointer data)
{
gchar *cbuffer;
GtkTextIter start, end;

if (file_name != NULL) g_free(file_name);

/* Грабим имя файла. */
file_name = gtk_file_selection_get_filename(
GTK_FILE_SELECTION (file_open));

/* Выводим чего награбили. */
g_print ("Selected file %sn", file_name);

/* Создаем файлик. */
workfile = open (file_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if(workfile == -1) {
g_print ("Error open %sn", file_name);
return FALSE;
}

/* Определяем начало и конец текста. Копируем текст в буффер. */
gtk_text_buffer_get_bounds (buffer, &start, &end);
cbuffer = gtk_text_buffer_get_text (buffer, &start, &end, TRUE);

/* Курсор на начало файла. Отрезаем лишнее. Сохраняем. */
lseek(workfile,0,0);
truncate(workfile, 0);
write(workfile, cbuffer, strlen(cbuffer));

/* Сколько на сохраняли. */
g_print("Writed %d bytes.n", strlen(cbuffer));

g_free (cbuffer);
return FALSE;
}

/* Обрабатываем нажатие "Сохранить" и "Сохранить как". */
gint save_event( GtkWidget *widget, GdkEvent *event, gpointer data)
{
gchar *cbuffer;
GtkTextIter start, end;

if ((workfile == -1) || ((char *) data)=="as") {
/* Открываем диалог. */
file_open = gtk_file_selection_new ("Save file as...");

/* Подключаем обработчик нажатия кнопки "Ok" в диалоге. */
g_signal_connect (
G_OBJECT(GTK_FILE_SELECTION(file_open)->ok_button),
"clicked", G_CALLBACK(save_ok_button_event),
NULL);

g_signal_connect_swapped (
G_OBJECT (GTK_FILE_SELECTION(file_open)->ok_button),
"clicked", G_CALLBACK(gtk_widget_destroy),
G_OBJECT (file_open));

g_signal_connect_swapped (
G_OBJECT (GTK_FILE_SELECTION(file_open)->cancel_button),
"clicked", G_CALLBACK(gtk_widget_destroy),
G_OBJECT (file_open));

gtk_widget_show (file_open);
return FALSE;
}

/* Определяем начало и конец текста. Копируем текст в буфер. */
gtk_text_buffer_get_bounds (buffer, &start, &end);
cbuffer = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);

/* Курсор на начало файла. Отрезаем лишнее. Сохраняем. */
lseek(workfile,0,0);
truncate(workfile, 0);
write(workfile, cbuffer,strlen(cbuffer));

/* Сколько на сохраняли. */
g_print("Writed %d bytes.n", strlen(cbuffer));

g_free (cbuffer);
return FALSE;
}

/* Создаем окно и основные виджеты. */
int make_main_window()
{
/* Создаем окно. Устанавливаем размер. */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW (window),400,400);

/* Устанавливаем заголовок окна. И бордюр. */
gtk_window_set_title (GTK_WINDOW (window),
"Out Cool Text Editor");

gtk_container_set_border_width (GTK_CONTAINER (window), 5);

/* Подключаем сигналы. */
g_signal_connect(G_OBJECT(window), "delete_event",
G_CALLBACK (delete_event), NULL);

g_signal_connect(G_OBJECT(window), "destroy",
G_CALLBACK (destroy), NULL);

/* Вертикальная коробка. */
ver_box = gtk_vbox_new(FALSE, 0);

/* Меню бар. */
menu_bar = gtk_menu_bar_new ();

/* Пакуем меню бар в коробку. И показываем его всем. */
gtk_box_pack_start (GTK_BOX (ver_box), menu_bar, FALSE, TRUE, 0);
gtk_widget_show (menu_bar);

/* Собственно меню "Файл". */
file_menu = gtk_menu_new();

/* Создаем элементы меню "Файл". */
open_item = gtk_menu_item_new_with_label ("Open");
save_item = gtk_menu_item_new_with_label ("Save");
save_as_item = gtk_menu_item_new_with_label ("Save as");
quit_item = gtk_menu_item_new_with_label ("Quit");

/* Пакуем элементы меню "Файл" в меню "Файл". */
gtk_menu_shell_append (GTK_MENU_SHELL (file_menu), open_item);
gtk_menu_shell_append (GTK_MENU_SHELL (file_menu), save_item);
gtk_menu_shell_append (GTK_MENU_SHELL (file_menu), save_as_item);
gtk_menu_shell_append (GTK_MENU_SHELL (file_menu), quit_item);

/* Подключаем к ним события. */
g_signal_connect (G_OBJECT (open_item), "activate",
G_CALLBACK (open_event), NULL);

g_signal_connect (G_OBJECT (save_item), "activate",
G_CALLBACK (save_event), NULL);

g_signal_connect (G_OBJECT (save_as_item), "activate",
G_CALLBACK (save_event), (gpointer) "as");

g_signal_connect_swapped (G_OBJECT (quit_item), "activate",
G_CALLBACK (gtk_main_quit), G_OBJECT(window));

/* Показываем элементы меню "Файл". */
gtk_widget_show(open_item);
gtk_widget_show(save_item);
gtk_widget_show(save_as_item);
gtk_widget_show(quit_item);

/* Создаем верхний элемент меню "Файл". */
file_item = gtk_menu_item_new_with_label ("File");
gtk_widget_show (file_item);

/* Подключаем к верхниму элементу меню. А его в меню бару. */
gtk_menu_item_set_submenu (GTK_MENU_ITEM (file_item), file_menu);
gtk_menu_bar_append (GTK_MENU_BAR(menu_bar), file_item);

/*Повторяем для меню "Помощь" теже действия,что и для меню "Файл"*/
help_menu = gtk_menu_new();

help_item = gtk_menu_item_new_with_label ("Help");
about_item = gtk_menu_item_new_with_label ("About");

gtk_menu_shell_append (GTK_MENU_SHELL (help_menu), help_item);
gtk_menu_shell_append (GTK_MENU_SHELL (help_menu), about_item);

g_signal_connect (G_OBJECT (help_item), "activate",
G_CALLBACK (hello), (gpointer) "Help Menu/Help");

g_signal_connect (G_OBJECT (about_item), "activate",
G_CALLBACK (hello), (gpointer) "Help Menu/About");

gtk_widget_show(help_item);
gtk_widget_show(about_item);

help_top_item = gtk_menu_item_new_with_label ("Help");
gtk_widget_show (help_top_item);

gtk_menu_item_set_submenu (
GTK_MENU_ITEM (help_top_item),
help_menu);

gtk_menu_bar_append (GTK_MENU_BAR(menu_bar), help_top_item);

/* Создаем окно с прокруткой. */
scroll_window = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (
GTK_SCROLLED_WINDOW (scroll_window),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);

/* Создаем окно редактирования. */
text_view = gtk_text_view_new ();

/* Вытаскием из редактора буфер. */
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));

/* Создаем тэг для текста. Моноширный, жирный и крупный. */
gtk_text_buffer_create_tag (buffer, "monospace_big_bold",
"family", "monospace",
"size", 20 * PANGO_SCALE,
"weight", PANGO_WEIGHT_BOLD,
NULL);

/* Засовываем текстовое окно в окно с прокруткой. */
gtk_container_add (GTK_CONTAINER (scroll_window), text_view);
gtk_widget_show (text_view);

/* Пакуем окно с прокруткой в коробку. */
gtk_box_pack_start (GTK_BOX (ver_box),scroll_window,TRUE,TRUE,0);
gtk_widget_show (scroll_window);

/* Коробку пакуем в главное окно. */
gtk_container_add(GTK_CONTAINER (window), ver_box);

/* Показываем, что наваяли. */
gtk_widget_show(ver_box);
gtk_widget_show(window);
return 0;
}

int main(int argc, char *argv[])
{
gtk_init (&argc, &argv);

make_main_window();

gtk_main();

return 0;
}

Для тех кто это дочитал до конца:
описание API
тутор GTK+ 2
среда разработки графических интерфейсов на GTK.

Снимок Glade:
(КАРТИНКА)

Версия для печати
Обсудить на форуме