1 
2 /*
3  * dialog-hyperlink.c: Add or edit a hyperlink
4  *
5  * Copyright (C) 2002 Jody Goldberg (jody@gnome.org)
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 as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) version 3.
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 St, Fifth Floor, Boston, MA  02110-1301
20  * USA
21  */
22 
23 #include <gnumeric-config.h>
24 #include <gnumeric.h>
25 #include <dialogs/dialogs.h>
26 #include <dialogs/help.h>
27 
28 #include <commands.h>
29 #include <widgets/gnm-expr-entry.h>
30 #include <expr-name.h>
31 #include <expr.h>
32 #include <gui-util.h>
33 #include <hlink.h>
34 #include <mstyle.h>
35 #include <style-color.h>
36 #include <sheet-control.h>
37 #include <sheet-view.h>
38 #include <sheet-style.h>
39 #include <value.h>
40 #include <wbc-gtk.h>
41 #include <goffice/goffice.h>
42 
43 #include <glib/gi18n-lib.h>
44 
45 #include <string.h>
46 
47 
48 typedef struct {
49 	WBCGtk  *wbcg;
50 	Workbook  *wb;
51 	SheetControl *sc;
52 	Sheet *sheet;
53 
54 	GtkBuilder *gui;
55 	GtkWidget *dialog;
56 
57 	GtkImage  *type_image;
58 	GtkLabel  *type_descriptor;
59 	GnmExprEntry *internal_link_ee;
60 	GnmHLink  *link;
61 	gboolean   is_new;
62 
63 	GtkWidget *use_def_widget;
64 } HyperlinkState;
65 
66 static void
dhl_free(HyperlinkState * state)67 dhl_free (HyperlinkState *state)
68 {
69 	if (state->gui != NULL) {
70 		g_object_unref (state->gui);
71 		state->gui = NULL;
72 	}
73 	if (state->link != NULL) {
74 		g_object_unref (state->link);
75 		state->link = NULL;
76 	}
77 	state->dialog = NULL;
78 	g_free (state);
79 }
80 
81 static char *
dhl_get_default_tip(char const * const target)82 dhl_get_default_tip (char const * const target) {
83 	char *default_text = _("Left click once to follow this link.\n"
84 			       "Middle click once to select this cell");
85 	if (target == NULL)
86 		return g_strdup (default_text);
87 	else
88 		return g_strjoin ("\n", target, default_text, NULL);
89 }
90 
91 static void
dhl_set_tip(HyperlinkState * state)92 dhl_set_tip (HyperlinkState* state)
93 {
94 	char const *tip = gnm_hlink_get_tip (state->link);
95 	GtkTextBuffer *tb;
96 	GtkWidget *w;
97 
98 	if (state->is_new) {
99 			w = go_gtk_builder_get_widget (state->gui, "use-default-tip");
100 			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), TRUE);
101 			return;
102 	}
103 
104 	if (tip != NULL) {
105 		char const * const target = gnm_hlink_get_target (state->link);
106 		char *default_tip = dhl_get_default_tip (target);
107 		gboolean is_default = (strcmp (tip, default_tip) == 0);
108 		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->use_def_widget),
109 					      is_default);
110 		g_free (default_tip);
111 		if (is_default)
112 			return;
113 	}
114 	w = go_gtk_builder_get_widget (state->gui, "use-this-tip");
115 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), TRUE);
116 
117 	tb = gtk_text_view_get_buffer
118 		(GTK_TEXT_VIEW (go_gtk_builder_get_widget (state->gui, "tip-entry")));
119 
120 	gtk_text_buffer_set_text (tb, (tip == NULL) ? "" : tip, -1);
121 }
122 
123 static char *
dhl_get_tip(HyperlinkState * state,char const * target)124 dhl_get_tip (HyperlinkState *state, char const *target)
125 {
126 	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (state->use_def_widget)))
127 		return NULL;
128 	else {
129 		char *tip;
130 		GtkTextBuffer *tb = gtk_text_view_get_buffer
131 			(GTK_TEXT_VIEW (go_gtk_builder_get_widget (state->gui, "tip-entry")));
132 		GtkTextIter start_iter, end_iter;
133 
134 		gtk_text_buffer_get_start_iter (tb, &start_iter);
135 		gtk_text_buffer_get_end_iter (tb, &end_iter);
136 
137 		tip  = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
138 
139 		if (tip != NULL && strlen (tip) == 0) {
140 			g_free (tip);
141 			tip = NULL;
142 		}
143 
144 		return tip;
145 	}
146 }
147 
148 static void
dhl_set_target_cur_wb(HyperlinkState * state,const char * const target)149 dhl_set_target_cur_wb (HyperlinkState *state, const char* const target)
150 {
151 	gnm_expr_entry_load_from_text (state->internal_link_ee, target);
152 }
153 
154 static char *
dhl_get_target_cur_wb(HyperlinkState * state,gboolean * success)155 dhl_get_target_cur_wb (HyperlinkState *state, gboolean *success)
156 {
157 	char *ret = NULL;
158 	GnmExprEntry *gee = state->internal_link_ee;
159 	char const *target = gnm_expr_entry_get_text (gee);
160 	Sheet *sheet = sc_sheet (state->sc);
161 	GnmValue *val;
162 
163 	*success = FALSE;
164 	if (strlen (target) == 0) {
165 		*success = TRUE;
166 	} else {
167 		val = gnm_expr_entry_parse_as_value (gee, sheet);
168 		if (!val) {
169 			/* not an address, is it a name ? */
170 			GnmParsePos pp;
171 			GnmNamedExpr *nexpr;
172 
173 			parse_pos_init_sheet (&pp, sheet);
174 			nexpr = expr_name_lookup (&pp, target);
175 			if (nexpr != NULL)
176 				val = gnm_expr_top_get_range (nexpr->texpr);
177 		}
178 		if (val) {
179 			*success = TRUE;
180 			ret = g_strdup (target);
181 			value_release (val);
182 		} else {
183 			go_gtk_notice_dialog (GTK_WINDOW (state->dialog),
184 					 GTK_MESSAGE_ERROR,
185 					 _("Not a range or name"));
186 			gnm_expr_entry_grab_focus (gee, TRUE);
187 		}
188 	}
189 	return ret;
190 }
191 
192 static void
dhl_set_target_external(HyperlinkState * state,const char * const target)193 dhl_set_target_external (HyperlinkState *state, const char* const target)
194 {
195 	GtkWidget *w = go_gtk_builder_get_widget (state->gui, "external-link");
196 
197 	gtk_entry_set_text (GTK_ENTRY (w), target);
198 }
199 
200 static char *
dhl_get_target_external(HyperlinkState * state,gboolean * success)201 dhl_get_target_external (HyperlinkState *state, gboolean *success)
202 {
203 	GtkWidget *w = go_gtk_builder_get_widget (state->gui, "external-link");
204 	const char *target = gtk_entry_get_text (GTK_ENTRY (w));
205 
206 	*success = TRUE;
207 	return strlen (target) > 0 ? g_strdup (target) : NULL;
208 }
209 
210 static void
dhl_set_target_email(HyperlinkState * state,const char * const target)211 dhl_set_target_email (HyperlinkState *state, const char* const target)
212 {
213 	GtkWidget *w = go_gtk_builder_get_widget (state->gui, "email-address");
214 	GtkWidget *w2 = go_gtk_builder_get_widget (state->gui, "email-subject");
215 	gchar* cursor;
216 	gchar* subject;
217 	gchar* guitext;
218 
219 	if (!target || *target == '\0')
220 		return;
221 
222 	if( strncmp (target, "mailto:", strlen ("mailto:")) != 0)
223 		return;
224 
225 	cursor = g_strdup (target + strlen ("mailto:"));
226 
227 	subject = strstr (cursor, "?subject=");
228 	if (subject) {
229 		guitext = g_uri_unescape_string (subject + strlen ("?subject="),
230 						 NULL);
231 		gtk_entry_set_text (GTK_ENTRY (w2), guitext);
232 		*subject = '\0';
233 		g_free (guitext);
234 	}
235 
236 	guitext = g_uri_unescape_string (cursor, NULL);
237 
238 	gtk_entry_set_text (GTK_ENTRY (w), guitext);
239 
240 	g_free (guitext);
241 	g_free (cursor);
242 }
243 
244 static char*
dhl_get_target_email(HyperlinkState * state,gboolean * success)245 dhl_get_target_email (HyperlinkState *state, gboolean *success)
246 {
247 	GtkWidget *w = go_gtk_builder_get_widget (state->gui, "email-address");
248 	GtkWidget *w2 = go_gtk_builder_get_widget (state->gui, "email-subject");
249 	const char *address = gtk_entry_get_text (GTK_ENTRY (w));
250 	const char *subject = gtk_entry_get_text (GTK_ENTRY (w2));
251 	gchar* enc_subj, *enc_addr;
252 	gchar* result;
253 
254 	*success = TRUE;
255 	if (!address || *address == '\0') {
256 		return NULL;
257 	}
258 
259 	enc_addr = go_url_encode (address, 0);
260 	if (!subject || *subject == '\0') {
261 		result =  g_strconcat ("mailto:", enc_addr, NULL);
262 	} else {
263 		enc_subj = go_url_encode (subject, 0);
264 
265 		result = g_strconcat ("mailto:", enc_addr,
266 				      "?subject=", enc_subj, NULL);
267 		g_free (enc_subj);
268 	}
269 
270 	g_free (enc_addr);
271 
272 	return result;
273 }
274 
275 static void
dhl_set_target_url(HyperlinkState * state,const char * const target)276 dhl_set_target_url (HyperlinkState *state, const char* const target)
277 {
278 	GtkWidget *w = go_gtk_builder_get_widget (state->gui, "url");
279 
280 	gtk_entry_set_text (GTK_ENTRY (w), target);
281 }
282 
283 static char *
dhl_get_target_url(HyperlinkState * state,gboolean * success)284 dhl_get_target_url (HyperlinkState *state, gboolean *success)
285 {
286 	GtkWidget *w = go_gtk_builder_get_widget (state->gui, "url");
287 	const char *target = gtk_entry_get_text (GTK_ENTRY (w));
288 
289 	*success = TRUE;
290 	return strlen (target) > 0 ? g_strdup (target) : NULL;
291 }
292 
293 static struct {
294 	char const *label;
295 	char const *icon_name;
296 	char const *name;
297 	char const *widget_name;
298 	char const *descriptor;
299 	void (*set_target) (HyperlinkState *state, const char* const target);
300 	char * (*get_target) (HyperlinkState *state, gboolean *success);
301 } const type[] = {
302 	{ N_("Internal Link"), "gnumeric-link-internal",
303 	  "GnmHLinkCurWB",	"internal-link-grid",
304 	  N_("Jump to specific cells or named range in the current workbook"),
305 	  dhl_set_target_cur_wb,
306 	  dhl_get_target_cur_wb },
307 
308 	{ N_("External Link"), "gnumeric-link-external",
309 	  "GnmHLinkExternal",	"external-link-grid" ,
310 	  N_("Open an external file with the specified name"),
311 	  dhl_set_target_external,
312 	  dhl_get_target_external },
313 	{ N_("Email Link"),	"gnumeric-link-email",
314 	  "GnmHLinkEMail",	"email-grid" ,
315 	  N_("Prepare an email"),
316 	  dhl_set_target_email,
317 	  dhl_get_target_email },
318 	{ N_("Web Link"),		"gnumeric-link-url",
319 	  "GnmHLinkURL",	"url-grid" ,
320 	  N_("Browse to the specified URL"),
321 	  dhl_set_target_url,
322 	  dhl_get_target_url }
323 };
324 
325 static void
dhl_set_target(HyperlinkState * state)326 dhl_set_target (HyperlinkState* state)
327 {
328 	unsigned i;
329 	char const * const target = gnm_hlink_get_target (state->link);
330 	char const * type_name;
331 
332 	if (target) {
333 		type_name = G_OBJECT_TYPE_NAME (state->link);
334 		for (i = 0 ; i < G_N_ELEMENTS (type); i++) {
335 			if (strcmp (type_name, type[i].name) == 0) {
336 				if (type[i].set_target)
337 					(type[i].set_target) (state, target);
338 				break;
339 			}
340 		}
341 	}
342 }
343 
344 static char *
dhl_get_target(HyperlinkState * state,gboolean * success)345 dhl_get_target (HyperlinkState *state, gboolean *success)
346 {
347 	unsigned i;
348 	char const *type_name = G_OBJECT_TYPE_NAME (state->link);
349 
350 	*success = FALSE;
351 	for (i = 0 ; i < G_N_ELEMENTS (type); i++) {
352 		if (strcmp (type_name, type[i].name) == 0) {
353 			if (type[i].get_target)
354 				return (type[i].get_target) (state, success);
355 			break;
356 		}
357 	}
358 
359 	return NULL;
360 }
361 
362 
363 static void
dhl_cb_cancel(G_GNUC_UNUSED GtkWidget * button,HyperlinkState * state)364 dhl_cb_cancel (G_GNUC_UNUSED GtkWidget *button, HyperlinkState *state)
365 {
366 	gtk_widget_destroy (state->dialog);
367 }
368 
369 static void
dhl_cb_ok(G_GNUC_UNUSED GtkWidget * button,HyperlinkState * state)370 dhl_cb_ok (G_GNUC_UNUSED GtkWidget *button, HyperlinkState *state)
371 {
372 	GnmStyle *style;
373 	char *cmdname;
374 	char *target;
375 	char *tip;
376 	gboolean success;
377 
378 	target = dhl_get_target (state, &success);
379 	if (!success)
380 		return;		/* Let user continue editing */
381 
382 	wb_control_sheet_focus (GNM_WBC (state->wbcg), state->sheet);
383 
384 	if (target) {
385 		GnmHLink *new_link = gnm_hlink_dup_to (state->link, state->sheet);
386 		gnm_hlink_set_target (new_link, target);
387 		tip = dhl_get_tip (state, target);
388 		gnm_hlink_set_tip (new_link, tip);
389 		g_free (tip);
390 		style = gnm_style_new ();
391 		gnm_style_set_hlink (style, new_link);
392 		gnm_style_set_font_uline (style, UNDERLINE_SINGLE);
393 		gnm_style_set_font_color (style, gnm_color_new_go (GO_COLOR_BLUE));
394 
395 		if (state->is_new) {
396 			cmdname = _("Add Hyperlink");
397 			cmd_selection_hyperlink (GNM_WBC (state->wbcg),
398 						 style,
399 						 cmdname, target);
400 		} else {
401 			cmdname = _("Edit Hyperlink");
402 			cmd_selection_hyperlink (GNM_WBC (state->wbcg),
403 						 style,
404 						 cmdname, NULL);
405 			g_free (target);
406 		}
407 	} else if (!state->is_new) {
408 		style = gnm_style_new ();
409 		gnm_style_set_hlink (style, NULL);
410 		cmdname = _("Remove Hyperlink");
411 		cmd_selection_hyperlink (GNM_WBC (state->wbcg), style,
412 					 cmdname, NULL);
413 	}
414 	gtk_widget_destroy (state->dialog);
415 }
416 
417 static void
dhl_setup_type(HyperlinkState * state)418 dhl_setup_type (HyperlinkState *state)
419 {
420 	GtkWidget *w;
421 	char const *name = G_OBJECT_TYPE_NAME (state->link);
422 	unsigned i;
423 
424 	for (i = 0 ; i < G_N_ELEMENTS (type); i++) {
425 		w = go_gtk_builder_get_widget (state->gui, type[i].widget_name);
426 
427 		if (!strcmp (name, type[i].name)) {
428 			gtk_widget_show_all (w);
429 			gtk_image_set_from_icon_name
430 				(state->type_image, type[i].icon_name,
431 				 GTK_ICON_SIZE_DIALOG);
432 			gtk_label_set_text (state->type_descriptor,
433 					    _(type[i].descriptor));
434 		} else
435 			gtk_widget_hide (w);
436 	}
437 }
438 
439 static void
dhl_set_type(HyperlinkState * state,GType type)440 dhl_set_type (HyperlinkState *state, GType type)
441 {
442 	GnmHLink *old = state->link;
443 
444 	state->link = gnm_hlink_new (type, state->sheet);
445 	if (old != NULL) {
446 		gnm_hlink_set_target (state->link, gnm_hlink_get_target (old));
447 		gnm_hlink_set_tip (state->link, gnm_hlink_get_tip (old));
448 		g_object_unref (old);
449 	}
450 	dhl_setup_type (state);
451 }
452 
453 static void
dhl_cb_menu_changed(GtkComboBox * box,HyperlinkState * state)454 dhl_cb_menu_changed (GtkComboBox *box, HyperlinkState *state)
455 {
456 	int i = gtk_combo_box_get_active (box);
457 	dhl_set_type (state, g_type_from_name (
458 		type [i].name));
459 }
460 
461 static gboolean
dhl_init(HyperlinkState * state)462 dhl_init (HyperlinkState *state)
463 {
464 	static char const * const label[] = {
465 		"internal-link-label",
466 		"external-link-label",
467 		"email-address-label",
468 		"email-subject-label",
469 		"url-label",
470 		"use-this-tip"
471 	};
472 	GtkWidget *w;
473 	GtkSizeGroup *size_group;
474 	GnmExprEntry *expr_entry;
475 	unsigned i, select = 0;
476 	GtkListStore *store;
477 	GtkTreeIter iter;
478 	GtkCellRenderer *renderer;
479 
480 #ifdef GNM_NO_MAILTO
481 	gtk_widget_hide (go_gtk_builder_get_widget (state->gui, "email-grid"));
482 #endif
483 	size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
484 	for (i = 0 ; i < G_N_ELEMENTS (label); i++)
485 		gtk_size_group_add_widget (size_group,
486 					   go_gtk_builder_get_widget (state->gui, label[i]));
487 	g_object_unref (size_group);
488 
489 	w = go_gtk_builder_get_widget (state->gui, "link-type-image");
490 	state->type_image = GTK_IMAGE (w);
491 	w = go_gtk_builder_get_widget (state->gui, "link-type-descriptor");
492 	state->type_descriptor = GTK_LABEL (w);
493 
494 	w = go_gtk_builder_get_widget (state->gui, "internal-link-grid");
495 	expr_entry = gnm_expr_entry_new (state->wbcg, TRUE);
496 	gtk_widget_set_hexpand (GTK_WIDGET (expr_entry), TRUE);
497 	gtk_container_add (GTK_CONTAINER (w), GTK_WIDGET (expr_entry));
498 	gtk_entry_set_activates_default
499 		(gnm_expr_entry_get_entry (expr_entry), TRUE);
500 	state->internal_link_ee = expr_entry;
501 
502 	w = go_gtk_builder_get_widget (state->gui, "cancel_button");
503 	g_signal_connect (G_OBJECT (w),
504 			  "clicked",
505 			  G_CALLBACK (dhl_cb_cancel), state);
506 
507 	w  = go_gtk_builder_get_widget (state->gui, "ok_button");
508 	g_signal_connect (G_OBJECT (w),
509 			  "clicked",
510 			  G_CALLBACK (dhl_cb_ok), state);
511 	gtk_window_set_default (GTK_WINDOW (state->dialog), w);
512 
513 	gnm_init_help_button (
514 		go_gtk_builder_get_widget (state->gui, "help_button"),
515 		GNUMERIC_HELP_LINK_HYPERLINK);
516 
517 	store = gtk_list_store_new (2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
518 	w = go_gtk_builder_get_widget (state->gui, "link-type-menu");
519 	gtk_combo_box_set_model (GTK_COMBO_BOX (w), GTK_TREE_MODEL (store));
520 	g_object_unref (store);
521 
522 	for (i = 0 ; i < G_N_ELEMENTS (type); i++) {
523 		GdkPixbuf *pixbuf = go_gtk_widget_render_icon_pixbuf
524 			(GTK_WIDGET (wbcg_toplevel (state->wbcg)),
525 			 type[i].icon_name, GTK_ICON_SIZE_MENU);
526 		gtk_list_store_append (store, &iter);
527 		gtk_list_store_set (store, &iter,
528 				    0, pixbuf,
529 				    1, _(type[i].label),
530 				    -1);
531 		g_object_unref (pixbuf);
532 
533 		if (strcmp (G_OBJECT_TYPE_NAME (state->link),
534 			    type [i].name) == 0)
535 			select = i;
536 	}
537 
538 	renderer = gtk_cell_renderer_pixbuf_new ();
539 	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (w),
540 				    renderer,
541 				    FALSE);
542 	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (w), renderer,
543 					"pixbuf", 0,
544 					NULL);
545 
546 	renderer = gtk_cell_renderer_text_new ();
547 	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (w),
548 				    renderer,
549 				    TRUE);
550 	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (w), renderer,
551 					"text", 1,
552 					NULL);
553 	gtk_combo_box_set_active (GTK_COMBO_BOX (w), select);
554 
555 	g_signal_connect (G_OBJECT (w), "changed",
556 			  G_CALLBACK (dhl_cb_menu_changed),
557 			  state);
558 
559 	gnm_link_button_and_entry (go_gtk_builder_get_widget (state->gui, "use-this-tip"),
560 				   go_gtk_builder_get_widget (state->gui, "tip-entry"));
561 
562 	gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state->dialog),
563 					   state->wbcg,
564 					   GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED);
565 
566 	return FALSE;
567 }
568 
569 #define DIALOG_KEY "hyperlink-dialog"
570 void
dialog_hyperlink(WBCGtk * wbcg,SheetControl * sc)571 dialog_hyperlink (WBCGtk *wbcg, SheetControl *sc)
572 {
573 	GtkBuilder *gui;
574 	HyperlinkState* state;
575 	GnmHLink	*link = NULL;
576 	GSList		*ptr;
577 
578 	g_return_if_fail (wbcg != NULL);
579 
580 	if (gnm_dialog_raise_if_exists (wbcg, DIALOG_KEY))
581 		return;
582 
583 	gui = gnm_gtk_builder_load ("res:ui/hyperlink.ui", NULL, GO_CMD_CONTEXT (wbcg));
584         if (gui == NULL)
585                 return;
586 
587 	state = g_new (HyperlinkState, 1);
588 	state->wbcg  = wbcg;
589 	state->wb   = wb_control_get_workbook (GNM_WBC (wbcg));
590 	state->sc   = sc;
591 	state->gui  = gui;
592         state->dialog = go_gtk_builder_get_widget (state->gui, "hyperlink-dialog");
593 
594 	state->use_def_widget = go_gtk_builder_get_widget (state->gui, "use-default-tip");
595 
596 	state->sheet = sc_sheet (sc);
597 	for (ptr = sc_view (sc)->selections; ptr != NULL; ptr = ptr->next) {
598 		GnmRange const *r = ptr->data;
599 		link = sheet_style_region_contains_link (state->sheet, r);
600 		if (link)
601 			break;
602 	}
603 
604 	/* We are creating a new link since the existing link */
605 	/* may be used in many places. */
606 	/* We are duplicating it here rather than in an ok handler in case */
607 	/* The link is changed for a differnet cell in a different view. */
608 	if (link == NULL) {
609 		state->link = gnm_hlink_new (gnm_hlink_url_get_type (), state->sheet);
610 		state->is_new = TRUE;
611 	} else {
612 		state->link = gnm_hlink_new (G_OBJECT_TYPE (link), state->sheet);
613 		state->is_new = FALSE;
614 		gnm_hlink_set_target (state->link, gnm_hlink_get_target (link));
615 		gnm_hlink_set_tip (state->link, gnm_hlink_get_tip (link));
616 	}
617 
618 	if (dhl_init (state)) {
619 		go_gtk_notice_dialog (wbcg_toplevel (wbcg), GTK_MESSAGE_ERROR,
620 				 _("Could not create the hyperlink dialog."));
621 		g_free (state);
622 		return;
623 	}
624 
625 	dhl_setup_type (state);
626 
627 	dhl_set_target (state);
628 	dhl_set_tip (state);
629 
630 	/* a candidate for merging into attach guru */
631 	gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
632 			       DIALOG_KEY);
633 	go_gtk_nonmodal_dialog (wbcg_toplevel (state->wbcg),
634 				   GTK_WINDOW (state->dialog));
635 
636 	wbc_gtk_attach_guru (state->wbcg, state->dialog);
637 	g_object_set_data_full (G_OBJECT (state->dialog),
638 		"state", state, (GDestroyNotify) dhl_free);
639 	gtk_widget_show (state->dialog);
640 }
641