Показаны сообщения с ярлыком Gtk. Показать все сообщения
Показаны сообщения с ярлыком Gtk. Показать все сообщения
В одной из программ на работе используется ряд самописных Gtk-виджетов для настройки фильтров изображения. Виджеты лежат в Box'е, добавляются, удаляются и настраиваются пользователем. У каждого типа виджетов свой набор настроек. Все настройки виджеты хранят в своих свойствах.
Чтобы сохранять после перезапуска программы список фильтров и их параметры, была идея сделать некий обобщенны код, не зависимый от конкретной задачи, который бы сохранял объекты Gobject в файлы (типы объектов и значения свойств) и потом воссоздавал последовательность объектов по этим файлам.
В качестве "proof of concept" был написан код, клонирующий объект Gobject. Функция g_object_clone возвращает копию объекта, переданного ей в качестве параметра src. Код основан на примере со StackOverflow. Обращаю внимание, что полноценно копируются только простые свойства, для boxed-типов копируется указатель с инкрементированием счетчика ссылок.
Код gobject_clone.c:
Чтобы сохранять после перезапуска программы список фильтров и их параметры, была идея сделать некий обобщенны код, не зависимый от конкретной задачи, который бы сохранял объекты Gobject в файлы (типы объектов и значения свойств) и потом воссоздавал последовательность объектов по этим файлам.
В качестве "proof of concept" был написан код, клонирующий объект Gobject. Функция g_object_clone возвращает копию объекта, переданного ей в качестве параметра src. Код основан на примере со StackOverflow. Обращаю внимание, что полноценно копируются только простые свойства, для boxed-типов копируется указатель с инкрементированием счетчика ссылок.
Код gobject_clone.c:
// gcc `pkg-config --libs --cflags glib-2.0 gobject-2.0` gobject_clone.c point.c -o gobject_clone #include <glib-object.h> #include "point.h" #include <stdio.h> #include <string.h> //< For strcmp.В качестве объектов в примере взят простенький класс Point с гитхаба за авторством Jacob Gelbman. Код point.h:// See http://stackoverflow.com/questions/3003655/is-there-a-good-way-to-copy-a-gtk-widget?answertab=votes#tab-top static GObject *g_object_clone(GObject *src) { GObject *dst; GParameter *params; GParamSpec **specs; guint n, n_specs, n_params; specs = g_object_class_list_properties(G_OBJECT_GET_CLASS(src), &n_specs); params = g_new0(GParameter, n_specs); n_params = 0; for (n = 0; n < n_specs; ++n) if((specs[n]->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE) { params[n_params].name = g_intern_string(specs[n]->name); g_value_init(¶ms[n_params].value, specs[n]->value_type); g_object_get_property(src, specs[n]->name, ¶ms[n_params].value); ++n_params; } dst = g_object_newv(G_TYPE_FROM_INSTANCE(src), n_params, params); g_free(specs); g_free(params); return dst; } // Print, clear, print again. static void psp (Point *p) { g_print("OBJECT %p\n", p); g_print(" Before clearing:\n"); point_print(p); g_signal_emit_by_name(p, "clear"); g_print(" After clearing:\n"); point_print(p); g_print(" ...\n"); } int main(int argc, char **argv) { g_type_init(); GArray *array = g_array_new(FALSE, FALSE, sizeof(gint)); gint val1 = 33, val2 = 44; g_array_append_val(array, val1); g_array_append_val(array, val2); g_array_append_val(array, val1); GObject *p1 = g_object_new(TYPE_POINT, "x", 5, "y", 10, "a", array, NULL); GObject *p2 = g_object_clone(p1); //< Note: garray uses ref/unref for copy/free. psp(POINT(p1)); g_print("Appending value '%d' to array.\n", val2); g_print(" ...\n"); g_array_append_val(array, val2); psp(POINT(p2)); g_clear_object(&p1); g_clear_object(&p2); return 0; }
// https://github.com/zorgnax/gobject-examples/tree/master/02-point-inheritance #ifndef __POINT_H__ #define __POINT_H__ #include <glib.h> #include <glib-object.h>Код point.c:#define TYPE_POINT (point_get_type ()) #define POINT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_POINT, Point)) #define POINT_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), TYPE_POINT, PointClass)) #define IS_POINT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_POINT)) #define IS_POINT_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE ((cls), TYPE_POINT)) #define POINT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_POINT, PointClass)) typedef struct _PointClass PointClass; typedef struct _Point Point; struct _PointClass { GObjectClass parent; void (*clear) (Point *self); }; struct _Point { GObject parent; gint x; gint y; GArray *array; }; GType point_get_type(void); void point_print(Point *self); #endif /*__POINT_H__*/
// https://github.com/zorgnax/gobject-examples/tree/master/02-point-inheritance #include <glib-object.h> #include "point.h"G_DEFINE_TYPE(Point, point, G_TYPE_OBJECT); enum { PROP_0, PROP_ARRAY, PROP_X, PROP_Y }; enum { CLEAR, LAST_SIGNAL }; static guint point_signals[LAST_SIGNAL] = {0}; void point_print (Point *self) { g_print(" x: %d, y: %d\n", self->x, self->y); g_print(" ar:"); if(self->array) { guint i; for(i = 0; i < self->array->len; i++) g_print(" %d", g_array_index(self->array, gint, i)); } else g_print("null"); g_print("\n"); } static void point_clear (Point *self) { g_object_set(self, "x", 0, "y", 0, "a", NULL, NULL); } static void point_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { Point *point = POINT(obj); switch (prop_id) { case PROP_ARRAY: g_value_set_boxed(value, point->array); break; case PROP_X: g_value_set_int(value, point->x); break; case PROP_Y: g_value_set_int(value, point->y); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } } static void point_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { Point *point = POINT(obj); switch (prop_id) { case PROP_ARRAY: if(point->array) g_array_unref(point->array); point->array = g_value_dup_boxed(value); break; case PROP_X: point->x = g_value_get_int(value); break; case PROP_Y: point->y = g_value_get_int(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } } static void point_finalize(GObject *point) { if(POINT(point)->array) g_array_unref(POINT(point)->array); G_OBJECT_CLASS(point_parent_class)->finalize(point); } static void point_class_init (PointClass *cls) { GObjectClass *g_object_class = G_OBJECT_CLASS(cls); GParamSpec *array_param; GParamSpec *x_param; GParamSpec *y_param; g_object_class->get_property = point_get_property; g_object_class->set_property = point_set_property; g_object_class->finalize = point_finalize; cls->clear = point_clear; array_param = g_param_spec_boxed( "a", "a", "some int array", G_TYPE_ARRAY, G_PARAM_READWRITE); x_param = g_param_spec_int( "x", "x", "x loc of point", INT_MIN, /* => */ INT_MAX, 0, G_PARAM_READWRITE); y_param = g_param_spec_int( "y", "y", "y loc of point", INT_MIN, /* => */ INT_MAX, 0, G_PARAM_READWRITE); g_object_class_install_property( g_object_class, PROP_ARRAY, array_param); g_object_class_install_property( g_object_class, PROP_X, x_param); g_object_class_install_property( g_object_class, PROP_Y, y_param); point_signals[CLEAR] = g_signal_new( "clear", /* signal_name */ TYPE_POINT, /* itype */ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, /* signal_flags */ G_STRUCT_OFFSET(PointClass, clear), /* class_offset */ NULL, /* accumulator */ NULL, /* accu_data */ g_cclosure_marshal_VOID__VOID, /* c_marshaller */ G_TYPE_NONE, /* return_type */ 0); /* n_params */ } static void point_init(Point *point) { point->array = NULL; }
Есть такая особенность в GTK, что если положить с помощью gtk_expander_set_label_widget в GtkExpander активируемый виджет вроде GtkCheckButton или GtkSwitch, то активируемый виджет вроде как работает (можно кликнуть по нему и получить статус "active"), но отрисовывается не правильно. Например, в случае GtkCheckButton не рисуется галочка, даже если кнопка в активном состоянии. В принципе даже из названия "label_widget" видно, что разработчиками не закладывалось, что кто-то будет туда класть какой-то активный элемент, максимум GtkLabel или картинку. Однако есть способ заставить активные виджеты работать, если виджет класть в Expander уже после того, как Expander сформирован.
Ниже пример на питоне. Отмечу, что обязательно нужно класть в label_widget GtkBox, а уже в него складывать свои активные виджеты. Функцию hacked_idle можно объявить один раз и использовать для всех Expander'ов.
Ниже пример на питоне. Отмечу, что обязательно нужно класть в label_widget GtkBox, а уже в него складывать свои активные виджеты. Функцию hacked_idle можно объявить один раз и использовать для всех Expander'ов.
import gi, sys
from gi.repository import Gtk, GLib
def hacked_idle(expander):
hbox = expander.get_label_widget()
expander.set_label_widget(None)
expander.set_label_widget(hbox)
return False
def expander_realize_cb(widget):
GLib.idle_add(hacked_idle, widget)
return False
class Test:
def __init__(self):
window = Gtk.Window()
window.set_default_size(640, 480)
window.connect_after("destroy", self.destroy)
exp = Gtk.Expander()
exp.connect("realize", expander_realize_cb)
exp.set_expanded(True)
window.add(exp)
hbox = Gtk.Box(Gtk.Orientation.HORIZONTAL, 5)
exp.set_label_widget(hbox)
check = Gtk.CheckButton()
check.set_label("Hi, There!!");
hbox.add(check)
switch = Gtk.Switch()
hbox.add(switch)
big_label = Gtk.Label()
big_label.set_markup("<big>Hello, World!!!</big>")
exp.add(big_label)
window.show_all()
def destroy(window, self):
Gtk.main_quit()
def main():
app = Test()
Gtk.main()
if __name__ == "__main__":
sys.exit(main())
Ключевые слова: Gtk, GtkCheckButton, GtkExpander
В свежеустановленной Astra Linux не работала функция gtk_show_uri() с URI типа ghelp.
В GError при этом можно было увидеть ошибку:
Естественно, yelp был установлен.
Вопреки некоторым советам из интернетов, gvfs тут ни при чем.
Проблема решилась установкой пакета desktop-file-utils.
В GError при этом можно было увидеть ошибку:
'Действие не поддерживается'.Или, с LANG=C:
'Operation not supported'.
Естественно, yelp был установлен.
Вопреки некоторым советам из интернетов, gvfs тут ни при чем.
Проблема решилась установкой пакета desktop-file-utils.
Ключевые слова: Хозяйке на заметку, AstraLinux, Gtk, Linux
Код основан на примере "Rotated button" из gtk-demo. Собирается под Gtk-2. Местами код неидеален (например виджет RotatedBin.scale создается вручную в main), но, думаю, вполне может быть полезен.
Скрин:
Код:
https://docs.google.com/file/d/0B40UHa6ea9QkdUFTVnRqNnFaTDg/edit
Скрин:
Код:
https://docs.google.com/file/d/0B40UHa6ea9QkdUFTVnRqNnFaTDg/edit
Ключевые слова: example, Gtk, off-screen window, Vala
Пара советов как привести изначально унылый внешний вид OpenOffice и Java-приложений к стилю программ на Gtk:
OpenOffice.org
Создаем файл /etc/profile.d/openoffice.sh со следующим содержанием:
#
# /etc/profile.d/openoffice.sh
export SAL_USE_VCLPLUGIN=gtk
export OOO_FORCE_DESKTOP=gnomeТеперь достаточно перелогиниться в системе, необходимые настройки подхватятся.Java
Создаем файл /etc/profile.d/gtkjava.sh со следующим содержанием:
#
# /etc/profile.d/gtkjava.sh
export _JAVA_OPTIONS=" -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel \
-Dawt.useSystemAAFontSettings=on"Также перелогинимся.
Ключевые слова: Gtk, Java, Linux, OpenOffice.org, tip
В связи со скудностью документации по библиотеке GtkExtra, мною был составлен довольно подробно комментированный простенький пример, по которому можно понять принципы построения графиков с помощью GtkExtra, его и выкладываю в блоге.
Пример во многом аналогичен примеру testiterator из стандартной поставки GtkExtra и выглядит так:
Собственно текст примера:
В примере довольно универсальной функции graph_open передаются три параметра:
1) Контейнер GTK, в котором и будет отрисовн график.
2) Количество точек для отрисовки.
3) Адрес области памяти, в которой лежат значения (double) координат точек.
Вспомогательная функция graph_iter служит для распаковки точек.
main - простенькое gtk-приложение, содержащее виджет main_frame в качестве контейнера для графика.
Пример во многом аналогичен примеру testiterator из стандартной поставки GtkExtra и выглядит так:
Собственно текст примера:// Пример дынных для отрисовки --> // Количество точек = 12 int nt = 12; // Структура для пар координат "время - значение" struct pair { double x; // По X - Время double y; // По Y - Значение }; // Область памяти с парами координат "время - значение" struct pair mem[12] = { { 0, 0.2}, // 1 { 1.5, 0}, { 2.4, 1}, { 3.7, 0}, { 4.44, 0.55}, // 5 { 6.3, 0.73}, { 7, 0.21}, { 8.9, 0.1}, { 11, 0.2}, { 14.6, 0.2}, // 10 { 17, 0.2}, { 20, 1} // 12 }; // Пример дынных для отрисовки <-- // Стандартные для GTK вещи --> #include <math.h> #include <stdio.h> #include <stdlib.h> #include <gtk/gtk.h> #include <gdk/gdk.h> void quit () { gtk_main_quit(); } // Стандартные для GTK вещи <-- #include <gtkplotcanvas.h> #include <gtkplotcanvastext.h> #include <gtkplotcanvasplot.h> #include <gtkplotdata.h> // Массив текста для подписей к точкам static gchar point_label[100]; // Функция, запускаемая для получения точек // (функция запускается по разу для каждой точки) static void graph_iter ( GtkPlot *plot, GtkPlotData *data, gint iter, gdouble *x, gdouble *y, gdouble *z, gdouble *a, gdouble *dx, gdouble *dy, gdouble *dz, gdouble *da, gchar **label, gboolean *error ) { // Достаем из объекта data указатель на область памяти с координатами точек double *memory = (double *) g_object_get_data(G_OBJECT(data), "Dots_memory_pointer"); // Распаковываем координаты из памяти *x = *( memory // Начало области памяти + 0 // X - первый в парах, так что без смещения + 2 * iter); // Смещение к нужной паре *y = *( memory // Начало области памяти + 1 // Y - второй в парах, смещение к нему + 2 * iter); // Смещение к нужной паре // Подписи к точкам sprintf(point_label, "Точка #%d\n", iter + 1); *label = point_label; *error = FALSE; } void graph_open ( GtkWidget *container, // Контейнер, в котором рисовать график int dots_number, // Количесвто точек void *memory // Область памяти, содержащее координаты этих точек ) { // Создаем холст canvas 900x700 пикселей в окне --> GtkWidget *canvas; // Виджет canvas = gtk_plot_canvas_new( // Создаем 900, 700, // Размер 1.); // В натуральную величину (например для уменьшения в два раза следует поставить 0.5) gtk_container_add(GTK_CONTAINER(container), canvas); // Помещаем в контейнер // Создаем холст canvas 900x700 пикселей в окне <-- // Создадим подпись вверху холста --> GtkWidget *top_label; top_label = (GtkWidget *) gtk_plot_canvas_text_new( "Times-BoldItalic", // Шрифт (можно выставить NULL для шрифта по умолчанию) // Доступные шрифты можно посмотреть в файле gtkplotps.c 20, // Высота шрифта 0, // Угол наклона надписи NULL, // Цвет шрифта NULL, // Цвет фона надписи TRUE, // Прозрачность GTK_JUSTIFY_CENTER, // Выравнивание "My first plot (первый плот).");// Собственно текст gtk_plot_canvas_put_child( // Помещаем на холст GTK_PLOT_CANVAS(canvas), // Холст (GtkPlotCanvasChild *) top_label, // Что помещать (child) // Координаты: .50, // x1 - отступ по горизонтали, в процентах от холста (зд. 50%, т.е. посередине) .05, // y1 - отступ по вертикалии, в процентах от холста (зд. 5%, т.е. почти наверху) 0., // x2 0.); // y2 // Создадим подпись вверху холста <-- // Устанавливаем цвет холста --> GdkColor color; gdk_color_parse("light blue", &color); gdk_color_alloc(gtk_widget_get_colormap(canvas), &color); gtk_plot_canvas_set_background(GTK_PLOT_CANVAS(canvas), &color); // Устанавливаем цвет холста <-- // Создаем график plot (точнее основу графика - оси, подписи и т.п., без значений) --> GtkWidget *plot; plot = gtk_plot_new_with_size( NULL, // GdkDrawable виджет .65, .45); // Высота и ширина виджета графика // Цвет графика --> gdk_color_parse("light yellow", &color); gdk_color_alloc(gtk_widget_get_colormap(plot), &color); gtk_plot_set_background(GTK_PLOT(plot), &color); // Цвет графика <-- gtk_plot_set_range( // По умолчанию график 1x1 деление,... GTK_PLOT(plot), // ...выставим другие значения: 0., 20., // По X - 20 делений 0., 1.); // По Y - 1 деление // Выставим с каким шагом проставлять метки координат на графике gtk_plot_set_ticks(GTK_PLOT(plot), GTK_PLOT_AXIS_X, // По оси X 2, // Цифры проставлять с шагом 2 1); // Ставить засечки с шагом 1 gtk_plot_set_ticks(GTK_PLOT(plot), GTK_PLOT_AXIS_Y, .1, 1); gtk_plot_grids_set_visible(GTK_PLOT(plot), // Координатная сетка TRUE, // Вертикальная сетка сплошной линией относительно проставленных на оси цифр TRUE, // Вертикальная сетка штриховой линией относительно проставленных на оси меток TRUE, // Горизонтальная сетка сплошной линией относительно проставленных на оси цифр TRUE); // Горизонтальная сетка штриховой линией относительно проставленных на оси меток gtk_widget_show(plot); // Нужно вызвать функцию отрисовки графика заранее, // иначе потом не удасться добавить к графику данные // Создаем график plot (точнее основу графика - оси, подписи и т.п., без значений) <-- // Подписи к графику --> // Верхнюю и правую просто скроем: gtk_plot_axis_hide_title(gtk_plot_get_axis(GTK_PLOT(plot), GTK_PLOT_AXIS_TOP)); gtk_plot_axis_hide_title(gtk_plot_get_axis(GTK_PLOT(plot), GTK_PLOT_AXIS_RIGHT)); // Остальные пропишем: gtk_plot_axis_set_title(gtk_plot_get_axis(GTK_PLOT(plot), GTK_PLOT_AXIS_LEFT), "Значение"); gtk_plot_axis_set_title(gtk_plot_get_axis(GTK_PLOT(plot), GTK_PLOT_AXIS_BOTTOM), "Время"); // Подписи к графику <-- // Создаем child с только что созданным графиком plot и рисуем его на холсте (put_child) --> GtkPlotCanvasChild *child; child = (GtkPlotCanvasChild *) gtk_plot_canvas_plot_new(GTK_PLOT(plot)); // Создание структуры child gtk_plot_canvas_put_child( // Помещаем на холст GTK_PLOT_CANVAS(canvas), // Холст child, // Что помещать (child) // Координаты: .15, // x1 - отступ по горизонтали, в процентах от холста .25, // y1 - отступ по вертикалии, в процентах от холста .80, // x2 - ширина графика .85); // y2 - высота графика // Создаем child с только что созданным графиком plot и рисуем его на холсте (put_child) <-- // Строим на графике значения (пока просто линии между точками) --> GtkPlotData *dataset; dataset = (GtkPlotData *) gtk_plot_data_new_iterator( // Заполняем структуру данных (значения и параметры) (GtkPlotIterator)graph_iter, // Функция, запускаемая для получения (распаковки) точек dots_number, // Количество точек GTK_PLOT_DATA_X|GTK_PLOT_DATA_Y|GTK_PLOT_DATA_LABELS); // Маска параметров // Здесь мы сохраним указатель на адрес области памяти с данными о точках... // ...в объекте dataset (см. GObject Reference Manual) g_object_set_data(G_OBJECT(dataset), "Dots_memory_pointer", memory); gtk_plot_data_show_labels(dataset, TRUE); // Отображать подписи к точкам gdk_color_parse("dark green", &color); gdk_color_alloc(gtk_widget_get_colormap(plot), &color); gtk_plot_data_labels_set_attributes(dataset, "Helvetica-Bold", // Шрифт (можно выставить NULL для шрифта по умолчанию) 14, // Высота шрифта 90, // Угол наклона подписи &color, // Цвет шрифта NULL); // Цвет фона подписи gtk_plot_add_data(GTK_PLOT(plot), dataset); // Добавляем значения на график... gtk_widget_show(GTK_WIDGET(dataset)); // ...и отображаем их // Строим на графике значения (пока просто линии между точками) <-- // Легенда графика gtk_plot_data_set_legend(dataset, "Легенда"); // Настроим вид точек <-- gdk_color_parse("green", &color); // Цвет - зеленый gdk_color_alloc(gdk_colormap_get_system(), &color); gtk_plot_data_set_symbol(dataset, GTK_PLOT_SYMBOL_DIAMOND, // Символ - ромб GTK_PLOT_SYMBOL_OPAQUE, // Стиль - непрозрачный 20, 2, // Размеры &color, &color // Цвета ); // Настроим вид точек --> // Настроим вид линий --> gdk_color_parse("red", &color); // Цвет - красный gdk_color_alloc(gdk_colormap_get_system(), &color); gtk_plot_data_set_line_attributes(dataset, GTK_PLOT_LINE_SOLID, // GtkPlotLineStyle 0, // GdkCapStyle (как рисовать концы линий, см. GDK Reference Manual) 0, // GdkJoinStyle (как объединяются сегменты линий, см. GDK Reference Manual) 3, // Ширина линии &color); // Цвет // Настроим вид линий <-- } int main(int argc, char *argv[]) { // Стандартные для GTK вещи --> // Здесь создаем окно и сопутствующие виджеты GtkWidget *window; gtk_init(&argc,&argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC(quit), NULL); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); GtkWidget *main_vbox; main_vbox = gtk_vbox_new(FALSE, 5); gtk_container_add(GTK_CONTAINER(window), main_vbox); gtk_widget_show(main_vbox); // Frame, в который будем класть виджет с графиком GtkWidget *main_frame; main_frame = gtk_frame_new("График"); gtk_box_pack_start(GTK_BOX(main_vbox), main_frame, TRUE, TRUE, 0); GtkWidget *main_hbox; main_hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_end(GTK_BOX(main_vbox), main_hbox, FALSE, FALSE, 0); GtkWidget *close_button; close_button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); g_signal_connect(GTK_OBJECT(close_button), "clicked", G_CALLBACK(quit), NULL); gtk_box_pack_end(GTK_BOX(main_hbox), close_button, FALSE, FALSE, 0); // Стандартные для GTK вещи <-- // Здесь предположим у нас есть область памяти со значениями координат mem // и количество точек nt. // Эти данные и передаем в нашу функцию. graph_open(main_frame, nt, mem); // Стандартные для GTK вещи --> gtk_widget_show_all(GTK_WIDGET(window)); gtk_main(); return 0; // Стандартные для GTK вещи <-- } // // Gtk Extra Example // http://damnsmallblog.blogspot.com //
В примере довольно универсальной функции graph_open передаются три параметра:
1) Контейнер GTK, в котором и будет отрисовн график.
2) Количество точек для отрисовки.
3) Адрес области памяти, в которой лежат значения (double) координат точек.
Вспомогательная функция graph_iter служит для распаковки точек.
main - простенькое gtk-приложение, содержащее виджет main_frame в качестве контейнера для графика.
Подписаться на:
Сообщения (Atom)
![[Image]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglZx1zAOvuAotO85VPgPbF8nLu2RwCZXqkZz3VRwGE7TFJVGFHBbS3U9L09AZTRxUFLX6nCWslVt6GxQP6rs26bUlUwaQ3UsSAkXAEiYBSY5DvLvDEj_In0eNrPpwjnGJY2ASx5bBp-eA/s320/rotated_widget.png)