From 80b5618bc571101e19d39b9565e69ed93513c71b Mon Sep 17 00:00:00 2001 From: Daniel Carl Date: Tue, 25 Jun 2013 19:36:07 +0200 Subject: [PATCH] Use TreeView to render the completions (#39). This seems to work, but there are some known issues with gtk3 at the moment. - The tree view widget size could not be set, so that tree items could be hidden if there are to many of them in completion - The active styles are not applied if used with gtk3 - The tree view shrinks to on single item and becomes unusable on some urls of the history - seems there are some char in them that break the tree view or the column renderer Not that the configuration for max-completion-items was removed that could not be applied to show the tree view. --- src/completion.c | 348 +++++++++++++++++++++-------------------------- src/completion.h | 2 +- src/config.h | 1 - src/main.h | 1 - src/setting.c | 10 +- 5 files changed, 160 insertions(+), 202 deletions(-) diff --git a/src/completion.c b/src/completion.c index c6ef46d..3e2df42 100644 --- a/src/completion.c +++ b/src/completion.c @@ -26,82 +26,65 @@ #include "setting.h" #define TAG_INDICATOR '!' +#define COMP_ITEM 0 extern VbCore vb; -typedef struct { - GtkWidget *label; - GtkWidget *event; - char *prefix; -} Completion; - static struct { - GList *completions; - GList *active; int count; char *prefix; + int active; } comps; -static GList *init_completion(GList *target, GList *source, const char *prefix); -static GList *update(GList *completion, GList *active, gboolean back); -static void show(gboolean back); -static void set_entry_text(Completion *completion); -static char *get_text(Completion *completion); -static Completion *get_new(const char *label, const char *prefix); -static void free_completion(Completion *completion); +static gboolean init_completion(GList *source); +static void show(GtkTreeView *tree); +static void update(GtkTreeView *tree, gboolean back); +static void echo_selection(GtkTreeSelection *sel); +static char *get_text(GtkTreeView *tree); gboolean completion_complete(gboolean back) { VbInputType type; const char *input, *prefix, *suffix; GList *source = NULL; + gboolean hasItems = false; input = GET_TEXT(); type = vb_get_input_parts(input, &prefix, &suffix); - if (comps.completions && comps.active - && (vb.state.mode & VB_MODE_COMPLETE) - ) { - char *text = get_text((Completion*)comps.active->data); + if (vb.state.mode & VB_MODE_COMPLETE) { + char *text = get_text(GTK_TREE_VIEW(vb.gui.compbox)); if (!strcmp(input, text)) { - /* updatecompletions */ - comps.active = update(comps.completions, comps.active, back); + /* step through the next/prev completion item */ + update(GTK_TREE_VIEW(vb.gui.compbox), back); g_free(text); + return true; - } else { - g_free(text); - /* if current input isn't the content of the completion item */ - completion_clean(); } + g_free(text); + /* if current input isn't the content of the completion item, stop + * completion and start it after that again */ + completion_clean(); } - /* don't disturb other command sub modes - complate only if no sub mode + /* don't disturb other command sub modes - complete only if no sub mode * is set before */ if (vb.state.mode != VB_MODE_COMMAND) { return false; } - /* create new completion */ -#ifdef HAS_GTK3 - vb.gui.compbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_box_set_homogeneous(GTK_BOX(vb.gui.compbox), true); -#else - vb.gui.compbox = gtk_vbox_new(true, 0); -#endif - gtk_box_pack_start(GTK_BOX(vb.gui.box), vb.gui.compbox, false, false, 0); - if (type == VB_INPUT_SET) { source = g_list_sort(setting_get_by_prefix(suffix), (GCompareFunc)g_strcmp0); - comps.completions = init_completion(comps.completions, source, prefix); + hasItems = init_completion(source); g_list_free(source); } else if (type == VB_INPUT_OPEN || type == VB_INPUT_TABOPEN) { /* if search string begins with TAG_INDICATOR lookup the bookmarks */ if (suffix && *suffix == TAG_INDICATOR) { source = bookmark_get_by_tags(suffix + 1); - comps.completions = init_completion(comps.completions, source, prefix); + hasItems = init_completion(source); } else { source = history_get_by_tags(HISTORY_URL, suffix); - comps.completions = init_completion(comps.completions, source, prefix); + hasItems = init_completion(source); } g_list_free_full(source, (GDestroyNotify)g_free); } else if (type == VB_INPUT_COMMAND) { @@ -110,216 +93,201 @@ gboolean completion_complete(gboolean back) comps.count = g_ascii_strtoll(suffix, &command, 10); source = g_list_sort(command_get_by_prefix(command), (GCompareFunc)g_strcmp0); - comps.completions = init_completion(comps.completions, source, prefix); + hasItems = init_completion(source); g_list_free(source); } else if (type == VB_INPUT_SEARCH_FORWARD || type == VB_INPUT_SEARCH_BACKWARD) { source = g_list_sort(history_get_by_tags(HISTORY_SEARCH, suffix), (GCompareFunc)g_strcmp0); - comps.completions = init_completion(comps.completions, source, prefix); + hasItems = init_completion(source); g_list_free_full(source, (GDestroyNotify)g_free); } - if (!comps.completions) { + if (!hasItems) { return false; } vb_set_mode(VB_MODE_COMMAND | VB_MODE_COMPLETE, false); - show(back); + OVERWRITE_STRING(comps.prefix, prefix); + show(GTK_TREE_VIEW(vb.gui.compbox)); return true; } -void completion_clean() +void completion_clean(void) { - g_list_free_full(comps.completions, (GDestroyNotify)free_completion); - comps.completions = NULL; - if (vb.gui.compbox) { gtk_widget_destroy(vb.gui.compbox); vb.gui.compbox = NULL; } OVERWRITE_STRING(comps.prefix, NULL); - comps.active = NULL; - comps.count = 0; + comps.count = 0; + comps.active = 0; /* remove completion flag from mode */ vb.state.mode &= ~VB_MODE_COMPLETE; } -static GList *init_completion(GList *target, GList *source, const char *prefix) +static gboolean init_completion(GList *source) { - OVERWRITE_STRING(comps.prefix, prefix); + GtkListStore *store; + GtkCellRenderer *renderer; + GtkTreeSelection *selection; + GtkTreeIter iter; + GtkRequisition size; + Gui *gui = &vb.gui; + gboolean hasItems = (source != NULL); + int height; + + /* init the tree view and the list store */ + gui->compbox = gtk_tree_view_new(); + gtk_box_pack_end(GTK_BOX(gui->box), gui->compbox, false, false, 0); + + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gui->compbox), false); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(renderer, + "font-desc", vb.style.comp_font, + "ellipsize", PANGO_ELLIPSIZE_MIDDLE, + NULL + ); + + VB_WIDGET_OVERRIDE_COLOR(gui->compbox, GTK_STATE_NORMAL, &vb.style.comp_fg[VB_COMP_NORMAL]); + VB_WIDGET_OVERRIDE_TEXT(gui->compbox, GTK_STATE_NORMAL, &vb.style.comp_fg[VB_COMP_NORMAL]); + VB_WIDGET_OVERRIDE_BASE(gui->compbox, GTK_STATE_NORMAL, &vb.style.comp_bg[VB_COMP_NORMAL]); + VB_WIDGET_OVERRIDE_BACKGROUND(gui->compbox, GTK_STATE_NORMAL, &vb.style.comp_bg[VB_COMP_NORMAL]); + + VB_WIDGET_OVERRIDE_COLOR(gui->compbox, GTK_STATE_ACTIVE, &vb.style.comp_fg[VB_COMP_ACTIVE]); + VB_WIDGET_OVERRIDE_TEXT(gui->compbox, GTK_STATE_ACTIVE, &vb.style.comp_fg[VB_COMP_ACTIVE]); + VB_WIDGET_OVERRIDE_BASE(gui->compbox, GTK_STATE_ACTIVE, &vb.style.comp_bg[VB_COMP_ACTIVE]); + VB_WIDGET_OVERRIDE_BACKGROUND(gui->compbox, GTK_STATE_ACTIVE, &vb.style.comp_bg[VB_COMP_ACTIVE]); + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(gui->compbox)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE); + + gtk_tree_view_insert_column_with_attributes( + GTK_TREE_VIEW(gui->compbox), -1, "", renderer, "text", COMP_ITEM, NULL + ); + + store = gtk_list_store_new(1, G_TYPE_STRING); for (GList *l = source; l; l = l->next) { - Completion *c = get_new(l->data, prefix); - target = g_list_prepend(target, c); - gtk_box_pack_start(GTK_BOX(vb.gui.compbox), c->event, true, true, 0); + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, COMP_ITEM, l->data, -1); } - target = g_list_reverse(target); + /* add the model after inserting the items - that's faster */ + gtk_tree_view_set_model(GTK_TREE_VIEW(gui->compbox), GTK_TREE_MODEL(store)); + g_object_unref(store); + + /* use max 1/3 of window height for the completion */ +#ifdef HAS_GTK3 + gtk_widget_get_preferred_size(gui->compbox, NULL, &size); +#else + gtk_widget_size_request(gui->compbox, &size); +#endif + gtk_window_get_size(GTK_WINDOW(gui->window), NULL, &height); + height /= 3; + if (size.height > height) { + gtk_widget_set_size_request(gui->compbox, -1, height); + } - return target; + return hasItems; } -static GList *update(GList *completion, GList *active, gboolean back) +/* allow to chenge the direction of display */ +static void show(GtkTreeView *tree) { - GList *old, *new; - Completion *comp; + GtkTreeSelection *selection; + GtkTreePath *path; + + /* this prevents the first item to be placed out of view if the completion + * is shown */ + gtk_widget_show(GTK_WIDGET(tree)); + while (gtk_events_pending()) { + gtk_main_iteration(); + } - int length = g_list_length(completion); - int max = vb.config.max_completion_items; - int items = MAX(length, max); - int r = (max) % 2; - int offset = max / 2 - 1 + r; + /* select the first completion item */ + path = gtk_tree_path_new_from_indices(0, -1); - old = active; - int position = g_list_position(completion, active) + 1; - if (back) { - if (!(new = old->prev)) { - new = g_list_last(completion); - } - if (position - 1 > offset && position < items - offset + r) { - comp = g_list_nth(completion, position - offset - 2)->data; - gtk_widget_show_all(comp->event); - comp = g_list_nth(completion, position + offset - r)->data; - gtk_widget_hide(comp->event); - } else if (position == 1) { - int i = 0; - for (GList *l = g_list_first(completion); l && i < max; l = l->next, i++) { - gtk_widget_hide(((Completion*)l->data)->event); - } - i = 0; - for (GList *l = g_list_last(completion); l && i < max; l = l->prev, i++) { - comp = l->data; - gtk_widget_show_all(comp->event); - } - } - } else { - if (!(new = old->next)) { - new = g_list_first(completion); - } - if (position > offset && position < items - offset - 1 + r) { - comp = g_list_nth(completion, position - offset - 1)->data; - gtk_widget_hide(comp->event); - comp = g_list_nth(completion, position + offset + 1 - r)->data; - gtk_widget_show_all(comp->event); - } else if (position == items || position == 1) { - int i = 0; - for (GList *l = g_list_last(completion); l && i < max; l = l->prev, i++) { - gtk_widget_hide(((Completion*)l->data)->event); - } - i = 0; - for (GList *l = g_list_first(completion); l && i < max; l = l->next, i++) { - gtk_widget_show_all(((Completion*)l->data)->event); - } - } - } + gtk_tree_view_set_cursor(tree, path, NULL, false); + gtk_tree_path_free(path); - VB_WIDGET_SET_STATE(((Completion*)old->data)->label, VB_GTK_STATE_NORMAL); - VB_WIDGET_SET_STATE(((Completion*)old->data)->event, VB_GTK_STATE_NORMAL); - VB_WIDGET_SET_STATE(((Completion*)new->data)->label, VB_GTK_STATE_ACTIVE); - VB_WIDGET_SET_STATE(((Completion*)new->data)->event, VB_GTK_STATE_ACTIVE); + selection = gtk_tree_view_get_selection(tree); - active = new; - set_entry_text(active->data); - return active; + echo_selection(selection); } -/* allow to chenge the direction of display */ -static void show(gboolean back) +static void update(GtkTreeView *tree, gboolean back) { - guint max = vb.config.max_completion_items; - int i = 0; + int rows; + GtkTreeSelection *selection; + GtkTreePath *path; + + rows = gtk_tree_model_iter_n_children(gtk_tree_view_get_model(tree), NULL); if (back) { - comps.active = g_list_last(comps.completions); - for (GList *l = comps.active; l && i < max; l = l->prev, i++) { - gtk_widget_show_all(((Completion*)l->data)->event); + /* step back */ + if (--comps.active < 0) { + comps.active = rows - 1; } } else { - comps.active = g_list_first(comps.completions); - for (GList *l = comps.active; l && i < max; l = l->next, i++) { - gtk_widget_show_all(((Completion*)l->data)->event); + /* step forward */ + if (++comps.active >= rows) { + comps.active = 0; } } - if (comps.active != NULL) { - Completion *active = (Completion*)comps.active->data; - VB_WIDGET_SET_STATE(active->label, VB_GTK_STATE_ACTIVE); - VB_WIDGET_SET_STATE(active->event, VB_GTK_STATE_ACTIVE); - set_entry_text(active); - gtk_widget_show(vb.gui.compbox); - } + /* get new path and move cursor to it */ + path = gtk_tree_path_new_from_indices(comps.active, -1); + gtk_tree_view_set_cursor(tree, path, NULL, false); + gtk_tree_path_free(path); + + selection = gtk_tree_view_get_selection(tree); + echo_selection(selection); } -static void set_entry_text(Completion *completion) +static void echo_selection(GtkTreeSelection *sel) { - char *text = get_text(completion); - gtk_entry_set_text(GTK_ENTRY(vb.gui.inputbox), text); - gtk_editable_set_position(GTK_EDITABLE(vb.gui.inputbox), -1); - g_free(text); + GtkTreeIter iter; + GtkTreeModel *model; + + if (gtk_tree_selection_get_selected(sel, &model, &iter)) { + char *value; + gtk_tree_model_get(model, &iter, COMP_ITEM, &value, -1); + if (comps.count) { + vb_echo_force(VB_MSG_NORMAL, false, "%s%d%s", comps.prefix, comps.count, value); + } else { + vb_echo_force(VB_MSG_NORMAL, false, "%s%s", comps.prefix, value); + } + g_free(value); + } } /** - * Retrieves the full new allocated entry text for given completion item. + * Retrieves the full new allocated entry text for selected tree view item. + * Returnes string must be freed. */ -static char *get_text(Completion *completion) +static char *get_text(GtkTreeView *tree) { char *text = NULL; - - /* print the previous typed command count into inputbox too */ - if (comps.count) { - text = g_strdup_printf( - "%s%d%s", completion->prefix, comps.count, gtk_label_get_text(GTK_LABEL(completion->label)) - ); - } else { - text = g_strdup_printf( - "%s%s", completion->prefix, gtk_label_get_text(GTK_LABEL(completion->label)) - ); + GtkTreeIter iter; + GtkTreeSelection *selection; + GtkTreeModel *model; + + /* select the first completion item */ + model = gtk_tree_view_get_model(tree); + selection = gtk_tree_view_get_selection(tree); + + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + char *value; + gtk_tree_model_get(model, &iter, COMP_ITEM, &value, -1); + if (comps.count) { + text = g_strdup_printf("%s%d%s", comps.prefix, comps.count, value); + } else { + text = g_strdup_printf("%s%s", comps.prefix, value); + } + g_free(value); } return text; } - -static Completion *get_new(const char *label, const char *prefix) -{ - const int padding = 2; - Completion *c = g_new0(Completion, 1); - - c->label = gtk_label_new(label); - c->event = gtk_event_box_new(); - c->prefix = g_strdup(prefix); - -#ifdef HAS_GTK3 - GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_box_set_homogeneous(GTK_BOX(hbox), true); -#else - GtkWidget *hbox = gtk_hbox_new(true, 0); -#endif - - gtk_box_pack_start(GTK_BOX(hbox), c->label, true, true, 5); - gtk_label_set_ellipsize(GTK_LABEL(c->label), PANGO_ELLIPSIZE_MIDDLE); - gtk_misc_set_alignment(GTK_MISC(c->label), 0.0, 0.5); - - VB_WIDGET_SET_STATE(c->label, VB_GTK_STATE_NORMAL); - VB_WIDGET_SET_STATE(c->event, VB_GTK_STATE_NORMAL); - - VB_WIDGET_OVERRIDE_COLOR(c->label, GTK_STATE_NORMAL, &vb.style.comp_fg[VB_COMP_NORMAL]); - VB_WIDGET_OVERRIDE_COLOR(c->label, GTK_STATE_ACTIVE, &vb.style.comp_fg[VB_COMP_ACTIVE]); - VB_WIDGET_OVERRIDE_BACKGROUND(c->event, GTK_STATE_NORMAL, &vb.style.comp_bg[VB_COMP_NORMAL]); - VB_WIDGET_OVERRIDE_BACKGROUND(c->event, GTK_STATE_ACTIVE, &vb.style.comp_bg[VB_COMP_ACTIVE]); - VB_WIDGET_OVERRIDE_FONT(c->label, vb.style.comp_font); - - GtkWidget *alignment = gtk_alignment_new(0.5, 0.5, 1, 1); - gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), padding, padding, padding, padding); - gtk_container_add(GTK_CONTAINER(alignment), hbox); - gtk_container_add(GTK_CONTAINER(c->event), alignment); - - return c; -} - -static void free_completion(Completion *completion) -{ - gtk_widget_destroy(completion->event); - g_free(completion->prefix); - g_free(completion); -} diff --git a/src/completion.h b/src/completion.h index 0b66b08..2337a73 100644 --- a/src/completion.h +++ b/src/completion.h @@ -22,7 +22,7 @@ #include "main.h" -void completion_clean(); +void completion_clean(void); gboolean completion_complete(gboolean back); #endif /* end of include guard: _COMPLETION_H */ diff --git a/src/config.h b/src/config.h index beb36bc..9e39796 100644 --- a/src/config.h +++ b/src/config.h @@ -140,7 +140,6 @@ const char *default_config[] = { "set completion-fg-active=#fff", "set completion-bg-normal=#656565", "set completion-bg-active=#777", - "set max-completion-items=15", "set hint-bg=#ff0", "set hint-bg-focus=#8f0", "set hint-fg=#000", diff --git a/src/main.h b/src/main.h index 904b7c8..c4896aa 100644 --- a/src/main.h +++ b/src/main.h @@ -265,7 +265,6 @@ typedef struct { typedef struct { time_t cookie_timeout; int scrollstep; - guint max_completion_items; char *home_page; char *download_dir; guint history_max; diff --git a/src/setting.c b/src/setting.c index 1719b17..6ba6c8d 100644 --- a/src/setting.c +++ b/src/setting.c @@ -95,7 +95,6 @@ static Setting default_settings[] = { {NULL, "completion-fg-active", TYPE_COLOR, completion_style, {0}}, {NULL, "completion-bg-normal", TYPE_COLOR, completion_style, {0}}, {NULL, "completion-bg-active", TYPE_COLOR, completion_style, {0}}, - {NULL, "max-completion-items", TYPE_INTEGER, completion_style, {0}}, {NULL, "hint-bg", TYPE_CHAR, hint_style, {0}}, {NULL, "hint-bg-focus", TYPE_CHAR, hint_style, {0}}, {NULL, "hint-fg", TYPE_CHAR, hint_style, {0}}, @@ -487,14 +486,7 @@ static gboolean completion_style(const Setting *s, const SettingType type) Style *style = &vb.style; CompletionStyle ctype = g_str_has_suffix(s->name, "normal") ? VB_COMP_NORMAL : VB_COMP_ACTIVE; - if (s->type == TYPE_INTEGER) { - /* max completion items */ - if (type == SETTING_GET) { - print_value(s, &vb.config.max_completion_items); - } else { - vb.config.max_completion_items = s->arg.i; - } - } else if (s->type == TYPE_FONT) { + if (s->type == TYPE_FONT) { if (type == SETTING_GET) { print_value(s, style->comp_font); } else { -- 2.20.1