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