Pager in place
authorrodin <rodin@ddfedd41-794f-dd11-ae45-00112f111e67>
Sat, 17 Apr 2010 19:47:20 +0000 (19:47 +0000)
committerrodin <rodin@ddfedd41-794f-dd11-ae45-00112f111e67>
Sat, 17 Apr 2010 19:47:20 +0000 (19:47 +0000)
12 files changed:
libchimara/Makefile.am
libchimara/chimara-glk-private.h
libchimara/chimara-glk.c
libchimara/pager.c [new file with mode: 0644]
libchimara/pager.h [new file with mode: 0644]
libchimara/style.c
libchimara/style.h
libchimara/window.c
libchimara/window.h
player/style.css
tests/barf.c
tests/barf.c.eventbox [new file with mode: 0644]

index 7283ed248cfbed4f3b2956e9695fc99536e1b701..5fb89bfedbd1390931e5e52f27c032350c88ae8a 100644 (file)
@@ -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 \
index 85e3af28dbd4d0c238a05a71584cfe6550d9424c..77eb4f5d16c6b235487cbda053d3ce677b57f442 100644 (file)
@@ -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 */
index 4af716b40e70883d5f458fc7874a3c812035304c..982b39fa6f1edbd89ef6d7147a27125a818b91c3 100644 (file)
@@ -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 (file)
index 0000000..f9a0a32
--- /dev/null
@@ -0,0 +1,146 @@
+#include <gdk/gdkkeysyms.h>
+
+#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 (file)
index 0000000..1979646
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef PAGER_H
+#define PAGER_H
+
+#include <gtk/gtk.h>
+#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
index 7147d53f9bc7ea5e1b19d53b4574579ee0365501..93bf2b330baba17944747e70ffccb1085b759e65 100644 (file)
@@ -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)
+               );
+       }
+}
index 5c2778b16c31f2e60b84dde7f237ade4ed3772ef..a8198ee1a3c62cdaa1e7f2ab9f93914bfd64f9c4 100644 (file)
@@ -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);
index af470566ccd734bf72eaba9b0b45983039287bc0..e8bcf3724a9a947fc86998d34231189121cd6ac1 100644 (file)
@@ -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;
 
index 571d02a16e4d4bd548984e13b1af391e2d545f15..e13549614351e776e35344f283152043fc46780a 100644 (file)
@@ -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
index 257f404cd2d68a40e7660d49eb6bcecc3766aa49..7f0d1fc477e5a7e8e9d1b27434c3af33b69c02c2 100644 (file)
@@ -75,3 +75,7 @@ buffer.user1 {
 buffer.user2 {
 }
 
+buffer.pager {
+       color: #ffffff;
+       background-color: #303030;
+}
index 36e51514c7c7e70d2f90c3a62c960ea141c5d941..42b535f8d890aebcf60dc1ae3715dbeea777c3a2 100644 (file)
@@ -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 (file)
index 0000000..29eda83
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * Compile me with:
+ * gcc -g -O0 -Wall -pthread -o barf barf.c `pkg-config --cflags --libs gtk+-2.0`
+ */
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+/* 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;
+}