Показаны сообщения с ярлыком gtk. Показать все сообщения
Показаны сообщения с ярлыком gtk. Показать все сообщения

воскресенье, 31 октября 2021 г.

Создаем GTK список (виджет)

 Приведу без комментариев код. 

// gtktable.c
#include <stdio.h>
#include <gtk-3.0/gtk/gtk.h>

GtkWidget* main_window;
GtkWidget* box1;
GtkWidget* scroll_window;

GtkWidget* list_widget;
GtkTreeStore* store_list;
enum{COL1, COL2, COL3, NCOL};
GtkTreeViewColumn *column;
GtkCellRenderer *renderer;
GtkTreeSelection* selection;
GtkTreeIter iter1;

gboolean
selection_func (GtkTreeSelection *selection,
                     GtkTreeModel     *model,
                     GtkTreePath      *path,
                     gboolean          path_currently_selected,
                     gpointer          userdata)
{
    g_print ("selection_func is called.\n");
    return TRUE;
}

void
create_gtk_table(){
    store_list = gtk_tree_store_new(NCOL, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING);
    list_widget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store_list));
    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list_widget));
    gtk_tree_selection_set_select_function(selection, selection_func, NULL, NULL);

    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list_widget), TRUE);
    gtk_tree_view_set_grid_lines(GTK_TREE_VIEW(list_widget),GTK_TREE_VIEW_GRID_LINES_BOTH);
    gtk_container_add(GTK_CONTAINER(scroll_window), list_widget);

    renderer = gtk_cell_renderer_text_new();
    g_object_set(G_OBJECT(renderer), "background", "grey", NULL);
    g_object_set(G_OBJECT(renderer), "foreground", "black", NULL);

    column = gtk_tree_view_column_new_with_attributes("Title1",renderer, "text", COL1, NULL);
    gtk_tree_view_column_set_clickable(column, TRUE);
    gtk_tree_view_append_column(GTK_TREE_VIEW(list_widget), column);

    column = gtk_tree_view_column_new_with_attributes("Title2",renderer, "text", COL2, NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(list_widget), column);

    column = gtk_tree_view_column_new_with_attributes("Title3",renderer, "text", COL3, NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(list_widget), column);

    g_object_unref(G_OBJECT(store_list));

    for(gint i = 0 ; i < 100; i++){
      gtk_tree_store_append(store_list, &iter1, NULL);
      gtk_tree_store_set(store_list, &iter1, COL1, i+1, COL2, "string ?", COL3, "the string!", -1);
    }
}

void
activate (GtkApplication *app,
          gpointer        user_data)
{
    main_window = gtk_application_window_new (app);
    gtk_window_set_title (GTK_WINDOW (main_window), "The Table in The Scrolling");
    gtk_window_set_default_size (GTK_WINDOW (main_window), 400, 500);
    gtk_container_set_border_width (GTK_CONTAINER (main_window), 20);

    box1 = gtk_button_box_new (GTK_ORIENTATION_VERTICAL);
    gtk_box_set_homogeneous (GTK_BOX(box1), FALSE);
    gtk_container_add (GTK_CONTAINER (main_window), box1);

    scroll_window = gtk_scrolled_window_new(NULL, NULL);
    gtk_widget_set_size_request(scroll_window, 350, 450);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
    gtk_box_pack_start(GTK_BOX (box1), scroll_window, FALSE, FALSE, 10);

    create_gtk_table();

    gtk_widget_show_all (main_window);
}

int main(int argc, char **argv) {
    GtkApplication *app;
    int status;
    app = gtk_application_new ("org.gtk.the_sin", G_APPLICATION_FLAGS_NONE);
    g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
    status = g_application_run (G_APPLICATION (app), argc, argv);
    g_object_unref (app);
    return status;
}

Код, который ответственнен за создание списка находится в функции  create_gtk_table. После выполнения эта программа выводит на дисплей вот такое:

Если для компиляции ипользовать CMake, то необходимо создать файл CMakeLists.txt следующего содержания:

cmake_minimum_required (VERSION 3.18)

project (gtktable)
find_package (PkgConfig REQUIRED)
pkg_check_modules (GTK3 REQUIRED gtk+-3.0)
include_directories (${GTK3_INCLUDE_DIRS})
link_directories (${GTK3_LIBRARY_DIRS})
add_executable(gtktable gtktable.c)
add_definitions (${GTK3_CFLAGS_OTHER})
target_link_libraries (gtktable ${GTK3_LIBRARIES})

Компилировалось и выполнялось на Debian Bullseye. Собственно, это все, что хотел сказать.

четверг, 14 октября 2021 г.

Простейшее испльзования систем CMake и Meson на примере gtk+ приложения

 Напишем простое gtk приложение, которое считает sin от вводимой величины. 

//   calc_sin.c
#include <gtk/gtk.h>
#include <math.h>

GtkWidget* main_window;
GtkWidget* box1;

GtkWidget* edit1;
GtkWidget* button1;
GtkWidget* label1;
GtkWidget* button_exit;

void
calc_sin(){
    double result = sin(atof(gtk_entry_get_text(GTK_ENTRY(edit1))));
    gchar *display = g_strdup_printf("%f", result);   
    gtk_label_set_text (GTK_LABEL(label1), display);
    g_free(display); 
}

void
activate (GtkApplication *app,
          gpointer        user_data)
{
      main_window = gtk_application_window_new (app);
      gtk_window_set_title (GTK_WINDOW (main_window), "The sin");
      gtk_window_set_default_size (GTK_WINDOW (main_window), 200, 400);
      gtk_container_set_border_width (GTK_CONTAINER (main_window), 20);

      box1 = gtk_button_box_new (GTK_ORIENTATION_VERTICAL);
      gtk_box_set_homogeneous (GTK_BOX(box1), FALSE);
      gtk_container_add (GTK_CONTAINER (main_window), box1);

      edit1=gtk_entry_new();
      gtk_box_pack_start(GTK_BOX (box1), edit1, FALSE, FALSE, 10);

      button1 = gtk_button_new_with_label("Calculate!");
      g_signal_connect (button1, "clicked", G_CALLBACK (calc_sin), NULL);
      gtk_box_pack_start(GTK_BOX (box1), button1, FALSE, FALSE, 10);

      label1 = gtk_label_new("result");
      gtk_box_pack_start(GTK_BOX (box1), label1, FALSE, FALSE, 10);

      button_exit = gtk_button_new_with_label ("Exit");
      gtk_box_pack_start(GTK_BOX (box1), button_exit, FALSE, FALSE, 10);
      g_signal_connect_swapped (button_exit, "clicked", G_CALLBACK
(gtk_widget_destroy), main_window);

      gtk_widget_show_all (main_window);
}

int
main (int    argc,
      char **argv)
{
    GtkApplication *app;
    int status;
      app = gtk_application_new ("org.gtk.the_sin", G_APPLICATION_FLAGS_NONE);
      g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
      status = g_application_run (G_APPLICATION (app), argc, argv);
      g_object_unref (app);
      return status;
}

 А теперь откомпилируем-отлинкуем этот код разными методами и выполним его. Предполагается, что cmake и meson уже установлены.

Сначала сделаем это "традиционно":

#gcc calc_sin.c `pkg-config  --cflags --libs  gtk+-3.0` -lm -o calc_sin_bin

 После выполнения полученного бинарника мы должны увидеть следующее:

В едитбокс вводим величину в радианах, нажимаем на кнопочку "Calculate!" и вместо слова "result" увидим величину синуса введенного значения.

Теперь соберем нашу программу с помощью CMake.
Создадим файл CMakeLists.txt следующего содержания:

cmake_minimum_required (VERSION 2.6)

project (calc_sin_prj)
find_package (PkgConfig REQUIRED)
pkg_check_modules (GTK3 REQUIRED gtk+-3.0)
include_directories (${GTK3_INCLUDE_DIRS})
link_directories (${GTK3_LIBRARY_DIRS})
add_executable(calc_sin_bin calc_sin.c)
add_definitions (${GTK3_CFLAGS_OTHER})
target_link_libraries (calc_sin_bin ${GTK3_LIBRARIES} m)

Этот файл в данном случае должен быть в директории с нашим исходником. Далее делаем такую последовательность команд:

#mkdir cmakebuilddir
#cd cmakebuilddir
#cmake ..
#cmake --build .

Теперь соберем нашу программу с помощью meson.
Создадим файл meson.build следующего содержания:

project('calc_sin_meson', 'c')
cc = meson.get_compiler('c')
m_dep = cc.find_library('m', required:false)
deps = [dependency('gtk+-3.0'), m_dep]
executable('calc_sin_bin', 'calc_sin.c', dependencies : deps)

 Делаем такую последовательность команд:

#meson setup mesonbuilddir
#cd mesonbuilddir
#meson compile

Во всех трех случаях получаем бинарник  calc_sin_bin с одинаковою функциональностью. Обратите внимание, что этот бинарник создается в случае meson в директории mesonbuilddir , а в случае CMake в директории  cmakebuilddir. Получается что дерево исходников находится отдельно, а все рабочие файлы каждой системной сборки (мезон и симэйк) находятся только в своей поддиректории соответственно. Аккуратно и логично.

среда, 21 августа 2013 г.

Понятие виджетов GNOME/GTK+

Попробуем коротко описать понятие виджета GTK. При изучении программирования на GTK необходимо понимать, что такое "программирование на Си в объектно-ориентированной манере". Речь идет не о объектном Си (языке программирования Objective C), а о об написании программ на "обычном" языке Си, но держа при этом написании в уме основные концепции ООП.

Объектно-ориентированный Си

  • Объектно-ориентированное программирование - это всего лишь метод организации данных и кодов. Библиотеки GNOME и GTK+ написаны на С, и их API состоит только из вызовов функций С. Тем не менее, код написан в объектно-ориентироанном стиле. Объектная ориентация не является частью языка С и ее пришлось имитировать. Имеется несколько характеристик объектов, относящихся к GNOME и GTK+, самыми важными из которых следует считать инкапсуляцию, наследование и методы.
  • Доступ к инкапсулированной информации может быть получен только посредством обращений к функциям, специально разработанным для этих целей. Эти функции называются методами.
  • Код, описывающий объект, называтся классом. Класс, из которого образуется другой класс, называется базовым. Все классы изначально происходят из GtkObject. Каждая структура включает в виде первого элемента полную структуру своего базового класса.
  • Для создания нового объекта используется метод. Все создающие объект функции имеют в своем имени слово new. Обычно функции, создающие  какой-либо виджет (кнопку, чекбокс и т.п.), возвращает указатель на структуру GtkWidget, а не структуру кнопки или чекбокса. Это возможно благодаря полиморфизму. Полиморфизм означает, что любой объект может рассматриваться в качестве одного из своих базовых классов.
  • Когда вызывается метод для работы с существующим объектом, адрес этого объекта передается как первый аргумент.
  • Методы всех классов в иерархии вызываются при использовании специальных макросов приведения типов данных. Для каждого виджета в GNOME и GTK+ имеется макрос приведения типов данных. Эти макросы делают несколько больше, чем просто приведение типов: во время работы приложения удостоверяют правильность приведения типов.
При программировании на  GTK важно понятие о событиях и их диспетчеризации.

 Последовательность диспетчеризации событий.

  • Происходит событие.
  • Соответствующая информация отправляутся менеджеру окон.
  • Менеджер окон имеет адрес очереди событий для каждого окна. Он преобразовывает полученные данные в стандартный для Х формат и ставит их в очередь, связанную с окном в которое послано событие.
  • Главный цикл приложения  использует низкоуровневую функцию системы Х для чтения событий из очереди. По идентификатору окна, присланному вместе с сообщением, программа определяет, какой из виджетов должен получить сигнал. Затем однп из функций GTK+ преобразует информацию в сигнал и передает его виджету.
  • Для каждого виджета существует функция, которая получает все поступающие сигналы.
  • Виджет просматривает список функций обратного вызова, которые связаны с определенным сигналом, и вызывает все эти функции.
Для создания сигнала можно применять функции g_signal_emit() g_signal_emit_by_name(). Соответствующие функции с префиксом gtk_ вместо g_ устарели и не рекомендуются для применения в "свежем" коде.Каждая функция обратного вызова имеет два общих параметра, но многие из функций имеют еще дополнительные параметры. Прототип самой простой функции обратного вызова выглядит так: 
void cb(GtkWidget*, gpointer); 
Если имеются дополнительные параметры, то они вставляются между двумя параметрами этого прототипа. Также кроме пустого типа могут возвращаться типы gint и boolean. На самом нижнем уровне создание события - это создание события Xevent. Xevent содержит тег, идентифицирующий окно, для которого это событие предназначено. Каждому событию соответствует своя структура. Каждая структура определена в виде двух частей. Первая - это собственно сама структура, а вторая - оператор typedef для этой структуры. Имена различаются только первым символом: имя структуры начинается символом подчеркивания.GdkEvent - это объединение нескольких структур событий. Чтобы облегчить задачу определения типа события и соответствующей ей структуры, первым полем во всех структурах является поле type. Для доступа к данным события напрямую можно выполнить приведение типа объединения к типу структуры. Какое именно приведение надовыполнить определяем из поля type.Информация о каждом событии содержится в структуре GSignalQuery. Ее поля заполняются с помощью функции g_signal_query().
typedef struct {
    guint signal_id;
    const gchar *signal_name;
    GType itype;
    GSignalFlags signal_flags;
    GType return_type; /* mangled with G_SIGNAL_TYPE_STATIC_SCOPE flag */
    guint n_params;
    const GType *param_types; /* mangled with G_SIGNAL_TYPE_STATIC_SCOPE flag */
} GSignalQuery;
У каждого виджета есть внутренние установки, которые прикладная программа может читать или записывать.Приложению необходим интерфейс для обращения к этим установкам (аргументам)  внутри указанного виджета. То есть, чтобы прочитать или записать одно из этих значений, программа должна знать имя виджета, имя переменной и тип данных этой переменной. Итак, каждый аргумент имеет тип данных и права чтения/записи. Большинство аргументов имеют права и чтения и записи.Есть два способа установить значение аргументв внутри виджета.Есть фунции, закрепленные к определенному аргументу, который можно изменять с помощью этой функции. Соответственно есть фукция для чтения этого аргумента.Второй способ заключается в использовании структуры GtkArg, у которой три поля: GtkType - определяет тип данных, указательна строку, определяющую имя аргумента, и объединение различных типов данных.Когда виджет рождается и проходит свой путь от шаблона бит в памяти до графического объекта на экране, то происходят процессы создания, реализации, преобразования и отображения. При уничтожении виджета происходят: скрывание, уничтожение соответствия, деактивизация и разрушения. Виджет может скрываться и появляться вновь. При перемещении из одного окна в другое происходят уничтожение реализации, а потом реализация.Указанным этапам соответствуют такие функции:
*_new()
gtk_widget_destroy()
gtk_widget_unref()
gtk_widget_show()
gtk_widget_hide()
gtk_widget_map()
gtk_widget_unmap()
gtk_widget_realize()
gtk_widget_unrealize()
У виджетов есть также до пяти так называемых стилей (состояний):
GTK_STATE_NORMAL
GTK_STATE_ACTIVE
GTK_STATE_PRELIGHT
GTK_STATE_SELECTED
GTK_STATE_INSENSITIVE
Для первого "философского" понимания, что такое виджет GTK - этого достаточно.

четверг, 28 июня 2012 г.

Создание GTK+ приложения на Оbjective-C

Создаем файлы такого содержания:
MainWindow.h:


#import <gtk/gtk.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>

id myMainWindow;

@interface MainWindow : NSObject{
GtkWidget *winMain; //главное GtkWindow
GtkWidget *button;
}


-(id)initWithArgCount:(int *)argc andArgVals:(char *[])argv;
-(void)destroyWidget; /* Освобождает ранее занятые ресурсы Gtk виджетами */
-(void)startGtkMainLoop; /* Стартует главный цикл обработки сообщений GTK */
-(void)printSomething; /* Пример некой Objective-C функции */

/** Cи фунции обратного вызова (обработчики сообщений)*/

void on_MainWindow_destroy(GtkObject *object, gpointer user_data);/* Вызывается, когда закрывается главное окно приложения */
void on_btnPushMe_clicked(GtkObject *object, gpointer user_data); /* Вызывается, когда мы жмем кнопку */

@end



MainWindow.m:


#import "MainWindow.h"

@implementation MainWindow

-(id)initWithArgCount:(int *)argc andArgVals:(char *[])argv{
if (self = [super init]) { //вызываем init родительского класса
winMain = gtk_window_new (GTK_WINDOW_TOPLEVEL); //создаем главное окно приложения
gtk_window_set_title (GTK_WINDOW (winMain), "Ну привет");
gtk_window_set_default_size(GTK_WINDOW(winMain), 230, 150);
button = gtk_button_new_with_label ("Нажми её!");//создаем кнопку
gtk_container_add (GTK_CONTAINER (winMain), button);
g_signal_connect (winMain, "destroy", G_CALLBACK (on_MainWindow_destroy), NULL);
//привязываем обработчики сообщений
g_signal_connect (button, "clicked", G_CALLBACK (on_btnPushMe_clicked), NULL);
gtk_widget_show_all(winMain); //показываем
}

myMainWindow = self; //назначаем C-compatible указатель
return self; //возвращаем указатель на этот объект
}

-(void)startGtkMainLoop{
gtk_main(); //стартуем основной цикл обработки сообщений gtk
}

-(void)printSomething{
NSLog(@"Printed from Objective-C`s NSLog function.");
printf("Also printed from standard printf function.\n");
}

-(void)destroyWidget{
myMainWindow = NULL;
if(GTK_IS_WIDGET (button)){
gtk_widget_destroy(button); //разрушаем кнопку
}
if(GTK_IS_WIDGET (winMain)){
gtk_widget_destroy(winMain); //разрушаем главное окно приложения
}
}

-(void)dealloc{
[self destroyWidget];
[super dealloc];
}

void
on_MainWindow_destroy(GtkObject *object, gpointer user_data){
gtk_main_quit(); //выходим из основного цикла обработки сообщений
}

void
on_btnPushMe_clicked(GtkObject *object, gpointer user_data){
printf("Button was clicked\n");
[myMainWindow printSomething]; //вызываем Objective-C функцию из Cи функции с помощью глобального указателяusing
}

@end


main.m:


#import "MainWindow.h"
#import <Foundation/NSAutoreleasePool.h>

int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
//создаем пул
gtk_init(&argc, &argv); //инициализируем gtk
MainWindow *mainWindow = [[MainWindow alloc] initWithArgCount:&argc andArgVals:argv];
//создаем GUI
[mainWindow startGtkMainLoop]; //стартуем главный цикл обработки сообщений GTK loop
[mainWindow release]; //освобождаем ресурсы занятые GUI
[pool release]; //освобождаем пул
return 0; //выходим из приложения
}


Компилируем это все из текущей директории следующей командой:
$ gcc `pkg-config --cflags --libs gtk+-2.0` -lgnustep-base -fconstant-string-class=NSConstantString MainWindow.m main.m -I /usr/include/GNUstep/ -L /usr/lib/GNUstep/ -std=c99 -O3

Запускаем на выполнение: ./a.out и видим:

Также выводиться сообщение в консоль.
Стоит ли говорить, что необходимо наличие установленных GTK и Objective-C? Необходимы пакеты gnustep-core-devel и libgtk2.0-dev. Все действия проводились на машине с установленной Debian Wheeze.
Да, еще надо сделать следующее. Если выскакивает ошибка "error: #error The current setting for native-objc-exceptions does not match that of gnustep-base ... please correct this." необходимо в файле /usr/include/GNUstep/GNUstepBase/GSConfig.h строку (221-я):
#define BASE_NATIVE_OBJC_EXCEPTIONS 1
заменить на:
#define BASE_NATIVE_OBJC_EXCEPTIONS 0

среда, 2 февраля 2011 г.

Пример простейшего применения Glade для создания графического интерфейса приложения

При использовании Glade значительно сокращается объем кода. Код, который соответствует собственно описанию интерфейса, практически полностью "переезжает" в xml-файл, который можно "подсоединить" вызовом функции gtk_builder_add_from_file. Упомянутый xml-файл создается в Glade.
Здесь продемонстрируется минимальный код приложения, которое показывает в главном окне всего два виджета: GtkEntry и GtkLabel. При вводе некоего текста в GtkEntry этот текст будет отображаться в GtkLabel.
Создание соответствующего интерфейса в Glade тривиально и больших комментариев не требует. Запускаем Glade.

Оставляем как есть. Жмем Close.
Далее создаем окно приложения:

Добавляем в него вертикальный ящик с тремя ячейками:

Далее вставляем в эти ячейки наши GtkEntry и GtkLabel, а также кнопку выхода из приложения:


Для обработки сигнала clicked кнопки выхода выбираем стандартный gtk_main_quit. А для обработки сигнала changed (находим его в GtkEditable) выбираем любой предлагаемый вариант (не возбраняется и свой вариант набрать). Выберем on_entry1_changed. Не забываем потом написать код для этой функции.
А вот собственно код нашего приложения:

/*labelAndEntry.c*/
#include <gtk/gtk.h>

typedef struct _ddata ddata;
struct _ddata{
GtkWidget* window1;
GtkWidget* entry1;
GtkWidget* label1;
GtkWidget* button1;
} ;

ddata* myData;

int
main( int argc, char **argv )
{
GtkBuilder *builder;
GtkWidget *window;
GError *error = NULL;

/* инициализируем GTK+ */
gtk_init( &argc, &argv );

/* создаем новый объект типа GtkBuilder */
builder = gtk_builder_new();
/* загружаем пользовательский интерфес из файла, который мы создали в Glade */
if( ! gtk_builder_add_from_file( builder, "entryAndLabel.xml", &error ) )
{
g_warning( "%s", error->message );
g_free( error );
return( 1 );
}

myData = g_slice_new(ddata);
myData->window1 = GTK_WIDGET( gtk_builder_get_object( builder, "window1" ) );
myData->entry1 = GTK_WIDGET( gtk_builder_get_object( builder, "entry1" ) );
myData->label1 = GTK_WIDGET( gtk_builder_get_object( builder, "label1" ) );
myData->button1 = GTK_WIDGET( gtk_builder_get_object( builder, "button1" ) );
/*
заметим, что имена полей структуры myData не обязаны совпадать со вторым аргументом функции gtk_builder_get object, они произвольны. Но этот второй аргумент должен совпадать с именем виджета, которое мы задали ему в окне "Doc Properties" Glade во вкладке General
*/
/* Get main window pointer from UI */
// window = GTK_WIDGET( gtk_builder_get_object( builder, "window1" ) );

/* "связываем" сигналы с объектами графического интерфейса */
// gtk_builder_connect_signals( builder, NULL );
gtk_builder_connect_signals( builder, myData );
/* разрушаем объект builder, так как он нам больше не нужен */
g_object_unref( G_OBJECT( builder ) );

/* даем команду показать главное окно приложения, а остальные виджеты отображаются объектом GtkBuilder */
gtk_widget_show( myData -> window1 );

/* стартуем */
gtk_main();
g_slice_free(ddata, myData);
return( 0 );
}
void
on_entry1_changed (GtkEditable *editable,
gpointer user_data) {
puts("changed!");
gtk_label_set_text(GTK_LABEL(myData->label1), gtk_entry_get_text(GTK_ENTRY(editable) ));
}
/*
имя этой функции должно совпадать с именем, которое мы дали обработчику соответствующего события (у нас - changed) в окне "Doc Properties" и вкладке Signals.
*/

Компилируем:
$ gcc -o labelAndEntry labelAndEntry.c $(pkg-config --cflags --libs gtk+-2.0 gmodule-2.0)
Запускаем на выполнение. Должно получиться следующее:

Если запустить наше прилодение из командной строки, то также при вводе какого-либо символа в GtkEntry, в консоли будет выводиться сообщение "changed!". При нажатии на кнопку будет осуществлен выход.
Все вышеописанное выполнялось в Debian squeeze.

воскресенье, 23 января 2011 г.

Компиляция приложения gtk в среде windows 7

Если у нас есть программа, написанная с использованием gtk, то она может работать и на юникс-системах и на windows. Ее исходники не надо переписывать отдельно для каждой платформы. Просто этот исходник надо откомпилировать на целевой платформе. Здесь мы и рассмотрим как это делается для windows.
Для того чтобы компилировать исходники gtk программы в среде windows нам необходимо проинсталлировать в windows такие пакеты: GTK+, MinGW, MSYS. Дадим очень краткое определение этим трем пакетам. GTK+ - это набор инструментариев для создания графического интерфейса. MinGW - минимальная среда разработки GNU для windows. MSYS - юниксоподобная командная оболочка для windows.
Начинаем.
Идем сначала на сайт www.gtk.org находим раздел загрузки GTK+ для windows и выкачиваем all-in-one bundles (переводится "все в одной связке", а вообще можно по отдельным компонентам выкачивать, но это не для нашего случая).
Затем идем на www.mingw.org и выкачиваем пакет MinGW. В нем содержатся компилятор gcc и GNU binutils (ассемблер, редактор связей, менеджер архивов) для windows.
Ссылка для загрузки перебросит нас на sourceforge.net. Там, я так понял тоже можно почастям этот пакет устанавливать, но мы возьмем установщик MinGW через сеть: Download mingw-get-inst-xxxxx.exe.
"Визуализирую" шаги инсталлирования:



Далее принимаем лицензионное соглашение. И указываем путь, по которому устанавливаем MinGW. Принимаем установку по умолчанию: C:\MinGW. Хотя я бы предпочел другой путь - в "Programm Files". Мне кажется, что так логичней. Оставим это как "домашнее задание". Далее вас спросят какой из предоставляемых компиляторов вы хотите видеть в своей системе (я выбрал компилятор си и фортрана), а также предоставят выбор ставить или не ставить MSYS Basic System, MinGW Developer Toolkit и MSYS System Builder. Я выбрал для установки все три, но это (в моем случае) не избавило меня устанавливать MSYS отдельно.


Изменяем системную переменную Path. Добавляем в конец строки значения переменной: C:\MinGW\bin\;
MinGW установлен. Теперь займемся MSYS. Идем на www.mingw.org/wiki/MSYS и выкачаем его инсталлятор. Запустим на выполнение:

Читаем лицензию и другую полезную информацию (ну жмем постоянно кнопку next). Далее выбираем путь, по которому будет устанавливаться MSYS. В нашем случае это: C:\MinGW\msys\1.0. Эта директория существует. И вас спросят, перезаписать ли ее. Отвечаем утвердительно. Далее будет задано несколько вопросов из консоли, то есть вам надо будет нажимать "y", а не NEXT.


На этом с MSYS покончено. Теперь мы можем вызывать командную оболочку MinGW из меню windows и со всеми юниксовыми особенностями и удобствами в ней работать.

Займемся теперь вплотную GTK:
Так же необходимо изменить переменную Path. Добавляем к строке, определяющей эту переменную следующее: C:\GTK\bin\;. Далее просто распаковываем содержимое "all-in-one bundles" в директорию C:\GTK. Тест на то, все ли в порядке, прост. Запускаем MinGW shell и из него смотрим, видит ли команда pkg-config библиотеки и заголовочные файлы GTK:

Также из оболочки можно запустить, входящую в комплект поставки этого "bundle" приложение gtk-demo. С помощью его можно изучать как писать GTK программы:

Теперь создадим файл demo-gtk.c следующего содержания:

#include <gtk/gtk.h>

static gboolean delete_event( GtkWidget *widget,
GdkEvent *event,
gpointer data )
{
g_print ("delete event occurred\n");
return FALSE;
}

static void destroy( GtkWidget *widget,
gpointer data )
{
gtk_main_quit ();
}

int main( int argc,
char *argv[] )
{
GtkWidget *window;
GtkWidget *label;

gtk_init (&argc, &argv);

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (window, "delete-event", G_CALLBACK (delete_event), NULL);
g_signal_connect (window, "destroy", G_CALLBACK (destroy), NULL);
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
gtk_widget_show (window);

label=gtk_label_new("HELLO!");
gtk_container_add (GTK_CONTAINER (window), label);
gtk_widget_show (label);

gtk_main ();

return 0;
}

и компилируем командой из MinGW shell, запускаем на выполнение и видим результат:

Файл находится и команда компиляции запускается с директории, которая в терминах windows находится (в нашем случае) по пути C:\MinGW\msys\1.0\home\user, где user - это наша учетная запись в windows.
Для короткого введения в данную тему - все.

воскресенье, 21 ноября 2010 г.

Сцены OpenGL в виджетах GTK

Весь интернет пестрит примерами использования OpenGL. Найти их не сложно. OpenGL сам по себе является кроссплатформенным. А поэтому, чтобы создать сцену OpenGL вам нужно знать только сам OpenGL. А вот когда сама сцена создана, встает вопрос - и куда мы эту сцену будем "запихивать"? Вот тут-то и всплывает "однобокость" этого великого разнообразия примеров OpenGL: они демонстрируют создание упомянутых сцен в одном единственном окне, окне библиотеки GLUT. Эта библиотека, как я понял, и была создана всего-навсего для написания "образовательных" и демонстрационных целей. И эти цели она выполнила и даже перевыполнила. Также к ее плюсам можно добавить ее кроссплатформенность. Ее реализиция есть и для систем UNIX и для Windows. Но ведь сцены OpenGL не являются необходимыми "сами по себе". Понятно же, что приложение, которое представляет из себя одно единственное окно с ,пусть с даже очень впечатляющим и красивым, двух- или трех- мерным изображением, вряд ли представит из себя какой-либо интерес, кроме, как мы уже указали, демонстрационного.
Здесь мы рассмотрим как вставить сцену OpenGL в виджет GTK. Интерфейс любого приложения GTK является набором, комбинацией так-называемых виджетов, размещенных, как укажет программист, на окнах приложения. Четкого определения, что такое виджет нет, из-за того, что это очень общее, объемное, понятие. Причем его "общность" истинна, так как на самом деле понять, что такое виджет совсем просто. Сложнее рассказать. Виджет - это "нечто", что можно отобразить на эране, окне приложения, диалоге. Любая кнопка, элемент управления, некая область, которая четко отделена и распознаваема от остальной части дилога, окна и есть виджет. Виджет GTK- это просто некий тип, структура GtkWidget.
Вот мы и подобрались вплотную к основной теме. Рассмотрим два виджета, которые позволяют рисовать сцены OpenGL в себя. Эти виджеты: gtkglarea и gtkglext. Имеются их реализации в виде пакетов для. самых распространенных по-крайней мере, дистрибутивов Linux. Также есть их порты для FreeBSD, что заставляет меня предполагать, что они есть для всех систем BSD.
Рассмотрим сначала gtkglarea.
Создаем файл gtkglarea_demo.c следующего содержания (вообще-то это совсем немного измененный для собственных нужд файл взятый отсюда):


#include <GL/gl.h>
#include <GL/glu.h>
#include <gtk/gtk.h>
#include <gtkgl/gtkglarea.h>

gint glarea_button_press (GtkWidget*, GdkEventButton*);
gint glarea_draw (GtkWidget*, GdkEventExpose*);
gint glarea_reshape (GtkWidget*, GdkEventConfigure*);
gint glarea_init (GtkWidget*);
gint glarea_destroy (GtkWidget*);
int main (int, char**);

gint
glarea_button_press (GtkWidget* widget, GdkEventButton* event) {
int x = event->x;
int y = event->y;
if (event->button == 1) {
g_print ("Button 1 press (%d, %d)\n", x, y);
return TRUE;
}
if (event->button == 2) {
g_print ("Button 1 press (%d, %d)\n", x, y);
return TRUE;
}
return FALSE;
}

gint
glarea_draw (GtkWidget* widget, GdkEventExpose* event) {
if (event->count > 0) {
return(TRUE);
}
g_print ("Expose Event\n");
if (gtk_gl_area_make_current(GTK_GL_AREA(widget))) {
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* */
/* Insert your drawing code here. */
/* */
gtk_gl_area_swapbuffers (GTK_GL_AREA(widget));
}
return (TRUE);
}

gint
glarea_reshape (GtkWidget* widget, GdkEventConfigure* event) {
int w = widget->allocation.width;
int h = widget->allocation.height;
g_print ("Reshape Event\n");
if (gtk_gl_area_make_current (GTK_GL_AREA(widget))) {
/* This is an example 2D reshape function. Writing reshape */
/* functions is beyond the scope of this demo. Check the */
/* red book or the www.opengl.org for more information on */
/* how to write reshape code to suit your needs. */
glViewport (0, 0, w, h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluOrtho2D (-(w>>1), (w>>1), -(h>>1), h>>1);
glMatrixMode (GL_MODELVIEW);
}
return (TRUE);
}

gint
glarea_init (GtkWidget* widget) {
g_print ("Realize Event\n");
if (gtk_gl_area_make_current (GTK_GL_AREA(widget))) {
/* Insert your OpenGL initialization code here */
}
return TRUE;
}

gint
glarea_destroy (GtkWidget* widget) {
g_print ("GTK GL Area Destroy Event\n");
/* Insert any required cleanup */
/* code here. */
return TRUE;
}

int
main (int argc, char** argv) {
GtkWidget* window;
GtkWidget* button_quit;
GtkWidget* box_main;
GtkWidget* glarea;
/* These attributes are passed to glXChooseVisual by the */
/* gdk (see gdk_gl_choose_visual in gdkgl.c from the GtkGlarea distro). */
int attrlist[] = {
GDK_GL_RGBA,
GDK_GL_DOUBLEBUFFER,
GDK_GL_DEPTH_SIZE, 1,
GDK_GL_NONE
};
gtk_init (&argc, &argv);
if(gdk_gl_query() == FALSE) {
g_print("OpenGL not supported!\n");
return (1);
}

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW(window), "GtkGLArea Demo For Blogger");
gtk_quit_add_destroy (1, GTK_OBJECT(window));
gtk_signal_connect (GTK_OBJECT(window), "delete_event", GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
gtk_container_set_border_width (GTK_CONTAINER(window), 10);
gtk_widget_show (window);

box_main = gtk_vbox_new (FALSE, 10);
gtk_container_add (GTK_CONTAINER(window), box_main);
gtk_widget_show (box_main);

glarea = gtk_gl_area_new(attrlist);
gtk_widget_set_events(GTK_WIDGET(glarea),
GDK_EXPOSURE_MASK|
GDK_BUTTON_PRESS_MASK);
gtk_signal_connect (GTK_OBJECT(glarea), "button_press_event", GTK_SIGNAL_FUNC(glarea_button_press), NULL);
gtk_signal_connect (GTK_OBJECT(glarea), "expose_event", GTK_SIGNAL_FUNC(glarea_draw), NULL);
gtk_signal_connect (GTK_OBJECT(glarea), "configure_event", GTK_SIGNAL_FUNC(glarea_reshape), NULL);
gtk_signal_connect (GTK_OBJECT(glarea), "realize", GTK_SIGNAL_FUNC(glarea_init), NULL);
gtk_signal_connect (GTK_OBJECT(glarea), "destroy", GTK_SIGNAL_FUNC (glarea_destroy), NULL);
gtk_widget_set_usize(GTK_WIDGET(glarea), 256, 256);
gtk_box_pack_start (GTK_BOX(box_main), glarea, FALSE, TRUE, 0);
gtk_widget_show (glarea);

button_quit = gtk_button_new_with_label ("Quit");
gtk_signal_connect (GTK_OBJECT(button_quit), "clicked", GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
gtk_box_pack_start (GTK_BOX(box_main), button_quit, FALSE, TRUE, 0);
gtk_widget_show (button_quit);

gtk_main ();

return (0);
}


И компиллируем его следующей командой:
#gcc gtkglarea_demo.c -o gtkglarea_demo `pkg-config gtk+-2.0 glib-2.0 gtkgl-2.0 --cflags --libs`
Запускаем на выполнение gtkglarea_demo и видим диалоговое окно с черным квадратом, что и есть (пустая) сцена OpenGL и кнопкой выхода под нашим виджетом.
Файл gtkglarea.h содержится в пакете libgtkgl2.0-dev.
Рассмотрим теперь "конкурента", то есть gtkglext.
Аналогично, создаем файл gtkglext_demo.c с содержимым (он является упрощенной и измененной версией файла simple.c из пакета libgtkglext1-dev):


#include <stdlib.h>
#include <gtk/gtk.h>
#include <gtk/gtkgl.h>
#include <GL/gl.h>
#include <GL/glu.h>

static void
realize (GtkWidget *widget, gpointer data){
GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);

if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
return;

glClearColor (0.0, 0.0, 0.0, 0.0);
glClearDepth (1.0);
glViewport (0, 0, widget->allocation.width, widget->allocation.height);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective (40.0, 1.0, 1.0, 10.0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
gluLookAt ( 0.0, 0.0, 3.0,
0.0, 0.0, 0.0,
0.0, 1.0, 0.0);
glTranslatef (0.0, 0.0, -3.0);

gdk_gl_drawable_gl_end (gldrawable);
}

static gboolean
configure_event (GtkWidget *widget, GdkEventConfigure *event, gpointer data){
GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);

if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
return FALSE;
glViewport (0, 0, widget->allocation.width, widget->allocation.height);
gdk_gl_drawable_gl_end (gldrawable);

return TRUE;
}

static gboolean
expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer data){
GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);

if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
return FALSE;
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (gdk_gl_drawable_is_double_buffered (gldrawable))
gdk_gl_drawable_swap_buffers (gldrawable);
else
glFlush ();
gdk_gl_drawable_gl_end (gldrawable);

return TRUE;
}

int
main (int argc, char *argv[]){
GdkGLConfig *glconfig;
gint major, minor;
GtkWidget *window;
GtkWidget *vbox;
GtkWidget *drawing_area;
GtkWidget *button;

gtk_init (&argc, &argv);
gtk_gl_init (&argc, &argv);
gdk_gl_query_version (&major, &minor);
g_print ("\nOpenGL extension version - %d.%d\n",major, minor);

glconfig = gdk_gl_config_new_by_mode ( GDK_GL_MODE_RGB |
GDK_GL_MODE_DEPTH |
GDK_GL_MODE_DOUBLE);
if (glconfig == NULL){
glconfig = gdk_gl_config_new_by_mode ( GDK_GL_MODE_RGB |
GDK_GL_MODE_DEPTH);
if (glconfig == NULL)
exit (1);
}

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "GtkGLExt Demo For Blogger");
gtk_container_set_reallocate_redraws (GTK_CONTAINER (window), TRUE);
g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
gtk_container_set_border_width (GTK_CONTAINER(window), 10);
gtk_widget_show (window);

vbox = gtk_vbox_new (FALSE, 10);
gtk_container_add (GTK_CONTAINER (window), vbox);
gtk_widget_show (vbox);

drawing_area = gtk_drawing_area_new ();
gtk_widget_set_size_request (drawing_area, 256, 256);
gtk_widget_set_gl_capability ( drawing_area,
glconfig,
NULL,
TRUE,
GDK_GL_RGBA_TYPE);
g_signal_connect_after (G_OBJECT (drawing_area), "realize", G_CALLBACK (realize), NULL);
g_signal_connect (G_OBJECT (drawing_area), "configure_event", G_CALLBACK (configure_event), NULL);
g_signal_connect (G_OBJECT (drawing_area), "expose_event", G_CALLBACK (expose_event), NULL);
gtk_box_pack_start (GTK_BOX (vbox), drawing_area, TRUE, TRUE, 0);
gtk_widget_show (drawing_area);

button = gtk_button_new_with_label ("Quit");
g_signal_connect (G_OBJECT (button), "clicked",G_CALLBACK (gtk_main_quit), NULL);
gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
gtk_widget_show (button);

gtk_main ();

return 0;
}


Компиллируем
#gcc gtkglext_demo.c -o gtkglext_demo `pkg-config gtk+-2.0 glib-2.0 gtkglext-1.0 --cflags --libs`
И получаем исполняемый файл gtkglext_demo, который дает тот же результат, что и предыдущий пример, только заголовок окна приложения соответственно изменен.
Коротко обсудим разницу между этими двумя протыми приложениями.
Интерфейс gtkglarea можно посмотреть в файле gtkglarea.h из пакета libgtkgl2.0-dev.
Там можно увидеть, что этот виджет является прямым потомком виджета GtkDrawingArea. Еще одним полем в структуре, соответствующей этому виджету, есть переменная типа GdkGLContext. И в первом примере мы с этим виджетом обращаемся стандартно: создаем его своим собственным конструктором, пакуем, устанавливаем сигналы, показываем. Когда рисуем в этот виджет, то пользуемся функцией gtk_gl_area_make_current.
В случае с gtkglext работа происходит несколько иначе. Создается объект типа GdkGLConfig и потом он "привязывается" к виджету GtkDrawingArea. Затем, когда посылаем команды OpenGL в этот виджет, то создаем объекты типа GdkGLContext и GdkGLDrawable, после чего заключаем команды OpenGL в "функциональные скобки" gdk_gl_drawable_gl_begin и gdk_gl_drawable_gl_end.
Оба примера были протестированы на squeeze Debian.