Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > C/C++ программирование > Qt и кроссплатформенное программирование С/С++
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 10.08.2009, 05:10   #1
oleg kutkov
Unix C++ developer
Форумчанин
 
Аватар для oleg kutkov
 
Регистрация: 16.04.2007
Сообщений: 651
По умолчанию Урок: Кроссплатформенный анимированный осциллограф (GTK+, Cairo)

Моей основной операционной системой является Linux, а основным окружением рабочего стола - Gnome.
Предидущий мой урок - Анимированный осциллограф на WinAPI (С++) вызвал интерес форумчан, поэтому я решил сделать кроссплатформенную версию этого импровизированного осциллографа и полностью переписал программу на GTK+. Данная статья может быть полезна всем, кто только начал или собирается изучать этот тулкит. Параллельно в уроке рассматривается использование некоторых функций мощной библиотеки двухмерной графики Cairo.
В примере показана компиляция программы в Linux, пользователи Windows могу скомпилировать программу, например, с помощью MinGW, так же скомпилировать программу могут и пользователи Mac OS X. Вопросы компиляции на платформах, отличных от Linux выходят за рамки данной статьи и рассматриваться не будут. Те кто желает - всегда сможет разобраться самостоятельно, используя поиск.
Урок будет изложен в моем обычном стиле - пошаговое, постепенное, рассмотрение кода и разбор каждой строчки. В конце будет приложен файл с полным исходным кодом.

Но в начале немного теории.
GTK+ - это кроссплатформенная библиотека, предназначенная для создания приложений с графическим интерфейсом пользователя, предоставляет богатый набор функций на все случаи жизни. Изначально библиотека разрабатывалась для программы Gimp. Библиотека написана на языке С, но тем не менее сохраняет принципы объектно-ориентированного программирования.
Для использования возможностей библиотеки, в Вашей системе, должна быть установлена как сама библиотека, так и заголовочные файлы (dev, devel пакеты) к ней, все это Вы можете сделать с помощью менеджера пакетов Вашего дистрибутива.
После установки всех недостающих компонентов можно подключать заголовочные файлы библиотеки в свою программу
Код:
#include <gtk/gtk.h>
Основными объектами библиотеки, с которыми нам придется иметь дело, являются так называемые виджеты. Каждый элемент управления (кнопка, метка, поле ввода) является виджетом. Само окно так же является виджетом. Еще одно очень важное понятие GTK+ - сигналы. Их можно считать, своего рода, аналогами сообщений в Windows. Сигналы посылают различные элементы управления (нажата кнопка, передвинуто окно), а так же функции, при наступлении некоторого события (сработал таймер). Для обработки сигналов необходимо создавать функции-обработчики. Но функция-обработчик не будет взаимодействовать с сигналом сама по себе, для этого их необходимо связать. Это делается с помощью специальных методов GTK+. Обратите, так же, внимание на явное приведение типов аргументов функций.
Настало время рассмотрения программы.

Последний раз редактировалось oleg kutkov; 10.08.2009 в 16:22.
oleg kutkov вне форума Ответить с цитированием
Старый 10.08.2009, 05:11   #2
oleg kutkov
Unix C++ developer
Форумчанин
 
Аватар для oleg kutkov
 
Регистрация: 16.04.2007
Сообщений: 651
По умолчанию

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

Код:
#include <gtk/gtk.h>
#include <stdio.h>
#include <math.h>
//объявляем функцию, обработчик сигнала закрытия окна
static void destroy(GtkWidget*, gpointer);
//объявляем функцию, обработчки сигнала перерисовки окна
static void draw(GtkWidget*, GdkEventExpose*, gpointer);
//объявляем функцию, объявляющую область перерисовки недействительной
static gboolean time_handler(GtkWidget*);
//две переменные, смещение синусоиды и текущее положение точки графика
gint count = 0, t = 0;
double radianPerx = 2 * 3.14 / 90; //угол радиан, определяет расстояние между гребнями волн
const double uAmplit = 900; //амплитуда синусоиды
После объявлений - главная функция main, в самом объявлении функции нет ничего необычного. Рассмотрим что твориться в ней.
Первым делом мы должны объявить два виджета, это наше будущее окно и область, в которой мы будем рисовать

Код:
GtkWidget *window, *drawing;
Пока что это просто виджеты, объекты, без указания конкретного типа.
Далее - инициализация gtk

Код:
gtk_init(&argc, &argv);
функции передаются два аргумента - аргументы функции main.
Теперь создаем новое окно и задаем ему необходимые параметры.

Код:
//создаем новое, обычное окно
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
//задаем текст заголовка окна
gtk_window_set_title(GTK_WINDOW(window), "Осциллограф");
//запрещаем изменение размеров окна
gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
//задаем размер окна
gtk_widget_set_size_request(window, 600, 400);
//задаем стартовую позицию - центр экрана
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
//задаем отступы контейнера окна
gtk_container_set_border_width(GTK_CONTAINER(window), 10);
Тут стоит сказать, что в Gtk, элемент управления не может существовать произвольно, в любом месте. Его может содержать в себе некий невизуальный объект-контейнер. Окно приложения может выполнять функцию контейнера, но может содержать только один объект, для расположения в окне множества элементов в окне - следует расположить в контейнера окна другой контейнер. Сам контейнер может содержать сколь угодно много элементов. Функция gtk_container_set_border_width задает "поля" контейнера окна. Если не вызывать эту функцию - контейнер, содержащий в себе, в данном случае, поле рисования, будет равен размеру самого окна, без заголовка.

Последний раз редактировалось oleg kutkov; 10.08.2009 в 05:30.
oleg kutkov вне форума Ответить с цитированием
Старый 10.08.2009, 05:12   #3
oleg kutkov
Unix C++ developer
Форумчанин
 
Аватар для oleg kutkov
 
Регистрация: 16.04.2007
Сообщений: 651
По умолчанию

Следующей функцией мы создаем новый таймер, с интервалом срабатывания - 100 миллисекунд и привязываем к таким срабатываниями функцию time_handler.

Код:
g_timeout_add(100, (GSourceFunc) time_handler, (gpointer) window);
Далее мы создаем новую область для рисования и помещаем ее в контейнер окна

Код:
//создаем новую область для рисования
drawing = gtk_drawing_area_new();
//помещаем ее в контейнер окна
gtk_container_add(GTK_CONTAINER(window), drawing);
И самое интересное, в главной функции - связывание сигналов с обработчиками, это делается с помощью функции g_signal_connect.

Код:
//связываем сигнал уничтожения окна и функцию-обработчик
g_signal_connect(GTK_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL);
//связываем сигнал перерисовки области рисования и функцию-обработчик
g_signal_connect(G_OBJECT(drawing), "expose_event", G_CALLBACK(draw), NULL);
Первым параметром функции является объект, который мониторится, следующий параметр - название сигнала, третий параметр - функция-обработчик сигнала. Последний параметр, который мы не используем, в данном случае - дополнительный параметр, который может быть передан в функцию-обработчик.

Оставшиеся две функции выполняют отображение всех виджетов на экране и запуск основного цикла программы.

Код:
gtk_widget_show_all(window);
gtk_main();
Теперь рассмотрим собственно функции-обработчики сигналов. Пойдем от простого к сложному.

Функция-обработчик сигнала закрытия окна

Код:
static void destroy(GtkWidget* window, gpointer data)
{
	//завершаем главный цикл приложения
	gtk_main_quit();
}
Здесь все просто, при вызове этой функции происходит вызов gtk_main_quit() и завершение работы программы. Стоит заметить, что если не написать этот обработчик - после закрытия окна, процесс бы оставался висеть в памяти и ожидать сигналов.

Функция-обработчик сигнала таймера

Код:
static gboolean time_handler(GtkWidget* widget)
{
  //если передан нулевой параметр - возвращаем ложь
  if (widget->window == NULL) return FALSE;
  //увеличиваем значение переменной смещения графика
  count++;
  //обнуляем точку положения на графике
  t = 0;
  //заставляем окно перерисоваться
  gtk_widget_queue_draw(widget);
  return TRUE;
}
Здес так же нет ничего замудренного. Сначала выполняется корректность переданного параметра, если он нулевой - функция завершается, вернув FALSE;
Если функция не завершилась - происходит увеление значения переменной, задающей смещение рисуемого графика. Так же обнуляется вспомогательная переменная, указывающая текущую точку на графике. Следующий вызов gtk_widget_queue_draw(widget) объявляет, переданный ему виджет, визуально недействительным и требующим перерисовки.

Последний раз редактировалось oleg kutkov; 10.08.2009 в 13:53.
oleg kutkov вне форума Ответить с цитированием
Старый 10.08.2009, 05:14   #4
oleg kutkov
Unix C++ developer
Форумчанин
 
Аватар для oleg kutkov
 
Регистрация: 16.04.2007
Сообщений: 651
По умолчанию

И наконец самое интересное - функция, выполняющая отрисовку координатной сетки и синусоиды. Для рисования используется библиотека Cairo, "встроенная" в Gtk. Мы будем использовать следующие методы:

gdk_cairo_create(GtkWidget* ) - данная функция создает, на основе переданного ей виджета, новый контекст рисования Cairo.
cairo_rectangle (cr, x1, y1, x2, y2) - данная функция рисует, в контексте рисования Cairo прямоугольник по заданным координатам.
cairo_set_line_width (cr, 3) - данная функция задает ширину линии, которой производится рисования в указанном контексте
cairo_set_source_rgb (cr, r, g, b) - данная функция задает цвет линии, которой производится рисования в указанном контексте, параметры r, g, b определяют, соответственно, интенсивность красного, зеленого и синего цветов.
cairo_move_to(cr, x, y) - с помощью этой функции мы переходим в новую исходную точку, из которой будем рисовать.
cairo_line_to (cr, x, y) - задаем точку, куда следует нарисовать линию.
cairo_stroke (cr) - собственно выполнение рисования в контексте.
cairo_show_text(cr, buf*) - рисование текста в заданном контексте, в точке, предварительно заданной cairo_move_to.

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

Код:
static void draw(GtkWidget* drawarea, GdkEventExpose* event, gpointer data)
{
	int ya, xa; //переменные для хранения X и Y координат рисования
	char *buf; //текстовый буфер для хранения текста метки
	//создаем область для рисования Cairo
	cairo_t *cr = gdk_cairo_create(drawarea->window);
	//рисуем рамку, размером с область рисования
        cairo_rectangle (cr, event->area.x, event->area.y, event->area.width, event->area.height);
	//задаем толщину линии
	cairo_set_line_width (cr, 3);
	//задачем черный цвет
	cairo_set_source_rgb (cr, 0, 0, 0);
	//рисуем линию оси Y
  	cairo_move_to(cr, 40, 0);
	cairo_line_to (cr, 40, 350);
	cairo_stroke (cr);
	//рисуем линию оси X
	cairo_move_to (cr, 30, 340);
	cairo_line_to (cr, 600, 340);
	cairo_stroke (cr);	
	//задаем толщину линии
	cairo_set_line_width (cr, 0.7);
	//рисуем риски на оси X, линии сетки и текстовые метки
	xa = 40;
	while(xa < 600)
	{
		//задаем черный цвет
		cairo_set_source_rgb (cr, 0, 0, 0);
		//рисуем риски
		cairo_move_to (cr, xa, 333);
		cairo_line_to (cr, xa, 347);
		cairo_stroke (cr);
		//преобразовывем значение координаты риски в строку	
		sprintf(buf, "%i", xa-40); 
		//рисуем текст метки в нужной точке 
		cairo_move_to (cr, xa-2, 360);
		cairo_show_text(cr, buf);
		//задаем зеленый цвет
		cairo_set_source_rgb (cr, 0, 0.5, 0);
		//рисуем линии сетки
		cairo_move_to (cr, xa+40, 333);
		cairo_line_to (cr, xa+40, 0);
		cairo_stroke (cr);		
		xa += 40;		
	}
	//рисуем риски на оси Y, линии сетки и текстовые метки
	ya = 340;
	while(ya > 0)
	{
		//задаем черный цвет
		cairo_set_source_rgb (cr, 0, 0, 0);		
		cairo_move_to (cr, 33, ya);
		cairo_line_to (cr, 47, ya);
		cairo_stroke (cr);
		//преобразовывем значение координаты риски в строку	
		sprintf(buf, "%i", 340-ya); 
		//рисуем текст метки в нужной точке 
		cairo_move_to (cr, 12, ya+2);
		cairo_show_text(cr, buf);
		//задаем зеленый цвет
		cairo_set_source_rgb (cr, 0, 0.5, 0);
		//рисуем линии сетки
		cairo_move_to (cr, 47, ya-40);
		cairo_line_to (cr, 600, ya-40);
		cairo_stroke (cr);		
		ya -= 40;			
	}
	//рисуем синусоиду
	//задаем красный цвет и толщину линии
	cairo_set_source_rgb (cr, 1, 0, 0);
	cairo_set_line_width (cr, 4);	
	//устонавливаем начальную точку синусоиды
 	cairo_move_to(cr, t+40, ((uAmplit * sin(t * radianPerx + count)) * 0.1 + 160));
	//рисуем синусоиду
	while(t < 600)
	{
		cairo_line_to(cr, t+40, ((uAmplit * sin(t * radianPerx + count)) * 0.1 + 160));
		t++;
	}
	cairo_stroke (cr);
}
oleg kutkov вне форума Ответить с цитированием
Старый 10.08.2009, 05:24   #5
oleg kutkov
Unix C++ developer
Форумчанин
 
Аватар для oleg kutkov
 
Регистрация: 16.04.2007
Сообщений: 651
По умолчанию

Компиляция исходного текста в файле osc.c выполняется командой
Код:
gcc -Wall -g osc.c -o osc `pkg-config --cflags gtk+-2.0` `pkg-config --libs gtk+-2.0`
Здесь просходит подключение всех необходимых библиотек, с помощью pkg-config (у вас должен быть установлена эта утилита). Если компилятор будет ругаться - значит у вас не установлены необходимые пакеты с заголовочными файлами.

Скриншот итогового приложения:


Полный исходный код:
Вложения
Тип файла: zip osc.c.zip (2.2 Кб, 274 просмотров)
oleg kutkov вне форума Ответить с цитированием
Старый 11.08.2009, 20:49   #6
*PB*
Форумчанин
 
Регистрация: 11.08.2009
Сообщений: 558
По умолчанию

Вот мой вариант подобно осцилографа, с вариантами для Windows x86 и Linux.

Скрины




Не обращайте внимание на "рваность" синусоиды в последнем скрине - это так криво снимает скрины стандартная прога линукса.

Для создания программ использовал кроссплатформенный компилятор PureBasic http://pure-basic.narod.ru
Уложился в 60 строк кода!

Во вложении есть исходник, котрый без каких-либо изменений можно скомпилировать под Windows и Linux, а так же скопилированые проги для Windows x86 и Linux.
Вложения
Тип файла: zip Oscilloscope_PureBasic.zip (29.7 Кб, 209 просмотров)
*PB* вне форума Ответить с цитированием
Старый 30.03.2010, 22:43   #7
ROD
Linux C++ Qt ARM
Старожил
 
Аватар для ROD
 
Регистрация: 30.11.2008
Сообщений: 3,030
По умолчанию

Code Blocks, при запуске скомпилированной проги, выдает ошибку
Цитата:
Segmentation fault
через консоль тоже запускаться не хочет.


Щас попробовал напрямую скомпилировать в gcc, без IDE - результат тот же.
Дилетант широкого профиля.

"Слова ничего не стоят - покажите мне код!" © Линус Торвальдс
ROD вне форума Ответить с цитированием
Старый 26.04.2010, 12:22   #8
oleg kutkov
Unix C++ developer
Форумчанин
 
Аватар для oleg kutkov
 
Регистрация: 16.04.2007
Сообщений: 651
По умолчанию

В моей программе ?
Так ничего невозможно сказать, хотя, по сути единственное место, где может падать - sprintf.
Вообщем gdb в помощь
oleg kutkov вне форума Ответить с цитированием
Старый 05.12.2010, 06:05   #9
Enchance
Пользователь
 
Регистрация: 20.10.2009
Сообщений: 23
По умолчанию

Спасибо за пример, очень пригодился.

А чтобы небыло ошибочег, меняем строку
Код:
char *buf;
на
Код:
char buf[10];
GCC еще предупреждение выдает, в строке со sprintf`ом. И да, динамические масивы - зло.
Enchance вне форума Ответить с цитированием
Старый 04.08.2011, 08:55   #10
rekon
 
Аватар для rekon
 
Регистрация: 28.07.2011
Сообщений: 7
Лампочка

Для Qt есть Qwt (QwtPlot3D). Не осциллограф, просто универсальное средство вывода графиков.
rekon вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Анимированный осцилограф на WinAPI (С++) oleg kutkov Общие вопросы C/C++ 36 26.02.2014 22:39
Анимированный Gif в компоненте Image Ermiss Мультимедиа в Delphi 17 12.09.2010 06:11
Анимированный фон Delph1n Мультимедиа в Delphi 11 31.01.2009 14:47