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 * 48 _gdaui_utility_entry_build_actions_menu (GObject *obj_data, guint attrs, GCallback function) 49 { 50 GtkWidget *menu, *mitem; do_ddl_queries(GtkWidget * do_widget)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 ** 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; tested_provider_changed_cb(G_GNUC_UNUSED GdauiProviderSelector * prov_sel,DemoData * data)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 update_possible_operations(DemoData * data)170 return colors; 171 } 172 173 174 175 /* 176 * _gdaui_utility_markup_title 177 */ 178 gchar * 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 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; get_provider_obj(DemoData * data)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; tested_operation_changed_cb(G_GNUC_UNUSED GdauiCombo * combo,DemoData * data)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 * 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++) { extract_named_parameters(GdaServerOperation * op,const gchar * root_path,GtkTextBuffer * tbuffer)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 * 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 } show_named_parameters(G_GNUC_UNUSED GtkButton * button,DemoData * data)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 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 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 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); show_sql(G_GNUC_UNUSED GtkButton * button,DemoData * data)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 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 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 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