diff options
Diffstat (limited to 'src/text.c')
-rw-r--r-- | src/text.c | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/src/text.c b/src/text.c new file mode 100644 index 0000000..bbac187 --- /dev/null +++ b/src/text.c @@ -0,0 +1,381 @@ +/* This file is part of Ganv. + * Copyright 2007-2015 David Robillard <http://drobilla.net> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include <gtk/gtkstyle.h> + +#include "ganv/canvas.h" +#include "ganv/text.h" + +#include "./color.h" +#include "./boilerplate.h" +#include "./gettext.h" +#include "./ganv-private.h" + +G_DEFINE_TYPE_WITH_CODE(GanvText, ganv_text, GANV_TYPE_ITEM, + G_ADD_PRIVATE(GanvText)) + +static GanvItemClass* parent_class; + +enum { + PROP_0, + PROP_TEXT, + PROP_X, + PROP_Y, + PROP_WIDTH, + PROP_HEIGHT, + PROP_COLOR, + PROP_FONT_SIZE +}; + +static void +ganv_text_init(GanvText* text) +{ + GanvTextPrivate* impl = ganv_text_get_instance_private(text); + + text->impl = impl; + + memset(&impl->coords, '\0', sizeof(GanvTextCoords)); + impl->coords.width = 1.0; + impl->coords.height = 1.0; + impl->old_coords = impl->coords; + + impl->layout = NULL; + impl->text = NULL; + impl->font_size = 0.0; + impl->color = DEFAULT_TEXT_COLOR; + impl->needs_layout = FALSE; +} + +static void +ganv_text_destroy(GtkObject* object) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_TEXT(object)); + + GanvText* text = GANV_TEXT(object); + GanvTextPrivate* impl = text->impl; + + if (impl->text) { + g_free(impl->text); + impl->text = NULL; + } + + if (impl->layout) { + g_object_unref(impl->layout); + impl->layout = NULL; + } + + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } +} + +void +ganv_text_layout(GanvText* text) +{ + GanvTextPrivate* impl = text->impl; + GanvItem* item = GANV_ITEM(text); + GanvCanvas* canvas = ganv_item_get_canvas(item); + GtkWidget* widget = GTK_WIDGET(canvas); + double points = impl->font_size; + GtkStyle* style = gtk_rc_get_style(widget); + + if (impl->font_size == 0.0) { + points = ganv_canvas_get_font_size(canvas); + } + + if (impl->layout) { + g_object_unref(impl->layout); + } + impl->layout = gtk_widget_create_pango_layout(widget, impl->text); + + PangoFontDescription* font = pango_font_description_copy(style->font_desc); + PangoContext* ctx = pango_layout_get_context(impl->layout); + cairo_font_options_t* opt = cairo_font_options_copy( + pango_cairo_context_get_font_options(ctx)); + + pango_font_description_set_size(font, points * (double)PANGO_SCALE); + pango_layout_set_font_description(impl->layout, font); + pango_cairo_context_set_font_options(ctx, opt); + cairo_font_options_destroy(opt); + pango_font_description_free(font); + + int width, height; + pango_layout_get_pixel_size(impl->layout, &width, &height); + + impl->coords.width = width; + impl->coords.height = height; + impl->needs_layout = FALSE; + + ganv_item_request_update(GANV_ITEM(text)); +} + +static void +ganv_text_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_TEXT(object)); + + GanvText* text = GANV_TEXT(object); + GanvTextPrivate* impl = text->impl; + + switch (prop_id) { + case PROP_X: + impl->coords.x = g_value_get_double(value); + break; + case PROP_Y: + impl->coords.y = g_value_get_double(value); + break; + case PROP_COLOR: + impl->color = g_value_get_uint(value); + break; + case PROP_FONT_SIZE: + impl->font_size = g_value_get_double(value); + impl->needs_layout = TRUE; + break; + case PROP_TEXT: + free(impl->text); + impl->text = g_value_dup_string(value); + impl->needs_layout = TRUE; + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + return; + } + if (impl->needs_layout) { + if (GANV_IS_NODE(GANV_ITEM(text)->impl->parent)) { + GANV_NODE(GANV_ITEM(text)->impl->parent)->impl->must_resize = TRUE; + } + } + ganv_item_request_update(GANV_ITEM(text)); +} + +static void +ganv_text_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_TEXT(object)); + + GanvText* text = GANV_TEXT(object); + GanvTextPrivate* impl = text->impl; + + if (impl->needs_layout && (prop_id == PROP_WIDTH + || prop_id == PROP_HEIGHT)) { + ganv_text_layout(text); + } + + switch (prop_id) { + GET_CASE(TEXT, string, impl->text); + GET_CASE(X, double, impl->coords.x); + GET_CASE(Y, double, impl->coords.y); + GET_CASE(WIDTH, double, impl->coords.width); + GET_CASE(HEIGHT, double, impl->coords.height); + GET_CASE(COLOR, uint, impl->color); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_text_bounds_item(GanvItem* item, + double* x1, double* y1, + double* x2, double* y2) +{ + GanvText* text = GANV_TEXT(item); + GanvTextPrivate* impl = text->impl; + + if (impl->needs_layout) { + ganv_text_layout(text); + } + + *x1 = impl->coords.x; + *y1 = impl->coords.y; + *x2 = impl->coords.x + impl->coords.width; + *y2 = impl->coords.y + impl->coords.height; +} + +static void +ganv_text_bounds(GanvItem* item, + double* x1, double* y1, + double* x2, double* y2) +{ + ganv_text_bounds_item(item, x1, y1, x2, y2); +} + +static void +ganv_text_update(GanvItem* item, int flags) +{ + // Update world-relative bounding box + ganv_text_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); + + ganv_canvas_request_redraw_w( + item->impl->canvas, item->impl->x1, item->impl->y1, item->impl->x2, item->impl->y2); + + parent_class->update(item, flags); +} + +static double +ganv_text_point(GanvItem* item, double x, double y, GanvItem** actual_item) +{ + *actual_item = NULL; + + double x1, y1, x2, y2; + ganv_text_bounds_item(item, &x1, &y1, &x2, &y2); + if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2)) { + return 0.0; + } + + // Point is outside the box + double dx, dy; + + // Find horizontal distance to nearest edge + if (x < x1) { + dx = x1 - x; + } else if (x > x2) { + dx = x - x2; + } else { + dx = 0.0; + } + + // Find vertical distance to nearest edge + if (y < y1) { + dy = y1 - y; + } else if (y > y2) { + dy = y - y2; + } else { + dy = 0.0; + } + + return sqrt((dx * dx) + (dy * dy)); +} + +static void +ganv_text_draw(GanvItem* item, + cairo_t* cr, double cx, double cy, double cw, double ch) +{ + GanvText* text = GANV_TEXT(item); + GanvTextPrivate* impl = text->impl; + + double wx = impl->coords.x; + double wy = impl->coords.y; + ganv_item_i2w(item, &wx, &wy); + + if (impl->needs_layout) { + ganv_text_layout(text); + } + + double r, g, b, a; + color_to_rgba(impl->color, &r, &g, &b, &a); + + cairo_set_source_rgba(cr, r, g, b, a); + cairo_move_to(cr, wx, wy); + pango_cairo_show_layout(cr, impl->layout); +} + +static void +ganv_text_class_init(GanvTextClass* klass) +{ + GObjectClass* gobject_class = (GObjectClass*)klass; + GtkObjectClass* object_class = (GtkObjectClass*)klass; + GanvItemClass* item_class = (GanvItemClass*)klass; + + parent_class = GANV_ITEM_CLASS(g_type_class_peek_parent(klass)); + + gobject_class->set_property = ganv_text_set_property; + gobject_class->get_property = ganv_text_get_property; + + g_object_class_install_property( + gobject_class, PROP_TEXT, g_param_spec_string( + "text", + _("Text"), + _("The string to display."), + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_X, g_param_spec_double( + "x", + _("x"), + _("Top left x coordinate."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_Y, g_param_spec_double( + "y", + _("y"), + _("Top left y coordinate."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_WIDTH, g_param_spec_double( + "width", + _("Width"), + _("The current width of the text."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 1.0, + G_PARAM_READABLE)); + + g_object_class_install_property( + gobject_class, PROP_HEIGHT, g_param_spec_double( + "height", + _("Height"), + _("The current height of the text."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 1.0, + G_PARAM_READABLE)); + + g_object_class_install_property( + gobject_class, PROP_COLOR, g_param_spec_uint( + "color", + _("Color"), + _("The color of the text."), + 0, G_MAXUINT, + DEFAULT_TEXT_COLOR, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_FONT_SIZE, g_param_spec_double( + "font-size", + _("Font size"), + _("The font size in points."), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + + object_class->destroy = ganv_text_destroy; + + item_class->update = ganv_text_update; + item_class->bounds = ganv_text_bounds; + item_class->point = ganv_text_point; + item_class->draw = ganv_text_draw; +} |