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 <gdk/gdkkeysyms.h>
23 #include "gdaui-entry-shell.h"
24 #include "gdaui-entry-none.h"
25 #include <libgda/gda-data-handler.h>
26 #include <libgda-ui/internal/utility.h>
27 #include <glib/gi18n-lib.h>
28 #include "widget-embedder.h"
29 static void gdaui_entry_shell_class_init (GdauiEntryShellClass *class);
30 static void gdaui_entry_shell_init (GdauiEntryShell *wid);
31 static void gdaui_entry_shell_dispose (GObject *object);
32 
33 static void gdaui_entry_shell_set_property (GObject *object,
34 					    guint param_id,
35 					    const GValue *value,
36 					    GParamSpec *pspec);
37 static void gdaui_entry_shell_get_property (GObject *object,
38 					    guint param_id,
39 					    GValue *value,
40 					    GParamSpec *pspec);
41 
42 
43 static gint event_cb (GtkWidget *widget, GdkEvent *event, GdauiEntryShell *shell);
44 static void show_all (GtkWidget *widget);
45 static void contents_modified_cb (GdauiEntryShell *shell, gpointer unused);
46 static void gdaui_entry_shell_refresh_status_display (GdauiEntryShell *shell);
47 
48 /* properties */
49 enum {
50 	PROP_0,
51 	PROP_HANDLER,
52 	PROP_ACTIONS,
53 	PROP_IS_CELL_RENDERER
54 };
55 
56 struct  _GdauiEntryShellPriv {
57         GtkWidget           *embedder;
58 	GtkWidget           *hbox;
59         GtkWidget           *button;
60         GdaDataHandler      *data_handler;
61 	gboolean             show_actions;
62 
63 	gboolean             value_is_null;
64 	gboolean             value_is_modified;
65 	gboolean             value_is_default;
66 	gboolean             value_is_non_valid;
67 
68 	gboolean             is_cell_renderer;
69 };
70 
71 /* get a pointer to the parents to be able to call their destructor */
72 static GObjectClass *parent_class = NULL;
73 
74 /**
75  * gdaui_entry_shell_get_type:
76  *
77  * Register the GdauiEntryShell class on the GLib type system.
78  *
79  * Returns: the GType identifying the class.
80  */
81 GType
gdaui_entry_shell_get_type(void)82 gdaui_entry_shell_get_type (void)
83 {
84 	static GType type = 0;
85 
86 	if (G_UNLIKELY (type == 0)) {
87 		static const GTypeInfo info = {
88 			sizeof (GdauiEntryShellClass),
89 			(GBaseInitFunc) NULL,
90 			(GBaseFinalizeFunc) NULL,
91 			(GClassInitFunc) gdaui_entry_shell_class_init,
92 			NULL,
93 			NULL,
94 			sizeof (GdauiEntryShell),
95 			0,
96 			(GInstanceInitFunc) gdaui_entry_shell_init,
97 			0
98 		};
99 
100 		type = g_type_register_static (GTK_TYPE_VIEWPORT, "GdauiEntryShell", &info, 0);
101 	}
102 	return type;
103 }
104 
105 
106 static void
gdaui_entry_shell_class_init(GdauiEntryShellClass * class)107 gdaui_entry_shell_class_init (GdauiEntryShellClass * class)
108 {
109 	GObjectClass *object_class = G_OBJECT_CLASS (class);
110 	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
111 
112 	parent_class = g_type_class_peek_parent (class);
113 
114 	object_class->dispose = gdaui_entry_shell_dispose;
115 	widget_class->show_all = show_all;
116 
117 	/* Properties */
118 	object_class->set_property = gdaui_entry_shell_set_property;
119 	object_class->get_property = gdaui_entry_shell_get_property;
120 	g_object_class_install_property (object_class, PROP_HANDLER,
121 					 g_param_spec_object ("handler", NULL, NULL, GDA_TYPE_DATA_HANDLER,
122 							      (G_PARAM_READABLE | G_PARAM_WRITABLE)));
123 	g_object_class_install_property (object_class, PROP_ACTIONS,
124 					 g_param_spec_boolean ("actions", NULL, NULL, TRUE,
125 							       (G_PARAM_READABLE | G_PARAM_WRITABLE)));
126 
127 	g_object_class_install_property (object_class, PROP_IS_CELL_RENDERER,
128 					 g_param_spec_boolean ("is-cell-renderer", NULL, NULL, TRUE,
129 							       (G_PARAM_READABLE | G_PARAM_WRITABLE)));
130 }
131 
132 static void
show_all(GtkWidget * widget)133 show_all (GtkWidget *widget)
134 {
135 	if (((GdauiEntryShell*) widget)->priv->show_actions)
136 		gtk_widget_show (((GdauiEntryShell*) widget)->priv->button);
137 }
138 
139 static void
gdaui_entry_shell_init(GdauiEntryShell * shell)140 gdaui_entry_shell_init (GdauiEntryShell *shell)
141 {
142 	GtkWidget *button, *hbox, *arrow;
143 	GValue *gval;
144 
145 	/* Private structure */
146 	shell->priv = g_new0 (GdauiEntryShellPriv, 1);
147 	shell->priv->embedder = NULL;
148 	shell->priv->button = NULL;
149 	shell->priv->show_actions = TRUE;
150 	shell->priv->data_handler = NULL;
151 
152 	shell->priv->value_is_null = FALSE;
153 	shell->priv->value_is_modified = FALSE;
154 	shell->priv->value_is_default = FALSE;
155 	shell->priv->value_is_non_valid = FALSE;
156 
157 	shell->priv->is_cell_renderer = FALSE;
158 
159 	/* Setting the initial layout */
160 	gtk_viewport_set_shadow_type (GTK_VIEWPORT (shell), GTK_SHADOW_NONE);
161 	gtk_container_set_border_width (GTK_CONTAINER (shell), 0);
162 
163 	/* hbox */
164 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
165 	gtk_container_add (GTK_CONTAINER (shell), hbox);
166 	gtk_widget_show (hbox);
167 	shell->priv->hbox = hbox;
168 
169 	/* vbox to insert the real widget to edit data */
170 	shell->priv->embedder = widget_embedder_new ();
171 	gtk_box_pack_start (GTK_BOX (hbox), shell->priv->embedder, TRUE, TRUE, 0);
172 	gtk_widget_show (shell->priv->embedder);
173 
174 	/* button to change the entry's state and to display that state */
175 	arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
176 	button = gtk_button_new ();
177 	gtk_container_add (GTK_CONTAINER (button), arrow);
178 	gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
179 	shell->priv->button = button;
180 	gtk_widget_show_all (button);
181 
182 	g_signal_connect (G_OBJECT (button), "event",
183 			  G_CALLBACK (event_cb), shell);
184 
185 	/* focus */
186 	gval = g_new0 (GValue, 1);
187 	g_value_init (gval, G_TYPE_BOOLEAN);
188 	g_value_set_boolean (gval, TRUE);
189 	g_object_set_property (G_OBJECT (button), "can-focus", gval);
190 	g_free (gval);
191 }
192 
193 static void
gdaui_entry_shell_dispose(GObject * object)194 gdaui_entry_shell_dispose (GObject   * object)
195 {
196 	GdauiEntryShell *shell;
197 
198 	g_return_if_fail (GDAUI_IS_ENTRY_SHELL (object));
199 
200 	shell = GDAUI_ENTRY_SHELL (object);
201 
202 	if (shell->priv) {
203 		if (shell->priv->data_handler)
204 			g_object_unref (shell->priv->data_handler);
205 
206 		g_free (shell->priv);
207 		shell->priv = NULL;
208 	}
209 
210 	/* for the parent class */
211 	parent_class->dispose (object);
212 }
213 
214 static void
gdaui_entry_shell_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)215 gdaui_entry_shell_set_property (GObject *object,
216 				guint param_id,
217 				const GValue *value,
218 				GParamSpec *pspec)
219 {
220 	gpointer ptr;
221 	GdauiEntryShell *shell;
222 
223 	shell = GDAUI_ENTRY_SHELL (object);
224 	if (shell->priv) {
225 		switch (param_id) {
226 		case PROP_HANDLER:
227 			ptr = g_value_get_object (value);
228 			if (shell->priv->data_handler) {
229 				g_object_unref (shell->priv->data_handler);
230 				shell->priv->data_handler = NULL;
231 			}
232 
233 			if (ptr) {
234 				shell->priv->data_handler = GDA_DATA_HANDLER (ptr);
235 				g_object_ref (G_OBJECT (shell->priv->data_handler));
236 			}
237 			else
238 				g_message (_("Widget of class '%s' does not have any associated GdaDataHandler, "
239 					     "(to be set using the 'handler' property) expect some mis-behaviours"),
240 					   G_OBJECT_TYPE_NAME (object));
241 			break;
242 		case PROP_ACTIONS:
243 			shell->priv->show_actions = g_value_get_boolean (value);
244 			if (shell->priv->show_actions)
245 				gtk_widget_show (shell->priv->button);
246 			else
247 				gtk_widget_hide (shell->priv->button);
248 			break;
249 		case PROP_IS_CELL_RENDERER:
250 			if (GTK_IS_CELL_EDITABLE (shell) &&
251 			    (g_value_get_boolean (value) != shell->priv->is_cell_renderer)) {
252 				shell->priv->is_cell_renderer = g_value_get_boolean (value);
253 			}
254 			break;
255 		default:
256 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
257 			break;
258 		}
259 	}
260 }
261 
262 static void
gdaui_entry_shell_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)263 gdaui_entry_shell_get_property (GObject *object,
264 				guint param_id,
265 				GValue *value,
266 				GParamSpec *pspec)
267 {
268 	GdauiEntryShell *shell;
269 
270 	shell = GDAUI_ENTRY_SHELL (object);
271 	if (shell->priv) {
272 		switch (param_id) {
273 		case PROP_HANDLER:
274 			g_value_set_object (value, shell->priv->data_handler);
275 			break;
276 		case PROP_ACTIONS:
277 			g_value_set_boolean (value, shell->priv->show_actions);
278 			break;
279 		case PROP_IS_CELL_RENDERER:
280 			g_value_set_boolean (value, shell->priv->is_cell_renderer);
281 			break;
282 		default:
283 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
284 			break;
285 		}
286 	}
287 }
288 
289 
290 /**
291  * gdaui_entry_shell_pack_entry:
292  * @shell: a #GdauiEntryShell object
293  * @main_widget: a #GtkWidget to pack into @shell
294  *
295  * Packs a #GTkWidget widget into the GdauiEntryShell.
296  */
297 void
gdaui_entry_shell_pack_entry(GdauiEntryShell * shell,GtkWidget * main_widget)298 gdaui_entry_shell_pack_entry (GdauiEntryShell *shell, GtkWidget *main_widget)
299 {
300 	g_return_if_fail (GDAUI_IS_ENTRY_SHELL (shell));
301 	g_return_if_fail (main_widget && GTK_IS_WIDGET (main_widget));
302 	gtk_container_add (GTK_CONTAINER (shell->priv->embedder), main_widget);
303 
304 	/* signals */
305 	g_signal_connect (G_OBJECT (shell), "contents-modified",
306 			  G_CALLBACK (contents_modified_cb), NULL);
307 
308 	g_signal_connect (G_OBJECT (shell), "status-changed",
309 			  G_CALLBACK (contents_modified_cb), NULL);
310 }
311 
312 static void
contents_modified_cb(GdauiEntryShell * shell,G_GNUC_UNUSED gpointer unused)313 contents_modified_cb (GdauiEntryShell *shell, G_GNUC_UNUSED gpointer unused)
314 {
315 	gdaui_entry_shell_refresh (shell);
316 }
317 
318 static void mitem_activated_cb (GtkWidget *mitem, GdauiEntryShell *shell);
319 static GdaValueAttribute gdaui_entry_shell_refresh_attributes (GdauiEntryShell *shell);
320 static gint
event_cb(G_GNUC_UNUSED GtkWidget * widget,GdkEvent * event,GdauiEntryShell * shell)321 event_cb (G_GNUC_UNUSED GtkWidget *widget, GdkEvent *event, GdauiEntryShell *shell)
322 {
323 	gboolean done = FALSE;
324 
325 	if (!shell->priv->show_actions)
326 		return done;
327 
328 	if (event->type == GDK_BUTTON_PRESS) {
329 		GdkEventButton *bevent = (GdkEventButton *) event;
330 		if ((bevent->button == 1) || (bevent->button == 3)) {
331 			GtkWidget *menu;
332 			guint attributes;
333 
334 			attributes = gdaui_entry_shell_refresh_attributes (shell);
335 			menu = _gdaui_utility_entry_build_actions_menu (G_OBJECT (shell), attributes,
336 									G_CALLBACK (mitem_activated_cb));
337 			gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
338 					bevent->button, bevent->time);
339 			done = TRUE;
340 		}
341 	}
342 
343 	if (event->type == GDK_KEY_PRESS) {
344 		GtkWidget *menu;
345 		GdkEventKey *kevent = (GdkEventKey *) event;
346 
347 		if (kevent->keyval == GDK_KEY_space) {
348 			guint attributes;
349 
350 			attributes = gdaui_entry_shell_refresh_attributes (shell);
351 			menu = _gdaui_utility_entry_build_actions_menu (G_OBJECT (shell), attributes,
352 									G_CALLBACK (mitem_activated_cb));
353 
354 			gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
355 					0, kevent->time);
356 			done = TRUE;
357 		}
358 		else {
359 			if (kevent->keyval == GDK_KEY_Tab)
360 				done = FALSE;
361 			else
362 				done = TRUE;
363 		}
364 	}
365 
366 	return done;
367 }
368 
369 static void
mitem_activated_cb(GtkWidget * mitem,GdauiEntryShell * shell)370 mitem_activated_cb (GtkWidget *mitem, GdauiEntryShell *shell)
371 {
372 	guint action;
373 
374 	action = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (mitem), "action"));
375 	gdaui_data_entry_set_attributes (GDAUI_DATA_ENTRY (shell), action, action);
376 }
377 
378 static void
gdaui_entry_shell_refresh_status_display(GdauiEntryShell * shell)379 gdaui_entry_shell_refresh_status_display (GdauiEntryShell *shell)
380 {
381 	static GdkRGBA **colors = NULL;
382 	GdkRGBA *normal = NULL, *prelight = NULL;
383 
384 	g_return_if_fail (GDAUI_IS_ENTRY_SHELL (shell));
385 
386 	if (!colors)
387 		colors = _gdaui_utility_entry_build_info_colors_array_a ();
388 
389 	gtk_widget_set_tooltip_text (shell->priv->button, NULL);
390 
391 	if (shell->priv->value_is_null) {
392 		normal = colors[0];
393 		prelight = colors[1];
394 		gtk_widget_set_tooltip_text (shell->priv->button, _("Value is NULL"));
395 	}
396 
397 	if (shell->priv->value_is_default) {
398 		normal = colors[2];
399 		prelight = colors[3];
400 		gtk_widget_set_tooltip_text (shell->priv->button, _("Value will be determined by default"));
401 	}
402 
403 	if (shell->priv->value_is_non_valid) {
404 		normal = colors[4];
405 		prelight = colors[5];
406 		gtk_widget_set_tooltip_text (shell->priv->button, _("Value is invalid"));
407 	}
408 
409 	gtk_widget_override_background_color (shell->priv->button, GTK_STATE_FLAG_NORMAL, normal);
410 	gtk_widget_override_background_color (shell->priv->button, GTK_STATE_FLAG_ACTIVE, normal);
411 	gtk_widget_override_background_color (shell->priv->button, GTK_STATE_FLAG_PRELIGHT, prelight);
412 }
413 
414 static GdaValueAttribute
gdaui_entry_shell_refresh_attributes(GdauiEntryShell * shell)415 gdaui_entry_shell_refresh_attributes (GdauiEntryShell *shell)
416 {
417 	GdaValueAttribute attrs;
418 
419 	attrs = gdaui_data_entry_get_attributes (GDAUI_DATA_ENTRY (shell));
420 	shell->priv->value_is_null = attrs & GDA_VALUE_ATTR_IS_NULL;
421 	shell->priv->value_is_modified = ! (attrs & GDA_VALUE_ATTR_IS_UNCHANGED);
422 	shell->priv->value_is_default = attrs & GDA_VALUE_ATTR_IS_DEFAULT;
423 	shell->priv->value_is_non_valid = attrs & GDA_VALUE_ATTR_DATA_NON_VALID;
424 
425 	return attrs;
426 }
427 
428 /**
429  * gdaui_entry_shell_refresh:
430  * @shell: the GdauiEntryShell widget to refresh
431  *
432  * Forces the shell to refresh its display (mainly the color of the
433  * button).
434  */
435 void
gdaui_entry_shell_refresh(GdauiEntryShell * shell)436 gdaui_entry_shell_refresh (GdauiEntryShell *shell)
437 {
438 	g_return_if_fail (GDAUI_IS_ENTRY_SHELL (shell));
439 	gdaui_entry_shell_refresh_attributes (shell);
440 	gdaui_entry_shell_refresh_status_display (shell);
441 }
442 
443 /**
444  * gdaui_entry_shell_set_unknown:
445  * @shell: the #GdauiEntryShell widget to refresh
446  * @unknown: set to %TRUE if @shell's contents is unavailable and should not be modified
447  *
448  * Defines if @shell's contents is in an undefined state (shows or hides @shell's contents)
449  */
450 void
gdaui_entry_shell_set_unknown(GdauiEntryShell * shell,gboolean unknown)451 gdaui_entry_shell_set_unknown (GdauiEntryShell *shell, gboolean unknown)
452 {
453 	g_return_if_fail (GDAUI_IS_ENTRY_SHELL (shell));
454 
455 	widget_embedder_set_valid ((WidgetEmbedder*) shell->priv->embedder, !unknown);
456 }
457 
458 /**
459  * gdaui_entry_shell_set_ucolor:
460  * @shell: a #GdauiEntryShell
461  * @red: the red component of a color
462  * @green: the green component of a color
463  * @blue: the blue component of a color
464  * @alpha: the alpha component of a color
465  *
466  * Defines the color to be used when @de displays an invalid value. Any value not
467  * between 0. and 1. will result in the default hard coded values to be used (grayish).
468  *
469  * Since: 5.0.3
470  */
471 void
gdaui_entry_shell_set_ucolor(GdauiEntryShell * shell,gdouble red,gdouble green,gdouble blue,gdouble alpha)472 gdaui_entry_shell_set_ucolor (GdauiEntryShell *shell, gdouble red, gdouble green,
473 			      gdouble blue, gdouble alpha)
474 {
475 	g_return_if_fail (GDAUI_IS_ENTRY_SHELL (shell));
476 	widget_embedder_set_ucolor ((WidgetEmbedder*) shell->priv->embedder, red, green, blue, alpha);
477 }
478