Книга: Основы программирования в Linux

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

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

В предыдущих главах вы разрабатывали базу данных компакт-дисков с помощью 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 

Оглавление книги


Генерация: 0.994. Запросов К БД/Cache: 2 / 0
поделиться
Вверх Вниз