1 /*
2  * Copyright (C) 2009 - 2011 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2010 David King <davidk@openismus.com>
4  * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 
21 #include <glib/gi18n-lib.h>
22 #include <string.h>
23 #include <gtk/gtk.h>
24 #include <libgda/gda-tree.h>
25 #include "objects-cloud.h"
26 #include <libgda-ui/gdaui-tree-store.h>
27 #include "../dnd.h"
28 #include "../support.h"
29 #include "../gdaui-bar.h"
30 #include "marshal.h"
31 #include <gdk/gdkkeysyms.h>
32 #include <libgda-ui/internal/popup-container.h>
33 
34 struct _ObjectsCloudPrivate {
35 	gboolean             show_schemas;
36 	ObjectsCloudObjType  type;
37 	GdaMetaStruct       *mstruct;
38 	GtkTextBuffer       *tbuffer;
39 	GtkWidget           *tview;
40 
41 	gboolean             hovering_over_link;
42 };
43 
44 static void objects_cloud_class_init (ObjectsCloudClass *klass);
45 static void objects_cloud_init       (ObjectsCloud *cloud,
46 				      ObjectsCloudClass *klass);
47 static void objects_cloud_dispose   (GObject *object);
48 
49 enum {
50 	SELECTED,
51 	LAST_SIGNAL
52 };
53 
54 static guint objects_cloud_signals[LAST_SIGNAL] = { 0 };
55 static GObjectClass *parent_class = NULL;
56 
57 
58 /*
59  * ObjectsCloud class implementation
60  */
61 
62 static void
objects_cloud_class_init(ObjectsCloudClass * klass)63 objects_cloud_class_init (ObjectsCloudClass *klass)
64 {
65 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
66 
67 	parent_class = g_type_class_peek_parent (klass);
68 
69 	/* signals */
70 	objects_cloud_signals [SELECTED] =
71                 g_signal_new ("selected",
72                               G_TYPE_FROM_CLASS (object_class),
73                               G_SIGNAL_RUN_FIRST,
74                               G_STRUCT_OFFSET (ObjectsCloudClass, selected),
75                               NULL, NULL,
76                               _common_marshal_VOID__ENUM_STRING, G_TYPE_NONE,
77                               2, G_TYPE_UINT, G_TYPE_STRING);
78 	klass->selected = NULL;
79 
80 	object_class->dispose = objects_cloud_dispose;
81 }
82 
83 
84 static void
objects_cloud_init(ObjectsCloud * cloud,G_GNUC_UNUSED ObjectsCloudClass * klass)85 objects_cloud_init (ObjectsCloud *cloud, G_GNUC_UNUSED ObjectsCloudClass *klass)
86 {
87 	cloud->priv = g_new0 (ObjectsCloudPrivate, 1);
88 	cloud->priv->show_schemas = FALSE;
89 	cloud->priv->tbuffer = gtk_text_buffer_new (NULL);
90 
91 	gtk_orientable_set_orientation (GTK_ORIENTABLE (cloud), GTK_ORIENTATION_VERTICAL);
92 
93 	gtk_text_buffer_create_tag (cloud->priv->tbuffer, "section",
94                                     "weight", PANGO_WEIGHT_BOLD,
95                                     "foreground", "blue", NULL);
96 }
97 
98 static void
objects_cloud_dispose(GObject * object)99 objects_cloud_dispose (GObject *object)
100 {
101 	ObjectsCloud *cloud = (ObjectsCloud *) object;
102 
103 	/* free memory */
104 	if (cloud->priv) {
105 		if (cloud->priv->mstruct)
106 			g_object_unref (cloud->priv->mstruct);
107 		g_object_unref (cloud->priv->tbuffer);
108 		g_free (cloud->priv);
109 		cloud->priv = NULL;
110 	}
111 
112 	parent_class->dispose (object);
113 }
114 
115 GType
objects_cloud_get_type(void)116 objects_cloud_get_type (void)
117 {
118 	static GType type = 0;
119 
120 	if (G_UNLIKELY (type == 0)) {
121 		static const GTypeInfo info = {
122 			sizeof (ObjectsCloudClass),
123 			(GBaseInitFunc) NULL,
124 			(GBaseFinalizeFunc) NULL,
125 			(GClassInitFunc) objects_cloud_class_init,
126 			NULL,
127 			NULL,
128 			sizeof (ObjectsCloud),
129 			0,
130 			(GInstanceInitFunc) objects_cloud_init,
131 			0
132 		};
133 		type = g_type_register_static (GTK_TYPE_BOX, "ObjectsCloud",
134 					       &info, 0);
135 	}
136 	return type;
137 }
138 
139 static gint
dbo_sort(GdaMetaDbObject * dbo1,GdaMetaDbObject * dbo2)140 dbo_sort (GdaMetaDbObject *dbo1, GdaMetaDbObject *dbo2)
141 {
142 	gint res;
143 	res = g_strcmp0 (dbo1->obj_schema, dbo2->obj_schema);
144 	if (res)
145 		return - res;
146 	return - g_strcmp0 (dbo1->obj_name, dbo2->obj_name);
147 }
148 
149 typedef struct {
150 	gchar *schema;
151 	GtkTextMark *mark;
152 	gint nb_tables;
153 } SchemaData;
154 static void add_to_schema_data (ObjectsCloud *cloud, SchemaData *sd, GdaMetaDbObject *dbo);
155 static void
update_display(ObjectsCloud * cloud)156 update_display (ObjectsCloud *cloud)
157 {
158 	GSList *schemas = NULL; /* holds pointers to @SchemaData structures */
159 	SchemaData *default_sd = NULL, *sd;
160 
161 	GtkTextBuffer *tbuffer;
162         GtkTextIter start, end;
163 
164         /* clean all */
165         tbuffer = cloud->priv->tbuffer;
166         gtk_text_buffer_get_start_iter (tbuffer, &start);
167         gtk_text_buffer_get_end_iter (tbuffer, &end);
168         gtk_text_buffer_delete (tbuffer, &start, &end);
169 
170 	/* default SchemaData */
171 	default_sd = g_new0 (SchemaData, 1);
172 	default_sd->schema = NULL;
173 	default_sd->mark = gtk_text_mark_new (NULL, TRUE);
174 	default_sd->nb_tables = 0;
175 	gtk_text_buffer_get_end_iter (tbuffer, &end);
176 
177 	gtk_text_buffer_get_end_iter (tbuffer, &end);
178 	if (cloud->priv->show_schemas) {
179 		gtk_text_buffer_insert_pixbuf (tbuffer, &end,
180 					       browser_get_pixbuf_icon (BROWSER_ICON_TABLE));
181 		gtk_text_buffer_insert (tbuffer, &end, " ", 1);
182 	}
183 	gtk_text_buffer_add_mark (tbuffer, default_sd->mark, &end);
184 
185 	/* using meta struct */
186 	GdaMetaStruct *mstruct;
187 	GSList *dbo_list, *list;
188 	mstruct = cloud->priv->mstruct;
189 	if (!mstruct) {
190 		/* nothing to display */
191 		goto out;
192 	}
193 	dbo_list = g_slist_reverse (gda_meta_struct_get_all_db_objects (mstruct));
194 	list = g_slist_sort (dbo_list, (GCompareFunc) dbo_sort);
195 	dbo_list = list;
196 	for (list = dbo_list; list; list = list->next) {
197 		GdaMetaDbObject *dbo = GDA_META_DB_OBJECT (list->data);
198 		GSList *list;
199 		gboolean is_default;
200 
201 		if (dbo->obj_type != GDA_META_DB_TABLE)
202 			continue;
203 		g_assert (dbo->obj_schema);
204 
205 		is_default = strcmp (dbo->obj_short_name, dbo->obj_full_name) ? TRUE : FALSE;
206 		sd = NULL;
207 		if (is_default) {
208 			add_to_schema_data (cloud, default_sd, dbo);
209 		}
210 		else if (cloud->priv->show_schemas) {
211 			for (list = schemas; list; list = list->next) {
212 				if (!strcmp (((SchemaData *) list->data)->schema, dbo->obj_schema)) {
213 					sd = (SchemaData *) list->data;
214 					break;
215 				}
216 			}
217 			if (!sd) {
218 				sd = g_new0 (SchemaData, 1);
219 				sd->schema = g_strdup (dbo->obj_schema);
220 				sd->mark = gtk_text_mark_new (NULL, TRUE);
221 				sd->nb_tables = 0;
222 
223 				gtk_text_buffer_get_end_iter (tbuffer, &end);
224 				gtk_text_buffer_insert (tbuffer, &end, "\n\n", 2);
225 				gtk_text_buffer_insert_pixbuf (tbuffer, &end,
226 							       browser_get_pixbuf_icon (BROWSER_ICON_TABLE));
227 				gtk_text_buffer_insert (tbuffer, &end, " ", 1);
228 				gtk_text_buffer_add_mark (tbuffer, sd->mark, &end);
229 
230 				schemas = g_slist_append (schemas, sd);
231 			}
232 
233 			add_to_schema_data (cloud, sd, dbo);
234 		}
235 	}
236 	g_slist_free (dbo_list);
237 
238  out:
239 	/* default_sd can't be NULL here */
240 	schemas = g_slist_prepend (schemas, default_sd);
241 
242 	/* get rid of the SchemaData structures */
243 	for (list = schemas; list; list = list->next) {
244 		GtkTextIter iter;
245 		gchar *str;
246 		sd = (SchemaData*) list->data;
247 
248 		gtk_text_buffer_get_iter_at_mark (tbuffer, &iter, sd->mark);
249 		if (sd == default_sd) {
250 			if (sd->nb_tables > 0)
251 				str = g_strdup_printf (ngettext ("%d table in current schema:",
252 								 "%d tables in current schema:", sd->nb_tables),
253 						       sd->nb_tables);
254 			else
255 				str = g_strdup (_("Tables in current schema:"));
256 		}
257 		else
258 			str = g_strdup_printf (ngettext ("%d Table in schema '%s':",
259 							 "%d Tables in schema '%s':", sd->nb_tables),
260 					       sd->nb_tables, sd->schema);
261 
262 		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &iter,
263 							  str, -1, "section", NULL);
264 		g_free (str);
265 		gtk_text_buffer_insert (tbuffer, &iter, "\n\n", 2);
266 
267 		if ((sd == default_sd) && (default_sd->nb_tables == 0))
268 			gtk_text_buffer_insert (tbuffer, &iter, _("None"), -1);
269 
270 		g_free (sd->schema);
271 		g_object_unref (sd->mark);
272 		g_free (sd);
273 	}
274 }
275 
276 static void
add_to_schema_data(ObjectsCloud * cloud,SchemaData * sd,GdaMetaDbObject * dbo)277 add_to_schema_data (ObjectsCloud *cloud, SchemaData *sd, GdaMetaDbObject *dbo)
278 {
279 	GtkTextTag *tag;
280 	GtkTextIter iter;
281 	gdouble scale = 1.0;
282 
283 	sd->nb_tables ++;
284 
285 	if (dbo->obj_type == GDA_META_DB_TABLE)
286 		scale = 1.5 + g_slist_length (dbo->depend_list) / 5.;
287 
288 	gtk_text_buffer_get_iter_at_mark (cloud->priv->tbuffer, &iter, sd->mark);
289 	tag = gtk_text_buffer_create_tag (cloud->priv->tbuffer, NULL,
290 					  "foreground", "#6161F2",
291 					  //"underline", PANGO_UNDERLINE_SINGLE,
292 					  "scale", scale,
293 					  NULL);
294 	g_object_set_data_full (G_OBJECT (tag), "dbo_obj_schema", g_strdup (dbo->obj_schema), g_free);
295 	g_object_set_data_full (G_OBJECT (tag), "dbo_obj_name", g_strdup (dbo->obj_name), g_free);
296 	g_object_set_data_full (G_OBJECT (tag), "dbo_obj_short_name", g_strdup (dbo->obj_short_name), g_free);
297 
298 	gtk_text_buffer_insert_with_tags (cloud->priv->tbuffer, &iter, dbo->obj_name, -1,
299 					  tag, NULL);
300 	gtk_text_buffer_insert (cloud->priv->tbuffer, &iter, "  ", -1);
301 }
302 
303 static gboolean key_press_event (GtkWidget *text_view, GdkEventKey *event, ObjectsCloud *cloud);
304 static gboolean event_after (GtkWidget *text_view, GdkEvent *ev, ObjectsCloud *cloud);
305 static gboolean motion_notify_event (GtkWidget *text_view, GdkEventMotion *event, ObjectsCloud *cloud);
306 static gboolean visibility_notify_event (GtkWidget *text_view, GdkEventVisibility *event, ObjectsCloud *cloud);
307 
308 static void
text_tag_table_foreach_cb(GtkTextTag * tag,const gchar * find)309 text_tag_table_foreach_cb (GtkTextTag *tag, const gchar *find)
310 {
311 	const gchar *name;
312 
313 	name = g_object_get_data (G_OBJECT (tag), "dbo_obj_name");
314 
315 	if (!name)
316 		return;
317 
318 	if (!*find) {
319 		g_object_set (tag, "foreground", "#6161F2", NULL);
320 	}
321 	else {
322 		gchar *ptr;
323 		gchar *lcname, *lcfind;
324 		lcname = g_utf8_strdown (name, -1);
325 		lcfind = g_utf8_strdown (find, -1);
326 
327 		ptr = strstr (lcname, lcfind);
328 		if (!ptr) {
329 			/* string not present in name */
330 			g_object_set (tag, "foreground", "#DBDBDB", NULL);
331 		}
332 		else if ((ptr == lcname) ||
333 			 ((*name == '"') && (ptr == lcname+1))) {
334 			/* string present as start of name */
335 			g_object_set (tag, "foreground", "#6161F2", NULL);
336 		}
337 		else {
338 			/* string present in name but not at the start */
339 			g_object_set (tag, "foreground", "#A0A0A0", NULL);
340 		}
341 
342 		g_free (lcname);
343 		g_free (lcfind);
344 	}
345 }
346 
347 void
objects_cloud_filter(ObjectsCloud * cloud,const gchar * filter)348 objects_cloud_filter (ObjectsCloud *cloud, const gchar *filter)
349 {
350 	g_return_if_fail (IS_OBJECTS_CLOUD (cloud));
351 
352 	GtkTextTagTable *tags_table = gtk_text_buffer_get_tag_table (cloud->priv->tbuffer);
353 
354 	gtk_text_tag_table_foreach (tags_table, (GtkTextTagTableForeach) text_tag_table_foreach_cb,
355 				    (gpointer) filter);
356 }
357 
358 /**
359  * objects_cloud_new
360  *
361  *
362  *
363  * Returns:
364  */
365 GtkWidget *
objects_cloud_new(GdaMetaStruct * mstruct,ObjectsCloudObjType type)366 objects_cloud_new (GdaMetaStruct *mstruct, ObjectsCloudObjType type)
367 {
368 	ObjectsCloud *cloud;
369 
370 	g_return_val_if_fail (!mstruct || GDA_IS_META_STRUCT (mstruct), NULL);
371 	cloud = OBJECTS_CLOUD (g_object_new (OBJECTS_CLOUD_TYPE, NULL));
372 
373 	if (mstruct)
374 		cloud->priv->mstruct = g_object_ref (mstruct);
375 	cloud->priv->type = type;
376 
377 	/* text contents */
378 	GtkWidget *sw, *vbox;
379 	sw = gtk_scrolled_window_new (NULL, NULL);
380 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC,
381 					GTK_POLICY_AUTOMATIC);
382 	gtk_box_pack_start (GTK_BOX (cloud), sw, TRUE, TRUE, 0);
383 
384 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
385 	gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
386 
387 	cloud->priv->tview = gtk_text_view_new_with_buffer (cloud->priv->tbuffer);
388 	gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (cloud->priv->tview), GTK_WRAP_WORD);
389 	gtk_text_view_set_editable (GTK_TEXT_VIEW (cloud->priv->tview), FALSE);
390 	gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (cloud->priv->tview), FALSE);
391 	gtk_box_pack_start (GTK_BOX (vbox), cloud->priv->tview, TRUE, TRUE, 0);
392 	gtk_widget_show_all (sw);
393 
394 	g_signal_connect (cloud->priv->tview, "key-press-event",
395 			  G_CALLBACK (key_press_event), cloud);
396 	g_signal_connect (cloud->priv->tview, "event-after",
397 			  G_CALLBACK (event_after), cloud);
398 	g_signal_connect (cloud->priv->tview, "motion-notify-event",
399 			  G_CALLBACK (motion_notify_event), cloud);
400 	g_signal_connect (cloud->priv->tview, "visibility-notify-event",
401 			  G_CALLBACK (visibility_notify_event), cloud);
402 
403 	/* initial update */
404 	update_display (cloud);
405 
406 	return (GtkWidget*) cloud;
407 }
408 
409 /**
410  * objects_cloud_set_meta_struct
411  *
412  */
413 void
objects_cloud_set_meta_struct(ObjectsCloud * cloud,GdaMetaStruct * mstruct)414 objects_cloud_set_meta_struct (ObjectsCloud *cloud, GdaMetaStruct *mstruct)
415 {
416 	g_return_if_fail (IS_OBJECTS_CLOUD (cloud));
417 	g_return_if_fail (!mstruct || GDA_IS_META_STRUCT (mstruct));
418 
419 	if (cloud->priv->mstruct) {
420 		g_object_unref (cloud->priv->mstruct);
421 		cloud->priv->mstruct = NULL;
422 	}
423 	if (mstruct)
424 		cloud->priv->mstruct = g_object_ref (mstruct);
425 	update_display (cloud);
426 }
427 
428 /**
429  * objects_cloud_show_schemas
430  */
431 void
objects_cloud_show_schemas(ObjectsCloud * cloud,gboolean show_schemas)432 objects_cloud_show_schemas (ObjectsCloud *cloud, gboolean show_schemas)
433 {
434 	g_return_if_fail (IS_OBJECTS_CLOUD (cloud));
435 
436 	cloud->priv->show_schemas = show_schemas;
437 	update_display (cloud);
438 }
439 
440 static void
find_entry_changed_cb(GtkWidget * entry,ObjectsCloud * cloud)441 find_entry_changed_cb (GtkWidget *entry, ObjectsCloud *cloud)
442 {
443 	gchar *find = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
444 	objects_cloud_filter (cloud, find);
445 	g_free (find);
446 }
447 
448 /**
449  * objects_cloud_create_filter
450  */
451 GtkWidget *
objects_cloud_create_filter(ObjectsCloud * cloud)452 objects_cloud_create_filter (ObjectsCloud *cloud)
453 {
454 	GtkWidget *hbox, *label, *wid;
455 	g_return_val_if_fail (IS_OBJECTS_CLOUD (cloud), NULL);
456 
457 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
458 
459 	label = gtk_label_new (_("Find:"));
460 	gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
461 	wid = gtk_entry_new ();
462 	g_signal_connect (wid, "changed",
463 			  G_CALLBACK (find_entry_changed_cb), cloud);
464 	gtk_box_pack_start (GTK_BOX (hbox), wid, TRUE, TRUE, 0);
465 	gtk_widget_show_all (hbox);
466 	gtk_widget_hide (hbox);
467 
468 	return hbox;
469 }
470 
471 static GdkCursor *hand_cursor = NULL;
472 static GdkCursor *regular_cursor = NULL;
473 
474 /* Looks at all tags covering the position (x, y) in the text view,
475  * and if one of them is a link, change the cursor to the "hands" cursor
476  * typically used by web browsers.
477  */
478 static void
set_cursor_if_appropriate(GtkTextView * text_view,gint x,gint y,ObjectsCloud * cloud)479 set_cursor_if_appropriate (GtkTextView *text_view, gint x, gint y, ObjectsCloud *cloud)
480 {
481 	GSList *tags = NULL, *tagp = NULL;
482 	GtkTextIter iter;
483 	gboolean hovering = FALSE;
484 
485 	gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
486 
487 	tags = gtk_text_iter_get_tags (&iter);
488 	for (tagp = tags;  tagp != NULL;  tagp = tagp->next) {
489 		GtkTextTag *tag = tagp->data;
490 		gchar *table_name;
491 
492 		table_name = g_object_get_data (G_OBJECT (tag), "dbo_obj_name");
493 		if (table_name) {
494 			hovering = TRUE;
495 			break;
496 		}
497 	}
498 
499 	if (hovering != cloud->priv->hovering_over_link) {
500 		cloud->priv->hovering_over_link = hovering;
501 
502 		if (cloud->priv->hovering_over_link) {
503 			if (! hand_cursor)
504 				hand_cursor = gdk_cursor_new (GDK_HAND2);
505 			gdk_window_set_cursor (gtk_text_view_get_window (text_view,
506 									 GTK_TEXT_WINDOW_TEXT),
507 					       hand_cursor);
508 		}
509 		else {
510 			if (!regular_cursor)
511 				regular_cursor = gdk_cursor_new (GDK_XTERM);
512 			gdk_window_set_cursor (gtk_text_view_get_window (text_view,
513 									 GTK_TEXT_WINDOW_TEXT),
514 					       regular_cursor);
515 		}
516 	}
517 
518 	if (tags)
519 		g_slist_free (tags);
520 }
521 
522 /*
523  * Also update the cursor image if the window becomes visible
524  * (e.g. when a window covering it got iconified).
525  */
526 static gboolean
visibility_notify_event(GtkWidget * text_view,G_GNUC_UNUSED GdkEventVisibility * event,ObjectsCloud * cloud)527 visibility_notify_event (GtkWidget *text_view, G_GNUC_UNUSED GdkEventVisibility *event, ObjectsCloud *cloud)
528 {
529 	gint wx, wy, bx, by;
530 	GdkDeviceManager *manager;
531 	GdkDevice *pointer;
532 	manager = gdk_display_get_device_manager (gtk_widget_get_display (text_view));
533 	pointer = gdk_device_manager_get_client_pointer (manager);
534 	gdk_window_get_device_position (gtk_widget_get_window (text_view), pointer, &wx, &wy, NULL);
535 
536 	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
537 					       GTK_TEXT_WINDOW_WIDGET,
538 					       wx, wy, &bx, &by);
539 
540 	set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), bx, by, cloud);
541 
542 	return FALSE;
543 }
544 
545 /*
546  * Update the cursor image if the pointer moved.
547  */
548 static gboolean
motion_notify_event(GtkWidget * text_view,GdkEventMotion * event,ObjectsCloud * cloud)549 motion_notify_event (GtkWidget *text_view, GdkEventMotion *event, ObjectsCloud *cloud)
550 {
551 	gint x, y;
552 
553 	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
554 					       GTK_TEXT_WINDOW_WIDGET,
555 					       event->x, event->y, &x, &y);
556 
557 	set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), x, y, cloud);
558 	return FALSE;
559 }
560 
561 
562 /* Looks at all tags covering the position of iter in the text view,
563  * and if one of them is a link, follow it by showing the page identified
564  * by the data attached to it.
565  */
566 static void
follow_if_link(G_GNUC_UNUSED GtkWidget * text_view,GtkTextIter * iter,ObjectsCloud * cloud)567 follow_if_link (G_GNUC_UNUSED GtkWidget *text_view, GtkTextIter *iter, ObjectsCloud *cloud)
568 {
569 	GSList *tags = NULL, *tagp = NULL;
570 
571 	tags = gtk_text_iter_get_tags (iter);
572 	for (tagp = tags;  tagp;  tagp = tagp->next) {
573 		GtkTextTag *tag = tagp->data;
574 		const gchar *schema;
575 
576 		schema = g_object_get_data (G_OBJECT (tag), "dbo_obj_schema");
577 		if (schema) {
578 			const gchar *objects_name, *short_name;
579 
580 			objects_name = g_object_get_data (G_OBJECT (tag), "dbo_obj_name");
581 			short_name = g_object_get_data (G_OBJECT (tag), "dbo_obj_short_name");
582 
583 			if (objects_name && short_name) {
584 				gchar *str, *tmp1, *tmp2, *tmp3;
585 				tmp1 = gda_rfc1738_encode (schema);
586 				tmp2 = gda_rfc1738_encode (objects_name);
587 				tmp3 = gda_rfc1738_encode (short_name);
588 				str = g_strdup_printf ("OBJ_TYPE=table;OBJ_SCHEMA=%s;OBJ_NAME=%s;OBJ_SHORT_NAME=%s",
589 						       tmp1, tmp2, tmp3);
590 				g_free (tmp1);
591 				g_free (tmp2);
592 				g_free (tmp3);
593 				g_signal_emit (cloud, objects_cloud_signals [SELECTED], 0,
594 					       cloud->priv->type, str);
595 				g_free (str);
596 			}
597 		}
598         }
599 
600 	if (tags)
601 		g_slist_free (tags);
602 }
603 
604 /*
605  * Links can also be activated by clicking.
606  */
607 static gboolean
event_after(GtkWidget * text_view,GdkEvent * ev,ObjectsCloud * cloud)608 event_after (GtkWidget *text_view, GdkEvent *ev, ObjectsCloud *cloud)
609 {
610 	GtkTextIter start, end, iter;
611 	GtkTextBuffer *buffer;
612 	GdkEventButton *event;
613 	gint x, y;
614 
615 	if (ev->type != GDK_BUTTON_RELEASE)
616 		return FALSE;
617 
618 	event = (GdkEventButton *)ev;
619 
620 	if (event->button != 1)
621 		return FALSE;
622 
623 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
624 
625 	/* we shouldn't follow a link if the user has selected something */
626 	gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
627 	if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end))
628 		return FALSE;
629 
630 	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
631 					       GTK_TEXT_WINDOW_WIDGET,
632 					       event->x, event->y, &x, &y);
633 
634 	gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), &iter, x, y);
635 
636 	follow_if_link (text_view, &iter, cloud);
637 
638 	return FALSE;
639 }
640 
641 /*
642  * Links can be activated by pressing Enter.
643  */
644 static gboolean
key_press_event(GtkWidget * text_view,GdkEventKey * event,ObjectsCloud * cloud)645 key_press_event (GtkWidget *text_view, GdkEventKey *event, ObjectsCloud *cloud)
646 {
647 	GtkTextIter iter;
648 	GtkTextBuffer *buffer;
649 
650 	switch (event->keyval) {
651 	case GDK_KEY_Return:
652 	case GDK_KEY_KP_Enter:
653 		buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
654 		gtk_text_buffer_get_iter_at_mark (buffer, &iter,
655 						  gtk_text_buffer_get_insert (buffer));
656 		follow_if_link (text_view, &iter, cloud);
657 		break;
658 
659 	default:
660 		break;
661 	}
662 	return FALSE;
663 }
664