1 /* 2 * Copyright (C) 2009 - 2011 Vivien Malerba <malerba@gnome-db.org> 3 * Copyright (C) 2010 David King <davidk@openismus.com> 4 * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 2 9 * of the License, or (at your option) any later version. 10 * 11 * This program 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 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 */ 20 21 #include <gtk/gtk.h> 22 #include <math.h> 23 #include <libgda/libgda.h> 24 #include <glib/gi18n-lib.h> 25 #include "browser-canvas.h" 26 #include "browser-canvas-fkey.h" 27 #include "browser-canvas-table.h" 28 #include "browser-canvas-text.h" 29 #include "browser-canvas-utility.h" 30 #include "browser-canvas-db-relations.h" 31 #include "../../tool-utils.h" 32 #include "../support.h" 33 #include "../browser-window.h" 34 #include "../common/fk-declare.h" 35 36 static void browser_canvas_fkey_class_init (BrowserCanvasFkeyClass * class); 37 static void browser_canvas_fkey_init (BrowserCanvasFkey * cc); 38 static void browser_canvas_fkey_dispose (GObject *object); 39 static void browser_canvas_fkey_finalize (GObject *object); 40 41 static void browser_canvas_fkey_set_property (GObject *object, 42 guint param_id, 43 const GValue *value, 44 GParamSpec *pspec); 45 static void browser_canvas_fkey_get_property (GObject *object, 46 guint param_id, 47 GValue *value, 48 GParamSpec *pspec); 49 50 static void browser_canvas_fkey_get_edge_nodes (BrowserCanvasItem *citem, 51 BrowserCanvasItem **from, BrowserCanvasItem **to); 52 53 static void clean_items (BrowserCanvasFkey *cc); 54 static void create_items (BrowserCanvasFkey *cc); 55 static void update_items (BrowserCanvasFkey *cc); 56 57 enum 58 { 59 PROP_0, 60 PROP_META_STRUCT, 61 PROP_FK_CONSTRAINT 62 }; 63 64 struct _BrowserCanvasFkeyPrivate 65 { 66 GdaMetaStruct *mstruct; 67 GdaMetaTableForeignKey *fk; 68 BrowserCanvasTable *fk_table_item; 69 BrowserCanvasTable *ref_pk_table_item; 70 GSList *shapes; /* list of BrowserCanvasCanvasShape structures */ 71 }; 72 73 /* get a pointer to the parents to be able to call their destructor */ 74 static GObjectClass *parent_class = NULL; 75 static GooCanvasLineDash *dash = NULL, *no_dash = NULL; 76 77 GType 78 browser_canvas_fkey_get_type (void) 79 { 80 static GType type = 0; 81 82 if (G_UNLIKELY (type == 0)) { 83 static const GTypeInfo info = { 84 sizeof (BrowserCanvasFkeyClass), 85 (GBaseInitFunc) NULL, 86 (GBaseFinalizeFunc) NULL, 87 (GClassInitFunc) browser_canvas_fkey_class_init, 88 NULL, 89 NULL, 90 sizeof (BrowserCanvasFkey), 91 0, 92 (GInstanceInitFunc) browser_canvas_fkey_init, 93 0 94 }; 95 96 type = g_type_register_static (TYPE_BROWSER_CANVAS_ITEM, "BrowserCanvasFkey", &info, 0); 97 } 98 99 return type; 100 } 101 102 static void 103 browser_canvas_fkey_class_init (BrowserCanvasFkeyClass * class) 104 { 105 GObjectClass *object_class = G_OBJECT_CLASS (class); 106 107 parent_class = g_type_class_peek_parent (class); 108 109 BROWSER_CANVAS_ITEM_CLASS (class)->get_edge_nodes = browser_canvas_fkey_get_edge_nodes; 110 111 object_class->dispose = browser_canvas_fkey_dispose; 112 object_class->finalize = browser_canvas_fkey_finalize; 113 114 /* Properties */ 115 object_class->set_property = browser_canvas_fkey_set_property; 116 object_class->get_property = browser_canvas_fkey_get_property; 117 118 g_object_class_install_property 119 (object_class, PROP_META_STRUCT, 120 g_param_spec_object ("meta-struct", NULL, NULL, 121 GDA_TYPE_META_STRUCT, 122 (G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY))); 123 g_object_class_install_property (object_class, PROP_FK_CONSTRAINT, 124 g_param_spec_pointer ("fk_constraint", "FK constraint", 125 NULL, 126 G_PARAM_WRITABLE)); 127 128 dash = goo_canvas_line_dash_new (2, 5., 1.5); 129 no_dash = goo_canvas_line_dash_new (0); 130 } 131 132 static void 133 browser_canvas_fkey_init (BrowserCanvasFkey *cc) 134 { 135 cc->priv = g_new0 (BrowserCanvasFkeyPrivate, 1); 136 cc->priv->mstruct = NULL; 137 cc->priv->fk = NULL; 138 cc->priv->fk_table_item = NULL; 139 cc->priv->ref_pk_table_item = NULL; 140 cc->priv->shapes = NULL; 141 } 142 143 static void 144 fk_table_item_weak_ref_lost (BrowserCanvasFkey *cc, G_GNUC_UNUSED BrowserCanvasTable *old_table_item) 145 { 146 cc->priv->fk_table_item = NULL; 147 } 148 149 static void 150 ref_pk_table_item_weak_ref_lost (BrowserCanvasFkey *cc, G_GNUC_UNUSED BrowserCanvasTable *old_table_item) 151 { 152 cc->priv->ref_pk_table_item = NULL; 153 } 154 155 156 static void 157 browser_canvas_fkey_dispose (GObject *object) 158 { 159 BrowserCanvasFkey *cc; 160 g_return_if_fail (object != NULL); 161 g_return_if_fail (IS_BROWSER_CANVAS_FKEY (object)); 162 163 cc = BROWSER_CANVAS_FKEY (object); 164 165 clean_items (cc); 166 if (cc->priv->mstruct) { 167 g_object_unref (cc->priv->mstruct); 168 cc->priv->mstruct = NULL; 169 } 170 cc->priv->fk = NULL; 171 if (cc->priv->fk_table_item) { 172 g_object_weak_unref (G_OBJECT (cc->priv->fk_table_item), 173 (GWeakNotify) fk_table_item_weak_ref_lost, cc); 174 cc->priv->fk_table_item = NULL; 175 } 176 if (cc->priv->ref_pk_table_item) { 177 g_object_weak_unref (G_OBJECT (cc->priv->ref_pk_table_item), 178 (GWeakNotify) ref_pk_table_item_weak_ref_lost, cc); 179 cc->priv->ref_pk_table_item = NULL; 180 } 181 182 /* for the parent class */ 183 parent_class->dispose (object); 184 } 185 186 187 static void 188 browser_canvas_fkey_finalize (GObject *object) 189 { 190 BrowserCanvasFkey *cc; 191 g_return_if_fail (object != NULL); 192 g_return_if_fail (IS_BROWSER_CANVAS_FKEY (object)); 193 194 cc = BROWSER_CANVAS_FKEY (object); 195 if (cc->priv) { 196 g_slist_free (cc->priv->shapes); 197 g_free (cc->priv); 198 cc->priv = NULL; 199 } 200 201 /* for the parent class */ 202 parent_class->finalize (object); 203 } 204 205 static void 206 browser_canvas_fkey_set_property (GObject *object, 207 guint param_id, 208 const GValue *value, 209 GParamSpec *pspec) 210 { 211 BrowserCanvasFkey *cc; 212 213 cc = BROWSER_CANVAS_FKEY (object); 214 215 switch (param_id) { 216 case PROP_META_STRUCT: 217 cc->priv->mstruct = g_value_dup_object (value); 218 break; 219 case PROP_FK_CONSTRAINT: 220 if (cc->priv->fk != g_value_get_pointer (value)) { 221 cc->priv->fk = g_value_get_pointer (value); 222 clean_items (cc); 223 create_items (cc); 224 } 225 break; 226 default: 227 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); 228 break; 229 } 230 } 231 232 static void 233 browser_canvas_fkey_get_property (GObject *object, 234 guint param_id, 235 GValue *value, 236 GParamSpec *pspec) 237 { 238 BrowserCanvasFkey *cc; 239 240 cc = BROWSER_CANVAS_FKEY (object); 241 242 switch (param_id) { 243 case PROP_META_STRUCT: 244 g_value_set_object (value, cc->priv->mstruct); 245 break; 246 default: 247 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); 248 break; 249 } 250 } 251 252 static void 253 browser_canvas_fkey_get_edge_nodes (BrowserCanvasItem *citem, 254 BrowserCanvasItem **from, BrowserCanvasItem **to) 255 { 256 BrowserCanvasFkey *cc; 257 258 cc = BROWSER_CANVAS_FKEY (citem); 259 260 if (from) 261 *from = (BrowserCanvasItem*) cc->priv->fk_table_item; 262 if (to) 263 *to = (BrowserCanvasItem*) cc->priv->ref_pk_table_item; 264 } 265 266 static gboolean single_item_enter_notify_event_cb (GooCanvasItem *ci, GooCanvasItem *target_item, 267 GdkEventCrossing *event, BrowserCanvasFkey *cc); 268 static gboolean single_item_leave_notify_event_cb (GooCanvasItem *ci, GooCanvasItem *target_item, 269 GdkEventCrossing *event, BrowserCanvasFkey *cc); 270 static gboolean single_item_button_press_event_cb (GooCanvasItem *ci, GooCanvasItem *target_item, 271 GdkEventButton *event, BrowserCanvasFkey *cc); 272 static void table_item_moved_cb (GooCanvasItem *table, BrowserCanvasFkey *cc); 273 274 /* 275 * destroy any existing GooCanvasItem objects 276 */ 277 static void 278 clean_items (BrowserCanvasFkey *cc) 279 { 280 if (cc->priv->fk_table_item) { 281 g_signal_handlers_disconnect_by_func (G_OBJECT (cc->priv->fk_table_item), 282 G_CALLBACK (table_item_moved_cb), cc); 283 g_object_weak_unref (G_OBJECT (cc->priv->fk_table_item), 284 (GWeakNotify) fk_table_item_weak_ref_lost, cc); 285 cc->priv->fk_table_item = NULL; 286 } 287 288 if (cc->priv->ref_pk_table_item) { 289 g_signal_handlers_disconnect_by_func (G_OBJECT (cc->priv->ref_pk_table_item), 290 G_CALLBACK (table_item_moved_cb), cc); 291 g_object_weak_unref (G_OBJECT (cc->priv->ref_pk_table_item), 292 (GWeakNotify) ref_pk_table_item_weak_ref_lost, cc); 293 cc->priv->ref_pk_table_item = NULL; 294 } 295 296 /* remove all the GooCanvasItem objects */ 297 browser_canvas_canvas_shapes_remove_all (cc->priv->shapes); 298 cc->priv->shapes = NULL; 299 } 300 301 /* 302 * create new GooCanvasItem objects 303 */ 304 static void 305 create_items (BrowserCanvasFkey *cc) 306 { 307 GSList *list, *canvas_shapes; 308 BrowserCanvasTable *table_item; 309 BrowserCanvas *canvas = g_object_get_data (G_OBJECT (goo_canvas_item_get_canvas (GOO_CANVAS_ITEM (cc))), 310 "browsercanvas"); 311 312 g_assert (cc->priv->fk); 313 314 /* Analyse FK constraint */ 315 table_item = browser_canvas_db_relations_get_table_item (BROWSER_CANVAS_DB_RELATIONS (canvas), 316 GDA_META_TABLE (cc->priv->fk->meta_table)); 317 cc->priv->fk_table_item = table_item; 318 g_return_if_fail (table_item); 319 g_object_weak_ref (G_OBJECT (table_item), (GWeakNotify) fk_table_item_weak_ref_lost, cc); 320 321 g_signal_connect (G_OBJECT (table_item), "moving", 322 G_CALLBACK (table_item_moved_cb), cc); 323 g_signal_connect (G_OBJECT (table_item), "moved", 324 G_CALLBACK (table_item_moved_cb), cc); 325 326 table_item = browser_canvas_db_relations_get_table_item (BROWSER_CANVAS_DB_RELATIONS (canvas), 327 GDA_META_TABLE (cc->priv->fk->depend_on)); 328 cc->priv->ref_pk_table_item = table_item; 329 g_return_if_fail (table_item); 330 331 g_object_weak_ref (G_OBJECT (table_item), (GWeakNotify) ref_pk_table_item_weak_ref_lost, cc); 332 g_signal_connect (G_OBJECT (table_item), "moving", 333 G_CALLBACK (table_item_moved_cb), cc); 334 g_signal_connect (G_OBJECT (table_item), "moved", 335 G_CALLBACK (table_item_moved_cb), cc); 336 337 /* actual line(s) */ 338 g_assert (!cc->priv->shapes); 339 canvas_shapes = browser_canvas_util_compute_anchor_shapes (GOO_CANVAS_ITEM (cc), NULL, 340 cc->priv->fk_table_item, 341 cc->priv->ref_pk_table_item, 342 /*MAX (cc->priv->fk->cols_nb, 1)*/ 1, 343 0, TRUE); 344 345 cc->priv->shapes = browser_canvas_canvas_shapes_remove_obsolete_shapes (canvas_shapes); 346 for (list = canvas_shapes; list; list = list->next) { 347 GooCanvasItem *item = BROWSER_CANVAS_CANVAS_SHAPE (list->data)->item; 348 gchar *color = "black"; 349 g_object_set (G_OBJECT (item), 350 "stroke-color", color, 351 "line-dash", GDA_META_TABLE_FOREIGN_KEY_IS_DECLARED (cc->priv->fk) ? dash : no_dash, 352 NULL); 353 354 if (G_OBJECT_TYPE (item) == GOO_TYPE_CANVAS_POLYLINE) { 355 g_object_set (G_OBJECT (item), 356 "start-arrow", TRUE, 357 "arrow-tip-length", 4., 358 "arrow-length", 5., 359 "arrow-width", 4., 360 NULL); 361 } 362 else if (G_OBJECT_TYPE (item) == GOO_TYPE_CANVAS_ELLIPSE) 363 g_object_set (G_OBJECT (item), 364 "fill-color", color, 365 NULL); 366 367 g_object_set_data (G_OBJECT (item), "fkcons", cc->priv->fk); 368 g_signal_connect (G_OBJECT (item), "enter-notify-event", 369 G_CALLBACK (single_item_enter_notify_event_cb), cc); 370 g_signal_connect (G_OBJECT (item), "leave-notify-event", 371 G_CALLBACK (single_item_leave_notify_event_cb), cc); 372 g_signal_connect (G_OBJECT (item), "button-press-event", 373 G_CALLBACK (single_item_button_press_event_cb), cc); 374 375 } 376 } 377 378 /* 379 * update GooCanvasItem objects 380 */ 381 static void 382 update_items (BrowserCanvasFkey *cc) 383 { 384 cc->priv->shapes = browser_canvas_util_compute_anchor_shapes (GOO_CANVAS_ITEM (cc), cc->priv->shapes, 385 cc->priv->fk_table_item, 386 cc->priv->ref_pk_table_item, 387 /*MAX (cc->priv->fk->cols_nb, 1)*/ 1, 388 0, TRUE); 389 cc->priv->shapes = browser_canvas_canvas_shapes_remove_obsolete_shapes (cc->priv->shapes); 390 } 391 392 /* 393 * item is for a single FK constraint 394 */ 395 static gboolean 396 single_item_enter_notify_event_cb (GooCanvasItem *ci, G_GNUC_UNUSED GooCanvasItem *target_item, 397 G_GNUC_UNUSED GdkEventCrossing *event, BrowserCanvasFkey *cc) 398 { 399 gint i; 400 401 for (i = 0; i < cc->priv->fk->cols_nb; i++) { 402 GdaMetaTableColumn *tcol; 403 BrowserCanvasColumn *column; 404 405 /* fk column */ 406 tcol = g_slist_nth_data (GDA_META_TABLE (cc->priv->fk->meta_table)->columns, 407 cc->priv->fk->fk_cols_array[i] - 1); 408 409 column = browser_canvas_table_get_column_item (cc->priv->fk_table_item, tcol); 410 browser_canvas_text_set_highlight (BROWSER_CANVAS_TEXT (column), TRUE); 411 412 /* ref pk column */ 413 tcol = g_slist_nth_data (GDA_META_TABLE (cc->priv->fk->depend_on)->columns, 414 cc->priv->fk->ref_pk_cols_array[i] - 1); 415 416 column = browser_canvas_table_get_column_item (cc->priv->ref_pk_table_item, tcol); 417 browser_canvas_text_set_highlight (BROWSER_CANVAS_TEXT (column), TRUE); 418 419 gchar *str; 420 str = g_strdup_printf ("%s '%s'\n%s: %s\n%s: %s", 421 GDA_META_TABLE_FOREIGN_KEY_IS_DECLARED (cc->priv->fk) ? 422 _("Declared foreign key") : _("Foreign key"), 423 cc->priv->fk->fk_name, 424 _("Policy on UPDATE"), 425 gda_tools_utils_fk_policy_to_string (GDA_META_TABLE_FOREIGN_KEY_ON_UPDATE_POLICY (cc->priv->fk)), 426 _("Policy on DELETE"), 427 gda_tools_utils_fk_policy_to_string (GDA_META_TABLE_FOREIGN_KEY_ON_DELETE_POLICY (cc->priv->fk))); 428 gtk_widget_set_tooltip_text (GTK_WIDGET (goo_canvas_item_get_canvas (GOO_CANVAS_ITEM (ci))), 429 str); 430 g_free (str); 431 } 432 433 return FALSE; 434 } 435 436 static gboolean 437 single_item_leave_notify_event_cb (G_GNUC_UNUSED GooCanvasItem *ci, G_GNUC_UNUSED GooCanvasItem *target_item, 438 G_GNUC_UNUSED GdkEventCrossing *event, BrowserCanvasFkey *cc) 439 { 440 gint i; 441 442 for (i = 0; i < cc->priv->fk->cols_nb; i++) { 443 GdaMetaTableColumn *tcol; 444 BrowserCanvasColumn *column; 445 446 /* fk column */ 447 tcol = g_slist_nth_data (GDA_META_TABLE (cc->priv->fk->meta_table)->columns, 448 cc->priv->fk->fk_cols_array[i] - 1); 449 450 column = browser_canvas_table_get_column_item (cc->priv->fk_table_item, tcol); 451 browser_canvas_text_set_highlight (BROWSER_CANVAS_TEXT (column), FALSE); 452 453 /* ref pk column */ 454 tcol = g_slist_nth_data (GDA_META_TABLE (cc->priv->fk->depend_on)->columns, 455 cc->priv->fk->ref_pk_cols_array[i] - 1); 456 457 column = browser_canvas_table_get_column_item (cc->priv->ref_pk_table_item, tcol); 458 browser_canvas_text_set_highlight (BROWSER_CANVAS_TEXT (column), FALSE); 459 } 460 461 return FALSE; 462 } 463 464 static void 465 delete_declared_fk_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvasFkey *cc) 466 { 467 GError *error = NULL; 468 GtkWidget *parent; 469 parent = (GtkWidget*) gtk_widget_get_toplevel ((GtkWidget*) goo_canvas_item_get_canvas (GOO_CANVAS_ITEM (cc))); 470 if (! fk_declare_undeclare (cc->priv->mstruct, 471 BROWSER_IS_WINDOW (parent) ? BROWSER_WINDOW (parent) : NULL, 472 cc->priv->fk, &error)) { 473 browser_show_error ((GtkWindow *) parent, _("Failed to undeclare foreign key: %s"), 474 error && error->message ? error->message : _("No detail")); 475 g_clear_error (&error); 476 } 477 else if (BROWSER_IS_WINDOW (parent)) 478 browser_window_show_notice (BROWSER_WINDOW (parent), 479 GTK_MESSAGE_INFO, "fkdeclare", 480 _("Successfully undeclared foreign key")); 481 else 482 browser_show_message ((GtkWindow *) parent, "%s", 483 _("Successfully undeclared foreign key")); 484 } 485 486 static gboolean 487 single_item_button_press_event_cb (G_GNUC_UNUSED GooCanvasItem *ci, G_GNUC_UNUSED GooCanvasItem *target_item, 488 G_GNUC_UNUSED GdkEventButton *event, BrowserCanvasFkey *cc) 489 { 490 GdaMetaTableForeignKey *fk = g_object_get_data (G_OBJECT (ci), "fkcons"); 491 if (GDA_META_TABLE_FOREIGN_KEY_IS_DECLARED (fk)) { 492 GtkWidget *menu, *entry; 493 494 menu = gtk_menu_new (); 495 entry = gtk_menu_item_new_with_label (_("Remove this declared foreign key")); 496 g_object_set_data (G_OBJECT (entry), "fkcons", fk); 497 g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (delete_declared_fk_cb), cc); 498 gtk_menu_shell_append (GTK_MENU_SHELL (menu), entry); 499 gtk_widget_show (entry); 500 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, 501 NULL, NULL, ((GdkEventButton *)event)->button, 502 ((GdkEventButton *)event)->time); 503 return TRUE; 504 } 505 else 506 return FALSE; 507 } 508 509 static void 510 table_item_moved_cb (G_GNUC_UNUSED GooCanvasItem *table, BrowserCanvasFkey *cc) 511 { 512 update_items (cc); 513 } 514 515 /** 516 * browser_canvas_fkey_new 517 * @parent: the parent item, or NULL. 518 * @fkcons: the #GdaMetaTableForeignKey to represent 519 * @...: optional pairs of property names and values, and a terminating NULL. 520 * 521 * Creates a new canvas item to represent the @fkcons FK constraint 522 * 523 * Returns: a new #GooCanvasItem object 524 */ 525 GooCanvasItem * 526 browser_canvas_fkey_new (GooCanvasItem *parent, GdaMetaStruct *mstruct, GdaMetaTableForeignKey *fkcons, ...) 527 { 528 GooCanvasItem *item; 529 const char *first_property; 530 va_list var_args; 531 532 g_return_val_if_fail (GDA_IS_META_STRUCT (mstruct), NULL); 533 534 item = g_object_new (TYPE_BROWSER_CANVAS_FKEY, "meta-struct", mstruct, NULL); 535 536 if (parent) { 537 goo_canvas_item_add_child (parent, item, -1); 538 g_object_unref (item); 539 } 540 541 g_object_set (item, "fk_constraint", fkcons, NULL); 542 543 va_start (var_args, fkcons); 544 first_property = va_arg (var_args, char*); 545 if (first_property) 546 g_object_set_valist ((GObject*) item, first_property, var_args); 547 va_end (var_args); 548 549 return item; 550 } 551