1 /*
2  * e-charset-combo-box.c
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
16  *
17  *
18  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19  *
20  */
21 
22 #include "e-charset-combo-box.h"
23 
24 #include <glib/gi18n.h>
25 
26 #include "e-charset.h"
27 #include "gtk-compat.h"
28 
29 #define E_CHARSET_COMBO_BOX_GET_PRIVATE(obj) \
30 	(G_TYPE_INSTANCE_GET_PRIVATE \
31 	((obj), E_TYPE_CHARSET_COMBO_BOX, ECharsetComboBoxPrivate))
32 
33 #define DEFAULT_CHARSET "UTF-8"
34 #define OTHER_VALUE G_MAXINT
35 
36 struct _ECharsetComboBoxPrivate {
37 	GtkActionGroup *action_group;
38 	GtkRadioAction *other_action;
39 	GHashTable *charset_index;
40 
41 	/* Used when the user clicks Cancel in the character set
42 	 * dialog. Reverts to the previous combo box setting. */
43 	gint previous_index;
44 
45 	/* When setting the character set programmatically, this
46 	 * prevents the custom character set dialog from running. */
47 	guint block_dialog : 1;
48 };
49 
50 enum {
51 	PROP_0,
52 	PROP_CHARSET
53 };
54 
55 static gpointer parent_class;
56 
57 static void
charset_combo_box_entry_changed_cb(GtkEntry * entry,GtkDialog * dialog)58 charset_combo_box_entry_changed_cb (GtkEntry *entry,
59                                     GtkDialog *dialog)
60 {
61 	const gchar *text;
62 	gboolean sensitive;
63 
64 	text = gtk_entry_get_text (entry);
65 	sensitive = (text != NULL && *text != '\0');
66 	gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_OK, sensitive);
67 }
68 
69 static void
charset_combo_box_run_dialog(ECharsetComboBox * combo_box)70 charset_combo_box_run_dialog (ECharsetComboBox *combo_box)
71 {
72 	GtkDialog *dialog;
73 	GtkEntry *entry;
74 	GtkWidget *container;
75 	GtkWidget *widget;
76 	GObject *object;
77 	gpointer parent;
78 	const gchar *charset;
79 
80 	/* FIXME Using a dialog for this is lame.  Selecting "Other..."
81 	 *       should unlock an entry directly in the Preferences tab.
82 	 *       Unfortunately space in Preferences is at a premium right
83 	 *       now, but we should revisit this when the space issue is
84 	 *       finally resolved. */
85 
86 	parent = gtk_widget_get_toplevel (GTK_WIDGET (combo_box));
87 	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
88 
89 	object = G_OBJECT (combo_box->priv->other_action);
90 	charset = g_object_get_data (object, "charset");
91 
92 	widget = gtk_dialog_new_with_buttons (
93 		_("Character Encoding"), parent,
94 		GTK_DIALOG_DESTROY_WITH_PARENT,
95 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
96 		GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
97 
98 	/* Load the broken border width defaults so we can override them. */
99 	gtk_widget_ensure_style (widget);
100 
101 	dialog = GTK_DIALOG (widget);
102 
103 	//gtk_dialog_set_has_separator (dialog, FALSE);
104 	gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK);
105 
106 	gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
107 
108 	widget = gtk_dialog_get_action_area (dialog);
109 	gtk_container_set_border_width (GTK_CONTAINER (widget), 0);
110 
111 	widget = gtk_dialog_get_content_area (dialog);
112 	gtk_box_set_spacing (GTK_BOX (widget), 12);
113 	gtk_container_set_border_width (GTK_CONTAINER (widget), 0);
114 
115 	container = widget;
116 
117 	widget = gtk_label_new (_("Enter the character set to use"));
118 	gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
119 	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
120 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
121 	gtk_widget_show (widget);
122 
123 	widget = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
124 	gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 12, 0);
125 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
126 	gtk_widget_show (widget);
127 
128 	container = widget;
129 
130 	widget = gtk_entry_new ();
131 	entry = GTK_ENTRY (widget);
132 	gtk_entry_set_activates_default (entry, TRUE);
133 	gtk_container_add (GTK_CONTAINER (container), widget);
134 	gtk_widget_show (widget);
135 
136 	g_signal_connect (
137 		entry, "changed",
138 		G_CALLBACK (charset_combo_box_entry_changed_cb), dialog);
139 
140 	/* Set the default text -after- connecting the signal handler.
141 	 * This will initialize the "OK" button to the proper state. */
142 	gtk_entry_set_text (entry, charset);
143 
144 	if (gtk_dialog_run (dialog) != GTK_RESPONSE_OK) {
145 		gint active;
146 
147 		/* Revert to the previously selected character set. */
148 		combo_box->priv->block_dialog = TRUE;
149 		active = combo_box->priv->previous_index;
150 		gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), active);
151 		combo_box->priv->block_dialog = FALSE;
152 
153 		goto exit;
154 	}
155 
156 	charset = gtk_entry_get_text (entry);
157 	g_return_if_fail (charset != NULL && charset[0] != '\0');
158 
159 	g_object_set_data_full (
160 		object, "charset", g_strdup (charset),
161 		(GDestroyNotify) g_free);
162 
163 exit:
164 	gtk_widget_destroy (GTK_WIDGET (dialog));
165 }
166 
167 static void
charset_combo_box_notify_charset_cb(ECharsetComboBox * combo_box)168 charset_combo_box_notify_charset_cb (ECharsetComboBox *combo_box)
169 {
170 	GtkToggleAction *action;
171 
172 	action = GTK_TOGGLE_ACTION (combo_box->priv->other_action);
173 	if (!gtk_toggle_action_get_active (action))
174 		return;
175 
176 	if (combo_box->priv->block_dialog)
177 		return;
178 
179 	/* "Other" action was selected by user. */
180 	charset_combo_box_run_dialog (combo_box);
181 }
182 
183 static void
charset_combo_box_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)184 charset_combo_box_set_property (GObject *object,
185                                 guint property_id,
186                                 const GValue *value,
187                                 GParamSpec *pspec)
188 {
189 	switch (property_id) {
190 		case PROP_CHARSET:
191 			e_charset_combo_box_set_charset (
192 				E_CHARSET_COMBO_BOX (object),
193 				g_value_get_string (value));
194 			return;
195 	}
196 
197 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
198 }
199 
200 static void
charset_combo_box_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)201 charset_combo_box_get_property (GObject *object,
202                                 guint property_id,
203                                 GValue *value,
204                                 GParamSpec *pspec)
205 {
206 	switch (property_id) {
207 		case PROP_CHARSET:
208 			g_value_set_string (
209 				value, e_charset_combo_box_get_charset (
210 				E_CHARSET_COMBO_BOX (object)));
211 			return;
212 	}
213 
214 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
215 }
216 
217 static void
charset_combo_box_dispose(GObject * object)218 charset_combo_box_dispose (GObject *object)
219 {
220 	ECharsetComboBoxPrivate *priv;
221 
222 	priv = E_CHARSET_COMBO_BOX_GET_PRIVATE (object);
223 
224 	if (priv->action_group != NULL) {
225 		g_object_unref (priv->action_group);
226 		priv->action_group = NULL;
227 	}
228 
229 	if (priv->other_action != NULL) {
230 		g_object_unref (priv->other_action);
231 		priv->other_action = NULL;
232 	}
233 
234 	g_hash_table_remove_all (priv->charset_index);
235 
236 	/* Chain up to parent's dispose() method. */
237 	G_OBJECT_CLASS (parent_class)->dispose (object);
238 }
239 
240 static void
charset_combo_box_finalize(GObject * object)241 charset_combo_box_finalize (GObject *object)
242 {
243 	ECharsetComboBoxPrivate *priv;
244 
245 	priv = E_CHARSET_COMBO_BOX_GET_PRIVATE (object);
246 
247 	g_hash_table_destroy (priv->charset_index);
248 
249 	/* Chain up to parent's finalize() method. */
250 	G_OBJECT_CLASS (parent_class)->finalize (object);
251 }
252 
253 static void
charset_combo_box_changed(GtkComboBox * combo_box)254 charset_combo_box_changed (GtkComboBox *combo_box)
255 {
256 	ECharsetComboBoxPrivate *priv;
257 
258 	priv = E_CHARSET_COMBO_BOX_GET_PRIVATE (combo_box);
259 
260 	/* Chain up to parent's changed() method. */
261 	GTK_COMBO_BOX_CLASS (parent_class)->changed (combo_box);
262 
263 	/* Notify -before- updating previous index. */
264 	g_object_notify (G_OBJECT (combo_box), "charset");
265 	priv->previous_index = gtk_combo_box_get_active (combo_box);
266 }
267 
268 static void
charset_combo_box_class_init(ECharsetComboBoxClass * class)269 charset_combo_box_class_init (ECharsetComboBoxClass *class)
270 {
271 	GObjectClass *object_class;
272 	GtkComboBoxClass *combo_box_class;
273 
274 	parent_class = g_type_class_peek_parent (class);
275 	g_type_class_add_private (class, sizeof (ECharsetComboBoxPrivate));
276 
277 	object_class = G_OBJECT_CLASS (class);
278 	object_class->set_property = charset_combo_box_set_property;
279 	object_class->get_property = charset_combo_box_get_property;
280 	object_class->dispose = charset_combo_box_dispose;
281 	object_class->finalize = charset_combo_box_finalize;
282 
283 	combo_box_class = GTK_COMBO_BOX_CLASS (class);
284 	combo_box_class->changed = charset_combo_box_changed;
285 
286 	g_object_class_install_property (
287 		object_class,
288 		PROP_CHARSET,
289 		g_param_spec_string (
290 			"charset",
291 			"Charset",
292 			"The selected character set",
293 			"UTF-8",
294 			G_PARAM_READWRITE |
295 			G_PARAM_CONSTRUCT));
296 }
297 
298 static void
charset_combo_box_init(ECharsetComboBox * combo_box)299 charset_combo_box_init (ECharsetComboBox *combo_box)
300 {
301 	GtkActionGroup *action_group;
302 	GtkRadioAction *radio_action;
303 	GHashTable *charset_index;
304 	GSList *group, *iter;
305 
306 	action_group = gtk_action_group_new ("charset-combo-box-internal");
307 
308 	charset_index = g_hash_table_new_full (
309 		g_str_hash, g_str_equal,
310 		(GDestroyNotify) g_free,
311 		(GDestroyNotify) g_object_unref);
312 
313 	combo_box->priv = E_CHARSET_COMBO_BOX_GET_PRIVATE (combo_box);
314 	combo_box->priv->action_group = action_group;
315 	combo_box->priv->charset_index = charset_index;
316 
317 	group = e_charset_add_radio_actions (
318 		action_group, "charset-", NULL, NULL, NULL);
319 
320 	/* Populate the character set index. */
321 	for (iter = group; iter != NULL; iter = iter->next) {
322 		GObject *object = iter->data;
323 		const gchar *charset;
324 
325 		charset = g_object_get_data (object, "charset");
326 		g_return_if_fail (charset != NULL);
327 
328 		g_hash_table_insert (
329 			charset_index, g_strdup (charset),
330 			g_object_ref (object));
331 	}
332 
333 	/* Note the "other" action is not included in the index. */
334 
335 	radio_action = gtk_radio_action_new (
336 		"charset-other", _("Other…"), NULL, NULL, OTHER_VALUE);
337 
338 	g_object_set_data (G_OBJECT (radio_action), "charset", (gpointer) "");
339 
340 	gtk_radio_action_set_group (radio_action, group);
341 	group = gtk_radio_action_get_group (radio_action);
342 
343 	e_action_combo_box_set_action (
344 		E_ACTION_COMBO_BOX (combo_box), radio_action);
345 
346 	e_action_combo_box_add_separator_after (
347 		E_ACTION_COMBO_BOX (combo_box), g_slist_length (group));
348 
349 	g_signal_connect (
350 		combo_box, "notify::charset",
351 		G_CALLBACK (charset_combo_box_notify_charset_cb), NULL);
352 
353 	combo_box->priv->other_action = radio_action;
354 }
355 
356 GType
e_charset_combo_box_get_type(void)357 e_charset_combo_box_get_type (void)
358 {
359 	static GType type = 0;
360 
361 	if (G_UNLIKELY (type == 0)) {
362 		static const GTypeInfo type_info = {
363 			sizeof (ECharsetComboBoxClass),
364 			(GBaseInitFunc) NULL,
365 			(GBaseFinalizeFunc) NULL,
366 			(GClassInitFunc) charset_combo_box_class_init,
367 			(GClassFinalizeFunc) NULL,
368 			NULL,  /* class_data */
369 			sizeof (ECharsetComboBox),
370 			0,     /* n_preallocs */
371 			(GInstanceInitFunc) charset_combo_box_init,
372 			NULL   /* value_table */
373 		};
374 
375 		type = g_type_register_static (
376 			E_TYPE_ACTION_COMBO_BOX, "ECharsetComboBox",
377 			&type_info, 0);
378 	}
379 
380 	return type;
381 }
382 
383 GtkWidget *
e_charset_combo_box_new(void)384 e_charset_combo_box_new (void)
385 {
386 	return g_object_new (E_TYPE_CHARSET_COMBO_BOX, NULL);
387 }
388 
389 /**
390  * e_radio_action_get_current_action:
391  * @radio_action: a #GtkRadioAction
392  *
393  * Returns the currently active member of the group to which @radio_action
394  * belongs.
395  *
396  * Returns: the currently active group member
397  **/
398 static GtkRadioAction *
e_radio_action_get_current_action(GtkRadioAction * radio_action)399 e_radio_action_get_current_action (GtkRadioAction *radio_action)
400 {
401 	GSList *group;
402 	gint current_value;
403 
404 	g_return_val_if_fail (GTK_IS_RADIO_ACTION (radio_action), NULL);
405 
406 	group = gtk_radio_action_get_group (radio_action);
407 	current_value = gtk_radio_action_get_current_value (radio_action);
408 
409 	while (group != NULL) {
410 		gint value;
411 
412 		radio_action = GTK_RADIO_ACTION (group->data);
413 		g_object_get (radio_action, "value", &value, NULL);
414 
415 		if (value == current_value)
416 			return radio_action;
417 
418 		group = g_slist_next (group);
419 	}
420 
421 	return NULL;
422 }
423 
424 
425 const gchar *
e_charset_combo_box_get_charset(ECharsetComboBox * combo_box)426 e_charset_combo_box_get_charset (ECharsetComboBox *combo_box)
427 {
428 	GtkRadioAction *radio_action;
429 
430 	g_return_val_if_fail (E_IS_CHARSET_COMBO_BOX (combo_box), NULL);
431 
432 	radio_action = combo_box->priv->other_action;
433 	radio_action = e_radio_action_get_current_action (radio_action);
434 
435 	return g_object_get_data (G_OBJECT (radio_action), "charset");
436 }
437 
438 void
e_charset_combo_box_set_charset(ECharsetComboBox * combo_box,const gchar * charset)439 e_charset_combo_box_set_charset (ECharsetComboBox *combo_box,
440                                  const gchar *charset)
441 {
442 	GHashTable *charset_index;
443 	GtkRadioAction *radio_action;
444 
445 	g_return_if_fail (E_IS_CHARSET_COMBO_BOX (combo_box));
446 
447 	if (charset == NULL || *charset == '\0')
448 		charset = "UTF-8";
449 
450 	charset_index = combo_box->priv->charset_index;
451 	radio_action = g_hash_table_lookup (charset_index, charset);
452 
453 	if (radio_action == NULL) {
454 		radio_action = combo_box->priv->other_action;
455 		g_object_set_data_full (
456 			G_OBJECT (radio_action),
457 			"charset", g_strdup (charset),
458 			(GDestroyNotify) g_free);
459 	}
460 
461 	combo_box->priv->block_dialog = TRUE;
462 	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (radio_action), TRUE);
463 	combo_box->priv->block_dialog = FALSE;
464 }
465