自定义GtkComboBoxText的完成

Bas*_*tch 11 c linux gtk gtk3

如何GtkComboBoxText使用"静态"方面和"动态"方面自定义a的完成?静态方面是因为某些条目是已知的并且在构造时添加到组合框文本中gtk_combo_box_text_append_text.动态方面,因为我还需要完成将向您介绍一些回调函数(S),即完成动态的的-after创建GtkComboBoxText小窗口一次几个大字已经被输入.

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

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

问题细节

我正在输入(输入GtkComboBoxText)名称对象ID.

  • 名称与C-identifier类似,但以字母开头,不能以下划线结尾.例如comment,if,the_GUI,the_system,payload_json,或者x1是有效的名称(但_a0bcd还是foobar_无效的名字,因为他们开始或下划线结束).我目前有十几个名字,但我可以有几千个.因此,这将是合理的报价完成一次只有一个或者是两个字母已经输入,并完成了,因为他们不是很多人的名字可以静态地发生(所以我觉得合理调用gtk_combo_box_append_text每个名称).

  • 对象的ID以下划线后跟数字开始,并具有正好18个字母数字(排序的随机的)字符.例如,_5Hf0fFKvRVa71ZPM0,_8261sbF1f9ohzu2Iu,_0BV96V94PJIn9si1K是可能的对象的id.实际上它是96个几乎随机的位(可能只有2 94个可能).object-id扮演UUID的角色(在某种意义上,假设它对于不同的对象是全世界唯一的)但具有C友好语法.我目前有几十个物体,但我可能有几十万(或者一百万).但考虑到四个大字像一个前缀_6S3或者_22z,我假设只有一个合理的数字(可能顶多十几好,而且肯定不超过一千)对象ID在我与前缀应用存在.当然,这是不合理的登记(静态)先验的所有对象ID(完成具有四个大字已经被输入后发生的,应该动态地发生).

所以我希望完成一个适用于名称的完成(例如,键入一个字母或许后面跟另一个字母字符应该足以提出最多一百个选项的完成),以及对象-id(键入四个字符_826就足够了触发完成可能最多几十个选择,如果运气不好可能是一千个).

因此键入三个按键p a tab将提供完成与像几个名字payload_jsonpayload_vectval等等,然后键入五个按键_ 5 H f tab就完成提供非常少的对象ID,特别是_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 ();
Run Code Online (Sandbox Code Playgroud)

我有Boehm垃圾收集的值,并且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]));
Run Code Online (Sandbox Code Playgroud)

在这一点上,我已经对所有(最多几千个)名称进行了排序,并使用"静态"添加它们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");
Run Code Online (Sandbox Code Playgroud)

我注意到有点意外gtk_entry_get_completion (GTK_ENTRY (combtextent))是空的.

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

  1. 让一些mom_set_complete_objectid(const char*prefix)给出至少四个字符的prefix类似"_47n"将返回收集的垃圾,mo_value_t表示具有该前缀的对象集.这对我来说很容易编码,而且差不多完成了.

  2. 制作我自己的本地GtkEntryCompletion* mycompl =......,这将完成我想要的.然后我会把它在文本输入combtextent我的GTK的组合框,文本使用gtk_entry_set_completion(GTK_ENTRY(combtextent), mycompl);

是否应该使用gtk_combo_box_text_append_text为"静态"名称完成角色添加的条目?我应该如何使用从我返回的动态设置值动态完成mom_set_complete_objectid; 给出了一些对象的指针obr和一些char bufid[20];我很容易和快速地能够与该对象的对象ID来填补它obrmo_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 */
Run Code Online (Sandbox Code Playgroud)

我的方法是正确的吗?

mom_objectentry上面的函数用于填充具有短寿命的模态对话框.

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

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

上下文

我的应用程序是一个实验性持久性编程语言和实现,具有近似Scheme或Python(或JavaScript,忽略原型方面,...)语义但具有广泛不同(尚未在2016 年9月7 实现)语法(将是在GTK小部件中显示和输入),使用Boehm垃圾收集器获取值(包括对象,集合,元组,字符串......)......值(包括对象)通常是持久的(除了GTK相关数据:应用程序以一个几乎空的窗口)._momstate.sql在应用程序启动时重新加载的一些Sqlite"数据库"(在应用程序出口处生成)中,整个语言堆以类似JSON的语法保存.Object-ids对于在GTK小部件中显示对用户的对象引用,持久性以及生成与对象相关的C代码非常有用(例如,id的对象_76f7e2VcL8IJC1hq6可能与mo_76f7e2VcL8IJC1hq6某些生成的C代码中的标识符相关;这是部分原因我有我的object-id格式而不是使用UUID).

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

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

B8v*_*ede 5

我不会显示确切的代码,因为我从来没有做过GTK和C,而是只做过GTK和Python,但是应该可以,因为C和Python函数中的函数可以轻松转换。

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

多数民众赞成涵盖静态部分,对于动态部分,如果我们可以只存储此静态模型并使用gtk_combo_box_set_model()_字符串开头找到a的temporay模型替换它,那将是完美的。但是我们不应该这样做,因为文档说:

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

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

在此处输入图片说明

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

由于内部GtkEntryCompletion使用GtkListStore,我们可以重复使用他的answer中提供的Nominal Animal代码。主要区别在于:在connect被上完成GtkEntry和替换GtkComboTextGtkEntryCompletion的填充器内。然后一切都应该没问题,我希望我能够写出体面的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()
Run Code Online (Sandbox Code Playgroud)


Nom*_*mal 4

这是我的建议:

\n\n

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

\n\n

(正如文档中所记录的gtk_list_store_set(),复制了一个G_TYPE_STRING项目。我认为这里额外复制的开销是可以接受的;我认为它不会对现实世界的性能产生太大影响,作为回报,GTK+将为我们管理引用计数。)

\n\n

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

\n\n

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

\n\n

实际例子

\n\n

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

\n\n
struct generator {\n    /* Whatever data you need to generate prefix matches */\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

组合框填充器辅助函数类似于

\n\n
static void combo_box_populator(GtkComboBox *combobox, gpointer genptr)\n{\n    struct generator *const generator = genptr;\n\n    GtkListStore *combo_list = GTK_LIST_STORE(gtk_combo_box_get_model(combobox));\n\n    GtkWidget *entry = gtk_bin_get_child(GTK_BIN(combobox));\n    const char *prefix = gtk_entry_get_text(GTK_ENTRY(entry));\n    const size_t prefix_len = (prefix) ? strlen(prefix) : 0;\n\n    GtkTreeIter iterator;\n\n    /* Clear the current store */\n    gtk_list_store_clear(combo_list);\n\n    /* Initialize the list iterator */\n    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(combo_list), &iterator);\n\n    /* Find all you want to have in the combo box;\n       for each  const char *match, do:\n    */\n\n        gtk_list_store_append(combo_list, &iterator);\n        gtk_list_store_set(combo_list, &iterator, 0, match, -1);\n\n    /* Note that the string pointed to by match is copied;\n       match is not referred to after the _set() returns.\n    */\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
    struct generator *generator;\n    GtkWidget *combobox;\n    GtkListStore *combo_list;\n\n    combo_list = gtk_list_store_new(1, G_TYPE_STRING);\n    combobox = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(combo_list));\n    gtk_combo_box_set_id_column(GTK_COMBO_BOX(combobox), 0);\n    gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combobox), 0);\n    gtk_combo_box_set_button_sensitivity(GTK_COMBO_BOX(combobox), GTK_SENSITIVITY_ON);\n\n    g_signal_connect(combobox, "popup", G_CALLBACK(combo_box_populator), generator);\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n\n

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

\n\n

比较我上面链接的两个示例(/usr/share/dict/words如果您使用 编译并运行它,它们都会抓取一些 500 个随机单词make),我没有看到任何速度差异。两者都使用相同的 na\xc3\xafve 方式从链接列表中选取前缀匹配,这意味着这两种方法(GtkComboBox+ model 或GtkComboBoxText)应该大约同样快。

\n\n

在我自己的机器上,弹出窗口中的匹配项超过 1000 个时,两者都会变得非常慢;只要有一百个或更少的匹配,感觉就是瞬间的。对我来说,这表明从链接列表中选取前缀匹配的慢/na\xc3\xafve 方式不是罪魁祸首(因为在两种情况下都会遍历整个列表),但 GTK+ 组合框并未设计对于大型列表。(这种减速肯定比线性减速要严重得多。)

\n