]> Pileus Git - ~andy/gtk/blob - demos/gtk-demo/hypertext.c
Deprecate widget flag: GTK_WIDGET_VISIBLE
[~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 static gboolean hovering_over_link = FALSE;
169 static GdkCursor *hand_cursor = NULL;
170 static 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   GtkTextIter iter;
183   gboolean hovering = FALSE;
184
185   gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
186   
187   tags = gtk_text_iter_get_tags (&iter);
188   for (tagp = tags;  tagp != NULL;  tagp = tagp->next)
189     {
190       GtkTextTag *tag = tagp->data;
191       gint page = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tag), "page"));
192
193       if (page != 0) 
194         {
195           hovering = TRUE;
196           break;
197         }
198     }
199
200   if (hovering != hovering_over_link)
201     {
202       hovering_over_link = hovering;
203
204       if (hovering_over_link)
205         gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), hand_cursor);
206       else
207         gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), regular_cursor);
208     }
209
210   if (tags) 
211     g_slist_free (tags);
212 }
213
214 /* Update the cursor image if the pointer moved. 
215  */
216 static gboolean
217 motion_notify_event (GtkWidget      *text_view,
218                      GdkEventMotion *event)
219 {
220   gint x, y;
221
222   gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), 
223                                          GTK_TEXT_WINDOW_WIDGET,
224                                          event->x, event->y, &x, &y);
225
226   set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), x, y);
227
228   gdk_window_get_pointer (text_view->window, NULL, NULL, NULL);
229   return FALSE;
230 }
231
232 /* Also update the cursor image if the window becomes visible
233  * (e.g. when a window covering it got iconified).
234  */
235 static gboolean
236 visibility_notify_event (GtkWidget          *text_view,
237                          GdkEventVisibility *event)
238 {
239   gint wx, wy, bx, by;
240   
241   gdk_window_get_pointer (text_view->window, &wx, &wy, NULL);
242   
243   gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), 
244                                          GTK_TEXT_WINDOW_WIDGET,
245                                          wx, wy, &bx, &by);
246
247   set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), bx, by);
248
249   return FALSE;
250 }
251
252 GtkWidget *
253 do_hypertext (GtkWidget *do_widget)
254 {
255   static GtkWidget *window = NULL;
256
257   if (!window)
258     {
259       GtkWidget *view;
260       GtkWidget *sw;
261       GtkTextBuffer *buffer;
262
263       hand_cursor = gdk_cursor_new (GDK_HAND2);
264       regular_cursor = gdk_cursor_new (GDK_XTERM);
265       
266       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
267       gtk_window_set_screen (GTK_WINDOW (window),
268                              gtk_widget_get_screen (do_widget));
269       gtk_window_set_default_size (GTK_WINDOW (window),
270                                    450, 450);
271       
272       g_signal_connect (window, "destroy",
273                         G_CALLBACK (gtk_widget_destroyed), &window);
274
275       gtk_window_set_title (GTK_WINDOW (window), "Hypertext");
276       gtk_container_set_border_width (GTK_CONTAINER (window), 0);
277
278       view = gtk_text_view_new ();
279       gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), GTK_WRAP_WORD);
280       g_signal_connect (view, "key-press-event", 
281                         G_CALLBACK (key_press_event), NULL);
282       g_signal_connect (view, "event-after", 
283                         G_CALLBACK (event_after), NULL);
284       g_signal_connect (view, "motion-notify-event", 
285                         G_CALLBACK (motion_notify_event), NULL);
286       g_signal_connect (view, "visibility-notify-event", 
287                         G_CALLBACK (visibility_notify_event), NULL);
288
289       buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
290       
291       sw = gtk_scrolled_window_new (NULL, NULL);
292       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
293                                       GTK_POLICY_AUTOMATIC,
294                                       GTK_POLICY_AUTOMATIC);
295       gtk_container_add (GTK_CONTAINER (window), sw);
296       gtk_container_add (GTK_CONTAINER (sw), view);
297
298       show_page (buffer, 1);
299
300       gtk_widget_show_all (sw);
301     }
302
303   if (!gtk_widget_get_visible (window))
304     {
305       gtk_widget_show (window);
306     }
307   else
308     {
309       gtk_widget_destroy (window);
310       window = NULL;
311     }
312
313   return window;
314 }
315