Программирование графических приложений для Linux

Glade & Gtk

В последнее время операционная система Linux становится все более популярной среди пользователей. Для нее написано уже достаточно много программ, улучшен графический интерфейс. Конечно, сложность в конфигурировании еще существует, но это незначительное неудобство для пользователя, особенно для опытного. Ядро системы обновляется несколько раз в год, приложения пишутся сообществами пользователей, благодаря этому Linux очень быстро развивается. Что ж, и мы не будем стоять в сторонке, а попробуем научиться писать полноценные программы. Для этой цели создано множество инструментов, сегодня мы обойдемся Glade, библиотекой GTK, компилятором gcc и текстовым редактором, хотя вместо него я советую использовать Geany, во-первых, он выдает подсказки, во-вторых, основываясь на отчетах компилятора, подчеркивает ошибки и строки, для которых есть предупреждения, что очень удобно.

Glade — свободное приложение для визуального создания графических интерфейсов на основе GTK+. Описание визуально создаваемого разработчиком интерфейса сохраняется в файлах XML-формата GladeXML, которые затем могут быть подключены во время исполнения к программам с использованием библиотеки libglade. Таким образом, Glade можно применять для описания и создания графических интерфейсов в программах на различных языках.

Писать будем на языке СИ. Если у вас нет вышеперечисленных программ, то скачайте и установите (можно воспользоваться менеджером пакетов). Итак, сначала начнем разработку интерфейса программы, для этого откроем Glade. Нам сразу же предлагают задать параметра проекта.

Формат файла проекта – GtkBuilder. Требуемая версия библиотек – здесь можно выбрать из нескольких версий библиотек, но я рекомендую использовать самую последнюю. Теперь нужно создать главное окно программы, для этого слева на панели выбираем виджет «окно», но с добавлением других компонентов не спешите. Если вы программировали в среде Delphi или Visual Basic, то знаете, что компоненты там можно бросать на форму и произвольно перетаскивать с помощью мыши, здесь же дело обстоит несколько иначе. Для расположения компонентов используются так называемые контейнеры, существуют разные типы контейнеров, но все они похожи. Добавим на форму компонент «вертикальный контейнер», появится окно, в котором нужно выбрать «число элементов». Выбираем три.

Форма разделилась на три части, добавим в самую верхнюю контейнер «строка меню». В среднюю — виджет «область текста», и в оставшуюся — виджет «строка состояния». У нас получилась форма, напоминающая «текстовый редактор». Да, сегодня мы с вами напишем простой блокнот, почти такой же, как под Windows. Выделим главное окно и впишем в свойство «заголовок окна» - «Блокнот». Также посмотрите другие свойства, поэкспериментируйте с ними. В контейнере «строка меню» удалите заголовок «Вид», для этого просто выделите его и нажмите клавишу «delete». Для виджета «область текста» создадим текстовый буфер. Для этого в свойствах найдем «буфер», после создания назначим ему имя «buffer». Также нам нужен диалог «о программе», выбираем его на панели виджетов среди окон и добавляем в проект. Редактируем нужные нам свойства, там все интуитивно понятно.

Сохраняем как GuiNotepad.xml, расширение обязательно должно быть .xml, а не .glade. Вот что получилось у меня.

Пришло время взглянуть на нашу программу в откомпилированном виде. Создаем текстовый файл с расширением C, в том же каталоге, что и файл Glade. И пишем туда следующий код:

Листинг: код программы.

#include // Подключаем gtk

/*Виджеты программы*/
GtkTextBuffer *buffer; // Текстовый буфер
GtkBuilder *builder; // GtkBuilder объект
GtkWidget *textview; // Область текста
GtkWidget *mainwindow; // Главное окно
GtkWidget *aboutdialog; // Диалог "о программе"
GtkWidget *menubar; // Строка меню
GtkClipboard *cb; // Буфер обмена

int main (int argc, char **argv)
{
gtk_init(&argc, &argv); // инициализируем gtk
builder = gtk_builder_new (); // создаем новый GtkBuilder объект
/*загружаем описание интерфейса из XML файла*/

gtk_builder_add_from_file(builder, "GuiNotepad.xml", NULL);
/* связываем наше окно с окном из файла */
mainwindow = GTK_WIDGET(gtk_builder_get_object(builder, "window1"));
/*Остальные виджеты тоже связываем*/
aboutdialog = GTK_WIDGET(gtk_builder_get_object(builder, "aboutdialog1"));
textview = GTK_WIDGET(gtk_builder_get_object(builder, "textview1"));
menubar = GTK_WIDGET(gtk_builder_get_object(builder, "menubar1"));
buffer = GTK_TEXT_BUFFER(gtk_builder_get_object(builder, "buffer"));

/*Включаем обработку сигналов*/
gtk_builder_connect_signals (builder, NULL);
gtk_widget_show_all (mainwindow); //Показываем форму и виджеты на ней
gtk_main(); // запускаем главный цикл приложения
return 0;
}

/*Обработчики событий*/
/*событие назначено по умолчанию*/

void on_mainwindow_destroy (GtkObject *object, gpointer user_data)
{ //События для кнопки «закрыть»
gtk_main_quit ();
}

Теперь взглянем на плоды наших трудов, для этого откомпилируем код и запустим приложение.
$ gcc notepad.c -o notepad -export-dynamic `pkg-config --cflags --libs gtk+-2.0`

Если вы меня послушались и вместо текстового редактора используете Geany, то прежде чем компилировать, зайдите в меню «сборка/параметры сборки» и измените поле «собрать» на следующее:

gcc -Wall -o "%e" "%f" -export-dynamic `pkg-config --cflags --libs gtk+-2.0`

Все это, конечно, очень замечательно, но толку от такого приложения? Надо его доделать, чтобы кнопки реагировали на определенные события и выполняли определенные действия. «Оживим» наши кнопки, заставим их работать. Возвращаемся в Glade и назначаем кнопкам события. Начнем с кнопки «Создать»: выбираем ее и в свойствах переходим на вкладку «сигналы». В GtkMenuItem находим сигнал activate, в обработчике выбираем on_imagemenuitem1_activate, в «После» ставим галочку. А в наш текстовый файл, в котором хранится код, дописываем (в самый конец):

void on_imagemenuitem1_activate (GtkObject *object, gpointer user_data)
{ //Создать
/*Название окна – «Блокнот»*/
gtk_window_set_title((GtkWindow *)mainwindow, (gchar*)"Блокнот"); gtk_text_buffer_set_text (buffer, "\0", -1); //Очищаем
текстовый буфер
}

Функция gtk_window_set_title устанавливает заголовок окна, для этого ей нужно передать указатель типа GtkWindow на само окно и указатель типа gchar на строку, которая станет его заголовком. При передаче параметров функции мы преобразовываем типы (GtkWindow *) и (gchar*). Это делать не обязательно, и без преобразования все прекрасно работает, но мы ведь с вами приличные программисты, потому будем соблюдать правила хорошего тона. Функция gtk_text_buffer_set_text присваивает «текстовому буферу» определенный текст, для этого ей нужно передать указатель на буфер; на сам текст в данном случае мы передаем "\0" – ноль байт, что эквивалентно очистке текстового поля. И последним параметром служит длина передаваемой строки, ставьте -1 для неопределенной длины.

Следующая кнопка – «Открыть», точно так же, как и для кнопки «Создать», назначаем ей обработчик события и дописываем такой код:

Листинг: обработчик для кнопки «Открыть»

void on_imagemenuitem2_activate (GtkObject *object, gpointer user_data)
{
//Открыть файл
gchar *result; //Путь к открываемому файлу
GtkWidget *filedlg; //Новый виджет, «диалог выбора файлов»
/*Создаем «диалог открытия файлов»*/
filedlg = gtk_file_chooser_dialog_new("Выбор файла", (GtkWindow*)mainwindow,
GTK_FILE_CHOOSER_ACTION_OPEN,GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN,
GTK_RESPONSE_ACCEPT, NULL);
/*Если нажата клавиша «Открыть», то… */
if (gtk_dialog_run (GTK_DIALOG(filedlg)) == GTK_RESPONSE_ACCEPT)
{//Сохраняем путь к файлу в переменную
result = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (filedlg));
gtk_widget_destroy(filedlg); //Уничтожаем виджет диалог
gtk_window_set_title((GtkWindow *)mainwindow, result); //Меняем название окна

/*Вызываем функцию открытия файла*/
OpenFile(result);
}
else
gtk_widget_destroy(filedlg); //Уничтожаем окно
}

Весь предыдущий код нужен лишь для того, чтобы вызвать и создать окно выбора файлов, которое реализовано объектом GtkFileChooserDialog, и получить путь до файла. Я специально не описывал этот виджет в glade, потому что такая реализация более удачная, виджет создается «на лету». Функция gtk_file_chooser_dialog_new создает новый диалог, в качестве первого параметра ей передается заголовок окна, второй параметр указывает на родительский объект, в нашем случае это mainwindow. Третий параметр определяет, какое окно создавать (для открытия или для сохранения), остальные параметры отвечают за кнопки, которые будет расположены на окне.
Функция gtk_dialog_run делает окно видимым и реагирует на нажатие кнопок, как параметр ей нужно передать ранее созданный диалог типа GtkDialog. Функция GTK_DIALOG нужна для преобразования типа. Кстати говоря, мы могли использовать и стандартное C-ишное преобразование (GtkDialog*). Если пользователь нажмет кнопку «открыть», функция gtk_dialog_run вернет значение GTK_RESPONSE_ACCEPT. Тогда мы должны определить имя и путь к выбранному файлу, для этого и существует функция gtk_file_chooser_get_filename, она возвращает значение типа gchar, в котором содержатся нужные нам данные, а как параметр ей передается созданный ранее диалог, причем мы должны преобразовать его тип в GtkFileChooser. Далее мы уничтожаем диалог за его ненадобностью.

Вышеприведенный код самостоятельно не открывает файл, функцию OpenFile мы должны написать сами, именно ей мы и будем передавать путь к файлу:

Листинг: Наша процедура, которая открывает файл

void OpenFile(char *filename)
{
GtkTextIter iter; FILE *f;
gint i=0; gchar line[256];

f=fopen(filename,"r"); //открываем файл

while(!feof(f))
{
fgets(line,256,f);
gtk_text_buffer_get_iter_at_line(buffer,&iter,i); //итер на строку с номером i
gtk_text_buffer_insert(buffer, &iter, line, -1); //вставляем строку в позицию итера
i++; //увеличиваем счетчик строк
}
fclose(f);
}

Эту функцию нужно глобально объявить void OpenFile(char *filename); и добавить дополнительные библиотеки для ее корректной работы: stdio.h, stdlib.h, string.h. После того как fgets скопирует строку в переменную line, мы вызываем функцию gtk_text_buffer_get_iter_at_line, она устанавливает позицию для вставки текста на строку с номером i (при первом проходе цикла это 0). А первые два параметра — это наше текстовое поле (буфер) и итер (так называются адреса в буфере; в данном случае мы будем устанавливать адрес на i-тую строку). Затем мы непосредственно вставляем текст функцией gtk_text_buffer_insert, первый параметр которой мы уже знаем, второй — это адрес (итер), начиная с которого мы будем вставлять текст (его мы установили предыдущей функцией), третий - строка для вставки, последний — длина строки (-1 для неопределенной длины). Код для кнопки «Сохранить как» очень похож на код кнопки «Открыть», потому мы пока перепрыгнем кнопку «Сохранить», заранее объявим и создадим функцию SaveFile, void SaveFile(char *filename).

Листинг: Наша процедура, сохранение файла

void SaveFile(char *filename)
{
FILE *f;
gchar *save_text;
GtkTextIter start,end;
gtk_text_buffer_get_start_iter(buffer, &start); //начальный итер
gtk_text_buffer_get_end_iter(buffer, &end); //конечный итер
save_text=gtk_text_buffer_get_text (buffer,&start,&end, FALSE); //Берем текст из буфера
f=fopen(filename,"w"); //открываем файл
fprintf(f,"%s",save_text);
fclose(f);
}

Функция gtk_text_buffer_get_start_iter устанавливает итер в начало буфера, а функция gtk_text_buffer_get_end_iter — в конец, для этого в обоих случаях функциям нужно передать указатель на буфер и указатель на заранее объявленные итеры. Функция gtk_text_buffer_get_text возвращает текст из буфера, при этом ей передается указатель на буфер, на итеры, между которыми расположен забираемый текст и переменная или значение булева типа (TRUE, FALSE), оно говорит о том, отображать в буфере скрытые символы или нет. Процедура для сохранения в файл готова, теперь напишем обработчик для кнопки «Сохранить как», вызывающий эту самую функцию. Он практически идентичен обработчику для кнопки «Открыть», потому я не буду на нем подробно останавливаться.

Листинг: обработчик для кнопки «Сохранить как»

void on_imagemenuitem4_activate(GtkObject *object, gpointer user_data)
{
//Сохранить как...
gchar *result; //Путь к открываемому файлу
GtkWidget *filedlg; //Новый виджет, «диалог выбора файлов»
/*Создаем «диалог сохранения файлов»*/
filedlg = gtk_file_chooser_dialog_new("Сохранить как...", (GtkWindow*)mainwindow,
GTK_FILE_CHOOSER_ACTION_SAVE,GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
/*Если нажата клавиша «Сохранить как», то… */
if (gtk_dialog_run (GTK_DIALOG(filedlg)) == GTK_RESPONSE_ACCEPT)
{{//Сохраняем путь к файлу в переменную
result = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filedlg));
gtk_widget_destroy(filedlg); //Уничтожаем окно
gtk_window_set_title((GtkWindow *)mainwindow, result); //Меняем название окна
/*Вызываем функцию открытия файла*/
SaveFile(result);
}
else
gtk_widget_destroy(filedlg); //Уничтожаем окно
}

Теперь вернемся к кнопке «Сохранить», ее обработчик очень прост.

void on_imagemenuitem3_activate(GtkObject *object, gpointer user_data)
{ //Сохранить
if (strcmp(gtk_window_get_title((GtkWindow *)mainwindow),(gchar*)"Блокнот"))
//Просто вызываем процедуру сохранения
SaveFile((gchar *)gtk_window_get_title((GtkWindow *)mainwindow));

Else //Вызываем процедуру «Сохранить как»
on_imagemenuitem4_activate((GtkObject*)mainwindow,"");
}

Функция strcmp сравнивает две строки; если строки равны, возвращает ноль. В примере заголовок окна сравнивается со словом «Блокнот», и если заголовок равен этому слову, то вызывается процедура кнопки «Сохранить как» (так мы определяем, что файл еще ни разу не сохраняли и не открывали с диска). Если заголовок не равен слову «Блокнот», тогда мы берем заголовок окна (в нем содержится путь к сохраненному или открытому ранее файлу, см. код выше) и отправляем функции SaveFile. Функция gtk_window_get_title(); возвращает название текущего окна. Остальное, я думаю, и так понятно. Код для кнопки «Выход», очень просто и вызывает всего одну функцию.

void on_imagemenuitem5_activate (GtkObject object, gpointer user_data)
{ // Закрываем программу
gtk_main_quit ();
}

Пора заняться вторым меню. Начнем по порядку, с кнопки «Вырезать»:

void on_imagemenuitem6_activate(GtkObject *object, gpointer user_data)
{ //Вырезать
gchar *atom = gdk_atom_name((GdkAtom)"CLIPBOARD");
Clipboard = gtk_clipboard_get((GdkAtom)atom);
g_free(atom);
gtk_text_buffer_cut_clipboard(buffer, Clipboard , TRUE);
}

Мы получаем указатель на строку с именем буфера обмена с помощью функции gdk_atom_name. Затем вызываем gtk_clipboard_get и передаем этой функции указатель на полученную ранее строку буфера обмена. Функция gtk_text_buffer_cut_clipboard предназначена для операции «Вырезать», первым параметром ей передается «буфер», вторым — буфер обмена, а третьим — значение типа boolean, которое отвечает за редактируемость буфера.

void on_imagemenuitem7_activate (GtkObject *object, gpointer user_data)
{ //Копировать
gchar * atom = gdk_atom_name((GdkAtom)"CLIPBOARD");
Clipboard = gtk_clipboard_get((GdkAtom)atom);
g_free(atom);
gtk_text_buffer_copy_clipboard(buffer, Clipboard );
}

gtk_text_buffer_copy_clipboard позволяет скопировать текст в буфер обмена.

void on_imagemenuitem8_activate(GtkObject *object, gpointer user_data)
{ //Вставить
gchar * atom = gdk_atom_name((GdkAtom)"CLIPBOARD");
Clipboard = gtk_clipboard_get((GdkAtom)atom);
g_free(atom);
gtk_text_buffer_paste_clipboard(buffer, Clipboard , NULL, TRUE);
}

Функция gtk_text_buffer_paste_clipboard вставляет содержимое буфера обмена в точку вставки, третий параметр отвечает за точку вставки: ставьте NULL, если хотите, чтобы текст был вставлен в позицию курсора. И последний параметр отвечает за редактируемость буфера по умолчанию.

void on_imagemenuitem9_activate(GtkObject *object, gpointer user_data)
{ //Удалить
gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
}

Ну и как можно догадаться, функция gtk_text_buffer_delete_selection удаляет выделенный текст из буфера. В качестве первого параметра она получает сам буфер, в качестве второго — значение булева типа, которое говорит, вызвано ли удаление пользователем. Последний параметр говорит о том, можно ли редактировать буфер по умолчанию, если нельзя, то пользователь не сможет удалить текст. Также функция возвращает значение true или false, по нему можно определить, было ли выделенное для удаления поле пустым.

Осталось только отобразить окно диалога «О программе». Это проще, чем переслать два байта:

void on_imagemenuitem10_activate (GtkObject *object, gpointer user_data)
{ //О программе
gtk_widget_show (aboutdialog);
g_signal_connect_swapped (aboutdialog,"response",
G_CALLBACK (gtk_widget_destroy),
aboutdialog);
}

Функция gtk_widget_show предназначена для отображения переданного ей виджета, именно она показывает на экране наш диалог. Но нужно еще и обрабатывать сигналы, поступающие от кнопок. Функция g_signal_connect_swapped призвана помочь нам. Первым параметром она получает объект, от которого исходит сигнал. Вторым — описание сигнала, третьим мы связываем сигнал с фактическим действием, в данном случае уничтожаем объект. Четвертым параметром идет объект, на который мы влияем, то есть в данном случае объект, который мы уничтожим. Как вы могли догадаться, это обработка от кнопки закрыть на виджете. Осталось только откомпилировать наш проект и, запустив наконец, порадоваться проделанной работе.

Библиотека Gtk очень мощный зверь (но не единственный), для изучения которого нужна не одна неделя, и сегодняшний обзор лишь маленькая толика, не способная полностью открыть перед нами завесу, за которой, возможно, скрывается истина. Множество приложений, написанных для Linux, используют эту библиотеку. Надеюсь, все усвоили принципы работы с gtk. Главное — понять, какие функции и в каком порядке нужно использовать, здесь буквально все программирование строится на использовании функций gtk. Потому совместно с ней можно использовать множество языков программирования, а не только C, так что дерзайте, пробуйте, экспериментируйте.

Kerny SASecurity gr.


Компьютерная газета. Статья была опубликована в номере 05 за 2010 год в рубрике программирование

©1997-2024 Компьютерная газета