/* * This is a plug-in for GIMP. * * Generates clickable image maps. * * Copyright (C) 1998-2005 Maurits Rijk m.rijk@chello.nl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "config.h" #include #include #include "libgimp/gimp.h" #include "libgimp/gimpui.h" #include "imap_commands.h" #include "imap_main.h" #include "imap_misc.h" #include "imap_menu.h" #include "imap_object_popup.h" #include "imap_polygon.h" #include "imap_stock.h" #include "imap_table.h" #include "libgimp/stdplugins-intl.h" #define MAX_POLYGON_POINTS 99 static gboolean polygon_is_valid(Object_t *obj); static void polygon_destruct(Object_t *obj); static Object_t *polygon_clone(Object_t *obj); static void polygon_assign(Object_t *obj, Object_t *des); static void polygon_draw(Object_t* obj, cairo_t *cr); static void polygon_draw_sashes(Object_t* obj, cairo_t *cr); static MoveSashFunc_t polygon_near_sash(Object_t *obj, gint x, gint y); static gboolean polygon_point_is_on(Object_t *obj, gint x, gint y); static void polygon_get_dimensions(Object_t *obj, gint *x, gint *y, gint *width, gint *height); static void polygon_resize(Object_t *obj, gint percentage_x, gint percentage_y); static void polygon_move(Object_t *obj, gint dx, gint dy); static gpointer polygon_create_info_widget(GtkWidget *frame); static void polygon_update_info_widget(Object_t *obj, gpointer data); static void polygon_fill_info_tab(Object_t *obj, gpointer data); static void polygon_set_initial_focus(Object_t *obj, gpointer data); static void polygon_update(Object_t* obj, gpointer data); static void polygon_write_csim(Object_t* obj, gpointer param, OutputFunc_t output); static void polygon_write_cern(Object_t* obj, gpointer param, OutputFunc_t output); static void polygon_write_ncsa(Object_t* obj, gpointer param, OutputFunc_t output); static void polygon_do_popup(Object_t *obj, GdkEventButton *event); static const gchar* polygon_get_stock_icon_name(void); static ObjectClass_t polygon_class = { N_("_Polygon"), NULL, /* info_dialog */ polygon_is_valid, polygon_destruct, polygon_clone, polygon_assign, NULL, /* polygon_normalize */ polygon_draw, polygon_draw_sashes, polygon_near_sash, polygon_point_is_on, polygon_get_dimensions, polygon_resize, polygon_move, polygon_create_info_widget, polygon_update_info_widget, polygon_fill_info_tab, polygon_set_initial_focus, polygon_update, polygon_write_csim, polygon_write_cern, polygon_write_ncsa, polygon_do_popup, polygon_get_stock_icon_name }; Object_t* create_polygon(GList *points) { Polygon_t *polygon = g_new(Polygon_t, 1); polygon->points = points; return object_init(&polygon->obj, &polygon_class); } static void polygon_free_list (Polygon_t *polygon) { g_list_free_full (polygon->points, (GDestroyNotify) g_free); polygon->points = NULL; } static void polygon_destruct(Object_t *obj) { Polygon_t *polygon = ObjectToPolygon(obj); polygon_free_list(polygon); } static gboolean polygon_is_valid(Object_t *obj) { return g_list_length(ObjectToPolygon(obj)->points) > 2; } static Object_t* polygon_clone(Object_t *obj) { Polygon_t *polygon = ObjectToPolygon(obj); Polygon_t *clone = g_new(Polygon_t, 1); GList *p; clone->points = NULL; for (p = polygon->points; p; p = p->next) { GdkPoint *point = (GdkPoint*) p->data; polygon_append_point(clone, point->x, point->y); } return &clone->obj; } static void polygon_assign(Object_t *obj, Object_t *des) { Polygon_t *src_polygon = ObjectToPolygon(obj); Polygon_t *des_polygon = ObjectToPolygon(des); GList *p; polygon_free_list(des_polygon); for (p = src_polygon->points; p; p = p->next) { GdkPoint *point = (GdkPoint*) p->data; polygon_append_point(des_polygon, point->x, point->y); } } static void polygon_draw(Object_t *obj, cairo_t *cr) { Polygon_t *polygon = ObjectToPolygon(obj); draw_polygon(cr, polygon->points); } static void polygon_draw_sashes(Object_t *obj, cairo_t *cr) { Polygon_t *polygon = ObjectToPolygon(obj); GList *p; for (p = polygon->points; p; p = p->next) { GdkPoint *point = (GdkPoint*) p->data; draw_sash(cr, point->x, point->y); } } static GdkPoint *_sash_point; static gint _sash_index; static void move_sash(Object_t *obj, gint dx, gint dy) { _sash_point->x += dx; _sash_point->y += dy; } static MoveSashFunc_t polygon_near_sash(Object_t *obj, gint x, gint y) { Polygon_t *polygon = ObjectToPolygon(obj); GList *p; _sash_index = 0; for (p = polygon->points; p; p = p->next, _sash_index++) { GdkPoint *point = (GdkPoint*) p->data; if (near_sash(point->x, point->y, x, y)) { _sash_point = point; return move_sash; } } return NULL; } static gboolean right_intersect(GdkPoint *p1, GdkPoint *p2, gint x, gint y) { gint dx = p2->x - p1->x; gint dy = p2->y - p1->y; if ((dy > 0 && y > p1->y && y < p2->y) || (dy < 0 && y > p2->y && y < p1->y)) { gint sx = p1->x + (y - p1->y) * dx / dy; return sx > x; } return FALSE; } static gboolean polygon_point_is_on(Object_t *obj, gint x, gint y) { Polygon_t *polygon = ObjectToPolygon(obj); GList *p; int count = 0; GdkPoint *first, *prev; p = polygon->points; first = prev = (GdkPoint*) p->data; p = p->next; for (; p; p = p->next) { GdkPoint *point = (GdkPoint*) p->data; if (right_intersect(prev, point, x, y)) count++; prev = point; } if (right_intersect(prev, first, x, y)) count++; return count % 2; } static void polygon_get_dimensions(Object_t *obj, gint *x, gint *y, gint *width, gint *height) { Polygon_t *polygon = ObjectToPolygon(obj); gint min_x = G_MAXINT, min_y = G_MAXINT; gint max_x = G_MININT, max_y = G_MININT; GList *p; for (p = polygon->points; p; p = p->next) { GdkPoint *point = (GdkPoint*) p->data; if (point->x < min_x) min_x = point->x; if (point->x > max_x) max_x = point->x; if (point->y < min_y) min_y = point->y; if (point->y > max_y) max_y = point->y; } *x = min_x; *y = min_y; *width = max_x - min_x; *height = max_y - min_y; } static void polygon_resize(Object_t *obj, gint percentage_x, gint percentage_y) { Polygon_t *polygon = ObjectToPolygon(obj); GList *p; for (p = polygon->points; p; p = p->next) { GdkPoint *point = (GdkPoint*) p->data; point->x = point->x * percentage_x / 100; point->y = point->y * percentage_y / 100; } } static void polygon_move(Object_t *obj, gint dx, gint dy) { Polygon_t *polygon = ObjectToPolygon(obj); GList *p; for (p = polygon->points; p; p = p->next) { GdkPoint *point = (GdkPoint*) p->data; point->x += dx; point->y += dy; } } typedef struct { Object_t *obj; GtkListStore *store; GtkTreeSelection *selection; GtkWidget *x; GtkWidget *y; GtkWidget *update; GtkWidget *insert; GtkWidget *append; GtkWidget *remove; gint selected_row; guint timeout; } PolygonProperties_t; static void select_row_cb(GtkTreeSelection *selection, PolygonProperties_t *data) { GtkTreeIter iter; GtkTreeModel *model; if (gtk_tree_selection_get_selected (selection, &model, &iter)) { GdkPoint *point; gtk_tree_model_get (model, &iter, 0, &point, -1); _sash_point = point; gtk_spin_button_set_value(GTK_SPIN_BUTTON(data->x), point->x); gtk_spin_button_set_value(GTK_SPIN_BUTTON(data->y), point->y); } } static void update_button_clicked(GtkWidget *widget, PolygonProperties_t *data) { GtkTreeIter iter; GtkTreeModel *model = GTK_TREE_MODEL(data->store); if (gtk_tree_selection_get_selected (data->selection, &model, &iter)) { GdkPoint *point; gint x = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(data->x)); gint y = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(data->y)); gtk_tree_model_get (model, &iter, 0, &point, -1); point->x = x; point->y = y; gtk_list_store_set (data->store, &iter, 0, point, -1); } } static void set_buttons_sensitivity(PolygonProperties_t *data) { gint rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL(data->store), NULL); gtk_widget_set_sensitive(data->insert, rows != MAX_POLYGON_POINTS); gtk_widget_set_sensitive(data->append, rows != MAX_POLYGON_POINTS); gtk_widget_set_sensitive(data->remove, rows > 2); } static void insert_button_clicked(GtkWidget *widget, PolygonProperties_t *data) { GtkTreeIter iter; GtkTreeModel *model = GTK_TREE_MODEL(data->store); if (gtk_tree_selection_get_selected (data->selection, &model, &iter)) { Polygon_t *polygon = ObjectToPolygon(data->obj); GdkPoint *point; GList *here; gint x = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(data->x)); gint y = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(data->y)); gtk_tree_model_get (model, &iter, 0, &point, -1); here = g_list_find(polygon->points, point); polygon->points = g_list_insert_before(polygon->points, here, new_point(x, y)); polygon_fill_info_tab(data->obj, data); } } static void append_button_clicked(GtkWidget *widget, PolygonProperties_t *data) { gint x = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(data->x)); gint y = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(data->y)); polygon_append_point(ObjectToPolygon(data->obj), x, y); polygon_fill_info_tab(data->obj, data); } static void remove_button_clicked(GtkWidget *widget, PolygonProperties_t *data) { GtkTreeIter iter; GtkTreeModel *model = GTK_TREE_MODEL(data->store); if (gtk_tree_selection_get_selected (data->selection, &model, &iter)) { Polygon_t *polygon = ObjectToPolygon(data->obj); GdkPoint *point; gtk_tree_model_get (model, &iter, 0, &point, -1); polygon->points = g_list_remove(polygon->points, point); g_free(point); polygon_fill_info_tab(data->obj, data); } } static void x_changed_cb(GtkWidget *widget, gpointer data) { Object_t *obj = ((PolygonProperties_t*) data)->obj; gint x = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget)); _sash_point->x = x; edit_area_info_dialog_emit_geometry_signal(obj->class->info_dialog); } static void y_changed_cb(GtkWidget *widget, gpointer data) { Object_t *obj = ((PolygonProperties_t*) data)->obj; gint y = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget)); _sash_point->y = y; edit_area_info_dialog_emit_geometry_signal(obj->class->info_dialog); } static void render_x(GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { GdkPoint *point; gchar scratch[16]; gtk_tree_model_get(tree_model, iter, 0, &point, -1); sprintf(scratch, "%d", point->x); g_object_set(cell, "text", scratch, "xalign", 1.0, NULL); } static void render_y(GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { GdkPoint *point; gchar scratch[16]; gtk_tree_model_get(tree_model, iter, 0, &point, -1); sprintf(scratch, "%d", point->y); g_object_set(cell, "text", scratch, "xalign", 1.0, NULL); } static gpointer polygon_create_info_widget(GtkWidget *frame) { PolygonProperties_t *props = g_new(PolygonProperties_t, 1); GtkWidget *hbox, *swin, *table, *label; GtkWidget *view; gint max_width = get_image_width(); gint max_height = get_image_height(); GtkCellRenderer *renderer; GtkTreeViewColumn *column; hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_container_add(GTK_CONTAINER(frame), hbox); gtk_widget_show(hbox); swin = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(swin), GTK_SHADOW_IN); gtk_box_pack_start(GTK_BOX(hbox), swin, FALSE, FALSE, FALSE); gtk_widget_show(swin); props->store = gtk_list_store_new (1, G_TYPE_POINTER); view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (props->store)); gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); g_object_unref (props->store); gtk_widget_show (view); renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes (_("x (pixels)"), renderer, NULL); gtk_tree_view_column_set_cell_data_func(column, renderer, render_x, props, NULL); gtk_tree_view_column_set_alignment(column, 0.5); gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes (_("y (pixels)"), renderer, NULL); gtk_tree_view_column_set_cell_data_func(column, renderer, render_y, props, NULL); gtk_tree_view_column_set_alignment(column, 0.5); gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); gtk_container_add (GTK_CONTAINER (swin), view); table = gtk_table_new(6, 3, FALSE); gtk_table_set_row_spacings(GTK_TABLE(table), 6); gtk_table_set_col_spacings(GTK_TABLE(table), 6); gtk_box_pack_start(GTK_BOX(hbox), table, FALSE, FALSE, FALSE); gtk_widget_show(table); label = create_label_in_table(table, 0, 0, "_x:"); props->x = create_spin_button_in_table(table, label, 0, 1, 1, 0, max_width - 1); g_signal_connect(props->x, "changed", G_CALLBACK(x_changed_cb), (gpointer) props); gtk_widget_set_size_request(props->x, 64, -1); create_label_in_table(table, 0, 2, _("pixels")); label = create_label_in_table(table, 1, 0, "_y:"); props->y = create_spin_button_in_table(table, label, 1, 1, 1, 0, max_height - 1); g_signal_connect(props->y, "changed", G_CALLBACK(y_changed_cb), (gpointer) props); gtk_widget_set_size_request(props->y, 64, -1); create_label_in_table(table, 1, 2, _("pixels")); props->update = gtk_button_new_with_mnemonic(_("_Update")); g_signal_connect(props->update, "clicked", G_CALLBACK(update_button_clicked), props); gtk_table_attach_defaults(GTK_TABLE(table), props->update, 1, 2, 2, 3); gtk_widget_show(props->update); props->insert = gtk_button_new_with_mnemonic(_("_Insert")); g_signal_connect(props->insert, "clicked", G_CALLBACK(insert_button_clicked), props); gtk_table_attach_defaults(GTK_TABLE(table), props->insert, 1, 2, 3, 4); gtk_widget_show(props->insert); props->append = gtk_button_new_with_mnemonic(_("A_ppend")); g_signal_connect(props->append, "clicked", G_CALLBACK(append_button_clicked), props); gtk_table_attach_defaults(GTK_TABLE(table), props->append, 1, 2, 4, 5); gtk_widget_show(props->append); props->remove = gtk_button_new_with_mnemonic(_("_Remove")); g_signal_connect(props->remove, "clicked", G_CALLBACK(remove_button_clicked), props); gtk_table_attach_defaults(GTK_TABLE(table), props->remove, 1, 2, 5, 6); gtk_widget_show(props->remove); props->timeout = 0; props->selection = gtk_tree_view_get_selection(GTK_TREE_VIEW (view)); gtk_tree_selection_set_mode(props->selection, GTK_SELECTION_SINGLE); g_signal_connect (props->selection, "changed", G_CALLBACK (select_row_cb), props); return props; } static gboolean update_timeout(gpointer data) { PolygonProperties_t *props = (PolygonProperties_t*) data; polygon_fill_info_tab(props->obj, data); return FALSE; } static void polygon_update_info_widget(Object_t *obj, gpointer data) { PolygonProperties_t *props = (PolygonProperties_t*) data; GtkTreeIter iter; gtk_spin_button_set_value(GTK_SPIN_BUTTON(props->x), _sash_point->x); gtk_spin_button_set_value(GTK_SPIN_BUTTON(props->y), _sash_point->y); if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(props->store), &iter, NULL, _sash_index)) { gtk_tree_selection_select_iter(props->selection, &iter); } if (props->timeout) g_source_remove(props->timeout); props->timeout = g_timeout_add(1000, update_timeout, data); } static void polygon_fill_info_tab(Object_t *obj, gpointer data) { Polygon_t *polygon = ObjectToPolygon(obj); PolygonProperties_t *props = (PolygonProperties_t*) data; GtkTreeIter iter; GList *p; props->obj = obj; gtk_list_store_clear(props->store); for (p = polygon->points; p; p = p->next) { gtk_list_store_append(props->store, &iter); gtk_list_store_set(props->store, &iter, 0, p->data, -1); } if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(props->store), &iter, NULL, _sash_index)) { gtk_tree_selection_select_iter(props->selection, &iter); } set_buttons_sensitivity(props); } static void polygon_set_initial_focus(Object_t *obj, gpointer data) { PolygonProperties_t *props = (PolygonProperties_t*) data; gtk_widget_grab_focus(props->x); } static void polygon_update(Object_t* obj, gpointer data) { /* Nothing to be done! */ } static void polygon_write_csim(Object_t *obj, gpointer param, OutputFunc_t output) { Polygon_t *polygon = ObjectToPolygon(obj); GList *p; output(param, "\"poly\" coords=\""); for (p = polygon->points; p; p = p->next) { GdkPoint *point = (GdkPoint*) p->data; output(param, "%d,%d", point->x, point->y); output(param, "%c", (p->next) ? ',' : '"'); } } static void polygon_write_cern(Object_t *obj, gpointer param, OutputFunc_t output) { Polygon_t *polygon = ObjectToPolygon(obj); GList *p; GdkPoint *first = (GdkPoint*) polygon->points->data; output(param, "poly "); for (p = polygon->points; p; p = p->next) { GdkPoint *point = (GdkPoint*) p->data; output(param, "(%d,%d) ", point->x, point->y); } output(param, "(%d,%d)", first->x, first->y); } static void polygon_write_ncsa(Object_t *obj, gpointer param, OutputFunc_t output) { Polygon_t *polygon = ObjectToPolygon(obj); GList *p; GdkPoint *first = (GdkPoint*) polygon->points->data; output(param, "poly %s", obj->url); for (p = polygon->points; p; p = p->next) { GdkPoint *point = (GdkPoint*) p->data; output(param, " %d,%d", point->x, point->y); } output(param, " %d,%d", first->x, first->y); } static Object_t *_current_obj; static gboolean _insert_edge; static gint _insert_x; static gint _insert_y; void polygon_insert_point(void) { Command_t *command = insert_point_command_new (_current_obj, _insert_x, _insert_y, _insert_edge); command_execute (command); } void polygon_delete_point(void) { Command_t *command = delete_point_command_new(_current_obj, _sash_point); command_execute (command); } static gboolean point_near_edge(GdkPoint *first, GdkPoint *second, gint x, gint y) { gint den, nom; gdouble u; den = (first->x - x) * (first->x - second->x) + (first->y - y) * (first->y - second->y); nom = (second->x - first->x) * (second->x - first->x) + (second->y - first->y) * (second->y - first->y); u = (gdouble) den / nom; if (u >= 0.0 && u <= 1.0) { gint sx = first->x + (gint) (u * (second->x - first->x)) - x; gint sy = first->y + (gint) (u * (second->y - first->y)) - y; return sx * sx + sy * sy <= 25; /* Fix me! */ } return FALSE; } static gint polygon_near_edge(Object_t *obj, gint x, gint y) { Polygon_t *polygon = ObjectToPolygon(obj); GList *p = polygon->points; GdkPoint *first = (GdkPoint*) p->data; GdkPoint *prev = first; gint n = 1; for (p = p->next; p; p = p->next, n++) { GdkPoint *next = (GdkPoint*) p->data; if (point_near_edge(prev, next, x, y)) return n; prev = next; } return (point_near_edge(prev, first, x, y)) ? n + 1 : 0; } static void polygon_handle_popup (GdkEventButton *event, gboolean near_sash, gboolean near_edge) { GtkWidget *popup = menu_get_widget ("/PolygonPopupMenu"); GtkWidget *delete = menu_get_widget ("/PolygonPopupMenu/DeletePoint"); GtkWidget *insert = menu_get_widget ("/PolygonPopupMenu/InsertPoint"); gtk_widget_set_sensitive (delete, near_sash); gtk_widget_set_sensitive (insert, near_edge); gtk_menu_popup(GTK_MENU(popup), NULL, NULL, NULL, NULL, event->button, event->time); } static void polygon_do_popup(Object_t *obj, GdkEventButton *event) { gint x = get_real_coord ((gint) event->x); gint y = get_real_coord ((gint) event->y); _current_obj = obj; if (polygon_near_sash (obj, x, y)) { polygon_handle_popup (event, TRUE, FALSE); } else { _insert_edge = polygon_near_edge (obj, x, y); if (_insert_edge) { _insert_x = x; _insert_y = y; polygon_handle_popup (event, FALSE, TRUE); } else { object_do_popup (obj, event); } } } static const gchar* polygon_get_stock_icon_name(void) { return IMAP_STOCK_POLYGON; } static GList *_prev_link; static gboolean polygon_factory_finish(Object_t *obj, gint x, gint y) { Polygon_t *polygon = ObjectToPolygon(obj); GdkPoint *prev_point = (GdkPoint*) _prev_link->data; if (x == prev_point->x && y == prev_point->y) { polygon_remove_last_point(polygon); return TRUE; } else { polygon_append_point(polygon, x, y); _prev_link = _prev_link->next; } return FALSE; } static gboolean polygon_factory_cancel(GdkEventButton *event, Object_t *obj) { if (event->state & GDK_SHIFT_MASK) { return TRUE; } else { Polygon_t *polygon = ObjectToPolygon(obj); GList *link = _prev_link; _prev_link = _prev_link->prev; g_free((GdkPoint*) link->data); polygon->points = g_list_remove_link(polygon->points, link); } return _prev_link == NULL; } static Object_t* polygon_factory_create_object(gint x, gint y) { GList *points; points = _prev_link = g_list_append(NULL, new_point(x, y)); points = g_list_append(points, new_point(x, y)); return create_polygon(points); } static void polygon_factory_set_xy(Object_t *obj, guint state, gint x, gint y) { Polygon_t *polygon = ObjectToPolygon(obj); GList *last = g_list_last(polygon->points); GdkPoint *point = (GdkPoint*) last->data; GdkPoint *prev = (GdkPoint*) last->prev->data; point->x = x; point->y = y; main_set_dimension(x - prev->x, y - prev->y); } static ObjectFactory_t polygon_factory = { NULL, /* Object pointer */ polygon_factory_finish, polygon_factory_cancel, polygon_factory_create_object, polygon_factory_set_xy }; ObjectFactory_t* get_polygon_factory(guint state) { return &polygon_factory; } void polygon_remove_last_point(Polygon_t *polygon) { GList *last = g_list_last(polygon->points); g_free((GdkPoint*) last->data); polygon->points = g_list_remove_link(polygon->points, last); } GdkPoint* new_point(gint x, gint y) { GdkPoint *point = g_new(GdkPoint, 1); point->x = x; point->y = y; return point; } void polygon_append_point(Polygon_t *polygon, gint x, gint y) { polygon->points = g_list_append(polygon->points, new_point(x, y)); }