Combined search and combobox?

classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

Combined search and combobox?

Gtk+ - Dev - General mailing list
Is there a widget that combines a searchbox with a combobox?

A use case would be to search for a fontname in a very long font list.

I would like to be able to type a search string, and have the opened combobox display only entries that match the typed string. A plus would be if it is possible to change how matches take place, e.g. between multiple word (like helm-mode in emacs), a regular expression, or an absolute match.

Has someone written anything like that?

Regards,
Dov

_______________________________________________
gtk-devel-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-devel-list
Reply | Threaded
Open this post in threaded view
|

Re: Combined search and combobox?

Reuben Rissler
On 07/26/2018 07:36 AM, Dov Grobgeld via gtk-devel-list wrote:
Is there a widget that combines a searchbox with a combobox?

A use case would be to search for a fontname in a very long font list.

I would like to be able to type a search string, and have the opened combobox display only entries that match the typed string. A plus would be if it is possible to change how matches take place, e.g. between multiple word (like helm-mode in emacs), a regular expression, or an absolute match.

Has someone written anything like that?

You didn't specify a language, so here's an example in Python. It uses space separated keywords, like in Google search or so. It may not be exactly as you requested, but will give you something to start with.

#! /usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import os, sys


class GUI (Gtk.Window):
    def __init__(self):

        Gtk.Window.__init__(self, title="Combo with search")
        self.model = Gtk.ListStore(str)
        self.populate_model()
        #combobox
        combo = Gtk.ComboBox.new_with_model_and_entry(model = self.model)
        combo.set_entry_text_column(0)
        combo.connect('changed', self.changed)
        #completion
        completion = Gtk.EntryCompletion ()
        completion.set_model(self.model)
        completion.set_text_column(0)
        completion.set_match_func(self.match_func)
        completion.connect ('match-selected', self.match_selected)
        #combobox entry
        entry = combo.get_child()
        entry.set_completion (completion)
        #main window
        self.add (combo)
        self.show_all()

    def changed (self, combo):
        _iter = combo.get_active_iter()
        if _iter != None:
            font = self.model[_iter][0]
            print ('You selected combo:', font)

    def match_selected (self, completion, model, _iter):
        print ('You selected completion:', model[_iter][0])

    def match_func (self, completion, string, _iter):
        for word in string.split():
            if word not in self.model[_iter][0].lower(): #search is always lower case
                return False
        return True

    def on_window_destroy(self, window):
        Gtk.main_quit()

    def populate_model (self):
        for i in range (100):
            self.model.append(["Font %d" % i])

def main():
    app = GUI()
    Gtk.main()
       
if __name__ == "__main__":
    sys.exit(main())


Reuben



_______________________________________________
gtk-devel-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-devel-list
Reply | Threaded
Open this post in threaded view
|

Re: Combined search and combobox?

Gtk+ - Dev - General mailing list
Thanks. This is exactly what I was looking for! I didn't realize that a combobox can be attached to a model, and that a model can be filtered.

Imo multiple partial string match should be default behavior, which it is unfortunately not. E.g. inkscape only matches in the beginning of the string.

Is there a GNOME guide line about this?

Regards,
Dov

On Thu, Jul 26, 2018 at 8:38 PM Reuben Rissler <[hidden email]> wrote:
On 07/26/2018 07:36 AM, Dov Grobgeld via gtk-devel-list wrote:
Is there a widget that combines a searchbox with a combobox?

A use case would be to search for a fontname in a very long font list.

I would like to be able to type a search string, and have the opened combobox display only entries that match the typed string. A plus would be if it is possible to change how matches take place, e.g. between multiple word (like helm-mode in emacs), a regular expression, or an absolute match.

Has someone written anything like that?

You didn't specify a language, so here's an example in Python. It uses space separated keywords, like in Google search or so. It may not be exactly as you requested, but will give you something to start with.

#! /usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import os, sys


class GUI (Gtk.Window):
    def __init__(self):

        Gtk.Window.__init__(self, title="Combo with search")
        self.model = Gtk.ListStore(str)
        self.populate_model()
        #combobox
        combo = Gtk.ComboBox.new_with_model_and_entry(model = self.model)
        combo.set_entry_text_column(0)
        combo.connect('changed', self.changed)
        #completion
        completion = Gtk.EntryCompletion ()
        completion.set_model(self.model)
        completion.set_text_column(0)
        completion.set_match_func(self.match_func)
        completion.connect ('match-selected', self.match_selected)
        #combobox entry
        entry = combo.get_child()
        entry.set_completion (completion)
        #main window
        self.add (combo)
        self.show_all()

    def changed (self, combo):
        _iter = combo.get_active_iter()
        if _iter != None:
            font = self.model[_iter][0]
            print ('You selected combo:', font)

    def match_selected (self, completion, model, _iter):
        print ('You selected completion:', model[_iter][0])

    def match_func (self, completion, string, _iter):
        for word in string.split():
            if word not in self.model[_iter][0].lower(): #search is always lower case
                return False
        return True

    def on_window_destroy(self, window):
        Gtk.main_quit()

    def populate_model (self):
        for i in range (100):
            self.model.append(["Font %d" % i])

def main():
    app = GUI()
    Gtk.main()
       
if __name__ == "__main__":
    sys.exit(main())


Reuben


_______________________________________________
gtk-devel-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-devel-list

_______________________________________________
gtk-devel-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-devel-list
Reply | Threaded
Open this post in threaded view
|

Re: Combined search and combobox?

Reuben Rissler
On 07/27/2018 12:15 AM, Dov Grobgeld wrote:
Thanks. This is exactly what I was looking for! I didn't realize that a combobox can be attached to a model, and that a model can be filtered.
Gtk models are powerful, but with great power comes great complexity ;)

Imo multiple partial string match should be default behavior, which it is unfortunately not. E.g. inkscape only matches in the beginning of the string.
Agreed.

Is there a GNOME guide line about this?
Not that I have seen in my travels using Gtk.

I came up with this tool out of necessity for my accounting/small business system. I never gave it a thought somebody else would find it useful until your post to this mailing list. When you posted, I thought you might find this interesting. So would this be useful to a wider audience? I don't know if the Gtk devs would consider making a special combo with this feature, as it seems so easy to setup. After you know how :)

Glad to be of help,
Reuben

_______________________________________________
gtk-devel-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-devel-list
Reply | Threaded
Open this post in threaded view
|

Re: Combined search and combobox?

Gtk+ - Dev - General mailing list
I agree that setting this up is a bit too complex, and it would be nice if there was a standalone widget that this everything for you.

Meanwhile I tried converting your widget into C (with the intention of replacing the font selection widget in gucharmap), and something is not working. I get lots of warnings about:

(gucharmap:30880): Gtk-CRITICAL **: 17:41:07.804: gtk_entry_set_text: assertion 'text != NULL' failed

This happens e.g. when I'm using the up down arrows within the text entry. Btw, this happens also if I only replace (without setting up all the connections), gtk_combo_box_new() with gtk_combo_box_new_with_entry(). I'm still trying to figure out what went wrong.

Regards,
Dov


On Fri, Jul 27, 2018 at 2:25 PM Reuben Rissler <[hidden email]> wrote:
On 07/27/2018 12:15 AM, Dov Grobgeld wrote:
Thanks. This is exactly what I was looking for! I didn't realize that a combobox can be attached to a model, and that a model can be filtered.
Gtk models are powerful, but with great power comes great complexity ;)

Imo multiple partial string match should be default behavior, which it is unfortunately not. E.g. inkscape only matches in the beginning of the string.
Agreed.

Is there a GNOME guide line about this?
Not that I have seen in my travels using Gtk.

I came up with this tool out of necessity for my accounting/small business system. I never gave it a thought somebody else would find it useful until your post to this mailing list. When you posted, I thought you might find this interesting. So would this be useful to a wider audience? I don't know if the Gtk devs would consider making a special combo with this feature, as it seems so easy to setup. After you know how :)

Glad to be of help,
Reuben
_______________________________________________
gtk-devel-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-devel-list

_______________________________________________
gtk-devel-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-devel-list
Reply | Threaded
Open this post in threaded view
|

Re: Combined search and combobox?

Reuben Rissler


(gucharmap:30880): Gtk-CRITICAL **: 17:41:07.804: gtk_entry_set_text: assertion 'text != NULL' failed

This happens e.g. when I'm using the up down arrows within the text entry. Btw, this happens also if I only replace (without setting up all the connections), gtk_combo_box_new() with gtk_combo_box_new_with_entry(). I'm still trying to figure out what went wrong.

I will hazard a guess that you didn't set the text column with gtk_combo_box_set_entry_text_column ()

Reuben


_______________________________________________
gtk-devel-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-devel-list
Reply | Threaded
Open this post in threaded view
|

Re: Combined search and combobox?

Gtk+ - Dev - General mailing list
Indeed, you were right regarding gtk_comb_box_set_entry_text_column().

For the record, below find a translation into C of your python program. Here are some enhancements that I thought of, though I highly doubt that they will be adapted because they are too specialized.
  • Turn it into a widget, that takes a receives of strings in its constructor.
  • Add keybindings for the following functionality:
    • Restore the last string input into the entry widget before selecting a match i the combo-box. E.g. [Meta-Up], [Ctrl-/]
    • Select next entry matching the last string input. [Ctrl-Up]
    • Select previous entry matching the last string input. [Ctrl-Down]
    • Toggle mathing method between Regular expressions, and partial string matches. [Ctrl-=]
  • The special keybindings should also be shown when doing rightclick in the enty widget, that should allow the above functionality.
Based on the code below, I have now also created a pull request for gucharmap: https://github.com/GNOME/gucharmap/pull/2 .

Thanks again!
#include <stdlib.h>
#include <gtk/gtk.h>

enum {
      COL_FONT = 0
};

void populate_store(GtkListStore *store)
{
    int i;

    for (i=0; i<100; i++) {
        char buf[100];
        GtkTreeIter iter;
        sprintf(buf, "Font %d", i);
        gtk_list_store_insert_with_values(store,
                                          &iter,
                                          -1,
                                          COL_FONT, buf,
                                          -1);
    }
}

static gboolean
match_function (GtkEntryCompletion *completion,
                const gchar *key,
                GtkTreeIter *iter,
                gpointer user_data)
{
    GtkTreeModel *model = GTK_TREE_MODEL(user_data);
    char *family, *family_fold;
    gchar **sub_match, **p;
    gboolean all_matches = TRUE;

    gtk_tree_model_get (model,
                        iter,
                        COL_FONT, &family,
                        -1);
    family_fold = g_utf8_casefold (family, -1);

    /* Match by all space separated substrings are part of family string */
    sub_match = g_strsplit(key, " ", -1);
    p = sub_match;
    while (*p) {
        gchar *match_fold = g_utf8_casefold (*p++, -1);
        if (!g_strstr_len (family_fold, -1, match_fold)) {
            all_matches = FALSE;
            break;
        }
    }

    g_free (family);
    g_free (family_fold);
    g_strfreev (sub_match);

    return all_matches;
}


static gboolean
completion_match_selected(GtkEntryCompletion *widget,
                          GtkTreeModel       *model,
                          GtkTreeIter        *iter,
                          gpointer           *user_data)
{
    char *font;

    gtk_tree_model_get (model,
                        iter,
                        COL_FONT, &font,
                        -1);
    printf("selected %s\n", font);
    g_free (font);

    return FALSE;
}

static void
combo_changed (GtkComboBox *combo,
               gpointer user_data)
{
    GtkTreeModel *model = GTK_TREE_MODEL (user_data);  
    GtkTreeIter iter;
    char *font;

    if (!gtk_combo_box_get_active_iter (combo, &iter))
        return;

    gtk_tree_model_get (model,
                        &iter,
                        COL_FONT, &font,
                        -1);
    if (!font)
        return;

    printf("Choosen %s\n", font);
    g_free (font);
}


int main(int argc, char **argv)
{
    gtk_init(&argc, &argv);
    GtkWidget *w_mw = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    g_signal_connect(w_mw,"destroy",gtk_main_quit, NULL);

    GtkListStore *store = gtk_list_store_new (1, G_TYPE_STRING);
    populate_store(store);
    GtkWidget *w_combo = gtk_combo_box_new_with_model_and_entry (GTK_TREE_MODEL (store));
    gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX(w_combo), COL_FONT);
    g_signal_connect (w_combo, "changed",
                      G_CALLBACK (combo_changed), store);
  
    GtkEntryCompletion *completion = gtk_entry_completion_new();
    gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (store));
    gtk_entry_completion_set_text_column (completion, COL_FONT);
    gtk_entry_completion_set_match_func(completion, match_function, GTK_TREE_MODEL(store), NULL);
    g_signal_connect (G_OBJECT (completion), "match-selected",
                      G_CALLBACK (completion_match_selected), NULL);

    gtk_entry_set_completion (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (w_combo))),
                              completion);

    gtk_container_add(GTK_CONTAINER(w_mw), w_combo);
    gtk_widget_show_all(w_mw);
    gtk_main();

    exit(0);
} 


On Sat, Jul 28, 2018 at 2:34 PM Reuben Rissler <[hidden email]> wrote:


(gucharmap:30880): Gtk-CRITICAL **: 17:41:07.804: gtk_entry_set_text: assertion 'text != NULL' failed

This happens e.g. when I'm using the up down arrows within the text entry. Btw, this happens also if I only replace (without setting up all the connections), gtk_combo_box_new() with gtk_combo_box_new_with_entry(). I'm still trying to figure out what went wrong.

I will hazard a guess that you didn't set the text column with gtk_combo_box_set_entry_text_column ()

Reuben


_______________________________________________
gtk-devel-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-devel-list