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 Daniel Espinosa <esodan@gmail.com>
5 * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #include <glib/gi18n-lib.h>
23 #include <string.h>
24 #include <gtk/gtk.h>
25 #include <gdk/gdkkeysyms.h>
26 #include <libgda/gda-tree.h>
27 #include "table-info.h"
28 #include "table-columns.h"
29 #include <libgda-ui/gdaui-tree-store.h>
30 #include "../tool-utils.h"
31 #include "../support.h"
32 #include "../gdaui-bar.h"
33 #include "mgr-columns.h"
34 #include "schema-browser-perspective.h"
35 #include "../browser-window.h"
36 #include "../common/fk-declare.h"
37 #ifdef HAVE_LDAP
38 #include "../ldap-browser/ldap-browser-perspective.h"
39 #endif
40 #include <libgda/gda-debug-macros.h>
41
42 struct _TableColumnsPrivate {
43 BrowserConnection *bcnc;
44 TableInfo *tinfo;
45 GdaTree *columns_tree;
46 guint idle_update_columns;
47
48 GtkTextBuffer *constraints;
49 gboolean hovering_over_link;
50 #ifdef HAVE_LDAP
51 GtkTextBuffer *ldap_def;
52 GtkWidget *ldap_header;
53 GtkWidget *ldap_text;
54 gboolean ldap_props_shown;
55 #endif
56 };
57
58 static void table_columns_class_init (TableColumnsClass *klass);
59 static void table_columns_init (TableColumns *tcolumns, TableColumnsClass *klass);
60 static void table_columns_dispose (GObject *object);
61 static void table_columns_show_all (GtkWidget *widget);
62
63 static void meta_changed_cb (BrowserConnection *bcnc, GdaMetaStruct *mstruct, TableColumns *tcolumns);
64
65 static GObjectClass *parent_class = NULL;
66
67
68 /*
69 * TableColumns class implementation
70 */
71
72 static void
table_columns_class_init(TableColumnsClass * klass)73 table_columns_class_init (TableColumnsClass *klass)
74 {
75 GObjectClass *object_class = G_OBJECT_CLASS (klass);
76
77 parent_class = g_type_class_peek_parent (klass);
78
79 object_class->dispose = table_columns_dispose;
80 GTK_WIDGET_CLASS (klass)->show_all = table_columns_show_all;
81 }
82
83
84 static void
table_columns_init(TableColumns * tcolumns,G_GNUC_UNUSED TableColumnsClass * klass)85 table_columns_init (TableColumns *tcolumns, G_GNUC_UNUSED TableColumnsClass *klass)
86 {
87 tcolumns->priv = g_new0 (TableColumnsPrivate, 1);
88 tcolumns->priv->idle_update_columns = 0;
89 tcolumns->priv->hovering_over_link = FALSE;
90
91 gtk_orientable_set_orientation (GTK_ORIENTABLE (tcolumns), GTK_ORIENTATION_VERTICAL);
92 }
93
94 static void
table_columns_dispose(GObject * object)95 table_columns_dispose (GObject *object)
96 {
97 TableColumns *tcolumns = (TableColumns *) object;
98
99 /* free memory */
100 if (tcolumns->priv) {
101 if (tcolumns->priv->idle_update_columns)
102 g_source_remove (tcolumns->priv->idle_update_columns);
103 if (tcolumns->priv->columns_tree)
104 g_object_unref (tcolumns->priv->columns_tree);
105 if (tcolumns->priv->bcnc) {
106 g_signal_handlers_disconnect_by_func (tcolumns->priv->bcnc,
107 G_CALLBACK (meta_changed_cb), tcolumns);
108 g_object_unref (tcolumns->priv->bcnc);
109 }
110 g_free (tcolumns->priv);
111 tcolumns->priv = NULL;
112 }
113
114 parent_class->dispose (object);
115 }
116
117 static void
table_columns_show_all(GtkWidget * widget)118 table_columns_show_all (GtkWidget *widget)
119 {
120 TableColumns *tcolumns = (TableColumns *) widget;
121 GTK_WIDGET_CLASS (parent_class)->show_all (widget);
122 #ifdef HAVE_LDAP
123 if (browser_connection_is_ldap (tcolumns->priv->bcnc)) {
124 if (! tcolumns->priv->ldap_props_shown) {
125 gtk_widget_hide (tcolumns->priv->ldap_header);
126 gtk_widget_hide (tcolumns->priv->ldap_text);
127 }
128 }
129 #endif
130 }
131
132 GType
table_columns_get_type(void)133 table_columns_get_type (void)
134 {
135 static GType type = 0;
136
137 if (G_UNLIKELY (type == 0)) {
138 static const GTypeInfo columns = {
139 sizeof (TableColumnsClass),
140 (GBaseInitFunc) NULL,
141 (GBaseFinalizeFunc) NULL,
142 (GClassInitFunc) table_columns_class_init,
143 NULL,
144 NULL,
145 sizeof (TableColumns),
146 0,
147 (GInstanceInitFunc) table_columns_init,
148 0
149 };
150 type = g_type_register_static (GTK_TYPE_BOX, "TableColumns", &columns, 0);
151 }
152 return type;
153 }
154
155 static gboolean
idle_update_columns(TableColumns * tcolumns)156 idle_update_columns (TableColumns *tcolumns)
157 {
158 gboolean done;
159 GError *lerror = NULL;
160 done = gda_tree_update_all (tcolumns->priv->columns_tree, &lerror);
161 if (!done) {
162 if (lerror &&
163 (lerror->domain == MGR_COLUMNS_ERROR) &&
164 ((lerror->code == MGR_COLUMNS_TABLE_NOT_FOUND) ||
165 (lerror->code == MGR_COLUMNS_WRONG_OBJ_TYPE)))
166 done = TRUE;
167 }
168 if (done)
169 tcolumns->priv->idle_update_columns = 0;
170 else
171 tcolumns->priv->idle_update_columns = g_timeout_add_seconds (1, (GSourceFunc) idle_update_columns,
172 tcolumns);
173 return FALSE;
174 }
175
176 static gboolean key_press_event (GtkWidget *text_view, GdkEventKey *event, TableColumns *tcolumns);
177 static gboolean event_after (GtkWidget *text_view, GdkEvent *ev, TableColumns *tcolumns);
178 static gboolean motion_notify_event (GtkWidget *text_view, GdkEventMotion *event, TableColumns *tcolumns);
179 static gboolean visibility_notify_event (GtkWidget *text_view, GdkEventVisibility *event, TableColumns *tcolumns);
180 static GSList *build_reverse_depend_list (GdaMetaStruct *mstruct, GdaMetaTable *mtable);
181
182 static void
meta_changed_cb(G_GNUC_UNUSED BrowserConnection * bcnc,GdaMetaStruct * mstruct,TableColumns * tcolumns)183 meta_changed_cb (G_GNUC_UNUSED BrowserConnection *bcnc, GdaMetaStruct *mstruct, TableColumns *tcolumns)
184 {
185 GtkTextBuffer *tbuffer;
186 GtkTextIter start, end;
187
188 /* cleanups */
189 #ifdef HAVE_LDAP
190 if (browser_connection_is_ldap (tcolumns->priv->bcnc)) {
191 tbuffer = tcolumns->priv->ldap_def;
192 gtk_text_buffer_get_start_iter (tbuffer, &start);
193 gtk_text_buffer_get_end_iter (tbuffer, &end);
194 gtk_text_buffer_delete (tbuffer, &start, &end);
195 }
196 #endif
197 tbuffer = tcolumns->priv->constraints;
198 gtk_text_buffer_get_start_iter (tbuffer, &start);
199 gtk_text_buffer_get_end_iter (tbuffer, &end);
200 gtk_text_buffer_delete (tbuffer, &start, &end);
201
202 /* update column's tree */
203 if (! gda_tree_update_all (tcolumns->priv->columns_tree, NULL)) {
204 if (tcolumns->priv->idle_update_columns == 0)
205 tcolumns->priv->idle_update_columns = g_idle_add ((GSourceFunc) idle_update_columns, tcolumns);
206 }
207 else {
208 /* constraints descr. update */
209 GdaMetaDbObject *dbo;
210 GValue *schema_v = NULL, *name_v, *catalog_v = NULL;
211 const gchar *str;
212
213 str = table_info_get_table_schema (tcolumns->priv->tinfo);
214 if (str)
215 g_value_set_string ((schema_v = gda_value_new (G_TYPE_STRING)), str);
216 str = table_info_get_table_name (tcolumns->priv->tinfo);
217 g_value_set_string ((name_v = gda_value_new (G_TYPE_STRING)), str);
218 dbo = gda_meta_struct_get_db_object (mstruct, NULL, schema_v, name_v);
219
220 if (dbo) {
221 GdaMetaTable *mtable = GDA_META_TABLE (dbo);
222 GtkTextIter current;
223 gtk_text_buffer_get_start_iter (tbuffer, ¤t);
224
225 /* Primary key */
226 if (mtable->pk_cols_nb > 0) {
227 gint i;
228
229 gtk_text_buffer_insert_with_tags_by_name (tbuffer,
230 ¤t,
231 _("Primary key"), -1,
232 "section", NULL);
233 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
234 for (i = 0; i < mtable->pk_cols_nb; i++) {
235 GdaMetaTableColumn *col;
236 if (i > 0)
237 gtk_text_buffer_insert (tbuffer, ¤t, ", ", -1);
238 col = g_slist_nth_data (mtable->columns, mtable->pk_cols_array [i]);
239 gtk_text_buffer_insert (tbuffer, ¤t,
240 col ? col->column_name : "???", -1);
241 }
242 gtk_text_buffer_insert (tbuffer, ¤t, "\n\n", -1);
243 }
244
245 /* Foreign keys */
246 GSList *list;
247 for (list = mtable->fk_list; list; list = list->next) {
248 GdaMetaTableForeignKey *fk = GDA_META_TABLE_FOREIGN_KEY (list->data);
249 GdaMetaDbObject *fdbo = fk->depend_on;
250 gint i;
251 gchar *str;
252
253 if (fdbo->obj_type == GDA_META_DB_UNKNOWN) {
254 GValue *v1, *v2, *v3;
255 g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)),
256 fdbo->obj_catalog);
257 g_value_set_string ((v2 = gda_value_new (G_TYPE_STRING)),
258 fdbo->obj_schema);
259 g_value_set_string ((v3 = gda_value_new (G_TYPE_STRING)),
260 fdbo->obj_name);
261 gda_meta_struct_complement (mstruct,
262 GDA_META_DB_TABLE, v1, v2, v3, NULL);
263 gda_value_free (v1);
264 gda_value_free (v2);
265 gda_value_free (v3);
266 continue;
267 }
268 if (GDA_META_TABLE_FOREIGN_KEY_IS_DECLARED (fk))
269 str = g_strdup_printf (_("Declared foreign key '%s' on "),
270 fk->fk_name);
271 else
272 str = g_strdup_printf (_("Foreign key '%s' on "),
273 fk->fk_name);
274 gtk_text_buffer_insert_with_tags_by_name (tbuffer,
275 ¤t, str, -1,
276 "section", NULL);
277 g_free (str);
278 if (fdbo->obj_type == GDA_META_DB_TABLE) {
279 /* insert link to table name */
280 GtkTextTag *tag;
281 tag = gtk_text_buffer_create_tag (tbuffer, NULL,
282 "foreground", "blue",
283 "weight", PANGO_WEIGHT_BOLD,
284 "underline", PANGO_UNDERLINE_SINGLE,
285 NULL);
286 g_object_set_data_full (G_OBJECT (tag), "table_schema",
287 g_strdup (fdbo->obj_schema), g_free);
288 g_object_set_data_full (G_OBJECT (tag), "table_name",
289 g_strdup (fdbo->obj_name), g_free);
290 g_object_set_data_full (G_OBJECT (tag), "table_short_name",
291 g_strdup (fdbo->obj_short_name), g_free);
292 gtk_text_buffer_insert_with_tags (tbuffer, ¤t,
293 fdbo->obj_short_name, -1, tag, NULL);
294 if (GDA_META_TABLE_FOREIGN_KEY_IS_DECLARED (fk)) {
295 tag = gtk_text_buffer_create_tag (tbuffer, NULL,
296 "foreground", "blue",
297 "underline",
298 PANGO_UNDERLINE_SINGLE,
299 NULL);
300 g_object_set_data_full (G_OBJECT (tag), "fk_name",
301 g_strdup (fk->fk_name),
302 g_free);
303 gtk_text_buffer_insert (tbuffer, ¤t, "\n(", -1);
304 gtk_text_buffer_insert_with_tags (tbuffer, ¤t,
305 _("Remove"), -1, tag, NULL);
306 gtk_text_buffer_insert (tbuffer, ¤t, ")", -1);
307 }
308 }
309 else
310 gtk_text_buffer_insert_with_tags_by_name (tbuffer,
311 ¤t,
312 fdbo->obj_short_name,
313 -1,
314 "section", NULL);
315
316 for (i = 0; i < fk->cols_nb; i++) {
317 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
318 gtk_text_buffer_insert (tbuffer, ¤t,
319 fk->fk_names_array [i], -1);
320 gtk_text_buffer_insert (tbuffer, ¤t, " ", -1);
321 gtk_text_buffer_insert_pixbuf (tbuffer, ¤t,
322 browser_get_pixbuf_icon (BROWSER_ICON_REFERENCE));
323
324 gtk_text_buffer_insert (tbuffer, ¤t, " ", -1);
325 gtk_text_buffer_insert (tbuffer, ¤t,
326 fk->ref_pk_names_array [i], -1);
327
328 if (fdbo->obj_type == GDA_META_DB_TABLE) {
329 GdaMetaTableColumn *fkcol, *refcol;
330 fkcol = g_slist_nth_data (mtable->columns,
331 fk->fk_cols_array[i] - 1);
332 refcol = g_slist_nth_data (GDA_META_TABLE (fdbo)->columns,
333 fk->ref_pk_cols_array[i] - 1);
334 if (fkcol && refcol &&
335 (fkcol->gtype != refcol->gtype)) {
336 /* incompatible FK types */
337 gchar *text;
338 text = g_strdup_printf (_("incompatible types: '%s' for the foreign key and '%s' for the referenced primary key"),
339 fkcol->column_type,
340 refcol->column_type);
341 gtk_text_buffer_insert (tbuffer, ¤t, " ", -1);
342 gtk_text_buffer_insert_with_tags_by_name (tbuffer,
343 ¤t,
344 text,
345 -1,
346 "warning", NULL);
347 g_free (text);
348 }
349 }
350 }
351
352 GdaMetaForeignKeyPolicy policy;
353 policy = GDA_META_TABLE_FOREIGN_KEY_ON_UPDATE_POLICY (fk);
354 if (policy != GDA_META_FOREIGN_KEY_UNKNOWN) {
355 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
356 /* To translators: the UPDATE is an SQL operation type */
357 gtk_text_buffer_insert (tbuffer, ¤t, _("Policy on UPDATE"), -1);
358 gtk_text_buffer_insert (tbuffer, ¤t, ": ", -1);
359 gtk_text_buffer_insert (tbuffer, ¤t,
360 gda_tools_utils_fk_policy_to_string (policy),
361 -1);
362 }
363 policy = GDA_META_TABLE_FOREIGN_KEY_ON_DELETE_POLICY (fk);
364 if (policy != GDA_META_FOREIGN_KEY_UNKNOWN) {
365 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
366 /* To translators: the DELETE is an SQL operation type */
367 gtk_text_buffer_insert (tbuffer, ¤t, _("Policy on DELETE"), -1);
368 gtk_text_buffer_insert (tbuffer, ¤t, ": ", -1);
369 gtk_text_buffer_insert (tbuffer, ¤t,
370 gda_tools_utils_fk_policy_to_string (policy),
371 -1);
372 }
373
374 gtk_text_buffer_insert (tbuffer, ¤t, "\n\n", -1);
375 }
376
377 /* Unique constraints */
378 GdaDataModel *model;
379 GError *error = NULL;
380 g_value_set_string ((catalog_v = gda_value_new (G_TYPE_STRING)), dbo->obj_catalog);
381 model = gda_meta_store_extract (browser_connection_get_meta_store (tcolumns->priv->bcnc),
382 "SELECT tc.constraint_name, k.column_name FROM _key_column_usage k INNER JOIN _table_constraints tc ON (k.table_catalog=tc.table_catalog AND k.table_schema=tc.table_schema AND k.table_name=tc.table_name AND k.constraint_name=tc.constraint_name) WHERE tc.constraint_type='UNIQUE' AND k.table_catalog = ##catalog::string AND k.table_schema = ##schema::string AND k.table_name = ##tname::string ORDER by k.ordinal_position", &error,
383 "catalog", catalog_v,
384 "schema", schema_v,
385 "tname", name_v, NULL);
386 if (!model) {
387 g_warning (_("Could not compute table's UNIQUE constraints for %s.%s.%s"),
388 g_value_get_string (catalog_v),
389 g_value_get_string (schema_v),
390 g_value_get_string (name_v));
391 g_error_free (error);
392 }
393 else {
394 gint nrows;
395
396 nrows = gda_data_model_get_n_rows (model);
397 if (nrows > 0) {
398 gint i;
399 GValue *current_value = NULL;
400 const GValue *cvalue;
401 for (i = 0; i < nrows; i++) {
402 cvalue = gda_data_model_get_value_at (model, 0, i, NULL);
403 if (!current_value ||
404 gda_value_compare (cvalue, current_value)) {
405 if (current_value)
406 gtk_text_buffer_insert (tbuffer, ¤t,
407 "\n\n", -1);
408 gtk_text_buffer_insert_with_tags_by_name (tbuffer,
409 ¤t,
410 _("Unique constraint"), -1,
411 "section", NULL);
412 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
413 current_value = gda_value_copy (cvalue);
414 }
415 else
416 gtk_text_buffer_insert (tbuffer, ¤t, ", ", -1);
417 cvalue = gda_data_model_get_value_at (model, 1, i, NULL);
418 gtk_text_buffer_insert (tbuffer, ¤t,
419 g_value_get_string (cvalue), -1);
420 }
421 gtk_text_buffer_insert (tbuffer, ¤t, "\n\n", -1);
422 }
423 g_object_unref (model);
424 }
425
426 /* reverse FK constraints */
427 GSList *rev_list;
428 rev_list = build_reverse_depend_list (mstruct, mtable);
429 if (rev_list) {
430 gtk_text_buffer_insert_with_tags_by_name (tbuffer,
431 ¤t,
432 _("Tables referencing this one"), -1,
433 "section", NULL);
434 for (list = rev_list; list; list = list->next) {
435 GdaMetaDbObject *ddbo;
436 ddbo = GDA_META_DB_OBJECT (list->data);
437 if (ddbo->obj_type == GDA_META_DB_TABLE) {
438 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
439 GtkTextTag *tag;
440 tag = gtk_text_buffer_create_tag (tbuffer, NULL,
441 "foreground", "blue",
442 "weight", PANGO_WEIGHT_NORMAL,
443 "underline", PANGO_UNDERLINE_SINGLE,
444 NULL);
445 g_object_set_data_full (G_OBJECT (tag), "table_schema",
446 g_strdup (ddbo->obj_schema), g_free);
447 g_object_set_data_full (G_OBJECT (tag), "table_name",
448 g_strdup (ddbo->obj_name), g_free);
449 g_object_set_data_full (G_OBJECT (tag), "table_short_name",
450 g_strdup (ddbo->obj_short_name), g_free);
451 gtk_text_buffer_insert_with_tags (tbuffer, ¤t,
452 ddbo->obj_short_name,
453 -1, tag, NULL);
454 }
455 }
456 g_slist_free (rev_list);
457 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
458 }
459
460 #ifdef HAVE_LDAP
461 if (browser_connection_is_ldap (tcolumns->priv->bcnc)) {
462 const gchar *base_dn, *filter, *attributes, *scope_str;
463 GdaLdapSearchScope scope;
464 tbuffer = tcolumns->priv->ldap_def;
465 gtk_text_buffer_get_start_iter (tbuffer, ¤t);
466 if (browser_connection_describe_table (tcolumns->priv->bcnc, dbo->obj_name,
467 &base_dn, &filter,
468 &attributes, &scope, NULL)) {
469 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t,
470 "BASE: ", -1,
471 "section", NULL);
472 if (base_dn) {
473 GtkTextTag *tag;
474 tag = gtk_text_buffer_create_tag (tbuffer, NULL,
475 "foreground", "blue",
476 "weight", PANGO_WEIGHT_NORMAL,
477 "underline", PANGO_UNDERLINE_SINGLE,
478 NULL);
479 g_object_set_data_full (G_OBJECT (tag), "dn",
480 g_strdup (base_dn), g_free);
481
482 gtk_text_buffer_insert_with_tags (tbuffer, ¤t, base_dn, -1,
483 tag, NULL);
484 }
485
486 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
487 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t,
488 "FILTER: ", -1,
489 "section", NULL);
490 if (filter)
491 gtk_text_buffer_insert (tbuffer, ¤t, filter, -1);
492
493 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
494 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t,
495 "ATTRIBUTES: ", -1,
496 "section", NULL);
497 if (attributes)
498 gtk_text_buffer_insert (tbuffer, ¤t, attributes, -1);
499
500 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
501 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t,
502 "SCOPE: ", -1,
503 "section", NULL);
504
505 switch (scope) {
506 case GDA_LDAP_SEARCH_BASE:
507 scope_str = "base";
508 break;
509 case GDA_LDAP_SEARCH_ONELEVEL:
510 scope_str = "onelevel";
511 break;
512 case GDA_LDAP_SEARCH_SUBTREE:
513 scope_str = "subtree";
514 break;
515 default:
516 TO_IMPLEMENT;
517 scope_str = _("Unknown");
518 break;
519 }
520 gtk_text_buffer_insert (tbuffer, ¤t, scope_str, -1);
521
522 tcolumns->priv->ldap_props_shown = TRUE;
523 gtk_widget_show (tcolumns->priv->ldap_header);
524 gtk_widget_show (tcolumns->priv->ldap_text);
525 }
526 else {
527 tcolumns->priv->ldap_props_shown = FALSE;
528 gtk_widget_hide (tcolumns->priv->ldap_header);
529 gtk_widget_hide (tcolumns->priv->ldap_text);
530 }
531 }
532 #endif
533
534 }
535
536 if (schema_v)
537 gda_value_free (schema_v);
538 if (catalog_v)
539 gda_value_free (catalog_v);
540 gda_value_free (name_v);
541 }
542 }
543
544 /*
545 * builds a list of #GdaMetaDbObject where dbo->depend_list includes @mtable in @mstruct
546 * Returns: (transfer container): a new list
547 */
548 static GSList *
build_reverse_depend_list(GdaMetaStruct * mstruct,GdaMetaTable * mtable)549 build_reverse_depend_list (GdaMetaStruct *mstruct, GdaMetaTable *mtable)
550 {
551 GSList *retlist = NULL;
552 GSList *list, *alldbo;
553 alldbo = gda_meta_struct_get_all_db_objects (mstruct);
554 for (list = alldbo; list; list = list->next) {
555 if (g_slist_find (GDA_META_DB_OBJECT (list->data)->depend_list, mtable))
556 retlist = g_slist_prepend (retlist, list->data);
557 }
558 g_slist_free (alldbo);
559 return retlist;
560 }
561
562 /**
563 * table_columns_new
564 *
565 * Returns: a new #GtkWidget
566 */
567 GtkWidget *
table_columns_new(TableInfo * tinfo)568 table_columns_new (TableInfo *tinfo)
569 {
570 TableColumns *tcolumns;
571 GdaTreeManager *manager;
572
573 enum {
574 COLUMN_NAME,
575 COLUMN_TYPE,
576 COLUMN_NOTNULL,
577 COLUMN_DEFAULT,
578 COLUMN_ICON,
579 COLUMN_DETAILS
580 };
581
582 g_return_val_if_fail (IS_TABLE_INFO (tinfo), NULL);
583
584 tcolumns = TABLE_COLUMNS (g_object_new (TABLE_COLUMNS_TYPE, NULL));
585
586 tcolumns->priv->tinfo = tinfo;
587 tcolumns->priv->bcnc = g_object_ref (table_info_get_connection (tinfo));
588 g_signal_connect (tcolumns->priv->bcnc, "meta-changed",
589 G_CALLBACK (meta_changed_cb), tcolumns);
590
591 /* main container */
592 GtkWidget *paned;
593 paned = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
594 gtk_box_pack_start (GTK_BOX (tcolumns), paned, TRUE, TRUE, 0);
595 gtk_widget_show (paned);
596
597 /*
598 * Columns
599 */
600 tcolumns->priv->columns_tree = gda_tree_new ();
601 manager = mgr_columns_new (tcolumns->priv->bcnc, table_info_get_table_schema (tinfo),
602 table_info_get_table_name (tinfo));
603 gda_tree_add_manager (tcolumns->priv->columns_tree, manager);
604 g_object_unref (manager);
605
606 /* update the tree's contents */
607 if (! gda_tree_update_all (tcolumns->priv->columns_tree, NULL)) {
608 if (tcolumns->priv->idle_update_columns == 0)
609 tcolumns->priv->idle_update_columns = g_idle_add ((GSourceFunc) idle_update_columns, tcolumns);
610 }
611
612 /* tree model */
613 GtkTreeModel *model;
614 GtkWidget *treeview;
615 GtkCellRenderer *renderer;
616 GtkTreeViewColumn *column;
617
618 model = gdaui_tree_store_new (tcolumns->priv->columns_tree, 6,
619 G_TYPE_STRING, MGR_COLUMNS_COL_NAME_ATT_NAME,
620 G_TYPE_STRING, MGR_COLUMNS_COL_TYPE_ATT_NAME,
621 G_TYPE_BOOLEAN, MGR_COLUMNS_COL_NOTNULL_ATT_NAME,
622 G_TYPE_STRING, MGR_COLUMNS_COL_DEFAULT_ATT_NAME,
623 G_TYPE_OBJECT, "icon",
624 G_TYPE_STRING, MGR_COLUMNS_COL_DETAILS);
625 treeview = browser_make_tree_view (model);
626 g_object_unref (model);
627
628 /* Colum: Name */
629 column = gtk_tree_view_column_new ();
630 gtk_tree_view_column_set_title (column, _("Column Name"));
631
632 renderer = gtk_cell_renderer_pixbuf_new ();
633 gtk_tree_view_column_pack_start (column, renderer, FALSE);
634 gtk_tree_view_column_add_attribute (column, renderer, "pixbuf", COLUMN_ICON);
635
636 renderer = gtk_cell_renderer_text_new ();
637 gtk_tree_view_column_pack_start (column, renderer, TRUE);
638 gtk_tree_view_column_add_attribute (column, renderer, "markup", COLUMN_NAME);
639
640 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
641
642 /* Colum: Type */
643 renderer = gtk_cell_renderer_text_new ();
644 /* To translators: "Type" is the data type of a table's column */
645 column = gtk_tree_view_column_new_with_attributes (_("Type"), renderer,
646 "text", COLUMN_TYPE, NULL);
647 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
648
649 /* Colum: Nullok */
650 renderer = gtk_cell_renderer_toggle_new ();
651 /* To translators: "Not NULL?" is a table's column's attribute. The NULL term should not be translated as it refers to the SQL NULL value */
652 column = gtk_tree_view_column_new_with_attributes (_("Not NULL?"), renderer,
653 "active", COLUMN_NOTNULL, NULL);
654 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
655
656 /* Colum: Default */
657 renderer = gtk_cell_renderer_text_new ();
658 /* To translators: "Default value" is a table's column's attribute */
659 column = gtk_tree_view_column_new_with_attributes (_("Default value"), renderer,
660 "text", COLUMN_DEFAULT, NULL);
661 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
662
663 /* Colum: Details */
664 renderer = gtk_cell_renderer_text_new ();
665 /* To translators: "Details" is a table's column's attribute */
666 column = gtk_tree_view_column_new_with_attributes (_("Details"), renderer,
667 "text", COLUMN_DETAILS, NULL);
668 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
669
670 /* scrolled window packing */
671 GtkWidget *sw;
672 sw = gtk_scrolled_window_new (NULL, NULL);
673 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_NONE);
674 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
675 GTK_POLICY_NEVER,
676 GTK_POLICY_AUTOMATIC);
677 gtk_container_add (GTK_CONTAINER (sw), treeview);
678 gtk_paned_pack1 (GTK_PANED (paned), sw, TRUE, FALSE);
679
680 /* Paned, part 2 */
681 GtkWidget *vbox;
682 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
683 gtk_paned_pack2 (GTK_PANED (paned), vbox, TRUE, TRUE);
684
685 #ifdef HAVE_LDAP
686 if (browser_connection_is_ldap (tcolumns->priv->bcnc)) {
687 GtkWidget *label;
688 gchar *str;
689
690 str = g_strdup_printf ("<b>%s</b>", _("LDAP virtual table definition"));
691 label = gdaui_bar_new (str);
692 g_free (str);
693 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
694 tcolumns->priv->ldap_header = label;
695
696 sw = gtk_scrolled_window_new (NULL, NULL);
697 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_NONE);
698 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
699 GTK_POLICY_NEVER,
700 GTK_POLICY_AUTOMATIC);
701 gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
702 tcolumns->priv->ldap_text = sw;
703
704 GtkWidget *textview;
705 textview = gtk_text_view_new ();
706 gtk_container_add (GTK_CONTAINER (sw), textview);
707 gtk_text_view_set_left_margin (GTK_TEXT_VIEW (textview), 5);
708 gtk_text_view_set_right_margin (GTK_TEXT_VIEW (textview), 5);
709 gtk_text_view_set_editable (GTK_TEXT_VIEW (textview), FALSE);
710 tcolumns->priv->ldap_def = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
711 gtk_text_buffer_set_text (tcolumns->priv->ldap_def, "aa", -1);
712
713 gtk_text_buffer_create_tag (tcolumns->priv->ldap_def, "section",
714 "weight", PANGO_WEIGHT_BOLD,
715 "foreground", "blue", NULL);
716
717 gtk_text_buffer_create_tag (tcolumns->priv->ldap_def, "warning",
718 "foreground", "red", NULL);
719
720 g_signal_connect (textview, "key-press-event",
721 G_CALLBACK (key_press_event), tcolumns);
722 g_signal_connect (textview, "event-after",
723 G_CALLBACK (event_after), tcolumns);
724 g_signal_connect (textview, "motion-notify-event",
725 G_CALLBACK (motion_notify_event), tcolumns);
726 g_signal_connect (textview, "visibility-notify-event",
727 G_CALLBACK (visibility_notify_event), tcolumns);
728 }
729 #endif
730
731 /*
732 * Constraints
733 */
734 GtkWidget *label;
735 gchar *str;
736
737 str = g_strdup_printf ("<b>%s</b>", _("Constraints and integrity rules"));
738 label = gdaui_bar_new (str);
739 g_free (str);
740 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
741
742 sw = gtk_scrolled_window_new (NULL, NULL);
743 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_NONE);
744 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
745 GTK_POLICY_NEVER,
746 GTK_POLICY_AUTOMATIC);
747 gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
748
749 GtkWidget *textview;
750 textview = gtk_text_view_new ();
751 gtk_container_add (GTK_CONTAINER (sw), textview);
752 gtk_text_view_set_left_margin (GTK_TEXT_VIEW (textview), 5);
753 gtk_text_view_set_right_margin (GTK_TEXT_VIEW (textview), 5);
754 gtk_text_view_set_editable (GTK_TEXT_VIEW (textview), FALSE);
755 tcolumns->priv->constraints = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
756 gtk_text_buffer_set_text (tcolumns->priv->constraints, "aa", -1);
757 gtk_widget_show_all (vbox);
758
759 gtk_text_buffer_create_tag (tcolumns->priv->constraints, "section",
760 "weight", PANGO_WEIGHT_BOLD,
761 "foreground", "blue", NULL);
762
763 gtk_text_buffer_create_tag (tcolumns->priv->constraints, "warning",
764 "foreground", "red", NULL);
765
766 g_signal_connect (textview, "key-press-event",
767 G_CALLBACK (key_press_event), tcolumns);
768 g_signal_connect (textview, "event-after",
769 G_CALLBACK (event_after), tcolumns);
770 g_signal_connect (textview, "motion-notify-event",
771 G_CALLBACK (motion_notify_event), tcolumns);
772 g_signal_connect (textview, "visibility-notify-event",
773 G_CALLBACK (visibility_notify_event), tcolumns);
774
775 gtk_widget_show_all (vbox);
776
777 /*
778 * initial update
779 */
780 GdaMetaStruct *mstruct;
781 mstruct = browser_connection_get_meta_struct (tcolumns->priv->bcnc);
782 if (mstruct)
783 meta_changed_cb (tcolumns->priv->bcnc, mstruct, tcolumns);
784
785 return (GtkWidget*) tcolumns;
786 }
787
788 static GdkCursor *hand_cursor = NULL;
789 static GdkCursor *regular_cursor = NULL;
790
791 /* Looks at all tags covering the position (x, y) in the text view,
792 * and if one of them is a link, change the cursor to the "hands" cursor
793 * typically used by web browsers.
794 */
795 static void
set_cursor_if_appropriate(GtkTextView * text_view,gint x,gint y,TableColumns * tcolumns)796 set_cursor_if_appropriate (GtkTextView *text_view, gint x, gint y, TableColumns *tcolumns)
797 {
798 GSList *tags = NULL, *tagp = NULL;
799 GtkTextIter iter;
800 gboolean hovering = FALSE;
801
802 gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
803
804 tags = gtk_text_iter_get_tags (&iter);
805 for (tagp = tags; tagp != NULL; tagp = tagp->next) {
806 GtkTextTag *tag = tagp->data;
807
808 if (g_object_get_data (G_OBJECT (tag), "table_name") ||
809 g_object_get_data (G_OBJECT (tag), "fk_name") ||
810 g_object_get_data (G_OBJECT (tag), "dn")) {
811 hovering = TRUE;
812 break;
813 }
814 }
815
816 if (hovering != tcolumns->priv->hovering_over_link) {
817 tcolumns->priv->hovering_over_link = hovering;
818
819 if (tcolumns->priv->hovering_over_link) {
820 if (! hand_cursor)
821 hand_cursor = gdk_cursor_new (GDK_HAND2);
822 gdk_window_set_cursor (gtk_text_view_get_window (text_view,
823 GTK_TEXT_WINDOW_TEXT),
824 hand_cursor);
825 }
826 else {
827 if (!regular_cursor)
828 regular_cursor = gdk_cursor_new (GDK_XTERM);
829 gdk_window_set_cursor (gtk_text_view_get_window (text_view,
830 GTK_TEXT_WINDOW_TEXT),
831 regular_cursor);
832 }
833 }
834
835 if (tags)
836 g_slist_free (tags);
837 }
838
839 /*
840 * Also update the cursor image if the window becomes visible
841 * (e.g. when a window covering it got iconified).
842 */
843 static gboolean
visibility_notify_event(GtkWidget * text_view,G_GNUC_UNUSED GdkEventVisibility * event,TableColumns * tcolumns)844 visibility_notify_event (GtkWidget *text_view, G_GNUC_UNUSED GdkEventVisibility *event,
845 TableColumns *tcolumns)
846 {
847
848 gint wx, wy, bx, by;
849 GdkDeviceManager *manager;
850 GdkDevice *pointer;
851
852 manager = gdk_display_get_device_manager (gtk_widget_get_display (text_view));
853 pointer = gdk_device_manager_get_client_pointer (manager);
854 gdk_window_get_device_position (gtk_widget_get_window (text_view), pointer, &wx, &wy, NULL);
855 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
856 GTK_TEXT_WINDOW_WIDGET,
857 wx, wy, &bx, &by);
858
859 set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), bx, by, tcolumns);
860
861 return FALSE;
862 }
863
864 /*
865 * Update the cursor image if the pointer moved.
866 */
867 static gboolean
motion_notify_event(GtkWidget * text_view,GdkEventMotion * event,TableColumns * tcolumns)868 motion_notify_event (GtkWidget *text_view, GdkEventMotion *event, TableColumns *tcolumns)
869 {
870 gint x, y;
871
872 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
873 GTK_TEXT_WINDOW_WIDGET,
874 event->x, event->y, &x, &y);
875
876 set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), x, y, tcolumns);
877
878 return FALSE;
879 }
880
881 /* Looks at all tags covering the position of iter in the text view,
882 * and if one of them is a link, follow it by showing the page identified
883 * by the data attached to it.
884 */
885 static void
follow_if_link(G_GNUC_UNUSED GtkWidget * text_view,GtkTextIter * iter,TableColumns * tcolumns)886 follow_if_link (G_GNUC_UNUSED GtkWidget *text_view, GtkTextIter *iter, TableColumns *tcolumns)
887 {
888 GSList *tags = NULL, *tagp = NULL;
889
890 tags = gtk_text_iter_get_tags (iter);
891 for (tagp = tags; tagp != NULL; tagp = tagp->next) {
892 GtkTextTag *tag = tagp->data;
893 const gchar *table_name;
894 const gchar *table_schema;
895 const gchar *table_short_name;
896 const gchar *fk_name;
897 const gchar *dn;
898 SchemaBrowserPerspective *bpers;
899
900 table_schema = g_object_get_data (G_OBJECT (tag), "table_schema");
901 table_name = g_object_get_data (G_OBJECT (tag), "table_name");
902 table_short_name = g_object_get_data (G_OBJECT (tag), "table_short_name");
903 fk_name = g_object_get_data (G_OBJECT (tag), "fk_name");
904 dn = g_object_get_data (G_OBJECT (tag), "dn");
905
906 bpers = SCHEMA_BROWSER_PERSPECTIVE (browser_find_parent_widget (GTK_WIDGET (tcolumns),
907 TYPE_SCHEMA_BROWSER_PERSPECTIVE));
908 if (table_name && table_schema && table_short_name && bpers) {
909 schema_browser_perspective_display_table_info (bpers,
910 table_schema,
911 table_name,
912 table_short_name);
913 }
914 else if (fk_name) {
915 GdaMetaStruct *mstruct;
916 GdaMetaDbObject *dbo;
917 GValue *v1, *v2;
918 GtkWidget *parent;
919 table_schema = table_info_get_table_schema (tcolumns->priv->tinfo);
920 table_name = table_info_get_table_name (tcolumns->priv->tinfo);
921 g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)), table_schema);
922 g_value_set_string ((v2 = gda_value_new (G_TYPE_STRING)), table_name);
923 mstruct = browser_connection_get_meta_struct (tcolumns->priv->bcnc);
924 dbo = gda_meta_struct_get_db_object (mstruct, NULL, v1, v2);
925 gda_value_free (v1);
926 gda_value_free (v2);
927 parent = gtk_widget_get_toplevel (GTK_WIDGET (tcolumns));
928 if (!dbo || (dbo->obj_type != GDA_META_DB_TABLE)) {
929 browser_show_error ((GtkWindow*) parent,
930 _("Could not find table '%s.%s'"),
931 table_schema, table_name);
932 }
933 else {
934 GdaMetaTableForeignKey *fk = NULL;
935 GdaMetaTable *mtable;
936 GSList *list;
937 mtable = GDA_META_TABLE (dbo);
938 for (list = mtable->fk_list; list; list = list->next) {
939 GdaMetaTableForeignKey *tfk;
940 tfk = (GdaMetaTableForeignKey*) list->data;
941 if (tfk->fk_name && !strcmp (tfk->fk_name, fk_name)) {
942 fk = tfk;
943 break;
944 }
945 }
946 if (fk) {
947 GError *error = NULL;
948 if (! fk_declare_undeclare (mstruct,
949 BROWSER_IS_WINDOW (parent) ? BROWSER_WINDOW (parent) : NULL,
950 fk, &error)) {
951 browser_show_error ((GtkWindow *) parent, _("Failed to undeclare foreign key: %s"),
952 error && error->message ? error->message : _("No detail"));
953 g_clear_error (&error);
954 }
955 else if (BROWSER_IS_WINDOW (parent))
956 browser_window_show_notice (BROWSER_WINDOW (parent),
957 GTK_MESSAGE_INFO, "fkdeclare",
958 _("Successfully undeclared foreign key"));
959 else
960 browser_show_message ((GtkWindow *) parent, "%s",
961 _("Successfully undeclared foreign key"));
962 }
963 else
964 browser_show_error ((GtkWindow*) parent,
965 _("Could not find declared foreign key '%s'"),
966 fk_name);
967 }
968 }
969 #ifdef HAVE_LDAP
970 else if (dn) {
971 BrowserWindow *bwin;
972 BrowserPerspective *pers;
973
974 bwin = (BrowserWindow*) gtk_widget_get_toplevel ((GtkWidget*) tcolumns);
975 pers = browser_window_change_perspective (bwin, _("LDAP browser"));
976
977 ldap_browser_perspective_display_ldap_entry (LDAP_BROWSER_PERSPECTIVE (pers), dn);
978 }
979 #endif
980 }
981
982 if (tags)
983 g_slist_free (tags);
984 }
985
986
987 /*
988 * Links can also be activated by clicking.
989 */
990 static gboolean
event_after(GtkWidget * text_view,GdkEvent * ev,TableColumns * tcolumns)991 event_after (GtkWidget *text_view, GdkEvent *ev, TableColumns *tcolumns)
992 {
993 GtkTextIter start, end, iter;
994 GtkTextBuffer *buffer;
995 GdkEventButton *event;
996 gint x, y;
997
998 if (ev->type != GDK_BUTTON_RELEASE)
999 return FALSE;
1000
1001 event = (GdkEventButton *)ev;
1002
1003 if (event->button != 1)
1004 return FALSE;
1005
1006 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
1007
1008 /* we shouldn't follow a link if the user has selected something */
1009 gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
1010 if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end))
1011 return FALSE;
1012
1013 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
1014 GTK_TEXT_WINDOW_WIDGET,
1015 event->x, event->y, &x, &y);
1016
1017 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), &iter, x, y);
1018
1019 follow_if_link (text_view, &iter, tcolumns);
1020
1021 return FALSE;
1022 }
1023
1024 /*
1025 * Links can be activated by pressing Enter.
1026 */
1027 static gboolean
key_press_event(GtkWidget * text_view,GdkEventKey * event,TableColumns * tcolumns)1028 key_press_event (GtkWidget *text_view, GdkEventKey *event, TableColumns *tcolumns)
1029 {
1030 GtkTextIter iter;
1031 GtkTextBuffer *buffer;
1032
1033 switch (event->keyval) {
1034 case GDK_KEY_Return:
1035 case GDK_KEY_KP_Enter:
1036 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
1037 gtk_text_buffer_get_iter_at_mark (buffer, &iter,
1038 gtk_text_buffer_get_insert (buffer));
1039 follow_if_link (text_view, &iter, tcolumns);
1040 break;
1041
1042 default:
1043 break;
1044 }
1045 return FALSE;
1046 }
1047