1f40bc3c4faa151ee1210dd21a7cc4a1f030c461
[projects/chimara/chimara.git] / libchimara / hyperlink.c
1 #include <config.h>
2 #include <glib/gi18n-lib.h>
3 #include "hyperlink.h"
4 #include "chimara-glk-private.h"
5 #include "magic.h"
6
7 extern GPrivate *glk_data_key;
8
9 /**
10  * glk_set_hyperlink:
11  * @linkval: Set to nonzero to initiate hyperlink mode. Set to zero to disengage.
12  *
13  * This call sets the current link value in the current output stream. See
14  * glk_set_hyperlink_stream().
15  */
16 void 
17 glk_set_hyperlink(glui32 linkval)
18 {
19         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
20         g_return_if_fail(glk_data->current_stream != NULL);
21         glk_set_hyperlink_stream(glk_data->current_stream, linkval);
22 }
23
24 /**
25  * glk_set_hyperlink_stream:
26  * @str: The stream to set the hyperlink mode on.
27  * @linkval: Set to non-zero to initiate hyperlink mode. Set to zero to 
28  * disengage.
29  *
30  * This call sets the current link value on the specified output stream. A link
31  * value is any non-zero integer; zero indicates no link. Subsequent text output
32  * is considered to make up the body of the link, which continues until the link
33  * value is changed (or set to zero).
34  *
35  * Note that it is almost certainly useless to change the link value of a stream
36  * twice with no intervening text. The result will be a zero-length link, which 
37  * the player probably cannot see or select; the library may optimize it out 
38  * entirely.
39  *
40  * Setting the link value of a stream to the value it already has, has no 
41  * effect.
42  *
43  * If the library supports images, they take on the current link value as they 
44  * are output, just as text does. The player can select an image in a link just 
45  * as he does text. (This includes margin-aligned images, which can lead to some 
46  * peculiar situations, since a right-margin image may not appear directly 
47  * adjacent to the text it was output with.)
48  * 
49  * The library will attempt to display links in some distinctive way (and it 
50  * will do this whether or not hyperlink input has actually been requested for 
51  * the window). Naturally, blue underlined text is most likely. Link images may 
52  * not be distinguished from non-link images, so it is best not to use a 
53  * particular image both ways. 
54  */
55 void 
56 glk_set_hyperlink_stream(strid_t str, glui32 linkval)
57 {
58         g_return_if_fail(str != NULL);
59         g_return_if_fail(str->type == STREAM_TYPE_WINDOW);
60         g_return_if_fail(str->window != NULL);
61         g_return_if_fail(str->window->type == wintype_TextBuffer || str->window->type == wintype_TextGrid);
62
63         flush_window_buffer(str->window);
64
65         if(linkval == 0) {
66                 /* Turn off hyperlink mode */
67                 str->hyperlink_mode = FALSE;
68                 str->window->current_hyperlink = NULL;
69                 return;
70         }
71
72         /* Check whether a tag with the needed value already exists */
73         hyperlink_t *new_hyperlink = g_hash_table_lookup(str->window->hyperlinks, &linkval);
74         if(new_hyperlink == NULL) {
75                 /* Create a new hyperlink with the requested value */
76                 new_hyperlink = g_new0(struct hyperlink, 1);
77                 new_hyperlink->value = linkval;
78                 new_hyperlink->tag = gtk_text_tag_new(NULL);
79                 new_hyperlink->event_handler = g_signal_connect( new_hyperlink->tag, "event", G_CALLBACK(on_hyperlink_clicked), new_hyperlink );
80                 if(!str->window->hyperlink_event_requested)
81                         g_signal_handler_block(new_hyperlink->tag, new_hyperlink->event_handler);
82                 new_hyperlink->window = str->window;
83
84                 /* Add the new tag to the tag table of the textbuffer */
85                 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(str->window->widget) );
86                 GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(textbuffer);
87                 gtk_text_tag_table_add(tags, new_hyperlink->tag);
88
89                 gint *linkval_pointer = g_new0(gint, 1);
90                 *linkval_pointer = linkval;
91                 g_hash_table_insert(str->window->hyperlinks, linkval_pointer, new_hyperlink);
92         }
93
94         str->hyperlink_mode = TRUE;
95         str->window->current_hyperlink = new_hyperlink;
96 }
97
98 /* Internal function used to iterate over all the hyperlinks, unblocking the event handler */
99 void
100 hyperlink_unblock_event_handler(gpointer key, gpointer value, gpointer user_data)
101 {
102         hyperlink_t *link = (hyperlink_t *) value;
103         g_signal_handler_unblock(link->tag, link->event_handler);
104 }
105
106 /* Internal function used to iterate over all the hyperlinks, blocking the event handler */
107 void
108 hyperlink_block_event_handler(gpointer key, gpointer value, gpointer user_data)
109 {
110         hyperlink_t *link = (hyperlink_t *) value;
111         g_signal_handler_block(link->tag, link->event_handler);
112 }
113
114 /**
115  * glk_request_hyperlink_event:
116  * @win: The window to request a hyperlink event on.
117  *
118  * This call works like glk_request_char_event(), glk_request_line_event() and
119  * glk_request_mouse_event(). A pending request on a window remains pending 
120  * until the player selects a link, or the request is cancelled.
121  *
122  * A window can have hyperlink input and mouse, character, or line input pending
123  * at the same time. However, if hyperlink and mouse input are requested at the
124  * same time, the library may not provide an intuitive way for the player to 
125  * distingish which a mouse click represents. Therefore, this combination should
126  * be avoided.
127  *
128  * When a link is selected in a window with a pending request, glk_select() will
129  * return an event of type %evtype_Hyperlink. In the event structure, @win tells
130  * what window the event came from, and @val1 gives the (non-zero) link value.
131  * 
132  * If no hyperlink request is pending in a window, the library will ignore 
133  * attempts to select a link. No %evtype_Hyperlink event will be generated 
134  * unless it has been requested. 
135  */
136 void 
137 glk_request_hyperlink_event(winid_t win)
138 {
139         VALID_WINDOW(win, return);
140         g_return_if_fail(win != NULL);
141         g_return_if_fail(win->type == wintype_TextBuffer || win->type == wintype_TextGrid);
142
143         if(win->hyperlink_event_requested) {
144                 WARNING(_("Tried to request a hyperlink event on a window that already had a hyperlink request"));
145                 return;
146         }
147
148         win->hyperlink_event_requested = TRUE;
149         g_hash_table_foreach(win->hyperlinks, hyperlink_unblock_event_handler, NULL);
150
151 }
152
153 /**
154  * glk_cancel_hyperlink_event:
155  * @win: The window in which to cancel the hyperlink event request.
156  *
157  * This call works like glk_cancel_char_event(), glk_cancel_line_event(), and
158  * glk_cancel_mouse_event(). See glk_request_hyperlink_event().
159  */
160 void 
161 glk_cancel_hyperlink_event(winid_t win)
162 {
163         VALID_WINDOW(win, return);
164         g_return_if_fail(win != NULL);
165         g_return_if_fail(win->type == wintype_TextBuffer || win->type == wintype_TextGrid);
166
167         if(!win->hyperlink_event_requested) {
168                 WARNING(_("Tried to cancel a nonexistent hyperlink request"));
169                 return;
170         }
171
172         win->hyperlink_event_requested = FALSE;
173         g_hash_table_foreach(win->hyperlinks, hyperlink_block_event_handler, NULL);
174 }
175
176 gboolean
177 on_hyperlink_clicked(GtkTextTag *tag, GObject *object, GdkEvent *event, GtkTextIter *iter, hyperlink_t *link)
178 {
179         ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(link->window->widget, CHIMARA_TYPE_GLK));
180         g_assert(glk);
181
182         if(event->type == GDK_BUTTON_PRESS) {
183                 link->window->hyperlink_event_requested = FALSE;
184                 g_hash_table_foreach(link->window->hyperlinks, hyperlink_block_event_handler, NULL);
185                 event_throw(glk, evtype_Hyperlink, link->window, link->value, 0);
186         }
187
188         return FALSE;
189 }