1 /*
2  * Copyright (C) 2009 - 2012 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2010 David King <davidk@openismus.com>
4  * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301, USA.
20  */
21 
22 #include <stdlib.h>
23 #include <string.h>
24 #include <libgda/libgda.h>
25 #include <glib/gi18n-lib.h>
26 #include "gdaui-data-cell-renderer-pict.h"
27 #include "custom-marshal.h"
28 #include "common-pict.h"
29 #include <libgda/gda-enum-types.h>
30 #include "gdaui-data-cell-renderer-util.h"
31 
32 static void gdaui_data_cell_renderer_pict_get_property  (GObject *object,
33 							 guint param_id,
34 							 GValue *value,
35 							 GParamSpec *pspec);
36 static void gdaui_data_cell_renderer_pict_set_property  (GObject *object,
37 							 guint param_id,
38 							 const GValue *value,
39 							 GParamSpec *pspec);
40 static void gdaui_data_cell_renderer_pict_dispose       (GObject *object);
41 
42 static void gdaui_data_cell_renderer_pict_init       (GdauiDataCellRendererPict      *celltext);
43 static void gdaui_data_cell_renderer_pict_class_init (GdauiDataCellRendererPictClass *class);
44 static void gdaui_data_cell_renderer_pict_render     (GtkCellRenderer            *cell,
45 						      cairo_t                    *cr,
46 						      GtkWidget                  *widget,
47 						      const GdkRectangle         *background_area,
48 						      const GdkRectangle         *cell_area,
49 						      GtkCellRendererState        flags);
50 static void gdaui_data_cell_renderer_pict_get_size   (GtkCellRenderer            *cell,
51 						      GtkWidget                  *widget,
52 						      const GdkRectangle        *cell_area,
53 						      gint                       *x_offset,
54 						      gint                       *y_offset,
55 						      gint                       *width,
56 						      gint                       *height);
57 static gboolean gdaui_data_cell_renderer_pict_activate  (GtkCellRenderer            *cell,
58 							 GdkEvent                   *event,
59 							 GtkWidget                  *widget,
60 							 const gchar                *path,
61 							 const GdkRectangle         *background_area,
62 							 const GdkRectangle         *cell_area,
63 							 GtkCellRendererState        flags);
64 
65 /* get a pointer to the parents to be able to call their destructor */
66 static GObjectClass  *parent_class = NULL;
67 
68 enum {
69 	CHANGED,
70 	LAST_SIGNAL
71 };
72 
73 
74 struct _GdauiDataCellRendererPictPrivate
75 {
76 	GdaDataHandler       *dh;
77         GType                 type;
78         GValue               *value;
79 	PictBinData           bindata;
80 	PictOptions           options;
81 	PictAllocation        size;
82 	PictMenu              popup_menu;
83 	gboolean              to_be_deleted;
84 	gboolean              invalid;
85 
86 	gboolean              editable;
87 	gboolean              active;
88 	gboolean              null;
89 };
90 
91 enum {
92 	PROP_0,
93 	PROP_VALUE,
94 	PROP_VALUE_ATTRIBUTES,
95 	PROP_EDITABLE,
96 	PROP_TO_BE_DELETED
97 };
98 
99 static guint pixbuf_cell_signals[LAST_SIGNAL] = { 0 };
100 
101 
102 GType
103 gdaui_data_cell_renderer_pict_get_type (void)
104 {
105 	static GType cell_type = 0;
106 
107 	if (!cell_type) {
108 		static const GTypeInfo cell_info = {
109 			sizeof (GdauiDataCellRendererPictClass),
110 			NULL,		/* base_init */
111 			NULL,		/* base_finalize */
112 			(GClassInitFunc) gdaui_data_cell_renderer_pict_class_init,
113 			NULL,		/* class_finalize */
114 			NULL,		/* class_data */
115 			sizeof (GdauiDataCellRendererPict),
116 			0,              /* n_preallocs */
117 			(GInstanceInitFunc) gdaui_data_cell_renderer_pict_init,
118 			0
119 		};
120 
121 		cell_type = g_type_register_static (GTK_TYPE_CELL_RENDERER_PIXBUF, "GdauiDataCellRendererPict",
122 						    &cell_info, 0);
123 	}
124 
125 	return cell_type;
126 }
127 
128 static void
129 notify_property_cb (GtkCellRenderer *cell, GParamSpec *pspec, G_GNUC_UNUSED gpointer data)
130 {
131 	if (!strcmp (pspec->name, "stock-size")) {
132 		GdauiDataCellRendererPict *pictcell;
133 		guint size;
134 
135 		pictcell = (GdauiDataCellRendererPict *) cell;
136 		g_object_get ((GObject *) cell, "stock-size", &size, NULL);
137 		gtk_icon_size_lookup (size, &(pictcell->priv->size.width), &(pictcell->priv->size.height));
138 		common_pict_clear_pixbuf_cache (&(pictcell->priv->options));
139 	}
140 }
141 
142 static void
143 gdaui_data_cell_renderer_pict_init (GdauiDataCellRendererPict *cell)
144 {
145 	cell->priv = g_new0 (GdauiDataCellRendererPictPrivate, 1);
146 	cell->priv->dh = NULL;
147 	cell->priv->type = GDA_TYPE_BINARY;
148 	cell->priv->editable = FALSE;
149 
150 	cell->priv->bindata.data = NULL;
151 	cell->priv->bindata.data_length = 0;
152 	cell->priv->options.encoding = ENCODING_NONE;
153 	cell->priv->options.serialize = FALSE;
154 	common_pict_init_cache (&(cell->priv->options));
155 
156 	gtk_icon_size_lookup (GTK_ICON_SIZE_DIALOG, &(cell->priv->size.width), &(cell->priv->size.height));
157 
158 	g_object_set ((GObject*) cell, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
159 		      "xpad", 2, "ypad", 2, NULL);
160 	g_signal_connect (G_OBJECT (cell), "notify",
161 			  G_CALLBACK (notify_property_cb), NULL);
162 }
163 
164 static void
165 gdaui_data_cell_renderer_pict_class_init (GdauiDataCellRendererPictClass *class)
166 {
167 	GObjectClass *object_class = G_OBJECT_CLASS (class);
168 	GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
169 
170 	parent_class = g_type_class_peek_parent (class);
171 
172 	object_class->get_property = gdaui_data_cell_renderer_pict_get_property;
173 	object_class->set_property = gdaui_data_cell_renderer_pict_set_property;
174 	object_class->dispose = gdaui_data_cell_renderer_pict_dispose;
175 
176 	cell_class->get_size = gdaui_data_cell_renderer_pict_get_size;
177 	cell_class->render = gdaui_data_cell_renderer_pict_render;
178 	cell_class->activate = gdaui_data_cell_renderer_pict_activate;
179 
180 	g_object_class_install_property (object_class,
181 					 PROP_VALUE,
182 					 g_param_spec_boxed ("value",
183 							     _("Value"),
184 							     _("GValue to render"),
185 							     G_TYPE_VALUE,
186 							     G_PARAM_READWRITE));
187 
188 	g_object_class_install_property (object_class,
189 					 PROP_VALUE_ATTRIBUTES,
190 					 g_param_spec_flags ("value-attributes", NULL, NULL, GDA_TYPE_VALUE_ATTRIBUTE,
191 							     GDA_VALUE_ATTR_NONE, G_PARAM_READWRITE));
192 
193 	g_object_class_install_property (object_class,
194 					 PROP_EDITABLE,
195 					 g_param_spec_boolean ("editable",
196 							       _("Editable"),
197 							       _("The toggle button can be activated"),
198 							       TRUE,
199 							       G_PARAM_READABLE |
200 							       G_PARAM_WRITABLE));
201 
202 	g_object_class_install_property (object_class,
203 					 PROP_TO_BE_DELETED,
204 					 g_param_spec_boolean ("to-be-deleted", NULL, NULL, FALSE,
205                                                                G_PARAM_WRITABLE));
206 
207 	pixbuf_cell_signals[CHANGED] = g_signal_new ("changed",
208 						     G_OBJECT_CLASS_TYPE (object_class),
209 						     G_SIGNAL_RUN_LAST,
210 						     G_STRUCT_OFFSET (GdauiDataCellRendererPictClass, changed),
211 						     NULL, NULL,
212 						     _marshal_VOID__STRING_VALUE,
213 						     G_TYPE_NONE, 2,
214 						     G_TYPE_STRING,
215 						     G_TYPE_VALUE);
216 }
217 
218 static void
219 gdaui_data_cell_renderer_pict_dispose (GObject *object)
220 {
221 	GdauiDataCellRendererPict *cell;
222 
223 	g_return_if_fail (object != NULL);
224 	g_return_if_fail (GDAUI_IS_DATA_CELL_RENDERER_PICT (object));
225 	cell = GDAUI_DATA_CELL_RENDERER_PICT (object);
226 
227 	if (cell->priv) {
228 		g_hash_table_destroy (cell->priv->options.pixbuf_hash);
229 
230 		/* the private area itself */
231 		g_free (cell->priv);
232 		cell->priv = NULL;
233 	}
234 
235 	/* for the parent class */
236 	parent_class->dispose (object);
237 }
238 
239 static void
240 gdaui_data_cell_renderer_pict_get_property (GObject *object,
241 					    guint param_id,
242 					    GValue *value,
243 					    GParamSpec *pspec)
244 {
245 	GdauiDataCellRendererPict *cell = GDAUI_DATA_CELL_RENDERER_PICT (object);
246 
247 	switch (param_id) {
248 	case PROP_VALUE:
249 		g_value_set_boxed (value, cell->priv->value);
250 		break;
251 	case PROP_VALUE_ATTRIBUTES:
252 		break;
253 	case PROP_EDITABLE:
254 		g_value_set_boolean (value, cell->priv->editable);
255 		break;
256 	default:
257 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
258 		break;
259 	}
260 }
261 
262 
263 static void
264 gdaui_data_cell_renderer_pict_set_property (GObject *object,
265 					    guint param_id,
266 					    const GValue *value,
267 					    GParamSpec *pspec)
268 {
269 	GdauiDataCellRendererPict *cell = GDAUI_DATA_CELL_RENDERER_PICT (object);
270 
271 	switch (param_id) {
272 	case PROP_VALUE:
273 		/* Because we don't have a copy of the value, we MUST NOT free it! */
274                 cell->priv->value = NULL;
275 		g_object_set (G_OBJECT (cell), "pixbuf", NULL, "stock-id", NULL, NULL);
276 		if (value) {
277                         GValue *gval = g_value_get_boxed (value);
278 			GdkPixbuf *pixbuf = NULL;
279 			const gchar *stock = NULL;
280 			GError *error = NULL;
281 
282 			if (!gval)
283 				cell->priv->invalid = TRUE;
284 
285 			if (cell->priv->bindata.data) {
286 				g_free (cell->priv->bindata.data);
287 				cell->priv->bindata.data = NULL;
288 				cell->priv->bindata.data_length = 0;
289 			}
290 
291 			/* fill in cell->priv->data */
292 			if (common_pict_load_data (&(cell->priv->options), gval, &(cell->priv->bindata), &stock, &error)) {
293 				/* try to make a pixbuf */
294 				pixbuf = common_pict_fetch_cached_pixbuf (&(cell->priv->options), gval);
295 				if (pixbuf)
296 					g_object_ref (pixbuf);
297 				else {
298 					pixbuf = common_pict_make_pixbuf (&(cell->priv->options),
299 									  &(cell->priv->bindata), &(cell->priv->size),
300 									  &stock, &error);
301 					if (pixbuf)
302 						common_pict_add_cached_pixbuf (&(cell->priv->options), gval, pixbuf);
303 				}
304 
305 				if (!pixbuf && !stock)
306 					stock = GTK_STOCK_MISSING_IMAGE;
307 			}
308 
309 			/* display something */
310 			if (pixbuf) {
311 				g_object_set (G_OBJECT (cell), "pixbuf", pixbuf, NULL);
312 				g_object_unref (pixbuf);
313 			}
314 
315 			if (stock)
316 				g_object_set (G_OBJECT (cell), "stock-id", stock, NULL);
317 			if (error)
318 				g_error_free (error);
319 
320                         cell->priv->value = gval;
321                 }
322 		else
323 			cell->priv->invalid = TRUE;
324 
325                 g_object_notify (object, "value");
326 		break;
327 	case PROP_VALUE_ATTRIBUTES:
328 		cell->priv->invalid = g_value_get_flags (value) & GDA_VALUE_ATTR_DATA_NON_VALID ? TRUE : FALSE;
329 		break;
330 	case PROP_EDITABLE:
331 		cell->priv->editable = g_value_get_boolean (value);
332 		/* FIXME */
333 		/*g_object_notify (G_OBJECT(object), "editable");*/
334 		break;
335 	case PROP_TO_BE_DELETED:
336 		cell->priv->to_be_deleted = g_value_get_boolean (value);
337 		break;
338 	default:
339 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
340 		break;
341 	}
342 }
343 
344 /**
345  * gdaui_data_cell_renderer_pict_new:
346  * @dh: a #GdaDataHandler object
347  * @type:
348  * @options: options string
349  *
350  * Creates a new #GdauiDataCellRendererPict. Adjust rendering
351  * parameters using object properties. Object properties can be set
352  * globally (with g_object_set()). Also, with #GtkTreeViewColumn, you
353  * can bind a property to a value in a #GtkTreeModel. For example, you
354  * can bind the "active" property on the cell renderer to a pict value
355  * in the model, thus causing the check button to reflect the state of
356  * the model.
357  *
358  * Returns: the new cell renderer
359  */
360 GtkCellRenderer *
361 gdaui_data_cell_renderer_pict_new (GdaDataHandler *dh, GType type, const gchar *options)
362 {
363 	GObject *obj;
364         GdauiDataCellRendererPict *cell;
365 
366         g_return_val_if_fail (dh && GDA_IS_DATA_HANDLER (dh), NULL);
367         obj = g_object_new (GDAUI_TYPE_DATA_CELL_RENDERER_PICT, "stock-size", GTK_ICON_SIZE_DIALOG, NULL);
368 
369         cell = GDAUI_DATA_CELL_RENDERER_PICT (obj);
370         cell->priv->dh = dh;
371         g_object_ref (G_OBJECT (dh));
372         cell->priv->type = type;
373 
374 	common_pict_parse_options (&(cell->priv->options), options);
375 
376         return GTK_CELL_RENDERER (obj);
377 }
378 
379 static void
380 gdaui_data_cell_renderer_pict_get_size (GtkCellRenderer *cell,
381 					GtkWidget       *widget,
382 					const GdkRectangle *cell_area,
383 					gint            *x_offset,
384 					gint            *y_offset,
385 					gint            *width,
386 					gint            *height)
387 {
388 	/* FIXME */
389 	/* GtkIconSize */
390 	GtkCellRendererClass *pixbuf_class = g_type_class_peek (GTK_TYPE_CELL_RENDERER_PIXBUF);
391 
392 	(pixbuf_class->get_size) (cell, widget, cell_area, x_offset, y_offset, width, height);
393 }
394 
395 static void
396 gdaui_data_cell_renderer_pict_render (GtkCellRenderer      *cell,
397 				      cairo_t              *cr,
398 				      GtkWidget            *widget,
399 				      const GdkRectangle   *background_area,
400 				      const GdkRectangle   *cell_area,
401 				      GtkCellRendererState  flags)
402 {
403 	GdauiDataCellRendererPict *datacell = GDAUI_DATA_CELL_RENDERER_PICT (cell);
404 	GtkCellRendererClass *pixbuf_class = g_type_class_peek (GTK_TYPE_CELL_RENDERER_PIXBUF);
405 
406 	(pixbuf_class->render) (cell, cr, widget, background_area, cell_area, flags);
407 
408 	if (datacell->priv->to_be_deleted) {
409 		GtkStyleContext *style_context = gtk_widget_get_style_context (widget);
410 		guint xpad;
411 		g_object_get ((GObject*) cell, "xpad", &xpad, NULL);
412 
413 		gdouble y = cell_area->y + cell_area->height / 2.;
414 		gtk_render_line (style_context,
415 				 cr,
416 				 cell_area->x + xpad, y, cell_area->x + cell_area->width - xpad, y);
417 	}
418 	if (datacell->priv->invalid)
419 		gdaui_data_cell_renderer_draw_invalid_area (cr, cell_area);
420 }
421 
422 static void
423 pict_data_changed_cb (PictBinData *bindata, GdauiDataCellRendererPict *pictcell)
424 {
425 	GValue *value;
426 
427 	value = common_pict_get_value (bindata, &(pictcell->priv->options),
428 				       pictcell->priv->type);
429 	g_free (bindata->data);
430 	g_signal_emit (G_OBJECT (pictcell), pixbuf_cell_signals[CHANGED], 0,
431 		       g_object_get_data (G_OBJECT (pictcell), "last-path"), value);
432 	gda_value_free (value);
433 }
434 
435 static gboolean
436 gdaui_data_cell_renderer_pict_activate  (GtkCellRenderer            *cell,
437 					 G_GNUC_UNUSED GdkEvent                   *event,
438 					 GtkWidget                  *widget,
439 					 const gchar                *path,
440 					 G_GNUC_UNUSED const GdkRectangle *background_area,
441 					 G_GNUC_UNUSED const GdkRectangle *cell_area,
442 					 G_GNUC_UNUSED GtkCellRendererState        flags)
443 {
444 	GdauiDataCellRendererPict *pictcell;
445 
446 	pictcell = GDAUI_DATA_CELL_RENDERER_PICT (cell);
447 	if (pictcell->priv->editable) {
448 		int event_time;
449 
450 		g_object_set_data_full (G_OBJECT (pictcell), "last-path", g_strdup (path), g_free);
451 		if (pictcell->priv->popup_menu.menu) {
452 			gtk_widget_destroy (pictcell->priv->popup_menu.menu);
453 			pictcell->priv->popup_menu.menu = NULL;
454 		}
455 		common_pict_create_menu (&(pictcell->priv->popup_menu), widget, &(pictcell->priv->bindata),
456 					 &(pictcell->priv->options),
457 					 (PictCallback) pict_data_changed_cb, pictcell);
458 
459 		common_pict_adjust_menu_sensitiveness (&(pictcell->priv->popup_menu), pictcell->priv->editable,
460 						       &(pictcell->priv->bindata));
461 		event_time = gtk_get_current_event_time ();
462 		gtk_menu_popup (GTK_MENU (pictcell->priv->popup_menu.menu), NULL, NULL, NULL, NULL,
463 				0, event_time);
464 	}
465 
466 	return FALSE;
467 }
468 
469 
470