diff options
-rw-r--r-- | src/browserbox.c | 85 | ||||
-rw-r--r-- | src/browserbox.h | 13 | ||||
-rw-r--r-- | src/documentbox.c | 63 | ||||
-rw-r--r-- | src/documentbox.h | 22 | ||||
-rw-r--r-- | src/inlinebox.c | 60 | ||||
-rw-r--r-- | src/inlinebox.h | 12 | ||||
-rw-r--r-- | src/main.c | 57 |
7 files changed, 261 insertions, 51 deletions
diff --git a/src/browserbox.c b/src/browserbox.c index fe69cde..9179ad1 100644 --- a/src/browserbox.c +++ b/src/browserbox.c @@ -118,26 +118,46 @@ static void builder_state_class_init (BuilderStateClass *klass) object_class->dispose = builder_state_dispose; } +void scroll_to (BuilderState *bs, GObject *target) +{ + GtkAllocation widget_alloc, *alloc; + if (GTK_IS_WIDGET(target)) { + gtk_widget_get_allocation(GTK_WIDGET(target), &widget_alloc); + alloc = &widget_alloc; + } else if (IS_IB_TEXT(target)) { + alloc = &IB_TEXT(target)->alloc; + } else { + puts("Shouldn't happen"); + return; + } + GtkAdjustment *adj = + gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(bs->docbox)); + gtk_adjustment_set_value(adj, alloc->y); + gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(bs->docbox), + adj); +} void scroll_to_identifier(BuilderState *bs, const char *identifier) { - GtkWidget *target = g_hash_table_lookup(bs->identifiers, identifier); - if (target) { - GtkAllocation widget_alloc, *alloc; - if (GTK_IS_WIDGET(target)) { - gtk_widget_get_allocation(target, &widget_alloc); - alloc = &widget_alloc; - } else if (IS_IB_TEXT(target)) { - alloc = &IB_TEXT(target)->alloc; - } else { - puts("Shouldn't happen"); - return; - } - GtkAdjustment *adj = - gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(bs->docbox)); - gtk_adjustment_set_value(adj, alloc->y); - gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(bs->docbox), - adj); + GObject *target = g_hash_table_lookup(bs->identifiers, identifier); + if (target != NULL) { + scroll_to(bs, target); + } +} + +void browser_box_set_status(BrowserBox *bb, const gchar *status_str) { + gtk_statusbar_remove_all(GTK_STATUSBAR(bb->status_bar), + gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status")); + gtk_statusbar_push(GTK_STATUSBAR(bb->status_bar), + gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status"), + status_str); +} + +void browser_box_display_search_status (BrowserBox *bb) { + gchar status[MAX_SEARCH_STRING_LEN + 33]; + if (bb->search_state == SEARCH_FORWARD) { + sprintf(status, "Forward search: %s", bb->search_string); + browser_box_set_status(bb, status); } } @@ -1212,11 +1232,7 @@ void hover_link_cb (void *ptr, gchar *url, BrowserBox *bb) { - gtk_statusbar_remove_all(GTK_STATUSBAR(bb->status_bar), - gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status")); - gtk_statusbar_push(GTK_STATUSBAR(bb->status_bar), - gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status"), - url); + browser_box_set_status(bb, url); } @@ -1238,11 +1254,7 @@ void document_loaded(SoupSession *session, htmlParseChunk(bs->parser, "", 0, 1); gtk_widget_grab_focus(GTK_WIDGET(bs->docbox)); printf("word cache: %u\n", g_hash_table_size(word_cache)); - gtk_statusbar_remove_all(GTK_STATUSBAR(bb->status_bar), - gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status")); - gtk_statusbar_push(GTK_STATUSBAR(bb->status_bar), - gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status"), - "Ready"); + browser_box_set_status(bb, "Ready"); } void got_chunk(SoupMessage *msg, @@ -1251,11 +1263,7 @@ void got_chunk(SoupMessage *msg, { BrowserBox *bb = ptr; BuilderState *bs = bb->builder_state; - gtk_statusbar_remove_all(GTK_STATUSBAR(bb->status_bar), - gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status")); - gtk_statusbar_push(GTK_STATUSBAR(bb->status_bar), - gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status"), - "Loading"); + browser_box_set_status(bb, "Loading"); if (bs->parser == NULL) { /* todo: maybe move it into got_headers */ char *uri_str = soup_uri_to_string(bs->uri, FALSE); @@ -1285,11 +1293,7 @@ void got_chunk(SoupMessage *msg, void got_headers(SoupMessage *msg, gpointer ptr) { BrowserBox *bb = ptr; - gtk_statusbar_remove_all(GTK_STATUSBAR(bb->status_bar), - gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status")); - gtk_statusbar_push(GTK_STATUSBAR(bb->status_bar), - gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status"), - "Got headers"); + browser_box_set_status(bb, "Got headers"); /* todo: check content type, don't assume HTML */ if (bb->builder_state != NULL) { if (bb->builder_state->docbox != NULL) { @@ -1306,11 +1310,7 @@ void got_headers(SoupMessage *msg, gpointer ptr) void document_request_sm (BrowserBox *bb, SoupMessage *sm) { - gtk_statusbar_remove_all(GTK_STATUSBAR(bb->status_bar), - gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status")); - gtk_statusbar_push(GTK_STATUSBAR(bb->status_bar), - gtk_statusbar_get_context_id(GTK_STATUSBAR(bb->status_bar), "status"), - "Requesting"); + browser_box_set_status(bb, "Requesting"); if (bb->builder_state != NULL) { bb->builder_state->active = FALSE; } @@ -1394,6 +1394,7 @@ browser_box_init (BrowserBox *bb) bb->forms = NULL; bb->history = NULL; bb->history_position = NULL; + bb->search_string[0] = 0; return; } diff --git a/src/browserbox.h b/src/browserbox.h index 5016c67..9af6f83 100644 --- a/src/browserbox.h +++ b/src/browserbox.h @@ -89,6 +89,15 @@ struct _BuilderState typedef struct _BrowserBox BrowserBox; typedef struct _BrowserBoxClass BrowserBoxClass; +typedef enum _BTSState BTSState; +enum _BTSState { + SEARCH_INACTIVE, + SEARCH_FORWARD, + SEARCH_BACKWARD +}; + +#define MAX_SEARCH_STRING_LEN 512 + struct _BrowserBox { BlockBox parent_instance; @@ -100,6 +109,8 @@ struct _BrowserBox GList *forms; GList *history; GList *history_position; + BTSState search_state; + gchar search_string[MAX_SEARCH_STRING_LEN + 1]; GtkStack *tabs; /* GHashTable *word_cache; */ }; @@ -125,6 +136,8 @@ void document_request_sm (BrowserBox *bb, SoupMessage *sm); void document_request (BrowserBox *bb, SoupURI *uri); gboolean history_back (BrowserBox *bb); gboolean history_forward (BrowserBox *bb); +void browser_box_set_status(BrowserBox *bb, const gchar *status_str); +void browser_box_display_search_status (BrowserBox *bb); GHashTable *word_cache; diff --git a/src/documentbox.c b/src/documentbox.c index b506385..f8a8e2b 100644 --- a/src/documentbox.c +++ b/src/documentbox.c @@ -95,6 +95,12 @@ static void document_box_init (DocumentBox *db) { db->links = NULL; + db->search.ib = NULL; + db->search.start = 0; + db->search.end = -1; + db->search.str = NULL; + db->search.forward = TRUE; + db->search.state = START; } @@ -487,3 +493,60 @@ DocumentBox *document_box_new () db->sel.selection_active = FALSE; return db; } + +static void +document_box_search (GtkWidget *widget, TextSearchState *tss) { + /* todo: backwards search */ + if (tss->state == FOUND) { + return; + } + if (tss->state == START && (tss->ib == NULL || GTK_WIDGET(tss->ib) == widget)) { + /* No previous position or found the widget */ + tss->state = LOOKING; + if (tss->ib != NULL) { + tss->ib->match_start = 0; + tss->ib->match_end = 0; + gtk_widget_queue_draw(GTK_WIDGET(tss->ib)); + } + } + if (tss->state == LOOKING && + (tss->ib == NULL || GTK_WIDGET(tss->ib) != widget)) { + tss->start = 0; + tss->end = -1; + } + if (tss->state == LOOKING && IS_INLINE_BOX(widget)) { + InlineBox *ib = INLINE_BOX(widget); + gint pos = inline_box_search(ib, tss->start, tss->end, tss->str); + if (pos != -1) { + tss->state = FOUND; + tss->ib = ib; + tss->start = pos; + tss->end = pos + strlen(tss->str); + ib->match_start = tss->start; + ib->match_end = tss->end; + gtk_widget_queue_draw(widget); + } + } else if (tss->state != FOUND && GTK_IS_CONTAINER(widget)) { + gtk_container_foreach(GTK_CONTAINER(widget), + (GtkCallback)document_box_search, tss); + } +} + +gboolean +document_box_find (DocumentBox *db, const gchar *str) +{ + /* todo: backwards search */ + db->search.str = str; + db->search.state = START; + db->search.end = -1; + document_box_search(GTK_WIDGET(db), &(db->search)); + if (db->search.state == FOUND) { + gtk_widget_grab_focus(GTK_WIDGET(db->search.ib)); + return TRUE; + } else { + db->search.ib = NULL; + db->search.start = 0; + db->search.end = -1; + return FALSE; + } +} diff --git a/src/documentbox.h b/src/documentbox.h index 351471b..64a2320 100644 --- a/src/documentbox.h +++ b/src/documentbox.h @@ -46,14 +46,32 @@ struct _SelectionState gboolean selecting; }; +typedef enum _TSState TSState; +enum _TSState { + START, + LOOKING, + FOUND +}; + +typedef struct _TextSearchState TextSearchState; +struct _TextSearchState +{ + InlineBox *ib; + gint start; + gint end; + const gchar *str; + gboolean forward; + TSState state; +}; + struct _DocumentBox { GtkScrolledWindow parent_instance; GtkEventBox *evbox; GList *links; SelectionState sel; + TextSearchState search; GdkWindow *event_window; - /* GList *forms; */ }; struct _DocumentBoxClass @@ -63,7 +81,7 @@ struct _DocumentBoxClass GType document_box_get_type(void) G_GNUC_CONST; DocumentBox *document_box_new(void); - +gboolean document_box_find (DocumentBox *db, const gchar *str); G_END_DECLS diff --git a/src/inlinebox.c b/src/inlinebox.c index 43b75fa..7eeaeae 100644 --- a/src/inlinebox.c +++ b/src/inlinebox.c @@ -165,6 +165,29 @@ inline_box_draw (GtkWidget *widget, sel_width, ibt->alloc.height); gtk_style_context_remove_class(styleCtx, "rubberband"); } + /* duplication here (todo) */ + if (ib->match_start <= text_position + text_len && + ib->match_end >= text_position) { + guint sel_start = ibt->alloc.x, sel_width = ibt->alloc.width; + gint x_pos; + if (ib->match_start > text_position) { + pango_layout_index_to_line_x(ibt->layout, + ib->match_start - text_position, + FALSE, NULL, &x_pos); + sel_start += x_pos / PANGO_SCALE; + sel_width -= x_pos / PANGO_SCALE; + } + if (ib->match_end < text_position + text_len) { + pango_layout_index_to_line_x(ibt->layout, + ib->match_end - text_position, + FALSE, NULL, &x_pos); + sel_width -= ibt->alloc.width - x_pos / PANGO_SCALE; + } + gtk_style_context_add_class(styleCtx, "rubberband"); + gtk_render_background(styleCtx, cr, sel_start, ibt->alloc.y, + sel_width, ibt->alloc.height); + gtk_style_context_remove_class(styleCtx, "rubberband"); + } gtk_render_layout(styleCtx, cr, ibt->alloc.x, ibt->alloc.y, ibt->layout); @@ -583,3 +606,40 @@ inline_box_forall (GtkContainer *container, gboolean include_internals, child = next; } } + +gchar* +inline_box_get_text (InlineBox *ib) +{ + GList *child; + gchar **words = calloc(g_list_length(ib->children) + 1, sizeof(gchar*)); + guint n = 0; + for (child = ib->children; child; child = child->next) { + if (IS_IB_TEXT(child->data)) { + words[n] = (gchar*)pango_layout_get_text(IB_TEXT(child->data)->layout); + n++; + } + } + gchar *result = g_strjoinv(NULL, words); + free(words); + return result; +} + +gint +inline_box_search (InlineBox *ib, + guint start, + gint end, + const gchar *str) +{ + gchar *orig_text = inline_box_get_text(ib); + gchar *text = g_utf8_strdown(orig_text, -1); + g_free(orig_text); + if (end != -1) { + end -= start; + } + gchar *result = g_strstr_len(text + start, end, str); + g_free(text); + if (result != NULL) { + return result - text; + } + return -1; +} diff --git a/src/inlinebox.h b/src/inlinebox.h index 03e5ec8..24c11d3 100644 --- a/src/inlinebox.h +++ b/src/inlinebox.h @@ -101,6 +101,8 @@ struct _InlineBox GObject *focused_object; guint selection_start; guint selection_end; + guint match_start; + guint match_end; gboolean wrap; }; @@ -109,10 +111,12 @@ struct _InlineBoxClass GtkContainerClass parent_class; }; -GType inline_box_get_type(void) G_GNUC_CONST; -InlineBox *inline_box_new(void); -void inline_box_add_text(InlineBox *container, IBText *text); -void inline_box_break(InlineBox *container); +GType inline_box_get_type (void) G_GNUC_CONST; +InlineBox *inline_box_new (void); +void inline_box_add_text (InlineBox *container, IBText *text); +void inline_box_break (InlineBox *container); +gchar *inline_box_get_text (InlineBox *ib); +gint inline_box_search (InlineBox *ib, guint start, gint end, const gchar *str); G_END_DECLS @@ -41,19 +41,70 @@ key_press_event_cb (GtkWidget *widget, GdkEventKey *ev, GtkStack *tabs) return TRUE; } else if (ev->keyval == GDK_KEY_w) { GtkWidget *current_tab = gtk_stack_get_visible_child(tabs); - if (current_tab) { + if (current_tab != NULL) { gtk_widget_destroy(current_tab); + return TRUE; + } + } else if (ev->keyval == GDK_KEY_s) { + GtkWidget *current_tab = gtk_stack_get_visible_child(tabs); + if (current_tab != NULL) { + BrowserBox *bb = BROWSER_BOX(current_tab); + if (bb->search_state == SEARCH_INACTIVE) { + bb->search_state = SEARCH_FORWARD; + browser_box_display_search_status(bb); + } else if (bb->search_state == SEARCH_FORWARD) { + TextSearchState *tss = &(DOCUMENT_BOX(bb->builder_state->docbox)->search); + if (tss->state == FOUND) { + tss->start++; + } + document_box_find(DOCUMENT_BOX(bb->builder_state->docbox), bb->search_string); + browser_box_display_search_status(bb); + } + return TRUE; + } + } else if (ev->keyval == GDK_KEY_g) { + GtkWidget *current_tab = gtk_stack_get_visible_child(tabs); + if (current_tab != NULL) { + BrowserBox *bb = BROWSER_BOX(current_tab); + if (bb->search_state != SEARCH_INACTIVE) { + bb->search_state = SEARCH_INACTIVE; + bb->search_string[0] = 0; + browser_box_set_status(bb, "Interrupted"); + } } - return TRUE; } } GtkWidget *current_tab = gtk_stack_get_visible_child(tabs); if (current_tab != NULL) { BrowserBox *bb = BROWSER_BOX(current_tab); - if (ev->keyval == GDK_KEY_Back || ev->keyval == GDK_KEY_BackSpace) { + if (ev->keyval == GDK_KEY_Back || + (bb->search_state == SEARCH_INACTIVE && + ev->keyval == GDK_KEY_BackSpace)) { return history_back(bb); } else if (ev->keyval == GDK_KEY_Forward) { return history_forward(bb); + } else if (bb->search_state != SEARCH_INACTIVE && + bb->builder_state != NULL && + bb->builder_state->docbox != NULL) { + size_t ss_len = strlen(bb->search_string); + DocumentBox *db = DOCUMENT_BOX(bb->builder_state->docbox); + if (ev->keyval == GDK_KEY_BackSpace && ss_len > 0) { + /* todo: this won't work well for unicode */ + bb->search_string[ss_len - 1] = 0; + document_box_find(db, bb->search_string); + browser_box_display_search_status(bb); + return TRUE; + } + if (ss_len + 4 < MAX_SEARCH_STRING_LEN) { + gunichar c = gdk_keyval_to_unicode(ev->keyval); + gint c_len = g_unichar_to_utf8(c, bb->search_string + ss_len); + if (c_len > 0) { + bb->search_string[ss_len + c_len] = 0; + browser_box_display_search_status(bb); + document_box_find(db, bb->search_string); + return TRUE; + } + } } } return FALSE; |