• Введение в систему X
  • X-сервер
  • X-клиент
  • X-протокол
  • Xlib
  • Комплекты инструментов
  • Оконные менеджеры
  • Другие способы создания GUI — платформно-независимые оконные API
  • Введение в GTK+
  • Система типов GLib
  • Система объектов GTK+
  • Знакомство с GNOME
  • Установка библиотек разработки GNOME/GTK+
  • События, сигналы и обратные вызовы
  • Виджеты упаковочных контейнеров
  • Виджеты GTK+
  • GtkWindow
  • GtkEntry
  • GtkSpinButton
  • GtkButton
  • GtkTreeView
  • Виджеты GNOME
  • Меню GNOME
  • Диалоговые окна
  • GtkDialog
  • Модальное диалоговое окно
  • Немодальные диалоговые окна
  • GtkMessageDialog
  • Приложение для работы с базой данных компакт-дисков
  • Резюме
  • Глава 16

    Программирование в GNOME с помощью GTK+

    До сих пор в этой книге мы обсуждали основные методы программирования в ОС Linux, касающиеся сложной внутренней начинки. Теперь же пора вдохнуть жизнь в наши приложения и узнать, как включить в них графический пользовательский интерфейс (Graphical User Interface, GUI). В этой главе и в главе 17 мы собираемся рассмотреть две самые популярные библиотеки GUI для ОС Linux: GTK+ и KDE/Qt. Эти библиотеки соответствуют двум популярнейшим интегрированным средам рабочего стола Linux: GNOME (GTK+) и KDE.

    Все библиотеки GUI в Linux размещены поверх низкоуровневой оконной системы, называемой X Window System (чаще X11 или просто X), поэтому, прежде чем вдаваться в подробности среды GNOME/GTK+, мы приведем обзор основных принципов работы системы X и поможем понять, как различные слои оконной системы пригоняются один к другому для создания того, что мы называем рабочим столом.

    В этой главе обсуждаются следующие темы:

    □ система X Window System;

    □ введение в среду GNOME/GTK+;

    □ виджеты или интерфейсные элементы окна GTK+;

    □ виджеты и меню среды GNOME;

    □ диалоговые окна;

    □ GUI базы данных компакт-дисков с использованием GNOME/GTK+.

    Введение в систему X

    Если вы когда-либо применяли оконную систему рабочего стола в ОС Linux, скорее всего вы использовали графическую систему X с открытым программным кодом. Одна из наиболее передовых и в результате разочаровывающих характеристик X — жесткая привязка к идеологии "инструментов, а не политики". Это означает, что в системе X нет определения какого-либо пользовательского интерфейса, но есть средства для его создания. Вы вольны создавать целиком собственную среду рабочего стола, экспериментируя и вводя новшества при желании. Но это же свойство долгое время тормозило разработку пользовательских интерфейсов в системах Linux и UNIX. Для заполнения этой пустоты возникли два проекта рабочего стола, предпочитаемые пользователями Linux: GNOME и KDE. Рабочий стол ОС Linux, тем не менее, не ограничивается системой X. В действительности рабочий стол в Linux — это довольно расплывчатая субстанция без определенной версии, выпущенной в рамках одного проекта или какой-либо группой специалистов. Современная установка содержит мириады библиотек, утилит и приложений, которые все вместе называются "рабочим столом".

    У системы X, первоначально разработанной в MIT (Массачусетский технологический институт) в начале 1980 гг., длинная и яркая история. Она создавалась как унифицированная оконная система для высокопроизводительных рабочих станций того времени, которые были очень дорогими перемалывающими огромные объемы чисел чудовищами.

    Когда наступили 1990 гг. и цены на оборудование упали, энтузиасты перенесли систему X на недорогие домашние компьютеры PC, этот проект стал называться XFree86 (процессоры PC, выпускавшиеся корпорацией Intel и другими компаниями, были известны как процессоры x86), и сегодня вместе с системой Linux распространяются потомки проекта XFree86, а в большинстве дистрибутивов Linux применяется вариант системы X, названный X.Org,

    X Window System разделена на компоненты аппаратного и программного уровней, называемые Х-сервером и Х-клиентом. Эти компоненты взаимодействуют с помощью протокола с легко угадываемым названием "X-протокол". В следующих разделах рассматривается по очереди каждый из этих компонентов.

    X-сервер

    X-сервер запускается на пользовательской локальной машине и выполняет низкоуровневые операции прорисовки графического экрана. Присутствие в названии слова "сервер" часто смущает: X-сервер выполняется на вашем настольном ПК. X-клиенты могут запускаться на вашем настольном ПК или на самом деле выполняться на других компьютерах в вашей сети, включая серверы. Если подумать, обратная терминология не лишена смысла, но часто кажется применяемой задом наперед.

    Поскольку X-сервер напрямую общается с видеокартой, вы должны применять X-сервер, соответствующий вашей видеокарте, и для него следует задавать подходящее разрешение, скорость обновления экрана, количество цветов и т.д. Файл конфигурации называется xorg.conf или Xfree86Config. В прошлом вы обычно должны были вручную редактировать файл конфигурации, чтобы добиться корректной работы системы X. К счастью, современные дистрибутивы Linux автоматически определяют нужные установочные параметры, экономя время пользователя и избавляя его от решения головоломок!

    X-сервер ждет ввод пользователя от мыши и клавиатуры и передает нажатия клавиш и щелчки кнопками мыши приложениям, X-клиентам. Эти сообщения называют событиями; они служат основными элементами программирования GUI. Позже в этой главе мы подробно рассмотрим события и их логическое расширение GTK+ сигналы.

    X-клиент

    X-клиент — это любая программа, использующая X Window System как GUI. Примерами могут служить xterm, xcalc и более сложные, приложения, например, Abiword. X-клиент ждет события пользователя, посылаемые X-сервером, и отвечает на них отправкой обратно серверу сообщений об обновлении изображений.

    Примечание

    X-клиент необязательно должен быть на той же машине, что и X-сервер.

    X-протокол

    X-клиент и X-сервер взаимодействуют с помощью X-протокола, который позволяет клиенту и серверу быть разделенными сетью. Например, вы можете запустить приложение X-клиент с удаленного компьютера через Интернет или шифруемую виртуальную частную сеть (Virtual Private Network, VPN). В большинстве персональных систем Linux X-клиенты и X-сервер работают в одной и той же системе.

    Xlib

    Xlib — это библиотека, неявно используемая X-клиентом для генерации сообщений X-протокола. Она предоставляет API очень низкого уровня, позволяющий клиенту отображать простейшие элементы на X-сервере и откликаться на простейший ввод. Мы должны подчеркнуть, что Xlib — это библиотека очень низкого уровня, и создание с ее применением даже чего-либо столь же простого, как меню, — невероятно трудоемкий процесс, требующий сотен строк программного кода.

    Разработчик GUI не может эффективно программировать непосредственно с помощью Xlib. Вам нужен API, делающий легким и простым создание таких элементов GUI, как меню, кнопки и раскрывающиеся списки. Говоря кратко, эту роль играет комплект инструментальных средств или элементов интерфейса.

    Комплекты инструментов

    Комплект инструментов или элементов интерфейса — это библиотека GUI, которую X-клиенты применяют для значительного упрощения создания окон, меню, кнопок и т.п. С помощью комплекта инструментальных средств вы можете создавать кнопки, меню, фреймы и тому подобное с помощью вызовов одной функции. Общий термин для обозначения элементов GUI, подобных перечисленным, — виджеты, универсальный элемент, который вы найдете во всех современных библиотеках GUI.

    Существует масса комплектов инструментов для системы X, из которых вы можете выбирать, и каждый из них обладает определенными достоинствами и недостатками. На каком остановиться — важное проектное решение для вашего приложения, и при выборе следует учитывать следующие факторы.

    □ Для кого предназначено ваше приложение?

    □ Будут ли установлены библиотеки комплекта инструментов у ваших пользователей?

    □ Перенесен ли комплект инструментов в другие популярные операционные системы?

    □ Какой лицензией программного обеспечения пользуется комплект инструментов и согласуется ли она с предполагаемым вами использованием?

    □ Поддерживает ли комплект инструментов ваш язык программирования?

    □ Современный ли внешний вид и реализация у комплекта инструментов?

    На протяжении многих лет самыми популярными комплектами инструментальных средств были Motif, OpenLook и Xt, но они уступили технически более совершенным комплектам GTK+ и Qt, формирующим основу рабочих столов GNOME и KDE соответственно.

    Оконные менеджеры

    Последний компонент X-мозаики — оконный менеджер или диспетчер, который отвечает за расположение окон на экране. Оконные менеджеры часто поддерживают отдельные "рабочие области", на которые делится рабочий стол, увеличивая область экрана, с которой вы можете взаимодействовать. Оконный менеджер также отвечает за графическое оформление всех окон, состоящее, как правило, из рамки и полосы заголовка с пиктограммами максимизации, минимизации и закрытия окна. Оконные менеджеры обеспечивают частично внешний вид рабочего стола, например заголовки окон.

    К широко распространенным относятся следующие оконные менеджеры:

    □ Metacity — оконный менеджер, используемый по умолчанию для рабочего стола GNOME;

    □ KWin — оконный менеджер, применяемый по умолчанию для рабочего стола KDE;

    □ Openbox — разработанный для экономии ресурсов и запускаемый на более старых и медленных системах;

    □ Enlightenment — оконный менеджер, отображающий превосходную графику и спецэффекты.

    Как и все в системе X, оконные менеджеры можно переключать. Тем не менее, большинство пользователей запускает оконный менеджер, входящий в их поставку среды рабочего стола.

    Другие способы создания GUI — платформно-независимые оконные API

    Следует упомянуть и другие способы создания GUI, характерные для ОС Linux, — существуют языки с собственной поддержкой GUI, функционирующей под управлением Linux.

    □ Язык Java поддерживает программирование GUI с помощью Swing и более старых API AWT. Внешнее оформление GUI на языке Java понравится не всем и на более старых машинах интерфейс может восприниматься как громоздкий и медленно реагирующий. Огромное преимущество Java заключается в том, что единожды откомпилированный код на языке Java выполняется неизменным да любой платформе с виртуальной машиной Java (Java Virtual Machine), которая включает Linux, Windows, Mac OS и мобильные устройства. Дополнительную информацию см. на Web-сайте http://java.sun.com.

    □ Язык программирования C# очень похож на язык Java. В систему Linux общеязыковая исполняющая среда (C# Common Language или CLR) пришла из проекта Mono, см. Web-сайт http://www.mono-project.com. C# на платформе Mono поддерживает модель программирования Windows Forms, применяемую в Windows, и специальную привязку к комплекту инструментов GTK+, названную Gtk#.

    □ Tcl/Tk — язык сценариев, отлично подходящий для быстрой разработки интерфейсов GUI и работающий с X, Windows и Mac OS. Он очень удобен для быстрого макетирования или маленьких утилит, нуждающихся в простоте и удобстве сопровождения сценария. Все подробности можно найти на Web-сайте http://tcl.tk.

    □ Python — тоже язык сценариев. Вы можете применять Tk, часть Tcl/Tk, из Python или программировать в привязке Python к GTK+, разрабатывая программы GTK+ на языке Python. Дополнительную информацию о языке Python см. на Web-сайте http://www.python.org.

    □ Perl — еще один популярный язык сценариев в Linux. Вы можете применять Tk, часть Tcl/Tk, в языке Perl как Perl/Tk. Дополнительную информацию о Perl см. на Web-сайте http://www.perl.org/.

    За платформою независимость, которую приносят эти языки, приходится платить. Совместное использование данных их собственными приложениями — например, применение операции перетаскивания мышью (drag and drop) — затруднено, и сохранение конфигурации обычно следует выполнять специфическим, а не стандартным способом, характерным для рабочего стола. Иногда поставщики программного обеспечения на языке Java хитрят, включая в поставку платформно-зависимые расширения для того, чтобы избежать подобных проблем.

    Введение в GTK+

    Теперь, когда вы познакомились с системой X Window System, самое время рассмотреть комплект инструментальных средств GTK+ Toolkit. GTK+ появился на свет как часть популярного графического редактора GNU Image Manipulation Program (GIMP), от которого он и унаследовал свое имя (The Gimp ToolKit). Очевидно, что программисты GIMP всерьез предвидели превращение GTK+ в самостоятельный проект, поскольку он вырос и стал одним из самых мощных и популярных комплектов инструментов. Домашнюю страницу проекта GTK+ можно найти по адресу http://www.gtk.org.

    Примечание

    В итоге, GTK+ — это библиотека, которая существенно упрощает создание графических интерфейсов пользователей (Graphical User Interface, GUI), предоставляя набор готовых компонентов, именуемых виджетами, которые вы соединяете вместе с помощью легких в использовании вызовов функций, включенных в логическую структуру вашего приложения.

    Несмотря на то, что GTK+ — это проект GNU, как и GIMP, он выпущен на условиях более либеральной лицензии (Lesser General Public License, Стандартная общественная лицензия ограниченного применения GNU), которая освобождает программное обеспечение (включая патентованное программное обеспечение с закрытым программным кодом), написанное с использованием GTK+, от уплаты лицензионных вознаграждений или авторских гонораров, а также других ограничений. Свобода, предлагаемая лицензией GTK+, отличает этот комплект инструментов от его конкурента Qt (который будет обсуждаться в следующей главе), чья лицензия GPL запрещает разработку коммерческого программного обеспечения с использованием Qt (в этом случае вы должны купить коммерческую лицензию для Qt).

    Комплект GTK+ целиком написан на языке С и большая часть программного обеспечения GTK+ также написана на С. К счастью, существует ряд привязок к языкам (language binding), позволяющих применять GTK+ в предпочитаемом вами языке программирования, будь то С++, Python, PHP, Ruby, Perl, C# или Java.

    Комплект GTK+ сформирован как надстройка для ряда других библиотек, К ним относятся следующие:

    □ GLib — предоставляет низкоуровневые структуры данных, типы, поддержку потоков, циклов событий и динамической загрузки;

    □ GObject — реализует объектно-ориентированную систему на языке С, не требующую применения языка С++;

    □ Pango — поддерживает визуализацию и форматирование текста;

    □ ATK — помогает создавать приложения с доступом и позволяет пользователям запускать ваши приложения с помощью средств чтения экрана и других средств доступа;

    □ GDK (GIMP Drawing Kit) — обрабатывает визуализацию низкоуровневой графики поверх библиотеки Xlib;

    □ GdkPixbuf — помогает манипулировать изображениями в программах GTK+;

    □ Xlib — предоставляет низкоуровневую графику в системах Linux и UNIX.

    Система типов GLib

    Если вы когда-нибудь просматривали программный код GTK+, то могли удивиться, увидев множество типов данных языка С с префиксом

    g
    , например,
    gint
    ,
    gchar
    ,
    gshort
    , а также незнакомые типы
    gint32
    и
    gpointer
    . Дело в том, что комплект GTK+ основан на библиотеках переносимости языка С (portability libraries), названных GLib и GObject, которые определяют эти типы для того, чтобы способствовать межплатформным разработкам.

    GLib и GObject помогают межплатформным разработкам, обеспечивая стандартный набор типов данных замещения, функций и макросов для поддержки управления памятью и общих задач. Эти типы, функции и макросы означают, что, как программисты GTK+, мы можем быть уверены в том, что наш программный код надежно переносится на другие платформы и архитектуры.

    В библиотеке Glib также определено несколько очень удобных констант:

    #include <glib/gmacros.h>

    #define FALSE 0

    #define TRUE !FALSE

    Дополнительные типы данных — это типы, служащие заменой для стандартных типов данных C (из соображений совместимости и читабельности) и гарантирующие одинаковый размер в байтах на. всех платформах:

    □ 

    gint
    ,
    guint
    ,
    gchar
    ,
    guchar
    ,
    glong
    ,
    gulong
    ,
    gfloat
    и
    gdouble 
    — просто замены для стандартных типов С для совместимости;

    □ 

    gpointer
    — синоним типа (
    void*
    );

    □ 

    gboolean
    — полезен для представления логических значений и служит оболочкой для
    int
    ;

    □ 

    gint8
    ,
    guint8
    ,
    gint16
    ,
    guint16
    ,
    gint32
    и
    guint32
    — знаковые и беззнаковые типы с гарантированным размером в байтах.

    Удобно то, что применение библиотек GLib и GObject почти прозрачно. Glib широко используется в GTK+, поэтому если у вас есть работающая установка GTK+, то вы обнаружите, что библиотека Glib уже установлена. Как вы увидите позже в этой главе, при программировании с помощью комплекта GTK+ вам даже не придется явно включать заголовочный файл glib.h.

    Система объектов GTK+

    Все, у кого уже есть опыт программирования GUI, возможно, поймут наше утверждение о строгой приверженности библиотек GUI концепции объектно-ориентированного программирования (ООП), настолько строгой, что все современные комплекты инструментов, включая GTK+, написаны в стиле объектно-ориентированного программирования.

    Несмотря на то, что комплект инструментов GTK+ написан на чистом С, он поддерживает объекты и ООП благодаря библиотеке GObject. Эта библиотека поддерживает наследование объектов и полиморфизм с помощью макросов.

    Давайте рассмотрим образец наследования и полиморфизма на примере иерархии объектов GtkWindow, взятой из документации GTK+ API.

    GObject

     +---GInitiallyUnowned

     +----GtkObject

           +----GtkWidget

                 +----GtkContainer

                       +----GtkBin

                             +----GtkWindow

    Этот список объектов говорит о том, что объект

    GtkWindow
    — потомок
    GtkBin
    , и, следовательно, любую функцию, которую вы вызываете с объектом
    GtkBin
    , вы можете вызвать и с объектом
    GtkWindow
    . Точно так же объект
    GtkWindow
    наследует из объекта
    GtkContainer
    , который в свою очередь наследует из объекта
    GtkWidget
    .

    Для удобства все функции создания виджетов возвращают тип

    GtkWidget
    . Например,

    GtkWidget* gtk_window_new(GtkWindowType type);

    Предположим, что вы создаете объект

    GtkWindow
    и хотите передать возвращенное значение в функцию, ожидающую объект типа
    GtkContainer
    , например, такую, как
    gtk_container_add
    :

    void gtk_container_add(GtkContainer* container, GtkWidget *widget);

    Вы применяете макрос

    GTK_CONTAINER
    для приведения типов
    GtkWidget
    и
    GtkContainer
    :

    GtkWidget * window = gtk_window_new(GTK GTK_WINDOW_TOPLEVEL);

    gtk_container_add(GTK_CONTAINER(window), awidget);

    Назначение этих функций вы узнаете позже; сейчас просто отметьте для себя частое применение макросов. Для каждого возможного приведения типа существует макрос.

    Примечание

    Не беспокойтесь, если вам все это не очень понятно; вам не нужно разбираться в подробностях ООП для того, чтобы освоить GNOME/GTK+. На самом деле это безболезненный способ усвоить идеи и преимущества ООП на базе знакомого вам языка С.

    Знакомство с GNOME

    GNOME — имя, данное проекту, начатому в 1997 г. программистами, работавшими в проекте GNU Image Manipulation Program (GIMP) над созданием унифицированного рабочего стола для Linux. Все были согласны с тем, что выбор ОС Linux как платформы рабочего стола тормозился отсутствием согласованной стратегии. В то время рабочий стол Linux напоминал Дикий Запад без общих стандартов или выработанных на практике приемов, и программисты могли делать все, что вздумается. Без сводной группы, контролирующей меню рабочего стола, согласованное представление и отображение, документацию, трансляцию и т.д., освоение рабочего стола новичком было в лучшем случае путанным, а в худшем — непригодным.

    Группа GNOME намеревалась создать рабочий стол для ОС Linux с лицензией GPL, разрабатывая утилиты и программы настройки в едином согласованном стиле, одновременно способствуя развитию стандартов для взаимодействия приложений, печати, управления сеансами и лучших приемов в программировании GUI приложений.

    Результаты их стараний очевидны: среда GNOME — основа стандартного рабочего стола Linux в дистрибутивах Fedora, Red Hat, Ubuntu, openSUSE и др. (рис. 16.1).

    Первоначально название GNOME означало GNU Network Object Model Environment (среда сетевых объектных моделей GNU), что отражает одну из ранее поставленных задач, внедрение в систему Linux объектной интегрированной системы, такой как Microsoft OLE, для того, чтобы вы могли, например, встроить электронную таблицу в документ текстового процессора. Теперь поставлены новые задачи, и то, что сегодня нам известно как GNOME, — это законченная среда рабочего стола, содержащая панель для запуска приложений, комплект программ и утилит, библиотеки программирования и средства поддержки разработчиков.

    Перед тем как начать программировать, следует убедиться в том, что все библиотеки установлены.

    Рис. 16.1 

    Установка библиотек разработки GNOME/GTK+

    Полный рабочий стол GNOME со своими стандартными приложениями и библиотеками разработки GNOME/GTK+ включает в себя более 60 пакетов, поэтому установка GNOME с нуля вручную или из исходного кода — устрашающая перспектива. К счастью, в современных дистрибутивах Linux есть отличные утилиты управления пакетами, превращающие установку GNOME/GTK+ и библиотек разработки в пустяковое дело.

    В дистрибутивах Linux Red Hat и Fedora вы открываете средство Package Management (Управление пакетами), щелкнув мышью кнопку меню Applications (Приложения) и выбрав команду Add/Remove Software (Добавить/удалить программы). Когда появится Package Management (рис. 16.2), убедитесь в том, что установлен флажок GNOME Software Development (Разработка программ GNOME). Загляните в область Development (Разработка) для этого установочного параметра.

    В этой главе вы будете работать с GNOME/GTK+ 2, поэтому убедитесь в том, что установлены библиотеки версии 2.x.Рис. 16.2 


    В случае дистрибутивов, применяющих RPM-пакеты, у вас должны быть установлены как минимум следующие RPM-пакеты:

    □ gtk2-2.10.11-7.fc7.rpm;

    □ gtk2-devel-2.10.11-7.fc7.rpm;

    □ gtk2-engines-2.10.0-3.fc7.rpm;

    □ libgnome-2.18.0-4.fc7.rpm;

    □ libgnomeui-2.18.l-2.fc7.rpm;

    □ libgnome-devel-2.18.0-4.fc7.rpm;

    □ libgnomeui-devel-2.18.1-2.fc7.rpm.

    Примечание

    В этом примере комбинация символов fc7 указывает на дистрибутив Linux Fedora 7. В вашей системе могут быть слегка отличающиеся имена.

    В дистрибутиве Debian и основанных на Debian системах, таких как Ubuntu, вы можете использовать программу apt-get для установки пакетов GNOME/GTK+ с разных сайтов-зеркал (mirrors). Для выяснения подробностей следуйте по ссылкам Web-сайта http://www.gnome.org.

    Опробуйте также демонстрационное приложение GTK+, в котором показаны все виджеты и их оформление (рис. 16.3).

    $ gtk-demo

    Рис. 16.3 


    Примечание

    Для каждого виджета отображаются вкладки Info (Информация) и Source (Исходный код). На вкладке Source (Исходный код) приведен программный код на языке С для применения данного виджета. На ней может быть представлено множество примеров.

    Выполните упражнение 16.1.

    Упражнение 16.1. Обычное окно

    GtkWindow

    Давайте начнем программирование средствами GTK+ с простейшей из программ GUI — отображения окна. Вы увидите библиотеки GTK+ в действии и большой набор функциональных возможностей, получаемых из очень короткого программного кода.

    1. Введите программу и назовите ее gtk1.с:

    #include <gtk/gtk.h>


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

     GtkWidget *window;

     gtk_init(&argc, &argv);

     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

     gtk_widget_show(window);

     gtk_main();

     return 0;

    }

    2. Для компиляции gtk1.c введите следующую команду:

    $ gcc gtk1.c -о gtk1 `pkg-config --cflags --libs gtk+-2.0`

    Примечание

    Будьте внимательны и набирайте обратные апострофы, а не обычные апострофы — помните о том, что обратные апострофы — это инструкции, заставляющие оболочку выполнить заключенную в них команду и добавить ее вывод в конец строки.

    Когда вы выполните программу с помощью следующей команды, ваше окно должно раскрыться (рис. 16.4).

    $ ./gtk1

    Рис. 16.4 


    Учтите, что вы можете перемещать окно, изменять его размер, сворачивать и раскрывать его на весь экран.

    Как это работает

    Включить заголовочные файлы, необходимые для библиотек GTK+ и связанных с ними библиотек, можно с помощью одного оператора

    #include <gtk/gtk.h>
    . Далее вы объявляете окно как указатель на объект
    GtkWidget
    .

    Затем для инициализации библиотек GTK+ следует выполнить вызов

    gtk_init
    , передав аргументы командной строки
    argc
    и
    argv
    . Это дает возможность GTK+ выполнить синтаксический анализ любых параметров командной строки, о которых комплект должен знать. Учтите, что вы всегда должны инициализировать GTK+ таким способом перед вызовом любых функций GTK+.

    Суть примера заключается в вызове функции

    gtk_window_new
    . Далее приведен ее прототип:

    GtkWidget* gtk_window_new(GtkWindowType type);

    Параметр type может принимать в зависимости от назначения окна одно из двух значений:

    □ 

    GTK_WINDOW_TOPLEVEL
    — стандартное окно с рамкой;

    □ 

    GTK_WINDOW_POPUP
    — окно без рамки, подходящее для диалогового окна.

    Почти всегда вы будете применять значение

    GTK_WINDOW_TOPLEVEL
    , потому что для создания диалоговых окон, как вы узнаете позже, есть гораздо более удобные способы.

    Вызов

    gtk_window_new
    создает окно в памяти, таким образом у вас появляется возможность перед реальным выводом окна на экран заполнить его виджетами, изменить размер окна, его заголовок и т.д. Для того чтобы окно появилось на экране, выполните вызов функции
    gtk_widget_show
    :

    gtk_widget_show(window);

    Эта функция принимает указатель типа

    GtkWidget
    , поэтому вы просто предоставляете ссылку на свое окно.

    Последним вы выполняете вызов функции

    gtk_main
    . Эта основная функция запускает процесс обмена информацией (interactivity process), передавая управление GTK+, и не возвращает его до тех пор, пока не будет выполнен вызов функции
    gtk_main_quit
    . Как видно в программе gtk1.с, этого никогда не происходит, поэтому приложение не завершается даже после закрытия окна. Проверьте это, щелкнув кнопкой мыши пиктограмму закрытия окна и убедившись в отсутствии строки, приглашающей вводить команду. Вы исправите это поведение после того, как познакомитесь с сигналами и обратными вызовами в следующем разделе. Сейчас завершите приложение, нажав комбинацию клавиш <Ctrl>+<C> в окне командной оболочки, которое вы использовали для запуска программы gtk1.

    События, сигналы и обратные вызовы

    У всех библиотек GUI есть нечто общее. Должен существовать некий механизм для выполнения программного кода в ответ на действие пользователя. Программа, выполняющаяся в режиме командной строки, может позволить себе останов выполнения в ожидании ввода и затем применить нечто вроде оператора выбора для выполнения разных ветвей программы в зависимости от введенных данных. Такой подход нецелесообразен в случае приложения GUI, поскольку оно должно непрерывно реагировать на ввод пользователя, например, ему приходится постоянно обновлять области окна.

    У современных оконных систем есть система событий и приемники событий, которым адресована эта задача. Идея заключается в том, что каждый пользовательский ввод обычно с помощью мыши или клавиатуры инициирует событие. Нажатие на клавиатуре, например, вызовет "событие клавиатуры". Затем пишется программный код, который ждет приема такого события и выполняется в случае его возникновения.

    Как вы уже видели, эти события генерирует система X Window System, но они мало помогут вам как программисту GTK+, т.к. они очень низкоуровневые. Когда производится щелчок кнопкой мыши, X порождает событие, содержащее координаты указателя мыши, а вам нужно знать, когда пользователь активизирует виджет.

    У GTK+ есть собственная система событий и приемников событий, называемых сигналами и обратными вызовами. Их очень легко применять, поскольку для установки обработчика сигнала можно использовать очень полезное свойство языка С, указатель на функцию.

    Сначала несколько определений. Сигнал GTK+ порождается объектом типа

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

    Примечание

    Имейте в виду, что сигнал GTK+ — это нечто иное, чем сигнал UNIX, обсуждавшийся в главе 11.

    Как программист, использующий GTK+, вы должны заботиться только о написании и связывании функций обратного вызова, поскольку код порождения сигнала — это внутренний программный код определенного виджета.

    Прототип или заголовок функции обратного вызова обычно похож на следующий:

    void a_callback_function(GtkWidget *widget, gpointer user_data);

    Вы передаете два параметра: первый — указатель на виджет, породивший сигнал, второй — произвольный указатель, который вы выбираете самостоятельно, когда связываете обратный вызов. Вы можете использовать этот указатель для любых целей.

    Связать функцию обратного вызова тоже очень просто. Вы вызываете функцию

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

    gulong g_signal_connect(gpointer *object, const gchar *name,

     GCallback func, gpointer user_data);

    Следует отметить, что для связывания функций обратного вызова нет ограничений. Вы можете иметь много сигналов, связанных с одной и той же функцией обратного вызова, и много функций обратного вызова, связанных с единственным сигналом.

    В документации по API GTK+ можно найти подробное описание сигналов, порождаемых каждым виджетом.

    Примечание

    До появления GTK+ 2 для связывания функций обратного вызова применялась функция

    gtk_signal_connect
    . Она была заменена функцией
    g_signal_connect
    и не должна применяться во вновь разрабатываемом программном коде.

    Вы опробуете функцию

    g_signal_connect
    в упражнении 16.2.

    Упражнение 16.2. Функция обратного вызова

    В программе gtk2.c вставьте в свое окно кнопку и свяжите сигнал

    clicked
    (щелчок мышью по кнопке) с вашей функцией обратного вызова для вывода короткого сообщения:

    #include <gtk/gtk.h>

    #include <stdio.h>


    static int count = 0;

    void button_clicked(GtkWidget *button, gpointer data) {

     printf("%s pressed %d time(s) \n", (char *)data, ++count);

    }


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

     GtkWidget *window;

     GtkWidget *button;

     gtk_init(&argc, &argv);

     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

     button = gtk_button_new_with_label("Hello World!");

     gtk_container_add(GTK_CONTAINER(window), button);

     g_signal_connect(GTK_OBJECT(button), "clicked",

      GTK_SIGNAL_FUNC(button_clicked), "Button 1");

     gtk_widget_show(button);

     gtk_widget_show(window);

     gtk_main();

     return 0;

    }

    Введите исходный текст программы и сохраните его в файле с именем gtk2.c. Откомпилируйте и скомпонуйте программу аналогично программе gtk1.с из предыдущего упражнения. Запустив ее, вы получите окно с кнопкой. При каждом щелчке кнопки мышью будет выводиться короткое сообщение (рис. 16.5).

    Рис. 16.5


    Как это работает

    Вы добавили два новых элемента в программу gtk2.c: виджет

    GtkButton
    и функцию обратного вызова.
    GtkButton
    — это виджет простой кнопки, которая может содержать текст, в нашем случае "Hello World", и порождает сигнал, названный
    clicked
    , каждый раз, когда кнопку щелкают мышью.

    Функция обратного вызова

    button_clicked
    связана с сигналом
    clicked
    виджета кнопки с помощью функции
    g_signal_connect
    :

    g_signal_connect(GTK_OBJECT(app), "clicked",

     GTK_SIGNAL_FUNC(button_clicked), "Button 1");

    Обратите внимание на то, что имя кнопки — "Button 1" — передается в функцию обратного вызова как данные пользователя.

    Весь остальной добавленный программный код касается виджета кнопки, создаваемой так же, как окно — вызовом функции

    gtk_button_new_with_label
    — функция
    gtk_widget_show
    делает ее видимой.

    Для расположения кнопки в окне вызывается функция

    gtk_container_add
    . Эта простая функция помещает
    GtkWidget
    внутрь объекта
    GtkContainer
    и принимает контейнер и виджет как аргументы: 

    void gtk_container_add(GtkContainer* container, GtkWidget *widget);
     

    Как вы уже знаете,

    GtkWindow
    — потомок или дочерний объект объекта
    GtkContainer
    . поэтому вы можете привести тип вашего объекта-окна к типу
    GtkContainer
    с помощью макроса
    GTK_CONTAINER
    :

    gtk_container_add(GTK_CONTAINER(window), button);

    Функция

    gtk_container_add
    прекрасно подходит для расположения в окне одиночного виджета, но гораздо чаще вам потребуется для создания хорошего интерфейса размещать несколько виджетов в разных частях окна. У комплекта GTK+ есть специальные виджеты как раз для этой цели, именуемые виджетами упаковочных контейнеров,

    Виджеты упаковочных контейнеров

    Компоновка GUI исключительно важна для удобства применения интерфейса, и добиться наилучшей компоновки труднее всего. Реальная трудность в размещении виджетов заключается в том, что вы не можете полагаться на наличие у всех пользователей одинаковых размеров окон, тем, шрифтов и цветовых схем. То, что может быть отличным интерфейсом для одной системы, в другой системе может оказаться просто нечитаемым.

    Для создания GUI, который выглядит одинаково во всех системах, вам необходимо избегать размещения виджетов на основе абсолютных координат и использовать более гибкую систему компоновки. У GTK+ есть для этой цели виджеты контейнеров. Виджеты-контейнеры позволяют управлять компоновкой виджетов в окнах вашего приложения. Виджеты упаковочных контейнеров (box) представляют очень удобный тип виджета-контейнера. GTK+ предлагает множество виджетов-контейнеров других типов, описанных в интерактивной документации к GTK+.

    Виджеты упаковочных контейнеров — невидимые виджеты, задача которых — хранить другие виджеты и управлять их компоновкой или схемой размещения. Для управления размером отдельных виджетов, содержащихся в виджете упаковочного контейнера, вы задаете правила вместо координат. Поскольку виджеты упаковочных контейнеров могут содержать любые объекты

    GtkWidget
    и объект
    GtkBox
    сам является объектом типа
    GtkWidget
    , для создания сложных компоновок можно формировать виджеты упаковочных контейнеров, вложенные один в другой.

    У типа

    GtkBox
    существуют два основных подкласса:

    □ 

    GtkHBox
    — однострочный горизонтальный упаковочный контейнер;

    □ 

    GtkVBox
    — одностолбцовый вертикальный упаковочный контейнер.

    После создания упаковочных контейнеров следует задать два параметра:

    homogeneous
    и
    spacing
    :

    GtkWidget* gtk_hbox_new(gboolean homogeneous, gint spacing);

    GtkWidget* gtk_vbox_new(gboolean homogeneous, gint spacing);

    Эти параметры управляют компоновкой всех виджетов в конкретном упаковочном контейнере. Параметр

    homogeneous
    — логический, если он равен
    TRUE
    , виджеты занимают одинаковую площадь независимо от их индивидуальных размеров. Параметр
    spacing
    задает расстояние между виджетами в пикселах.

    После того как упаковочный контейнер создан, добавьте в него виджеты с помощью функций

    gtk_box_pack_start
    и
    gtk_box_pack_end
    :

    void gtk_box_pack_start(GtkBox *box, GtkWidget *child,

     gboolean expand, gboolean f ill, guint padding);

    void gtk_box_pack_end(GtkBox *box, GtkWidget *child,

     gboolean expand, gboolean fill, guint padding);

    Функция

    gtk_box_pack_start
    вставляет виджеты, начиная от левого края контейнера
    GtkHBox
    и нижнего края контейнера
    GtkVBox
    ; функция
    gtk_box_pack_end
    , наоборот, начинает от правого и верхнего краев контейнера. Параметры функций управляют расстоянием между виджетами и форматом каждого виджета, находящегося в упаковочном контейнере.

    В табл. 16.1 описаны параметры, которые вы можете передавать в функцию

    gtk_box_pack_start
    или
    gtk_box_pack_end
    .


    Таблица 16.1

    Параметр Описание
    GtkBox *box
    Заполняемый упаковочный контейнер
    GtkWidget *child
    Виджет, который следует поместить в упаковочный контейнер
    gboolean expand
    Если равен
    TRUE
    , данный виджет занимает все доступное пространство, используемое совместно с другими виджетами, у которых этот флаг также равен
    TRUE
    gboolean fill
    Если равен
    TRUE
    , данный виджет будет занимать всю доступную площадь вместо использования ее как отступа от краев. Действует, только если флаг
    expand
    равен
    TRUE
    guint padding
    Размер отступа вокруг виджета в пикселах

    Давайте теперь рассмотрим эти виджеты упаковочных контейнеров и создадим более сложный пользовательский интерфейс, демонстрирующий вложенные упаковочные контейнеры (упражнение 16.3).

    Упражнение 16.3. Макет виджета-контейнера

    В этом примере вы спланируете размещение нескольких простых виджетов-меток типа

    GtkLabel
    с помощью контейнеров типа
    GtkHBox
    и
    GtkVBox
    . Виджеты-метки — простые графические элементы, подходящие для вывода коротких текстовых фрагментов. Назовите эту программу container.c:

    #include <gtk/gtk.h>


    void closeApp(GtkWidget *window, gpointer data) {

     gtk_main_quit();

    }


    /* Обратный вызов позволяет приложению отменить событие

       close/destroy. (Для отмены возвращает TRUE.) */

    gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) {

     printf("In delete_event\n");

     return FALSE;

    }


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

     GtkWidget *window;

     GtkWidget *label1, *label2, *label3;

     GtkWidget *hbox;

     GtkWidget *vbox;

     gtk_init(&argc, &argv);

     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

     gtk_window_set_title(GTK_WINDOW window), "The Window Title");

     gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

     gtk_window_set_default_size(GTK_WTNDOW(window), 300, 200);

     g_signal_connect(GTK_OBJECT(window), "destroy",

      GTK_SIGNAL_FUNC(closeApp), NULL);

     g_signal_connect(GTK_OBJECT(window), "delete_event",

      GTK_SIGNAL_FUNC(delete_event), NULL);

     label1 = gtk_label_new("Label 1");

     label2 = gtk_label_new("Label 2");

     label3 = gtk_label_new("Label 3");

     hbox = gtk_hbox_new(TRUE, 5);

     vbox = gtk_vbox_new(FALSE, 10);

     gtk_box_pack_start(GTK_BOX(vbox), label1, TRUE, FALSE, 5);

     gtk_box_pack_start(GTK_BOX(vbox), label2, TRUE, FALSE, 5);

     gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 5);

     gtk_box_pack_start(GTK_BOX(hbox), label3, FALSE, FALSE, 5);

     gtk_container_add(GTK_CONTAINER(window), hbox);

     gtk_widget_show_all(window);

     gtk_main();

     return 0;

    }

    Когда вы выполните эту программу, то увидите следующую схему расположения виджетов-меток в вашем окне (рис. 16.6).

    Рис. 16.6


    Как это работает

    Вы создаете два виджета упаковочных контейнеров:

    hbox
    и
    vbox
    . С помощью функции
    gtk_box_pack_start
    вы заполняете
    vbox
    виджетами
    label1
    и
    label2
    , причем
    label2
    располагается у нижнего края контейнера, потому что вставляется после
    label1
    . Далее контейнер
    vbox
    целиком наряду с меткой
    label3
    вставляется в контейнер
    hbox
    .

    В заключение

    hbox
    добавляется в окно и выводится на экран с помощью функции
    gtk_widget_show_all
    .

    Схему размещения упаковочного контейнера легче понять с помощью блок-схемы, показанной на рис. 16.7.

    Рис. 16.7


    Познакомившись с виджетами, сигналами, обратными вызовами и виджетами-контейнерами, вы рассмотрели основы комплекта инструментов GTK+. Но для того чтобы стать программистом, профессионально применяющим GTK+, нужно понять, как наилучшим образом использовать имеющиеся в комплекте виджеты.

    Виджеты GTK+

    В этом разделе мы рассмотрим API самых популярных виджетов GTK+, которые вы будете применять чаще всего в своих приложениях.

    GtkWindow

    GtkWindow
    — базовый элемент всех приложений GTK+. До сих пор вы использовали его для хранения своих виджетов.

    GtkWidget

     +---- GtkContainer

            +---- GtkBin

                   +---- GtkWindow

    Существуют десятки вызовов API

    GtkWindow
    , но далее приведены функции, заслуживающие особого внимания.

    GtkWidget* gtk_window_new(GtkWindowType type);

    void gtk_window_set_title(GtkWindow *window, const gchar *title);

    void gtk_window_set_position(GtkWindow *window, GtkWindowPosition position);

    void gtk_window_set_default_size(GtkWindow *window, gint width, gint height);

    void gtk_window_resize(GtkWindow *window, gint width, gint height);

    void gtk_window_set_resizable(GtkWindow *window, gboolean resizable);

    void gtk_window_present(GtkWindow *window);

    void gtk_window_maximize(GtkWindow *window);

    void gtk_window_unmaximize(GtkWindow *window);

    Как вы видели, функция

    gtk_window_new
    создает в памяти новое пустое окно. Заголовок окна не задан и размер и местоположение окна не определены. Обычно вы будете заполнять окно виджетами и задавать меню и панель инструментов перед выводом окна на экран с помощью вызова функции
    gtk_widget_show
    .

    Функция

    gtk_window_set_title
    изменяет текст полосы заголовка, информируя оконный менеджер запроса.

    Примечание

    Поскольку за отображение оформления окна отвечает оконный менеджер, а не библиотека GTK+, шрифт, цвет и размер текста зависят от вашего выбора оконного менеджера.

    Функция

    gtk_window_setposition
    управляет начальным местоположением на экране. Параметр
    position
    может принимать пять значений, перечисленных в табл. 16.2.


    Таблица 16.2

    Параметр
    position
    Описание
    GTK_WIN_POS_NONE
    Окно располагается по усмотрению оконного менеджера
    GTK_WIN_POS_CENTER
    Окно центрируется на экране
    GTK_WIN_POS_MOUSE
    Расположение окна задаётся указателем мыши
    GTK_WIN_POS_CENTER_ALWAYS
    Окно остается отцентрированным независимо от его размера
    GTK_WIN_POS_CENTER_ON_PARENT
    Окно центрируется относительно родительского окна (удобно для диалоговых окон)

    Функция

    gtk_window_set_default_size
    задает окно на экране в единицах отображения GTK+. Явное задание размера окна гарантирует, что содержимое окна не будет закрыто чем-либо или скрыто. Для того чтобы изменить размеры окна после его вывода на экран, можно воспользоваться функцией
    gtk_window_resize
    . По умолчанию пользователь может изменить размеры окна, перемещая обычным способом его границу мышью. Если вы хотите помешать этому, можно вызвать функцию
    gtk_window_set_resizeable
    , приравненную FALSE.

    Для того чтобы убедиться в том, что ваше окно присутствует на экране и видно пользователю, т.е. не свернуто или скрыто, подойдет функция

    gtk_window_present
    . Она полезна для диалоговых окон, т.к. позволяет убедиться в том, что окна не свернуты, когда вам нужен какой-либо пользовательский ввод. В противном случае, для раскрытия окна на весь экран и его сворачивания у вас есть функции
    gtk_window_maximize
    и
    gtk_window_minimize
    .

    GtkEntry

    GtkEntry
    — виджет однострочного текстового поля, который обычно применяется для ввода простых текстовых данных, например, адреса электронной почты, имени пользователя или имени узла сети. Существуют вызовы API, позволяющие задать как считывание введенного текста, так и его максимальную длину в символах, а также другие параметры, управляющие местоположением текста и его выделением.

    GtkWidget

     +----GtkEntry

    Можно настроить

    GtkEntry
    на отображение звездочек (или любого другого определенного пользователем символа) на месте набранных буквенно-цифровых символов, что очень удобно для ввода паролей, когда вы не хотите, чтобы кто-то заглядывал через ваше плечо и читал текст.

    Мы опишем большинство самых полезных функций виджета

    GtkEntry
    :

    GtkWidget* gtk_entry_new(void);

    GtkWidget* gtk_entry_new_with_max_length(gint max);

    void gtk_entry_set_max_length(GtkEntry *entry, gint max);

    G_CONST_RETURN gchar* gtk_entry_get_text(GtkEntry *entry);

    void gtk_entry_set_text(GtkEntry *entry, const gchar *text);

    void gtk_entry_append_text(GtkEntry *entry, const gchar *text);

    void gtk_entry_prepend_text(GtkEntry* entry, const gchar *text);

    void gtk_entry_set_visibility(GtkEntry *entry, gboolean visible);

    void gtk_entry_set_invisible_char(GtkEntry *entry, gchar invch);

    void gtk_entry_set_editable(GtkEntry *entry, gboolean editable);

    Вы можете создать

    GtkEntry
    с помощью функции
    gtk_entry_new
    или при вводе текста фиксированной длины с помощью функции
    gtk_entry_new_with_max_length
    . Ограничение ввода определенной длиной текста избавляет вас от проверки корректности длины ввода и, возможно, необходимости информировать пользователя о том, что текст слишком длинный.

    Для получения содержимого виджета

    GtkEntry
    вызывайте функцию
    gtk_entry_get_text
    , которая возвращает указатель
    const char
    , внутренний по отношению к
    GtkEntry
    (
    G_CONST_RETURN
    — макрос, определенный в библиотеке GLib). Если вы хотите изменить текст или передать его в функцию, которая может его модифицировать, следует скопировать строку с помощью, например, функции
    strcpy
    .

    Вы можете вручную задавать и изменять содержимое виджета

    GtkEntry
    , применяя функции
    _set_text
    ,
    _append_text
    и
    _modify_text
    . Учтите, что они принимают указатели const.

    Для применения

    GtkEntry
    в качестве поля ввода пароля, которое отображает звездочки на месте символов, воспользуйтесь функцией
    gtk_entry_set_visibility
    , передав ей параметр
    visible
    со значением
    FALSE
    . Скрывающий символ можно изменить в соответствии с вашими требованиями с помощью функции
    gtk_entry_set_invisible_char
    .

    Выполните упражнение 16.4.

    Упражнение 16.4. Ввод имени пользователя или пароля

    Теперь, познакомившись с функциями виджета GtkEntry, посмотрим на них в действии в небольшой программе. Программа entry.c будет создавать окно ввода имени пользователя и пароля и сравнивать введенный пароль с секретным.

    1. Сначала определим секретный пароль, остроумно заданный как

    secret
    :

    #include <gtk/gtk.h>

    #include <stdio.h>

    #include <string.h>

    const char * password = "secret";

    2. У вас есть две функции обратного вызова, которые вызываются, когда уничтожается окно и щелкается мышью кнопка OK:

    void closeApp(GtkWidget *window, gpointer data) {

     gtk_main_quit();

    }


    void button_clicked(GtkWidget *button, gpointer data) {

     const char *password_text =

      gtk_entry_get_text(GTK_ENTRY((GtkWidget *) data));

     if (strcmp(password_text, password) == 0)

     printf("Access granted!\n");

     else printf("Access denied!\n");

    }

    3. В функции

    main
    создается, компонуется интерфейс и связываются обратные вызовы с сигналами. Для компоновки виджетов меток и полей ввода примените виджеты-контейнеры hbox и vbox:

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

     GtkWidget *window;

     GtkWidget *username_label, *password_label;

     GtkWidget *username_entry, *password_entry;

     GtkWidget *ok_button;

     GtkWidget *hbox1, *hbox2;

     GtkWidget *vbox;

     gtk_init(&argc, &argv);

     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

     gtk_window_set_title(GTK_WINDOW(window), "GtkEntryBox");

     gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

     gtk_windowset_default_size(GTK_WINDOW(window), 200, 200);

     g_signal_connect(GTK_OBJECT(window), "destroy",

     GTK_SIGNAL_FUNC(closeApp), NULL);

     username_label = gtk_label_new("Login:");

     password_label = gtk_label_new("Password:");

     username_entry = gtk_entry_new();

     password_entry = gtk_entry_new();

     gtk_entry_set_visibility(GTK_ENTRY(password_entry), FALSE);

     ok_button = gtk_button_new_with_label("Ok");

     g_signal_connect(GTK_OBJECT(ok_button), "clicked",

      GTK_SIGNAL_FUNC(button_clicked), password_entry);

     hbox1 = gtk_hbox_new(TRUE, 5);

     hbox2 = gtk_hbox_new(TRUE, 5);

     vbox = gtk_vbox_new(FALSE, 10);

     gtk_box_pack_start(GTK_BOX(hbox1), username_label, TRUE, FALSE, 5);

     gtk_box_pack_start(GTK_BOX(hbox1), username_entry, TRUE, FALSE, 5);

     gtk_box_pack_start(GTK_BOX(hbox2), password_label, TRUE, FALSE, 5);

     gtk_box_pack_start(GTK_BOX(hbox2), password_entry, TRUE, FALSE, 5);

     gtk_box_pack_start(GTK_BOX(vbox), hbox1, FALSE, FALSE, 5);

     gtk_box_pack_start(GTK_BOX(vbox), hbox2, FALSE, FALSE, 5);

     gtk_box_pack_start(GTK_BOX(vbox), ck_button, FALSE, FALSE, 5);

     gtk_container_add(GTK_CONTAINER(window), vbox);

     gtk_widget_show_all(window);

     gtk_main();

     return 0;

    }

    Когда вы запустите программу, то получите окно, показанное на рис. 16.8.

    Рис. 16.8 


    Как это работает

    Программа создает два виджета типа

    GtkEntry
    ,
    username_entry
    и
    password_entry
    , а также задает видимость
    password_entry
    , равной
    FALSE
    , чтобы скрыть введенный пароль. Затем она формирует кнопку
    GtkButton
    , с помощью которой вы связываете сигнал
    clicked
    с функцией обратного вызова
    button_clicked
    .

    Как только в функции обратного вызова программа извлечет введенный пароль и сравнит его с секретным паролем, на экран выводится соответствующее сообщение.

    Обратите внимание на то, что для вставки виджетов в свои контейнеры вы много раз повторили операторы

    gtk_box_pack_start
    . Для сокращения этого повторяющегося программного кода в последующих примерах будет определена вспомогательная функция.

    GtkSpinButton

    Порой вам нужно, чтобы пользователь ввел числовое значение, например, максимальную скорость или размер инструмента, и в такой ситуации виджет

    GtkSpinButton
    (кнопка-счетчик) идеален. Он ограничивает ввод пользователя только цифровыми символами и можно задать диапазон допустимых значений от нижней до верхней границы. Виджет также содержит стрелки, направленные вверх и вниз, так что пользователь может "накручивать" значение, для удобства пользуясь только мышью.

    GtkWidget

     +---- GtkEntry

            +---- GtkSpinButton

    И снова API понятен, и мы перечислим наиболее часто применяемые вызовы:

    GtkWidget* gtk_spin_button_new(GtkAdjustment *adjustment,

     gdouble climb_rate, guint digits);

    GtkWidget* gtk_spin_button_new_with_range(gdouble min, gdouble max,

     gdouble step);

    void gtk_spin_button_set_digits(GtkSpinButton *spin_button, guint digits);

    void gtk_spin_button_set_increments(GtkSpinButton *spin_button,

     gdouble step, gdouble page);

    void gtk_spin_button_set_range(GtkSpinButton *spin_button, gdouble min,

     gdouble max);

    gdouble gtk_spin_button_get_value(GtkSpinButton *spin_button);

    gint gtk_spin_button_get_value_as_int(GtkSpinButton *spin_button);

    void gtk_spin_button_set_value(GtkSpinButton *spin button, gdouble value);

    Для создания виджета

    GtkSpinButton
    с помощью функции
    gtk_spin_button_new
    вы сначала должны создать объект
    GtkAdjustment
    . Виджет
    GtkAdjustment
    — это абстрактный объект, содержащий логику, касающуюся управления значениями с ограничениями. Он также применяется и в других виджетах, таких как
    GtkHScale
    и
    GtkVScale
    .

    Для создания объекта типа

    GtkAdjustment
    передайте в функцию нижнюю и верхнюю границы и размер приращения.

    GtkObject* gtk_adjustment_new(gdouble value, gdouble lower,

     gdouble upper, gdouble step_increment,

     gdouble page_increment, gdouble page_size);

    Значения параметров

    step_increment
    и
    page_increment
    задают величину минимального и максимального приращений, В случае кнопки-счетчика
    GtkSpinButton
    параметр
    step_increment
    определяет, насколько изменится значение при щелчке мышью стрелки виджета. Параметры
    page_increment
    и
    page_size
    в виджетах
    GtkSpinButton
    не важны.

    Второй параметр,

    climb_rate
    , функции
    gtk_spin_button_new
    управляет скоростью прокрутки значений при нажатии и удерживании кнопки со стрелкой. И наконец, параметр
    digits
    задает точность представления числового значения, виджета, если, например,
    digits
    равен 3, кнопка-счетчик отобразит 0.00.

    Функция

    gtk_spin_button_new_with_range
    — удобный способ создания объекта
    GtkAdjustment
    . Просто задайте нижнюю и верхнюю границы и величину приращения.

    Прочесть текущее значение очень легко благодаря функции

    gtk_spin_button_getvalue
    , а если вам нужно целое число, можно применить функцию
    gtk_spin_button_get_value_as_int
    .

    Выполните упражнение 16.5.

    Упражнение 16.5. Использование виджета
    GtkSpinButton

    Сейчас мы посмотрим в коротком примере, как действует кнопка-счетчик GtkSpinButton. Назовите файл spin.с.

    #include <gtk/gtk.h>


    void closeApp(GtkWidget *window, gpointer data) {

     gtk_main_quit();

    }


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

     GtkWidget* window;

     GtkWidget *spinbutton;

     GtkObject *adjustment;

     gtk_init(&argc, &argv);

     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

     gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);

     g_signal_connect(GTK_OBJECT(window), "destroy",

      GTK_SIGNAL_FUNC(closeApp), NULL);

     adjustment = gtk_adjustment_new(100.0, 50.0, 150.0, 0.5, 0.05, 0.05);

     spinbutton = gtk_spin_button_new(GTK_ADJUSTMENT(adjustment), 0.01, 2);

     gtk_container_add(GTK_CONTAINER(window), spinbutton);

     gtk_widget_show_all(window);

     gtk_main();

     return 0;

    }

    Когда вы выполните программу, то получите кнопку-счетчик, ограниченную диапазоном значений 50–150 (рис. 16.9).

    Рис. 16.9 

    GtkButton

    Вы уже видели виджет кнопки

    GtkButton
    в действии, но существует несколько виджетов, потомков
    GtkButton
    , с чуть большими функциональными возможностями, заслуживающими упоминания.

    GtkButton

     +----GtkToggleButton

           +----GtkCheckButton

                 +----GtkRadioButton

    Как видно из иерархии виджетов, кнопка-переключатель типа

    GtkToggleButton
    — прямой потомок кнопки
    GtkButton
    , кнопка-флажок
    GtkCheckButton
    — кнопки-выключателя
    GtkToggleButton
    и то же самое для переключателя
    GtkRadioButton
    , причем каждый дочерний виджет предназначен для определенных задач.

    GtkToggleButton

    Виджет

    GtkToggleButton
    идентичен виджету
    GtkButton
    за исключением одной важной детали:
    GtkToggleButton
    обладает состоянием. Это означает, что кнопка-выключатель может быть включена или выключена. Когда пользователь щелкает мышью виджет
    GtkToggleButton
    , последний стандартным способом порождает сигнал
    clicked
    и изменяет (или "переключает") свое состояние.

    API у виджета

    GtkToggleButton
    очень простой:

    GtkWidget* gtk_toggle_button_new(void);

    GtkWidget* gtk_toggle_button_new_with_label(const gchar* label);

    gboolean gtk_toggle_button_get_active(GtkToggleButton *toggle_button);

    void gtk_toggle_button_set_active(GtkToggleButton *toggle_button,

     gboolean is_active);

    Наиболее интересные функции —

    gtk_toggle_button_get_active
    и
    gtk_toggle_button_set_active
    , которые вы вызываете для чтения и установки состояния кнопки-выключателя. Если характеристика функционирования равна
    TRUE
    , это означает, что кнопка-выключатель
    GtkToggleButton
    включена.

    GtkCheckButton

    Кнопка-флажок

    GtkCheckButton
    — это замаскированная кнопка-выключатель
    GtkToggleButton
    . Вместо скучного прямоугольного отображения
    GtkToggleButton
    кнопка
    GtkCheckButton
    выводится как привлекательный флажок с расположенным рядом текстом. Функциональных различий между ними нет.

    GtkWidget* gtk_check_button_new(void);

    GtkWidget* gtk_check_button_new_with_label(const gchar *label);

    GtkRadioButton

    Эта кнопка немного отличается от предыдущих, т.к. может группироваться с другими кнопками того же типа. Переключатель (или радиокнопка)

    GtkRadioButton
    — одна из тех кнопок, которые позволяют выбирать только один вариант из группы предложенных. Имя заимствовано у старых радиоприемников с механическими кнопками, которые выскакивали с треском, возвращаясь в прежнее состояние, при нажатии другой кнопки.

    GtkWidget* gtk_radio_button_new(GSList *group);

    GtkWidget* gtk_radio_button_new_from_widget(GtkRadioButton *group);

    GtkWidget* gtk_radio_button_new_with_label(GSList *group, const gchar *label);

    void gtk_radio_button_set_group(GtkRadioButton *radio_button, GSList *group);

    GSList* gtk_radio_button_get_group(GtkRadioButton *radio_button);

    Группа переключателей представлена в объекте-списке библиотеки GLib, названном

    GSList
    . Для того чтобы объединить переключатели в группу, вы можете создать объект
    GSList
    и затем передать ему каждую кнопку с помощью функций
    gtk_radio_button_new
    и
    gtk_radio_button_get_group
    , есть и более легкий способ в виде функции
    gtk_radio_button_new_with_widget
    , которая включает в GSList существующую кнопку. Вы увидите ее в действии в упражнении 16.6, которое позволит вам опробовать разные кнопки
    GtkButton
    .

    Упражнение 16.6.
    GtkCheckButton
    ,
    GtkToggleButton
    и
    GtkRadioButton

    Введите следующий текст в файл с именем buttons.с.

    1. Сначала объявите указатели на кнопки как глобальные переменные:

    #include <gtk/gtk.h>

    #include <stdio.h>


    GtkWidget *togglebutton;

    GtkWidget *checkbutton;

    GtkWidget *radiobutton1, *radiobutton2;

    void closeApp(GtkWidget *window, gpointer data) {

     gtk_main_quit();

    }

    2. Далее определите вспомогательную функцию, которая упаковывает

    GtkWidget
    и
    GtkLabel
    в контейнер
    GtkHbox
    и затем вставляет этот
    GtkHbox
    в заданный виджет- контейнер. Это поможет вам сократить повторяющийся программный код:

    void add_widget_with_label(GtkContainer * box, gchar * caption,

     GtkWidget * widget) {

     GtkWidget *label = gtk_label_new(caption);

     GtkWidget *hbox = gtk_hbox_new(TRUE, 4);

     gtk_container_add(GTK_CONTAINER(hbox), label);

     gtk_container_add(GTK_CONTAINER(hbox), widget);

     gtk_container_add(box, hbox);

    }

    3. 

    print_active
    — еще одна вспомогательная функция, которая выводит текущее состояние заданной кнопки-выключателя
    GtkToggleButton
    со строкой описания. Он вызывается из функции
    button_clicked
    , функции обратного вызова, связанной с сигналом
    clicked
    кнопки OK. При каждом щелчке мышью этой кнопки вы получаете на экране отчет о состоянии кнопок:

    void print_active(char * button_name, GtkToggleButton* button) {

     gboolean active = gtk_toggle_button_get_active(button);

     printf("%s is %s\n", button_name, active?"active":"not active");

    }


    void button_clicked(GtkWidget *button, gpointer data) {

     print_active("Checkbutton", GTK_TOGGLE_BUTTON(checkbutton));

     print_active("Togglebutton", GTK_TOGGLE_BUTTON(togglebutton));

     print_active("Radiobutton1", GTK_TOGGLE_BUTTON(radiobutton1));

     print_active("Radiobutton2", GTK_TOGGLE_BUTTON(radiobutton2));

     printf("\n");

    }

    4. В функции

    main
    вы создаете виджеты кнопок, поочередно помещаете их в контейнер
    GtkVBox
    , добавив пояснительные метки, и связываете сигнал обратного вызова с кнопкой OK:

    gint main(gint argc, gchar *argv[]) {

     GtkWidget* window;

     GtkWidget *button;

     GtkWidget *vbox;

     gtk_init(&argc, &argv);

     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

     gtk_window_set_default_size(GTK_WINDOW(window), 200, 200);

     g_signal_connect(GTK_OBJECT(window), "destroy",

      GTK_SIGNAL_FUNC(closeApp), NULL);

     button = gtk_button_new_with_label("Ok");

     togglebutton = gtk_toggle_button_new_with_label("Toggle");

     checkbutton = gtk_check_button_new();

     radiobutton1 = gtk_radio_button_new(NULL);

     radiobutton2 =

      gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(radiobutton1));

     vbox = gtk_vbox_new(TRUE, 4);

     add_widget_with_label(GTK_CONTAINER(vbox), "ToggleButton:",

      togglebutton);

     add_widget_with_label(GTK_CONTAINER(vbox), "CheckButton:",

      checkbutton);

     add_widget_with_label(GTK_CONTAINER(vbox), "Radio 1:", radiobutton1);

     add_widget_with_label(GTK_CONTAINER(vbox), "Radio 2:", radiobutton2);

     add_widget_with_label(GTK_CONTAINER(vbox), "Button:", button);

     g_signal_connect(GTK_OBJECT(button), "clicked",

      GTK_SIGNAL_FUNC(button_clicked), NULL);

     gtk_container_add(GTK_CONTAINER(window), vbox);

     gtk_widget_show_all(window);

     gtk_main();

     return 0;

    }

    На рис. 16.10 показана программа buttons.c в действии с виджетами

    GtkButton
    четырех часто применяемых типов.

    Рис. 16.10


    Щелкните мышью кнопку OK, чтобы увидеть состояние разных кнопок.

    Данная программа — простой пример использования кнопок

    GtkButton
    четырех типов — показывает, как можно считать состояние кнопки типа
    GtkToggleButton
    ,
    GtkCheckButton
    и
    GtkRadioButton
    с помощью единственной функции
    gtk_toggle_button_get_active
    . Это одно из огромных преимуществ объектно-ориентированного подхода — поскольку вам не нужны отдельные функции
    get_active
    для каждого типа кнопки, вы можете сократить требующийся программный код.

    GtkTreeView

    К этому моменту мы рассмотрели несколько простых виджетов GTK+, но не все виджеты представляют собой однострочные инструменты для ввода или отображения. Сложность виджетов ничем не ограничивается, и

    GtkTreeView
    — яркий пример виджета, инкапсулирующего огромный объем функциональных возможностей.

    GtkWidget

     +---- GtkContainer

            +---- GtkTreeView

    GtkTreeView
    — член семейства виджетов, новых для комплекта GTK+ 2, создающий представление данных в виде дерева или списка наподобие тех, которые вы можете встретить в электронной таблице или файловом менеджере. С помощью виджета
    GtkTreeView
    можно создать сложные представления данных, смешивая текст, растровую графику и даже данные, вводимые с помощью виджетов
    GtkEntry
    , и т.д.

    Самый быстрый способ испытания

    GtkTreeView
    — запуск приложения gtk-demo, которое поставляется вместе с GTK+. Демонстрационное приложение показывает возможности всех виджетов GTK+, включая
    GtkTreeView
    (рис. 16.11).

    Рис. 16.11


    Семейство

    GtkTreeView
    составляется из четырех компонентов:

    □ 

    GtkTreeView
    — отображение дерева или списка;

    □ 

    GtkTreeViewColumn
    — представление столбца списка или дерева;

    □ 

    GtkCellRenderer
    — управление отображаемыми ячейками;

    □ 

    GtkTreeModel
    — представление данных дерева и списка. 

    Первые три компонента формируют так называемое Представление, а последний — Модель. Концепция разделения Представления и Модели (часто называемая проектным шаблоном Модель/Представление/Действие (Model/View/Controller) или сокращенно MVC) не свойственна GTK+, но проектированию уделяется все больше и больше внимания на всех этапах программирования.

    Ключевое достоинство проектного шаблона MVC заключается в возможности одновременной визуализации данных в виде разных представлений без ненужного их дублирования. Например, текстовые редакторы могут иметь две разные панели и редактировать разные фрагменты документа без хранения в памяти двух копий документа.

    Шаблон MVC также очень популярен в Web-программировании, поскольку облегчает создание Web-сайтов, которые визуализируются в мобильных или WAP-обозревателях не так, как в настольных, просто за счет наличия отдельных компонентов Представление, оптимизированных для Web-обозревателя каждого типа. Вы также можете отделить логику сбора данных, например, запросов к базе данных, от логики пользовательского интерфейса.

    Мы начнем с рассмотрения компонента Модель, представленного в GTK+ двумя типами. Объект типа

    GtkTreeStore
    содержит многоуровневые данные, например иерархию каталогов, а объект
    GtkListStore
    предназначен для простых данных.

    Для создания объекта

    GtkTreeStore
    в функцию передается количество столбцов, за которым следуют типы всех столбцов:

    GtkWidget *store = gtk_tree_store_new(3, G_TYPE_STRING, G_TYPE_INT,

     G_TYPE_BOOLEAN);

    Чтение, вставка, редактирование и удаление данных из модели выполняется с помощью структур

    GtkTreeIter
    . Эти структуры итераторов указывают на узлы дерева (или строки списка) и помогают находить фрагменты структур данных потенциально очень большого объема, а также манипулировать ими. Есть несколько вызовов API для получения объекта-итератора для разных точек дерева, но мы рассмотрим простейшую функцию
    gtk_tree_store_append
    .

    Перед тем как вставлять какие-либо данные в модель дерева, вам нужно получить итератор, указывающий на новую строку. Функция

    gtk_tree_store_append
    заполняет объект
    GtkTreeIter
    , который представляет новую строку в дереве, как строку верхнего уровня (если вы передаете значение
    NULL
    в третьем аргументе), так и подчиненную или дочернюю строку (если вы передаете итератор главной или родительской строки):

    GtkTreeIter iter;

    gtk_tree_store_append(store, &iter, NULL);

    Получив итератор, вы можете заполнять строку с помощью функции

    gtk_tree_store_set
    :

    gtk_tree_store_set(store, &iter,

     0, "Def Leppard",

     1, 1987,

     2, TRUE, -1);

    Номер столбца и данные передаются парами, которые завершаются -1. Позже вы примените тип enum для того, чтобы сделать номера столбцов более информативными.

    Для того чтобы добавить ветвь к данной строке (дочернюю строку), вам нужен только итератор для дочерней строки, который вы получаете, вызвав снова функцию

    gtk_tree_store_append
    и указав на этот раз в качестве параметра строку верхнего уровня:

    GtkTreeIter child;

    gtk_tree_store_append(store, &child, &iter);

    Дополнительную информацию об объектах

    GtkTreeStore
    и функциях объекта
    GtkListStore
    см. в документации API, а мы пойдем дальше и рассмотрим компонент Представление типа
    GtkTreeView
    .

    Создание объекта

    GtkTreeView
    — сама простота: только передайте в конструктор в качестве параметра модель типа
    GtkTreeStore
    или
    GtkListStore
    :

    GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));

    Сейчас самое время настроить виджет для отображения данных именно так, как вы хотите. Для каждого столбца следует определить

    GtkCellRenderer
    и источник данных. Можно выбрать, например, визуализацию только определенных столбцов данных или изменить порядок вывода столбцов.

    GtkCellRenderer
    — это объект, отвечающий за прорисовку каждой ячейки на экране, и существует три подкласса, имеющие дело с текстовыми ячейками, ячейками пиксельной графики и ячейками кнопок-выключателей:

    GtkCellRendererText
    ;

    GtkCellRendererPixBuf
    ;

    GtkCellRendererToggle
    .

    В вашем Представлении будет применено текстовое представление ячеек,

    GtkCellRendererText
    .

    GtkCellRenderer* renderer = gtk_cell_renderer_text_new();

    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),

     "This is the column title", renderer, "text", 0, NULL);

    Вы создаете представление ячейки и передаете его в функцию вставки столбца. Эта функция позволяет сразу задать свойства

    GtkCellRendererText
    , передавая заканчивающиеся значением
    NULL
    пары "ключ/значение". В качестве параметров указаны представление дерева, номер столбца, заголовок столбца, представление ячейки и его свойства. В приведенном примере вы задаете атрибут "text", передав номер столбца источника данных. Для объекта
    GtkCellRendererText
    определено несколько других атрибутов, включая подчеркивание, шрифт, размер и т.д.

    В упражнении 16.7, выполнив необходимые шаги, вы увидите, как это работает на практике.

    Упражнение 16.7. Использование виджета
    GtkTreeView

    Введите следующий программный код и назовите файл tree.с.

    1. Примените тип

    enum
    для обозначения столбцов, чтобы можно было ссылаться на них по именам. Общее количество столбцов удобно обозначить как
    N_COLUMNS
    :

    #include <gtk/gtk.h>


    enum {

     COLUMN_TITLE, COLUMN_ARTIST, COLUMN_CATALOGUE, N_COLUMNS

    };


    void closeApp(GtkWidget *window, gpointer data) {

     gtk_main_quit();

    }


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

     GtkWidget *window;

     GtkTreeStore *store;

     GtkWidget *view;

     GtkTreeIter parent_iter, child_iter;

     GtkCellRenderer *renderer;

     gtk_init(&argc, &argv);

     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

     gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);

     g_signal_connect(GTK_OBJECT(window), "destroy",

      GTK_SIGNAL_FUNC(сloseApp), NULL);

    2. Далее вы создаете модель дерева, передавая количество столбцов и тип каждого из них:

     store = gtk_tree_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,

      G_TYPE_STRING);

    3. Следующий этап — вставка родительской и дочерней строк в дерево:

     gtk_tree_store_append(store, &parent_iter, NULL);

     gtk_tree_store_set(store, &parent_iter,

      COLUMN_TITLE, "Dark Side of the Moon",

      COLUMN_ARTIST, "Pink Floyd",

      COLUMN_CATALOGUE, "B000024D4P", -1);

     gtk_tree_store_append(store, &child_iter, &parent_iter);

     gtk_tree_store_set (store, &child_iter,

      COLUMN_TITLE, "Speak to Me", -1);

     view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));

    4. Наконец, добавьте столбцы в представление, задавая источники данных для них и заголовки:

     renderer = gtk_cell_renderer_text_new();

     gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),

      COLUMN_TITLE, "Title", renderer, "text",

      COLUMN_TITLE, NULL);

     gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),

      COLUMN_ARTIST, "Artist", renderer, "text",

      COLUMN_ARTIST, NULL);

     gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),

      COLUMN_CATALOGUE, "Catalogue", renderer, "text",

      COLUMN_CATALOGUE, NULL);

     gtk_container_add(GTK_CONTAINER(window), view);

     gtk_widget_show_all(window); gtk_main();

     return 0;

    }

    Вы будете применять

    GtkTreeView
    как основной объект вашего приложения для работы с компакт-дисками, когда будете модифицировать содержимое
    GtkTreeView
    в соответствии с запросами к базе данных компакт-дисков.

    Мы завершили обзор виджетов GTK+ и теперь обратим наше внимание на другую половину: среду GNOME. Вы увидите, как вставлять меню в ваше приложение с помощью библиотек GNOME и как виджеты GNOME облегчают программирование для рабочего стола GNOME.

    Виджеты GNOME

    Комплект GTK+ спроектирован как нейтральный по отношению к рабочему столу, т.е. GTK+ не делает никаких допущений о том, что он выполняется в среде GNOME или даже в системе Linux. Причина заключается в том, что комплект инструментов GTK+ можно с относительной легкостью перенести для выполнения в ОС Windows или любой другой оконной системе. В результате GTK+ не хватает средств для связывания программы с рабочим столом, таких как средства сохранения настройки программы, отображение файлов помощи или программные апплеты (апплеты — это небольшие утилиты, выполняющиеся на краевых панелях (edge panels)).

    Библиотеки среды включают виджеты GNOME, расширяющие комплект GTK+ и замещающие его части более легкими в применении виджетами. В этом разделе мы расскажем, как программировать с помощью виджетов GNOME.

    Перед использованием библиотек GNOME их следует инициализировать при запуске ваших программ точно так же, как вы поступали с библиотеками GTK+. Вы вызываете функцию

    gnome_program_init
    также, как вы вызывали функцию
    gtk_init
    в чистых программах GTK+.

    Эта функция принимает параметры

    app_id
    и
    арр_version
    , применяемые для описания вашей программы в среде GNOME,
    module_info
    , сообщающий GNOME о том, какой библиотечный модуль инициализировать, параметры командной строки и свойства приложения, заданные как NULL-терминированный список пар "имя/значение".

    GnomeProgram* gnome_program_init(const char *app_id,

     const char *app_version, const GnomeModuleInfо *module_infо,

     int argc, char **argv, const char *first_property_name, ...);

    Необязательный список свойств позволяет задать такие характеристики, как, например, каталог для поиска растровой графики.

    Выполните упражнение 16.8.

    Упражнение 16.8. Окно GNOME

    Давайте рассмотрим программу, применяющую средства GNOME, в которой выполняется GNOME-замещение объекта

    GtkWindow
    виджетом
    GnomeApp
    .

    Введите эту программу и назовите ее gnome1.c:

    #include <gnome.h>


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

     GtkWidget *app;

     gnome_program_init("gnome1", "1.0", MODULE, argc, argv, NULL);

     app = gnome_app_new("gnome1", "The Window Title");

     gtk_widget_show(app);

     gtk_main();

     return 0;

    }

    Для компиляции вам необходимо включить заголовочные файлы GNOME, поэтому передайте библиотеки libgnomeui и libgnome в команду

    pkg-config
    :

    $ gcc gnome1.с -о gnome1 `pkg-config --cflags --libs libgnome-2.0 libgnomeui-2.0`

    Виджет

    GnomeApp
    расширяет возможности GtkWindow и облегчает вставку меню, панелей инструментов и строки состояния вдоль нижнего края окна. Поскольку он потомок
    GtkWindow
    , вы можете применять к виджету
    GnomeApp
    любую функцию виджета
    GtkWindow
    . Далее вы познакомитесь с созданием меню и добавите строку состояния в ваш финальный пример.

    Примечание

    Вы можете использовать комплект инструментов GTK+ для создания меню, но среда GNOME предоставляет полезные структуры и макросы, которые существенно облегчают эту задачу. В интерактивной документации описывается, как создавать меню средствами GTK+.

    Меню GNOME

    Создание строки раскрывающихся меню в среде GNOME на удивление просто. Каждый пункт в строке меню представляется как массив структур

    GNOMEUIInfo
    , причем каждый элемент массива соответствует одному пункту меню. Например, если у вас есть меню File (Файл), Edit (Правка) и View (Вид), то у вас будут три массива, описывающих содержимое каждого меню.

    После определения отдельных меню создается строка меню как таковая с помощью ссылок на эти массивы в еще одном массиве структур

    GNOMEUIInfo
    .

    Структура

    GNOMEUIInfo
    немного сложна и нуждается в дополнительных пояснениях.

    typedef struct {

     GnomeUIInfoType type;

     gchar const *label;

     gchar const *hint;

     gpointer moreinfо;

     gpointer user_data;

     gpointer unused_data;

     GnomeUIPixmapType pixmap_type;

     gconstpointer pixmap_info;

     guint accelerator_key;

     GdkModifierType ac_mods;

     GtkWidget *widget;

    } GnomeUIInfo;

    Первый элемент в структуре,

    type
    , определяет тип элемента меню, который описывается далее. Он может быть одним из 11 типов
    GnomeUIInfоТуре
    , определяемых средой GNOME и приведенных в табл. 16.3.


    Таблица 16.3

    Типы
    GnomeUIInfоТуре
    Описание
    GNOME_APP_UI_ENDOFINFO
    Означает, что этот элемент — последний пункт меню в массиве
    GNOME_APP_UI_ITEM
    Обычный пункт меню или переключатель, если ему предшествует элемент
    GNOME_APP_UI_RADIOITEMS
    GNOME_APP_UI_TOGGLEITEM
    Пункт меню в виде кнопки-переключателя или кнопки-флажка
    GNOME_APP_UI_RADIOITEMS
    Группа переключателей или зависимых переключателей
    GNOME_APP_UI_SUBTREE Означает, что данный элемент представляет собой подменю. Задайте
    moreinfo
    для указания на массив подменю
    GNOME_APP_UI_SEPARATOR
    Вставляет разделительную линию в меню
    GNOME_APP_UI_HELP
    Создает список тем справки для использования в меню Help (Справка)
    GNOME_APP_UI_BUILDER_DATA
    Задает данные построения (builder data) для следующих элементов
    GNOME_APP_UI_ITEM_CONFIGURABLE
    Настраиваемый пункт меню
    GNOME_APP_UI_SUBTREE_STOCK
    Такой же, как
    GNOME_APP_UI_SUBTREE
    за исключением того, что надписи следует искать в каталоге gnome-libs
    GNOME_APP_UI_INCLUDE
    Такой же, как
    GNOME_APP_UI_SUBTREE
    за исключением того, что пункты включены в текущее меню, а не в подменю

    Второй и третий элементы структуры определяют текст пункта меню и всплывающей подсказки. (Подсказка выводится в строке состояния, у нижнего края окна.)

    Назначение элемента

    moreinfo
    зависит от типа. В случае
    ITEM
    и
    TOGGLEITEM
    он указывает на функцию обратного вызова, которую следует вызвать при активации пункта меню. Для
    RADIOITEMS
    он указывает на массив структур
    GnomeUIInfo
    , в которых группируются переключатели.

    user_data
    — произвольный указатель, передаваемый в функцию обратного вызова. Элементы
    pixmap_type
    и
    pixmap_info
    позволяют добавить к пункту меню растровую пиктограмму, a
    accelerator_key
    и
    ac_mods
    помогут определить клавиатурный эквивалент пункта меню.

    И наконец, элемент

    widget
    применяется для внутреннего хранения указателя на виджет пункта меню функцией создания меню.

    Выполните упражнение 16.9.

    Упражнение 16.9. Меню GNOME

    Вы сможете опробовать меню с помощью данной короткой программы. Назовите ее menu1.с.

    #include <gnome.h>

    void closeApp(GtkWidget *window, gpointer data) {

     gtk_main_quit();

    }

    1. Определите для пунктов меню функцию обратного вызова, названную

    item_clicked
    :

    void item clicked(GtkWidget *widget, gpointer user_data) {

     printf("Item Clicked!\n");

    }

    2. Далее следуют определения меню. У вас есть подменю, меню верхнего уровня и массив строки меню:

    static GnomeUIInfo submenu[] = {

     {GNOME_APP_UI_ITEM, "SubMenu", "SubMenu Hint",

      GTK_SIGNAL_FUNC(item_clicked), NULL, NULL, 0, NULL, 0, 0, NULL},

     {GNOME_APP_UI_ENDOFINFO, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0,

      NULL}

    };

    static GnomeUIInfo menu[] = {

     {GNOME_APP_UI_ITEM, "Menu Item 1", "Menu Hint",

      NULL, NULL, NULL, 0, NULL, 0, 0, NULL},

     {GNOME_APP_UI_SUBTREE, "Menu Item 2", "Menu Hint",

      submenu, NULL, NULL, 0, NULL, 0, 0, NULL},

     {GNOME_APP_UI_ENDOFINFO, NULL, NULL, null,

      NULL, NULL, 0, NULL, 0, 0, NULL}

    };

    static GnomeUIInfo menubar[] = {

     {GNOME_APP_UI_SUBTREE, "Toplevel Item", NULL,

      menu, NULL, NULL, 0, NULL, 0, 0, NULL},

     {GNOME_APP_UI_ENDOFINFO, NULL, NULL, NULL,

      NULL, NULL, 0, NULL, 0, 0, NULL}

    };

    3. В функции

    main
    вы имеете дело с обычной инициализацией и затем создаете ваш виджет
    GnomeApp
    и задаете все меню:

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

     GtkWidget *app;

     gnome_program_init("gnome1", "0.1", LIBGNOMEUI_MODULE,

      argc, argv, GNOME_PARAM_NONE);

     app = gnome_app_new("gnome1", "Menus, menus, menus");

     gtk_window_set_default_size(GTK_WINDOW(app), 300, 200);

     g_signal_connect(GTK_OBJECT(app), "destroy",

      GTK_SIGNAL_FUNC(closeApp), NULL);

     gnome_app_create_menus(GNOME_APP(app), menubar);

     gtk_widget_show(app);

     gtk_main();

     return 0;

    }

    Попробуйте выполнить menu1 и посмотрите в действии строку меню, подменю и меню GNOME обратного вызова, показанные на рис. 16.12.

    Рис. 16.12


    Структура

    GnomeUIInfo
    едва ли дружественная по отношению к программисту, если учесть, что она состоит из 11 элементов, большинство из которых обычно равно
    NULL
    или нулю. При их вводе очень легко допустить ошибку и трудно отличить одно поле от другого в длинном массиве элементов. Для улучшения сложившейся ситуации в среде GNOME определены макросы, устраняющие необходимость определения структур вручную. Эти макросы также вставляют пиктограммы и клавиатурные акселераторы для вас, и все даром. На самом деле редко возникают причины, заставляющие использовать вместо них что-то другое.

    Существуют два набора макросов, первый из которых определяет отдельные пункты меню. Эти макросы принимают два параметра: указатель на функцию обратного вызова и данные пользователя.

    #include <libgnomeui/libgnameui.h>

    #define GNOMEUIINFO_MENU_OPEN_ITEM(cb, data)

    #define GNOMEUIINFO_MENU_SAVE_ITEM(cb, data)

    #define GNOMEUIINFO_MENU_SAVE_AS_IТЕМ(cb, data)

    #define GNOMEUIINFO_MENU_PRINT_ITEM(cb, data)

    #define GNOMEUIINFO_MENU_PRINT_SETUP_ITEM(cb, data)

    #define GNOMEUIINFO_MENU_CLOSE_IТЕМ(cb, data)

    #define GNOMEUIINFO_MENU_EXIT_IТЕМ(cb, data)

    #define GNOMEUIINFO_MENU_QUIT_IТЕМ(cb, data)

    #define GNOMEUIINFO_MENU_CUT_ITEM(cb, data)

    #define GNOMEUIINFO_MENU_COPY_ITEM(cb, data)

    #define GNOMEUIINFO_MENU_PASTE_ITEM(cb, data)

    #define GNOMEUIINFO_MENU_SELECT_ALL_ITEM(cb, data)

    ...

    Второй набор предназначен для определений верхнего уровня, в него вы просто передаете массив.

    #define GNOMEUIINFO_MENU_FILE_TREE     (tree)

    #define GNOMEUIINFO_MENU_EDIT_TREE     (tree)

    #define GNOMEUIINFO_MENU_VIEW_TREE     (tree)

    #define GNOMEUIINFO_MENU_SETTINGS_TREE (tree)

    #define GNOMEUIINFO_MENU_FILES_TREE    (tree)

    #define GNOMEUIINFO_MENU_WINDOWS_TREE  (tree)

    #define GNOMEUIINFO_MENU_HELP_TREE     (tree)

    #define GNOMEUIINFO_MENU_GAME_TREE     (tree)

    Выполните упражнение 16.10.

    Упражнение 16.10. Меню с помощью макросов GNOME

    В этом примере вы воспользуетесь уже заданными меню и посмотрите, как работают макросы. Внесите следующие изменения в программу menu1.с и назовите новый вариант menu2.c. Для простоты в этом примере для пунктов меню не определены функции обратного вызова. В данном случае наша задача — просто продемонстрировать удобство применения макросов GNOME, формирующих меню.

    #include <gnome.h>


    static GnomeUIInfo filemenu[] = {

     GNOMEUIINFO_MENU_NEW_ITEM("New", "Menu Hint", NULL, NULL),

     GNOMEUIINFO_MENU_OPEN_ITEM(NULL, NULL),

     GNOMEUIINFO_MENU_SAVE_AS_ITEM(NULL, NULL),

     GNOMEUIINFO_SEPARATOR,

     GNOMEIINFO_MENU_EXIT_ITEM(NULL, NULL),

     GNOMEUUINFO_END

    };


    static GnomeUUInfo editmenu[] =

     GNOMEUIINFO_MENU_FIND_ITEM(NULL, NULL),

     GNOMEUIINFO_END

    };


    static GnomeUIInfo menubar[] = {

     GNOMEUIINFO_MENU_FILE_TREE(filemenu),

     GNOMEUIINFO_MENU_EDIT_TREE(editmenu),

     GNOMEUIINFO_END

    };


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

     GtkWidget *app, *toolbar;

     gnome_program_init("gnome1", "0.1", LIBGNOMEUI_MODULE,

      argc, argv, GNOME_PARAM_NONE);

     app = gnome_app_new("gnome1", "Menus, menus, menus");

     gtk_window_set_default_size(GTK_WINDOW(app), 300, 200);

     gnome_app_create_menus(GNOME_APP(app), menubar);

     gtk_widget_show(app);

     gtk_main();

     return 0;

    }

    Применив макросы libgnomeui в menu2.c, вы значительно сократили код, который нужно набирать, и сделали его гораздо понятнее. Макросы экономят ваше время и усилия, предпринимаемые для создания меню и согласования текста меню, клавиатурных акселераторов и пиктограмм с другими приложениями GNOME. Старайтесь применять их в ваших приложениях при любой возможности.

    На рис. 16.13 показана программа menu3.c в действии на сей раз со стандартизованными в среде GNOME пунктами меню.

    Рис. 16.13

    Диалоговые окна

    Основная часть любого приложения GUI — взаимодействие с пользователем и информирование его о важных событиях. Обычно для этого вы создаете временное окно с кнопками OK и Cancel и, если информация настолько важна, что требует немедленного отклика, например удаление файла, вам приходится блокировать доступ ко всем остальным окнам до тех пор, пока пользователь не сделает выбор (такие окна называют модальными диалоговыми окнами).

    Мы только что описали диалоговое окно, и в комплекте GTK+ есть специальные виджеты диалоговых окон, являющиеся потомками виджета GtkWindow, что существенно облегчает вашу программистскую работу.

    GtkDialog

    Как вы можете видеть, объект

    GtkDialog
    — потомок объекта
    GtkWindow
    и наследует все его функции и свойства.

    GtkWindow

     +----GtkDialog

    GtkDialog
    делит окно на две области, одна для содержимого виджета и другая для кнопок, которые располагаются вдоль нижнего края окна. Вы можете задать нужные вам кнопки и другие параметры диалогового окна во время его создания.

    GtkWidget* gtk_dialog_new_with_buttons(const gchar *title,

     GtkWindow *parent, GtkDialogFlags flags,

     const gchar *first button text, ...);

    Эта функция создает диалоговое окно с заголовком и кнопками. Второй параметр,

    parent
    , должен указывать на главное окно вашего приложения, чтобы комплект GTK+ мог убедиться в том, что диалоговое окно остается присоединенным к главному окну и минимизируется при сворачивании главного окна.

    Параметр

    flags
    определяет комбинацию свойств диалогового окна:

    GTK_DIALOG_MODAL
    ;

    GTK_DIALOG_DESTROY_WITH_PARENT
    ;

    GTK_DIALOG_NO_SEPARATOR
    .

    Вы можете комбинировать флаги с помощью поразрядной операции

    OR
    ; например, комбинация
    GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR
    означает одновременно и модальное окно, и окно без разделительной линии между основной областью окна и областью кнопок.

    Оставшиеся параметры — это

    NULL
    -терминированный список кнопок и код соответствующего отклика. Вы поймете, что именно означает этот код отклика, когда познакомитесь с функцией
    gtk_dialog_run
    . Обычно кнопки выбираются из длинного списка готовых кнопок, которые определяет GTK+, поскольку вы получите уже готовые пиктограммы в кнопках.

    Далее показано, как бы вы создавали диалоговое окно с кнопками OK и Cancel, которое возвращает

    GTK_RESPONSE_ACCEPT
    и
    GTK_RESPONSE_REJECT
    при нажатии этих кнопок:

    GtkWidget *dialog = gtk_dialog_new_with_buttons("Important question",

     parent_window,

     GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK,

     GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL,

     GTK_RESPONSE_REJECT, NULL);

    Мы остановились на двух кнопках, но на самом деле на количество кнопок в диалоговом окне нет ограничений. Более того, вы можете выбирать из ряда флагов типа отклика. Флаги

    accept
    (принять) и
    reject
    (отвергнуть) не применяются в стандарте GNOME и могут использоваться в ваших приложениях по вашему усмотрению. (Помните о том, что
    accept
    в вашем приложении должен означать "принять".) Другие варианты, включая отклик OK и CANCEL, приведены в типе
    GtkResponseType enum
    в следующем разделе.

    Естественно, вы должны вставить содержимое в ваше диалоговое окно и для этого объект

    GtkDialog
    содержит готовый упаковочный контейнер
    GtkVBox
    для заполнения виджетами. Вы получаете указатель прямо из объекта:

    GtkWidget *vbox = GTK_DIALOG(dialog)->vbox;

    Этот

    GtkVBox
    применяется обычным способом с помощью функции
    gtk_box_pack_start
    или чего-то подобного.

    После того как диалоговое окно создано, следующий шаг — представить его пользователю и ждать от него ответа. Сделать это можно двумя способами: в модальном режиме, который блокирует весь ввод за исключением диалогового окна, или в немодальном режиме, который воспринимает диалоговое окно как любое другое окно. Давайте сначала рассмотрим запуск модального диалогового окна.

    Модальное диалоговое окно

    Модальное диалоговое окно заставляет пользователя ответить до того, как сможет выполниться любое другое действие. Оно полезно в тех ситуациях, когда пользователь собирается сделать что-то, сопряженное с серьезными последствиями, или нужно вывести сообщения об ошибках и предупреждениях.

    Диалоговое окно можно сделать модальным, установив флаг

    GTK_DIALOG_MODAL
    и вызвав функцию
    gtk_widget_show
    , но есть лучший путь. Функция
    gtk_dialog_run
    выполнит за вас всю тяжелую работу, остановив дальнейшее выполнение программы до тех пор, пока не будет нажата кнопка в диалоговом окне.

    Когда пользователь нажимает кнопку (или диалоговое окно уничтожается), функция

    gtk_dialog_run
    возвращает результат типа
    int
    , указывающий на кнопку, нажатую пользователем. В GTK+ очень кстати определен тип
    enum
    для описания возможных значений.

    typedef enum {

     GTK_RESPONSE_NONE = -1,

     GTK_RESPONSE_REJECT = -2,

     GTK_RESPONSE_ACCEPT = -3,

     GTK_RESPONSE_DELETE_EVENT = -4

     GTK_RESPONSE_OK = -5,

     GTK_RESPONSE_CANCEL = -6,

     GTK_RESPONSE_CLOSE = -7,

     GTK_RESPONSE_YES = -8,

     GTK_RESPONSE_NO = -9,

     GTK_RESPONSE_APPLY = -10,

     GTK_RESPONSE_HELP = -11

    } GtkResponseType;

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

    gtk_dialog_new_with_buttons
    , — это код возврата типа
    GtkResponseType
    , который функция
    gtk_dialog_run
    возвращает, когда нажата кнопка. Если диалоговое окно уничтожается (это происходит, например, когда пользователь щелкает кнопкой мыши пиктограмму закрытия), вы получаете результат
    GTK_RESPONSE_NONE
    .

    Для вызова соответствующих операторов идеально подходит конструкция

    switch
    :

    GtkWidget* dialog = create_dialog();

    int result = gtk_dialog_run(GTK_DIALOG(dialog));

    switch(result) {

    case GTK_RESPONSE_ACCEPT:

     delete_file();

     break;

    сазе GTK_RESPONSE_REJECT:

     do_nothing();

     break;

    default:

     dialog_was_cancelled();

     break;

    }

    gtk_widget_destroy(dialog);

    Это все, что есть для простых модальных окон в комплекте инструментов GTK+. Как видите, включен очень небольшой программный код и потрачено немного усилий. В конце нужно только провести чистку с помощью функции

    gtk_widget_destroy
    .

    Если вам понадобится немодальное диалоговое окно, все будет не так просто. Вы не сможете использовать функцию

    gtk_dialog_run
    , вместо нее придется связать функции обратного вызова с кнопками диалогового окна.

    Немодальные диалоговые окна

    Мы рассмотрели, как применять функцию

    gtk_dialog_run
    для создания модального (блокирующего) диалогового окна. Немодальное окно действует несколько иначе, хотя и создается тем же способом. Вместо вызова функции
    gtk_dialog_run
    вы связываете функцию обратного вызова с сигналом отклика объекта
    GtkDialog
    , который генерируется при щелчке кнопки мышью или уничтожении окна.

    Связывание сигнала обратного вызова выполняется обычным образом с той лишь разницей, что у функции обратного вызова появляется дополнительный аргумент отклика, играющий ту же роль, что код возврата функции

    gtk_dialog_run
    . В приведенном далее фрагменте программного кода показаны основные принципы использования немодального диалогового окна:

    void dialog_button_clicked(GtkWidget *dialog, gint response,

     gpointer user_data) {

     switch (response) {

     case GTK_RESPONSE_ACCEPT:

      do_stuff();

      break;

     case GTK_RESPONSE_REJECT:

      do_nothing();

      break;

     default:

      dialog_was_cancelled();

      break;

     }

     gtk_widget_destroy(dialog);

    }


    int main() {

     ...

     GtkWidget *dialog = create_dialog();

     g_signal_connect(GTK_OBJECT(dialog), "response",

      GTK_SIGNAL_FUNC(dialog_button_clicked), user_data);

     gtk_widget_show(dialog);

     ...

    }

    С немодальными диалоговыми окнами могут возникать сложности, т.к. от пользователя не требуется немедленного ответа, и он может свернуть диалоговое окно и забыть о нем. Вы должны предусмотреть действия при попытке пользователя повторно открыть диалоговое окно до закрытия первого экземпляра окна. Следует проверить, равен ли

    NULL
    указатель диалогового окна и если нет, повторно вывести уже открытое диалоговое окно на экран, вызвав функцию
    gtk_window_present
    . Вы увидите этот прием в действии в разд. "Приложение для работы с базой данных компакт-дисков" в конце данной главы.

    GtkMessageDialog

    Для очень простых диалоговых окон даже тип

    GtkDialog
    излишне сложен.

    GtkDialog

     +----GtkMessageDialog

    С помощью типа

    GtkMessageDialog
    вы можете создать информационное диалоговое окно одной строкой программного кода.

    GtkWidget* gtk_message_dialog_new(GtkWindow *parent,

     GtkDialogFlags flags, GtkMessageType type,

     GtkButtonsType buttons, const gchar *message_format, ...);

    Эта функция создает диалоговое окно, снабженное пиктограммами, заголовком и настраиваемыми кнопками.

    Параметр
    type задает готовую пиктограмму и заголовок диалогового окна в соответствии с его предполагаемым назначением; например, окно с предупреждением содержит пиктограмму предупреждения в виде треугольника. Существует четыре возможных варианта для простых диалоговых окон, с которыми вы будете сталкиваться чаще всего:

    GTK_MESSAGE_INFO
    ;

    GTK_MESSAGE_WARNING
    ;

    GTK_MESSAGE_QUESTION
    ;

    GTK_MESSAGE_ERROR
    .

    Вы также можете выбрать значение

    GTK_MESSAGE_OTHER
    , применяемое в тех случаях, когда не используются перечисленные типы. Для окна типа
    GtkMessageDialog
    можно передать тип
    GtkButtonsType
    (табл. 16.4) вместо перечисления всех кнопок по очереди.


    Таблица 16.4

    Тип
    GtkButtonsType
    Описание
    GTK_BUTTONS_OK
    Кнопка OK
    GTK_BUTTONS_CLOSE
    Кнопка Close
    GTK_BUTTONS_CANCEL
    Кнопка Cancel
    GTK_BUTTONS_YES_NO
    Кнопки Yes и No
    GTK_BUTTONS_OK_CANCEL
    Кнопки OK и Cancel
    GTK_BUTTONS_NONE
    Нет кнопок

    Теперь остается только текст диалогового окна, который можно создать из строки подстановки, формируемой так же, как в функции

    printf
    . В данном примере вы спрашиваете пользователя, настаивает ли он на своем требовании удалить файл:

    GtkWidget *dialog = gtk_message_dialog_new(main_window,

     GTK_DIALOG_DESTROY_WITH_PARENT,

     GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,

     "Are you sure you wish to delete %s?", filename);

    result = gtk_dialog_run(GTK_DIALOG(dialog));

    gtk_widget_destroy(dialog);

    Это диалоговое окно будет отображаться так, как показано на рис. 16.14.

    Рис. 16.14


    Окно типа

    GtkMessageDialog
    — простейший способ обмена информацией или получения ответов на вопросы типа "да/нет". Вы воспользуетесь им в следующем разделе, когда примените полученные знания для создания GUI вашего приложения для работы с базой данных компакт-дисков.

    Приложение для работы с базой данных компакт-дисков

    В предыдущих главах вы разрабатывали базу данных компакт-дисков с помощью MySQL и интерфейса на языке С. Теперь вы увидите, как просто вставить внешний GUI средствами GNOME/GTK+ и создать пользовательский интерфейс с богатыми функциональными возможностями. 

    Примечание

    Для проверки примера приложения для работы с базой данных компакт-дисков у вас должны быть установлены СУБД MySQL и библиотеки разработки, т.е. должны выполняться те же самые требования, что и к аналогичному приложению в главе 8.

    Из соображений простоты и ясности мы создадим базовый скелетный интерфейс, в котором реализовано лишь подмножество функций — к примеру, вы не сможете добавлять информацию о дорожках в компакт-диски или удалять CD. Но вы увидите в вашем приложении в действии виджеты, обсуждавшиеся в этой главе, и поймете, как они применяются в реальных программах.

    Будет написан программный код для следующих ключевых действий:

    □ регистрация в базе данных из GUI;

    □ поиск компакт-диска;

    □ отображение сведений о компакт-диске и его дорожках;

    □ вставка компакт-диска в базу данных;

    □ создание окна About (О программе);

    □ формирование подтверждения при завершении работы пользователя.

    Разделим код на три файла, совместно использующие заголовочный файл cdapp_gnome.h. В исходных файлах функции создания окон и диалоговых окон — функции формирования интерфейса — отделены от функций обратного вызова (упражнения 16.11-16.14).

    Упражнение 16.11. Файл cdapp_gnome.h

    Сначала рассмотрим файл cdapp_gnome.h и функции, которые вы должны реализовать.

    1. Включите в исходный текст программы заголовочные файлы среды GNOME и заголовочный файл для функций интерфейса, разработанного вами в главе 8. В данном примере программы используются файлы app_mysql.h и app_mysql.c из главы 8 и созданная там же база данных.

    #include <gnome.h>

    #include "app_mysql.h"

    2. В типе

    enum
    обозначены столбцы виджета
    GtkTreeView
    , который вы будете применять для отображения сведений о компакт-дисках и их дорожках. 

    enum {

     COLUMN_TITLE,

     COLUMN_ARTIST,

     COLUMN_CATALOGUE,

     N_COLUMNS

    };

    3. У вас есть три функции создания окна в файле interface.c.

    GtkWidget *create_main_window();

    GtkWidget *create_login_dialog();

    GtkWidget *create_addcd_dialog();

    4. Функции обратного вызова для пунктов меню, панели инструментов, кнопок диалогового окна и кнопки поиска находятся в файле callbacks.с.

    /* Обратный вызов для выхода из приложения */

    void quit_app(GtkWidget* window, gpointer data);

    /* Обратный вызов для подтверждения завершения перед выходом */

    gboolean delete_event_handler(GtkWidget* window, GdkEvent *event,

     gpointer data);

    /* Обратный вызов, связанный с сигналом отклика диалогового окна addcd */

    void addcd_dialog_button_clicked(GtkDialog * dialog, gint response,

     gpointer userdata);

    /* Обратный вызов для кнопки Add CD меню и панели инструментов */

    void on_addcd_activate(GtkWidget *widget, gpointer user_data);

    /* Обратный вызов для кнопки меню About */

    void on_about_activate(GtkWidget* widget, gpointer user_data);

    /* Обратный вызов для кнопки поиска */

    void on_search_button_clicked(GtkWidget *widget, gpointer userdata);

    Упражнение 16.12. Файл interface.c

    Первым рассмотрим файл interface.c, в котором определяются окна и диалоговые окна, применяемые в приложении.

    1. Сначала несколько указателей виджетов, на которые вы ссылаетесь в файлах callbacks.c и main.c:

    #include "app_gnome.h"


    GtkWidget* treeview;

    GtkWidget* appbar;

    GtkWidget* artist_entry;

    GtkWidget *title_entry;

    GtkWidget *catalogue_entry;

    GtkWidget *username_entry;

    GtkWidget *password_entry;

    2. 

    app
    — глобальная переменная, указатель на главное окно:

    static GtkWidget *арр;

    3. Определите вспомогательную функцию, которая вставляет в контейнер виджет-метку с заданным текстом:

    void add_widget_with_label(GtkContainer *box,

     gchar *caption, GtkWidget *widget) {

     GtkWidget *label = gtk_label_new(caption);

     GtkWidget *hbox = gtk_hbox_new(TRUE, 4);

     gtk_container_add(GTK_CONTAINER(hbox), label);

     gtk_container_add(GTK_CONTAINER(hbox), widget);

     gtk_container_add(box, hbox);

    }

    4. Определения строки меню, использующие для удобства макросы

    GNOMEUIINFO
    :

    static GnomeUIInfo filemenu[] = {

     GNOMEUIINFO_MENU_NEW_ITEM("_New CD", NULL, on_addcd_activate, NULL),

     GNOMEUIINFO_SEPARATOR,

     GNOMEUIINFO_MENU_EXIT_ITEM(close_app, NULL),

     GNOMEUIINFO_END

    };

    static GnomeUIInfo helpmenu[] = {

     GNOMEUIINFO_MENU_ABOUT_ITEM(on_about_activate, NULL),

     GNOMEUIINFO_END

    };

    static GnomeUIInfo menubar[] = {

     GNOMEUIINFO_MENU_FILE_TREE(filemenu),

     GNOMEUIINFO_MENU_HELP_TREE(helpmenu),

     GNOMEUIINFO_END

    };

    5. Теперь вы создаете главное окно, вставляете меню и панель инструментов, задаете их размер, центрируете относительно экрана и собираете виджеты, формирующие интерфейс. Учтите, что эта функция не отображает окно на экране, а просто возвращает указатель на окно:

    GtkWidget *create_main_window() {

     GtkWidget* toolbar;

     GtkWidget* vbox;

     GtkWidget* hbox;

     GtkWidget* label;

     GtkWidget* entry;

     GtkWidget *search_button;

     GtkWidget* scrolledwindow;

     GtkCellRenderer *renderer;

     app = gnome_app_new("GnomeCD", "CD Database");

     gtk_window_set_position(GTK_WINDOW(app), GTK_WIN_POS_CENTER);

     gtk_window_set_defeult_size(GTK_WINDOW(app ), 540, 480);

     gnome_app_create_menus(GNOME_APP(app), menubar);

    6. Создайте панель инструментов с помощью стандартных пиктограмм GTK+ и свяжите с ней функции обратного вызова:

     toolbar = gtk_toolbar_new();

     gnome_app_add_toolbar(GNOME_APP(app), GTK_TOOLBAR(toolbar),

      "toolbar", BONOBO_DOCK_ITEM_BEH_EXCLUSIVE,

      BONOBO_DOCK_TOP, 1, 0, 0);

     gtk_container_set_border_width(GTK_CONTAINER(toolbar), 1);

     gtk_toolbar_insert_stock(GTK_TOOLBAR(toolbar), "gtk-add", "Add new CD",

      NULL, GTK_SIGNAL_FUNC(on_addcd_activate), NULL, -1);

     gtk_toolbar_insert_space(GTK_TOOLBAR(toolbar), 1);

     gtk_toolbar_insert_stock(GTK_TOOLBAR(toolbar), "gtk-quit",

      "Quit the Application", NULL, GTK_SIGNAL_FUNC(on_quit_activate), NULL, -1);

    7. Затем вы создаете виджеты, используемые для поиска компакт-диска:

     label = gtk_label_new("Search String:");

     entry = gtk_entry_new();

     search_button = gtk_button_new_with_label("Search");

    8. Окно

    gtk_scrolled_window
    предоставляет полосы прокрутки, позволяя виджету (в данном случае
    GtkTreeView
    ) превышать размеры окна:

     scrolledwindow = gtk_scrolled_window_new(NULL, NULL);

     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow),

      GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

    9. Далее скомпонуйте интерфейс, применяя стандартным способом виджеты-контейнеры:

     vbox = gtk_vbox_new(FALSE, 0);

     hbox = gtk_hbox_new(FALSE, 0);

     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);

     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);

     gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 6);

     gtk_box_pack_start(GTK_BOX(hbox), search_button, FALSE, FALSE, 5);

     gtk_box_pack_start(GTK_BOX(vbox), scrolledwindow, TRUE, TRUE, 0);

    10. Затем создайте виджет

    GtkTreeView
    , вставьте три столбца и поместите его в окно
    GtkScrolledWindow
    :

     treeview = gtk_tree_view_new();

     renderer = gtk_cell_renderer_text_new();

     gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),

      COLUMN_TITLE, "Title", renderer, "text", COLUMN_TITLE, NULL);

     gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),

      COLUMN_ARTIST, "Artist", renderer, "text", CQLUMN_ARTIST, NULL);

     gtk_tree_view_insert_column_with_attrihutes(GTK_TREE_VIEW(treeview),

      COLUMN_CATALOGUE, "Catalogue", renderer, "text", COLUMN_CATALOGUE, NULL);

     gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview),

      COLUMN_TITLE);

     gtk_container_add(GTK_CONTAINER(scrolledwindow), treeview);

    11. В заключение задайте содержимое главного окна, вставьте строку состояния

    GnomeApp
    и подсоедините нужные обратные вызовы:

     gnome_app_set_contents(GNOMEAPP(app), vbox);

     appbar = gnome_appbar_new(FALSE, TRUE, GNOME_PREFERENCES_NEVER);

     gnome_app_set_statusbar(GNOME_APP(app), appbar);

     gnome_app_install_menu_hints(GNOME_APP(app), menubar);

     g_signal_connect(GTK_OBJECT(search_button), "clicked",

      GTK_SIGNAL_FUNC(on_search_button_clicked), entry);

     g_signal_connect(GTK_OBJECT(app), "delete_event",

      GTK_SIGNAL_FUNC(delete_event_handler), NULL);

     g_signal_connect(GTK_OBJECT(app), "destroy",

      GTK_SIGNAL_FUNC(quit_app), NULL);

     return app;

    }

    12. Следующая функция создает простое диалоговое окно, позволяющее добавлять новый компакт-диск в базу данных. Оно состоит из полей ввода для исполнителя, названия и полей каталога, а также кнопок OK и Cancel:

    GtkWidget *create_addcd_dialog() {

     artist_entry = gtk_entry_new();

     title_entry = gtk_entry_new();

     catalogue_entry = gtk_entry_new();

     GtkWidget* dialog = gtk_dialog_new_with_buttons("Add CD",

      app,

      GTK_DIALOG_DESTROY_WITH_PARENT,

      GTK_STOCK_OK,

      GTK_RESPONSE_ACCEPT,

      GTK_STOCK_CANCEL,

      GTK_RESPONSE_REJECT,

      NULL);

     add_widget_with_label(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),

      "Artist", artist_entry);

     add_widget_with_label(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),

      "Title", title_entry);

     add_widget_with_label(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),

      "Catalogue", catalogue_entry);

     g_signal_connect(GTK_OBJECT(dialog), "response",

      GTK_SIGNAL_FUNC(addcd_dialog_button_clicked), NULL);

     return dialog;

    }

    13. База данных требует регистрации пользователя перед выполнением запросов к ней, поэтому данная функция создает диалоговое окно для ввода имени пользователя и пароля:

    GtkWidget *create_login_dialog() {

     GtkWidget* dialog = gtk_dialog_new_with_buttons("Database Login",

      app, GTK_DIALOG_MODAL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,

      GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);

     username_entry = gtk_entry_new();

     password_entry = gtk_entry_new();

     gtk_entry_set_visibility(GTK_ENTRY(password_entry), FALSE);

     add_widget_with_label(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),

      "Username", username_entry);

     add_widget_with_label(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),

      "Password", password_entry);

     gtk_widget_show_all(GTK_WIDGET(GTK_DIALOG(dialog)->vbox));

     return dialog;

    }

    Упражнение 16.13. callbacks.c

    Файл callbacks.с содержит функции, задающие обратные вызовы для виджетов пользовательского интерфейса.

    1. Сначала необходимо включить заголовочный файл и ссылки на некоторые определенные в файле interface.c глобальные переменные для чтения и изменения конкретных свойств виджетов:

    #include "app_gnome.h"


    extern GtkWidget *treeview;

    extern GtkWidget *app;

    extern GtkWidget *appbar;

    extern GtkWidget *artist_entry;

    extern GtkWidget *title_entry;

    extern GtkWidget *catalogue_entry;

    static GtkWidget *addcd_dialog;

    2. В функции

    quit_app
    вы вызываете функцию
    database_end
    для чистки и закрытия базы данных перед выходом:

    void quit_app(GtkWidget* window, gpointer data) {

     database_end();

     gtk_main_quit();

    }

    3. Следующая функция выводит простое диалоговое окно для подтверждения вашего желания завершить приложение, возвращая отклик в виде значения

    gboolean
    :

    gboolean confirm_exit() {

     gint result;

     GtkWidget* dialog = gtk_message_dialog_new(NULL,

      GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,

      GTK_BUTTONS_YES_NO, "Are you sure you want to quit?");

     result = gtk_dialog_run(GTK_DIALOG(dialog));

     gtk_widget_destroy(dialog);

     return (result == GTK_RESPONSE_YES);

    }

    4. 

    delete_event_handler
    — функция обратного вызова, которую вы связываете с событием главного окна
    Gdk delete event
    . Событие генерируется, когда вы пытаетесь закрыть окно до того (что существенно), как послан сигнал GTK+ уничтожения окна:

    gboolean delete_event_handler(GtkWidget* window, GdkEvent *event,

     gpointer data) {

     return !confirm_exit();

    }

    5. Следующая функция вызывается, когда мышью щелкается кнопка в диалоговом окне вставки компакт-диска. Если вы щелкнули мышью кнопку OK, программа копирует строки в массив типа char и передает его данные в интерфейсную функцию MySQL add_cd:

    void addcd_dialog_button_clicked(GtkDialog * dialog, gint response,

     gpointer userdata) {

     const gchar *artist_const;

     const gchar* title_const;

     const gchar *catalogue_const;

     gchar artist[200];

     gchar title[200];

     gchar catalogue[200];

     gint *cd_id;

     if (response == GTK_RESPONSE_ACCEPT) {

      artist_const = gtk_entry_get_text(GTK_ENTRY(artist_entry));

      title_const = gtk_entry_get_text(GTK_ENTRY(title_entry));

      catalogue_const = gtk_entry_get_text(GTK_ENTRY(catalogue_entry));

      strcpy(artist, artist_const);

      strcpy(title, title_const);

      strcpy(catalogue, catalogue_const);

      add_cd(artist, title, catalogue, cd_id);

     }

     addcd_dialog = NULL;

     gtk_widget_destroy(GTK_WIDGET(dialog));

    }

    6. Далее идет самая важная часть приложения: извлечение результатов поиска и заполнение объекта

    GtkTreeView
    :

    void on_search_button_clicked(GtkButton* button, gpointer userdata) {

     struct cd_search_st cd_res;

     struct current_cd_st cd;

     struct current_tracks_st ct;

     gint res1, res2, res3;

     gchar track_title[110];

     const gchar *search_string_const;

     gchar search string[200];

     gchar search_text[200];

     gint i = 0, j = 0;

     GtkTreeStore *tree_store;

     GtkTreeIter parent_iter, child_iter;

     memset(&track_title, 0, sizeof(track_title));

    7. Здесь вы получаете строку поиска из виджета ввода, копируете ее в переменную и выбираете соответствующие ID компакт-дисков:

     search_string_const = gtk_entry_get_text(GTK_ENTRY(userdata));

     strcpy(search_string, search_string_const);

     resl = find_cds(search_string, &cd_res);

    8. Затем вы обновляете

    appbar
    для вывода сообщения, информирующего пользователя о результатах поиска:

     sprintf(search_text, "Displaying %d result(s) for search string ' %s'",

      MIN(res1, MAX_CD_RESULT), search_string);

     gnome_appbar_push(GNOME_APPBAR(appbar), search_text);

    9. Теперь у вас есть результаты поиска, и можно заполнять ими модель

    GtkTreeStore
    . Для каждого ID компакт-диска необходимо извлечь соответствующую структуру типа
    current_cd_st
    , которая содержит название и исполнителя CD, и затем извлечь список дорожек диска. В заголовочном файле app_mysql.h задано ограничение количества элементов,
    MAX_CD_RESULT
    , для того, чтобы не было переполнения модели
    GtkTreeStore
    :

     tree_store = gtk_tree_store_new(N_COLUMNS,

      G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);

     while (i < res1 && i < MAX_CD_RESULT) {

      res2 = get_cd(cd_res.cd_id[i], &cd);

      /* В модель вставляется новая строка */

      gtk_tree_store_append(tree_store, &parent_iter, NULL);

      gtk_tree_store_set(tree_store, &parent_iter, COLUMN_TITLE,

       cd.title, COLUMN_ARTIST, cd.artist_name, COLUMN_CATALOGUE,

       cd.catalogue, -1);

      res3 = get_cd_tracks(cd_res.cd_id[i++], &ct);

      j = 0;

      /* Заполнение дерева дорожками текущего компакт-диска */

      while (j < res3) {

       sprintf(track_title, " Track %d. ", j+1);

       strcat(track_title, ct.track[j++]);

       gtk_tree_store_append(tree_store, &child_iter, &parent_iter);

       gtk_tree_store_set(tree_store, &child_iter,

        COLUMN_TITLE, track_title, -1);

      }

     }

     gtk_tree_view_set_model(GTK_TREE_VIEW(treeview),

     GTK_TREE_MODEL(tree_store));

    }

    10. Диалоговое окно

    addcd
    немодальное. Следовательно, перед его созданием и отображением вы проверяете, не активно ли оно уже:

    void on_addcd_activate(GtkMenuItem* menuitem, gpointer user_data) {

     if (addcd_dialog != NULL) return;

     addcd_dialog = create_addcd_dialog();

     gtk_widget_show_all(addcd_dialog);

    }


    gboolean close_app(GtkWidget * window, gpointer data) {

     gboolean exit;

     if ((exit = confirm_exit())) {

      quit_app(NULL, NULL);

     }

     return exit;

    }

    11. Когда вы щелкаете мышью кнопку About (О программе), раскрывается стандартное поле

    about
    среды GNOME:

    void on_about_activate(GtkMenuItem* menuitem, gpointer user_data) {

     const char* authors[] = {"Wrox Press", NULL};

     GtkWidget* about = gnome_about_new("CD Database", "1.0",

      " (c) Wrox Press", "Beginning Linux Programming",

      (const char **)authors, NULL, "Translators", NULL);

     gtk_widget_show(about);

    }

    Упражнение 16.14. Файл main.c

    Введите следующий программный код в файл main.с, содержащий функцию

    main
    программы.

    1. После операторов

    include
    вы ссылаетесь на поля ввода имени пользователя и пароля из файла interface.c:

    #include <stdio.h>

    #include <stdlib.h>

    #include "app_gnome.h"


    extern GtkWidget* username_entry;

    extern GtkWidget* password_entry;


    gint main(gint argc, gchar *argv[]) {

     GtkWidget *main_window;

     GtkWidget *login_dialog;

     const char *user_const;

     const char *pass_const;

     gchar username[100];

     gchar password[100];

     gint result;

    2. Инициализируйте как обычно библиотеки GNOME и затем создайте и отобразите на экране главное окно и диалоговое окно вашей регистрации:

     gnome_program_init("CdDatabase", "0.1", LIBGNOMEUI_MODULE, argc, argv,

      GNOME_PARAM_APP_DATADIR, "", NULL);

     main_window = create_main_window();

     gtk_widget_show_all(main_window);

     login_dialog = create_login_dialog();

    3. Вы ждете в цикле, пока пользователь не введет корректную комбинацию имени пользователя и пароля. Он может выйти из приложения, щелкнув мышью кнопку Cancel, причем в этом случае ему придется подтвердить свое действие:

     while (1) {

      result = gtk_dialog_run(GTK_DIALOG(login_dialog));

      if (result != GTK_RESPONSE_ACCEPT) {

       if (confirm_exit()) return 0;

       else continue;

      }

      user_const = gtk_entry_get_text(GTK_ENTRY(username_entry));

      pass_const = gtk_entry_get_text(GTK_ENTRY(password_entry));

      strcpy(username, user_const);

      strcpy(password, pass_const);

      if (database_start(username, password) == TRUE) break;

    4. Если функция

    database_start
    завершается аварийно, вы выводите сообщение и диалоговое окно регистрации снова отображается на экране:

      GtkWidget* error_dialog =

       gtk_message_dialog_new(GTK_WINDOW(main_window),

        GTK_DIALOG_DESTROY_WITH_PARENT,

        GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,

        "Could not log on! — Check Username and Password");

      gtk_dialog_run(GTK_DIALOG(error_dialog));

      gtk_widget_destroy(error_dialog);

     }

     gtk_widget_destroy(login_dialog);

     gtk_main();

     return 0;

    }

    5. Для компиляции этого приложения напишите make-файл. Как и в главе 8, вам, возможно, придется указать место расположения библиотеки mysql-клиента с помощью строки, подобной приведенной далее:

    -L/usr/lib/mysql

    После опции

    -L
    поместите каталог, в котором ваша система хранит библиотеки MySQL:

    all: app


    app: app_mysql.c callbacks.с interface.c main.с app_gnome.h app_mysql.h

     gcc -o app -I/usr/include/mysql app_mysql.с callbacks.с interface.c main.с -lmysqlclient `pkg-config --cflags --libs libgnome-2.0 libgnomeui-2.0`


    clean:

     rm -f app

    6. Теперь для компиляции приложения для работы с компакт-дисками просто воспользуйтесь командой

    make
    :

    make -f Makefile

    Когда вы запустите приложение арр, то получите ваше приложение для работы с базой данных компакт-дисков в стиле GNOME (рис. 16.15)!

    Рис. 16.15 

    Резюме

    В этой главе вы узнали о программировании с помощью библиотек GTK+/GNOME для создания приложений с профессионально выглядящем интерфейсом GUI. Сначала вы рассмотрели с X Window System и научились применять комплекты инструментальных средств, а затем вкратце познакомились с принципами работы GTK+ под управлением системы объектов и механизма сигналов/обратных вызовов этого комплекта инструментов.

    Далее вы перешли к API виджетов GTK+, продемонстрировав их применение на простых и более сложных примерах, приведенных в нескольких листингах программ. Рассмотрев виджет

    GnomeApp
    , вы научились легко создавать меню с помощью вспомогательных макросов. В заключение вы узнали, как создавать модальные и немодальные диалоговые окна для взаимодействия с пользователем.

    И в конце главы вы создали средствами GNOME/GTK+ интерфейс пользователя для вашей базы данных компакт-дисков, который позволяет регистрироваться в базе данных, искать компакт-диски и пополнять базу данных новыми CD.

    В главе 17 вы познакомитесь с комплектом инструментальных средств, конкурирующим с GTK+, и научитесь программировать в среде KDE, применяя комплект Qt.








    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх