]> Pileus Git - ~andy/gtk/blob - demos/gtk-demo/hypertext.c
Don't include config.h in the examples.
[~andy/gtk] / demos / gtk-demo / hypertext.c
1 /* Text Widget/Hypertext
2  *
3  * Usually, tags modify the appearance of text in the view, e.g. making it 
4  * bold or colored or underlined. But tags are not restricted to appearance. 
5  * They can also affect the behavior of mouse and key presses, as this demo 
6  * shows.
7  */
8
9 #include <gtk/gtk.h>
10 #include <gdk/gdkkeysyms.h>
11
12 /* Inserts a piece of text into the buffer, giving it the usual
13  * appearance of a hyperlink in a web browser: blue and underlined.
14  * Additionally, attaches some data on the tag, to make it recognizable
15  * as a link. 
16  */
17 static void 
18 insert_link (GtkTextBuffer *buffer, 
19              GtkTextIter   *iter, 
20              gchar         *text, 
21              gint           page)
22 {
23   GtkTextTag *tag;
24   
25   tag = gtk_text_buffer_create_tag (buffer, NULL, 
26                                     "foreground", "blue", 
27                                     "underline", PANGO_UNDERLINE_SINGLE, 
28                                     NULL);
29   g_object_set_data (G_OBJECT (tag), "page", GINT_TO_POINTER (page));
30   gtk_text_buffer_insert_with_tags (buffer, iter, text, -1, tag, NULL);
31 }
32
33 /* Fills the buffer with text and interspersed links. In any real
34  * hypertext app, this method would parse a file to identify the links.
35  */
36 static void
37 show_page (GtkTextBuffer *buffer, 
38            gint           page)
39 {
40   GtkTextIter iter;
41
42   gtk_text_buffer_set_text (buffer, "", 0);
43   gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
44   if (page == 1)
45     {
46       gtk_text_buffer_insert (buffer, &iter, "Some text to show that simple ", -1);
47       insert_link (buffer, &iter, "hypertext", 3);
48       gtk_text_buffer_insert (buffer, &iter, " can easily be realized with ", -1);
49       insert_link (buffer, &iter, "tags", 2);
50       gtk_text_buffer_insert (buffer, &iter, ".", -1);
51     }
52   else if (page == 2)
53     {
54       gtk_text_buffer_insert (buffer, &iter, 
55                               "A tag is an attribute that can be applied to some range of text. "
56                               "For example, a tag might be called \"bold\" and make the text inside "
57                               "the tag bold. However, the tag concept is more general than that; "
58                               "tags don't have to affect appearance. They can instead affect the "
59                               "behavior of mouse and key presses, \"lock\" a range of text so the "
60                               "user can't edit it, or countless other things.\n", -1);
61       insert_link (buffer, &iter, "Go back", 1);
62     }
63   else if (page == 3) 
64     {
65       GtkTextTag *tag;
66   
67       tag = gtk_text_buffer_create_tag (buffer, NULL, 
68                                         "weight", PANGO_WEIGHT_BOLD, 
69                                         NULL);
70       gtk_text_buffer_insert_with_tags (buffer, &iter, "hypertext:\n", -1, tag, NULL);
71       gtk_text_buffer_insert (buffer, &iter, 
72                               "machine-readable text that is not sequential but is organized "
73                               "so that related items of information are connected.\n", -1);
74       insert_link (buffer, &iter, "Go back", 1);
75     }
76 }
77
78 /* Looks at all tags covering the position of iter in the text view, 
79  * and if one of them is a link, follow it by showing the page identified
80  * by the data attached to it.
81  */
82 static void
83 follow_if_link (GtkWidget   *text_view, 
84                 GtkTextIter *iter)
85 {
86   GSList *tags = NULL, *tagp = NULL;
87
88   tags = gtk_text_iter_get_tags (iter);
89   for (tagp = tags;  tagp != NULL;  tagp = tagp->next)
90     {
91       GtkTextTag *tag = tagp->data;
92       gint page = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tag), "page"));
93
94       if (page != 0)
95         {
96           show_page (gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)), page);
97           break;
98         }
99     }
100
101   if (tags) 
102     g_slist_free (tags);
103 }
104
105 /* Links can be activated by pressing Enter.
106  */
107 static gboolean
108 key_press_event (GtkWidget *text_view,
109                  GdkEventKey *event)
110 {
111   GtkTextIter iter;
112   GtkTextBuffer *buffer;
113
114   switch (event->keyval)
115     {
116       case GDK_Return: 
117       case GDK_KP_Enter:
118         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
119         gtk_text_buffer_get_iter_at_mark (buffer, &iter, 
120                                           gtk_text_buffer_get_insert (buffer));
121         follow_if_link (text_view, &iter);
122         break;
123
124       default:
125         break;
126     }
127
128   return FALSE;
129 }
130
131 /* Links can also be activated by clicking.
132  */
133 static gboolean
134 event_after (GtkWidget *text_view,
135              GdkEvent  *ev)
136 {
137   GtkTextIter start, end, iter;
138   GtkTextBuffer *buffer;
139   GdkEventButton *event;
140   gint x, y;
141
142   if (ev->type != GDK_BUTTON_RELEASE)
143     return FALSE;
144
145   event = (GdkEventButton *)ev;
146
147   if (event->button != 1)
148     return FALSE;
149
150   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
151
152   /* we shouldn't follow a link if the user has selected something */
153   gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
154   if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end))
155     return FALSE;
156
157   gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), 
158                                          GTK_TEXT_WINDOW_WIDGET,
159                                          event->x, event->y, &x, &y);
160
161   gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), &iter, x, y);
162
163   follow_if_link (text_view, &iter);
164
165   return FALSE;
166 }
167
168 gboolean hovering_over_link = FALSE;
169 GdkCursor *hand_cursor = NULL;
170 GdkCursor *regular_cursor = NULL;
171
172 /* Looks at all tags covering the position (x, y) in the text view, 
173  * and if one of them is a link, change the cursor to the "hands" cursor
174  * typically used by web browsers.
175  */
176 static void
177 set_cursor_if_appropriate (GtkTextView    *text_view,
178                            gint            x,
179                            gint            y)
180 {
181   GSList *tags = NULL, *tagp = NULL;
182   GtkTextBuffer *buffer;
183   GtkTextIter iter;
184   gboolean hovering = FALSE;
185
186   buffer = gtk_text_view_get_buffer (text_view);
187
188   gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
189   
190   tags = gtk_text_iter_get_tags (&iter);
191   for (tagp = tags;  tagp != NULL;  tagp = tagp->next)
192     {
193       GtkTextTag *tag = tagp->data;
194       gint page = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tag), "page"));
195
196       if (page != 0) 
197         {
198           hovering = TRUE;
199           break;
200         }
201     }
202
203   if (hovering != hovering_over_link)
204     {
205       hovering_over_link = hovering;
206
207       if (hovering_over_link)
208         gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), hand_cursor);
209       else
210         gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), regular_cursor);
211     }
212
213   if (tags) 
214     g_slist_free (tags);
215 }
216
217 /* Update the cursor image if the pointer moved. 
218  */
219 static gboolean
220 motion_notify_event (GtkWidget      *text_view,
221                      GdkEventMotion *event)
222 {
223   gint x, y;
224
225   gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), 
226                                          GTK_TEXT_WINDOW_WIDGET,
227                                          event->x, event->y, &x, &y);
228
229   set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), x, y);
230
231   gdk_window_get_pointer (text_view->window, NULL, NULL, NULL);
232   return FALSE;
233 }
234
235 /* Also update the cursor image if the window becomes visible
236  * (e.g. when a window covering it got iconified).
237  */
238 static gboolean
239 visibility_notify_event (GtkWidget          *text_view,
240                          GdkEventVisibility *event)
241 {
242   gint wx, wy, bx, by;
243   
244   gdk_window_get_pointer (text_view->window, &wx, &wy, NULL);
245   
246   gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), 
247                                          GTK_TEXT_WINDOW_WIDGET,
248                                          wx, wy, &bx, &by);
249
250   set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), bx, by);
251
252   return FALSE;
253 }
254
255 GtkWidget *
256 do_hypertext (GtkWidget *do_widget)
257 {
258   static GtkWidget *window = NULL;
259
260   if (!window)
261     {
262       GtkWidget *view;
263       GtkWidget *sw;
264       GtkTextBuffer *buffer;
265
266       hand_cursor = gdk_cursor_new (GDK_HAND2);
267       regular_cursor = gdk_cursor_new (GDK_XTERM);
268       
269       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
270       gtk_window_set_screen (GTK_WINDOW (window),
271                              gtk_widget_get_screen (do_widget));
272       gtk_window_set_default_size (GTK_WINDOW (window),
273                                    450, 450);
274       
275       g_signal_connect (window, "destroy",
276                         G_CALLBACK (gtk_widget_destroyed), &window);
277
278       gtk_window_set_title (GTK_WINDOW (window), "Hypertext");
279       gtk_container_set_border_width (GTK_CONTAINER (window), 0);
280
281       view = gtk_text_view_new ();
282       gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), GTK_WRAP_WORD);
283       g_signal_connect (view, "key-press-event", 
284                         G_CALLBACK (key_press_event), NULL);
285       g_signal_connect (view, "event-after", 
286                         G_CALLBACK (event_after), NULL);
287       g_signal_connect (view, "motion-notify-event", 
288                         G_CALLBACK (motion_notify_event), NULL);
289       g_signal_connect (view, "visibility-notify-event", 
290                         G_CALLBACK (visibility_notify_event), NULL);
291
292       buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
293       
294       sw = gtk_scrolled_window_new (NULL, NULL);
295       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
296                                       GTK_POLICY_AUTOMATIC,
297                                       GTK_POLICY_AUTOMATIC);
298       gtk_container_add (GTK_CONTAINER (window), sw);
299       gtk_container_add (GTK_CONTAINER (sw), view);
300
301       show_page (buffer, 1);
302
303       gtk_widget_show_all (sw);
304     }
305
306   if (!GTK_WIDGET_VISIBLE (window))
307     {
308       gtk_widget_show (window);
309     }
310   else
311     {
312       gtk_widget_destroy (window);
313       window = NULL;
314     }
315
316   return window;
317 }
318