summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShaun McCance <shaunm@src.gnome.org>2004-02-03 21:06:13 +0000
committerShaun McCance <shaunm@src.gnome.org>2004-02-03 21:06:13 +0000
commit2bcb7e7f6c1788b96668aed9a938521c50345b06 (patch)
tree6f11ccfcac6fc0c29c0ec8ac260762016f5ac1ca
parent2fb523b26949b4aa7983a9ce2a9a05920d1e0d88 (diff)
downloadyelp-2bcb7e7f6c1788b96668aed9a938521c50345b06.tar.gz
- Introduced a timeout function for scrolling to anchor/top, since
* src/yelp-html-gtkhtml2.c: - Introduced a timeout function for scrolling to anchor/top, since gtkhtml2 also has a timeout that likes to scroll to the first link. This timeout checks if gtkhtml2's timeout is still pending, and readds itself if so. * src/yelp-window.c: - Do the writing of the page in an idle, which produces better renderings and helps me to beat idle and timeout functions that mess with scrolling. - Fix problem where Previous/Next/TOC menu items stayed active when they shouldn't have.
-rw-r--r--ChangeLog13
-rw-r--r--src/yelp-html-gtkhtml2.c70
-rw-r--r--src/yelp-window.c137
3 files changed, 180 insertions, 40 deletions
diff --git a/ChangeLog b/ChangeLog
index d73f5d67..ceaea30f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2004-02-03 Shaun McCance <shaunm@gnome.org>
+
+ * src/yelp-html-gtkhtml2.c:
+ - Introduced a timeout function for scrolling to anchor/top, since gtkhtml2
+ also has a timeout that likes to scroll to the first link. This timeout
+ checks if gtkhtml2's timeout is still pending, and readds itself if so.
+
+ * src/yelp-window.c:
+ - Do the writing of the page in an idle, which produces better renderings
+ and helps me to beat idle and timeout functions that mess with scrolling.
+ - Fix problem where Previous/Next/TOC menu items stayed active when they
+ shouldn't have.
+
2004-01-23 Mikael Hallendal <micke@imendio.com>
* src/yelp-window.h: include gtkwindow.h instead of gnome-app.h
diff --git a/src/yelp-html-gtkhtml2.c b/src/yelp-html-gtkhtml2.c
index e7b23707..f40e7522 100644
--- a/src/yelp-html-gtkhtml2.c
+++ b/src/yelp-html-gtkhtml2.c
@@ -24,6 +24,8 @@
#include <config.h>
#endif
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkenums.h>
#include <libgnomevfs/gnome-vfs.h>
#include <libgnomevfs/gnome-vfs-mime-utils.h>
#include <libgnome/gnome-i18n.h>
@@ -46,6 +48,7 @@
typedef struct _YelpHtmlBoxNode YelpHtmlBoxNode;
#define YELP_HTML_BOX_NODE(x) ((YelpHtmlBoxNode *)(x))
+#define ADJUSTMENT_TIMEOUT_INTERVAL 200
struct _YelpHtmlPriv {
HtmlView *view;
@@ -61,6 +64,8 @@ struct _YelpHtmlPriv {
GList *find_list;
GList *find_elem;
gboolean find_is_forward;
+
+ gchar *anchor;
};
struct _YelpHtmlBoxNode {
@@ -86,6 +91,7 @@ static void html_link_clicked_cb (HtmlDocument *doc,
static void html_title_changed_cb (HtmlDocument *doc,
const gchar *new_title,
YelpHtml *html);
+static gint adjustment_timeout_cb (gpointer data);
static DomNode * html_get_dom_node (HtmlDocument *doc,
const gchar *node_name);
@@ -159,7 +165,7 @@ html_init (YelpHtml *html)
G_CALLBACK (html_url_requested_cb), html);
g_signal_connect (G_OBJECT (priv->doc), "title_changed",
G_CALLBACK (html_title_changed_cb), html);
-
+
gtk_widget_set_size_request (GTK_WIDGET (priv->view), 300, 200);
html->priv = priv;
@@ -295,6 +301,7 @@ html_link_clicked_cb (HtmlDocument *doc, const gchar *url, YelpHtml *html)
html_clear_find_data (html);
g_signal_emit (html, signals[URI_SELECTED], 0, uri, handled);
+ yelp_uri_unref (uri);
}
static void
@@ -308,6 +315,48 @@ html_title_changed_cb (HtmlDocument *doc,
g_signal_emit (html, signals[TITLE_CHANGED], 0, new_title);
}
+static gint
+adjustment_timeout_cb (gpointer data)
+{
+ YelpHtml *html = YELP_HTML (data);
+ YelpHtmlPriv *priv = html->priv;
+ GtkAdjustment *adjustment;
+
+ /* gtkhtml registers a relayout callback on a one second timeout which,
+ * among other things, focuses the first link on the page, which causes
+ * it to scroll to said link. This causes anchor scrolling not to work,
+ * and causes pages without a link at the top to start off scrolled down.
+ * As I'm sure you can imagine, this is really annoying. Here we have my
+ * ugly hack wherein I put in my own timeout callback to scroll to where
+ * I actually want the page. In order to make it visually quick but still
+ * avoid happening before gtkhtml's timeout, I check relayout_timeout_id
+ * on the HtmlView, which is non-zero if there's a timeout still waiting
+ * to happen. If the gtkhtml timeout is still there, this function is
+ * readded, and we try again later.
+ *
+ * There also seems to be an idle function in GtkLayout that triggers the
+ * gtkhtml2 relayout function. So technically, we have a race condition.
+ * However, grabbing focus before everything else, regardless of whether
+ * or not we readd the timeout function, seems to be fairly reliable.
+ */
+
+ gtk_widget_grab_focus (GTK_WIDGET (priv->view));
+
+ if (priv->view->relayout_timeout_id != 0)
+ return TRUE;
+
+ adjustment = gtk_layout_get_vadjustment (GTK_LAYOUT (priv->view));
+ gtk_adjustment_set_value (adjustment, adjustment->lower);
+
+ adjustment = gtk_layout_get_hadjustment (GTK_LAYOUT (priv->view));
+ gtk_adjustment_set_value (adjustment, adjustment->lower);
+
+ if (priv->anchor)
+ html_view_jump_to_anchor (HTML_VIEW (priv->view), priv->anchor);
+
+ return FALSE;
+}
+
YelpHtml *
yelp_html_new (void)
{
@@ -356,6 +405,11 @@ yelp_html_clear (YelpHtml *html)
priv = html->priv;
+ if (priv->anchor) {
+ g_free (priv->anchor);
+ priv->anchor = NULL;
+ }
+
html_document_clear (priv->doc);
html_document_open_stream (priv->doc, "text/html");
html_stream_set_cancel_func (priv->doc->current_stream,
@@ -409,8 +463,7 @@ yelp_html_close (YelpHtml *html)
html_document_close_stream (priv->doc);
- gtk_adjustment_set_value (gtk_layout_get_vadjustment (GTK_LAYOUT (priv->view)),
- 0);
+ g_timeout_add (ADJUSTMENT_TIMEOUT_INTERVAL, adjustment_timeout_cb, html);
}
GtkWidget *
@@ -802,5 +855,14 @@ void
yelp_html_jump_to_anchor (YelpHtml *html,
gchar *anchor)
{
- html_view_jump_to_anchor (HTML_VIEW (html->priv->view), anchor);
+ YelpHtmlPriv *priv;
+
+ g_return_if_fail (html != NULL);
+
+ priv = html->priv;
+
+ if (priv->anchor)
+ g_free (priv->anchor);
+
+ priv->anchor = g_strdup (anchor);
}
diff --git a/src/yelp-window.c b/src/yelp-window.c
index d2ae002b..a44c1403 100644
--- a/src/yelp-window.c
+++ b/src/yelp-window.c
@@ -26,6 +26,7 @@
#include <config.h>
#endif
+#include <libgtkhtml/gtkhtml.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gtk/gtk.h>
#include <bonobo/bonobo-main.h>
@@ -227,6 +228,22 @@ struct _YelpWindowPriv {
gchar *toc;
};
+typedef struct _IdleWriterContext IdleWriterContext;
+struct _IdleWriterContext {
+ YelpWindow *window;
+
+ enum {
+ IDLE_WRITER_MEMORY,
+ IDLE_WRITER_VFS
+ } type;
+
+ const gchar *buffer;
+ gint cur;
+ gint length;
+};
+
+static gboolean idle_write (IdleWriterContext *context);
+
static GtkItemFactoryEntry menu_items[] = {
{N_("/_File"), NULL, 0, 0, "<Branch>" },
{N_("/File/_New window"), NULL,
@@ -584,8 +601,6 @@ window_populate (YelpWindow *window)
gtk_box_pack_start (GTK_BOX (priv->main_box),
priv->html_sw,
TRUE, TRUE, 0);
-
- gtk_widget_grab_focus (yelp_html_get_widget (priv->html_view));
}
static GtkWidget *
@@ -844,8 +859,6 @@ window_handle_pager_uri (YelpWindow *window,
gchar *loading = _("Loading...");
GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);
- yelp_html_clear (priv->html_view);
-
gdk_window_set_cursor (GTK_WIDGET (window)->window, cursor);
gdk_cursor_unref (cursor);
@@ -868,6 +881,7 @@ window_handle_pager_uri (YelpWindow *window,
gtk_window_set_title (GTK_WINDOW (window),
(const gchar *) loading);
+ yelp_html_clear (priv->html_view);
yelp_html_printf
(priv->html_view,
"<html><head><meta http-equiv='Content-Type'"
@@ -944,23 +958,28 @@ window_handle_html_uri (YelpWindow *window,
gtk_widget_set_sensitive (menu_item, FALSE);
- yelp_html_clear (priv->html_view);
- yelp_html_set_base_uri (priv->html_view, uri);
-
result = gnome_vfs_open (&handle,
gnome_vfs_uri_get_path (uri->uri),
GNOME_VFS_OPEN_READ);
if (result != GNOME_VFS_OK) {
- // FIXME: Give an error
+ GError *error = NULL;
+ yelp_set_error (&error, YELP_ERROR_NO_DOC);
+ window_error (window, error);
+ g_error_free (error);
return FALSE;
}
+ yelp_html_clear (priv->html_view);
+ yelp_html_set_base_uri (priv->html_view, uri);
+
while ((result = gnome_vfs_read
(handle, buffer, BUFFER_SIZE, &n)) == GNOME_VFS_OK) {
yelp_html_write (priv->html_view, buffer, n);
}
+ yelp_html_close (priv->html_view);
+
gnome_vfs_close (handle);
return TRUE;
@@ -977,6 +996,7 @@ window_handle_page (YelpWindow *window,
YelpWindowPriv *priv;
gchar *id;
gboolean valid;
+ IdleWriterContext *context;
g_return_if_fail (YELP_IS_WINDOW (window));
@@ -1015,39 +1035,43 @@ window_handle_page (YelpWindow *window,
}
}
- if (page->prev) {
- priv->prev = page->prev;
- menu_item =
- gtk_item_factory_get_item_by_action (priv->item_factory,
- YELP_WINDOW_GO_PREVIOUS);
- if (menu_item)
- gtk_widget_set_sensitive (menu_item, TRUE);
- }
- if (page->next) {
- priv->next = page->next;
- menu_item =
- gtk_item_factory_get_item_by_action (priv->item_factory,
- YELP_WINDOW_GO_NEXT);
- if (menu_item)
- gtk_widget_set_sensitive (menu_item, TRUE);
- }
- if (page->toc) {
- priv->toc = page->toc;
- menu_item =
- gtk_item_factory_get_item_by_action (priv->item_factory,
- YELP_WINDOW_GO_TOC);
- if (menu_item)
- gtk_widget_set_sensitive (menu_item, TRUE);
- }
+ priv->prev = page->prev;
+ menu_item =
+ gtk_item_factory_get_item_by_action (priv->item_factory,
+ YELP_WINDOW_GO_PREVIOUS);
+ if (menu_item)
+ gtk_widget_set_sensitive (menu_item,
+ priv->prev ? TRUE : FALSE);
+
+ priv->next = page->next;
+ menu_item =
+ gtk_item_factory_get_item_by_action (priv->item_factory,
+ YELP_WINDOW_GO_NEXT);
+ if (menu_item)
+ gtk_widget_set_sensitive (menu_item,
+ priv->next ? TRUE : FALSE);
+
+ priv->toc = page->toc;
+ menu_item =
+ gtk_item_factory_get_item_by_action (priv->item_factory,
+ YELP_WINDOW_GO_TOC);
+ if (menu_item)
+ gtk_widget_set_sensitive (menu_item,
+ priv->toc ? TRUE : FALSE);
gtk_window_set_title (GTK_WINDOW (window),
(const gchar *) page->title);
+ context = g_new0 (IdleWriterContext, 1);
+ context->window = window;
+ context->type = IDLE_WRITER_MEMORY;
+ context->buffer = page->chunk;
+ context->length = strlen (page->chunk);
+
yelp_html_clear (priv->html_view);
yelp_html_set_base_uri (priv->html_view, uri);
- yelp_html_write (priv->html_view,
- page->chunk,
- strlen (page->chunk));
+
+ gtk_idle_add ((GtkFunction) idle_write, context);
if (gnome_vfs_uri_get_fragment_identifier (uri->uri)) {
yelp_html_jump_to_anchor
@@ -1192,7 +1216,6 @@ html_uri_selected_cb (YelpHtml *html,
if (!handled) {
yelp_window_open_uri (window, uri);
- yelp_uri_unref (uri);
}
}
@@ -1711,3 +1734,45 @@ tree_model_iter_following (GtkTreeModel *model,
return FALSE;
}
+/* Writing incrementally in an idle function has a number of advantages. First,
+ * it keeps the interface responsive for really big pages. Second, it prevents
+ * some weird rendering artifacts in gtkhtml2. Third, and most important, it
+ * helps me beat the relayout race condition that I discuss at length in a big
+ * comment in yelp-html-gtkhtml2.c.
+ */
+
+static gboolean
+idle_write (IdleWriterContext *context)
+{
+ YelpWindowPriv *priv;
+
+ g_return_val_if_fail (context != NULL, FALSE);
+ g_return_val_if_fail (context->window != NULL, FALSE);
+
+ priv = context->window->priv;
+
+ switch (context->type) {
+ case IDLE_WRITER_MEMORY:
+ if (context->cur + BUFFER_SIZE < context->length) {
+ yelp_html_write (priv->html_view,
+ context->buffer + context->cur,
+ BUFFER_SIZE);
+ context->cur += BUFFER_SIZE;
+ return TRUE;
+ } else {
+ if (context->length > context->cur)
+ yelp_html_write (priv->html_view,
+ context->buffer + context->cur,
+ context->length - context->cur);
+ yelp_html_close (priv->html_view);
+ g_free (context);
+ return FALSE;
+ }
+ break;
+ case IDLE_WRITER_VFS:
+ default:
+ g_assert_not_reached ();
+ }
+
+ return FALSE;
+}