一尘不染

自定义GtkComboBoxText的完成

linux

如何通过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)

问题细节

我输入的GtkComboBoxTextnameobject-id (输入)。

  • 名称 是C-标识符状,但以字母开头,并且不能以下划线结束。例如commentifthe_GUIthe_systempayload_json,或者x1是有效的名称(但 _a0bcd 还是 foobar_ 无效的名字,因为他们开始或以下划线结尾)。我目前有很多名字,但我可以有数千个。因此,只有键入了一个或两个字母后才提供补全是合理的,并且名称的补全可以静态发生,因为它们不多(因此,我想gtk_combo_box_append_text为每个名称打电话都很合理)。

  • 对象的ID 以下划线开始后跟数字和具有正好18个字母数字(排序的随机的)字符。例如,_5Hf0fFKvRVa71ZPM0_8261sbF1f9ohzu2Iu_0BV96V94PJIn9si1K是可能的对象的id。实际上,它几乎是96个随机位(可能只有2 94个)。object-id扮演UUID的角色(在某种意义上,假定它对不同的对象在全球范围内是唯一的),但是具有C友好的语法。我目前有几十个对象ID,但我可以有几十万个(也许一百万个)。但是给定四个字符的前缀,例如_6S3_22z,我假设只有一个合理的数字(可能最多十二个,并且肯定不超过一千个) object-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 ();

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

至此,我已经对所有(最多几千个)名称进行了排序,并使用来“静态”添加它们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"_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];我可以轻松,快速地obrmo_cstring_from_hi_lo_ids(bufid, obr->mo_ob_hid, obr->mo_ob_loid).. 填充该对象的对象ID 。

我不知道如何编写以上代码。作为参考,我现在只返回combo-box-text:

  // 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代码!),实际上,我可能只有数百个名称,最多只有几十万个对象ID。因此性能并不是很重要,但是简化编码(从概念上讲是“丢弃”代码)更为重要。

我不想(如果可能)添加自己的GTK类。我更喜欢使用现有的GTK类和小部件,并通过GTK信号和回调对其进行自定义。

语境

我的应用程序是一种实验
性的持久

编程语言,其实现方式具有接近Scheme或Python(或JavaScript,忽略了原型方面,…)的 语义,语法
却相差很大(尚未于2016 年9月7
日实现)。显示并在GTK小部件中输入),使用Boehm垃圾收集器收集值(包括对象,集合,元组,字符串…)…值(包括对象)通常是持久性的(与GTK相关的数据除外:应用程序以几乎是空的窗口)。整个语言堆以类似JSON的语法保存在某些Sqlite“数据库”(在应用程序出口中生成)中,转储到_momstate.sql在应用程序启动时重新加载。对象ID可用于在GTK小部件中向用户显示对象引用,以实现持久性,并生成与对象相关的C代码(例如,ID的对象_76f7e2VcL8IJC1hq6可能与mo_76f7e2VcL8IJC1hq6某些生成的C代码中的标识符相关;这部分是为什么我有我的对象ID格式,而不是使用UUID。

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

注意:这里提到的对象暗含我的语言对象,而不是GTK对象。全部都有一个唯一的对象ID,其中一些(但不是大多数)被命名。


阅读 471

收藏
2020-06-07

共1个答案

一尘不染

这是我的建议:

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

(如记录所示gtk_list_store_set(),一个G_TYPE_STRING项目已被复制。我认为额外副本的开销在这里是可以接受的;无论如何,我认为它不会对现实世界的性能产生太大影响,因此,GTK
+将为我们管理引用计数。)

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

请注意,正如B8vrede在评论中指出的那样,GtkComboBoxText不应通过其模型修改a
;这就是为什么一个人应该/必须使用一个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.xztarball,CC0):它从标准输入中读取行,并在组合框列表中(当弹出时)以相反的顺序列出与用户前缀匹配的行。如果该项为空,则组合框将包含所有输入行。我没有更改默认的加速器,因此Tab,请尝试Alt+Down

我也有相同的示例,但这里使用了GtkComboBoxText替代示例(也是CC0)。这不使用模型,而是使用和函数直接操作列表内容。(两个示例中只有几行不同。)不幸的是,文档并未明确说明此接口是引用还是复制字符串。尽管复制是唯一有意义的选项,并且可以从当前的Gtk
+来源进行验证,但是缺少明确的文档使我犹豫不决。GtkListStoregtk_combo_box_text_remove_all()gtk_combo_box_text_append_text()

比较上面链接到的两个示例(/usr/share/dict/words如果使用进行编译和运行,都可以抓取500个随机单词make),但看不到任何速度差异。两者都使用相同的简单方法从链接列表中选择前缀匹配,这意味着这两种方法(GtkComboBox+模型或GtkComboBoxText)应大致相同。

在我自己的计算机上,两者都令人讨厌地变慢,弹出窗口中的匹配项超过1000个左右。只需进行一百场或更少的比赛,就可以立即感受到。对我来说,这表明从链接列表中选择前缀匹配项的缓慢/幼稚方式不是罪魁祸首(因为在两种情况下都会遍历整个列表),但是GTK+组合框并不是专门为大型列表而设计的。(速度下降肯定比线性下降严重得多。)

2020-06-07