1 /* This file is part of Ganv.
2 * Copyright 2007-2015 David Robillard <http://drobilla.net>
3 *
4 * Ganv is free software: you can redistribute it and/or modify it under the
5 * terms of the GNU General Public License as published by the Free Software
6 * Foundation, either version 3 of the License, or any later version.
7 *
8 * Ganv is distributed in the hope that it will be useful, but WITHOUT ANY
9 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
11 *
12 * You should have received a copy of the GNU General Public License along
13 * with Ganv. If not, see <http://www.gnu.org/licenses/>.
14 */
15
16 #include "boilerplate.h"
17 #include "color.h"
18 #include "ganv-private.h"
19 #include "gettext.h"
20
21 #include "ganv/canvas.h"
22 #include "ganv/item.h"
23 #include "ganv/node.h"
24 #include "ganv/text.h"
25 #include "ganv/types.h"
26
27 #include <cairo.h>
28 #include <glib-object.h>
29 #include <glib.h>
30 #include <gtk/gtk.h>
31 #include <pango/pango-font.h>
32 #include <pango/pango-fontmap.h>
33 #include <pango/pango-layout.h>
34 #include <pango/pango-types.h>
35 #include <pango/pangocairo.h>
36
37 #include <math.h>
38 #include <stdlib.h>
39 #include <string.h>
40
41 G_DEFINE_TYPE_WITH_CODE(GanvText, ganv_text, GANV_TYPE_ITEM,
42 G_ADD_PRIVATE(GanvText))
43
44 static GanvItemClass* parent_class;
45
46 enum {
47 PROP_0,
48 PROP_TEXT,
49 PROP_X,
50 PROP_Y,
51 PROP_WIDTH,
52 PROP_HEIGHT,
53 PROP_COLOR,
54 PROP_FONT_SIZE
55 };
56
57 static void
ganv_text_init(GanvText * text)58 ganv_text_init(GanvText* text)
59 {
60 GanvTextPrivate* impl =
61 (GanvTextPrivate*)ganv_text_get_instance_private(text);
62
63 text->impl = impl;
64
65 memset(&impl->coords, '\0', sizeof(GanvTextCoords));
66 impl->coords.width = 1.0;
67 impl->coords.height = 1.0;
68 impl->old_coords = impl->coords;
69
70 impl->layout = NULL;
71 impl->text = NULL;
72 impl->font_size = 0.0;
73 impl->color = DEFAULT_TEXT_COLOR;
74 impl->needs_layout = FALSE;
75 }
76
77 static void
ganv_text_destroy(GtkObject * object)78 ganv_text_destroy(GtkObject* object)
79 {
80 g_return_if_fail(object != NULL);
81 g_return_if_fail(GANV_IS_TEXT(object));
82
83 GanvText* text = GANV_TEXT(object);
84 GanvTextPrivate* impl = text->impl;
85
86 if (impl->text) {
87 g_free(impl->text);
88 impl->text = NULL;
89 }
90
91 if (impl->layout) {
92 g_object_unref(impl->layout);
93 impl->layout = NULL;
94 }
95
96 if (GTK_OBJECT_CLASS(parent_class)->destroy) {
97 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
98 }
99 }
100
101 void
ganv_text_layout(GanvText * text)102 ganv_text_layout(GanvText* text)
103 {
104 GanvTextPrivate* impl = text->impl;
105 GanvItem* item = GANV_ITEM(text);
106 GanvCanvas* canvas = ganv_item_get_canvas(item);
107 GtkWidget* widget = GTK_WIDGET(canvas);
108 double points = impl->font_size;
109 GtkStyle* style = gtk_rc_get_style(widget);
110
111 if (impl->font_size == 0.0) {
112 points = ganv_canvas_get_font_size(canvas);
113 }
114
115 if (impl->layout) {
116 g_object_unref(impl->layout);
117 }
118 impl->layout = gtk_widget_create_pango_layout(widget, impl->text);
119
120 PangoFontDescription* font = pango_font_description_copy(style->font_desc);
121 PangoContext* ctx = pango_layout_get_context(impl->layout);
122 cairo_font_options_t* opt = cairo_font_options_copy(
123 pango_cairo_context_get_font_options(ctx));
124
125 pango_font_description_set_size(font, points * (double)PANGO_SCALE);
126 pango_layout_set_font_description(impl->layout, font);
127 pango_cairo_context_set_font_options(ctx, opt);
128 cairo_font_options_destroy(opt);
129 pango_font_description_free(font);
130
131 int width = 0;
132 int height = 0;
133 pango_layout_get_pixel_size(impl->layout, &width, &height);
134
135 impl->coords.width = width;
136 impl->coords.height = height;
137 impl->needs_layout = FALSE;
138
139 ganv_item_request_update(GANV_ITEM(text));
140 }
141
142 static void
ganv_text_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)143 ganv_text_set_property(GObject* object,
144 guint prop_id,
145 const GValue* value,
146 GParamSpec* pspec)
147 {
148 g_return_if_fail(object != NULL);
149 g_return_if_fail(GANV_IS_TEXT(object));
150
151 GanvText* text = GANV_TEXT(object);
152 GanvTextPrivate* impl = text->impl;
153
154 switch (prop_id) {
155 case PROP_X:
156 impl->coords.x = g_value_get_double(value);
157 break;
158 case PROP_Y:
159 impl->coords.y = g_value_get_double(value);
160 break;
161 case PROP_COLOR:
162 impl->color = g_value_get_uint(value);
163 break;
164 case PROP_FONT_SIZE:
165 impl->font_size = g_value_get_double(value);
166 impl->needs_layout = TRUE;
167 break;
168 case PROP_TEXT:
169 free(impl->text);
170 impl->text = g_value_dup_string(value);
171 impl->needs_layout = TRUE;
172 break;
173 default:
174 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
175 return;
176 }
177 if (impl->needs_layout) {
178 if (GANV_IS_NODE(GANV_ITEM(text)->impl->parent)) {
179 GANV_NODE(GANV_ITEM(text)->impl->parent)->impl->must_resize = TRUE;
180 }
181 }
182 ganv_item_request_update(GANV_ITEM(text));
183 }
184
185 static void
ganv_text_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)186 ganv_text_get_property(GObject* object,
187 guint prop_id,
188 GValue* value,
189 GParamSpec* pspec)
190 {
191 g_return_if_fail(object != NULL);
192 g_return_if_fail(GANV_IS_TEXT(object));
193
194 GanvText* text = GANV_TEXT(object);
195 GanvTextPrivate* impl = text->impl;
196
197 if (impl->needs_layout && (prop_id == PROP_WIDTH
198 || prop_id == PROP_HEIGHT)) {
199 ganv_text_layout(text);
200 }
201
202 switch (prop_id) {
203 GET_CASE(TEXT, string, impl->text)
204 GET_CASE(X, double, impl->coords.x)
205 GET_CASE(Y, double, impl->coords.y)
206 GET_CASE(WIDTH, double, impl->coords.width)
207 GET_CASE(HEIGHT, double, impl->coords.height)
208 GET_CASE(COLOR, uint, impl->color)
209 default:
210 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
211 break;
212 }
213 }
214
215 static void
ganv_text_bounds_item(GanvItem * item,double * x1,double * y1,double * x2,double * y2)216 ganv_text_bounds_item(GanvItem* item,
217 double* x1, double* y1,
218 double* x2, double* y2)
219 {
220 GanvText* text = GANV_TEXT(item);
221 GanvTextPrivate* impl = text->impl;
222
223 if (impl->needs_layout) {
224 ganv_text_layout(text);
225 }
226
227 *x1 = impl->coords.x;
228 *y1 = impl->coords.y;
229 *x2 = impl->coords.x + impl->coords.width;
230 *y2 = impl->coords.y + impl->coords.height;
231 }
232
233 static void
ganv_text_bounds(GanvItem * item,double * x1,double * y1,double * x2,double * y2)234 ganv_text_bounds(GanvItem* item,
235 double* x1, double* y1,
236 double* x2, double* y2)
237 {
238 ganv_text_bounds_item(item, x1, y1, x2, y2);
239 }
240
241 static void
ganv_text_update(GanvItem * item,int flags)242 ganv_text_update(GanvItem* item, int flags)
243 {
244 // Update world-relative bounding box
245 ganv_text_bounds(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2);
246 ganv_item_i2w_pair(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2);
247
248 ganv_canvas_request_redraw_w(
249 item->impl->canvas, item->impl->x1, item->impl->y1, item->impl->x2, item->impl->y2);
250
251 parent_class->update(item, flags);
252 }
253
254 static double
ganv_text_point(GanvItem * item,double x,double y,GanvItem ** actual_item)255 ganv_text_point(GanvItem* item, double x, double y, GanvItem** actual_item)
256 {
257 *actual_item = NULL;
258
259 double x1 = 0.0;
260 double y1 = 0.0;
261 double x2 = 0.0;
262 double y2 = 0.0;
263 ganv_text_bounds_item(item, &x1, &y1, &x2, &y2);
264 if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2)) {
265 return 0.0;
266 }
267
268 // Point is outside the box
269 double dx = 0.0;
270 double dy = 0.0;
271
272 // Find horizontal distance to nearest edge
273 if (x < x1) {
274 dx = x1 - x;
275 } else if (x > x2) {
276 dx = x - x2;
277 } else {
278 dx = 0.0;
279 }
280
281 // Find vertical distance to nearest edge
282 if (y < y1) {
283 dy = y1 - y;
284 } else if (y > y2) {
285 dy = y - y2;
286 } else {
287 dy = 0.0;
288 }
289
290 return sqrt((dx * dx) + (dy * dy));
291 }
292
293 static void
ganv_text_draw(GanvItem * item,cairo_t * cr,double cx,double cy,double cw,double ch)294 ganv_text_draw(GanvItem* item,
295 cairo_t* cr, double cx, double cy, double cw, double ch)
296 {
297 (void)cx;
298 (void)cy;
299 (void)cw;
300 (void)ch;
301
302 GanvText* text = GANV_TEXT(item);
303 GanvTextPrivate* impl = text->impl;
304
305 double wx = impl->coords.x;
306 double wy = impl->coords.y;
307 ganv_item_i2w(item, &wx, &wy);
308
309 if (impl->needs_layout) {
310 ganv_text_layout(text);
311 }
312
313 double r = 0.0;
314 double g = 0.0;
315 double b = 0.0;
316 double a = 0.0;
317 color_to_rgba(impl->color, &r, &g, &b, &a);
318
319 cairo_set_source_rgba(cr, r, g, b, a);
320 cairo_move_to(cr, wx, wy);
321 pango_cairo_show_layout(cr, impl->layout);
322 }
323
324 static void
ganv_text_class_init(GanvTextClass * klass)325 ganv_text_class_init(GanvTextClass* klass)
326 {
327 GObjectClass* gobject_class = (GObjectClass*)klass;
328 GtkObjectClass* object_class = (GtkObjectClass*)klass;
329 GanvItemClass* item_class = (GanvItemClass*)klass;
330
331 parent_class = GANV_ITEM_CLASS(g_type_class_peek_parent(klass));
332
333 gobject_class->set_property = ganv_text_set_property;
334 gobject_class->get_property = ganv_text_get_property;
335
336 g_object_class_install_property(
337 gobject_class, PROP_TEXT, g_param_spec_string(
338 "text",
339 _("Text"),
340 _("The string to display."),
341 NULL,
342 G_PARAM_READWRITE));
343
344 g_object_class_install_property(
345 gobject_class, PROP_X, g_param_spec_double(
346 "x",
347 _("x"),
348 _("Top left x coordinate."),
349 -G_MAXDOUBLE, G_MAXDOUBLE,
350 0.0,
351 G_PARAM_READWRITE));
352
353 g_object_class_install_property(
354 gobject_class, PROP_Y, g_param_spec_double(
355 "y",
356 _("y"),
357 _("Top left y coordinate."),
358 -G_MAXDOUBLE, G_MAXDOUBLE,
359 0.0,
360 G_PARAM_READWRITE));
361
362 g_object_class_install_property(
363 gobject_class, PROP_WIDTH, g_param_spec_double(
364 "width",
365 _("Width"),
366 _("The current width of the text."),
367 -G_MAXDOUBLE, G_MAXDOUBLE,
368 1.0,
369 G_PARAM_READABLE));
370
371 g_object_class_install_property(
372 gobject_class, PROP_HEIGHT, g_param_spec_double(
373 "height",
374 _("Height"),
375 _("The current height of the text."),
376 -G_MAXDOUBLE, G_MAXDOUBLE,
377 1.0,
378 G_PARAM_READABLE));
379
380 g_object_class_install_property(
381 gobject_class, PROP_COLOR, g_param_spec_uint(
382 "color",
383 _("Color"),
384 _("The color of the text."),
385 0, G_MAXUINT,
386 DEFAULT_TEXT_COLOR,
387 G_PARAM_READWRITE));
388
389 g_object_class_install_property(
390 gobject_class, PROP_FONT_SIZE, g_param_spec_double(
391 "font-size",
392 _("Font size"),
393 _("The font size in points."),
394 -G_MAXDOUBLE, G_MAXDOUBLE,
395 0.0,
396 G_PARAM_READWRITE));
397
398
399 object_class->destroy = ganv_text_destroy;
400
401 item_class->update = ganv_text_update;
402 item_class->bounds = ganv_text_bounds;
403 item_class->point = ganv_text_point;
404 item_class->draw = ganv_text_draw;
405 }
406