summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2019-08-24 13:38:44 +0300
committerdefanor <defanor@uberspace.net>2019-08-24 13:38:44 +0300
commitf308e319f3170a2949624e211adf75002ee5d3f1 (patch)
treed086626c17e1a88860dcf44c3bbb5f74ce41d869
parent2395d5ab1e83507530fdbd675c917d0c55187e66 (diff)
Introduce incremental text search
It is quite rough and should be refined, but as with most of the other features, introducing it at this stage primarily to ensure that it will not require major changes.
-rw-r--r--src/browserbox.c85
-rw-r--r--src/browserbox.h13
-rw-r--r--src/documentbox.c63
-rw-r--r--src/documentbox.h22
-rw-r--r--src/inlinebox.c60
-rw-r--r--src/inlinebox.h12
-rw-r--r--src/main.c57
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
diff --git a/src/main.c b/src/main.c
index 65adadf..0603b03 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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;