1 /* 2 * Copyright (C) 2009 - 2011 Vivien Malerba <malerba@gnome-db.org> 3 * Copyright (C) 2010 David King <davidk@openismus.com> 4 * 5 * This program is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU General Public License 7 * as published by the Free Software Foundation; either version 2 8 * of the License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 */ 19 20 #include <gtk/gtk.h> 21 #include <libgda/libgda.h> 22 #include "browser-canvas.h" 23 #include "browser-canvas-priv.h" 24 #include "browser-canvas-table.h" 25 #include "browser-canvas-column.h" 26 #include <glib/gi18n-lib.h> 27 #include <string.h> 28 29 static void browser_canvas_table_class_init (BrowserCanvasTableClass *class); 30 static void browser_canvas_table_init (BrowserCanvasTable *drag); 31 static void browser_canvas_table_dispose (GObject *object); 32 static void browser_canvas_table_finalize (GObject *object); 33 34 static void browser_canvas_table_set_property (GObject *object, 35 guint param_id, 36 const GValue *value, 37 GParamSpec *pspec); 38 static void browser_canvas_table_get_property (GObject *object, 39 guint param_id, 40 GValue *value, 41 GParamSpec *pspec); 42 43 static void browser_canvas_table_drag_data_get (BrowserCanvasItem *citem, GdkDragContext *drag_context, 44 GtkSelectionData *data, guint info, guint time); 45 static void browser_canvas_table_set_selected (BrowserCanvasItem *citem, gboolean selected); 46 47 static xmlNodePtr browser_canvas_table_serialize (BrowserCanvasItem *citem); 48 49 enum 50 { 51 PROP_0, 52 PROP_META_STRUCT, 53 PROP_TABLE, 54 PROP_MENU_FUNC 55 }; 56 57 struct _BrowserCanvasTablePrivate 58 { 59 GdaMetaStruct *mstruct; 60 GdaMetaTable *table; 61 62 /* UI building information */ 63 GSList *column_items; /* list of GooCanvasItem for the columns */ 64 GSList *other_items; /* list of GooCanvasItem for other purposes */ 65 gdouble *column_ypos; /* array for each column's Y position in this canvas group */ 66 GtkWidget *(*popup_menu_func) (BrowserCanvasTable *ce); 67 68 GooCanvasItem *selection_mark; 69 }; 70 71 /* get a pointer to the parents to be able to call their destructor */ 72 static GObjectClass *table_parent_class = NULL; 73 74 GType 75 browser_canvas_table_get_type (void) 76 { 77 static GType type = 0; 78 79 if (G_UNLIKELY (type == 0)) { 80 static const GTypeInfo info = { 81 sizeof (BrowserCanvasTableClass), 82 (GBaseInitFunc) NULL, 83 (GBaseFinalizeFunc) NULL, 84 (GClassInitFunc) browser_canvas_table_class_init, 85 NULL, 86 NULL, 87 sizeof (BrowserCanvasTable), 88 0, 89 (GInstanceInitFunc) browser_canvas_table_init, 90 0 91 }; 92 93 type = g_type_register_static (TYPE_BROWSER_CANVAS_ITEM, "BrowserCanvasTable", &info, 0); 94 } 95 96 return type; 97 } 98 99 100 static void 101 browser_canvas_table_class_init (BrowserCanvasTableClass *class) 102 { 103 GObjectClass *object_class = G_OBJECT_CLASS (class); 104 BrowserCanvasItemClass *iclass = BROWSER_CANVAS_ITEM_CLASS (class); 105 106 table_parent_class = g_type_class_peek_parent (class); 107 iclass->drag_data_get = browser_canvas_table_drag_data_get; 108 iclass->set_selected = browser_canvas_table_set_selected; 109 iclass->serialize = browser_canvas_table_serialize; 110 111 object_class->dispose = browser_canvas_table_dispose; 112 object_class->finalize = browser_canvas_table_finalize; 113 114 /* Properties */ 115 object_class->set_property = browser_canvas_table_set_property; 116 object_class->get_property = browser_canvas_table_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 124 (object_class, PROP_TABLE, 125 g_param_spec_pointer ("table", NULL, NULL, 126 (G_PARAM_READABLE | G_PARAM_WRITABLE))); 127 g_object_class_install_property 128 (object_class, PROP_MENU_FUNC, 129 g_param_spec_pointer ("popup_menu_func", "Popup menu function", 130 "Function to create a popup menu on each BrowserCanvasTable", 131 G_PARAM_WRITABLE)); 132 } 133 134 static gboolean button_press_event_cb (BrowserCanvasTable *ce, GooCanvasItem *target_item, GdkEventButton *event, 135 gpointer unused_data); 136 137 static void 138 browser_canvas_table_init (BrowserCanvasTable *table) 139 { 140 table->priv = g_new0 (BrowserCanvasTablePrivate, 1); 141 table->priv->mstruct = NULL; 142 table->priv->table = NULL; 143 table->priv->column_ypos = NULL; 144 table->priv->popup_menu_func = NULL; 145 146 table->priv->selection_mark = NULL; 147 148 g_signal_connect (G_OBJECT (table), "button-press-event", 149 G_CALLBACK (button_press_event_cb), NULL); 150 } 151 152 static void clean_items (BrowserCanvasTable *ce); 153 static void create_items (BrowserCanvasTable *ce); 154 155 static void 156 browser_canvas_table_dispose (GObject *object) 157 { 158 BrowserCanvasTable *ce; 159 160 g_return_if_fail (IS_BROWSER_CANVAS_TABLE (object)); 161 162 ce = BROWSER_CANVAS_TABLE (object); 163 164 /* REM: let the GooCanvas library destroy the items itself */ 165 ce->priv->table = NULL; 166 if (ce->priv->mstruct) { 167 g_object_unref (ce->priv->mstruct); 168 ce->priv->mstruct = NULL; 169 } 170 171 /* for the parent class */ 172 table_parent_class->dispose (object); 173 } 174 175 176 static void 177 browser_canvas_table_finalize (GObject *object) 178 { 179 BrowserCanvasTable *ce; 180 g_return_if_fail (object != NULL); 181 g_return_if_fail (IS_BROWSER_CANVAS_TABLE (object)); 182 183 ce = BROWSER_CANVAS_TABLE (object); 184 if (ce->priv) { 185 g_slist_free (ce->priv->column_items); 186 g_slist_free (ce->priv->other_items); 187 if (ce->priv->column_ypos) 188 g_free (ce->priv->column_ypos); 189 190 g_free (ce->priv); 191 ce->priv = NULL; 192 } 193 194 /* for the parent class */ 195 table_parent_class->finalize (object); 196 } 197 198 static void 199 browser_canvas_table_set_property (GObject *object, 200 guint param_id, 201 const GValue *value, 202 GParamSpec *pspec) 203 { 204 BrowserCanvasTable *ce = NULL; 205 206 ce = BROWSER_CANVAS_TABLE (object); 207 208 switch (param_id) { 209 case PROP_META_STRUCT: 210 ce->priv->mstruct = g_value_dup_object (value); 211 break; 212 case PROP_TABLE: { 213 GdaMetaTable *table; 214 table = g_value_get_pointer (value); 215 if (table && (table == ce->priv->table)) 216 return; 217 218 if (ce->priv->table) { 219 ce->priv->table = NULL; 220 clean_items (ce); 221 } 222 223 if (table) { 224 ce->priv->table = (GdaMetaTable*) table; 225 create_items (ce); 226 } 227 break; 228 } 229 case PROP_MENU_FUNC: 230 ce->priv->popup_menu_func = (GtkWidget *(*) (BrowserCanvasTable *ce)) g_value_get_pointer (value); 231 break; 232 default: 233 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); 234 break; 235 } 236 } 237 238 static void 239 browser_canvas_table_get_property (GObject *object, 240 guint param_id, 241 GValue *value, 242 GParamSpec *pspec) 243 { 244 BrowserCanvasTable *ce = NULL; 245 246 ce = BROWSER_CANVAS_TABLE (object); 247 248 switch (param_id) { 249 case PROP_META_STRUCT: 250 g_value_set_object (value, ce->priv->mstruct); 251 break; 252 case PROP_TABLE: 253 g_value_set_pointer (value, ce->priv->table); 254 break; 255 default: 256 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); 257 break; 258 } 259 } 260 261 /* 262 * destroy any existing GooCanvasItem obejcts 263 */ 264 static void 265 clean_items (BrowserCanvasTable *ce) 266 { 267 GSList *list; 268 /* destroy all the items in the group */ 269 while (ce->priv->column_items) 270 g_object_unref (G_OBJECT (ce->priv->column_items->data)); 271 272 for (list = ce->priv->other_items; list; list = list->next) 273 g_object_unref (G_OBJECT (list->data)); 274 g_slist_free (ce->priv->other_items); 275 ce->priv->other_items = NULL; 276 277 /* free the columns positions */ 278 if (ce->priv->column_ypos) { 279 g_free (ce->priv->column_ypos); 280 ce->priv->column_ypos = NULL; 281 } 282 } 283 284 /* 285 * create new GooCanvasItem objects 286 */ 287 static void 288 create_items (BrowserCanvasTable *ce) 289 { 290 GooCanvasItem *item, *frame, *title; 291 gdouble y, ysep; 292 #define HEADER_Y_PAD 3. 293 #define Y_PAD 0. 294 #define X_PAD 3. 295 #define RADIUS_X 5. 296 #define RADIUS_Y 5. 297 #define MIN_HEIGHT 70. 298 #define SELECTION_SIZE 4. 299 GooCanvasBounds border_bounds; 300 GooCanvasBounds bounds; 301 const gchar *cstr; 302 gchar *tmpstr = NULL; 303 GSList *columns, *list; 304 gint column_nb; 305 gdouble column_width; 306 307 clean_items (ce); 308 g_assert (ce->priv->table); 309 310 /* title */ 311 cstr = GDA_META_DB_OBJECT (ce->priv->table)->obj_short_name; 312 if (cstr) 313 tmpstr = g_markup_printf_escaped ("<b>%s</b>", cstr); 314 else 315 tmpstr = g_strdup_printf ("<b>%s</b>", _("No name")); 316 317 y = RADIUS_Y; 318 title = goo_canvas_text_new (GOO_CANVAS_ITEM (ce), tmpstr, 319 RADIUS_X + X_PAD, y, 320 -1, GOO_CANVAS_ANCHOR_NORTH_WEST, 321 "font", "Sans 11", 322 "use-markup", TRUE, NULL); 323 324 g_free (tmpstr); 325 goo_canvas_item_get_bounds (title, &bounds); 326 border_bounds = bounds; 327 border_bounds.x1 = 0.; 328 border_bounds.y1 = 0.; 329 y += bounds.y2 - bounds.y1 + HEADER_Y_PAD; 330 331 /* separator's placeholder */ 332 ysep = y; 333 y += HEADER_Y_PAD; 334 335 /* columns' vertical position */ 336 columns = ce->priv->table->columns; 337 ce->priv->column_ypos = g_new0 (gdouble, g_slist_length (columns) + 1); 338 339 /* columns */ 340 for (column_nb = 0, list = columns; list; list = list->next, column_nb++) { 341 ce->priv->column_ypos [column_nb] = y; 342 item = browser_canvas_column_new (GOO_CANVAS_ITEM (ce), 343 ce->priv->mstruct, 344 GDA_META_TABLE_COLUMN (list->data), 345 X_PAD, ce->priv->column_ypos [column_nb], NULL); 346 ce->priv->column_items = g_slist_append (ce->priv->column_items, item); 347 goo_canvas_item_get_bounds (item, &bounds); 348 border_bounds.x1 = MIN (border_bounds.x1, bounds.x1); 349 border_bounds.x2 = MAX (border_bounds.x2, bounds.x2); 350 border_bounds.y1 = MIN (border_bounds.y1, bounds.y1); 351 border_bounds.y2 = MAX (border_bounds.y2, bounds.y2); 352 353 y += bounds.y2 - bounds.y1 + Y_PAD; 354 } 355 if (!columns && (border_bounds.y2 - border_bounds.y1 < MIN_HEIGHT)) 356 border_bounds.y2 += MIN_HEIGHT - (border_bounds.y2 - border_bounds.y1); 357 358 /* border */ 359 column_width = border_bounds.x2 - border_bounds.x1; 360 border_bounds.y2 += RADIUS_Y; 361 border_bounds.x2 += RADIUS_X; 362 frame = goo_canvas_rect_new (GOO_CANVAS_ITEM (ce), border_bounds.x1, border_bounds.y1, 363 border_bounds.x2, border_bounds.y2, 364 "radius-x", RADIUS_X, 365 "radius-y", RADIUS_Y, 366 "fill-color", "#f8f8f8", 367 NULL); 368 ce->priv->other_items = g_slist_prepend (ce->priv->other_items, frame); 369 370 ce->priv->selection_mark = goo_canvas_rect_new (GOO_CANVAS_ITEM (ce), border_bounds.x1 - SELECTION_SIZE, 371 border_bounds.y1 - SELECTION_SIZE, 372 border_bounds.x2 + 2 * SELECTION_SIZE, 373 border_bounds.y2 + 2 * SELECTION_SIZE, 374 "radius-x", RADIUS_X, 375 "radius-y", RADIUS_Y, 376 "fill-color", "#11d155",//"#ffea08", 377 "stroke-color", "#11d155",//"#ffea08", 378 NULL); 379 g_object_set (G_OBJECT (ce->priv->selection_mark), "visibility", GOO_CANVAS_ITEM_HIDDEN, NULL); 380 381 /* title's background */ 382 gchar *cpath; 383 cpath = g_strdup_printf ("M %d %d H %d V %d H %d Z", 384 (gint) border_bounds.x1, (gint) border_bounds.y1, 385 (gint) border_bounds.x2, (gint) ysep, 386 (gint) border_bounds.x1); 387 item = goo_canvas_rect_new (GOO_CANVAS_ITEM (ce), border_bounds.x1, border_bounds.y1, 388 border_bounds.x2, ysep + RADIUS_X, 389 "clip_path", cpath, 390 "radius-x", RADIUS_X, 391 "radius-y", RADIUS_Y, 392 "fill-color", "#aaaaff", 393 NULL); 394 g_free (cpath); 395 goo_canvas_item_lower (item, NULL); 396 397 /* separator */ 398 item = goo_canvas_polyline_new_line (GOO_CANVAS_ITEM (ce), border_bounds.x1, ysep, border_bounds.x2, ysep, 399 "close-path", FALSE, 400 "line-width", .7, NULL); 401 ce->priv->other_items = g_slist_prepend (ce->priv->other_items, item); 402 403 goo_canvas_item_lower (frame, NULL); 404 goo_canvas_item_lower (ce->priv->selection_mark, NULL); 405 406 /* setting the columns' background width to be the same for all */ 407 for (list = ce->priv->column_items; list; list = list->next) 408 g_object_set (G_OBJECT (list->data), "width", column_width, NULL); 409 } 410 411 static gboolean 412 button_press_event_cb (BrowserCanvasTable *ce, G_GNUC_UNUSED GooCanvasItem *target_item, 413 GdkEventButton *event, 414 G_GNUC_UNUSED gpointer data) 415 { 416 if ((event->button == 3) && ce->priv->popup_menu_func) { 417 GtkWidget *menu; 418 menu = ce->priv->popup_menu_func (ce); 419 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, 420 NULL, NULL, ((GdkEventButton *)event)->button, 421 ((GdkEventButton *)event)->time); 422 return TRUE; 423 } 424 425 return FALSE; 426 } 427 428 /** 429 * browser_canvas_table_get_column_item 430 * @ce: a #BrowserCanvasTable object 431 * @column: a #GdaMetaTableColumn object 432 * 433 * Get the #BrowserCanvasColumn object representing @column 434 * in @ce. 435 * 436 * Returns: the corresponding #BrowserCanvasColumn 437 */ 438 BrowserCanvasColumn * 439 browser_canvas_table_get_column_item (BrowserCanvasTable *ce, GdaMetaTableColumn *column) 440 { 441 gint pos; 442 443 g_return_val_if_fail (ce && IS_BROWSER_CANVAS_TABLE (ce), NULL); 444 g_return_val_if_fail (ce->priv, NULL); 445 g_return_val_if_fail (ce->priv->table, NULL); 446 447 pos = g_slist_index (ce->priv->table->columns, column); 448 g_return_val_if_fail (pos >= 0, NULL); 449 450 return g_slist_nth_data (ce->priv->column_items, pos); 451 } 452 453 454 /** 455 * browser_canvas_table_get_column_ypos 456 * @ce: a #BrowserCanvasTable object 457 * @column: a #GdaMetaTableColumn object 458 * 459 * Get the Y position of the middle of the #BrowserCanvasColumn object representing @column 460 * in @ce, in @ce's coordinates. 461 * 462 * Returns: the Y coordinate. 463 */ 464 gdouble 465 browser_canvas_table_get_column_ypos (BrowserCanvasTable *ce, GdaMetaTableColumn *column) 466 { 467 gint pos; 468 469 g_return_val_if_fail (ce && IS_BROWSER_CANVAS_TABLE (ce), 0.); 470 g_return_val_if_fail (ce->priv, 0.); 471 g_return_val_if_fail (ce->priv->table, 0.); 472 g_return_val_if_fail (ce->priv->column_ypos, 0.); 473 474 pos = g_slist_index (ce->priv->table->columns, column); 475 g_return_val_if_fail (pos >= 0, 0.); 476 return (0.75 * ce->priv->column_ypos[pos+1] + 0.25 * ce->priv->column_ypos[pos]); 477 } 478 479 480 /** 481 * browser_canvas_table_new 482 * @parent: the parent item, or NULL. 483 * @table: a #GdaMetaTable to display 484 * @x: the x coordinate 485 * @y: the y coordinate 486 * @...: optional pairs of property names and values, and a terminating NULL. 487 * 488 * Creates a new canvas item to display the @table table 489 * 490 * Returns: a new #GooCanvasItem object 491 */ 492 GooCanvasItem * 493 browser_canvas_table_new (GooCanvasItem *parent, GdaMetaStruct *mstruct, GdaMetaTable *table, 494 gdouble x, gdouble y, ...) 495 { 496 GooCanvasItem *item; 497 const char *first_property; 498 va_list var_args; 499 500 g_return_val_if_fail (GDA_IS_META_STRUCT (mstruct), NULL); 501 502 item = g_object_new (TYPE_BROWSER_CANVAS_TABLE, "meta-struct", mstruct, 503 "allow-move", TRUE, 504 "allow-select", TRUE, NULL); 505 506 if (parent) { 507 goo_canvas_item_add_child (parent, item, -1); 508 g_object_unref (item); 509 } 510 511 g_object_set (item, "table", table, NULL); 512 513 va_start (var_args, y); 514 first_property = va_arg (var_args, char*); 515 if (first_property) 516 g_object_set_valist ((GObject*) item, first_property, var_args); 517 va_end (var_args); 518 519 goo_canvas_item_translate (item, x, y); 520 521 return item; 522 } 523 524 static void 525 browser_canvas_table_drag_data_get (BrowserCanvasItem *citem, G_GNUC_UNUSED GdkDragContext *drag_context, 526 GtkSelectionData *data, G_GNUC_UNUSED guint info, 527 G_GNUC_UNUSED guint time) 528 { 529 BrowserCanvasTable *ctable; 530 531 ctable = BROWSER_CANVAS_TABLE (citem); 532 if (!ctable->priv->table) 533 return; 534 535 GdaMetaDbObject *dbo; 536 gchar *str, *tmp1, *tmp2, *tmp3; 537 538 dbo = GDA_META_DB_OBJECT (ctable->priv->table); 539 tmp1 = gda_rfc1738_encode (dbo->obj_schema); 540 tmp2 = gda_rfc1738_encode (dbo->obj_name); 541 tmp3 = gda_rfc1738_encode (dbo->obj_short_name); 542 str = g_strdup_printf ("OBJ_TYPE=table;OBJ_SCHEMA=%s;OBJ_NAME=%s;OBJ_SHORT_NAME=%s", tmp1, tmp2, tmp3); 543 g_free (tmp1); 544 g_free (tmp2); 545 g_free (tmp3); 546 gtk_selection_data_set (data, gtk_selection_data_get_target (data), 8, (guchar*) str, strlen (str)); 547 g_free (str); 548 } 549 550 static void 551 browser_canvas_table_set_selected (BrowserCanvasItem *citem, gboolean selected) 552 { 553 g_object_set (G_OBJECT (BROWSER_CANVAS_TABLE (citem)->priv->selection_mark), 554 "visibility", selected ? GOO_CANVAS_ITEM_VISIBLE : GOO_CANVAS_ITEM_HIDDEN, NULL); 555 } 556 557 static xmlNodePtr 558 browser_canvas_table_serialize (BrowserCanvasItem *citem) 559 { 560 BrowserCanvasTable *ctable; 561 562 ctable = BROWSER_CANVAS_TABLE (citem); 563 if (!ctable->priv->table) 564 return NULL; 565 566 GdaMetaDbObject *dbo; 567 xmlNodePtr node; 568 GooCanvasBounds bounds; 569 gchar *str; 570 571 dbo = GDA_META_DB_OBJECT (ctable->priv->table); 572 node = xmlNewNode (NULL, BAD_CAST "table"); 573 xmlSetProp (node, BAD_CAST "schema", BAD_CAST (dbo->obj_schema)); 574 xmlSetProp (node, BAD_CAST "name", BAD_CAST (dbo->obj_name)); 575 goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (citem), &bounds); 576 str = g_strdup_printf ("%.1f", bounds.x1); 577 xmlSetProp (node, BAD_CAST "x", BAD_CAST str); 578 g_free (str); 579 str = g_strdup_printf ("%.1f", bounds.y1); 580 xmlSetProp (node, BAD_CAST "y", BAD_CAST str); 581 g_free (str); 582 583 return node; 584 } 585 586 /** 587 * browser_canvas_table_get_anchor_bounds 588 * 589 * Get the bounds to be used to compute anchors, ie. without the selection mark or any other 590 * artefact not part of the table's rectangle. 591 */ 592 void 593 browser_canvas_table_get_anchor_bounds (BrowserCanvasTable *ce, GooCanvasBounds *bounds) 594 { 595 g_return_if_fail (IS_BROWSER_CANVAS_TABLE (ce)); 596 g_return_if_fail (bounds); 597 598 goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (ce), bounds); 599 bounds->x1 += SELECTION_SIZE; 600 bounds->y1 += SELECTION_SIZE; 601 bounds->x2 -= SELECTION_SIZE; 602 bounds->y2 -= SELECTION_SIZE; 603 } 604