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, &current);
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 									  &current,
231 									  _("Primary key"), -1,
232 									  "section", NULL);
233 				gtk_text_buffer_insert (tbuffer, &current, "\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, &current, ", ", -1);
238 					col = g_slist_nth_data (mtable->columns, mtable->pk_cols_array [i]);
239 					gtk_text_buffer_insert (tbuffer, &current,
240 								col ? col->column_name : "???", -1);
241 				}
242 				gtk_text_buffer_insert (tbuffer, &current, "\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 									  &current, 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, &current,
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, &current, "\n(", -1);
304 						gtk_text_buffer_insert_with_tags (tbuffer, &current,
305 										  _("Remove"), -1, tag, NULL);
306 						gtk_text_buffer_insert (tbuffer, &current, ")", -1);
307 					}
308 				}
309 				else
310 					gtk_text_buffer_insert_with_tags_by_name (tbuffer,
311 										  &current,
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, &current, "\n", -1);
318 					gtk_text_buffer_insert (tbuffer, &current,
319 								fk->fk_names_array [i], -1);
320 					gtk_text_buffer_insert (tbuffer, &current, " ", -1);
321 					gtk_text_buffer_insert_pixbuf (tbuffer, &current,
322 								       browser_get_pixbuf_icon (BROWSER_ICON_REFERENCE));
323 
324 					gtk_text_buffer_insert (tbuffer, &current, " ", -1);
325 					gtk_text_buffer_insert (tbuffer, &current,
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, &current, " ", -1);
342 							gtk_text_buffer_insert_with_tags_by_name (tbuffer,
343 												  &current,
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, &current, "\n", -1);
356 					/* To translators: the UPDATE is an SQL operation type */
357 					gtk_text_buffer_insert (tbuffer, &current, _("Policy on UPDATE"), -1);
358 					gtk_text_buffer_insert (tbuffer, &current, ": ", -1);
359 					gtk_text_buffer_insert (tbuffer, &current,
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, &current, "\n", -1);
366 					/* To translators: the DELETE is an SQL operation type */
367 					gtk_text_buffer_insert (tbuffer, &current, _("Policy on DELETE"), -1);
368 					gtk_text_buffer_insert (tbuffer, &current, ": ", -1);
369 					gtk_text_buffer_insert (tbuffer, &current,
370 								gda_tools_utils_fk_policy_to_string (policy),
371 								-1);
372 				}
373 
374 				gtk_text_buffer_insert (tbuffer, &current, "\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, &current,
407 											"\n\n", -1);
408 							gtk_text_buffer_insert_with_tags_by_name (tbuffer,
409 												  &current,
410 												  _("Unique constraint"), -1,
411 												  "section", NULL);
412 							gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
413 							current_value = gda_value_copy (cvalue);
414 						}
415 						else
416 							gtk_text_buffer_insert (tbuffer, &current, ", ", -1);
417 						cvalue = gda_data_model_get_value_at (model, 1, i, NULL);
418 						gtk_text_buffer_insert (tbuffer, &current,
419 									g_value_get_string (cvalue), -1);
420 					}
421 					gtk_text_buffer_insert (tbuffer, &current, "\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 									  &current,
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, &current, "\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, &current,
452 										  ddbo->obj_short_name,
453 										  -1, tag, NULL);
454 					}
455 				}
456 				g_slist_free (rev_list);
457 				gtk_text_buffer_insert (tbuffer, &current, "\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, &current);
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, &current,
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, &current, base_dn, -1,
483 										  tag, NULL);
484 					}
485 
486 					gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
487 					gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
488 										  "FILTER: ", -1,
489 										  "section", NULL);
490 					if (filter)
491 						gtk_text_buffer_insert (tbuffer, &current, filter, -1);
492 
493 					gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
494 					gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
495 										  "ATTRIBUTES: ", -1,
496 										  "section", NULL);
497 					if (attributes)
498 						gtk_text_buffer_insert (tbuffer, &current, attributes, -1);
499 
500 					gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
501 					gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
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, &current, 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