From 68d8cb11ff2e3e6978cc01947c66312bacd52a99 Mon Sep 17 00:00:00 2001 From: rodin Date: Sun, 22 Nov 2009 23:04:31 +0000 Subject: [PATCH] Added first version of hyperlink support. Only textbuffers at the moment, but can easilily be added to textgrids in the future. New text program styletest.so available to test some basic style/hyperlink functionality. Load with the plugin loader. --- libchimara/gestalt.c | 10 ++++-- libchimara/hyperlink.c | 79 ++++++++++++++++++++++++++++++++++-------- libchimara/hyperlink.h | 10 +++++- libchimara/stream.h | 2 +- libchimara/strio.c | 37 ++++++++++++++++++-- libchimara/style.c | 32 +++++++++++------ libchimara/style.h | 1 + libchimara/window.c | 16 ++++----- libchimara/window.h | 6 ++-- tests/Makefile.am | 21 +++++++++-- tests/main.c | 4 +++ tests/style.css | 8 ++--- 12 files changed, 177 insertions(+), 49 deletions(-) diff --git a/libchimara/gestalt.c b/libchimara/gestalt.c index 1729a87..2316dca 100644 --- a/libchimara/gestalt.c +++ b/libchimara/gestalt.c @@ -103,6 +103,14 @@ glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, glui32 arrlen) /* Timer capabilities present */ case gestalt_Timer: return 1; + + /* Hyperlink capabilities present */ + case gestalt_Hyperlinks: + return 1; + + /* Hyperlinks supported on textbuffers only at the moment */ + case gestalt_HyperlinkInput: + return val == wintype_TextBuffer; /* Unsupported capabilities */ case gestalt_MouseInput: @@ -111,8 +119,6 @@ glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, glui32 arrlen) case gestalt_Sound: case gestalt_SoundVolume: case gestalt_SoundNotify: - case gestalt_Hyperlinks: - case gestalt_HyperlinkInput: case gestalt_SoundMusic: case gestalt_GraphicsTransparency: /* Selector not supported */ diff --git a/libchimara/hyperlink.c b/libchimara/hyperlink.c index 6d0d263..7d4be65 100644 --- a/libchimara/hyperlink.c +++ b/libchimara/hyperlink.c @@ -24,7 +24,7 @@ glk_set_hyperlink(glui32 linkval) } /** - * glk_set_hyperlink: + * glk_set_hyperlink_stream: * @str: The stream to set the hyperlink mode on. * @linkval: Set to nonzero to initiate hyperlink mode. Set to zero to disengage. * @@ -43,7 +43,57 @@ glk_set_hyperlink_stream(strid_t str, glui32 linkval) g_return_if_fail(str->window != NULL); g_return_if_fail(str->window->type == wintype_TextBuffer); - str->hyperlink_mode = (linkval != 0); + flush_window_buffer(str->window); + + if(linkval == 0) { + /* Turn off hyperlink mode */ + str->hyperlink_mode = FALSE; + str->window->current_hyperlink = NULL; + return; + } + + /* Check whether a tag with the needed value already exists */ + hyperlink_t *new_hyperlink = g_hash_table_lookup(str->window->hyperlinks, &linkval); + if(new_hyperlink == NULL) { + /* Create a new hyperlink with the requested value */ + new_hyperlink = g_new0(struct hyperlink, 1); + new_hyperlink->value = linkval; + new_hyperlink->tag = gtk_text_tag_new(NULL); + new_hyperlink->event_handler = g_signal_connect( new_hyperlink->tag, "event", G_CALLBACK(on_hyperlink_clicked), new_hyperlink ); + g_signal_handler_block(new_hyperlink->tag, new_hyperlink->event_handler); + new_hyperlink->window = str->window; + + /* Add the new tag to the tag table of the textbuffer */ + GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(str->window->widget) ); + GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(textbuffer); + gtk_text_tag_table_add(tags, new_hyperlink->tag); + + printf("inserting link %d\n", linkval); + + gint *linkval_pointer = g_new0(gint, 1); + *linkval_pointer = linkval; + g_hash_table_insert(str->window->hyperlinks, linkval_pointer, new_hyperlink); + } + + str->hyperlink_mode = TRUE; + str->window->current_hyperlink = new_hyperlink; +} + +/* Internal function used to iterate over all the hyperlinks, unblocking the event handler */ +void +hyperlink_unblock_event_handler(gpointer key, gpointer value, gpointer user_data) +{ + hyperlink_t *link = (hyperlink_t *) value; + g_signal_handler_unblock(link->tag, link->event_handler); + printf("unblocking link %d\n", link->value); +} + +/* Internal function used to iterate over all the hyperlinks, blocking the event handler */ +void +hyperlink_block_event_handler(gpointer key, gpointer value, gpointer user_data) +{ + hyperlink_t *link = (hyperlink_t *) value; + g_signal_handler_block(link->tag, link->event_handler); } void @@ -51,10 +101,10 @@ glk_request_hyperlink_event(winid_t win) { VALID_WINDOW(win, return); g_return_if_fail(win != NULL); - g_return_if_fail(win->mouse_click_handler != 0); g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); - g_signal_handler_unblock( G_OBJECT(win->widget), win->mouse_click_handler ); + g_hash_table_foreach(win->hyperlinks, hyperlink_unblock_event_handler, NULL); + } void @@ -62,19 +112,20 @@ glk_cancel_hyperlink_event(winid_t win) { VALID_WINDOW(win, return); g_return_if_fail(win != NULL); - g_return_if_fail(win->mouse_click_handler != 0); g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); - - g_signal_handler_block( G_OBJECT(win->widget), win->mouse_click_handler ); + + g_hash_table_foreach(win->hyperlinks, hyperlink_block_event_handler, NULL); } -/* Internal function: General callback for signal button-release-event on a - * text buffer or text grid window. Used for detecting clicks on hyperlinks. - * Blocked when not in use. - */ gboolean -on_window_button_release_event(GtkWidget *widget, GdkEventButton *event, winid_t win) +on_hyperlink_clicked(GtkTextTag *tag, GObject *object, GdkEvent *event, GtkTextIter *iter, hyperlink_t *link) { - printf("Click on (%f,%f)\n", event->x, event->y); - return TRUE; + ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(link->window->widget, CHIMARA_TYPE_GLK)); + g_assert(glk); + + if(event->type == GDK_BUTTON_PRESS) { + event_throw(glk, evtype_Hyperlink, link->window, link->value, 0); + } + + return FALSE; } diff --git a/libchimara/hyperlink.h b/libchimara/hyperlink.h index 2b5b1eb..fa49d92 100644 --- a/libchimara/hyperlink.h +++ b/libchimara/hyperlink.h @@ -8,6 +8,14 @@ #include "window.h" #include "event.h" -G_GNUC_INTERNAL gboolean on_window_button_release_event(GtkWidget *widget, GdkEventButton *event, winid_t win); +struct hyperlink { + guint32 value; + GtkTextTag *tag; + gulong event_handler; + winid_t window; +}; +typedef struct hyperlink hyperlink_t; + +G_GNUC_INTERNAL gboolean on_hyperlink_clicked(GtkTextTag *tag, GObject *object, GdkEvent *event, GtkTextIter *iter, hyperlink_t *link); #endif diff --git a/libchimara/stream.h b/libchimara/stream.h index 1e5a720..193da47 100644 --- a/libchimara/stream.h +++ b/libchimara/stream.h @@ -49,7 +49,7 @@ struct glk_stream_struct gchar *filename; /* Displayable filename in UTF-8 for error handling */ gchar *style; /* Name of the current style */ - gboolean hyperlink_mode; /* WHen turned on, text written to the stream will be a hyperlink */ + gboolean hyperlink_mode; /* When turned on, text written to the stream will be a hyperlink */ }; G_GNUC_INTERNAL strid_t file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode); diff --git a/libchimara/strio.c b/libchimara/strio.c index 467c6c6..bac3153 100644 --- a/libchimara/strio.c +++ b/libchimara/strio.c @@ -46,7 +46,17 @@ flush_window_buffer(winid_t win) { GtkTextIter iter; gtk_text_buffer_get_end_iter(buffer, &iter); - gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, win->buffer->str, -1, win->window_stream->style, NULL); + + GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer); + GtkTextTag *style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->style); + + if(win->window_stream->hyperlink_mode) { + GtkTextTag *link_style_tag = gtk_text_tag_table_lookup(tags, "hyperlink"); + GtkTextTag *link_tag = win->current_hyperlink->tag; + gtk_text_buffer_insert_with_tags(buffer, &iter, win->buffer->str, -1, style_tag, link_style_tag, link_tag, NULL); + } else { + gtk_text_buffer_insert_with_tags(buffer, &iter, win->buffer->str, -1, style_tag, NULL); + } ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK)); g_assert(glk); @@ -74,7 +84,18 @@ flush_window_buffer(winid_t win) GtkTextIter end = start; gtk_text_iter_forward_to_line_end(&end); gtk_text_buffer_delete(buffer, &start, &end); - gtk_text_buffer_insert_with_tags_by_name(buffer, &start, win->buffer->str + (length - chars_left), available_space, win->window_stream->style, NULL); + + GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer); + GtkTextTag *style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->style); + + if(win->window_stream->hyperlink_mode) { + GtkTextTag *link_style_tag = gtk_text_tag_table_lookup(tags, "hyperlink"); + GtkTextTag *link_tag = win->current_hyperlink->tag; + gtk_text_buffer_insert_with_tags(buffer, &start, win->buffer->str + (length - chars_left), available_space, style_tag, link_style_tag, link_tag, NULL); + } else { + gtk_text_buffer_insert_with_tags(buffer, &start, win->buffer->str + (length - chars_left), available_space, style_tag, NULL); + } + chars_left -= available_space; gtk_text_iter_forward_line(&start); available_space = win->width; @@ -84,7 +105,17 @@ flush_window_buffer(winid_t win) GtkTextIter end = start; gtk_text_iter_forward_chars(&end, chars_left); gtk_text_buffer_delete(buffer, &start, &end); - gtk_text_buffer_insert_with_tags_by_name(buffer, &start, win->buffer->str + (length - chars_left), -1, win->window_stream->style, NULL); + + GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer); + GtkTextTag *style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->style); + + if(win->window_stream->hyperlink_mode) { + GtkTextTag *link_style_tag = gtk_text_tag_table_lookup(tags, "hyperlink"); + GtkTextTag *link_tag = win->current_hyperlink->tag; + gtk_text_buffer_insert_with_tags(buffer, &start, win->buffer->str + (length - chars_left), -1, style_tag, link_style_tag, link_tag, NULL); + } else { + gtk_text_buffer_insert_with_tags(buffer, &start, win->buffer->str + (length - chars_left), -1, style_tag, NULL); + } } gtk_text_buffer_move_mark(buffer, cursor, &start); diff --git a/libchimara/style.c b/libchimara/style.c index b725703..5c639cc 100644 --- a/libchimara/style.c +++ b/libchimara/style.c @@ -9,7 +9,7 @@ static gboolean style_accept_style_selector(GScanner *scanner); static gboolean style_accept_style_hint(GScanner *scanner, GtkTextTag *current_tag); static void style_add_tag_to_textbuffer(gpointer key, gpointer tag, gpointer tag_table); static void style_table_copy(gpointer key, gpointer tag, gpointer target_table); -static GtkTextTag* gtk_text_tag_copy(GtkTextTag *tag); +GtkTextTag* gtk_text_tag_copy(GtkTextTag *tag); /** * glk_set_style: @@ -123,7 +123,7 @@ style_table_copy(gpointer key, gpointer tag, gpointer target_table) } /* Internal function that copies a text tag */ -static GtkTextTag* +GtkTextTag* gtk_text_tag_copy(GtkTextTag *tag) { GtkTextTag *copy; @@ -157,8 +157,11 @@ gtk_text_tag_copy(GtkTextTag *tag) _COPY_FLAG (language_set); #undef _COPY_FLAG - /* Copy the reverse_color attribute, that was added manually */ - g_object_set_data( G_OBJECT(copy), "reverse_color", g_object_get_data(G_OBJECT(tag), "reverse_color") ); + /* Copy the data that was added manually */ + gpointer reverse_color = g_object_get_data( G_OBJECT(tag), "reverse_color" ); + + if(reverse_color) + g_object_set_data( G_OBJECT(copy), "reverse_color", reverse_color ); return copy; } @@ -168,10 +171,10 @@ void style_init() { ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key); - GHashTable *default_text_grid_styles = g_hash_table_new(g_str_hash, g_str_equal); - GHashTable *default_text_buffer_styles = g_hash_table_new(g_str_hash, g_str_equal); - GHashTable *current_text_grid_styles = g_hash_table_new(g_str_hash, g_str_equal); - GHashTable *current_text_buffer_styles = g_hash_table_new(g_str_hash, g_str_equal); + GHashTable *default_text_grid_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref); + GHashTable *default_text_buffer_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref); + GHashTable *current_text_grid_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref); + GHashTable *current_text_buffer_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref); GtkTextTag *tag; /* Create the CSS file scanner */ @@ -217,13 +220,17 @@ style_init() g_hash_table_insert(default_text_grid_styles, "note", tag); tag = gtk_text_tag_new("block-quote"); - g_object_set(tag, "style", PANGO_STYLE_ITALIC, NULL); + g_object_set(tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL); g_hash_table_insert(default_text_grid_styles, "block-quote", tag); g_hash_table_insert(default_text_grid_styles, "input", gtk_text_tag_new("input")); g_hash_table_insert(default_text_grid_styles, "user1", gtk_text_tag_new("user1")); g_hash_table_insert(default_text_grid_styles, "user2", gtk_text_tag_new("user2")); + tag = gtk_text_tag_new("hyperlink"); + g_object_set(tag, "foreground", "#0000ff", "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL); + g_hash_table_insert(default_text_grid_styles, "hyperlink", tag); + /* Tags for the textbuffer */ tag = gtk_text_tag_new("normal"); g_object_set(tag, "font-desc", glk_data->default_font_desc, NULL); @@ -254,13 +261,18 @@ style_init() g_hash_table_insert(default_text_buffer_styles, "note", tag); tag = gtk_text_tag_new("block-quote"); - g_object_set(tag, "justification", GTK_JUSTIFY_CENTER, "style", PANGO_STYLE_ITALIC, NULL); + g_object_set(tag, "justification", GTK_JUSTIFY_CENTER, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL); g_hash_table_insert(default_text_buffer_styles, "block-quote", tag); g_hash_table_insert(default_text_buffer_styles, "input", gtk_text_tag_new("input")); g_hash_table_insert(default_text_buffer_styles, "user1", gtk_text_tag_new("user1")); g_hash_table_insert(default_text_buffer_styles, "user2", gtk_text_tag_new("user2")); + tag = gtk_text_tag_new("hyperlink"); + g_object_set(tag, "foreground", "#0000ff", "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL); + g_hash_table_insert(default_text_buffer_styles, "hyperlink", tag); + + glk_data->default_styles->text_grid = default_text_grid_styles; glk_data->default_styles->text_buffer = default_text_buffer_styles; diff --git a/libchimara/style.h b/libchimara/style.h index 9a16c1a..8d7c69e 100644 --- a/libchimara/style.h +++ b/libchimara/style.h @@ -15,6 +15,7 @@ G_GNUC_INTERNAL void style_init_textbuffer(GtkTextBuffer *buffer); G_GNUC_INTERNAL void style_init_textgrid(GtkTextBuffer *buffer); G_GNUC_INTERNAL void style_init(); G_GNUC_INTERNAL PangoFontDescription* get_current_font(guint32 wintype); +G_GNUC_INTERNAL GtkTextTag* gtk_text_tag_copy(GtkTextTag *tag); typedef struct StyleSet { GHashTable *text_grid; diff --git a/libchimara/window.c b/libchimara/window.c index edb241f..5cef17e 100644 --- a/libchimara/window.c +++ b/libchimara/window.c @@ -35,6 +35,9 @@ window_new_common(glui32 rock) /* Initialise the buffer */ win->buffer = g_string_sized_new(1024); + /* Initialise hyperlink table */ + win->hyperlinks = g_hash_table_new_full(g_int_hash, g_direct_equal, g_free, g_object_unref); + return win; } @@ -57,6 +60,8 @@ window_close_common(winid_t win, gboolean destroy_node) g_list_free(win->history); g_string_free(win->buffer, TRUE); + g_hash_table_destroy(win->hyperlinks); + g_free(win->current_hyperlink); g_free(win); } @@ -493,10 +498,6 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, g_signal_handler_block(textview, win->char_input_keypress_handler); win->line_input_keypress_handler = g_signal_connect(textview, "key-press-event", G_CALLBACK(on_line_input_key_press_event), win); g_signal_handler_block(textview, win->line_input_keypress_handler); - - gtk_widget_add_events( textview, GDK_BUTTON_RELEASE_MASK ); - win->mouse_click_handler = g_signal_connect_after( G_OBJECT(textview), "button-release-event", G_CALLBACK(on_window_button_release_event), win ); - g_signal_handler_block( textview, win->mouse_click_handler ); } break; @@ -536,12 +537,9 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, win->line_input_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(on_line_input_key_press_event), win ); g_signal_handler_block(textview, win->line_input_keypress_handler); - gtk_widget_add_events( GTK_WIDGET(textview), GDK_BUTTON_RELEASE_MASK ); - win->mouse_click_handler = g_signal_connect_after( textview, "button-release-event", G_CALLBACK(on_window_button_release_event), win ); - g_signal_handler_block( textview, win->mouse_click_handler ); - win->insert_text_handler = g_signal_connect_after( textbuffer, "insert-text", G_CALLBACK(after_window_insert_text), win ); - g_signal_handler_block( textbuffer, win->insert_text_handler ); + g_signal_handler_block(textbuffer, win->insert_text_handler); + /* Create an editable tag to indicate uneditable parts of the window (for line input) */ diff --git a/libchimara/window.h b/libchimara/window.h index 853f896..e0222b0 100644 --- a/libchimara/window.h +++ b/libchimara/window.h @@ -70,10 +70,12 @@ struct glk_window_struct gulong char_input_keypress_handler; gulong line_input_keypress_handler; gulong insert_text_handler; - gulong mouse_click_handler; - gulong mouse_move_handler; + gulong tag_event_handler; /* Window buffer */ GString *buffer; + /* Hyperlinks */ + GHashTable *hyperlinks; + struct hyperlink *current_hyperlink; }; #endif diff --git a/tests/Makefile.am b/tests/Makefile.am index 22b0c2b..45e0acf 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,8 +1,19 @@ AM_CFLAGS = -Wall AM_CPPFLAGS = -I$(top_srcdir) -dist_data_DATA = chimara.ui chimara.menus glulxercise.ui +PLUGIN_LIBTOOL_FLAGS=-module -avoid-version -export-symbols-regex "^glk_main$$" + +if TARGET_ILIAD + +iliad_SOURCES = iliad.c +iliad_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS) +iliad_LDADD = @TEST_LIBS@ $(top_builddir)/libchimara/libchimara.la + +noinst_PROGRAMS = iliad +else + +dist_data_DATA = chimara.ui chimara.menus glulxercise.ui noinst_PROGRAMS = test-chimara test-multisession glulxercise plugin-loader test_chimara_SOURCES = main.c callbacks.c error.c error.h @@ -25,8 +36,7 @@ plugin_loader_SOURCES = plugin-loader.c plugin_loader_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS) plugin_loader_LDADD = @TEST_LIBS@ $(top_builddir)/libchimara/libchimara.la -pkglib_LTLIBRARIES = first.la model.la gridtest.la splittest.la multiwin.la -PLUGIN_LIBTOOL_FLAGS=-module -avoid-version -export-symbols-regex "^glk_main$$" +pkglib_LTLIBRARIES = first.la model.la gridtest.la splittest.la multiwin.la styletest.la first_la_SOURCES = first.c first_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) @@ -42,3 +52,8 @@ splittest_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) multiwin_la_SOURCES = multiwin.c multiwin_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) + +styletest_la_SOURCES = styletest.c +styletest_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) + +endif diff --git a/tests/main.c b/tests/main.c index 4912739..5cadc22 100644 --- a/tests/main.c +++ b/tests/main.c @@ -120,6 +120,8 @@ create_window(void) } glk = chimara_if_new(); + //chimara_if_set_preferred_interpreter( CHIMARA_IF(glk), CHIMARA_IF_FORMAT_Z8, CHIMARA_IF_INTERPRETER_NITFOL); + g_object_set(glk, "border-width", 6, "spacing", 6, @@ -180,6 +182,8 @@ main(int argc, char *argv[]) error_dialog(GTK_WINDOW(window), error, "Error starting Glk library: "); return 1; } + //chimara_glk_run( CHIMARA_GLK(glk), ".libs/multiwin.so", argc, argv, NULL); + gdk_threads_enter(); gtk_main(); diff --git a/tests/style.css b/tests/style.css index 4a0d51f..6597f99 100644 --- a/tests/style.css +++ b/tests/style.css @@ -25,7 +25,7 @@ * text-align (left/right/center) */ grid.normal { - font-size: 14; + font-size: 10; } grid.user1 { @@ -34,17 +34,17 @@ grid.user1 { } buffer.normal { - font-size: 14; + font-size: 10; } buffer.header { - font-size: 18; + font-size: 14; font-weight: bold; text-align: center; } buffer.subheader { - font-size: 16; + font-size: 12; font-weight: bold; } -- 2.30.2