1 /*
2  * Gnome Chemisty Utils
3  * gcuperiodic.c
4  *
5  * Copyright (C) 2002-2012 Jean Bréfort <jean.brefort@normalesup.org>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
20  * USA
21  */
22 
23 #include "config.h"
24 #include "gcuperiodic.h"
25 #include <gcu/chemistry.h>
26 #include <goffice/goffice.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <gsf/gsf-impl-utils.h>
31 #include <glib/gi18n-lib.h>
32 
33 struct _GcuPeriodic
34 {
35 	GtkBin bin;
36 
37 	GtkGrid *grid;
38 	GtkToggleButton* buttons[119];
39 	GtkLabel* labels[119];
40 	double red[119], blue[119], green[119];
41 	GtkNotebook *book;
42 	guint Z;
43 	gboolean can_unselect;
44 	unsigned colorstyle;
45 	GArray *colorschemes;
46 	unsigned nbschemes;
47 	unsigned tips;
48 };
49 
50 struct _GcuPeriodicClass
51 {
52 	GtkBinClass parent_class;
53 
54 	void (* element_changed_event)(GcuPeriodic *periodic);
55 };
56 
57 GType
gcu_periodic_color_style_get_type(void)58 gcu_periodic_color_style_get_type (void)
59 {
60   static GType etype = 0;
61   if (etype == 0) {
62     static const GEnumValue values[] = {
63       { GCU_PERIODIC_COLOR_NONE, "GCU_PERIODIC_COLOR_NONE", "none" },
64       { GCU_PERIODIC_COLOR_DEFAULT, "GCU_PERIODIC_COLOR_DEFAULT", "default" },
65       { 0, NULL, NULL }
66     };
67     etype = g_enum_register_static ("GcuPeriodicColorStyle", values);
68   }
69   return etype;
70 }
71 #define GCU_TYPE_PERIODIC_COLOR_STYLE_TYPE (gcu_periodic_color_style_get_type())
72 
73 static GtkBinClass *parent_class = NULL;
74 
75 struct ColorScheme {
76 	GcuPeriodicColorFunc f;
77 	int page;
78 	gpointer data;
79 };
80 
81 enum {
82   ELEMENT_CHANGED,
83   LAST_SIGNAL
84 };
85 
86 enum {
87 	PROP_0,
88 	PROP_CAN_UNSELECT,
89 	PROP_COLOR_STYLE
90 };
91 
92 static guint gcu_periodic_signals[LAST_SIGNAL] = { 0 };
93 
on_clicked(GtkToggleButton * button,GcuPeriodic * periodic)94 void on_clicked (GtkToggleButton *button, GcuPeriodic* periodic)
95 {
96 	static gboolean change = FALSE;
97 	if (button != periodic->buttons[0]) {
98 		const gchar* name;
99 		change = TRUE;
100 		if (periodic->buttons[0])
101 			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (periodic->buttons[0]), FALSE);
102 		periodic->buttons[0] = button;
103 		name = gtk_buildable_get_name (GTK_BUILDABLE (periodic->buttons[0]));
104 		periodic->Z = atoi (name + 3);
105 		g_signal_emit (periodic, gcu_periodic_signals[ELEMENT_CHANGED], 0, periodic->Z);
106 		change = FALSE;
107 	} else if (!change) {
108 		if (periodic->can_unselect) {
109 			periodic->buttons[0] = NULL;
110 			periodic->Z = 0;
111 			g_signal_emit (periodic, gcu_periodic_signals[ELEMENT_CHANGED], 0, 0);
112 		} else if (periodic->buttons[0])
113 			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (periodic->buttons[0]), TRUE);
114 	}
115 }
116 
117 static void
gcu_periodic_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)118 gcu_periodic_set_property (GObject              *object,
119 				guint                 param_id,
120 				const GValue         *value,
121 				GParamSpec           *pspec)
122 {
123 	GcuPeriodic *periodic;
124 	g_return_if_fail (object != NULL);
125 	g_return_if_fail (GCU_IS_PERIODIC (object));
126 
127 	periodic = GCU_PERIODIC (object);
128 
129 	switch (param_id) {
130 	case PROP_CAN_UNSELECT:
131 		periodic->can_unselect = g_value_get_boolean (value);
132 		break;
133 
134 	case PROP_COLOR_STYLE: {
135 		unsigned style = g_value_get_uint (value);
136 		if (style < GCU_PERIODIC_COLOR_MAX + periodic->nbschemes) {
137 			periodic->colorstyle = style;
138 			int page = (style >= GCU_PERIODIC_COLOR_MAX)?
139 				g_array_index (periodic->colorschemes, struct ColorScheme, style - GCU_PERIODIC_COLOR_MAX).page: 0;
140 			gtk_notebook_set_current_page (periodic->book, page);
141 			gcu_periodic_set_colors (periodic);
142 		} else
143 			g_warning (_("Out of range value %d for property \"color-style\" for GcuPeriodic instance %p\n"), style, periodic);
144 		break;
145 	}
146 
147 	default:
148 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
149 		break;
150 	}
151 }
152 
153 static void
gcu_periodic_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)154 gcu_periodic_get_property (GObject              *object,
155 				guint                 param_id,
156 				GValue               *value,
157 				GParamSpec           *pspec)
158 {
159 	GcuPeriodic *periodic;
160 
161 	g_return_if_fail (object != NULL);
162 	g_return_if_fail (GCU_IS_PERIODIC (object));
163 
164 	periodic = GCU_PERIODIC (object);
165 
166 	switch (param_id) {
167 	case PROP_CAN_UNSELECT:
168 		g_value_set_boolean (value, periodic->can_unselect);
169 		break;
170 
171 	case PROP_COLOR_STYLE:
172 		g_value_set_uint (value, periodic->colorstyle);
173 		break;
174 
175 	default:
176 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
177 		break;
178 	}
179 }
180 
gcu_periodic_get_preferred_height(GtkWidget * w,gint * minimum_height,gint * natural_height)181 static void gcu_periodic_get_preferred_height (GtkWidget *w, gint *minimum_height, gint *natural_height)
182 {
183 	GtkWidget *child = gtk_bin_get_child (GTK_BIN (w));
184 	gboolean visible = FALSE;
185 	if (child)
186 		g_object_get (G_OBJECT (child), "visible", &visible, NULL);
187 	if (visible)
188 		gtk_widget_get_preferred_height (child, minimum_height, natural_height);
189 	else
190 		*minimum_height = *natural_height = 0;
191 }
192 
gcu_periodic_get_preferred_width(GtkWidget * w,gint * minimum_width,gint * natural_width)193 static void gcu_periodic_get_preferred_width (GtkWidget *w, gint *minimum_width, gint *natural_width)
194 {
195 	GtkWidget *child = gtk_bin_get_child (GTK_BIN (w));
196 	gboolean visible = FALSE;
197 	if (child)
198 		g_object_get (G_OBJECT (child), "visible", &visible, NULL);
199 	if (visible)
200 		gtk_widget_get_preferred_width (child, minimum_width, natural_width);
201 	else
202 		*minimum_width = *natural_width = 0;
203 }
204 
gcu_periodic_size_allocate(GtkWidget * w,GtkAllocation * alloc)205 static void gcu_periodic_size_allocate (GtkWidget *w, GtkAllocation *alloc)
206 {
207 	GtkWidget *child = gtk_bin_get_child (GTK_BIN (w));
208 	gboolean visible = FALSE;
209 	if (child)
210 		g_object_get (G_OBJECT (child), "visible", &visible, NULL);
211 	if (visible)
212 		gtk_widget_size_allocate (child, alloc);
213 	(GTK_WIDGET_CLASS (parent_class))->size_allocate (w, alloc);
214 }
215 
gcu_periodic_finalize(GObject * object)216 static void gcu_periodic_finalize (GObject *object)
217 {
218 	GcuPeriodic *periodic = (GcuPeriodic*) object;
219 
220 	g_array_free (periodic->colorschemes, FALSE);
221 
222 	if (G_OBJECT_CLASS (parent_class)->finalize)
223 		(* G_OBJECT_CLASS (parent_class)->finalize) (object);
224 }
225 
gcu_periodic_class_init(GcuPeriodicClass * klass)226 static void gcu_periodic_class_init (GcuPeriodicClass *klass)
227 {
228 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
229 	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
230 	parent_class = (GtkBinClass*) g_type_class_peek_parent (klass);
231 
232 	gobject_class->set_property = gcu_periodic_set_property;
233 	gobject_class->get_property = gcu_periodic_get_property;
234 	klass->element_changed_event = NULL;
235 	gcu_periodic_signals[ELEMENT_CHANGED] =
236 	g_signal_new ("element_changed",
237 				  G_TYPE_FROM_CLASS(gobject_class),
238 				  G_SIGNAL_RUN_LAST,
239 				  G_STRUCT_OFFSET(GcuPeriodicClass, element_changed_event),
240 				  NULL, NULL,
241 				  g_cclosure_marshal_VOID__UINT,
242 				  G_TYPE_NONE, 1,
243 				  G_TYPE_UINT
244 				  );
245 	g_object_class_install_property
246 			(gobject_class,
247 			 PROP_CAN_UNSELECT,
248 			 g_param_spec_boolean ("can_unselect", NULL, NULL,
249 				   FALSE,
250 				   (G_PARAM_READABLE | G_PARAM_WRITABLE)));
251 	g_object_class_install_property
252 			(gobject_class,
253 			 PROP_COLOR_STYLE,
254 			 g_param_spec_uint ("color-style", NULL, NULL,
255 								GCU_PERIODIC_COLOR_NONE, G_MAXUINT,
256 								GCU_PERIODIC_COLOR_NONE,
257 								(G_PARAM_READABLE | G_PARAM_WRITABLE)));
258 	gobject_class->finalize = gcu_periodic_finalize;
259 	widget_class->get_preferred_height = gcu_periodic_get_preferred_height;
260 	widget_class->get_preferred_width = gcu_periodic_get_preferred_width;
261 	widget_class->size_allocate = gcu_periodic_size_allocate;
262 }
263 
on_draw(GtkWidget * w,cairo_t * cr,GcuPeriodic * periodic)264 static void on_draw (GtkWidget *w, cairo_t *cr, GcuPeriodic *periodic)
265 {
266 	if (periodic->colorstyle != GCU_PERIODIC_COLOR_NONE) {
267 		GtkAllocation alloc;
268 		unsigned i = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (w), "elt"));
269 		gtk_widget_get_allocation (w, &alloc);
270 		cairo_rectangle (cr, 0, 0, alloc.width, alloc.height);
271 		cairo_set_source_rgb (cr, periodic->red[i], periodic->green[i], periodic->blue[i]);
272 		cairo_fill (cr);
273 	}
274 	GTK_WIDGET_CLASS (G_OBJECT_GET_CLASS (w))->draw (w, cr);
275 }
276 
gcu_periodic_init(GcuPeriodic * periodic)277 static void gcu_periodic_init (GcuPeriodic *periodic)
278 {
279 	GtkBuilder* xml;
280 	char name[8] = "elt";
281 	GtkToggleButton* button;
282 	int i;
283 	xml = go_gtk_builder_load (UIDIR"/gcuperiodic.ui", GETTEXT_PACKAGE, NULL);
284 	g_return_if_fail (xml);
285 	periodic->grid = GTK_GRID (gtk_builder_get_object (xml, "periodic-grid"));
286 	periodic->book = GTK_NOTEBOOK (gtk_builder_get_object (xml, "book"));
287 	periodic->colorstyle = GCU_PERIODIC_COLOR_NONE;
288 	memset(periodic->buttons, 0, sizeof (GtkToggleButton*) * 119);
289 	for (i = 1; i <= 118; i++) {
290 		sprintf(name + 3, "%d", i);
291 		button = (GtkToggleButton*) gtk_builder_get_object (xml, name);
292 		if (GTK_IS_TOGGLE_BUTTON (button)) {
293 			gtk_widget_set_tooltip_text (GTK_WIDGET(button), gcu_element_get_name(i));
294 			periodic->buttons[i] = button;
295 			periodic->labels[i] = GTK_LABEL (gtk_bin_get_child (GTK_BIN (button)));
296 			g_object_set_data (G_OBJECT (periodic->labels[i]), "elt", GUINT_TO_POINTER (i));
297 			g_signal_connect (G_OBJECT (button), "toggled", G_CALLBACK (on_clicked), periodic);
298 			g_signal_connect (G_OBJECT (periodic->labels[i]), "draw", G_CALLBACK (on_draw), periodic);
299 		}
300 	}
301 	periodic->Z = 0;
302 	gtk_container_add (GTK_CONTAINER (periodic), GTK_WIDGET (periodic->grid));
303 	gtk_widget_show_all (GTK_WIDGET (periodic));
304 	periodic->colorschemes = g_array_new (FALSE, FALSE, sizeof (struct ColorScheme));
305 	g_object_unref (xml);
306 }
307 
GSF_CLASS(GcuPeriodic,gcu_periodic,gcu_periodic_class_init,gcu_periodic_init,GTK_TYPE_BIN)308 GSF_CLASS (GcuPeriodic, gcu_periodic,
309            gcu_periodic_class_init, gcu_periodic_init,
310            GTK_TYPE_BIN)
311 
312 GtkWidget* gcu_periodic_new ()
313 {
314 	return GTK_WIDGET (g_object_new (GCU_TYPE_PERIODIC, NULL));
315 }
316 
gcu_periodic_get_element(GcuPeriodic * periodic)317 guint gcu_periodic_get_element(GcuPeriodic* periodic)
318 {
319 	g_return_val_if_fail(GCU_IS_PERIODIC(periodic), 0);
320 	return periodic->Z;
321 }
322 
gcu_periodic_set_element(GcuPeriodic * periodic,guint element)323 void gcu_periodic_set_element (GcuPeriodic* periodic, guint element)
324 {
325 	g_return_if_fail(GCU_IS_PERIODIC(periodic));
326 	if (periodic->can_unselect && periodic->buttons[0]) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(periodic->buttons[0]), FALSE);
327 	if (element)
328 	{
329 		gtk_toggle_button_set_active(periodic->buttons[element], TRUE);
330 		periodic->buttons[0] = periodic->buttons[element];
331 		periodic->Z = element;
332 	}
333 	else if (periodic->can_unselect)
334 	{
335 		periodic->buttons[0] = NULL;
336 		periodic->Z = 0;
337 	}
338 }
339 
gcu_periodic_set_colors(GcuPeriodic * periodic)340 void gcu_periodic_set_colors(GcuPeriodic *periodic)
341 {
342 	const double *colors;
343 	PangoAttribute *attr;
344 	PangoAttrList *l;
345 	int i;
346 	GdkRGBA rgba;
347 	rgba.alpha = 1.;
348 	GcuPeriodicColorFunc func = NULL;
349 	gpointer data = NULL;
350 	if (periodic->colorstyle >= GCU_PERIODIC_COLOR_MAX) {
351 		func = g_array_index (periodic->colorschemes, struct ColorScheme, periodic->colorstyle - GCU_PERIODIC_COLOR_MAX).f;
352 		data = g_array_index (periodic->colorschemes, struct ColorScheme, periodic->colorstyle - GCU_PERIODIC_COLOR_MAX).data;
353 	}
354 	for (i = 1; i <= 118; i++)
355 	{
356 		if (!periodic->labels[i])
357 			continue;
358 		switch (periodic->colorstyle)
359 		{
360 		case GCU_PERIODIC_COLOR_NONE:
361 			l = pango_attr_list_new ();
362 			gtk_label_set_attributes (periodic->labels[i], l);
363 		break;
364 		case GCU_PERIODIC_COLOR_DEFAULT:
365 			colors = gcu_element_get_default_color(i);
366 			periodic->red[i] = colors[0];
367 			periodic->green[i] = colors[1];
368 			periodic->blue[i] = colors[2];
369 			if (colors[0] > 0.6 ||  colors[1] > 0.6 || colors[2] > 0.6)
370 				attr = pango_attr_foreground_new (0, 0, 0);
371 			else
372 				attr = pango_attr_foreground_new (65535, 65535, 65535);
373 			attr->start_index = 0;
374 			attr->end_index = 100;
375 			l = pango_attr_list_new ();
376 			pango_attr_list_insert (l, attr);
377 			gtk_label_set_attributes (periodic->labels[i], l);
378 			break;
379 		default: {
380 			func (i, &rgba, data);
381 			periodic->red[i] = rgba.red;
382 			periodic->green[i] = rgba.green;
383 			periodic->blue[i] = rgba.blue;
384 			if (rgba.red > 0.6 ||  rgba.green > 0.6 || rgba.blue > 0.6)
385 				attr = pango_attr_foreground_new (0, 0, 0);
386 			else
387 				attr = pango_attr_foreground_new (65535, 65535, 65535);
388 			attr->start_index = 0;
389 			attr->end_index = 100;
390 			l = pango_attr_list_new ();
391 			pango_attr_list_insert (l, attr);
392 			gtk_label_set_attributes (periodic->labels[i], l);
393 			break;
394 		}
395 		}
396 	}
397 }
398 
gcu_periodic_add_color_scheme(GcuPeriodic * periodic,GcuPeriodicColorFunc func,GtkWidget * extra_widget,gpointer user_data)399 int	gcu_periodic_add_color_scheme (GcuPeriodic *periodic,
400 		GcuPeriodicColorFunc func, GtkWidget *extra_widget, gpointer user_data)
401 {
402 	struct ColorScheme s;
403 	s.f = func;
404 	if (extra_widget)
405 		s.page = gtk_notebook_append_page (periodic->book, extra_widget, NULL);
406 	else
407 		s.page = 0;
408 	s.data = user_data;
409 	g_array_append_val (periodic->colorschemes, s);
410 	return GCU_PERIODIC_COLOR_MAX + periodic->nbschemes++;
411 }
412 
gcu_periodic_set_tips(GcuPeriodic * periodic,unsigned scheme)413 void gcu_periodic_set_tips (GcuPeriodic *periodic, unsigned scheme)
414 {
415 	if (scheme != periodic->tips) {
416 		int i;
417 		periodic->tips = scheme;
418 		switch (scheme) {
419 		default:
420 		case GCU_PERIODIC_TIP_NAME:
421 			for (i = 1; i <= 118; i++) {
422 				if (periodic->buttons[i])
423 					gtk_widget_set_tooltip_text (GTK_WIDGET (periodic->buttons[i]), gcu_element_get_name (i));
424 			}
425 			break;
426 		case GCU_PERIODIC_TIP_STANDARD:
427 			for (i = 1; i <= 118; i++) {
428 				GtkWidget *win, *grid, *w;
429 				char *markup, *str;
430 				char const *conf;
431 				if (!periodic->buttons[i])
432 					continue;
433 				win = gtk_window_new (GTK_WINDOW_POPUP);
434 				gtk_widget_set_name (win, "gtk-tooltip");
435 				grid = gtk_grid_new ();
436 				gtk_container_add (GTK_CONTAINER (win), grid);
437 				w = GTK_WIDGET (g_object_new (GTK_TYPE_LABEL, "xalign", 0., NULL));
438 				markup = g_strdup_printf ("%u", i);
439 				gtk_label_set_text (GTK_LABEL (w), markup);
440 				g_free (markup);
441 				gtk_grid_attach (GTK_GRID (grid), w, 0, 0, 1, 1);
442 				str = gcu_element_get_weight_as_string (i);
443 				conf = gcu_element_get_electronic_configuration (i);
444 				w = GTK_WIDGET (g_object_new (GTK_TYPE_LABEL, "justify", GTK_JUSTIFY_CENTER, NULL));
445 				markup = g_strdup_printf ("<span face=\"Sans\" size=\"xx-large\">%s</span>\n%s\n%s\n%s",
446 				                          gcu_element_get_symbol (i), gcu_element_get_name (i), (conf)? conf: "", (str)? str: "");
447 				g_free (str);
448 				gtk_label_set_markup (GTK_LABEL (w), markup);
449 				g_free (markup);
450 				gtk_grid_attach (GTK_GRID (grid), w, 0, 1, 1, 1);
451 				gtk_widget_show_all (grid);
452 				gtk_widget_set_tooltip_window (GTK_WIDGET (periodic->buttons[i]), GTK_WINDOW (win));
453 			}
454 			break;
455 		}
456 	}
457 }
458