1 /*
2  * Copyright (C) 2009 - 2011 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2010 David King <davidk@openismus.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library 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 GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301, USA.
19  */
20 
21 #include <glib/gi18n-lib.h>
22 #include "gdaui-entry-number.h"
23 #include "gdaui-numeric-entry.h"
24 #include <libgda/gda-data-handler.h>
25 #include "gdk/gdkkeysyms.h"
26 #include <libgda/gda-debug-macros.h>
27 
28 /*
29  * Main static functions
30  */
31 static void gdaui_entry_number_class_init (GdauiEntryNumberClass *klass);
32 static void gdaui_entry_number_init (GdauiEntryNumber *srv);
33 static void gdaui_entry_number_dispose (GObject *object);
34 static void gdaui_entry_number_finalize (GObject *object);
35 
36 static void gdaui_entry_number_set_property (GObject *object,
37 					     guint param_id,
38 					     const GValue *value,
39 					     GParamSpec *pspec);
40 static void gdaui_entry_number_get_property (GObject *object,
41 					     guint param_id,
42 					     GValue *value,
43 					     GParamSpec *pspec);
44 
45 /* properties */
46 enum
47 {
48 	PROP_0,
49 	PROP_EDITING_CANCELED,
50 	PROP_OPTIONS
51 };
52 
53 /* GtkCellEditable interface */
54 static void gdaui_entry_number_cell_editable_init (GtkCellEditableIface *iface);
55 static void gdaui_entry_number_start_editing (GtkCellEditable *iface, GdkEvent *event);
56 static void sync_entry_options (GdauiEntryNumber *mgstr);
57 
58 /* virtual functions */
59 static GtkWidget *create_entry (GdauiEntryWrapper *mgwrap);
60 static void       real_set_value (GdauiEntryWrapper *mgwrap, const GValue *value);
61 static GValue    *real_get_value (GdauiEntryWrapper *mgwrap);
62 static void       connect_signals(GdauiEntryWrapper *mgwrap, GCallback modify_cb, GCallback activate_cb);
63 static void       set_editable (GdauiEntryWrapper *mgwrap, gboolean editable);
64 static void       grab_focus (GdauiEntryWrapper *mgwrap);
65 
66 /* options */
67 static void set_entry_options (GdauiEntryNumber *mgstr, const gchar *options);
68 
69 /* get a pointer to the parents to be able to call their destructor */
70 static GObjectClass  *parent_class = NULL;
71 
72 /* private structure */
73 struct _GdauiEntryNumberPrivate
74 {
75 	GtkWidget     *entry;
76 	gboolean       editing_canceled;
77 
78 	guchar         thousand_sep;
79 	guint16        nb_decimals;
80 	gchar         *currency;
81 
82 	gulong         entry_change_sig;
83 };
84 
85 static void
gdaui_entry_number_cell_editable_init(GtkCellEditableIface * iface)86 gdaui_entry_number_cell_editable_init (GtkCellEditableIface *iface)
87 {
88 	iface->start_editing = gdaui_entry_number_start_editing;
89 }
90 
91 GType
gdaui_entry_number_get_type(void)92 gdaui_entry_number_get_type (void)
93 {
94 	static GType type = 0;
95 
96 	if (G_UNLIKELY (type == 0)) {
97 		static const GTypeInfo info = {
98 			sizeof (GdauiEntryNumberClass),
99 			(GBaseInitFunc) NULL,
100 			(GBaseFinalizeFunc) NULL,
101 			(GClassInitFunc) gdaui_entry_number_class_init,
102 			NULL,
103 			NULL,
104 			sizeof (GdauiEntryNumber),
105 			0,
106 			(GInstanceInitFunc) gdaui_entry_number_init,
107 			0
108 		};
109 
110 		static const GInterfaceInfo cell_editable_info = {
111 			(GInterfaceInitFunc) gdaui_entry_number_cell_editable_init,    /* interface_init */
112 			NULL,                                                 /* interface_finalize */
113 			NULL                                                  /* interface_data */
114 		};
115 
116 		type = g_type_register_static (GDAUI_TYPE_ENTRY_WRAPPER, "GdauiEntryNumber", &info, 0);
117 		g_type_add_interface_static (type, GTK_TYPE_CELL_EDITABLE, &cell_editable_info);
118 	}
119 	return type;
120 }
121 
122 static void
gdaui_entry_number_class_init(GdauiEntryNumberClass * klass)123 gdaui_entry_number_class_init (GdauiEntryNumberClass * klass)
124 {
125 	GObjectClass   *object_class = G_OBJECT_CLASS (klass);
126 
127 	parent_class = g_type_class_peek_parent (klass);
128 
129 	object_class->dispose = gdaui_entry_number_dispose;
130 	object_class->finalize = gdaui_entry_number_finalize;
131 
132 	GDAUI_ENTRY_WRAPPER_CLASS (klass)->create_entry = create_entry;
133 	GDAUI_ENTRY_WRAPPER_CLASS (klass)->real_set_value = real_set_value;
134 	GDAUI_ENTRY_WRAPPER_CLASS (klass)->real_get_value = real_get_value;
135 	GDAUI_ENTRY_WRAPPER_CLASS (klass)->connect_signals = connect_signals;
136 	GDAUI_ENTRY_WRAPPER_CLASS (klass)->set_editable = set_editable;
137 	GDAUI_ENTRY_WRAPPER_CLASS (klass)->grab_focus = grab_focus;
138 
139 	/* Properties */
140 	object_class->set_property = gdaui_entry_number_set_property;
141 	object_class->get_property = gdaui_entry_number_get_property;
142 
143 	g_object_class_install_property (object_class, PROP_EDITING_CANCELED,
144 					 g_param_spec_boolean ("editing-canceled", NULL, NULL, FALSE,
145 							       G_PARAM_READABLE | G_PARAM_WRITABLE));
146 	g_object_class_install_property (object_class, PROP_OPTIONS,
147 					 g_param_spec_string ("options", NULL, NULL, NULL, G_PARAM_WRITABLE));
148 }
149 
150 static gboolean
key_press_event_cb(GdauiEntryNumber * mgstr,GdkEventKey * key_event,G_GNUC_UNUSED gpointer data)151 key_press_event_cb (GdauiEntryNumber *mgstr, GdkEventKey *key_event, G_GNUC_UNUSED gpointer data)
152 {
153 	if (key_event->keyval == GDK_KEY_Escape)
154 		mgstr->priv->editing_canceled = TRUE;
155 	return FALSE;
156 }
157 
158 static void
gdaui_entry_number_init(GdauiEntryNumber * mgstr)159 gdaui_entry_number_init (GdauiEntryNumber *mgstr)
160 {
161 	mgstr->priv = g_new0 (GdauiEntryNumberPrivate, 1);
162 	mgstr->priv->entry = NULL;
163 
164 	mgstr->priv->thousand_sep = 0;
165 	mgstr->priv->nb_decimals = G_MAXUINT16; /* unlimited number of decimals */
166 	mgstr->priv->currency = NULL;
167 
168 	mgstr->priv->entry_change_sig = 0;
169 
170 	g_signal_connect (mgstr, "key-press-event",
171 			  G_CALLBACK (key_press_event_cb), NULL);
172 }
173 
174 gboolean
gdaui_entry_number_is_type_numeric(GType type)175 gdaui_entry_number_is_type_numeric (GType type)
176 {
177 	if ((type == G_TYPE_INT64) || (type == G_TYPE_UINT64) || (type == G_TYPE_DOUBLE) ||
178 	    (type == G_TYPE_INT) || (type == GDA_TYPE_NUMERIC) || (type == G_TYPE_FLOAT) ||
179 	    (type == GDA_TYPE_SHORT) || (type == GDA_TYPE_USHORT) || (type == G_TYPE_CHAR) ||
180 	    (type == G_TYPE_UCHAR) || (type == G_TYPE_LONG) || (type ==  G_TYPE_ULONG) || (type == G_TYPE_UINT))
181 		return TRUE;
182 	else
183 		return FALSE;
184 }
185 
186 /**
187  * gdaui_entry_number_new:
188  * @dh: the data handler to be used by the new widget
189  * @type: the requested data type (compatible with @dh)
190  *
191  * Creates a new data entry widget
192  *
193  * Returns: (transfer full): the new widget
194  */
195 GtkWidget *
gdaui_entry_number_new(GdaDataHandler * dh,GType type,const gchar * options)196 gdaui_entry_number_new (GdaDataHandler *dh, GType type, const gchar *options)
197 {
198 	GObject *obj;
199 	GdauiEntryNumber *mgstr;
200 
201 	g_return_val_if_fail (GDA_IS_DATA_HANDLER (dh), NULL);
202 	g_return_val_if_fail (gda_data_handler_accepts_g_type (dh, type), NULL);
203 	g_return_val_if_fail (gdaui_entry_number_is_type_numeric (type), NULL);
204 
205 	obj = g_object_new (GDAUI_TYPE_ENTRY_NUMBER, "handler", dh, NULL);
206 	mgstr = GDAUI_ENTRY_NUMBER (obj);
207 	gdaui_data_entry_set_value_type (GDAUI_DATA_ENTRY (mgstr), type);
208 
209 	g_object_set (obj, "options", options, NULL);
210 
211 	return GTK_WIDGET (obj);
212 }
213 
214 static void
gdaui_entry_number_dispose(GObject * object)215 gdaui_entry_number_dispose (GObject   * object)
216 {
217 	GdauiEntryNumber *mgstr;
218 
219 	g_return_if_fail (object != NULL);
220 	g_return_if_fail (GDAUI_IS_ENTRY_NUMBER (object));
221 
222 	mgstr = GDAUI_ENTRY_NUMBER (object);
223 	if (mgstr->priv) {
224 		if (mgstr->priv->entry)
225 			mgstr->priv->entry = NULL;
226 	}
227 
228 	/* parent class */
229 	parent_class->dispose (object);
230 }
231 
232 static void
gdaui_entry_number_finalize(GObject * object)233 gdaui_entry_number_finalize (GObject   * object)
234 {
235 	GdauiEntryNumber *mgstr;
236 
237 	g_return_if_fail (object != NULL);
238 	g_return_if_fail (GDAUI_IS_ENTRY_NUMBER (object));
239 
240 	mgstr = GDAUI_ENTRY_NUMBER (object);
241 	if (mgstr->priv) {
242 		g_free (mgstr->priv->currency);
243 		g_free (mgstr->priv);
244 		mgstr->priv = NULL;
245 	}
246 
247 	/* parent class */
248 	parent_class->finalize (object);
249 }
250 
251 static void
gdaui_entry_number_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)252 gdaui_entry_number_set_property (GObject *object,
253 				 guint param_id,
254 				 const GValue *value,
255 				 GParamSpec *pspec)
256 {
257 	GdauiEntryNumber *mgstr;
258 
259 	mgstr = GDAUI_ENTRY_NUMBER (object);
260 	if (mgstr->priv) {
261 		switch (param_id) {
262 		case PROP_OPTIONS:
263 			set_entry_options (mgstr, g_value_get_string (value));
264 			break;
265 		case PROP_EDITING_CANCELED:
266 			TO_IMPLEMENT;
267 			break;
268 		default:
269 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
270 			break;
271 		}
272 	}
273 }
274 
275 static void
gdaui_entry_number_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)276 gdaui_entry_number_get_property (GObject *object,
277 				 guint param_id,
278 				 GValue *value,
279 				 GParamSpec *pspec)
280 {
281 	GdauiEntryNumber *mgstr;
282 
283 	mgstr = GDAUI_ENTRY_NUMBER (object);
284 	if (mgstr->priv) {
285 		switch (param_id) {
286 		case PROP_EDITING_CANCELED:
287 			g_value_set_boolean (value, mgstr->priv->editing_canceled);
288 			break;
289 		default:
290 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
291 			break;
292 		}
293 	}
294 }
295 
296 static GtkWidget *
create_entry(GdauiEntryWrapper * mgwrap)297 create_entry (GdauiEntryWrapper *mgwrap)
298 {
299 	GdauiEntryNumber *mgstr;
300 
301 	g_return_val_if_fail (GDAUI_IS_ENTRY_NUMBER (mgwrap), NULL);
302 	mgstr = GDAUI_ENTRY_NUMBER (mgwrap);
303 
304 	mgstr->priv->entry = gdaui_numeric_entry_new (gdaui_data_entry_get_value_type (GDAUI_DATA_ENTRY (mgwrap)));
305 	sync_entry_options (mgstr);
306 
307 	return mgstr->priv->entry;
308 }
309 
310 static void
real_set_value(GdauiEntryWrapper * mgwrap,const GValue * value)311 real_set_value (GdauiEntryWrapper *mgwrap, const GValue *value)
312 {
313 	GdauiEntryNumber *mgstr;
314 	GdaDataHandler *dh;
315 	gchar *text;
316 
317 	g_return_if_fail (GDAUI_IS_ENTRY_NUMBER (mgwrap));
318 	mgstr = GDAUI_ENTRY_NUMBER (mgwrap);
319 
320 	dh = gdaui_data_entry_get_handler (GDAUI_DATA_ENTRY (mgwrap));
321 
322 	text = gda_data_handler_get_str_from_value (dh, value);
323 	if (value) {
324 		if (gda_value_is_null ((GValue *) value))
325 			gdaui_entry_set_text (GDAUI_ENTRY (mgstr->priv->entry), NULL);
326 		else
327 			gdaui_entry_set_text (GDAUI_ENTRY (mgstr->priv->entry), text);
328 	}
329 	else
330 		gdaui_entry_set_text (GDAUI_ENTRY (mgstr->priv->entry), NULL);
331 
332 	g_free (text);
333 }
334 
335 static GValue *
real_get_value(GdauiEntryWrapper * mgwrap)336 real_get_value (GdauiEntryWrapper *mgwrap)
337 {
338 	GValue *value;
339 	GdauiEntryNumber *mgstr;
340 
341 	g_return_val_if_fail (GDAUI_IS_ENTRY_NUMBER (mgwrap), NULL);
342 	mgstr = GDAUI_ENTRY_NUMBER (mgwrap);
343 	g_return_val_if_fail (mgstr->priv, NULL);
344 
345 	value = gdaui_numeric_entry_get_value (GDAUI_NUMERIC_ENTRY (mgstr->priv->entry));
346 
347 	if (!value) {
348 		/* in case the contents of the GtkEntry cannot be interpreted as a GValue */
349 		value = gda_value_new_null ();
350 	}
351 
352 	return value;
353 }
354 
355 static void
connect_signals(GdauiEntryWrapper * mgwrap,GCallback modify_cb,GCallback activate_cb)356 connect_signals (GdauiEntryWrapper *mgwrap, GCallback modify_cb, GCallback activate_cb)
357 {
358 	GdauiEntryNumber *mgstr;
359 
360 	g_return_if_fail (GDAUI_IS_ENTRY_NUMBER (mgwrap));
361 	mgstr = GDAUI_ENTRY_NUMBER (mgwrap);
362 	g_return_if_fail (mgstr->priv);
363 
364 	mgstr->priv->entry_change_sig = g_signal_connect (G_OBJECT (mgstr->priv->entry), "changed",
365 							  modify_cb, mgwrap);
366 	g_signal_connect (G_OBJECT (mgstr->priv->entry), "activate",
367 			  activate_cb, mgwrap);
368 }
369 
370 static void
set_editable(GdauiEntryWrapper * mgwrap,gboolean editable)371 set_editable (GdauiEntryWrapper *mgwrap, gboolean editable)
372 {
373 	GdauiEntryNumber *mgstr;
374 
375 	g_return_if_fail (GDAUI_IS_ENTRY_NUMBER (mgwrap));
376 	mgstr = GDAUI_ENTRY_NUMBER (mgwrap);
377 
378 	gtk_editable_set_editable (GTK_EDITABLE (mgstr->priv->entry), editable);
379 }
380 
381 static void
grab_focus(GdauiEntryWrapper * mgwrap)382 grab_focus (GdauiEntryWrapper *mgwrap)
383 {
384 	GdauiEntryNumber *mgstr;
385 
386 	g_return_if_fail (GDAUI_IS_ENTRY_NUMBER (mgwrap));
387 	mgstr = GDAUI_ENTRY_NUMBER (mgwrap);
388 
389 	gtk_widget_grab_focus (mgstr->priv->entry);
390 }
391 
392 /*
393  * GtkCellEditable interface
394  */
395 static void
gtk_cell_editable_entry_editing_done_cb(G_GNUC_UNUSED GtkEntry * entry,GdauiEntryNumber * mgstr)396 gtk_cell_editable_entry_editing_done_cb (G_GNUC_UNUSED GtkEntry *entry, GdauiEntryNumber *mgstr)
397 {
398 	gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (mgstr));
399 }
400 
401 static void
gtk_cell_editable_entry_remove_widget_cb(G_GNUC_UNUSED GtkEntry * entry,GdauiEntryNumber * mgstr)402 gtk_cell_editable_entry_remove_widget_cb (G_GNUC_UNUSED GtkEntry *entry, GdauiEntryNumber *mgstr)
403 {
404 	gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (mgstr));
405 }
406 
407 static void
gdaui_entry_number_start_editing(GtkCellEditable * iface,GdkEvent * event)408 gdaui_entry_number_start_editing (GtkCellEditable *iface, GdkEvent *event)
409 {
410 	GdauiEntryNumber *mgstr;
411 
412 	g_return_if_fail (GDAUI_IS_ENTRY_NUMBER (iface));
413 	mgstr = GDAUI_ENTRY_NUMBER (iface);
414 
415 	mgstr->priv->editing_canceled = FALSE;
416 	g_object_set (G_OBJECT (mgstr->priv->entry), "has-frame", FALSE, "xalign", 0., NULL);
417 
418 	gtk_cell_editable_start_editing (GTK_CELL_EDITABLE (mgstr->priv->entry), event);
419 	g_signal_connect (G_OBJECT (mgstr->priv->entry), "editing-done",
420 			  G_CALLBACK (gtk_cell_editable_entry_editing_done_cb), mgstr);
421 	g_signal_connect (G_OBJECT (mgstr->priv->entry), "remove-widget",
422 			  G_CALLBACK (gtk_cell_editable_entry_remove_widget_cb), mgstr);
423 	gdaui_entry_shell_refresh (GDAUI_ENTRY_SHELL (mgstr));
424 
425 	gtk_widget_grab_focus (mgstr->priv->entry);
426 	gtk_widget_queue_draw (GTK_WIDGET (mgstr));
427 }
428 
429 /*
430  * Options handling
431  */
432 
433 static guchar
get_default_thousands_sep()434 get_default_thousands_sep ()
435 {
436 	static guchar value = 255;
437 
438 	if (value == 255) {
439 		gchar text[20];
440 		sprintf (text, "%'f", 1234.);
441 		if (text[1] == '2')
442 			value = ' ';
443 		else
444 			value = text[1];
445 	}
446 	return value;
447 }
448 
449 static void
set_entry_options(GdauiEntryNumber * mgstr,const gchar * options)450 set_entry_options (GdauiEntryNumber *mgstr, const gchar *options)
451 {
452 	g_assert (mgstr->priv);
453 
454 	if (options && *options) {
455                 GdaQuarkList *params;
456                 const gchar *str;
457 
458                 params = gda_quark_list_new_from_string (options);
459 
460 		str = gda_quark_list_find (params, "THOUSAND_SEP");
461 		if (str) {
462 			if ((*str == 't') || (*str == 'T'))
463 				mgstr->priv->thousand_sep = get_default_thousands_sep ();
464 			else
465 				mgstr->priv->thousand_sep = 0;
466 		}
467 		str = gda_quark_list_find (params, "NB_DECIMALS");
468 		if (str) {
469 			if (*str)
470 				mgstr->priv->nb_decimals = atoi (str);
471 			else
472 				mgstr->priv->nb_decimals = 0;
473 		}
474 		str = gda_quark_list_find (params, "CURRENCY");
475 		if (str && *str) {
476 			g_free (mgstr->priv->currency);
477 			mgstr->priv->currency = g_strdup_printf ("%s ", str);
478 		}
479                 gda_quark_list_free (params);
480 		sync_entry_options (mgstr);
481         }
482 }
483 
484 /* sets the correct options for mgstr->priv->entry if it exists */
485 static void
sync_entry_options(GdauiEntryNumber * mgstr)486 sync_entry_options (GdauiEntryNumber *mgstr)
487 {
488 	if (!mgstr->priv->entry)
489 		return;
490 
491 	g_object_set (G_OBJECT (mgstr->priv->entry),
492 		      "type", gdaui_data_entry_get_value_type (GDAUI_DATA_ENTRY (mgstr)),
493 		      "n-decimals", mgstr->priv->nb_decimals,
494 		      "thousands-sep", mgstr->priv->thousand_sep,
495 		      "prefix", mgstr->priv->currency,
496 		      NULL);
497 	g_signal_emit_by_name (mgstr->priv->entry, "changed");
498 }
499