From fae5833c8925fe635f5a7454e224bf6f977fd593 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 5 Aug 2003 19:16:01 +0000 Subject: [PATCH] Add a "Hypertext" demo. --- demos/gtk-demo/Makefile.am | 3 +- demos/gtk-demo/hypertext.c | 315 +++++++++++++++++++++++++++++++++++++ demos/gtk-demo/textview.c | 2 +- 3 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 demos/gtk-demo/hypertext.c diff --git a/demos/gtk-demo/Makefile.am b/demos/gtk-demo/Makefile.am index c12164382b..f04ec82bb6 100644 --- a/demos/gtk-demo/Makefile.am +++ b/demos/gtk-demo/Makefile.am @@ -21,7 +21,8 @@ demos = \ sizegroup.c \ stock_browser.c \ textview.c \ - tree_store.c + tree_store.c \ + hypertext.c INCLUDES = \ -DDEMOCODEDIR="\"$(democodedir)\"" \ diff --git a/demos/gtk-demo/hypertext.c b/demos/gtk-demo/hypertext.c new file mode 100644 index 0000000000..b346a1f6dc --- /dev/null +++ b/demos/gtk-demo/hypertext.c @@ -0,0 +1,315 @@ +/* Text Widget/Hypertext + * + * Usually, tags modify the appearance of text in the view, e.g. making it bold or colored or underlined. + * But tags are not restricted to appearance. They can also affect the behavior of mouse and key presses, + * as this demo shows. + */ + +#include +#include + +/* Inserts a piece of text into the buffer, giving it the usual + * appearance of a hyperlink in a web browser: blue and underlined. + * Additionally, attaches some data on the tag, to make it recognizable + * as a link. + */ +static void +insert_link (GtkTextBuffer *buffer, + GtkTextIter *iter, + gchar *text, + gint page) +{ + GtkTextTag *tag; + + tag = gtk_text_buffer_create_tag (buffer, NULL, + "foreground", "blue", + "underline", PANGO_UNDERLINE_SINGLE, + NULL); + g_object_set_data (G_OBJECT (tag), "page", GINT_TO_POINTER (page)); + gtk_text_buffer_insert_with_tags (buffer, iter, text, -1, tag, NULL); +} + +/* Fills the buffer with text and interspersed links. In any real + * hypertext app, this method would parse a file to identify the links. + */ +static void +show_page (GtkTextBuffer *buffer, + gint page) +{ + GtkTextIter iter; + + gtk_text_buffer_set_text (buffer, "", 0); + gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0); + if (page == 1) + { + gtk_text_buffer_insert (buffer, &iter, "Some text to show that simple ", -1); + insert_link (buffer, &iter, "hypertext", 3); + gtk_text_buffer_insert (buffer, &iter, " can easily be realized with ", -1); + insert_link (buffer, &iter, "tags", 2); + gtk_text_buffer_insert (buffer, &iter, ".", -1); + } + else if (page == 2) + { + gtk_text_buffer_insert (buffer, &iter, + "A tag is an attribute that can be applied to some range of text. " + "For example, a tag might be called \"bold\" and make the text inside " + "the tag bold. However, the tag concept is more general than that; " + "tags don't have to affect appearance. They can instead affect the " + "behavior of mouse and key presses, \"lock\" a range of text so the " + "user can't edit it, or countless other things.\n", -1); + insert_link (buffer, &iter, "Go back", 1); + } + else if (page == 3) + { + GtkTextTag *tag; + + tag = gtk_text_buffer_create_tag (buffer, NULL, + "weight", PANGO_WEIGHT_BOLD, + NULL); + gtk_text_buffer_insert_with_tags (buffer, &iter, "hypertext:\n", -1, tag, NULL); + gtk_text_buffer_insert (buffer, &iter, + "machine-readable text that is not sequential but is organized " + "so that related items of information are connected.\n", -1); + insert_link (buffer, &iter, "Go back", 1); + } +} + +/* Looks at all tags covering the position of iter in the text view, + * and if one of them is a link, follow it by showing the page identified + * by the data attached to it. + */ +static void +follow_if_link (GtkWidget *text_view, + GtkTextIter *iter) +{ + GSList *tags = NULL, *tagp = NULL; + + tags = gtk_text_iter_get_tags (iter); + for (tagp = tags; tagp != NULL; tagp = tagp->next) + { + GtkTextTag *tag = tagp->data; + gint page = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tag), "page")); + + if (page != 0) + { + show_page (gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)), page); + break; + } + } + + if (tags) + g_slist_free (tags); +} + +/* Links can be activated by pressing Enter. + */ +static gboolean +key_press_event (GtkWidget *text_view, + GdkEventKey *event) +{ + GtkTextIter iter; + GtkTextBuffer *buffer; + + switch (event->keyval) + { + case GDK_Return: + case GDK_KP_Enter: + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)); + gtk_text_buffer_get_iter_at_mark (buffer, &iter, + gtk_text_buffer_get_insert (buffer)); + follow_if_link (text_view, &iter); + break; + + default: + break; + } + + return FALSE; +} + +/* Links can also be activated by clicking. + */ +static gboolean +event_after (GtkWidget *text_view, + GdkEvent *ev) +{ + GtkTextIter start, end, iter; + GtkTextBuffer *buffer; + GdkEventButton *event; + gint x, y; + + if (ev->type != GDK_BUTTON_RELEASE) + return FALSE; + + event = (GdkEventButton *)ev; + + if (event->button != 1) + return FALSE; + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)); + + /* we shouldn't follow a link if the user has selected something */ + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) + return FALSE; + + gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), + GTK_TEXT_WINDOW_WIDGET, + event->x, event->y, &x, &y); + + gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), &iter, x, y); + + follow_if_link (text_view, &iter); + + return FALSE; +} + +gboolean hovering_over_link = FALSE; +GdkCursor *hand_cursor = NULL; +GdkCursor *regular_cursor = NULL; + +/* Looks at all tags covering the position (x, y) in the text view, + * and if one of them is a link, change the cursor to the "hands" cursor + * typically used by web browsers. + */ +static void +set_cursor_if_appropriate (GtkTextView *text_view, + gint x, + gint y) +{ + GSList *tags = NULL, *tagp = NULL; + GtkTextBuffer *buffer; + GtkTextIter iter; + gboolean hovering = FALSE; + + buffer = gtk_text_view_get_buffer (text_view); + + gtk_text_view_get_iter_at_location (text_view, &iter, x, y); + + tags = gtk_text_iter_get_tags (&iter); + for (tagp = tags; tagp != NULL; tagp = tagp->next) + { + GtkTextTag *tag = tagp->data; + gint page = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tag), "page")); + + if (page != 0) + { + hovering = TRUE; + break; + } + } + + if (hovering != hovering_over_link) + { + hovering_over_link = hovering; + + if (hovering_over_link) + gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), hand_cursor); + else + gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), regular_cursor); + } + + if (tags) + g_slist_free (tags); +} + +/* Update the cursor image if the pointer moved. + */ +static gboolean +motion_notify_event (GtkWidget *text_view, + GdkEventMotion *event) +{ + gint x, y; + + gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), + GTK_TEXT_WINDOW_WIDGET, + event->x, event->y, &x, &y); + + set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), x, y); + + gdk_window_get_pointer (text_view->window, NULL, NULL, NULL); + return FALSE; +} + +/* Also update the cursor image if the window becomes visible + * (e.g. when a window covering it got iconified). + */ +static gboolean +visibility_notify_event (GtkWidget *text_view, + GdkEventVisibility *event) +{ + gint wx, wy, bx, by; + + gdk_window_get_pointer (text_view->window, &wx, &wy, NULL); + + gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), + GTK_TEXT_WINDOW_WIDGET, + wx, wy, &bx, &by); + + set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), bx, by); + + return FALSE; +} + +GtkWidget * +do_hypertext (void) +{ + static GtkWidget *window = NULL; + + if (!window) + { + GtkWidget *view; + GtkWidget *sw; + GtkTextBuffer *buffer; + + hand_cursor = gdk_cursor_new (GDK_HAND2); + regular_cursor = gdk_cursor_new (GDK_XTERM); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (window), + 450, 450); + + g_signal_connect (window, "destroy", + G_CALLBACK (gtk_widget_destroyed), &window); + + gtk_window_set_title (GTK_WINDOW (window), "Hypertext"); + gtk_container_set_border_width (GTK_CONTAINER (window), 0); + + view = gtk_text_view_new (); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), GTK_WRAP_WORD); + g_signal_connect (G_OBJECT (view), "key-press-event", + G_CALLBACK (key_press_event), NULL); + g_signal_connect (G_OBJECT (view), "event-after", + G_CALLBACK (event_after), NULL); + g_signal_connect (G_OBJECT (view), "motion-notify-event", + G_CALLBACK (motion_notify_event), NULL); + g_signal_connect (G_OBJECT (view), "visibility-notify-event", + G_CALLBACK (visibility_notify_event), NULL); + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (window), sw); + gtk_container_add (GTK_CONTAINER (sw), view); + + show_page (buffer, 1); + + gtk_widget_show_all (sw); + } + + if (!GTK_WIDGET_VISIBLE (window)) + { + gtk_widget_show (window); + } + else + { + gtk_widget_destroy (window); + window = NULL; + } + + return window; +} + diff --git a/demos/gtk-demo/textview.c b/demos/gtk-demo/textview.c index 0b870ee734..fbec120b4b 100644 --- a/demos/gtk-demo/textview.c +++ b/demos/gtk-demo/textview.c @@ -1,4 +1,4 @@ -/* Text Widget +/* Text Widget/Multiple Views * * The GtkTextView widget displays a GtkTextBuffer. One GtkTextBuffer * can be displayed by multiple GtkTextViews. This demo has two views