1 /*
2  * Copyright (C) 2009 - 2011 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2010 David King <davidk@openismus.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19 
20 #include <libgda/libgda.h>
21 #include "browser-canvas.h"
22 #include "browser-canvas-text.h"
23 
24 static void browser_canvas_text_class_init (BrowserCanvasTextClass * class);
25 static void browser_canvas_text_init       (BrowserCanvasText *text);
26 static void browser_canvas_text_dispose    (GObject   * object);
27 static void browser_canvas_text_finalize   (GObject   * object);
28 
29 static void browser_canvas_text_set_property    (GObject *object,
30 					     guint param_id,
31 					     const GValue *value,
32 					     GParamSpec *pspec);
33 static void browser_canvas_text_get_property    (GObject *object,
34 					     guint param_id,
35 					     GValue *value,
36 					     GParamSpec *pspec);
37 
38 static gboolean enter_notify_cb (GooCanvasItem *item, GooCanvasItem *target_item, GdkEventCrossing *event, BrowserCanvasText *ct);
39 static gboolean leave_notify_cb (GooCanvasItem *item, GooCanvasItem *target_item, GdkEventCrossing *event, BrowserCanvasText *ct);
40 
41 enum
42 {
43 	PROP_0,
44 	PROP_TEXT,
45 	PROP_WIDTH,
46 	PROP_HEIGHT,
47 	PROP_HIGHLIGHT_COLOR,
48 	PROP_UNDERLINE,
49 	PROP_BOLD
50 };
51 
52 struct _BrowserCanvasTextPrivate
53 {
54 	gchar                *text;
55 
56 	/* properties */
57 	gboolean              underline;
58 	gboolean              bold;
59 	gchar                *highlight_color;
60 
61 	/* UI building information */
62         GooCanvasItem        *bg_item;
63         GooCanvasItem        *text_item;
64 
65 	/* animation */
66 	guint                 anim_id;
67 	guint                 current_anim_rgba;
68 	guint                 end_anim_rgba;
69 
70 };
71 
72 /* get a pointer to the parents to be able to call their destructor */
73 static GObjectClass *text_parent_class = NULL;
74 
75 GType
browser_canvas_text_get_type(void)76 browser_canvas_text_get_type (void)
77 {
78 	static GType type = 0;
79 
80         if (G_UNLIKELY (type == 0)) {
81 		static const GTypeInfo info = {
82 			sizeof (BrowserCanvasTextClass),
83 			(GBaseInitFunc) NULL,
84 			(GBaseFinalizeFunc) NULL,
85 			(GClassInitFunc) browser_canvas_text_class_init,
86 			NULL,
87 			NULL,
88 			sizeof (BrowserCanvasText),
89 			0,
90 			(GInstanceInitFunc) browser_canvas_text_init,
91 			0
92 		};
93 
94 		type = g_type_register_static (TYPE_BROWSER_CANVAS_ITEM, "BrowserCanvasText", &info, 0);
95 	}
96 
97 	return type;
98 }
99 
100 static void
browser_canvas_text_class_init(BrowserCanvasTextClass * class)101 browser_canvas_text_class_init (BrowserCanvasTextClass * class)
102 {
103 	GObjectClass   *object_class = G_OBJECT_CLASS (class);
104 
105 	text_parent_class = g_type_class_peek_parent (class);
106 
107 	object_class->dispose = browser_canvas_text_dispose;
108 	object_class->finalize = browser_canvas_text_finalize;
109 
110 	/* Properties */
111 	object_class->set_property = browser_canvas_text_set_property;
112 	object_class->get_property = browser_canvas_text_get_property;
113 
114 	g_object_class_install_property
115 		(object_class, PROP_WIDTH,
116 		 g_param_spec_double ("width", NULL, NULL, 0., G_MAXDOUBLE, 0., G_PARAM_WRITABLE));
117 
118 	g_object_class_install_property
119 		(object_class, PROP_HEIGHT,
120 		 g_param_spec_double ("height", NULL, NULL, 0., G_MAXDOUBLE, 0., G_PARAM_WRITABLE));
121 
122 	g_object_class_install_property
123 		(object_class, PROP_TEXT,
124 		 g_param_spec_string ("text", NULL, NULL, NULL, (G_PARAM_READABLE | G_PARAM_WRITABLE)));
125 
126 	g_object_class_install_property
127 		(object_class, PROP_HIGHLIGHT_COLOR,
128 		 g_param_spec_string ("highlight_color", NULL, NULL, NULL, (G_PARAM_READABLE | G_PARAM_WRITABLE)));
129 
130 	g_object_class_install_property
131 		(object_class, PROP_UNDERLINE,
132 		 g_param_spec_boolean ("text_underline", NULL, NULL, FALSE, (G_PARAM_READABLE | G_PARAM_WRITABLE)));
133 
134 	g_object_class_install_property
135 		(object_class, PROP_BOLD,
136 		 g_param_spec_boolean ("text_bold", NULL, NULL, FALSE, (G_PARAM_READABLE | G_PARAM_WRITABLE)));
137 }
138 
139 static void
browser_canvas_text_init(BrowserCanvasText * text)140 browser_canvas_text_init (BrowserCanvasText *text)
141 {
142 	text->priv = g_new0 (BrowserCanvasTextPrivate, 1);
143 	text->priv->text = NULL;
144 	text->priv->highlight_color = g_strdup (BROWSER_CANVAS_ENTITY_COLOR);
145 
146 	g_signal_connect (G_OBJECT (text), "enter-notify-event",
147 			  G_CALLBACK (enter_notify_cb), text);
148 	g_signal_connect (G_OBJECT (text), "leave-notify-event",
149 			  G_CALLBACK (leave_notify_cb), text);
150 }
151 
152 static void
browser_canvas_text_dispose(GObject * object)153 browser_canvas_text_dispose (GObject *object)
154 {
155 	BrowserCanvasText *ct;
156 	g_return_if_fail (object != NULL);
157 	g_return_if_fail (IS_BROWSER_CANVAS_TEXT (object));
158 
159 	ct = BROWSER_CANVAS_TEXT (object);
160 
161 	/* animation */
162 	if (ct->priv->anim_id) {
163 		g_source_remove (ct->priv->anim_id);
164 		ct->priv->anim_id = 0;
165 	}
166 
167 	/* for the parent class */
168 	text_parent_class->dispose (object);
169 }
170 
171 
172 static void
browser_canvas_text_finalize(GObject * object)173 browser_canvas_text_finalize (GObject *object)
174 {
175 	BrowserCanvasText *ct;
176 	g_return_if_fail (object != NULL);
177 	g_return_if_fail (IS_BROWSER_CANVAS_TEXT (object));
178 
179 	ct = BROWSER_CANVAS_TEXT (object);
180 	if (ct->priv) {
181 		g_free (ct->priv->text);
182 
183 		if (ct->priv->highlight_color)
184 			g_free (ct->priv->highlight_color);
185 
186 		g_free (ct->priv);
187 		ct->priv = NULL;
188 	}
189 
190 	/* for the parent class */
191 	text_parent_class->finalize (object);
192 }
193 
194 static void clean_items (BrowserCanvasText *ct);
195 static void create_items (BrowserCanvasText *ct);
196 
197 static void
adjust_text_pango_attributes(BrowserCanvasText * ct)198 adjust_text_pango_attributes (BrowserCanvasText *ct)
199 {
200 	if (! ct->priv->text_item)
201 		return;
202 
203 	if (ct->priv->bold || ct->priv->underline) {
204 		gchar *str;
205 		if (ct->priv->bold) {
206 			if (ct->priv->underline)
207 				str = g_strdup_printf ("<b><u>%s</u></b>", ct->priv->text);
208 			else
209 				str = g_strdup_printf ("<b>%s</b>", ct->priv->text);
210 		}
211 		else
212 			str = g_strdup_printf ("<u>%s</u>", ct->priv->text);
213 		g_object_set (G_OBJECT (ct->priv->text_item),
214 			      "text", str,
215 			      "use-markup", TRUE, NULL);
216 		g_free (str);
217 	}
218 	else
219 		g_object_set (G_OBJECT (ct->priv->text_item),
220 			      "text", ct->priv->text,
221 			      "use-markup", FALSE, NULL);
222 }
223 
224 static void
browser_canvas_text_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)225 browser_canvas_text_set_property (GObject *object,
226 				  guint param_id,
227 				  const GValue *value,
228 				  GParamSpec *pspec)
229 {
230 	BrowserCanvasText *ct = NULL;
231 	const gchar *cstr = NULL;
232 	gchar *str;
233 	gdouble size = 0;
234 	gboolean bool = FALSE;
235 
236 	ct = BROWSER_CANVAS_TEXT (object);
237 
238 	switch (param_id) {
239 	case PROP_TEXT:
240 		g_free (ct->priv->text);
241 		ct->priv->text = NULL;
242 		clean_items (ct);
243 		ct->priv->text = g_strdup (g_value_get_string (value));
244 		create_items (ct);
245 		break;
246 	case PROP_WIDTH:
247 		size = g_value_get_double (value);
248 		if (ct->priv->bg_item)
249 			g_object_set (G_OBJECT (ct->priv->bg_item),
250 				      "width", size,
251 				      NULL);
252 		break;
253 	case PROP_HEIGHT:
254 		size = g_value_get_double (value);
255 		if (ct->priv->bg_item)
256 			g_object_set (G_OBJECT (ct->priv->bg_item),
257 				      "height", size,
258 				      NULL);
259 		break;
260 	case PROP_HIGHLIGHT_COLOR:
261 		cstr = g_value_get_string (value);
262 		if (ct->priv->highlight_color) {
263 			g_free (ct->priv->highlight_color);
264 			ct->priv->highlight_color = NULL;
265 		}
266 		if (cstr)
267 			ct->priv->highlight_color = g_strdup (cstr);
268 		else
269 			ct->priv->highlight_color = g_strdup (BROWSER_CANVAS_ENTITY_COLOR);
270 		break;
271 	case PROP_UNDERLINE:
272 		bool = g_value_get_boolean (value);
273 		ct->priv->underline = bool;
274 		adjust_text_pango_attributes (ct);
275 		if (ct->priv->text_item) {
276 			if (bool) {
277 				str = g_strdup_printf ("<u>%s</u>", ct->priv->text);
278 				g_object_set (G_OBJECT (ct->priv->text_item),
279 					      "text", str,
280 					      "use-markup", TRUE, NULL);
281 				g_free (str);
282 			}
283 			else
284 				g_object_set (G_OBJECT (ct->priv->text_item),
285 					      "text", ct->priv->text,
286 					      "use-markup", FALSE, NULL);
287 		}
288 	case PROP_BOLD:
289 		bool = g_value_get_boolean (value);
290 		ct->priv->bold = bool;
291 		adjust_text_pango_attributes (ct);
292 		break;
293 	default:
294 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
295 		break;
296 	}
297 }
298 
299 static void
browser_canvas_text_get_property(GObject * object,guint param_id,G_GNUC_UNUSED GValue * value,GParamSpec * pspec)300 browser_canvas_text_get_property    (GObject *object,
301 				    guint param_id,
302 				    G_GNUC_UNUSED GValue *value,
303 				    GParamSpec *pspec)
304 {
305 	switch (param_id) {
306 	default:
307 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
308 		break;
309 	}
310 }
311 
312 /*
313  * destroy any existing GooCanvasItem obejcts
314  */
315 static void
clean_items(BrowserCanvasText * ct)316 clean_items (BrowserCanvasText *ct)
317 {
318 	if (ct->priv->bg_item) {
319 		goo_canvas_item_remove (GOO_CANVAS_ITEM (ct->priv->bg_item));
320 		ct->priv->bg_item = NULL;
321 	}
322 	if (ct->priv->text_item) {
323 		goo_canvas_item_remove (GOO_CANVAS_ITEM (ct->priv->text_item));
324 		ct->priv->text_item = NULL;
325 	}
326 }
327 
328 /*
329  * create new GooCanvasItem objects
330  */
331 static void
create_items(BrowserCanvasText * ct)332 create_items (BrowserCanvasText *ct)
333 {
334 	GooCanvasItem *item, *text;
335 	GooCanvasBounds bounds;
336 
337 	g_object_set (G_OBJECT (ct),
338 		      "allow_move", FALSE,
339 		      NULL);
340 
341 	/* text: text's name */
342 	text = goo_canvas_text_new (GOO_CANVAS_ITEM (ct), ct->priv->text,
343 				    0., 0.,
344 				    -1, GOO_CANVAS_ANCHOR_NORTH_WEST,
345 				    "fill_color", "black",
346 				    "font", BROWSER_CANVAS_FONT,
347 				    "alignment", PANGO_ALIGN_RIGHT,
348 				    NULL);
349 	ct->priv->text_item = text;
350 
351 	/* UI metrics */
352 	goo_canvas_item_get_bounds (text, &bounds);
353 
354 	/* background */
355 	item = goo_canvas_rect_new (GOO_CANVAS_ITEM (ct),
356 				    0., 0.,
357 				    bounds.x2 - bounds.x1,
358 				    bounds.y2 - bounds.y1,
359 				    "fill_color", BROWSER_CANVAS_OBJ_BG_COLOR,
360 				    "radius-x", 2.,
361 				    "radius-y", 2.,
362 				    "stroke-pattern", NULL,
363 				    NULL);
364 
365 	ct->priv->bg_item = item;
366 	goo_canvas_item_lower (item, NULL);
367 
368 	adjust_text_pango_attributes (ct);
369 }
370 
371 static gboolean
enter_notify_cb(G_GNUC_UNUSED GooCanvasItem * item,G_GNUC_UNUSED GooCanvasItem * target_item,G_GNUC_UNUSED GdkEventCrossing * event,BrowserCanvasText * ct)372 enter_notify_cb (G_GNUC_UNUSED GooCanvasItem *item, G_GNUC_UNUSED GooCanvasItem *target_item,
373 		 G_GNUC_UNUSED GdkEventCrossing *event, BrowserCanvasText *ct)
374 {
375 	browser_canvas_text_set_highlight (ct, TRUE);
376 	return FALSE;
377 }
378 
379 static gboolean
leave_notify_cb(G_GNUC_UNUSED GooCanvasItem * item,G_GNUC_UNUSED GooCanvasItem * target_item,G_GNUC_UNUSED GdkEventCrossing * event,BrowserCanvasText * ct)380 leave_notify_cb (G_GNUC_UNUSED GooCanvasItem *item, G_GNUC_UNUSED GooCanvasItem *target_item,
381 		 G_GNUC_UNUSED GdkEventCrossing *event, BrowserCanvasText *ct)
382 {
383 	browser_canvas_text_set_highlight (ct, FALSE);
384 	return FALSE;
385 }
386 
387 static guint
compute_step_value(guint current,guint end)388 compute_step_value (guint current, guint end)
389 {
390 	const guint STEP = 15;
391 	if (current < end)
392 		return current + MIN (STEP, (end - current));
393 	else if (current > end)
394 		return current - MIN (STEP, (current - end));
395 	else
396 		return current;
397 }
398 
399 static gboolean
anim_cb(BrowserCanvasText * ct)400 anim_cb (BrowserCanvasText *ct)
401 {
402 	guint current, end, value;
403 	guint rgba = 0;
404 
405 	/* red */
406 	current = (ct->priv->current_anim_rgba >> 24) & 0xFF;
407 	end = (ct->priv->end_anim_rgba >> 24) & 0xFF;
408 	value = compute_step_value (current, end) << 24;
409 	rgba += value;
410 
411 	/* green */
412 	current = (ct->priv->current_anim_rgba >> 16) & 0xFF;
413 	end = (ct->priv->end_anim_rgba >> 16) & 0xFF;
414 	value = compute_step_value (current, end) << 16;
415 	rgba += value;
416 
417 	/* blue */
418 	current = (ct->priv->current_anim_rgba >> 8) & 0xFF;
419 	end = (ct->priv->end_anim_rgba >> 8) & 0xFF;
420 	value = compute_step_value (current, end) << 8;
421 	rgba += value;
422 
423 	/* alpha */
424 	current = ct->priv->current_anim_rgba & 0xFF;
425 	end = ct->priv->end_anim_rgba & 0xFF;
426 	value = compute_step_value (current, end);
427 	rgba += value;
428 
429 	if (rgba == ct->priv->end_anim_rgba) {
430 		ct->priv->anim_id = 0;
431 		return FALSE;
432 	}
433 	else {
434 		g_object_set (G_OBJECT (ct->priv->bg_item),  "fill_color_rgba", rgba, NULL);
435 		ct->priv->current_anim_rgba = rgba;
436 		return TRUE;
437 	}
438 }
439 
440 /**
441  * browser_canvas_text_set_highlight
442  * @ct: a #BrowserCanvasText object
443  * @highlight:
444  *
445  * Turns ON or OFF the highlighting of @ct
446  */
447 void
browser_canvas_text_set_highlight(BrowserCanvasText * ct,gboolean highlight)448 browser_canvas_text_set_highlight (BrowserCanvasText *ct, gboolean highlight)
449 {
450 	gchar *str_color;
451 	GdkColor gdk_color;
452 
453 	g_return_if_fail (ct && IS_BROWSER_CANVAS_TEXT (ct));
454 	g_return_if_fail (ct->priv);
455 
456 	if (! ct->priv->bg_item)
457 		return;
458 
459 	if (ct->priv->anim_id) {
460 		g_source_remove (ct->priv->anim_id);
461 		ct->priv->anim_id = 0;
462 	}
463 
464 	str_color = highlight ? ct->priv->highlight_color : BROWSER_CANVAS_OBJ_BG_COLOR;
465 	if (gdk_color_parse (str_color, &gdk_color)) {
466 		guint col;
467 
468 		col = ((guint) (gdk_color.red * 255. / 65535.0));
469 		ct->priv->end_anim_rgba = col << 24;
470 		col = ((guint) (gdk_color.green * 255. / 65535.0));
471 		ct->priv->end_anim_rgba += col << 16;
472 		col = ((guint) (gdk_color.blue * 255. / 65535.0));
473 		ct->priv->end_anim_rgba += col << 8;
474 		if (!ct->priv->current_anim_rgba)
475 			ct->priv->current_anim_rgba = ct->priv->end_anim_rgba;
476 
477 		if (highlight)
478 			ct->priv->end_anim_rgba += 255;
479 		else
480 			ct->priv->end_anim_rgba += 50;
481 
482 		ct->priv->anim_id = g_timeout_add (10, (GSourceFunc) anim_cb, ct);
483 	}
484 	else
485 		g_object_set (G_OBJECT (ct->priv->bg_item),  "fill_color", str_color, NULL);
486 }
487 
488 /**
489  * browser_canvas_text_new
490  * @parent: the parent item, or NULL.
491  * @txt: text to display
492  * @x: the x coordinate of the text.
493  * @y: the y coordinate of the text.
494  * @...: optional pairs of property names and values, and a terminating NULL.
495  *
496  * Creates a new canvas item to display the @txt message
497  *
498  * Returns: a new #GooCanvasItem object
499  */
500 GooCanvasItem *
browser_canvas_text_new(GooCanvasItem * parent,const gchar * txt,gdouble x,gdouble y,...)501 browser_canvas_text_new (GooCanvasItem *parent,
502 		       const gchar *txt,
503 		       gdouble x,
504 		       gdouble y,
505 		       ...)
506 {
507 	GooCanvasItem *item;
508 	const char *first_property;
509 	va_list var_args;
510 
511 	item = g_object_new (TYPE_BROWSER_CANVAS_TEXT, NULL);
512 
513 	if (parent) {
514 		goo_canvas_item_add_child (parent, item, -1);
515 		g_object_unref (item);
516 	}
517 
518 	va_start (var_args, y);
519 	first_property = va_arg (var_args, char*);
520 	if (first_property)
521 		g_object_set_valist ((GObject*) item, first_property, var_args);
522 	va_end (var_args);
523 
524 	g_object_set (item, "text", txt, NULL);
525 	goo_canvas_item_translate (item, x, y);
526 
527 	return item;
528 }
529