最近用到GtkSearchEntry的编程,其中一个信号是stop-search,对应绑定的默认快捷键是ESC,一般捕获为退出信号,感觉不顺手,想绑定到Ctrl-C,中途遇到了问题,查看全网没有发现解决方案,但是从StackOverFlow的这篇文章里找到一点灵感 Gtk3 keys bindings in css files。特此记录此问题的解决过程和思路。
查看Gtk开发手册,如下:
The “stop-search” signal
1 2 3 4 5 |
void user_function (GtkSearchEntry *entry, gpointer user_data) |
The ::stop-search signal is a keybinding signal which gets emitted when the user stops a search via keyboard input.
Applications should connect to it, to implement hiding the search entry in this case.
The default bindings for this signal is Escape.
Parameters
entry | the entry on which the signal was emitted | |
user_data | user data set when the signal handler was connected. |
Flags: Action
Since: 3.16
通过描述可以看到stop-search是键盘绑定信号,用户输入特定键盘触发后发射出该信号。
点进 keybinding signal ,找到文章开头的一些函数:
void | gtk_binding_entry_add_signall () |
GtkBindingSet * | gtk_binding_set_new () |
GtkBindingSet * | gtk_binding_set_by_class () |
GtkBindingSet * | gtk_binding_set_find () |
gboolean | gtk_bindings_activate () |
gboolean | gtk_bindings_activate_event () |
gboolean | gtk_binding_set_activate () |
void | gtk_binding_entry_add_signal () |
GTokenType | gtk_binding_entry_add_signal_from_string () |
void | gtk_binding_entry_skip () |
void | gtk_binding_entry_remove () |
void | gtk_binding_set_add_path () |
其中看着比较像的是gtk_binding_entry_add_signal (),点进去看函数原型:
gtk_binding_entry_add_signal ()
1 2 3 4 5 6 7 8 9 |
void gtk_binding_entry_add_signal (GtkBindingSet *binding_set, guint keyval, GdkModifierType modifiers, const gchar *signal_name, guint n_args, ...); |
Override or install a new key binding for keyval with modifiers on binding_set . When the binding is activated, signal_name will be emitted on the target widget, with n_args Varargs used as arguments.
Each argument to the signal must be passed as a pair of varargs: the GType of the argument, followed by the argument value (which must be of the given type). There must be n_args pairs in total.
keyval(键值),modifiers(修饰符),和signal_name(信号名)容易知道,比如我想要绑定到Ctrl-C,那么keyval就是c,modifiers就是control,signal-name就是stop-search,但是写法可能要替换成一些特定的宏,这些问题不大,从gtk的源码中找到规律,keyval一般写成 GDK_KEY_c 这种格式,Control写成 GDK_CONTROL_MASK , signal-name还是原来的字符串不用变。
问题最大的是GtkBindingSet如何获取,它的原型是一个结构体,里面包含很多变量,我们不可能自己定义一个,因为这在Gtk源码中肯定已经定义过了,目的到现在变成了如何获取这个GtkBindingSet。
再细看上面的一些函数,其中有3个返回值都是GtkBindingSet,大概率是从这里入手,通过名称可以排除包含new的函数,因为是获取已有的,而不是new一个新的,剩下两个通过对比确定为如下目标函数:
gtk_binding_set_by_class ()
1 2 3 4 |
GtkBindingSet * gtk_binding_set_by_class (gpointer object_class); |
This function returns the binding set named after the type name of the passed in class structure. New binding sets are created on demand by this function.
Parameters
object_class | a valid GObject class |
Returns
the binding set corresponding to object_class .
至于为什么选这个,首先从它的内部传参可以看出,只需要传进一个有效的GObject class 即可。
因此,问题再进一步,变成:如何获取GObject class
到这里,感觉离目标已经越来越接近,但是对于Gtk Class了解不够,且不太懂得该如何获取,其内部机制也不了解。
一个很关键的思路转折出现了,既然GtkSearchEntry是继承自GtkEntry,那么关键突破点不应该只在GtkSearchEntry中找,很有可能在其继承的基类中存在一些我们要的结果。
而类名一般与部件widget名相关,即,可能对我们有帮助的类名是GtkSearchEntryClass或者是GtkEntryClass,接着我在Gtk源码根目录输入如下命令:
1 2 3 |
grep GtkEntryClass -r |
从输出结果中看到这么一行
1 2 3 |
gtkentry.c: GtkEntryClass *class = GTK_ENTRY_GET_CLASS (entry); |
用vim打开这个文件:找到上面的内容,如下:
123456789101112131415161718192021222324252627 static G_GNUC_UNUSED voidget_frame_size (GtkEntry *entry,gboolean relative_to_window,gint *x,gint *y,gint *width,gint *height){GtkEntryClass *class = GTK_ENTRY_GET_CLASS (entry); //关键语句,看函数参数发现entry是GtkEntry类型变量g_assert (class->get_frame_size != NULL);class->get_frame_size (entry, x, y, width, height);if (!relative_to_window){GtkAllocation allocation;gtk_widget_get_allocation (GTK_WIDGET (entry), &allocation);if (x)*x -= allocation.x;if (y)*y -= allocation.y;}}
发现使用 GTK_ENTRY_GET_CLASS()宏即可返回GtkEntry的类,而且由GtkSearchEntry继承自GtkEntry的关系中可以推断,这个宏对GtkSearchEntry应该同样起作用,由此又更进一步,得到了GtkEntryClass,最后一步只要获取到GObjectClass就真正搞定了。
接下来又在gtksearchentry.c中找到以下语句:
1 2 3 |
GObjectClass *object_class = G_OBJECT_CLASS (klass); |
同样是采用宏定义的方式将某个Class转换成了原始的GObjectClass,到此追根溯源就此结束,全部未知变量我们都已经拿到,可以进行快捷键注册了。
直接上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#include <gtk/gtk.h> void search_change ( GtkSearchEntry *entry, gpointer data ) { const char *buf; buf = gtk_entry_get_text ( (GtkEntry*)entry ); printf("%s\n", buf); } gboolean stop_search ( GtkSearchEntry *entry, gpointer data ) { gtk_main_quit(); } int main(int argc, char **argv) { GtkWidget *window; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); GtkWidget *search = gtk_search_entry_new (); gtk_window_set_default_size(GTK_WINDOW(window), 300, 100); gtk_window_set_keep_above ( GTK_WINDOW(window), TRUE ); gtk_window_set_title(GTK_WINDOW(window), ""); gtk_window_set_position (GTK_WINDOW(window), (GTK_WIN_POS_CENTER_ALWAYS )); gtk_window_set_decorated ( GTK_WINDOW(window), FALSE ); /* 绑定退出键为Ctrl-C*/ GtkEntryClass *klass = GTK_ENTRY_GET_CLASS ( (GtkEntry*)search ); GObjectClass *class = G_OBJECT_CLASS ( klass ); GtkBindingSet *bset = gtk_binding_set_by_class ( class ); gtk_binding_entry_add_signal ( bset, GDK_KEY_c, GDK_CONTROL_MASK, "stop-search", 0 ); gtk_container_add ( GTK_CONTAINER(window), search ); g_signal_connect(G_OBJECT(window), "destroy", \ G_CALLBACK(gtk_main_quit),NULL); g_signal_connect ( G_OBJECT( search ), "search-changed",\ G_CALLBACK( search_change ), NULL); g_signal_connect ( G_OBJECT( search ), "stop-search",\ G_CALLBACK( stop_search ), NULL); gtk_widget_show_all(window); gtk_main(); return 0; } |
命名searchWin.c
编译:
1 2 3 |
gcc `pkg-config --cflags --libs gtk+-3.0` searchWin.c -o searchWin |
运行:
1 2 3 |
./searchWin |
程序现象:
弹出一个搜索框,输入内容会实时被打印到终端,按Ctrl-C退出界面。
拓展:
完成后突然想到都是Class,GObjectClass应该可以从其他Class直接强转过来,试了后发现果然,直接将 GObjectClass *class = (GObjectClass*)( klass ) 代替 GObjectClass *class = G_OBJECT_CLASS ( klass );效果是一样的,宏定义完成的方式猜测也包含强制转换。
结语:
要善于利用互联网的力量,但也要培养起自己独立解决问题的能力和思路,方能解前人所未解之难题,在编程的路上越走越远,成为一名创造者,而非搬砖者。