自定义GtkComboBoxText的完成

我怎样才能自定义一个“静态”方面和“dynamic”一个GtkComboBoxText的完成? 静态方面是因为一些条目是已知的,并在构build时使用gtk_combo_box_text_append_text添加到combobox文本中。 dynamic的方面是因为我还需要完成一些callback函数,即在创buildGtkComboBoxText小部件之后dynamic地完成 – 一旦input了几个字符。

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

(我不在乎非Linux系统,GTK3.20以前的GTK3库,GCC6以前的GCC编译器)

问题的细节

我正在input(input到GtkComboBoxText名称对象ID

  • 名称类似C标识符,但以字母开头,不能以下划线结尾。 例如, commentifthe_GUIthe_systempayload_jsonx1是有效的名称(但_a0bcdfoobar_是无效名称,因为它们以下划线开头或结尾)。 我目前有十几个名字,但是我可以有几千个名字。 因此,一次只能input一个或者两个字母就可以完成一个完成,而名称的完成可以静态地发生,因为它们不是很多(所以我觉得为每个名称调用gtk_combo_box_append_text是合理的)。

  • object-id以下划线开头,后面跟着一个数字,正好有18个字母数字(随机sorting)字符。 例如, _5Hf0fFKvRVa71ZPM0_8261sbF1f9ohzu2Iu_0BV96V94PJIn9si1K是可能的object-id。 实际上它几乎是96个随机位(可能只有2 94个 )。 object-id扮演着UUID的angular色(从某种意义上说,它被认为是全球唯一的不同对象),但是具有C友好的语法。 我目前有几十个对象 – ID,但是我可以有几十万(或者可能是一百万)。 但是给定一个像_6S3_22z这样的四个字符的前缀,我假设我的应用程序中只有一个合理的数字(可能至多有一打,肯定不超过一千个) object-id存在于这个前缀中。 当然,将(静态) 先验注册所有的对象ID(在四个字符input之后完成必须发生,并且应该dynamic地发生)是不合理的。

所以我想要一个完整的名称(例如,input一个字母也许跟着另一个字母字符应该足以build议完成至多一百个select),并在对象ids(键入四个字符,如_826应该是足够的触发完成可能最多有几十个select,如果不幸的话可能是一千个)。

因此,键入一个 标签的三个键将提供一些名称,如payload_jsonpayload_vectval等完成…键入五个键_5 H f 选项卡将提供完成很less的对象ids,特别是_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 (); 

我有Boehm垃圾收集的值,而mo_value_t是指向其中任何一个的指针。 值可以被标记为整数,指向string,对象或元组或对象集的指针。 所以namesetv现在包含一组命名对象(可能less于几千个命名对象)。

  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])); 

在这一点上,我已经sorting了所有(最多几千个)名称,并使用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))为空。

但是我被困在这里 我在想:

  1. 有一些mom_set_complete_objectid(const char*prefix)给出的prefix至less为四个字符的"_47n"将返回一个垃圾回收的mo_value_t代表具有该前缀的对象集合。 这对我来说是非常容易的,几乎已经完成了。

  2. 使我自己的本地GtkEntryCompletion* mycompl = …,这将完成我想要的。 然后,我会使用gtk_entry_set_completion(GTK_ENTRY(combtextent), mycompl);将其放在我的gtk-combo-box-text的文本项combtextentgtk_entry_set_completion(GTK_ENTRY(combtextent), mycompl);

是否应该使用gtk_combo_box_text_append_text添加的条目作为“静态”名称完成angular色? 我应该如何dynamic完成使用从我的mom_set_complete_objectid返回的dynamic设置值; 给出了一些对象指针obr和一些char bufid[20]; 我很容易,很快就能用mo_cstring_from_hi_lo_ids(bufid, obr->mo_ob_hid, obr->mo_ob_loid)来填充该对象的对象标识符。

我不知道如何编码以上。 作为参考,我现在只是返回combobox文本:

  // 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 */ 

我的方法是正确的吗?

上面的mom_objectentry函数用于填充使用寿命较短的模式对话框。

我赞成简单的代码而不是效率。 实际上,我的代码是暂时的(我希望引导我的语言,并生成所有的C代码!),实际上我可能只有几百个名字,最多只有几十个object-id。 所以性能不是很重要,但编码的简单性(一些概念上的“丢弃”代码)更重要。

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

上下文

我的应用程序是一个实验性的持久性编程语言,实现了一个接近Scheme或者Python(或者JavaScript,忽略了原型方面…)的语义,但是有一个很大的不同(在2016年9月7 还没有实现)语法(包括对象,集合,元组,string…)…值(包括对象)通常是持久的(GTK相关数据除外):应用程序以一个几乎空的窗口)。 整个语言堆在转储到应用程序启动时重新加载的_momstate.sql中的一些Sqlite“数据库”(在应用程序出口处生成)中以类JSON语法持久化。 Object-id对GTK小部件中的用户显示对象引用,持久性以及生成与对象相关的C代码很有用(例如, _76f7e2VcL8IJC1hq6的对象可能与某些生成的C代码中的mo_76f7e2VcL8IJC1hq6标识符有关;这是部分为什么我有我的对象id格式,而不是使用UUID)。

PS。 我的C代码是GPLv3免费软件,可在github上获得。 这是MELT监视器,分支expjs ,提交e2b3b99ef66394 …

注意:这里提到的对象隐含的是我的语言对象,而不是GTK对象。 所有人都有一个独特的对象ID,有些但不是大多数被命名。

Solutions Collecting From Web of "自定义GtkComboBoxText的完成"

这是我的建议:

使用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 */ }; 

然后组合框助手功能是类似的

 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

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

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

比较上面链接的两个例子(如果你用make编译和运行的话,都会从/usr/share/dict/words获取大约500个随机单词),但是我没有看到速度上的差异。 两者都使用相同的天真方式从链表中选取前缀匹配,这意味着两种方法( GtkComboBox + model或GtkComboBoxText )应该大致相同。

在我自己的机器上,在弹出窗口中都会有1000多个左右的比赛, 只有一百个或更少的比赛,感觉是瞬间的。 这对我来说,表明从链表中选择前缀匹配的慢/天真的方式并不是罪魁祸首(因为整个列表在两种情况下都被遍历),但是GTK +组合框并不是为大的列表而设计的。 (这个速度肯定比线性差很多。)

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

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

这就包含了静态部分,对于动态部分来说,如果我们能够存储这个静态模型,并且在字符串开头找到一个_ ,那么使用gtk_combo_box_set_model()替换它就是一个完美的选择。 但是我们不应该这样做,因为文件说:

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

所以我们需要解决这个问题,这样做的一个方法是在GtkComboBoxText的入口处添加一个GtkEntryCompletion 。 这将使输入尝试根据当前模型完成当前字符串。 作为一个额外的好处,它也可以添加所有的选项都有这样的共同点:

在这里输入图像说明

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

GtkEntryCompletion在内部使用GtkListStore ,我们可以重用他的答案中提供的部分Nominal Animal代码 。 主要区别在于: connect是在GtkEntry上完成的,并且在populator中用GtkEntryCompletion代替了GtkComboText 。 那么一切都应该没问题,我希望我能写出体面的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()