自定义完成GtkComboBoxText

时间:2016-09-07 09:32:44

标签: c linux gtk gtk3

如何使用"静态"来自定义GtkComboBoxText的完成情况?方面和"动态"一?静态方面是因为某些条目是已知的,并且在构造时使用gtk_combo_box_text_append_text添加到组合框文本中。动态方面是因为我还需要通过一些回调函数来完成,即在创建GtkComboBoxText小部件之后完成动态 - 一旦输入了几个字符。

我的应用程序使用Boehm的GC(当然除了GTK对象),例如Guile或SCM或Bigloo正在做的事情。它可以看作是一个实验性的持久性动态类型编程语言实现,带有编译的集成编辑器和用于Debian / Linux / x86-64的系统GTK3.21库,它用C99编码(其中一些是生成的)并使用GCC6编译。

(我不关心非Linux系统,GTK3库早于GTK3.20,GCC编译器早于GCC6)

问题详情

我输入(输入GtkComboBoxText名称 object-id

  • 名称与C-identifier类似,但以字母开头,不能以下划线结尾。例如,commentifthe_GUIthe_systempayload_jsonx1是有效名称(但 {{1 } _a0bcd 是无效的名称,因为它们以下划线开头或结尾。我目前有十几个名字,但我可以有几千个。因此,只要输入一个或两个字母就可以提供完成是合理的,并且名称的完成可以静态发生,因为它们不是很多(所以我觉得为每个名称调用foobar_是合理的)。

  • object-id 以下划线开头,后跟数字,并且正好有18个字母数字(随机排序)字符。例如,gtk_combo_box_append_text_5Hf0fFKvRVa71ZPM0_8261sbF1f9ohzu2Iu是可能的对象ID。实际上它是96个几乎随机的位(可能只有2 94 是可能的)。 object-id扮演UUID s的角色(在某种意义上,假设它对于不同的对象是全世界唯一的)但具有C友好语法。我目前有几十个物体,但我可能有几十万(或者一百万)。但是假设前缀为_0BV96V94PJIn9si1K_6S3这四个字符,我假设只有一个合理的数字(可能最多十几个,肯定不超过一千个) object-ids < / em>在我的应用程序中存在该前缀。当然,(静态地)注册先验所有对象id是不合理的(完成必须在键入四个字符后发生,并且应该动态发生)。

所以我想要一个既可以在名称上工作的完成(例如,输入一个字母后面跟着另一个字母字符应该足以提出最多一百个选项的完成),以及对象-id(键入四个字符如{ {1}}应该足以触发最多几十个选项的完成,如果不幸的话可能会有一千个选项。

因此,键入三个键 p a 选项卡将提供一些名称,如_22z或{{1}等等...并键入五个键 _ 5 H f tab 将提供很少的对象ID,特别是_826

示例不完整的代码

到目前为止,我编写了以下代码:

payload_json

我有Boehm垃圾收集的值,payload_vectval是指向其中任何一个的指针。值可以标记为整数,指向字符串,对象或元组或对象集的指针。所以_5Hf0fFKvRVa71ZPM0现在包含一组命名对象(可能少于几千个命名对象)。

static GtkWidget *
mom_objectentry (void)
{
  GtkWidget *obent = gtk_combo_box_text_new_with_entry ();
  gtk_widget_set_size_request (obent, 30, 10);
  mo_value_t namsetv = mo_named_objects_set ();

此时我已经对所有(最多数千个)名称进行了排序,并添加了#34;静态&#34;他们使用mo_value_t

namesetv

我有点意外地注意到 int nbnam = mo_set_size (namsetv); MOM_ASSERTPRINTF (nbnam > 0, "bad nbnam"); mo_value_t *namarr = mom_gc_alloc (nbnam * sizeof (mo_value_t)); int cntnam = 0; for (int ix = 0; ix < nbnam; ix++) { mo_objref_t curobr = mo_set_nth (namsetv, ix); mo_value_t curnamv = mo_objref_namev (curobr); if (mo_dyncast_string (curnamv)) namarr[cntnam++] = curnamv; } qsort (namarr, cntnam, sizeof (mo_value_t), mom_obname_cmp); for (int ix = 0; ix < cntnam; ix++) gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (obent), mo_string_cstr (namarr[ix])); 为空。

但我被困在这里。我在考虑:

  1. 让某些gtk_combo_box_text_append_text给出 GtkWidget *combtextent = gtk_bin_get_child (GTK_BIN (obent)); MOM_ASSERTPRINTF (GTK_IS_ENTRY (combtextent), "bad combtextent"); MOM_ASSERTPRINTF (gtk_entry_get_completion (GTK_ENTRY (combtextent)) == NULL, "got completion in combtextent"); 至少包含四个字符的gtk_entry_get_completion (GTK_ENTRY (combtextent))将返回收集的垃圾mom_set_complete_objectid(const char*prefix),表示具有该前缀的对象集。这对我来说很容易编码,差不多完成了。

  2. 制作我自己的本地prefix ...,这会像我想的那样完成。然后我会使用"_47n"

  3. 将其放在我的 gtk-combo-box-text 的文本条目mo_value_t

    是否应使用GtkEntryCompletion* mycompl =添加的条目作为&#34;静态&#34;姓名完成角色?如何使用从combtextent返回的动态设置值动态完成;给定一些对象指针gtk_entry_set_completion(GTK_ENTRY(combtextent), mycompl);和一些gtk_combo_box_text_append_text我可以轻松快速地使用mom_set_complete_objectid对象obr的对象ID填充它。

    我不知道如何编写上述代码。作为参考,我现在只是返回组合框文本:

    char bufid[20];

    我的方法是正确的吗?

    上面的obr函数用于填充具有较短生命周期的模态对话框。

    我赞成简单的代码而不是效率。实际上,我的代码是暂时的(我希望能够引导我的语言,并生成所有的C代码!)并且在实践中我可能只有几百个名称,最多只有几十个对象ID。因此,性能不是很重要,但编码的简单性(一些概念上和#34;扔掉&#34;代码)更为重要。

    我不想(如果可能的话)添加我自己的GTK课程。我更喜欢使用现有的GTK类和小部件,使用GTK信号和回调来定制它们。

    上下文

    我的应用程序是一种实验性persistent编程语言和实现,具有近似Scheme或Python(或JavaScript,忽略原型方面,......)语义但具有广泛的不同(尚未在9月7日 th ,2016)语法(将在GTK小部件中显示和输入),使用Boehm垃圾收集器获取值(包括对象,集合,元组,字符串......) ...值(包括对象)通常是持久的(除了GTK相关数据:应用程序以几乎为空的窗口开始)。在一些Sqlite&#34;数据库&#34;中,整个语言堆以类似JSON的语法保存。 (在应用程序出口处生成)转储到_momstate.sql,在应用程序启动时重新加载。 Object-ids对于在GTK小部件中显示对用户的对象引用,持久性以及生成与对象相关的C代码非常有用(例如,id mo_cstring_from_hi_lo_ids(bufid, obr->mo_ob_hid, obr->mo_ob_loid)的对象可能与 // if the entered text starts with a letter, I want it to be // completed with the appended text above if the entered text starts // with an undersore, then a digit, then two alphanum (like _0BV or // _6S3 for example), I want to call a completion function. #warning objectentry: what should I code here? return obent; } /* end mom_objectentry */ 标识符相关在一些生成的C代码中;这是我使用object-id格式而不是使用UUID的部分原因。

    PS。我的C代码是GPLv3免费软件,可在github上找到。它是MELT监视器,分支expjs,提交e2b3b99ef66394...

    注意:这里提到的对象是隐含的语言对象,而不是GTK对象。 all都有一个唯一的object-id,有些但不是大多数都被命名。

2 个答案:

答案 0 :(得分:5)

我不会显示有关如何操作的确切代码,因为我从未做过GTK&amp; C只有GTK&amp; Python,但它应该没问题,因为C和Python函数中的函数可以很容易地被翻译。

OP的方法实际上是正确的,所以我会尽力填补空白。由于静态选项的数量有限,可能不会改变太多,使用gtk_combo_box_text_append添加它们确实有意义,这会将它们添加到GtkComboBoxText的内部模型中。

这就是静态部分,对于动态部分来说,如果我们可以只存储这个静态模型并在开始时找到gtk_combo_box_set_model()时使用_将其替换为临时模型,那将是完美的。的字符串。但是我们不应该这样做,因为文档说:

  

您不应该调用gtk_combo_box_set_model()或尝试通过其GtkCellLayout接口将更多单元格打包到此组合框中。

所以我们需要解决这个问题,一种方法是在GtkEntryCompletion的条目中添加GtkComboBoxText。这将使条目尝试根据其当前模型完成当前字符串。作为额外的奖励,它还可以添加所有选项共有的所有角色,如下所示:

enter image description here

由于我们不想事先加载所有动态选项,我认为最好的方法是将changed侦听器连接到GtkEntry,这样我们就可以加载动态选项了我们有一个下划线和一些字符。

由于GtkEntryCompletion在内部使用GtkListStore,我们可以重用其答案中提供的部分代码Nominal Animal 。主要区别在于:connectGtkEntry上完成,GtkComboText替换为GtkEntryCompletion在populator中。那么一切都应该没问题,我希望我能写出不错的C然后我会为你提供代码,但这必须要做。

编辑:使用GTK3进行Python的小型演示

import gi

gi.require_version('Gtk', '3.0')

import gi.repository.Gtk as Gtk

class CompletingComboBoxText(Gtk.ComboBoxText):
    def __init__(self, static_options, populator, **kwargs):
        # Set up the ComboBox with the Entry
        Gtk.ComboBoxText.__init__(self, has_entry=True, **kwargs)

        # Store the populator reference in the object
        self.populator = populator

        # Create the completion
        completion = Gtk.EntryCompletion(inline_completion=True)

        # Specify that we want to use the first col of the model for completion
        completion.set_text_column(0)
        completion.set_minimum_key_length(2)

        # Set the completion model to the combobox model such that we can also autocomplete these options
        self.static_options_model = self.get_model()
        completion.set_model(self.static_options_model)

        # The child of the combobox is the entry if 'has_entry' was set to True
        entry = self.get_child()
        entry.set_completion(completion)

        # Set the active option of the combobox to 0 (which is an empty field)
        self.set_active(0)

        # Fill the model with the static options (could also be used for a history or something)
        for option in static_options:
            self.append_text(option)

        # Connect a listener to adjust the model when the user types something
        entry.connect("changed", self.update_completion, True)


    def update_completion(self, entry, editable):
        # Get the current content of the entry
        text = entry.get_text()

        # Get the completion which needs to be updated
        completion = entry.get_completion()

        if text.startswith("_") and len(text) >= completion.get_minimum_key_length():
            # Fetch the options from the populator for a given text
            completion_options = self.populator(text)

            # Create a temporary model for the completion and fill it
            dynamic_model = Gtk.ListStore.new([str])
            for completion_option in completion_options:
                dynamic_model.append([completion_option])
            completion.set_model(dynamic_model)
        else:
            # Restore the default static options
            completion.set_model(self.static_options_model)


def demo():
    # Create the window
    window = Gtk.Window()

    # Add some static options
    fake_static_options = [
        "comment",
        "if",
        "the_GUI",
        "the_system",
        "payload_json",
        "x1",
        "payload_json",
        "payload_vectval"
    ]

    # Add the the Combobox
    ccb = CompletingComboBoxText(fake_static_options, dynamic_option_populator)
    window.add(ccb)

    # Show it
    window.show_all()
    Gtk.main()


def dynamic_option_populator(text):
    # Some fake returns for the populator
    fake_dynamic_options = [
        "_5Hf0fFKvRVa71ZPM0",
        "_8261sbF1f9ohzu2Iu",
        "_0BV96V94PJIn9si1K",
        "_0BV1sbF1f9ohzu2Iu",
        "_0BV0fFKvRVa71ZPM0",
        "_0Hf0fF4PJIn9si1Ks",
        "_6KvRVa71JIn9si1Kw",
        "_5HKvRVa71Va71ZPM0",
        "_8261sbF1KvRVa71ZP",
        "_0BKvRVa71JIn9si1K",
        "_0BV1KvRVa71ZPu2Iu",
        "_0BV0fKvRVa71ZZPM0",
        "_0Hf0fF4PJIbF1f9oh",
        "_61sbFV0fFKn9si1Kw",
        "_5Hf0fFKvRVa71ozu2",
    ]

    # Only return those that start with the text
    return [fake_dynamic_option for fake_dynamic_option in fake_dynamic_options if fake_dynamic_option.startswith(text)]


if __name__ == '__main__':
    demo()
    Gtk.main()

答案 1 :(得分:4)

这是我的建议:

使用GtkListStore包含与当前前缀字符串匹配的GTK管理字符串列表(实质上是标识符字符串的副本)。

(如gtk_list_store_set()所述,复制了一个G_TYPE_STRING项目。我认为这里可以接受额外副本的开销;不管怎么说,它不应该影响真实世界的表现,我认为,返回,GTK +将为我们管理引用计数。)

以上是在GTK +回调函数中实现的,该函数获取额外的指针作为有效负载(在创建或激活GUI时设置;我建议您使用一些结构来保留生成匹配所需的引用)。回调连接到组合框popup信号,以便在扩展列表时调用它。

请注意,正如B8vrede在评论中指出的那样,不应通过其模型修改GtkComboBoxText;这就是为什么人们应该/必须使用GtkComboBox代替。

实际示例

为简单起见,我们假设您需要查找或生成匹配的所有已知标识符所需的所有数据都保存在结构中,例如

struct generator {
    /* Whatever data you need to generate prefix matches */
};

然后组合框populator辅助函数就像

static void combo_box_populator(GtkComboBox *combobox, gpointer genptr)
{
    struct generator *const generator = genptr;

    GtkListStore *combo_list = GTK_LIST_STORE(gtk_combo_box_get_model(combobox));

    GtkWidget *entry = gtk_bin_get_child(GTK_BIN(combobox));
    const char *prefix = gtk_entry_get_text(GTK_ENTRY(entry));
    const size_t prefix_len = (prefix) ? strlen(prefix) : 0;

    GtkTreeIter iterator;

    /* Clear the current store */
    gtk_list_store_clear(combo_list);

    /* Initialize the list iterator */
    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(combo_list), &iterator);

    /* Find all you want to have in the combo box;
       for each  const char *match, do:
    */

        gtk_list_store_append(combo_list, &iterator);
        gtk_list_store_set(combo_list, &iterator, 0, match, -1);

    /* Note that the string pointed to by match is copied;
       match is not referred to after the _set() returns.
    */
}

构建或激活UI时,您需要确保GtkComboBox有一个条目(以便用户可以在其中写入文本)和GtkListStore模型:

    struct generator *generator;
    GtkWidget *combobox;
    GtkListStore *combo_list;

    combo_list = gtk_list_store_new(1, G_TYPE_STRING);
    combobox = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(combo_list));
    gtk_combo_box_set_id_column(GTK_COMBO_BOX(combobox), 0);
    gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combobox), 0);
    gtk_combo_box_set_button_sensitivity(GTK_COMBO_BOX(combobox), GTK_SENSITIVITY_ON);

    g_signal_connect(combobox, "popup", G_CALLBACK(combo_box_populator), generator);

在我的系统上,默认弹出加速器是 Alt + Down ,但我认为你已经将其更改为 Tab

我有一个粗略的工作示例here.tar.xz tarball,CC0):它从标准输入读取行,并在相反的顺序中列出与用户前缀匹配的行组合框列表(弹出时)。如果条目为空,则组合框将包含所有输入行。我没有更改默认加速器,因此请尝试 Alt + Down 而不是 Tab

我也有相同的示例,但使用GtkComboBoxText代替here(也CC0)。这不使用GtkListStore模型,而是使用gtk_combo_box_text_remove_all()gtk_combo_box_text_append_text()函数直接操作列表内容。 (两个示例中只有几行不同。)不幸的是,文档不明确该接口是引用还是复制字符串。虽然复制是唯一有意义的选择,并且可以从当前的Gtk +源验证,但缺乏明确的文档让我犹豫不决。

比较我上面链接的两个例子(如果您使用/usr/share/dict/words编译并运行它,则从make获取大约500个随机单词),我看不到任何速度差异。两者都使用相同的天真方式从链表中挑选前缀匹配,这意味着两种方法(GtkComboBox + model或GtkComboBoxText)应该同样快。

在我自己的机器上,弹出窗口中超过1000个左右的匹配变得非常慢;只有一百个或更少的比赛,它感觉瞬间。对我来说,这表明从链接列表中选择前缀匹配的缓慢/天真的方式不是罪魁祸首(因为在两种情况下遍历整个列表),但是GTK +组合框不是为大型列表设计的。 (减速肯定比线性更糟,更糟糕。)