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