Use TreeView to render the completions (#39).
authorDaniel Carl <danielcarl@gmx.de>
Tue, 25 Jun 2013 17:36:07 +0000 (19:36 +0200)
committerDaniel Carl <danielcarl@gmx.de>
Wed, 26 Jun 2013 09:00:33 +0000 (11:00 +0200)
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
src/completion.h
src/config.h
src/main.h
src/setting.c

index c6ef46d..3e2df42 100644 (file)
 #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);
-}
index 0b66b08..2337a73 100644 (file)
@@ -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 */
index beb36bc..9e39796 100644 (file)
@@ -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",
index 904b7c8..c4896aa 100644 (file)
@@ -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;
index 1719b17..6ba6c8d 100644 (file)
@@ -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 {