GUI: Move .ui files from goffice resources to glib resources
[gnumeric.git] / src / dialogs / dialog-hyperlink.c
blob8e71e47a5ce8f73985dbe71ab567a8535a0657b4
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /*
4 * dialog-hyperlink.c: Add or edit a hyperlink
6 * Copyright (C) 2002 Jody Goldberg (jody@gnome.org)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) version 3.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 * USA
24 #include <gnumeric-config.h>
25 #include <gnumeric.h>
26 #include "dialogs.h"
27 #include "help.h"
29 #include <commands.h>
30 #include <widgets/gnumeric-expr-entry.h>
31 #include "expr-name.h"
32 #include "expr.h"
33 #include <gui-util.h>
34 #include <hlink.h>
35 #include <mstyle.h>
36 #include <style-color.h>
37 #include <sheet-control.h>
38 #include <sheet-view.h>
39 #include <sheet-style.h>
40 #include <value.h>
41 #include <wbc-gtk.h>
42 #include <goffice/goffice.h>
44 #include <gtk/gtk.h>
45 #include <glib/gi18n-lib.h>
47 #include <string.h>
50 typedef struct {
51 WBCGtk *wbcg;
52 Workbook *wb;
53 SheetControl *sc;
54 Sheet *sheet;
56 GtkBuilder *gui;
57 GtkWidget *dialog;
59 GtkImage *type_image;
60 GtkLabel *type_descriptor;
61 GnmExprEntry *internal_link_ee;
62 GnmHLink *link;
63 gboolean is_new;
65 GtkWidget *use_def_widget;
66 } HyperlinkState;
68 static void
69 dhl_free (HyperlinkState *state)
71 if (state->gui != NULL) {
72 g_object_unref (state->gui);
73 state->gui = NULL;
75 if (state->link != NULL) {
76 g_object_unref (state->link);
77 state->link = NULL;
79 state->dialog = NULL;
80 g_free (state);
83 static char *
84 dhl_get_default_tip (char const * const target) {
85 char *default_text = _("Left click once to follow this link.\n"
86 "Middle click once to select this cell");
87 if (target == NULL)
88 return g_strdup (default_text);
89 else
90 return g_strjoin ("\n", target, default_text, NULL);
93 static void
94 dhl_set_tip (HyperlinkState* state)
96 char const *tip = gnm_hlink_get_tip (state->link);
97 GtkTextBuffer *tb;
98 GtkWidget *w;
100 if (state->is_new) {
101 w = go_gtk_builder_get_widget (state->gui, "use-default-tip");
102 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), TRUE);
103 return;
106 if (tip != NULL) {
107 char const * const target = gnm_hlink_get_target (state->link);
108 char *default_tip = dhl_get_default_tip (target);
109 gboolean is_default = (strcmp (tip, default_tip) == 0);
110 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->use_def_widget),
111 is_default);
112 g_free (default_tip);
113 if (is_default)
114 return;
116 w = go_gtk_builder_get_widget (state->gui, "use-this-tip");
117 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), TRUE);
119 tb = gtk_text_view_get_buffer
120 (GTK_TEXT_VIEW (go_gtk_builder_get_widget (state->gui, "tip-entry")));
122 gtk_text_buffer_set_text (tb, (tip == NULL) ? "" : tip, -1);
125 static char *
126 dhl_get_tip (HyperlinkState *state, char const *target)
128 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (state->use_def_widget)))
129 return NULL;
130 else {
131 char *tip;
132 GtkTextBuffer *tb = gtk_text_view_get_buffer
133 (GTK_TEXT_VIEW (go_gtk_builder_get_widget (state->gui, "tip-entry")));
134 GtkTextIter start_iter, end_iter;
136 gtk_text_buffer_get_start_iter (tb, &start_iter);
137 gtk_text_buffer_get_end_iter (tb, &end_iter);
139 tip = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
141 if (tip != NULL && strlen (tip) == 0) {
142 g_free (tip);
143 tip = NULL;
146 return tip;
150 static void
151 dhl_set_target_cur_wb (HyperlinkState *state, const char* const target)
153 gnm_expr_entry_load_from_text (state->internal_link_ee, target);
156 static char *
157 dhl_get_target_cur_wb (HyperlinkState *state, gboolean *success)
159 char *ret = NULL;
160 GnmExprEntry *gee = state->internal_link_ee;
161 char const *target = gnm_expr_entry_get_text (gee);
162 Sheet *sheet = sc_sheet (state->sc);
163 GnmValue *val;
165 *success = FALSE;
166 if (strlen (target) == 0) {
167 *success = TRUE;
168 } else {
169 val = gnm_expr_entry_parse_as_value (gee, sheet);
170 if (!val) {
171 /* not an address, is it a name ? */
172 GnmParsePos pp;
173 GnmNamedExpr *nexpr;
175 parse_pos_init_sheet (&pp, sheet);
176 nexpr = expr_name_lookup (&pp, target);
177 if (nexpr != NULL)
178 val = gnm_expr_top_get_range (nexpr->texpr);
180 if (val) {
181 *success = TRUE;
182 ret = g_strdup (target);
183 value_release (val);
184 } else {
185 go_gtk_notice_dialog (GTK_WINDOW (state->dialog),
186 GTK_MESSAGE_ERROR,
187 _("Not a range or name"));
188 gnm_expr_entry_grab_focus (gee, TRUE);
191 return ret;
194 static void
195 dhl_set_target_external (HyperlinkState *state, const char* const target)
197 GtkWidget *w = go_gtk_builder_get_widget (state->gui, "external-link");
199 gtk_entry_set_text (GTK_ENTRY (w), target);
202 static char *
203 dhl_get_target_external (HyperlinkState *state, gboolean *success)
205 GtkWidget *w = go_gtk_builder_get_widget (state->gui, "external-link");
206 const char *target = gtk_entry_get_text (GTK_ENTRY (w));
208 *success = TRUE;
209 return strlen (target) > 0 ? g_strdup (target) : NULL;
212 static void
213 dhl_set_target_email (HyperlinkState *state, const char* const target)
215 GtkWidget *w = go_gtk_builder_get_widget (state->gui, "email-address");
216 GtkWidget *w2 = go_gtk_builder_get_widget (state->gui, "email-subject");
217 gchar* cursor;
218 gchar* subject;
219 gchar* guitext;
221 if (!target || *target == '\0')
222 return;
224 if( strncmp (target, "mailto:", strlen ("mailto:")) != 0)
225 return;
227 cursor = g_strdup (target + strlen ("mailto:"));
229 subject = strstr (cursor, "?subject=");
230 if (subject) {
231 guitext = g_uri_unescape_string (subject + strlen ("?subject="),
232 NULL);
233 gtk_entry_set_text (GTK_ENTRY (w2), guitext);
234 *subject = '\0';
235 g_free (guitext);
238 guitext = g_uri_unescape_string (cursor, NULL);
240 gtk_entry_set_text (GTK_ENTRY (w), guitext);
242 g_free (guitext);
243 g_free (cursor);
246 static char*
247 dhl_get_target_email (HyperlinkState *state, gboolean *success)
249 GtkWidget *w = go_gtk_builder_get_widget (state->gui, "email-address");
250 GtkWidget *w2 = go_gtk_builder_get_widget (state->gui, "email-subject");
251 const char *address = gtk_entry_get_text (GTK_ENTRY (w));
252 const char *subject = gtk_entry_get_text (GTK_ENTRY (w2));
253 gchar* enc_subj, *enc_addr;
254 gchar* result;
256 *success = TRUE;
257 if (!address || *address == '\0') {
258 return NULL;
261 enc_addr = go_url_encode (address, 0);
262 if (!subject || *subject == '\0') {
263 result = g_strconcat ("mailto:", enc_addr, NULL);
264 } else {
265 enc_subj = go_url_encode (subject, 0);
267 result = g_strconcat ("mailto:", enc_addr,
268 "?subject=", enc_subj, NULL);
269 g_free (enc_subj);
272 g_free (enc_addr);
274 return result;
277 static void
278 dhl_set_target_url (HyperlinkState *state, const char* const target)
280 GtkWidget *w = go_gtk_builder_get_widget (state->gui, "url");
282 gtk_entry_set_text (GTK_ENTRY (w), target);
285 static char *
286 dhl_get_target_url (HyperlinkState *state, gboolean *success)
288 GtkWidget *w = go_gtk_builder_get_widget (state->gui, "url");
289 const char *target = gtk_entry_get_text (GTK_ENTRY (w));
291 *success = TRUE;
292 return strlen (target) > 0 ? g_strdup (target) : NULL;
295 static struct {
296 char const *label;
297 char const *icon_name;
298 char const *name;
299 char const *widget_name;
300 char const *descriptor;
301 void (*set_target) (HyperlinkState *state, const char* const target);
302 char * (*get_target) (HyperlinkState *state, gboolean *success);
303 } const type[] = {
304 { N_("Internal Link"), "gnumeric-link-internal",
305 "GnmHLinkCurWB", "internal-link-grid",
306 N_("Jump to specific cells or named range in the current workbook"),
307 dhl_set_target_cur_wb,
308 dhl_get_target_cur_wb },
310 { N_("External Link"), "gnumeric-link-external",
311 "GnmHLinkExternal", "external-link-grid" ,
312 N_("Open an external file with the specified name"),
313 dhl_set_target_external,
314 dhl_get_target_external },
315 { N_("Email Link"), "gnumeric-link-email",
316 "GnmHLinkEMail", "email-grid" ,
317 N_("Prepare an email"),
318 dhl_set_target_email,
319 dhl_get_target_email },
320 { N_("Web Link"), "gnumeric-link-url",
321 "GnmHLinkURL", "url-grid" ,
322 N_("Browse to the specified URL"),
323 dhl_set_target_url,
324 dhl_get_target_url }
327 static void
328 dhl_set_target (HyperlinkState* state)
330 unsigned i;
331 char const * const target = gnm_hlink_get_target (state->link);
332 char const * type_name;
334 if (target) {
335 type_name = G_OBJECT_TYPE_NAME (state->link);
336 for (i = 0 ; i < G_N_ELEMENTS (type); i++) {
337 if (strcmp (type_name, type[i].name) == 0) {
338 if (type[i].set_target)
339 (type[i].set_target) (state, target);
340 break;
346 static char *
347 dhl_get_target (HyperlinkState *state, gboolean *success)
349 unsigned i;
350 char const *type_name = G_OBJECT_TYPE_NAME (state->link);
352 *success = FALSE;
353 for (i = 0 ; i < G_N_ELEMENTS (type); i++) {
354 if (strcmp (type_name, type[i].name) == 0) {
355 if (type[i].get_target)
356 return (type[i].get_target) (state, success);
357 break;
361 return NULL;
365 static void
366 dhl_cb_cancel (G_GNUC_UNUSED GtkWidget *button, HyperlinkState *state)
368 gtk_widget_destroy (state->dialog);
371 static void
372 dhl_cb_ok (G_GNUC_UNUSED GtkWidget *button, HyperlinkState *state)
374 GnmStyle *style;
375 char *cmdname;
376 char *target;
377 char *tip;
378 gboolean success;
380 target = dhl_get_target (state, &success);
381 if (!success)
382 return; /* Let user continue editing */
384 wb_control_sheet_focus (GNM_WBC (state->wbcg), state->sheet);
386 if (target) {
387 gnm_hlink_set_sheet (state->link, state->sheet);
388 gnm_hlink_set_target (state->link, target);
389 tip = dhl_get_tip (state, target);
390 gnm_hlink_set_tip (state->link, tip);
391 g_free (tip);
392 style = gnm_style_new ();
393 gnm_style_set_hlink (style, g_object_ref (state->link));
394 gnm_style_set_font_uline (style, UNDERLINE_SINGLE);
395 gnm_style_set_font_color (style, gnm_color_new_go (GO_COLOR_BLUE));
397 if (state->is_new) {
398 cmdname = _("Add Hyperlink");
399 cmd_selection_hyperlink (GNM_WBC (state->wbcg),
400 style,
401 cmdname, target);
402 } else {
403 cmdname = _("Edit Hyperlink");
404 cmd_selection_hyperlink (GNM_WBC (state->wbcg),
405 style,
406 cmdname, NULL);
407 g_free (target);
409 } else if (!state->is_new) {
410 style = gnm_style_new ();
411 gnm_style_set_hlink (style, NULL);
412 cmdname = _("Remove Hyperlink");
413 cmd_selection_hyperlink (GNM_WBC (state->wbcg), style,
414 cmdname, NULL);
416 gtk_widget_destroy (state->dialog);
419 static void
420 dhl_setup_type (HyperlinkState *state)
422 GtkWidget *w;
423 char const *name = G_OBJECT_TYPE_NAME (state->link);
424 unsigned i;
426 for (i = 0 ; i < G_N_ELEMENTS (type); i++) {
427 w = go_gtk_builder_get_widget (state->gui, type[i].widget_name);
429 if (!strcmp (name, type[i].name)) {
430 gtk_widget_show_all (w);
431 gtk_image_set_from_icon_name
432 (state->type_image, type[i].icon_name,
433 GTK_ICON_SIZE_DIALOG);
434 gtk_label_set_text (state->type_descriptor,
435 _(type[i].descriptor));
436 } else
437 gtk_widget_hide (w);
441 static void
442 dhl_set_type (HyperlinkState *state, GType type)
444 GnmHLink *old = state->link;
446 state->link = gnm_hlink_new (type, state->sheet);
447 if (old != NULL) {
448 gnm_hlink_set_target (state->link, gnm_hlink_get_target (old));
449 gnm_hlink_set_tip (state->link, gnm_hlink_get_tip (old));
450 g_object_unref (old);
452 dhl_setup_type (state);
455 static void
456 dhl_cb_menu_changed (GtkComboBox *box, HyperlinkState *state)
458 int i = gtk_combo_box_get_active (box);
459 dhl_set_type (state, g_type_from_name (
460 type [i].name));
463 static gboolean
464 dhl_init (HyperlinkState *state)
466 static char const * const label[] = {
467 "internal-link-label",
468 "external-link-label",
469 "email-address-label",
470 "email-subject-label",
471 "url-label",
472 "use-this-tip"
474 GtkWidget *w;
475 GtkSizeGroup *size_group;
476 GnmExprEntry *expr_entry;
477 unsigned i, select = 0;
478 GtkListStore *store;
479 GtkTreeIter iter;
480 GtkCellRenderer *renderer;
482 #ifdef GNM_NO_MAILTO
483 gtk_widget_hide (go_gtk_builder_get_widget (state->gui, "email-grid"));
484 #endif
485 size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
486 for (i = 0 ; i < G_N_ELEMENTS (label); i++)
487 gtk_size_group_add_widget (size_group,
488 go_gtk_builder_get_widget (state->gui, label[i]));
489 g_object_unref (size_group);
491 w = go_gtk_builder_get_widget (state->gui, "link-type-image");
492 state->type_image = GTK_IMAGE (w);
493 w = go_gtk_builder_get_widget (state->gui, "link-type-descriptor");
494 state->type_descriptor = GTK_LABEL (w);
496 w = go_gtk_builder_get_widget (state->gui, "internal-link-grid");
497 expr_entry = gnm_expr_entry_new (state->wbcg, TRUE);
498 gtk_widget_set_hexpand (GTK_WIDGET (expr_entry), TRUE);
499 gtk_container_add (GTK_CONTAINER (w), GTK_WIDGET (expr_entry));
500 gtk_entry_set_activates_default
501 (gnm_expr_entry_get_entry (expr_entry), TRUE);
502 state->internal_link_ee = expr_entry;
504 w = go_gtk_builder_get_widget (state->gui, "cancel_button");
505 g_signal_connect (G_OBJECT (w),
506 "clicked",
507 G_CALLBACK (dhl_cb_cancel), state);
509 w = go_gtk_builder_get_widget (state->gui, "ok_button");
510 g_signal_connect (G_OBJECT (w),
511 "clicked",
512 G_CALLBACK (dhl_cb_ok), state);
513 gtk_window_set_default (GTK_WINDOW (state->dialog), w);
515 gnm_init_help_button (
516 go_gtk_builder_get_widget (state->gui, "help_button"),
517 GNUMERIC_HELP_LINK_HYPERLINK);
519 store = gtk_list_store_new (2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
520 w = go_gtk_builder_get_widget (state->gui, "link-type-menu");
521 gtk_combo_box_set_model (GTK_COMBO_BOX (w), GTK_TREE_MODEL (store));
522 g_object_unref (store);
524 for (i = 0 ; i < G_N_ELEMENTS (type); i++) {
525 GdkPixbuf *pixbuf = go_gtk_widget_render_icon_pixbuf
526 (GTK_WIDGET (wbcg_toplevel (state->wbcg)),
527 type[i].icon_name, GTK_ICON_SIZE_MENU);
528 gtk_list_store_append (store, &iter);
529 gtk_list_store_set (store, &iter,
530 0, pixbuf,
531 1, _(type[i].label),
532 -1);
533 g_object_unref (pixbuf);
535 if (strcmp (G_OBJECT_TYPE_NAME (state->link),
536 type [i].name) == 0)
537 select = i;
540 renderer = gtk_cell_renderer_pixbuf_new ();
541 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (w),
542 renderer,
543 FALSE);
544 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (w), renderer,
545 "pixbuf", 0,
546 NULL);
548 renderer = gtk_cell_renderer_text_new ();
549 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (w),
550 renderer,
551 TRUE);
552 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (w), renderer,
553 "text", 1,
554 NULL);
555 gtk_combo_box_set_active (GTK_COMBO_BOX (w), select);
557 g_signal_connect (G_OBJECT (w), "changed",
558 G_CALLBACK (dhl_cb_menu_changed),
559 state);
561 gnm_link_button_and_entry (go_gtk_builder_get_widget (state->gui, "use-this-tip"),
562 go_gtk_builder_get_widget (state->gui, "tip-entry"));
564 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state->dialog),
565 state->wbcg,
566 GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED);
568 return FALSE;
571 #define DIALOG_KEY "hyperlink-dialog"
572 void
573 dialog_hyperlink (WBCGtk *wbcg, SheetControl *sc)
575 GtkBuilder *gui;
576 HyperlinkState* state;
577 GnmHLink *link = NULL;
578 GSList *ptr;
580 g_return_if_fail (wbcg != NULL);
582 if (gnm_dialog_raise_if_exists (wbcg, DIALOG_KEY))
583 return;
585 gui = gnm_gtk_builder_load ("res:ui/hyperlink.ui", NULL, GO_CMD_CONTEXT (wbcg));
586 if (gui == NULL)
587 return;
589 state = g_new (HyperlinkState, 1);
590 state->wbcg = wbcg;
591 state->wb = wb_control_get_workbook (GNM_WBC (wbcg));
592 state->sc = sc;
593 state->gui = gui;
594 state->dialog = go_gtk_builder_get_widget (state->gui, "hyperlink-dialog");
596 state->use_def_widget = go_gtk_builder_get_widget (state->gui, "use-default-tip");
598 state->sheet = sc_sheet (sc);
599 for (ptr = sc_view (sc)->selections; ptr != NULL; ptr = ptr->next) {
600 GnmRange const *r = ptr->data;
601 link = sheet_style_region_contains_link (state->sheet, r);
602 if (link)
603 break;
606 /* We are creating a new link since the existing link */
607 /* may be used in many places. */
608 /* We are duplicating it here rather than in an ok handler in case */
609 /* The link is changed for a differnet cell in a different view. */
610 if (link == NULL) {
611 state->link = gnm_hlink_new (gnm_hlink_url_get_type (), state->sheet);
612 state->is_new = TRUE;
613 } else {
614 state->link = gnm_hlink_new (G_OBJECT_TYPE (link), state->sheet);
615 state->is_new = FALSE;
616 gnm_hlink_set_target (state->link, gnm_hlink_get_target (link));
617 gnm_hlink_set_tip (state->link, gnm_hlink_get_tip (link));
620 if (dhl_init (state)) {
621 go_gtk_notice_dialog (wbcg_toplevel (wbcg), GTK_MESSAGE_ERROR,
622 _("Could not create the hyperlink dialog."));
623 g_free (state);
624 return;
627 dhl_setup_type (state);
629 dhl_set_target (state);
630 dhl_set_tip (state);
632 /* a candidate for merging into attach guru */
633 gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
634 DIALOG_KEY);
635 go_gtk_nonmodal_dialog (wbcg_toplevel (state->wbcg),
636 GTK_WINDOW (state->dialog));
638 wbc_gtk_attach_guru (state->wbcg, state->dialog);
639 g_object_set_data_full (G_OBJECT (state->dialog),
640 "state", state, (GDestroyNotify) dhl_free);
641 gtk_widget_show (state->dialog);