Spec-Zone.ru › GTK 3.24

Создание приложений

Приложение состоит из нескольких файлов:

Исполняемый файл

Он устанавливается в /usr/bin.

Файл рабочего стола

Файл рабочего стола предоставляет важную информацию об приложении оболочке рабочего стола, такую как его имя, значок, имя D-Bus, командную строку для запуска и т. д. Он устанавливается в /usr/share/applications.

Значок

Значок устанавливается в /usr/share/icons/hicolor/48x48/apps, где его можно найти независимо от текущей темы.

Схема настроек

Если приложение использует GSettings, оно установит свою схему в /usr/share/glib-2.0/schemas, чтобы инструменты, такие как dconf-editor, могли её найти.

Другие ресурсы

Другие файлы, такие как файлы интерфейса GtkBuilder, лучше загружать из ресурсов, хранящихся в самом исполняемом файле приложения. Это устраняет необходимость в большинстве файлов, которые традиционно устанавливаются в местоположение, специфичное для приложения, в /usr/share.

GTK+ включает поддержку приложений, построенную на основе GApplication. В этом руководстве мы создадим простое приложение, начиная с нуля, постепенно добавляя новые элементы. По пути мы узнаем о GtkApplication, шаблонах, ресурсах, меню приложений, настройках, GtkHeaderBar, GtkStack, GtkSearchBar, GtkListBox и многом другом.

Полные, готовые к компиляции исходные коды этих примеров можно найти в каталоге examples/ дистрибутива GTK+, или в онлайн-репозитории GTK+ в Git. Вы можете скомпилировать каждый пример отдельно, используя make с файлом Makefile.example. Более подробную информацию см. в README в каталоге примеров.

Тривиальное приложение

При использовании GtkApplication, функция main() может быть очень простой. Мы просто вызываем g_application_run() и передаём ей экземпляр нашего класса приложения.

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

Для обработки этих двух случаев мы переопределяем vfunc activate(), который вызывается при запуске приложения без аргументов командной строки, и vfunc open(), который вызывается при запуске приложения с аргументами командной строки.

Чтобы узнать больше о точках входа GApplication, обратитесь к документации GIO.

Ещё один важный класс, который входит в поддержку приложений в GTK+, — GtkApplicationWindow. Он также обычно подклассифицируется. Наш подкласс пока ничего не делает, поэтому мы получим пустое окно.

В качестве части начальной настройки нашего приложения, мы также создаём значок и файл рабочего стола.

Обратите внимание, что @bindir@ необходимо заменить фактическим путём к исполняемому файлу, прежде чем этот файл рабочего стола сможет быть использован.

Вот что мы достигли на данный момент:

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

Заполнение окна

На этом шаге мы используем шаблон GtkBuilder для связи файла интерфейса GtkBuilder с классом окна нашего приложения.

Наш простой файл интерфейса помещает GtkHeaderBar поверх виджета GtkStack. Панель заголовков содержит GtkStackSwitcher, который является отдельным виджетом для отображения ряда «вкладок» для страниц GtkStack.

Чтобы использовать этот файл в нашем приложении, мы возвращаемся к нашему подклассу GtkApplicationWindow и вызываем gtk_widget_class_set_template_from_resource() из функции инициализации класса для установки файла интерфейса в качестве шаблона для этого класса. Мы также добавляем вызов gtk_widget_init_template() в функцию инициализации экземпляра для инициализации шаблона для каждого экземпляра нашего класса.

 ...

static void
example_app_window_init (ExampleAppWindow *win)
{
  gtk_widget_init_template (GTK_WIDGET (win));
}

static void
example_app_window_class_init (ExampleAppWindowClass *class)
{
  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
                                               "/org/gtk/exampleapp/window.ui");
}

 ...
        

(полный исходный код)

Вы могли заметить, что мы использовали вариант функции, устанавливающей шаблон с помощью _from_resource(). Теперь нам нужно использовать функциональность ресурсов GLib, чтобы включить файл интерфейса в исполняемый файл. Это обычно выполняется путём перечисления всех ресурсов в файле .gresource.xml, таком как этот:

Этот файл должен быть преобразован в файл исходного кода C, который будет скомпилирован и связан с приложением вместе с другими исходными файлами. Для этого мы используем утилиту glib-compile-resources:

      glib-compile-resources exampleapp.gresource.xml --target=resources.c --generate-source
      

Теперь наше приложение выглядит так:

Открытие файлов

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

Для этого мы добавляем структуру данных для нашего подкласса окна приложения и сохраняем ссылку на GtkStack. Функция gtk_widget_class_bind_template_child_private() организует вещи так, чтобы после инициализации шаблона член stack структуры данных указывать на виджет с аналогичным именем из шаблона.

...

struct _ExampleAppWindowPrivate
{
  GtkWidget *stack;
};

G_DEFINE_TYPE_WITH_PRIVATE(ExampleAppWindow, example_app_window, GTK_TYPE_APPLICATION_WINDOW);

...

static void
example_app_window_class_init (ExampleAppWindowClass *class)
{
  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
                                               "/org/gtk/exampleapp/window.ui");
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), ExampleAppWindow, stack);
}

...
        

(полный исходный код)

Теперь мы возвращаемся к функции example_app_window_open(), которая вызывается для каждого аргумента командной строки, и создаём GtkTextView, который затем добавляем в качестве страницы в стек:

...

void
example_app_window_open (ExampleAppWindow *win,
                         GFile            *file)
{
  ExampleAppWindowPrivate *priv;
  gchar *basename;
  GtkWidget *scrolled, *view;
  gchar *contents;
  gsize length;

  priv = example_app_window_get_instance_private (win);
  basename = g_file_get_basename (file);

  scrolled = gtk_scrolled_window_new (NULL, NULL);
  gtk_widget_show (scrolled);
  gtk_widget_set_hexpand (scrolled, TRUE);
  gtk_widget_set_vexpand (scrolled, TRUE);
  view = gtk_text_view_new ();
  gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
  gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), FALSE);
  gtk_widget_show (view);
  gtk_container_add (GTK_CONTAINER (scrolled), view);
  gtk_stack_add_titled (GTK_STACK (priv->stack), scrolled, basename, basename);

  if (g_file_load_contents (file, NULL, &contents, &length, NULL, NULL))
    {
      GtkTextBuffer *buffer;

      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
      gtk_text_buffer_set_text (buffer, contents, length);
      g_free (contents);
    }

  g_free (basename);
}

...
        

(полный исходный код)

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

Наше приложение начинает принимать форму:

Меню приложения

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

Так же, как и шаблон окна, мы определяем меню приложения в файле интерфейса и добавляем его в качестве ресурса в наш исполняемый файл.

Для связи меню приложения с приложением, нам нужно вызвать gtk_application_set_app_menu(). Поскольку меню приложения работают путём активации GActions, нам также нужно добавить соответствующий набор действий в наше приложение.

Обе эти задачи лучше всего выполнять в vfunc startup(), который гарантированно будет вызван один раз для каждого экземпляра приложения:

...

static void
preferences_activated (GSimpleAction *action,
                       GVariant      *parameter,
                       gpointer       app)
{
}

static void
quit_activated (GSimpleAction *action,
                GVariant      *parameter,
                gpointer       app)
{
  g_application_quit (G_APPLICATION (app));
}

static GActionEntry app_entries[] =
{
  { "preferences", preferences_activated, NULL, NULL, NULL },
  { "quit", quit_activated, NULL, NULL, NULL }
};

static void
example_app_startup (GApplication *app)
{
  GtkBuilder *builder;
  GMenuModel *app_menu;
  const gchar *quit_accels[2] = { "<Ctrl>Q", NULL };

  G_APPLICATION_CLASS (example_app_parent_class)->startup (app);

  g_action_map_add_action_entries (G_ACTION_MAP (app),
                                   app_entries, G_N_ELEMENTS (app_entries),
                                   app);
  gtk_application_set_accels_for_action (GTK_APPLICATION (app),
                                         "app.quit",
                                         quit_accels);

  builder = gtk_builder_new_from_resource ("/org/gtk/exampleapp/app-menu.ui");
  app_menu = G_MENU_MODEL (gtk_builder_get_object (builder, "appmenu"));
  gtk_application_set_app_menu (GTK_APPLICATION (app), app_menu);
  g_object_unref (builder);
}

static void
example_app_class_init (ExampleAppClass *class)
{
  G_APPLICATION_CLASS (class)->startup = example_app_startup;
  ...
}

...
        

(полный исходный код)

Наш пункт меню «Настройки» ещё ничего не делает, но пункт «Выход» полностью функционален. Обратите внимание, что он также может быть активирован стандартной комбинацией клавиш Ctrl-Q. Сочетание клавиш было добавлено с помощью gtk_application_set_accels_for_action().

Меню приложения выглядит так:

Диалог настроек

Типичное приложение имеет некоторые настройки, которые следует сохранять между запусками. Даже для нашего простого примера приложения мы можем захотеть изменить шрифт, используемый для содержимого.

Мы будем использовать GSettings для хранения настроек. GSettings требует схемы, описывающей наши настройки:

Прежде чем мы сможем использовать эту схему в нашем приложении, нам нужно скомпилировать её в двоичный формат, ожидаемый GSettings. GIO предоставляет макросы для этого в проектах, основанных на autotools.

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

...

static void
example_app_window_init (ExampleAppWindow *win)
{
  ExampleAppWindowPrivate *priv;

  priv = example_app_window_get_instance_private (win);
  gtk_widget_init_template (GTK_WIDGET (win));
  priv->settings = g_settings_new ("org.gtk.exampleapp");

  g_settings_bind (priv->settings, "transition",
                   priv->stack, "transition-type",
                   G_SETTINGS_BIND_DEFAULT);
}

...
        

(полный исходный код)

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

На этом этапе приложение уже будет реагировать при изменении одной из настроек, например, с помощью командной строки gsettings. Конечно, мы ожидаем, что приложение предоставит диалог настроек для этого. Итак, давайте сделаем это сейчас. Наш диалог настроек будет подклассом GtkDialog, и мы будем использовать те же методы, что и раньше: шаблоны, структуры-члены, привязки настроек.

Давайте начнём с шаблона.

Далее следует подкласс диалога.

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

...

static void
preferences_activated (GSimpleAction *action,
                       GVariant      *parameter,
                       gpointer       app)
{
  ExampleAppPrefs *prefs;
  GtkWindow *win;

  win = gtk_application_get_active_window (GTK_APPLICATION (app));
  prefs = example_app_prefs_new (EXAMPLE_APP_WINDOW (win));
  gtk_window_present (GTK_WINDOW (prefs));
}

...
        

(полный исходный код)

После всей этой работы наше приложение теперь может отобразить диалог настроек так:

Добавление строки поиска

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

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

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

...

static void
search_text_changed (GtkEntry         *entry,
                     ExampleAppWindow *win)
{
  ExampleAppWindowPrivate *priv;
  const gchar *text;
  GtkWidget *tab;
  GtkWidget *view;
  GtkTextBuffer *buffer;
  GtkTextIter start, match_start, match_end;

  text = gtk_entry_get_text (entry);

  if (text[0] == '\0')
    return;

  priv = example_app_window_get_instance_private (win);

  tab = gtk_stack_get_visible_child (GTK_STACK (priv->stack));
  view = gtk_bin_get_child (GTK_BIN (tab));
  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));

  /* Very simple-minded search implementation */
  gtk_text_buffer_get_start_iter (buffer, &start);
  if (gtk_text_iter_forward_search (&start, text, GTK_TEXT_SEARCH_CASE_INSENSITIVE,
                                    &match_start, &match_end, NULL))
    {
      gtk_text_buffer_select_range (buffer, &match_start, &match_end);
      gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (view), &match_start,
                                    0.0, FALSE, 0.0, 0.0);
    }
}

static void
example_app_window_init (ExampleAppWindow *win)
{

...

  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), search_text_changed);

...

}

...
        

(полный исходный код)

Со строкой поиска наше приложение теперь выглядит так:

Добавление боковой панели

В качестве ещё одной функциональности мы добавляем боковую панель, которая демонстрирует GtkMenuButton, GtkRevealer и GtkListBox.

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

Как и ожидалось, меню «Шестерёнки» указано в файле пользовательского интерфейса GtkBuilder.

Для подключения пункта меню к настройке show-words мы используем GAction, соответствующую заданному ключу GSettings.

...

static void
example_app_window_init (ExampleAppWindow *win)
{

...

  builder = gtk_builder_new_from_resource ("/org/gtk/exampleapp/gears-menu.ui");
  menu = G_MENU_MODEL (gtk_builder_get_object (builder, "menu"));
  gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (priv->gears), menu);
  g_object_unref (builder);

  action = g_settings_create_action (priv->settings, "show-words");
  g_action_map_add_action (G_ACTION_MAP (win), action);
  g_object_unref (action);
}

...
        

(полный исходный код)

Так выглядит наше приложение сейчас:

Свойства

У виджетов и других объектов есть много полезных свойств.

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

Для этого мы добавим две метки в строку заголовка в нашем шаблоне окна, с именами lines_label и lines, и свяжем их с членами структуры в структуре-члене, как мы уже видели несколько раз.

Мы добавляем новый пункт меню «Строки» в меню «Шестерёнки», который запускает действие show-lines:

Чтобы сделать этот пункт меню функциональным, мы создаём действие свойства для свойства visible метки lines и добавляем его к действиям окна. В результате видимость метки будет переключаться каждый раз при активации действия.

Поскольку мы хотим, чтобы обе метки появлялись и исчезали вместе, мы связываем свойство visible виджета lines_label с тем же свойством виджета lines.

...

static void
example_app_window_init (ExampleAppWindow *win)
{
  ...

  action = (GAction*) g_property_action_new ("show-lines", priv->lines, "visible");
  g_action_map_add_action (G_ACTION_MAP (win), action);
  g_object_unref (action);

  g_object_bind_property (priv->lines, "visible",
                          priv->lines_label, "visible",
                          G_BINDING_DEFAULT);
}

...
        

(полный исходный код)

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

Это приведёт наше примерное приложение к такому виду:

Строка заголовка

Наше приложение уже использует GtkHeaderBar, но пока оно всё ещё получает обычную строку заголовка окна сверху. Это немного избыточно, и теперь мы сообщим GTK+, чтобы он использовал строку заголовка как замену для строки заголовка. Для этого мы переместим её, чтобы она стала прямым потомком окна, и установим её тип в titlebar.

#include <gtk/gtk.h>

#include "exampleapp.h"

int
main (int argc, char *argv[])
{
  return g_application_run (G_APPLICATION (example_app_new ()), argc, argv);
}

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

Если мы установим иконку окна для нашего окна, кнопка меню будет использовать её вместо общей иконки-заполнителя, которую вы видите здесь.

© 2005–2020 The GNOME Project
Licensed under the GNU Lesser General Public License version 2.1 or later.
https://developer.gnome.org/gtk3/3.24/ch01s04.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API