/* This file is part of Ganv. * Copyright 2007-2015 David Robillard * * Ganv 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 any later version. * * Ganv 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 details. * * You should have received a copy of the GNU General Public License along * with Ganv. If not, see . */ #include "boilerplate.h" #include "color.h" #include "ganv-private.h" #include "gettext.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include G_DEFINE_TYPE_WITH_CODE(GanvCircle, ganv_circle, GANV_TYPE_NODE, G_ADD_PRIVATE(GanvCircle)) static GanvNodeClass* parent_class; enum { PROP_0, PROP_RADIUS, PROP_RADIUS_EMS, PROP_FIT_LABEL }; static void ganv_circle_init(GanvCircle* circle) { circle->impl = (GanvCirclePrivate*)ganv_circle_get_instance_private(circle); memset(&circle->impl->coords, '\0', sizeof(GanvCircleCoords)); circle->impl->coords.radius = 0.0; circle->impl->coords.radius_ems = 1.0; circle->impl->coords.width = 2.0; circle->impl->old_coords = circle->impl->coords; circle->impl->fit_label = TRUE; } static void ganv_circle_destroy(GtkObject* object) { g_return_if_fail(object != NULL); g_return_if_fail(GANV_IS_CIRCLE(object)); if (GTK_OBJECT_CLASS(parent_class)->destroy) { (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); } } static void ganv_circle_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) { g_return_if_fail(object != NULL); g_return_if_fail(GANV_IS_CIRCLE(object)); GanvCircle* circle = GANV_CIRCLE(object); switch (prop_id) { SET_CASE(RADIUS, double, circle->impl->coords.radius) SET_CASE(RADIUS_EMS, double, circle->impl->coords.radius_ems) SET_CASE(FIT_LABEL, boolean, circle->impl->fit_label) default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } if (prop_id == PROP_RADIUS_EMS) { ganv_circle_set_radius_ems(circle, circle->impl->coords.radius_ems); } } static void ganv_circle_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) { g_return_if_fail(object != NULL); g_return_if_fail(GANV_IS_CIRCLE(object)); GanvCircle* circle = GANV_CIRCLE(object); switch (prop_id) { GET_CASE(RADIUS, double, circle->impl->coords.radius) GET_CASE(RADIUS_EMS, double, circle->impl->coords.radius_ems) GET_CASE(FIT_LABEL, boolean, circle->impl->fit_label) default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void ganv_circle_resize(GanvNode* self) { GanvNode* node = GANV_NODE(self); GanvCircle* circle = GANV_CIRCLE(self); GanvCanvas* canvas = GANV_CANVAS(GANV_ITEM(node)->impl->canvas); if (node->impl->label) { if (node->impl->label->impl->needs_layout) { ganv_text_layout(node->impl->label); } const double label_w = node->impl->label->impl->coords.width; const double label_h = node->impl->label->impl->coords.height; if (circle->impl->fit_label) { // Resize to fit text const double radius = MAX(label_w, label_h) / 2.0 + 3.0; if (radius != circle->impl->coords.radius) { ganv_item_set(GANV_ITEM(self), "radius", radius, NULL); } } // Center label ganv_item_set(GANV_ITEM(node->impl->label), "x", label_w / -2.0, "y", label_h / -2.0, NULL); } if (parent_class->resize) { parent_class->resize(self); } ganv_canvas_for_each_edge_on( canvas, node, (GanvEdgeFunc)ganv_edge_update_location, NULL); } static void ganv_circle_redraw_text(GanvNode* self) { GanvCircle* circle = GANV_CIRCLE(self); if (circle->impl->coords.radius_ems) { ganv_circle_set_radius_ems(circle, circle->impl->coords.radius_ems); } if (parent_class->redraw_text) { parent_class->redraw_text(self); } } static gboolean ganv_circle_is_within(const GanvNode* self, double x1, double y1, double x2, double y2) { const double x = GANV_ITEM(self)->impl->x; const double y = GANV_ITEM(self)->impl->y; return x >= x1 && x <= x2 && y >= y1 && y <= y2; } static void ganv_circle_vector(const GanvNode* self, const GanvNode* other, double* x, double* y, double* dx, double* dy) { GanvCircle* circle = GANV_CIRCLE(self); const double cx = GANV_ITEM(self)->impl->x; const double cy = GANV_ITEM(self)->impl->y; const double other_x = GANV_ITEM(other)->impl->x; const double other_y = GANV_ITEM(other)->impl->y; const double border = circle->node.impl->border_width; const double xdist = other_x - cx; const double ydist = other_y - cy; const double h = sqrt((xdist * xdist) + (ydist * ydist)); const double theta = asin(xdist / (h + DBL_EPSILON)); const double y_mod = (cy < other_y) ? 1 : -1; const double ret_h = h - circle->impl->coords.radius - border / 2.0; const double ret_x = other_x - sin(theta) * ret_h; const double ret_y = other_y - cos(theta) * ret_h * y_mod; *x = ret_x; *y = ret_y; *dx = 0.0; *dy = 0.0; ganv_item_i2w(GANV_ITEM(circle)->impl->parent, x, y); } static void request_redraw(GanvItem* item, const GanvCircleCoords* coords, gboolean world) { const double w = coords->width; double x1 = coords->x - coords->radius - w; double y1 = coords->y - coords->radius - w; double x2 = coords->x + coords->radius + w; double y2 = coords->y + coords->radius + w; if (!world) { // Convert from parent-relative coordinates to world coordinates ganv_item_i2w_pair(item, &x1, &y1, &x2, &y2); } ganv_canvas_request_redraw_w(item->impl->canvas, x1, y1, x2, y2); } static void coords_i2w(GanvItem* item, GanvCircleCoords* coords) { ganv_item_i2w(item, &coords->x, &coords->y); } static void ganv_circle_bounds_item(GanvItem* item, double* x1, double* y1, double* x2, double* y2) { const GanvCircle* circle = GANV_CIRCLE(item); const GanvCircleCoords* coords = &circle->impl->coords; *x1 = coords->x - coords->radius - coords->width; *y1 = coords->y - coords->radius - coords->width; *x2 = coords->x + coords->radius + coords->width; *y2 = coords->y + coords->radius + coords->width; } static void ganv_circle_bounds(GanvItem* item, double* x1, double* y1, double* x2, double* y2) { ganv_circle_bounds_item(item, x1, y1, x2, y2); } static void ganv_circle_update(GanvItem* item, int flags) { GanvCircle* circle = GANV_CIRCLE(item); GanvCirclePrivate* impl = circle->impl; impl->coords.width = circle->node.impl->border_width; // Request redraw of old location request_redraw(item, &impl->old_coords, TRUE); // Store old coordinates in world relative coordinates in case the // group we are in moves between now and the next update impl->old_coords = impl->coords; coords_i2w(item, &impl->old_coords); // Update world-relative bounding box ganv_circle_bounds(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2); ganv_item_i2w_pair(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2); // Request redraw of new location request_redraw(item, &impl->coords, FALSE); GANV_ITEM_CLASS(parent_class)->update(item, flags); } static void ganv_circle_draw(GanvItem* item, cairo_t* cr, double cx, double cy, double cw, double ch) { GanvNode* node = GANV_NODE(item); GanvCircle* circle = GANV_CIRCLE(item); GanvCirclePrivate* impl = circle->impl; double r = 0.0; double g = 0.0; double b = 0.0; double a = 0.0; double x = impl->coords.x; double y = impl->coords.y; ganv_item_i2w(item, &x, &y); double dash_length = 0.0; double border_color = 0.0; double fill_color = 0.0; ganv_node_get_draw_properties( &circle->node, &dash_length, &border_color, &fill_color); // Fill cairo_new_path(cr); cairo_arc(cr, x, y, impl->coords.radius + (impl->coords.width / 2.0), 0, 2 * G_PI); color_to_rgba(fill_color, &r, &g, &b, &a); cairo_set_source_rgba(cr, r, g, b, a); cairo_fill(cr); // Border cairo_arc(cr, x, y, impl->coords.radius, 0, 2 * G_PI); color_to_rgba(border_color, &r, &g, &b, &a); cairo_set_source_rgba(cr, r, g, b, a); cairo_set_line_width(cr, impl->coords.width); if (dash_length > 0) { cairo_set_dash(cr, &dash_length, 1, circle->node.impl->dash_offset); } else { cairo_set_dash(cr, &dash_length, 0, 0); } cairo_stroke(cr); // Draw label if (node->impl->label) { GanvItem* label_item = GANV_ITEM(node->impl->label); if (label_item->object.flags & GANV_ITEM_VISIBLE) { GANV_ITEM_GET_CLASS(label_item)->draw( label_item, cr, cx, cy, cw, ch); } } } static double ganv_circle_point(GanvItem* item, double x, double y, GanvItem** actual_item) { const GanvCircle* circle = GANV_CIRCLE(item); const GanvCircleCoords* coords = &circle->impl->coords; *actual_item = item; const double dx = fabs(x - coords->x); const double dy = fabs(y - coords->y); const double d = sqrt((dx * dx) + (dy * dy)); if (d <= coords->radius + coords->width) { // Point is inside the circle return 0.0; } else { // Distance from the edge of the circle return d - (coords->radius + coords->width); } } static void ganv_circle_class_init(GanvCircleClass* klass) { GObjectClass* gobject_class = (GObjectClass*)klass; GtkObjectClass* object_class = (GtkObjectClass*)klass; GanvItemClass* item_class = (GanvItemClass*)klass; GanvNodeClass* node_class = (GanvNodeClass*)klass; parent_class = GANV_NODE_CLASS(g_type_class_peek_parent(klass)); gobject_class->set_property = ganv_circle_set_property; gobject_class->get_property = ganv_circle_get_property; g_object_class_install_property( gobject_class, PROP_RADIUS, g_param_spec_double( "radius", _("Radius"), _("The radius of the circle."), 0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_class_install_property( gobject_class, PROP_RADIUS_EMS, g_param_spec_double( "radius-ems", _("Radius in ems"), _("The radius of the circle in ems."), 0, G_MAXDOUBLE, 1.0, G_PARAM_READWRITE)); g_object_class_install_property( gobject_class, PROP_FIT_LABEL, g_param_spec_boolean( "fit-label", _("Fit label"), _("If true, expand circle to fit its label"), TRUE, (GParamFlags)G_PARAM_READWRITE)); object_class->destroy = ganv_circle_destroy; node_class->resize = ganv_circle_resize; node_class->is_within = ganv_circle_is_within; node_class->tail_vector = ganv_circle_vector; node_class->head_vector = ganv_circle_vector; node_class->redraw_text = ganv_circle_redraw_text; item_class->update = ganv_circle_update; item_class->bounds = ganv_circle_bounds; item_class->point = ganv_circle_point; item_class->draw = ganv_circle_draw; } GanvCircle* ganv_circle_new(GanvCanvas* canvas, const char* first_property_name, ...) { GanvCircle* circle = GANV_CIRCLE( g_object_new(ganv_circle_get_type(), "canvas", canvas, NULL)); va_list args; va_start(args, first_property_name); g_object_set_valist(G_OBJECT(circle), first_property_name, args); va_end(args); return circle; } double ganv_circle_get_radius(const GanvCircle* circle) { return circle->impl->coords.radius; } void ganv_circle_set_radius(GanvCircle* circle, double radius) { circle->impl->coords.radius = radius; ganv_item_request_update(GANV_ITEM(circle)); } double ganv_circle_get_radius_ems(const GanvCircle* circle) { return circle->impl->coords.radius_ems; } void ganv_circle_set_radius_ems(GanvCircle* circle, double ems) { GanvCanvas* canvas = GANV_CANVAS(GANV_ITEM(circle)->impl->canvas); const double points = ganv_canvas_get_font_size(canvas); circle->impl->coords.radius_ems = ems; circle->impl->coords.radius = points * ems; ganv_item_request_update(GANV_ITEM(circle)); } gboolean ganv_circle_get_fit_label(const GanvCircle* circle) { return circle->impl->fit_label; } void ganv_circle_set_fit_label(GanvCircle* circle, gboolean fit_label) { circle->impl->fit_label = fit_label; ganv_item_request_update(GANV_ITEM(circle)); }