Отправляет email-рассылки с помощью сервиса Sendsay

Практические советы по GTK+

  Все выпуски  

Практические советы по GTK+ - image_viewer 1.0


 

Image viewer 1.0

Попробуем написать настоящее приложение, имеющее практическое применение.
Пусть это будет программа для просмотра графических файлов,
что-то вроде ACDSee или XnView, но более лёгкая.
GТК содержит большой набор инструментов для управления графикой, в том
числе и картинками, поэтому сделать подобное приложение будет несложно.
В этом выпуске создадим каркас будущего приложения, а назовём его image_viewer.
Для начала приложение будет состоять из трёх файлов:
image_viewer.h    : основной заголовочный файл
image_viewer.c    : основной файл приложения
create_menu.c    : Создание меню и обработку выбора элементов в нём вынесем в отдельный файл

Главная функция будет выглядеть вот так:

int main(int argc, char* argv[])
{
    gtk_init(&argc,&argv);// Инициализация  GTK;

    // Создаём главное окно
    create_main_form();

    // выставляем первоначальный размер главного окна
    gtk_window_set_default_size (GTK_WINDOW (window), 640,(int)(480));
    // отображение основного окна
    gtk_widget_show_all(window);
    // главный цикл приложения;
    gtk_main();
    return 0;
}


После инициализации gtk_init идёт создание главного окна create_main_form,
которое для наглядности вынесено в отдельную функцию:

void create_main_form(void)
{
    GtkWidget *vbox;
    GtkWidget *vbox_in;
    gchar *str;

    // главное окно
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);// Главное окно создаем;  GTK_WINDOW_TOPLEVEL GTK_WINDOW_POPUP
    // Сигналы
    // сигнал ("delete_event") - принудительное завершение
    gtk_signal_connect( GTK_OBJECT( window ),"delete_event",GTK_SIGNAL_FUNC(gtk_false),NULL );//не передаем никаких данных в обработчик
    // Данный сигнал ("destroy") можно создать путем вызова функции  void gtk_widget_destroy( GtkWidget * );
    // Или он будет выработан, когда обработчик сигнала "delete_event" вернет значение FALSE.
    gtk_signal_connect( GTK_OBJECT( window ),"destroy",GTK_SIGNAL_FUNC(app_exit), NULL);
    // заголовок окна
    str = g_strdup_printf("%s %s","Image Viewer",VERSION);
    gtk_window_set_title(GTK_WINDOW(window),str);
    g_free(str);
   
    // главный виджет в окне
    vbox = gtk_vbox_new(FALSE,0);
    // упаковываем его в окно
    gtk_container_add(GTK_CONTAINER (window),vbox);

    // создаем меню;
    {
        GtkWidget *menubar;
        menubar = create_menubar();
        // добавляем меню в главный виджет vbox
        gtk_box_pack_start (GTK_BOX (vbox), menubar,FALSE, TRUE, 0);
    }
    // Ещё один вертикальный контейнер, будет внутри основного
    vbox_in = gtk_vbox_new(FALSE,4);
    // выставляем размер рамки вокруг виджета, то есть величину отступа по краю от родительского виджета
    gtk_container_set_border_width(GTK_CONTAINER (vbox_in),6);
    // добавляем vbox_in в главный виджет vbox
    gtk_box_pack_start (GTK_BOX (vbox), vbox_in,TRUE, TRUE,4);
   
    // Картинка - сделаём её из области для рисования
    draw_area = gtk_drawing_area_new();
    gtk_widget_set_size_request (draw_area, 100, 100);//set a minimum size
    // подключаем обработчик "expose_event" - функция map_expose будет вызываться при перерисовке окна
    g_signal_connect(G_OBJECT(draw_area), "expose_event",G_CALLBACK (map_expose), NULL);
    // Упаковываем draw_area в vbox_in
    gtk_box_pack_start (GTK_BOX (vbox_in), draw_area,TRUE, TRUE, 0);

    // Текстовая метка с информацией о картинке
    label_info = gtk_label_new("Файл: не загружен.");
    gtk_misc_set_alignment(GTK_MISC(label_info),0,0.5);// выравнивание текста: по OX - слева,по OX - по центру.
    // Упаковываем label_info в vbox_in
    gtk_box_pack_start (GTK_BOX (vbox_in), label_info,FALSE, FALSE, 0);
   
}

Для основного окна подключаем обработчик сигнала "destroy", который вырабатывается при уничтожении окна
gtk_signal_connect( GTK_OBJECT( window ),"destroy",GTK_SIGNAL_FUNC(app_exit), NULL);

Вот функция обработчика выхода из приложения:
static void app_exit(GtkWidget *widget, gpointer data)// выход
{
    printf("bye!\n");// Вывод на консоль, если она есть;
    gtk_main_quit();// завершение главного цикла приложения gtk_main()
}


Основной виджет в окне - вертикальный контейнер vbox, который упакован в главное окно.
В нём мы разместим меню menubar и ещё один вертикальный контейнер vbox_in,
но уже с отступом по краю в 6 пикселов.
В новом вертикальном контейнере vbox_in разместится область для рисования draw_area и
текстовая метка со служебной информацией о картинке.
В области для рисования draw_area будет отображён графический файл.
Обратите внимание, что при упаковке draw_area третий параметр expand в gtk_box_pack_start()
равен TRUE, это значит, что виджет draw_area будет пытаться занять всё доступное место в окне,
а раз он один такой, то практически вся область в окне принадлежит ему, что нам и надо.
gtk_box_pack_start(GTK_BOX (vbox_in),draw_area,TRUE, TRUE, 0);


Для draw_area подключаем обработчик сигнала "expose_event" - это функция map_expose,
которая будет вызываться каждый раз при перерисовке окна:
g_signal_connect(G_OBJECT(draw_area), "expose_event",G_CALLBACK (map_expose), NULL);

Вот её код:
static gboolean map_expose(GtkWidget *draw_area,GdkEventExpose *event,gpointer data)
{
    GdkGC *gc;
    GdkColor color;
    // размеры виджета draw_area
    gint dx;
    gint dy;
    // Узнаём размеры области вывода
    gdk_drawable_get_size (draw_area->window, &dx,&dy);
    gc = gdk_gc_new (draw_area->window);
    // выбираем цвет для рисования;
    color.red  = 65535;
    color.green= 0;
    color.blue = 0;
    gdk_gc_set_rgb_fg_color (gc, &color);
    // параметры линий;
    gdk_gc_set_line_attributes(gc,1,GDK_LINE_SOLID,GDK_CAP_ROUND,GDK_JOIN_ROUND);
    // Рисуем рамку
    gdk_draw_rectangle (draw_area->window,gc,FALSE,0,0,dx-1,dy-1);

    g_object_unref (gc);
    // return TRUE because we've handled this event, so no further processing is required.
    return TRUE;
}

Как известно, GTK+ все свои виджеты рисует через библиотеку GDK.
Для рисования чего-то отличного от виджетов GTK используются функции библиотеки GDK напрямую ( с префиксом gdk_ ).
Если GTK+ работает с виджетами, то GDK работает с GDK окнами.
GDK окна есть у большинства виджетов.
Получить указатель на GDK окно можно так:
GdkWindow *gdk_window = GTK_WIDGET(widget)->window;
GTK окона с GDK окнами нельзя путать, ведь GTK окно - это виджет GTK+, у которого также есть GDK окно.
Итак, воспользуемся GDK библиотекой и нарисуем рамку во весь виджет:
gdk_draw_rectangle(draw_area->window,gc,FALSE,0,0,dx-1,dy-1);


Создание меню вынесено в отдельную функцию create_menubar(),
код которой находится в файле create_menu.c

// Создать все меню
GtkWidget* create_menubar(void)
{
    GtkWidget *menubar;
    GtkWidget *menu;
    GtkWidget *menuitem;
   
    // создаём основное меню
    menubar = gtk_menu_bar_new();
    // Первый элемент главного меню [с подменю] - File
    menuitem = gtk_menu_item_new_with_mnemonic("_File");// создаём элемент меню
    gtk_menu_shell_append (GTK_MENU_SHELL (menubar), menuitem);// вставляем элемент меню в главное меню
    menu = gtk_menu_new();// создаём меню, которое будет появлять при выборе элемента File
    gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), menu);// добавляем в пунк меню File подменю.
    // ПодМеню
    {
        gchar *str;
        // menu item  - "Open File"
        str = g_strdup_printf(" %s  ","Open File");
        menuitem = create_my_menuitem(menu,str,"Ctrl+O","OpenFile");// создаём элемент меню OpenFile
        g_free(str);
        // menu item  - Разделитель
        menuitem = gtk_separator_menu_item_new();
        gtk_menu_shell_append(GTK_MENU_SHELL (menu), menuitem);// создаём элемент меню Разделитель
        // menu item  - "Quit"
        str = g_strdup_printf(" %s  ","Quit");
        menuitem = create_my_menuitem(menu,str,"Ctrl+Q","Quit");// создаём элемент меню Quit
        g_free(str);
    }
   
    // элемент главного меню  - О программе
    menuitem = gtk_menu_item_new_with_mnemonic("_About");// создаём элемент меню
    gtk_menu_shell_append(GTK_MENU_SHELL (menubar), menuitem);// вставляем элемент меню в главное меню
    // подключаем обработчик меню
    g_signal_connect(G_OBJECT (menuitem),"activate",G_CALLBACK (menuitem_response),"About");
    return menubar;
}

Создадим полоску главного меню без элементов:
menubar = gtk_menu_bar_new();
в которую будем вставлять элементы меню.
Создадим два элемента главного меню - File и About. Причём File будет содержать подменю, а About нет.
Итак, создаём элемент меню File:
menuitem = gtk_menu_item_new_with_mnemonic("_File");
где подчёркивание перед какой-либо буквой означает горячую клавишу.
Например, в нашем случае чтобы вызвать меню File, достаточно будет нажать Alt+F.
С русскими буквами такой трюк с горячей клавишей, к сожалению, не пройдёт.
Вставим  элемент меню File в главное меню:
gtk_menu_shell_append(GTK_MENU_SHELL(menubar),menuitem);
Для второго пункта меню About - всё аналогично.
Теперь нужно создать выпадающее меню, которое будет вызываться при выборе элемента File:
menu = gtk_menu_new();
Делаем новое выпадающее меню как подменю для File:
gtk_menu_item_set_submenu(GTK_MENU_ITEM (menuitem), menu);
Теперь создадим в подменю три пункта:Open File,разделитель,Quit.
Текстовые элементы в подменю создаются с помощью функции:
static GtkWidget* create_my_menuitem(GtkWidget *menu,gchar *labelmenu,gchar *labelmenu_r,gchar *active)
{
    GtkWidget *menuitem;
    GtkWidget *align;
    GtkWidget *label;
    // контейнер для выравнивания
    align = gtk_alignment_new(0,0.5,0,0);// выравнивание по левому краю, по высоте по центру и без расширения
    menuitem = gtk_menu_item_new();// создаём пункт меню
    label = gtk_label_new(labelmenu);// создаём текстовую метку
    gtk_container_add(GTK_CONTAINER (align),label);// добавляем в виджет для выравнивания текстовую метку
    if(!labelmenu_r)
        // если labelmenu_r не задана -> упаковываем в элемент меню только одну текстовую метку
        gtk_container_add (GTK_CONTAINER (menuitem), align);
    else
    {
        GtkWidget *hbox;
        GtkWidget *label2;
        // создаём горизонтальный контейнер
        hbox = gtk_hbox_new (FALSE, 0);
        label2 = gtk_label_new(labelmenu_r);
        gtk_box_pack_start (GTK_BOX (hbox), align, FALSE, FALSE, 0);// упаковываем в начало hbox;
        gtk_box_pack_end (GTK_BOX (hbox), label2, FALSE, FALSE, 0);// упаковываем в конец hbox;
        // упаковываем в элемент меню горизонтальный контейнер с двумя текстовыми метками
        gtk_container_add (GTK_CONTAINER (menuitem), hbox);
    }
    // вставляем созданный элемент меню в родительское меню menu
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    // подключаем обработчик меню
    g_signal_connect(G_OBJECT (menuitem),"activate",G_CALLBACK (menuitem_response),(gpointer)active);
    return menuitem;
}


Конечно, можно было бы использовать gtk_menu_item_new_with_mnemonic() в которую можно упаковать только одну текстовую метку,
мы пошли дальше и создали  элемент меню через gtk_menu_item_new() и упаковали туда две текстовые метки,
как-нибудь потом вставим туда ещё картинку.

На выбор элементов меню надо как-то реагировать, поэтому к каждому элементу подключаем обработчик выбора элемента меню
g_signal_connect(G_OBJECT (menuitem),"activate",G_CALLBACK (menuitem_response),(gpointer)active);
"activate" - это какой сигнал мы перехватываем,
menuitem_response - функция, которая будет вызываться при выборе элемента меню,
active - дополнительный параметр, передаваемый в эту функцию.

Обратите внимание, что для пункта главного меню About мы не создаём подменю и
подключаем обработчик меню непосредственно к элементу главного меню About:
g_signal_connect(G_OBJECT (menuitem),"activate",G_CALLBACK (menuitem_response),"About");

У нас получается, что на несколько пунктов меню повешан один и тот же обработчик, чтобы не плодить код.
Поэтому отличаться вызовы menuitem_response будут только дополнительным параметром
(в g_signal_connect последний параметр), в который мы поместим текст, уникальный для каждого элемента меню.

// обработчик выбора элемента меню
static void  menuitem_response (GtkMenuItem *menuitem,gpointer user_data)
{
    // какой элемент меню был выбран
    gchar *menu_name = (gchar*)user_data;
    printf("menuitem %s\n",menu_name);// печатаем в консоль - для отладки
    if (!strcmp(menu_name,"Quit"))// ВЫХОД
        gtk_widget_destroy(window);
}


Пока только при выборе подменю Quit будем уничтожать главное окно:
функция gtk_widget_destroy(window) кроме уничтожения главного окна, вызовет функцию обработчика app_exit()
в которой через gtk_main_quit() произойдёт выход из главного цикла приложения gtk_main()
и следовательно, выход из программы.
 
Рассмотренное приложение вместе с исходными кодами доступно по ссылке: image-viewer1.0.tar.gz  [56 кб]



В избранное