1 /*
2  * Copyright (C) 2009 - 2011 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2010 David King <davidk@openismus.com>
4  * Copyright (C) 2012 Snicksie <none@none.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 <string.h>
23 #include <glib/gprintf.h>
24 #include "utility.h"
25 #include <libgda/libgda.h>
26 #include <glib/gi18n-lib.h>
27 #include <libgda-ui/gdaui-data-entry.h>
28 #include <libgda-ui/libgda-ui.h>
29 #include <libgda-ui/gdaui-decl.h>
30 
31 
32 /*
33  * _gdaui_utility_entry_build_actions_menu
34  * @obj_data:
35  * @attrs:
36  * @function:
37  *
38  * Creates a GtkMenu widget which contains the possible actions on a data entry which
39  * attributes are @attrs. After the menu has been displayed, and when an action is selected,
40  * the @function callback is called with the 'user_data' being @obj_data.
41  *
42  * The menu item which emits the signal has a "action" attribute which describes what action the
43  * user has requested.
44  *
45  * Returns: the new menu
46  */
47 GtkWidget *
_gdaui_utility_entry_build_actions_menu(GObject * obj_data,guint attrs,GCallback function)48 _gdaui_utility_entry_build_actions_menu (GObject *obj_data, guint attrs, GCallback function)
49 {
50 	GtkWidget *menu, *mitem;
51 	gchar *str;
52 	gboolean nullact = FALSE;
53 	gboolean defact = FALSE;
54 	gboolean reset = FALSE;
55 
56 	gboolean value_is_null;
57 	gboolean value_is_modified;
58 	gboolean value_is_default;
59 
60 	menu = gtk_menu_new ();
61 
62 	/* which menu items to make sensitive ? */
63 	value_is_null = attrs & GDA_VALUE_ATTR_IS_NULL;
64 	value_is_modified = ! (attrs & GDA_VALUE_ATTR_IS_UNCHANGED);
65 	value_is_default = attrs & GDA_VALUE_ATTR_IS_DEFAULT;
66 
67 	if (! (attrs & GDA_VALUE_ATTR_NO_MODIF)) {
68 		if ((attrs & GDA_VALUE_ATTR_CAN_BE_NULL) &&
69 		    !(attrs & GDA_VALUE_ATTR_IS_NULL))
70 			nullact = TRUE;
71 		if ((attrs & GDA_VALUE_ATTR_CAN_BE_DEFAULT) &&
72 		    !(attrs & GDA_VALUE_ATTR_IS_DEFAULT))
73 			defact = TRUE;
74 		if (!(attrs & GDA_VALUE_ATTR_IS_UNCHANGED)) {
75 			if (attrs & GDA_VALUE_ATTR_HAS_VALUE_ORIG)
76 				reset = TRUE;
77 		}
78 	}
79 
80 	/* set to NULL item */
81 	str = g_strdup (_("Unset"));
82 	mitem = gtk_check_menu_item_new_with_label (str);
83 	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mitem),
84 					value_is_null);
85 	gtk_widget_show (mitem);
86 	g_object_set_data (G_OBJECT (mitem), "action", GUINT_TO_POINTER (GDA_VALUE_ATTR_IS_NULL));
87 	g_signal_connect (G_OBJECT (mitem), "activate",
88 			  G_CALLBACK (function), obj_data);
89 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
90 	g_free (str);
91 	gtk_widget_set_sensitive (mitem, nullact);
92 
93 	/* default value item */
94 	str = g_strdup (_("Set to default value"));
95 	mitem = gtk_check_menu_item_new_with_label (str);
96 	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mitem),
97 					value_is_default);
98 	gtk_widget_show (mitem);
99 	g_object_set_data (G_OBJECT (mitem), "action", GUINT_TO_POINTER (GDA_VALUE_ATTR_IS_DEFAULT));
100 	g_signal_connect (G_OBJECT (mitem), "activate",
101 			  G_CALLBACK (function), obj_data);
102 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
103 	g_free (str);
104 	gtk_widget_set_sensitive (mitem, defact);
105 
106 	/* reset to original value item */
107 	str = g_strdup (_("Reset to original value"));
108 	mitem = gtk_check_menu_item_new_with_label (str);
109 	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mitem),
110 					!value_is_modified);
111 	gtk_widget_show (mitem);
112 	g_object_set_data (G_OBJECT (mitem), "action", GUINT_TO_POINTER (GDA_VALUE_ATTR_IS_UNCHANGED));
113 	g_signal_connect (G_OBJECT (mitem), "activate",
114 			  G_CALLBACK (function), obj_data);
115 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
116 	g_free (str);
117 	gtk_widget_set_sensitive (mitem, reset);
118 
119 	return menu;
120 }
121 
122 /*
123  * _gdaui_utility_entry_build_info_colors_array_a
124  *
125  * Creates an array of colors for the different states of an entry:
126  *    Valid   <-> No special color
127  *    Null    <-> Green
128  *    Default <-> Blue
129  *    Invalid <-> Red
130  * Each status (except Valid) is represented by two colors for GTK_STATE_NORMAL and
131  * GTK_STATE_PRELIGHT.
132  *
133  * Returns: a new array of 6 colors
134  */
135 GdkRGBA **
_gdaui_utility_entry_build_info_colors_array_a(void)136 _gdaui_utility_entry_build_info_colors_array_a (void)
137 {
138 	GdkRGBA **colors;
139 	GdkRGBA *color;
140 
141 	colors = g_new0 (GdkRGBA *, 6);
142 
143 	/* Green color */
144 	color = g_new0 (GdkRGBA, 1);
145 	g_assert (gdk_rgba_parse (color, GDAUI_COLOR_NORMAL_NULL));
146 	colors[0] = color;
147 
148 	color = g_new0 (GdkRGBA, 1);
149 	g_assert (gdk_rgba_parse (color, GDAUI_COLOR_PRELIGHT_NULL));
150 	colors[1] = color;
151 
152 	/* Blue color */
153 	color = g_new0 (GdkRGBA, 1);
154 	g_assert (gdk_rgba_parse (color, GDAUI_COLOR_NORMAL_DEFAULT));
155 	colors[2] = color;
156 
157 	color = g_new0 (GdkRGBA, 1);
158 	g_assert (gdk_rgba_parse (color, GDAUI_COLOR_PRELIGHT_DEFAULT));
159 	colors[3] = color;
160 
161 	/* Red color */
162 	color = g_new0 (GdkRGBA, 1);
163 	g_assert (gdk_rgba_parse (color, GDAUI_COLOR_NORMAL_INVALID));
164 	colors[4] = color;
165 
166 	color = g_new0 (GdkRGBA, 1);
167 	g_assert (gdk_rgba_parse (color, GDAUI_COLOR_PRELIGHT_INVALID));
168 	colors[5] = color;
169 
170 	return colors;
171 }
172 
173 
174 
175 /*
176  * _gdaui_utility_markup_title
177  */
178 gchar *
_gdaui_utility_markup_title(const gchar * title,gboolean optional)179 _gdaui_utility_markup_title (const gchar *title, gboolean optional)
180 {
181 	if (!optional)
182 		return g_strdup_printf ("%s <span foreground='red' weight='bold'>*</span>:", title);
183 	else
184 		return g_strdup_printf ("%s:", title);
185 }
186 
187 /*
188  * _gdaui_utility_proxy_compute_attributes_for_group
189  *
190  * Computes an attributes from the individual attributes of the values stored in @store and
191  * corresponding to the columns for the parameters in @group (as described by @model_iter),
192  * at row pointed by @iter
193  *
194  * Returns: the attributes
195  */
196 guint
_gdaui_utility_proxy_compute_attributes_for_group(GdauiSetGroup * group,GdauiDataStore * store,GdaDataModelIter * model_iter,GtkTreeIter * tree_iter,gboolean * to_be_deleted)197 _gdaui_utility_proxy_compute_attributes_for_group (GdauiSetGroup *group, GdauiDataStore *store,
198 						     GdaDataModelIter *model_iter, GtkTreeIter *tree_iter,
199 						     gboolean *to_be_deleted)
200 {
201 	guint attributes = GDA_VALUE_ATTR_IS_NULL | GDA_VALUE_ATTR_CAN_BE_NULL |
202                 GDA_VALUE_ATTR_IS_DEFAULT | GDA_VALUE_ATTR_CAN_BE_DEFAULT |
203                 GDA_VALUE_ATTR_IS_UNCHANGED | GDA_VALUE_ATTR_HAS_VALUE_ORIG;
204         gboolean to_del = TRUE, local_to_del;
205         GSList *list;
206         gint col;
207         gint offset;
208         guint localattr;
209 	GdaDataProxy *proxy;
210 
211 	proxy = gdaui_data_store_get_proxy (store);
212         offset = gda_data_proxy_get_proxied_model_n_cols (proxy);
213 
214         /* list the values in proxy_model for each param in GDA_SET_NODE (group->group->nodes->data)->params */
215 	attributes = 0;
216         for (list = gda_set_group_get_nodes (gdaui_set_group_get_group (group)); list; list = list->next) {
217 		col = g_slist_index (((GdaSet*)model_iter)->holders, gda_set_node_get_holder (GDA_SET_NODE (list->data)));
218                 gtk_tree_model_get (GTK_TREE_MODEL (store), tree_iter,
219                                     GDAUI_DATA_STORE_COL_TO_DELETE, &local_to_del,
220                                     offset + col, &localattr, -1);
221 		if (list == gda_set_group_get_nodes (gdaui_set_group_get_group (group)))
222 			attributes = localattr;
223 		else
224 			attributes &= localattr;
225                 to_del = to_del && local_to_del;
226         }
227 
228 	if (to_be_deleted)
229                 *to_be_deleted = to_del;
230 
231         return attributes;
232 }
233 
234 /*
235  * _gdaui_utility_proxy_compute_values_for_group:
236  *
237  * Computes a list of values containing the individual values stored in @store and
238  * corresponding to the columns for the parameters if @group, at row pointed by @tree_iter.
239  *
240  * If @model_values is TRUE, then the values in the returned list are the values of the
241  * @group->nodes_source->data_model, at the @group->nodes_source->shown_cols_index columns
242  *
243  * For both performances reasons and because the contents of the @group->nodes_source->data_model
244  * may change, this function uses the gda_data_proxy_get_model_row_value() function.
245  *
246  * The returned list must be freed by the caller, but the values in the list must not be modified or
247  * freed.
248  */
249 GList *
_gdaui_utility_proxy_compute_values_for_group(GdauiSetGroup * group,GdauiDataStore * store,GdaDataModelIter * model_iter,GtkTreeIter * tree_iter,gboolean model_values)250 _gdaui_utility_proxy_compute_values_for_group (GdauiSetGroup *group, GdauiDataStore *store,
251 					       GdaDataModelIter *model_iter,
252 					       GtkTreeIter *tree_iter, gboolean model_values)
253 {
254 	GList *retval = NULL;
255 #ifdef PROXY_STORE_EXTRA_VALUES
256 	GdaDataProxy *proxy;
257 	proxy = gdaui_data_store_get_proxy (store);
258 #endif
259 	if (!model_values) {
260 		GSList *list;
261 		GValue *value;
262 
263 		for (list = gda_set_group_get_nodes (gdaui_set_group_get_group (group)); list; list = list->next) {
264 			gint col;
265 
266 			col = g_slist_index (((GdaSet*)model_iter)->holders, gda_set_node_get_holder (GDA_SET_NODE (list->data)));
267 			gtk_tree_model_get (GTK_TREE_MODEL (store), tree_iter, col, &value, -1);
268 			retval = g_list_append (retval, value);
269 		}
270 	}
271 	else {
272 		gint col, i;
273 #ifdef PROXY_STORE_EXTRA_VALUES
274 		gint proxy_row;
275 #endif
276 		GdauiSetSource *source;
277 		const GValue *value;
278 		gboolean slow_way = FALSE;
279 		gboolean ret_null = FALSE;
280 #ifdef PROXY_STORE_EXTRA_VALUES
281 		proxy_row = gdaui_data_store_get_row_from_iter (store, tree_iter);
282 #endif
283 		source = gdaui_set_group_get_source (group);
284 		for (i = 0 ; (i < gdaui_set_source_get_shown_n_cols (source))  && !ret_null; i++) {
285 			col = (gdaui_set_source_get_shown_columns (source))[i];
286 #ifdef PROXY_STORE_EXTRA_VALUES
287 			if (!slow_way) {
288 				value = gda_data_proxy_get_model_row_value (proxy, source->data_model, proxy_row, col);
289 				if (value)
290 					retval = g_list_append (retval, (GValue *) value);
291 				else {
292 					if (gda_data_proxy_get_assigned_model_col (proxy, source->data_model, col) < 0)
293 						slow_way = TRUE;
294 					else
295 						retval = g_list_append (retval, NULL);
296 				}
297 			}
298 #endif
299 			slow_way = TRUE;
300 
301 			if (slow_way) {
302 				/* may be a bit slow: try to find the values in the model, it's better if the user
303 				 * uses gda_data_proxy_assign_model_col()!  */
304 				GSList *key_values = NULL;
305 				gint row, *cols_index;
306 				GSList *list;
307 				gint j;
308 
309 				cols_index = g_new0 (gint, gda_set_group_get_n_nodes (gdaui_set_group_get_group (group)));
310 				for (list = gda_set_group_get_nodes (gdaui_set_group_get_group (group)), j = 0; list; list = list->next, j++) {
311 					gint colno;
312 					colno = g_slist_index (((GdaSet*)model_iter)->holders,
313 							       gda_set_node_get_holder (GDA_SET_NODE (list->data)));
314 					cols_index [j] = gda_set_node_get_source_column (GDA_SET_NODE (list->data));
315 					gtk_tree_model_get (GTK_TREE_MODEL (store), tree_iter,
316 							    colno, &value, -1);
317 					key_values = g_slist_append (key_values, (GValue *) value);
318 				}
319 
320 				row = gda_data_model_get_row_from_values (gda_set_source_get_data_model (gdaui_set_source_get_source (source)),
321 									  key_values, cols_index);
322 				if (row >= 0) {
323 					value = gda_data_model_get_value_at (gda_set_source_get_data_model (gdaui_set_source_get_source (source)),
324 									     col, row, NULL);
325 					retval = g_list_append (retval, (GValue *) value);
326 				}
327 				else {
328 #ifdef DEBUG_WARNING
329 					g_warning ("Could not find requested value in restricting data model");
330 					g_print ("Requested: ");
331 					list = key_values;
332 					j = 0;
333 					while (list) {
334 						gchar *str;
335 
336 						if (value) {
337 							str = gda_value_stringify ((GValue *) list->data);
338 							g_print ("/%s @col %d", str, cols_index [j]);
339 							g_free (str);
340 						}
341 						else
342 							g_print ("/NULL @col %d", cols_index [j]);
343 						list = g_slist_next (list);
344 						j++;
345 					}
346 					g_print (" in data model: \n");
347 					gda_data_model_dump (source->data_model, stdout);
348 #endif
349 					ret_null = TRUE;
350 				}
351 				g_slist_free (key_values);
352 			}
353 		}
354 
355 		if (ret_null) {
356 			g_list_free (retval);
357 			retval = NULL;
358 		}
359 	}
360 
361 	return retval;
362 }
363 
364 /*
365  * Errors reporting
366  */
367 static GtkWidget *
create_data_error_dialog(GdauiDataProxy * form,gboolean with_question,gboolean can_discard,GError * filled_error)368 create_data_error_dialog (GdauiDataProxy *form, gboolean with_question, gboolean can_discard, GError *filled_error)
369 {
370 	GtkWidget *dlg;
371 	const gchar *msg1 = NULL, *msg2 = NULL;
372 
373 	if (can_discard) {
374 		msg1 = _("Current modified data is invalid");
375 		if (with_question)
376 			msg2 =_("You may now choose to correct it, or to discard "
377 				"the modifications.\n\n"
378 				"What do you want to do?");
379 		else
380 			msg2 = _("please correct it and try again, or discard "
381 				 "the modifications.");
382 	}
383 	else {
384 		if (with_question)
385 			g_warning ("Incoherence problem...\n");
386 		else {
387 			msg1 = _("Part of the current modified data was invalid");
388 			msg2 = _("As no transaction was used, only a part of the valid data\n"
389 				 "has been written, and the remaining modification have been discarded.");
390 		}
391 	}
392 	dlg = gtk_message_dialog_new_with_markup ((GtkWindow *) gtk_widget_get_toplevel (GTK_WIDGET (form)),
393 						  GTK_DIALOG_MODAL,
394 						  GTK_MESSAGE_ERROR,
395 						  with_question ? GTK_BUTTONS_NONE : GTK_BUTTONS_CLOSE,
396 						  "<b>%s:</b>\n\n%s", msg1, msg2);
397 
398 	if (filled_error && filled_error->message) {
399 		GtkWidget *label;
400 
401 		label = gtk_label_new (filled_error->message);
402 		gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
403 				    label, TRUE, TRUE, 0);
404 		gtk_widget_show (label);
405 	}
406 
407 	return dlg;
408 }
409 
410 /*
411  * _gdaui_utility_display_error_with_keep_or_discard_choice
412  * @form: a #GdauiDataProxy
413  * @filled_error: a #GError containing the error to display
414  *
415  * Displays a dialog showing @filled_error's message and asks the user to either keep the data which
416  * led to the error, or to discard it.
417  *
418  * Returns: TRUE if current data can be discarded
419  */
420 gboolean
_gdaui_utility_display_error_with_keep_or_discard_choice(GdauiDataProxy * form,GError * filled_error)421 _gdaui_utility_display_error_with_keep_or_discard_choice (GdauiDataProxy *form, GError *filled_error)
422 {
423 	GtkWidget *dlg;
424 	gint res;
425 
426 	if (filled_error && (filled_error->domain == GDA_DATA_PROXY_ERROR) &&
427 	    (filled_error->code == GDA_DATA_PROXY_COMMIT_CANCELLED))
428 		return FALSE;
429 
430 	dlg = create_data_error_dialog (form, TRUE, TRUE, filled_error);
431 	gtk_dialog_add_buttons (GTK_DIALOG (dlg),
432 				_("Discard modified data"), GTK_RESPONSE_REJECT,
433 				_("Correct data first"), GTK_RESPONSE_NONE, NULL);
434 	res = gtk_dialog_run (GTK_DIALOG (dlg));
435 	gtk_widget_destroy (dlg);
436 	return (res == GTK_RESPONSE_REJECT) ? TRUE : FALSE;
437 }
438 
439 /*
440  * _gdaui_utility_display_error
441  * @form: a #GdauiDataProxy
442  * @filled_error: a #GError containing the error to display
443  *
444  * Displays a dialog showing @filled_error's message and asks the user to either keep the data which
445  * led to the error.
446  */
447 void
_gdaui_utility_display_error(GdauiDataProxy * form,gboolean can_discard,GError * filled_error)448 _gdaui_utility_display_error (GdauiDataProxy *form, gboolean can_discard, GError *filled_error)
449 {
450 	GtkWidget *dlg;
451 
452 	if (filled_error && (filled_error->domain == GDA_DATA_PROXY_ERROR) &&
453 	    (filled_error->code == GDA_DATA_PROXY_COMMIT_CANCELLED))
454 		return;
455 
456 	dlg = create_data_error_dialog (form, FALSE, can_discard, filled_error);
457 	gtk_dialog_run (GTK_DIALOG (dlg));
458 	gtk_widget_destroy (dlg);
459 
460 }
461 
462 /*
463  * _gdaui_utility_show_error
464  * @format:
465  * @...:
466  *
467  */
468 void
_gdaui_utility_show_error(GtkWindow * parent,const gchar * format,...)469 _gdaui_utility_show_error (GtkWindow *parent, const gchar *format, ...)
470 {
471 	va_list args;
472         gchar sz[2048];
473 	GtkWidget *dialog;
474 
475 	/* build the message string */
476         va_start (args, format);
477         vsnprintf (sz, sizeof sz, format, args);
478         va_end (args);
479 
480 	/* create the error message dialog */
481 	gchar *str;
482 	str = g_strdup_printf ("<span weight=\"bold\">%s</span>%s\n", _("Error:"), sz);
483 	dialog = gtk_message_dialog_new_with_markup (parent,
484                                                      GTK_DIALOG_DESTROY_WITH_PARENT |
485                                                      GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
486                                                      GTK_BUTTONS_CLOSE, "%s", str);
487 	g_free (str);
488 	gtk_dialog_add_action_widget (GTK_DIALOG (dialog),
489 				      gtk_button_new_from_stock (GTK_STOCK_OK),
490 				      GTK_RESPONSE_OK);
491 	gtk_widget_show_all (dialog);
492 	gtk_dialog_run (GTK_DIALOG (dialog));
493 	gtk_widget_destroy (dialog);
494 }
495 
496 /*
497  * Compares 2 GdaDataModelIter
498  *
499  * Returns: %TRUE if they have at least one difference
500  */
501 gboolean
_gdaui_utility_iter_differ(GdaDataModelIter * iter1,GdaDataModelIter * iter2)502 _gdaui_utility_iter_differ (GdaDataModelIter *iter1, GdaDataModelIter *iter2)
503 {
504 	GSList *list1, *list2;
505         gboolean retval = TRUE;
506 
507 	for (list1 = GDA_SET (iter1)->holders, list2 = GDA_SET (iter2)->holders;
508              list1 && list2;
509              list1 = list1->next, list2 = list2->next) {
510                 GdaHolder *oh, *nh;
511                 oh = GDA_HOLDER (list1->data);
512                 nh = GDA_HOLDER (list2->data);
513 
514                 if (gda_holder_get_not_null (oh) != gda_holder_get_not_null (nh))
515                         goto out;
516 
517                 GType ot, nt;
518                 ot = gda_holder_get_g_type (oh);
519                 nt = gda_holder_get_g_type (nh);
520                 if (ot && (ot != nt))
521                         goto out;
522                 if (ot != nt)
523                         g_object_set (oh, "g-type", nt, NULL);
524 
525                 const gchar *oid, *nid;
526                 oid = gda_holder_get_id (oh);
527                 nid = gda_holder_get_id (nh);
528                 if ((oid && !nid) || (!oid && nid) ||
529                     (oid && strcmp (oid, nid)))
530                         goto out;
531         }
532 
533         if (list1 || list2)
534                 goto out;
535         retval = FALSE; /* iters don't differ */
536 
537  out:
538         return retval;
539 }
540 
541 
542 static gboolean tree_view_button_pressed_cb (GtkWidget *widget, GdkEventButton *event, gpointer unuseddata);
543 /*
544  * Setup a callback on the treeview to, on right click:
545  *   - if there is no row under the cursor, then force an empty selection
546  *   OR
547  *   - if the row under the cursor is not selected, then force the selection of only that row
548  *   OR
549  *   - otherwise don't change anything
550  */
551 void
_gdaui_setup_right_click_selection_on_treeview(GtkTreeView * tview)552 _gdaui_setup_right_click_selection_on_treeview (GtkTreeView *tview)
553 {
554 	g_return_if_fail (GTK_IS_TREE_VIEW (tview));
555 	g_signal_connect (G_OBJECT (tview), "button-press-event",
556                           G_CALLBACK (tree_view_button_pressed_cb), NULL);
557 }
558 
559 static gboolean
tree_view_button_pressed_cb(GtkWidget * widget,GdkEventButton * event,G_GNUC_UNUSED gpointer data)560 tree_view_button_pressed_cb (GtkWidget *widget, GdkEventButton *event, G_GNUC_UNUSED gpointer data)
561 {
562 	GtkTreeView *tree_view;
563 	GtkTreeSelection *selection;
564 
565 	if (event->button != 3)
566 		return FALSE;
567 
568 	tree_view = GTK_TREE_VIEW (widget);
569 	selection = gtk_tree_view_get_selection (tree_view);
570 
571 	/* force selection of row on which clicked occurred */
572 	if (event->window == gtk_tree_view_get_bin_window (tree_view)) {
573 		GtkTreePath *path;
574 		if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, &path,
575 						   NULL, NULL, NULL)) {
576 			if (! gtk_tree_selection_path_is_selected (selection, path)) {
577 				gtk_tree_selection_unselect_all (selection);
578 				gtk_tree_selection_select_path (selection, path);
579 			}
580 			gtk_tree_path_free (path);
581 		}
582 		else
583 			gtk_tree_selection_unselect_all (selection);
584 	}
585 
586 	return FALSE;
587 }
588