From: Marijn van Vliet Date: Sat, 17 Apr 2010 19:47:20 +0000 (+0000) Subject: Pager in place X-Git-Url: https://git.stderr.nl/gitweb?a=commitdiff_plain;ds=inline;h=a2e82e317a6000511953c2dbc0eb1026c7b245a7;p=rodin%2Fchimara.git Pager in place git-svn-id: http://lassie.dyndns-server.com/svn/gargoyle-gtk@232 ddfedd41-794f-dd11-ae45-00112f111e67 --- diff --git a/libchimara/Makefile.am b/libchimara/Makefile.am index 7283ed2..5fb89bf 100644 --- a/libchimara/Makefile.am +++ b/libchimara/Makefile.am @@ -27,6 +27,7 @@ libchimara_la_SOURCES = \ input.c input.h \ magic.c magic.h \ mouse.c \ + pager.c pager.h \ resource.c resource.h \ schannel.c \ stream.c stream.h \ diff --git a/libchimara/chimara-glk-private.h b/libchimara/chimara-glk-private.h index 85e3af2..77eb4f5 100644 --- a/libchimara/chimara-glk-private.h +++ b/libchimara/chimara-glk-private.h @@ -34,9 +34,12 @@ struct _ChimaraGlkPrivate { /* Hashtable containing the default and current style */ struct StyleSet *default_styles; struct StyleSet *current_styles; + PangoAttrList *pager_attr_list; gboolean style_initialized; /* Have styles been initialized */ /* Final message displayed when game exits */ gchar *final_message; + /* Image cache */ + GSList *image_cache; /* *** Threading data *** */ /* Whether program is running */ @@ -69,8 +72,6 @@ struct _ChimaraGlkPrivate { GCond *resource_loaded; GCond *resource_info_available; guint32 resource_available; - /* Image cache */ - GSList *image_cache; /* *** Glk library data *** */ /* User-defined interrupt handler */ diff --git a/libchimara/chimara-glk.c b/libchimara/chimara-glk.c index 4af716b..982b39f 100644 --- a/libchimara/chimara-glk.c +++ b/libchimara/chimara-glk.c @@ -88,6 +88,7 @@ chimara_glk_init(ChimaraGlk *self) priv->css_file = "style.css"; priv->default_styles = g_new0(StyleSet,1); priv->current_styles = g_new0(StyleSet,1); + priv->pager_attr_list = pango_attr_list_new(); priv->style_initialized = FALSE; priv->final_message = g_strdup("[ The game has finished ]"); priv->running = FALSE; @@ -191,6 +192,7 @@ chimara_glk_finalize(GObject *object) g_hash_table_destroy(priv->default_styles->text_grid); g_hash_table_destroy(priv->current_styles->text_buffer); g_hash_table_destroy(priv->current_styles->text_grid); + pango_attr_list_unref(priv->pager_attr_list); priv->style_initialized = FALSE; /* Free the event queue */ diff --git a/libchimara/pager.c b/libchimara/pager.c new file mode 100644 index 0000000..f9a0a32 --- /dev/null +++ b/libchimara/pager.c @@ -0,0 +1,146 @@ +#include + +#include "pager.h" + +/* Helper function: move the pager to the last visible position in the buffer, + and return the distance between the pager and the end of the buffer in buffer + coordinates */ +static gint +move_pager_and_get_scroll_distance(GtkTextView *textview) +{ + GdkRectangle pagerpos, endpos, visiblerect; + GtkTextIter oldpager, newpager, end; + GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview); + GtkTextMark *pager = gtk_text_buffer_get_mark(buffer, "pager_position"); + + /* Get an iter at the lower right corner of the visible part of the buffer */ + gtk_text_view_get_visible_rect(textview, &visiblerect); + gtk_text_view_get_iter_at_location(textview, &newpager, visiblerect.x + visiblerect.width, visiblerect.y + visiblerect.height); + gtk_text_buffer_get_iter_at_mark(buffer, &oldpager, pager); + + gtk_text_buffer_move_mark(buffer, pager, &newpager); + + /* Get the buffer coordinates of the pager and the end iter */ + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_get_iter_at_mark(buffer, &newpager, pager); + gtk_text_view_get_iter_location(textview, &newpager, &pagerpos); + gtk_text_view_get_iter_location(textview, &end, &endpos); + + //g_printerr("View height = %d\n", visiblerect.height); + //g_printerr("End - Pager = %d\n", endpos.y - pagerpos.y); + + return endpos.y - pagerpos.y; +} + +/* Helper function: turn on paging for this textview */ +static void +start_paging(winid_t win) +{ + win->currently_paging = TRUE; + g_signal_handler_unblock(win->widget, win->pager_expose_handler); + g_signal_handler_unblock(win->widget, win->pager_keypress_handler); +} + +/* Helper function: turn off paging for this textview */ +static void +stop_paging(winid_t win) +{ + win->currently_paging = FALSE; + g_signal_handler_block(win->widget, win->pager_expose_handler); + g_signal_handler_block(win->widget, win->pager_keypress_handler); +} + +/* Update the pager position after new text is inserted in the buffer */ +void +pager_after_insert_text(GtkTextBuffer *buffer, GtkTextIter *location, gchar *text, gint len, winid_t win) +{ + while(gtk_events_pending()) + gtk_main_iteration(); + + /* Move the pager to the last visible character in the buffer */ + gint scroll_distance = move_pager_and_get_scroll_distance( GTK_TEXT_VIEW(win->widget) ); + + if(scroll_distance > 0 && !win->currently_paging) + start_paging(win); +} + +void +pager_after_adjustment_changed(GtkAdjustment *adj, winid_t win) +{ + while(gtk_events_pending()) + gtk_main_iteration(); + + /* Move the pager, etc. */ + gint scroll_distance = move_pager_and_get_scroll_distance( GTK_TEXT_VIEW(win->widget) ); + + if(scroll_distance > 0 && !win->currently_paging) + start_paging(win); + else if(scroll_distance == 0 && win->currently_paging) + stop_paging(win); + + /* Refresh the widget so that any extra "more" prompts disappear */ + gtk_widget_queue_draw(win->widget); +} + +/* Handle key press events in the textview while paging is active */ +gboolean +pager_on_key_press_event(GtkTextView *textview, GdkEventKey *event, winid_t win) +{ + /*** ALTERNATIVE, POSSIBLY INFERIOR, METHOD OF SCROLLING *** + GtkTextMark *pagermark = gtk_text_buffer_get_mark(buffer, "pager_position"); + gtk_text_view_scroll_to_mark(textview, pagermark, 0.0, TRUE, 0.0, 0.0); + */ + + GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment( GTK_SCROLLED_WINDOW(win->frame) ); + gdouble step_increment, page_size, upper, lower, value; + g_object_get(adj, + "page-size", &page_size, + "step-increment", &step_increment, + "upper", &upper, + "lower", &lower, + "value", &value, + NULL); + + switch (event->keyval) { + case GDK_space: case GDK_KP_Space: case GDK_Page_Down: case GDK_KP_Page_Down: + gtk_adjustment_set_value(adj, CLAMP(value + page_size, lower, upper - page_size)); + return TRUE; + case GDK_Page_Up: case GDK_KP_Page_Up: + gtk_adjustment_set_value(adj, CLAMP(value - page_size, lower, upper - page_size)); + return TRUE; + case GDK_Return: case GDK_KP_Enter: + gtk_adjustment_set_value(adj, CLAMP(value + step_increment, lower, upper - page_size)); + return TRUE; + /* don't handle "up" and "down", they're used for input history */ + } + + return FALSE; /* if the key wasn't handled here, pass it to other handlers */ +} + +/* Draw the "more" prompt on top of the buffer, after the regular expose event has run */ +gboolean +pager_on_expose(GtkTextView *textview, GdkEventExpose *event, winid_t win) +{ + /* Calculate the position of the 'more' tag */ + gint promptwidth, promptheight; + pango_layout_get_pixel_size(win->pager_layout, &promptwidth, &promptheight); + + gint winx, winy, winwidth, winheight; + gdk_window_get_position(event->window, &winx, &winy); + gdk_drawable_get_size(GDK_DRAWABLE(event->window), &winwidth, &winheight); + + /* Draw the 'more' tag */ + GdkGC *context = gdk_gc_new(GDK_DRAWABLE(event->window)); + /* + gdk_draw_layout_with_colors(event->window, context, + winx + winwidth - promptwidth, + winy + winheight - promptheight, + prompt, &white, &red); + */ + gdk_draw_layout(event->window, context, + winx + winwidth - promptwidth, + winy + winheight - promptheight, + win->pager_layout); + + return FALSE; /* Propagate event further */ +} diff --git a/libchimara/pager.h b/libchimara/pager.h new file mode 100644 index 0000000..1979646 --- /dev/null +++ b/libchimara/pager.h @@ -0,0 +1,13 @@ +#ifndef PAGER_H +#define PAGER_H + +#include +#include "glk.h" +#include "window.h" + +G_GNUC_INTERNAL gboolean pager_on_expose(GtkTextView *textview, GdkEventExpose *event, winid_t win); +G_GNUC_INTERNAL gboolean pager_on_key_press_event(GtkTextView *textview, GdkEventKey *event, winid_t win); +G_GNUC_INTERNAL void pager_after_insert_text(GtkTextBuffer *buffer, GtkTextIter *location, gchar *text, gint len, winid_t win); +G_GNUC_INTERNAL void pager_after_adjustment_changed(GtkAdjustment *adj, winid_t win); + +#endif diff --git a/libchimara/style.c b/libchimara/style.c index 7147d53..93bf2b3 100644 --- a/libchimara/style.c +++ b/libchimara/style.c @@ -15,6 +15,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 void text_tag_to_attr_list(GtkTextTag *tag, PangoAttrList *list); GtkTextTag* gtk_text_tag_copy(GtkTextTag *tag); /** @@ -80,6 +81,16 @@ glk_set_style_stream(strid_t str, glui32 styl) { str->style = get_tag_name(styl); } +/* Internal function: call this to initialize the layout of the 'more' prompt. */ +void +style_init_more_prompt(winid_t win) +{ + ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key); + + win->pager_layout = gtk_widget_create_pango_layout(win->widget, "More"); + pango_layout_set_attributes(win->pager_layout, glk_data->pager_attr_list); +} + /* Internal function: call this to initialize the default styles to a textbuffer. */ void style_init_textbuffer(GtkTextBuffer *buffer) @@ -95,6 +106,7 @@ style_init_textbuffer(GtkTextBuffer *buffer) g_hash_table_foreach(glk_data->current_styles->text_buffer, style_add_tag_to_textbuffer, gtk_text_buffer_get_tag_table(buffer)); } + /* Internal function: call this to initialize the default styles to a textgrid. */ void style_init_textgrid(GtkTextBuffer *buffer) @@ -265,6 +277,10 @@ style_init() g_object_set(tag, "foreground", "#0000ff", "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL); g_hash_table_insert(default_text_buffer_styles, "hyperlink", tag); + GtkTextTag *pager_tag = gtk_text_tag_new("pager"); + g_object_set(pager_tag, "foreground", "#ffffff", "background", "#000000", NULL); + g_hash_table_insert(default_text_buffer_styles, "pager", pager_tag); + glk_data->default_styles->text_grid = default_text_grid_styles; glk_data->default_styles->text_buffer = default_text_buffer_styles; @@ -299,6 +315,8 @@ style_init() glk_data->current_styles->text_grid = current_text_grid_styles; glk_data->current_styles->text_buffer = current_text_buffer_styles; + text_tag_to_attr_list(pager_tag, glk_data->pager_attr_list); + glk_data->style_initialized = TRUE; } @@ -876,3 +894,61 @@ get_current_font(guint32 wintype) return font; } + +/* Internal function copying the attributes of a text tag to a pango attribute list */ +static void +text_tag_to_attr_list(GtkTextTag *tag, PangoAttrList *list) +{ + gboolean set; + GdkColor *foreground, *background; + gchar *string; + PangoFontDescription *font_desc; + gboolean strikethrough; + PangoUnderline underline; + + g_object_get(tag, "foreground-set", &set, "foreground-gdk", &foreground, NULL); + if(set) { + pango_attr_list_insert( + list, + pango_attr_foreground_new(foreground->red, foreground->green, foreground->blue) + ); + } + g_object_get(tag, "background-set", &set, "background-gdk", &background, NULL); + if(set) { + pango_attr_list_insert( + list, + pango_attr_background_new(background->red, background->green, background->blue) + ); + } + g_object_get(tag, "language-set", &set, "language", &string, NULL); + if(set) { + pango_attr_list_insert( + list, + pango_attr_language_new( pango_language_from_string(string) ) + ); + } + + /* Font description updates the following properties simultaniously: + * family, style, weight, variant, stretch, size + */ + g_object_get(tag, "font-desc", &font_desc, NULL); + pango_attr_list_insert( + list, + pango_attr_font_desc_new(font_desc) + ); + + g_object_get(tag, "strikethrough-set", &set, "strikethrough", &strikethrough, NULL); + if(set) { + pango_attr_list_insert( + list, + pango_attr_strikethrough_new(strikethrough) + ); + } + g_object_get(tag, "underline-set", &set, "underline", &underline, NULL); + if(set) { + pango_attr_list_insert( + list, + pango_attr_underline_new(underline) + ); + } +} diff --git a/libchimara/style.h b/libchimara/style.h index 5c2778b..a8198ee 100644 --- a/libchimara/style.h +++ b/libchimara/style.h @@ -6,6 +6,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_more_prompt(winid_t win); 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); diff --git a/libchimara/window.c b/libchimara/window.c index af47056..e8bcf37 100644 --- a/libchimara/window.c +++ b/libchimara/window.c @@ -3,6 +3,7 @@ #include "magic.h" #include "chimara-glk-private.h" #include "gi_dispa.h" +#include "pager.h" extern GPrivate *glk_data_key; @@ -65,6 +66,10 @@ window_close_common(winid_t win, gboolean destroy_node) g_string_free(win->buffer, TRUE); g_hash_table_destroy(win->hyperlinks); g_free(win->current_hyperlink); + + if(win->pager_layout) + g_object_unref(win->pager_layout); + g_free(win); } @@ -525,13 +530,14 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, gtk_container_add( GTK_CONTAINER(scrolledwindow), textview ); gtk_widget_show_all(scrolledwindow); + win->widget = textview; + win->frame = scrolledwindow; + /* Create the styles available to the window stream */ style_init_textbuffer(textbuffer); + style_init_more_prompt(win); gtk_widget_modify_font( textview, get_current_font(wintype) ); - win->widget = textview; - win->frame = scrolledwindow; - /* Determine the size of a "0" character in pixels */ PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0"); pango_layout_set_font_description( zero, get_current_font(wintype) ); @@ -539,15 +545,28 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, g_object_unref(zero); /* Connect signal handlers */ + + /* Pager */ + win->pager_expose_handler = g_signal_connect( textview, "expose-event", G_CALLBACK(pager_on_expose), win ); + g_signal_handler_block(textview, win->pager_expose_handler); + win->pager_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(pager_on_key_press_event), win ); + g_signal_handler_block(textview, win->pager_keypress_handler); + g_signal_connect_after( textbuffer, "insert-text", G_CALLBACK(pager_after_insert_text), win ); + GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolledwindow)); + g_signal_connect_after(adj, "value-changed", G_CALLBACK(pager_after_adjustment_changed), win); + + /* Char and line input */ win->char_input_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(on_char_input_key_press_event), win ); 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); - win->shutdown_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(on_shutdown_key_press_event), win ); - g_signal_handler_block(textview, win->shutdown_keypress_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); + /* Shutdown key press */ + win->shutdown_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(on_shutdown_key_press_event), win ); + g_signal_handler_block(textview, win->shutdown_keypress_handler); + /* Create an editable tag to indicate uneditable parts of the window (for line input) */ gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL); @@ -556,6 +575,10 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, GtkTextIter end; gtk_text_buffer_get_end_iter(textbuffer, &end); gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE); + + /* Create the pager position mark; it stands for the last character in the buffer + that has been on-screen */ + gtk_text_buffer_create_mark(textbuffer, "pager_position", &end, TRUE); } break; diff --git a/libchimara/window.h b/libchimara/window.h index 571d02a..e135496 100644 --- a/libchimara/window.h +++ b/libchimara/window.h @@ -77,6 +77,8 @@ struct glk_window_struct gulong shutdown_keypress_handler; gulong button_press_event_handler; gulong size_allocate_handler; + gulong pager_expose_handler; + gulong pager_keypress_handler; /* Window buffer */ GString *buffer; /* Hyperlinks */ @@ -84,6 +86,9 @@ struct glk_window_struct struct hyperlink *current_hyperlink; /* Graphics */ glui32 background_color; + /* Pager (textbuffer only) */ + gboolean currently_paging; + PangoLayout *pager_layout; }; #endif diff --git a/player/style.css b/player/style.css index 257f404..7f0d1fc 100644 --- a/player/style.css +++ b/player/style.css @@ -75,3 +75,7 @@ buffer.user1 { buffer.user2 { } +buffer.pager { + color: #ffffff; + background-color: #303030; +} diff --git a/tests/barf.c b/tests/barf.c index 36e5151..42b535f 100644 --- a/tests/barf.c +++ b/tests/barf.c @@ -13,6 +13,10 @@ static gulong pager_handler = 0; static gulong expose_handler = 0; static gboolean currently_paging = FALSE; +static gint promptwidth, promptheight; +static PangoLayout *prompt; +static GdkColor red, white; + static gboolean quit() { @@ -65,8 +69,8 @@ move_pager_and_get_scroll_distance(GtkTextView *textview) gtk_text_view_get_iter_location(textview, &newpager, &pagerpos); gtk_text_view_get_iter_location(textview, &end, &endpos); - g_printerr("View height = %d\n", visiblerect.height); - g_printerr("End - Pager = %d\n", endpos.y - pagerpos.y); + //g_printerr("View height = %d\n", visiblerect.height); + //g_printerr("End - Pager = %d\n", endpos.y - pagerpos.y); return endpos.y - pagerpos.y; } @@ -161,14 +165,7 @@ pager_wait(GtkTextView *textview, GdkEventKey *event, GtkTextBuffer *buffer) static gboolean expose_prompt(GtkTextView *textview, GdkEventExpose *event) { - PangoLayout *prompt = gtk_widget_create_pango_layout(GTK_WIDGET(textview), "More"); - gint promptwidth, promptheight; - pango_layout_get_pixel_size(prompt, &promptwidth, &promptheight); - GdkGC *context = gdk_gc_new(GDK_DRAWABLE(event->window)); - GdkColor red, white; - gdk_color_parse("red", &red); - gdk_color_parse("white", &white); gint winx, winy, winwidth, winheight; gdk_window_get_position(event->window, &winx, &winy); @@ -207,6 +204,12 @@ main(int argc, char **argv) /* Set up the textview widget to receive exposure events, must be done after widget has been shown */ gdk_window_set_events(gtk_widget_get_window(textview), GDK_EXPOSURE_MASK); + /* Create the 'more' prompt */ + prompt = gtk_widget_create_pango_layout(GTK_WIDGET(textview), "More"); + pango_layout_get_pixel_size(prompt, &promptwidth, &promptheight); + gdk_color_parse("red", &red); + gdk_color_parse("white", &white); + /* Connect paging signals */ GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview)); g_signal_connect_after(buffer, "insert-text", G_CALLBACK(after_insert), textview); @@ -229,5 +232,7 @@ main(int argc, char **argv) g_signal_connect(button, "clicked", G_CALLBACK(barf), textview); gtk_main(); + + g_object_unref(prompt); return 0; } diff --git a/tests/barf.c.eventbox b/tests/barf.c.eventbox new file mode 100644 index 0000000..29eda83 --- /dev/null +++ b/tests/barf.c.eventbox @@ -0,0 +1,246 @@ +/* + * Compile me with: + * gcc -g -O0 -Wall -pthread -o barf barf.c `pkg-config --cflags --libs gtk+-2.0` + */ + +#include +#include + +/* Uncomment to try the other paging method */ +/* #define PAGE_ONLY_UNSEEN 1 */ + +static gulong pager_handler = 0; +static gulong expose_handler = 0; +static gboolean currently_paging = FALSE; + +static gint promptwidth, promptheight; +static PangoLayout *prompt; +static GdkColor red, white; + +static gboolean +quit() +{ + gtk_main_quit(); + return TRUE; +} + +/* Vomit a load of text onto the screen */ +static void +barf(GtkButton *button, GtkTextView *textview) +{ + GtkTextIter end; + GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview); + gtk_text_buffer_get_end_iter(buffer, &end); + + gchar *loremipsum; + g_file_get_contents("loremipsum.txt", &loremipsum, NULL, NULL); + gtk_text_buffer_insert(buffer, &end, loremipsum, -1); + g_free(loremipsum); + + gtk_widget_grab_focus(GTK_WIDGET(textview)); +} + +/* Helper function: move the pager to the last visible position in the buffer, + and return the distance between the pager and the end of the buffer in buffer + coordinates */ +static gint +move_pager_and_get_scroll_distance(GtkTextView *textview) +{ + GdkRectangle pagerpos, endpos, visiblerect; + GtkTextIter oldpager, newpager, end; + GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview); + GtkTextMark *pager = gtk_text_buffer_get_mark(buffer, "pager_position"); + + /* Get an iter at the lower right corner of the visible part of the buffer */ + gtk_text_view_get_visible_rect(textview, &visiblerect); + gtk_text_view_get_iter_at_location(textview, &newpager, visiblerect.x + visiblerect.width, visiblerect.y + visiblerect.height); + gtk_text_buffer_get_iter_at_mark(buffer, &oldpager, pager); + +#ifdef PAGE_ONLY_UNSEEN + if(gtk_text_iter_compare(&oldpager, &newpager) < 0) + gtk_text_buffer_move_mark(buffer, pager, &newpager); +#else + gtk_text_buffer_move_mark(buffer, pager, &newpager); +#endif + + /* Get the buffer coordinates of the pager and the end iter */ + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_get_iter_at_mark(buffer, &newpager, pager); + gtk_text_view_get_iter_location(textview, &newpager, &pagerpos); + gtk_text_view_get_iter_location(textview, &end, &endpos); + + //g_printerr("View height = %d\n", visiblerect.height); + //g_printerr("End - Pager = %d\n", endpos.y - pagerpos.y); + + return endpos.y - pagerpos.y; +} + +/* Helper function: turn on paging for this textview */ +static void +start_paging(GtkWidget *eventbox) +{ + currently_paging = TRUE; + g_signal_handler_unblock(eventbox, expose_handler); + g_signal_handler_unblock(eventbox, pager_handler); +} + +/* Helper function: turn off paging for this textview */ +static void +stop_paging(GtkWidget *eventbox) +{ + currently_paging = FALSE; + g_signal_handler_block(eventbox, expose_handler); + g_signal_handler_block(eventbox, pager_handler); +} + +/* Update the pager position after new text is inserted in the buffer */ +static void +after_insert(GtkTextBuffer *buffer, GtkTextIter *location, gchar *text, gint len, GtkTextView *textview) +{ + while(gtk_events_pending()) + gtk_main_iteration(); + + /* Move the pager to the last visible character in the buffer */ + gint scroll_distance = move_pager_and_get_scroll_distance(textview); + + if(scroll_distance > 0 && !currently_paging) { + GtkWidget *eventbox = gtk_widget_get_parent( gtk_widget_get_parent(GTK_WIDGET(textview)) ); + start_paging(eventbox); + } +} + +static void +adjustment_changed(GtkAdjustment *adjustment, GtkTextView *textview) +{ + while(gtk_events_pending()) + gtk_main_iteration(); + + /* Move the pager, etc. */ + gint scroll_distance = move_pager_and_get_scroll_distance(textview); + + GtkWidget *eventbox = gtk_widget_get_parent( gtk_widget_get_parent(GTK_WIDGET(textview)) ); + if(scroll_distance > 0 && !currently_paging) + start_paging(eventbox); + else if(scroll_distance == 0 && currently_paging) + stop_paging(eventbox); + + /* Refresh the widget so that any extra "more" prompts disappear */ + gtk_widget_queue_draw(GTK_WIDGET(eventbox)); +} + +/* Handle key press events in the textview while paging is active */ +static gboolean +pager_wait(GtkTextView *eventbox, GdkEventKey *event, GtkTextBuffer *buffer) +{ + /*** ALTERNATIVE, POSSIBLY INFERIOR, METHOD OF SCROLLING *** + GtkTextMark *pagermark = gtk_text_buffer_get_mark(buffer, "pager_position"); + gtk_text_view_scroll_to_mark(textview, pagermark, 0.0, TRUE, 0.0, 0.0); + */ + + GtkWidget *scrolledwindow = gtk_bin_get_child(GTK_BIN(eventbox)); + GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolledwindow)); + gdouble step_increment, page_size, upper, lower, value; + g_object_get(adj, + "page-size", &page_size, + "step-increment", &step_increment, + "upper", &upper, + "lower", &lower, + "value", &value, + NULL); + + switch (event->keyval) { + case GDK_space: case GDK_KP_Space: case GDK_Page_Down: case GDK_KP_Page_Down: + gtk_adjustment_set_value(adj, CLAMP(value + page_size, lower, upper - page_size)); + return TRUE; + case GDK_Page_Up: case GDK_KP_Page_Up: + gtk_adjustment_set_value(adj, CLAMP(value - page_size, lower, upper - page_size)); + return TRUE; + case GDK_Return: case GDK_KP_Enter: + gtk_adjustment_set_value(adj, CLAMP(value + step_increment, lower, upper - page_size)); + return TRUE; + /* don't handle "up" and "down", they're used for input history */ + } + + return FALSE; /* if the key wasn't handled here, pass it to other handlers */ +} + +/* Draw the "more" prompt on top of the buffer, after the regular expose event has run */ +static gboolean +expose_prompt(GtkEventBox *eventbox, GdkEventExpose *event) +{ + GdkGC *context = gdk_gc_new(GDK_DRAWABLE(event->window)); + + gint winx, winy, winwidth, winheight; + gdk_window_get_position(event->window, &winx, &winy); + gdk_drawable_get_size(GDK_DRAWABLE(event->window), &winwidth, &winheight); + + gdk_draw_layout_with_colors(event->window, context, + winx + winwidth - promptwidth, + //winy + winheight - promptheight, + winy - 5, + prompt, &white, &red); + + return FALSE; /* Propagate event further */ +} + +int +main(int argc, char **argv) +{ + gtk_init(&argc, &argv); + + /* Construct the widgets */ + GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request(window, 400, 400); + GtkWidget *button = gtk_button_new_with_label("Barf"); + GtkWidget *eventbox = gtk_event_box_new(); +// gtk_event_box_set_above_child( GTK_EVENT_BOX(eventbox), TRUE ); + GTK_WIDGET_SET_FLAGS(eventbox, GTK_APP_PAINTABLE); + GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL); + GtkWidget *textview = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR); + gtk_text_view_set_editable(GTK_TEXT_VIEW(textview), FALSE); + GtkWidget *vbox = gtk_vbox_new(FALSE, 6); + + /* Pack all the widgets into their containers */ + gtk_container_add(GTK_CONTAINER(scrolledwindow), textview); + gtk_container_add(GTK_CONTAINER(eventbox), scrolledwindow); + gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), eventbox, TRUE, TRUE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show_all(window); + + /* Set up the textview widget to receive exposure events, must be done after widget has been shown */ + gdk_window_set_events(gtk_widget_get_window(eventbox), GDK_EXPOSURE_MASK); + + /* Create the 'more' prompt */ + prompt = gtk_widget_create_pango_layout(GTK_WIDGET(textview), "More"); + pango_layout_get_pixel_size(prompt, &promptwidth, &promptheight); + gdk_color_parse("red", &red); + gdk_color_parse("white", &white); + + /* Connect paging signals */ + GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview)); + g_signal_connect_after(buffer, "insert-text", G_CALLBACK(after_insert), textview); + GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolledwindow)); + g_signal_connect_after(adj, "value-changed", G_CALLBACK(adjustment_changed), textview); + pager_handler = g_signal_connect(eventbox, "key-press-event", G_CALLBACK(pager_wait), buffer); + g_signal_handler_block(eventbox, pager_handler); + expose_handler = g_signal_connect(eventbox, "expose-event", G_CALLBACK(expose_prompt), NULL); + g_signal_handler_block(eventbox, expose_handler); + + /* Create the pager position mark; it stands for the last character in the buffer + that has been on-screen */ + GtkTextIter end; + gtk_text_buffer_get_end_iter(buffer, &end); + GtkTextMark *pagermark = gtk_text_buffer_create_mark(buffer, "pager_position", &end, TRUE); + gtk_text_mark_set_visible(pagermark, TRUE); + + /* Connect "regular program" signals */ + g_signal_connect(window, "delete-event", G_CALLBACK(quit), NULL); + g_signal_connect(button, "clicked", G_CALLBACK(barf), textview); + + gtk_main(); + + g_object_unref(prompt); + return 0; +}