19、GTK编程:绘图程序与信号处理全解析

GTK编程:绘图程序与信号处理全解析

1. 绘图程序Scribble
1.1 使用扩展设备信息

在启用设备后,我们可以使用事件结构额外字段中的扩展设备信息。即便未启用扩展事件,这些字段也会有合理的默认值,因此使用这些信息是安全的。

需要注意的是,我们要调用 gdk_input_window_get_pointer() 而非 gdk_window_get_pointer() ,因为后者不会返回扩展设备信息。 gdk_input_window_get_pointer() 函数的原型如下:

void gdk_input_window_get_pointer( GdkWindow *window,
                                   guint32 deviceid,
                                   gdouble *x,
                                   gdouble *y,
                                   gdouble *pressure,
                                   gdouble *xtilt,
                                   gdouble *ytilt,
                                   GdkModifierType *mask);

调用此函数时,我们需要同时指定设备ID和窗口。通常,设备ID可从事件结构的 deviceid 字段获取。即使未启用扩展事件,该函数也会返回合理的值(此时 event->deviceid 的值为 GDK_CORE_POINTER )。

以下是按钮按下和移动事件处理函数的代码示例:

static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
    print_button_press (event->deviceid);
    if (event->button == 1 && pixmap != NULL)
        draw_brush (widget, event->source, event->x, event->y, event->pressure);
    return TRUE;
}

static gint
motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
{
    gdouble x, y;
    gdouble pressure;
    GdkModifierType state;
    if (event->is_hint)
        gdk_input_window_get_pointer (event->window, event->deviceid,
                                      &x, &y, &pressure, NULL, NULL, &state);
    else
    {
        x = event->x;
        y = event->y;
        pressure = event->pressure;
        state = event->state;
    }
    if (state & GDK_BUTTON1_MASK && pixmap != NULL)
        draw_brush (widget, event->source, x, y, pressure);
    return TRUE;
}

新的 draw_brush() 函数会根据 event->source 使用不同的颜色进行绘制,并根据压力改变画笔大小:

static void
draw_brush (GtkWidget *widget, GdkInputSource source,
            gdouble x, gdouble y, gdouble pressure)
{
    GdkGC *gc;
    GdkRectangle update_rect;
    switch (source)
    {
        case GDK_SOURCE_MOUSE:
            gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)];
            break;
        case GDK_SOURCE_PEN:
            gc = widget->style->black_gc;
            break;
        case GDK_SOURCE_ERASER:
            gc = widget->style->white_gc;
            break;
        default:
            gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)];
    }
    update_rect.x = x - 10 * pressure;
    update_rect.y = y - 10 * pressure;
    update_rect.width = 20 * pressure;
    update_rect.height = 20 * pressure;
    gdk_draw_rectangle (pixmap, gc, TRUE,
                        update_rect.x, update_rect.y,
                        update_rect.width, update_rect.height);
    gtk_widget_draw (widget, &update_rect);
}
1.2 了解设备更多信息

我们的程序会打印每次按钮按下时产生该事件的设备名称。要获取设备名称,可调用 gdk_input_list_devices() 函数:

GList *gdk_input_list_devices(void);

该函数返回一个 GList (glib库中的链表类型),链表中的元素为 GdkDeviceInfo 结构。 GdkDeviceInfo 结构定义如下:

struct _GdkDeviceInfo
{
    guint32 deviceid;
    gchar *name;
    GdkInputSource source;
    GdkInputMode mode;
    gint has_cursor;
    gint num_axes;
    GdkAxisUse *axes;
    gint num_keys;
    GdkDeviceKey *keys;
};

大部分字段是配置信息,除非实现XInput配置保存,否则可以忽略。我们关注的是 name 字段,它是X分配给设备的名称。另一个非配置信息的字段是 has_cursor ,若其值为 false ,则需要自行绘制光标。但由于我们指定了 GDK_EXTENSION_EVENTS_CURSOR ,所以无需担心这个问题。

print_button_press() 函数会遍历返回的列表,直到找到匹配项,然后打印设备名称:

static void
print_button_press (guint32 deviceid)
{
    GList *tmp_list;
    tmp_list = gdk_input_list_devices();
    while (tmp_list)
    {
        GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data;
        if (info->deviceid == deviceid)
        {
            printf("Button press on device ’%s’\n", info->name);
            return;
        }
        tmp_list = tmp_list->next;
    }
}
1.3 进一步完善

虽然程序现在对XInput的支持已经不错,但仍缺少一些功能。用户可能不希望每次运行程序都重新配置设备,因此我们应允许他们保存设备配置。具体做法是遍历 gdk_input_list_devices() 的返回结果,并将配置信息写入文件。

下次运行程序时,可使用GDK提供的函数来更改设备配置,如:
- gdk_input_set_extension_events()
- gdk_input_set_source()
- gdk_input_set_mode()
- gdk_input_set_axes()
- gdk_input_set_key()

需要注意的是, gdk_input_list_devices() 返回的列表不能直接修改。相关示例可参考绘图程序 gsumi (可从 http://www.msc.cornell.edu/otaylor/gsumi/ 获取)。

另外,程序还缺少光标绘制功能。除XFree86之外的平台目前不允许设备同时作为核心指针和被应用程序直接使用。若要支持更广泛的用户群体,应用程序需要自行绘制光标。绘制光标需要完成两件事:
1. 确定当前设备是否需要绘制光标,可通过搜索设备列表来实现,就像查找设备名称那样。
2. 确定当前设备是否处于接近状态(如绘图板,当笔离开绘图板时让光标消失是个不错的设计),可通过选择 “proximity out” 事件来实现。GTK发行版中的 testinput 程序提供了绘制自定义光标的示例。

2. GTK应用程序编写技巧
2.1 使用GNU autoconf和automake

GNU autoconf和automake是编写GTK应用程序的好帮手。 automake 会检查C文件,确定它们之间的依赖关系,并生成Makefile,确保文件能按正确顺序编译。 autoconf 允许自动配置软件安装,处理大量系统特性以提高可移植性。

2.2 C代码注释规范

编写C代码时,应使用C风格的注释(以 /* 开头,以 */ 结尾),避免使用C++风格的注释( // )。虽然许多C编译器能理解C++注释,但并非所有编译器都支持,且ANSI C标准并未要求处理C++风格的注释。

3. 贡献与版权

如果你对GTK的某个方面有了解且相关内容尚无文档记录,欢迎为相关文档做出贡献。若决定贡献内容,请将文本发送至Tony Gale(gale@gtk.org)。需注意,整个文档是免费的,你提供的任何补充内容也必须免费,即人们可以在他们的程序中使用你的示例的任何部分,并且可以随意分发文档副本。

文档版权信息如下:
- GTK教程版权归1997年的Ian Main以及1998 - 1999年的Tony Gale所有。
- 允许制作和分发该手册的逐字副本,但需保留版权声明和许可声明。
- 允许在满足逐字复制条件的情况下复制和分发修改后的版本,但必须包含与原文完全相同的版权声明,且整个派生作品必须按照与本许可声明相同的条款进行分发。
- 允许在上述修改版本的条件下复制和分发该文档的翻译版本。

4. GTK信号

GTK是一个面向对象的小部件集,具有继承层次结构,信号也遵循这种继承机制。因此,使用本节列出的信号时,应参考小部件层次结构树。以下是部分GTK对象的信号列表:
| 对象类型 | 信号 | 说明 |
| ---- | ---- | ---- |
| GtkObject | destroy | 当对象被销毁时触发 |
| GtkWidget | show hide map unmap 等 | 分别对应小部件的显示、隐藏、映射、取消映射等操作 |
| GtkContainer | add remove check-resize 等 | 用于添加、移除子部件以及检查大小调整等操作 |
| GtkCalendar | month-changed day-selected 等 | 与日历的月份更改、日期选择等操作相关 |

以下是部分信号的代码示例:

// GtkObject信号示例
void GtkObject::destroy (GtkObject *, gpointer);

// GtkWidget信号示例
void GtkWidget::show (GtkWidget *, gpointer);
void GtkWidget::hide (GtkWidget *, gpointer);

通过合理使用这些信号,我们可以实现各种交互功能,提升GTK应用程序的用户体验。

综上所述,GTK编程涉及到设备信息处理、应用程序编写技巧、文档贡献以及信号处理等多个方面。掌握这些知识和技巧,能够帮助我们开发出功能强大、用户友好的GTK应用程序。希望本文能为你在GTK编程的道路上提供一些帮助。

GTK编程:绘图程序与信号处理全解析

5. 更多GTK对象信号详解

除了前面提到的部分GTK对象信号,还有许多其他对象的信号也非常重要,下面为大家详细介绍。

5.1 GtkEditable信号

GtkEditable对象用于处理可编辑的文本区域,其相关信号如下:
| 信号 | 说明 |
| ---- | ---- |
| changed | 当文本内容发生改变时触发 |
| insert-text | 在插入文本时触发 |
| delete-text | 在删除文本时触发 |
| activate | 当用户激活可编辑区域(如按下回车键)时触发 |

以下是部分信号的代码示例:

void GtkEditable::changed (GtkEditable *, gpointer);
void GtkEditable::insert-text (GtkEditable *, GtkString *, ggint, ggpointer, gpointer);
5.2 GtkTipsQuery信号

GtkTipsQuery对象用于处理提示信息查询,其信号如下:
| 信号 | 说明 |
| ---- | ---- |
| start-query | 开始查询提示信息时触发 |
| stop-query | 停止查询提示信息时触发 |
| widget-entered | 鼠标进入小部件时触发 |
| widget-selected | 小部件被选中时触发 |

代码示例:

void GtkTipsQuery::start-query (GtkTipsQuery *, gpointer);
void GtkTipsQuery::widget-entered (GtkTipsQuery *, GtkWidget *, GtkString *, GtkString *, gpointer);
5.3 GtkCList信号

GtkCList对象用于处理列表视图,其信号如下:
| 信号 | 说明 |
| ---- | ---- |
| select-row | 选中某一行时触发 |
| unselect-row | 取消选中某一行时触发 |
| row-move | 行移动时触发 |
| click-column | 点击某一列时触发 |

代码示例:

void GtkCList::select-row (GtkCList *, ggint, ggint, GdkEvent *, gpointer);
void GtkCList::row-move (GtkCList *, ggint, ggint, gpointer);
6. 信号处理流程

在GTK应用程序中,信号处理是一个关键环节。下面是一个简单的信号处理流程mermaid流程图:

graph TD;
    A[事件发生] --> B[信号触发];
    B --> C[查找信号处理函数];
    C --> D{是否找到处理函数};
    D -- 是 --> E[执行处理函数];
    D -- 否 --> F[忽略该信号];
    E --> G[处理完成];
7. 信号处理的实际应用

以一个简单的按钮点击事件为例,展示信号处理在实际应用中的使用。以下是完整的代码示例:

#include <gtk/gtk.h>

// 按钮点击信号处理函数
static void button_clicked(GtkWidget *widget, gpointer data) {
    g_print("Button clicked!\n");
}

int main(int argc, char *argv[]) {
    GtkWidget *window;
    GtkWidget *button;

    // 初始化GTK
    gtk_init(&argc, &argv);

    // 创建窗口
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // 创建按钮
    button = gtk_button_new_with_label("Click me");
    g_signal_connect(button, "clicked", G_CALLBACK(button_clicked), NULL);

    // 将按钮添加到窗口
    gtk_container_add(GTK_CONTAINER(window), button);

    // 显示所有小部件
    gtk_widget_show_all(window);

    // 进入主循环
    gtk_main();

    return 0;
}

在这个示例中,当按钮被点击时, button_clicked 函数会被调用,并打印出 “Button clicked!”。

8. 总结与展望

通过本文的介绍,我们了解了GTK绘图程序中扩展设备信息的使用、设备信息的获取、应用程序编写技巧、文档贡献以及丰富的GTK信号处理知识。这些内容对于开发功能强大、交互性好的GTK应用程序至关重要。

在未来的GTK编程中,我们可以进一步探索更多的信号处理方式,结合不同的GTK对象,实现更加复杂和多样化的用户界面和交互功能。同时,随着技术的不断发展,GTK也可能会有更多的新特性和改进,我们需要持续关注和学习,以跟上技术的步伐,开发出更加优秀的GTK应用程序。

希望本文能为广大GTK开发者提供有价值的参考,帮助大家在GTK编程的道路上取得更好的成果。

源码地址: https://pan.quark.cn/s/d1f41682e390 miyoubiAuto 米游社每日米游币自动化Python脚本(务必使用Python3) 8更新:更换cookie的获取地址 注意:禁止在B站、贴吧、或各大论坛大肆传播! 作者已退游,项目不维护了。 如果有能力的可以pr修复。 小引一波 推荐关注几个非常可爱有趣的女孩! 欢迎B站搜索: @嘉然今天吃什么 @向晚大魔王 @乃琳Queen @贝拉kira 第三方库 食用方法 下载源码 在Global.py中设置米游社Cookie 运行myb.py 本地第一次运行时会自动生产一个文件储存cookie,请勿删除 当前仅支持单个账号! 获取Cookie方法 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 按刷新页面,按下图复制 Cookie: How to get mys cookie 当触发时,可尝试按关闭,然后再次刷新页面,最后复制 Cookie。 也可以使用另一种方法: 复制代码 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 控制台粘贴代码并运行,获得类似的输出信息 部分即为所需复制的 Cookie,点击确定复制 部署方法--腾讯云函数版(推荐! ) 下载项目源码和压缩包 进入项目文件夹打开命令行执行以下命令 xxxxxxx为通过上面方式或取得米游社cookie 一定要用双引号包裹!! 例如: png 复制返回内容(包括括号) 例如: QQ截图20210505031552.png 登录腾讯云函数官网 选择函数服务-新建-自定义创建 函数名称随意-地区随意-运行环境Python3....
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值