谁编写了 GTK4

GTK 4 是一项历时多年、规模庞大的开发工作,始于 2016 年 10 月,止于 2020 年 12 月。现在 4.0 版本终于发布了,是时候回顾一下这四年里数百位贡献者所完成的惊人工作量了。

早在 2016 年,我们对时间表显然有点过于乐观,认为能够在三年内,即 2019 年底发布 4.0 版本。计划是从更改 GTK 的渲染管线开始,将其转换为可以提交给 GPU 的保留操作图,而不是自工具包诞生之初就使用的即时模式渲染,这种模式经历了两个主要的 API 周期——首先是抽象化 Xlib 绘图命令,然后是转向 Cairo 操作。当然,我们也知道我们想要改进其他子系统,如输入和窗口系统 API,以摆脱 X11 的影响,并朝着更符合 Wayland(和其他窗口系统)要求的方向设计。最终,我们得到的是工具包内部的深度重新设计,以及一种更倾向于通过辅助对象进行更多委托,减少泄漏的抽象和深度类型层次结构的不同编程模型;此外,我们精简了暴露的内部结构,以确保工具包和使用它的应用程序在未来更易于维护。缺点是 GTK 不再是一个“元工具包”,其内部状态可以从外部窥视,并期望在多个版本之间工作;从长远来看,鉴于可用的资源,这种方法是不可持续的,并导致我们无法优化或改进 GTK 的内部结构,从而损害了每个用户。

我们不希望下一个主要开发周期花费这么长时间,但你知道俗话说:任何行动计划都无法确定地延伸到与主要敌对力量的首次接触之后。不过,重要的是,我们仍然看到了改进的潜力;虽然我们想庆祝 GTK4 的发布,以及为之做出贡献的每个人,但我们也想传达,2016 年概述的计划仍然非常有效。我们已经在 GitLab 项目页面上开始收集 GTK5 开发周期的项目,并且我们计划与各方利益相关者进行更多讨论,以推动未来的工作。

一些统计数据

从 2016 年 10 月 5 日的提交 4cce6104 开始

  • 总提交次数: 16852
  • 开发者: 256
  • 雇主: 10
  • 添加的行数:1053492,删除的行数:1053542(差值:-50)

最有趣的是,尽管做了这么多工作,我们最终从现有代码库中删除了总共 50 行代码,包括测试和文档。

按年份细分

年份 开发者 提交次数 添加的行数 删除的行数 差值
2016 50 1422 64014 152103 -88089
2017 84 3446 186024 166198 19826
2018 111 2332 129726 140526 -10800
2019 85 3244 200476 207623 -7147
2020 109 6384 470907 386761 84206

我们在 2016 年只有几个月的时间,10 月到 12 月之间最大的工作是删除已弃用的 API,以及应对从 GTK3 重命名为 GTK4 的各种更改。尽管如此,GSK 最初还是在 11 月/12 月着陆的。11 月 21 日,GTK 3.89.1 发布,这是新 GTK 4 API 的第一个开发快照。

在 2017 年,GSK 的 API 进行了多次迭代:添加了新的渲染节点来实现 CSS 绘图原语,并且小部件从旧的 Cairo 渲染原语移动到了 GtkSnapshot API。此外,还引入了新的剪贴板 API,采用了基于流的实现。3 月 31 日,GTK 3.90.0 发布,随后在 10 月发布了 3.92.0。

在 2018 年,事情似乎放慢了脚步,但这主要是因为我们迁移到了 GitLab。突然之间,我们可以访问 CI 管道和合并请求,并且我们对我们的开发过程越来越有信心;很多工作发生在开发分支中,尤其是大型重构,例如将 GdkWindow 重命名为 GdkSurface;移动到 GtkGesture 对象并删除每个小部件的事件信号;或使叶子类不可派生。GTK 3.94.0 于 6 月发布。

在 2019 年,开发速度再次加快

  • 小部件转换
  • 布局管理器
  • 约束布局
  • 不再有 GtkMenu
  • GtkBuilder 的作用域对象
  • GtkText 小部件
  • GtkNativeGtkRoot 接口
  • 每个小部件的动作
  • 简化的 GdkSurface 子类型

此外,当然,渲染管线中还有许多性能和功能改进。GTK 3.96.0 于 5 月发布。

最后,我们来到了 2020 年

  • 基于事件控制器的新拖放 API
  • GDK 中新的 macOS 后端,取代了旧的 Quartz 后端
  • 新的辅助功能 API,放弃 ATK
  • 不再有 GtkContainer
  • 不再有 GtkRadioButton
  • 简化的 GdkDevice API
  • 新的键盘快捷键 API
  • 新的表达式和筛选模型
  • 新的基于模型的列表和树形小部件

GTK 3.98.0 于 2 月发布;最后,3.99.0 于 7 月发布,作为向 GTK 4.0 的第一个真正的 beta 版本。

 

开发者

按提交次数排名前 20
Matthias Clasen 6519 (38.7%)
Timm Bäder 3229 (19.2%)
Benjamin Otte 2596 (15.4%)
Emmanuele Bassi 1094 (6.0%)
Carlos Garnacho 494 (2.9%)
Daniel Boles 383 (2.3%)
Alexander Larsson 313 (1.9%)
Jonas Ådahl 167 (1.0%)
Chun-wei Fan 162 (1.0%)
Christian Hergert 158 (0.9%)
Jakub Steiner 134 (0.8%)
Piotr Drąg 132 (0.8%)
Руслан Ижбулатов 120 (0.7%)
Alexander Mikhaylenko 93 (0.6%)
Rico Tzschichholz 78 (0.5%)
nana-4 66 (0.4%)
Christoph Reiter 62 (0.4%)
Tim-Philipp Müller 60 (0.4%)
Mohammed Sadiq 57 (0.3%)
Olivier Fourdan 42 (0.2%)
按更改次数排名前 20
Matthias Clasen 620248 (36.8%)
Benjamin Otte 466996 (27.7%)
Timm Bäder 187516 (11.1%)
Emmanuele Bassi 165354 (9.8%)
Alexander Larsson 53065 (3.1%)
Carlos Garnacho 27227 (1.6%)
Christian Hergert 26964 (1.6%)
Руслан Ижбулатов 21760 (1.3%)
Jakub Steiner 18388 (1.1%)
Jonas Ådahl 12824 (0.8%)
Chun-wei Fan 12518 (0.7%)
Daniel Boles 12371 (0.7%)
Lapo Calamandrei 9995 (0.6%)
Christoph Reiter 8391 (0.5%)
Alexander Mikhaylenko 4936 (0.3%)
Tim-Philipp Müller 3932 (0.2%)
Rico Tzschichholz 3108 (0.2%)
William Hua 2900 (0.2%)
Jason Francis 1908 (0.1%)
Peter Bloomfield 1727 (0.1%)

像往常一样,Matthias 在提交次数和代码更改方面都超过了其他所有贡献者。

雇主

按提交次数
红帽 13706 (81.3%)
(未知) 1185 (7.0%)
GNOME 基金会 1095 (6.5%)
GNOME 571 (3.4%)
Purism 93 (0.6%)
Canonical 84 (0.5%)
Centricular 75 (0.4%)
Endless 20 (0.1%)
Collabora 14 (0.1%)
英特尔 6 (0.0%)
Novell 3 (0.0%)
按更改次数
红帽 1415771 (84.0%)
GNOME 基金会 165355 (9.8%)
(未知) 58220 (3.5%)
GNOME 33442 (2.0%)
Purism 4936 (0.3%)
Centricular 4205 (0.2%)
Canonical 3347 (0.2%)
Novell 336 (0.0%)
英特尔 222 (0.0%)
Collabora 208 (0.0%)
Endless 121 (0.0%)
按贡献者
(未知) 199 (72.4%)
GNOME 32 (11.6%)
红帽 22 (8.0%)
Endless 6 (2.2%)
Collabora 4 (1.5%)
Centricular 3 (1.1%)
Novell 3 (1.1%)
GNOME 基金会 2 (0.7%)
Canonical 2 (0.7%)
英特尔 1 (0.4%)
Purism 1 (0.4%)

虽然红帽员工的影响最大,但我们仍然有来自社区的大量贡献者——他们既使用自己的 GNOME 电子邮件地址,也使用工作地址,即使是在个人层面上做出贡献时也是如此。我们还有来自 GNOME 和自由软件生态系统中各个公司的多个贡献者,这标志着围绕该项目存在一个健康的社区。

与 GTK3 比较数据有点棘手

  • GTK 2 → 3 的开发阶段只花了大约一年多的时间,而不是四年
  • 我们仍然在使用旧的 GNOME 基础设施,这意味着更少、更大的补丁,以及以 Bugzilla 的形式提高了贡献的门槛

尽管如此,与 2011 年相比,贡献者的数量增加了一倍以上,其中非附属贡献者的数量增加了近三倍。

¹ – GNOME 基金会的付费员工。

² – 使用 gnome.org 电子邮件地址且就业状况不明的贡献者。

GTK 4.0

2020 年是非常漫长的一年。有什么比发布一个主要版本更好的方式来结束它呢!今天,我们发布了 GTK 4.0。

GTK 4.0 是一个由小型敬业的开发团队努力工作的结果。我们将单独发布一篇帖子来回顾统计数据,但简而言之,自 2016 年 11 月的 3.89.1 版本发布以来,我们已经添加了 18000 多个提交,并发布了 20 多个开发版本。

祝贺并衷心感谢所有参与这项工作的人,特别是 Benjamin、Emmanuele、Timm、Carlos、Jonas 和 Christian!

新功能

不可能在一篇文章中总结 4 年的开发工作。在过去的一年里,我们已经撰写了详细的文章,介绍了此版本中的许多新内容:数据传输事件控制器布局管理器渲染节点媒体播放可伸缩列表着色器辅助功能。以下是一些以视觉形式呈现的亮点

媒体播放

拖放

布局管理器和变换

可伸缩列表和网格

着色器

旧功能

GTK 4 现在是稳定的,我们认为它已经准备好投入使用了。这并不意味着 GTK 3 已经死了——在可预见的未来,我们将继续支持和更新它(最新版本 3.24.24 在几天前悄然发布)。然而,这确实意味着 GTK 2 已经走到了尽头。我们将在未来几天发布最后一个 2.x 版本,我们鼓励每个人将其 GTK 2 应用程序移植到 GTK 3 或 4。

如何获取

源代码 tarball 可在通常的位置获得。二进制包应该很快就会出现在主要发行版中。

GNOME 40 版本将有许多应用程序移植到 GTK 4。如果您想立即尝试 GTK 4.0,可以使用gtk4-demogtk4-widget-factory 的 nightly flatpak 构建版本。

$ flatpak install https://nightly.gnome.org/repo/appstream/org.gtk.Demo4.flatpakref
$ flatpak run org.gtk.Demo4

如果您渴望将您的应用程序移植到 GTK 4,我们的迁移指南作为文档的一部分提供

如何支持 GTK

GTK 的开发离不开众多志愿者的贡献,他们提供了错误报告、补丁、翻译或想法。感谢所有人的付出。我们还要感谢 GNOME 基金会对 GTK 的支持,包括开发资源、基础设施和差旅协助。

向 GNOME 基金会捐款是支持未来 GTK 开发的好方法。

接下来会发生什么

我们非常感谢所有早期测试人员提供的错误报告和反馈,这使此版本有了很大的改进。但我们完全预料到会很快发布 4.0.1 版本,以修复只有在 .0 版本发布后才会暴露出来的疏忽和陷阱。

现在我们有了 4.0 版本,我们需要带动整个库生态系统,以便应用程序能够使用它。vte、webkit 和 gtksourceview 是一些最常用的与 GTK 一起使用的著名库。我们预计这些库的 GTK 4 移植版将很快推出。

如果出现更严重的问题,我们将在 GNOME 40 发布前及时推出 4.2 版本,否则我们可能会等到夏天再发布。

GTK 4 之后的未来会带来什么,还有待观察。我们有一些有趣的项目正在进行中,但也希望听到应用程序开发人员希望在 GTK 中看到哪些功能。请告诉我们!

庆祝活动

我们将在本周五举行(在线)聚会来庆祝 4.0 版本的发布。欢迎大家前来参加!

GTK 4 中的可访问性

上周 GTK 3.99.3 版本中的重大新闻是,我们为新的可访问性实现提供了第一个重要的后端。因此,现在是深入了解 GTK 4 中可访问性的好时机。

概述

让我们先快速回顾一下可访问性在 Linux 上的工作原理。其中的参与者是应用程序和辅助技术 (ATs),例如屏幕阅读器(例如,Orca)、放大镜等。

AT 的目的通常是为用户提供与其需求相匹配的、与应用程序交互的替代方式(例如,放大视图、大声朗读文本或语音命令)。为此,AT 需要大量有关应用程序 UI 的详细信息,而这正是可访问性堆栈发挥作用的地方——它是应用程序(或其工具包)和 AT 之间的连接层。

应用程序和 AT 通过可访问性总线进行通信,这是 D-Bus 会话总线的一个单独实例,使用 AT-SPI 项目描述的接口。应用程序的 UI 元素在总线上表示为对象,这些对象实现了一些抽象的接口,例如 TextValue。应用程序发出信号来传达 UI 中的更改,而 AT 可以调用对象的方法来获取信息或进行更改(例如,更改 Value 接口的当前值以移动它所代表的 GtkScale)。

发生了什么变化

在 GTK 2 和 3 中,这是以一种笨拙的间接方式完成的:GTK 小部件具有辅助的可访问对象,这些对象是 ATK 接口的实现(转换 1:GTK ➙ ATK)。然后,这些对象被转换为 AT-SPI 对象(转换 2:ATK ➙ AT-SPI),这些对象由 at-spi2-atk 中的适配器代码表示在可访问性总线上。在另一端,AT 然后使用 pyatspi 将 AT-SPI 接口转换为 Python 对象(转换 3:AT-SPI ➙ Python)。

这种多步过程效率低下、有损且难以维护;它需要在至少三个组件上实现相同的功能,并且导致了文档化的 AT-SPI 方法和属性与实际通过可访问性总线发送的方法和属性之间的差异。

在 GTK 4 中,我们通过删除了 ATK 和 at-spi2-atk 来简化应用程序端。小部件现在实现了一个 GtkAccessible 接口,该接口允许它们设置一些角色、状态、属性和关系,这些角色、状态、属性和关系或多或少直接取自 W3C 发布的 WAI-ARIA 规范。GTK 可访问性 API 的 AT-SPI 后端然后获取这些受 ARIA 启发的属性(以及小部件本身的知识),并将这些小部件表示为可访问性总线上的对象,为它们实现相关的 AT-SPI 接口。

这是一种更直接的方法,与 Qt 和 Web 浏览器已有的做法相匹配。

应用程序 API

以下是应用程序中使用 GTK 4 时最可能遇到的可访问性 API 的要点

设置可访问的角色。角色是对小部件语义的描述,AT 将使用它来决定应向用户呈现哪种行为。设置角色是一次性操作,这意味着它必须在小部件创建时完成,无论是在 class_init 中还是在实例初始化期间。

gtk_widget_class_set_accessible_role (widget_class,  
                                      GTK_ACCESSIBLE_ROLE_BUTTON);

更新小部件的可访问状态或属性。只要小部件的可访问表示发生更改,就应该执行此操作。

gtk_accessible_update_property (GTK_ACCESSIBLE (widget),
                       GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, minimum,
                       GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, value,
                       GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, maximum,
                       -1);

GTK 参考文档提供了可访问性 API 的概述,其中包括针对应用程序开发人员和小部件编写人员的指南。

接下来是什么?

对于 GTK 4.0,我们专注于完成 AT-SPI 后端。但是有了新的 API 和后端分离,我们有了一条明确的路径来为其他平台制作可访问性后端,这是我们希望在后续 GTK 4 版本中研究的内容。

在 Linux 上,我们希望与其他利益相关者合作,使 AT-SPI 接口现代化,以最终克服在某些地方仍然可见的 CORBA 遗留问题。其中的一部分将是从可访问性总线转向应用程序和 AT 之间的点对点连接;这将增强可访问性堆栈的安全性,并填补诸如 Flatpak 等技术使用的沙盒中的漏洞。

未来,我们希望引入工具来确保应用程序开发人员了解缺少的可访问性注释,例如为 UI 中的图标和图像提供标签属性或 labelled-by 关系;或者确保每个 UI 元素都在可访问的树中正确表示。我们已经为 GtkAccessible 接口提供了一个测试后端,该后端可用于编写单元测试并验证在必要时更新角色和属性。

GTK 3.99.2

GTK 3.99.2 版本继续了 3.99.1 中的主题:API 清理、新的和改进的演示、更好的文档。您可以在这里查看详细信息。

关于文档主题的一点说明是,我们依赖于一些未发布的 gtk-doc 功能。因此,我们现在将 gtk-doc 作为子项目包含在 gtk 发布 tarball 中。如果您是发行商,请不要惊讶于构建 GTK 现在会安装 gtk-doc 工具。

此快照中的重大新闻是我们致力于更多地展示新的基于 GL 的渲染堆栈的强大功能。

预热:Shadertoy

gtk4-demo 现在包含一个 Shadertoy 演示。

该演示使用 GtkGLArea 小部件来运行与 shadertoy.com 上的 GLSL 代码片段兼容的代码片段。如果您将这些示例粘贴到此演示的编辑器中,则许多示例都将有效。

这很有趣,但有些限制。GLSL 被限制在其“沙盒”,即 GtkGLArea 小部件中,该小部件使用 GL API 来编译和使用着色器。

着色器作为一等对象

这不是我们第一次尝试制作类似 shadertoy 的东西。当我们第一次看到它时,我们认为我们会制作一个应用程序可以使用的着色器抽象。当事实证明跨不同渲染器和后端使其工作需要我们编写自己的着色器编译器时,我们将其放在了一边——工作量太大。

但在我们 Shadertoy 成功之后,我们重新审视了将着色器作为一等对象的想法,并制定了更适度的目标:我们使用 GLSL,并且不尝试使着色器与 OpenGL 渲染器以外的任何其他渲染器一起使用。

在 3.99.2 中,我们现在有

有了这些组件,我们制作了一个演示,展示了着色器的各种用途。它可能有点超载,并且某些效果有点过分,但它表达了重点:您可以在小部件中使用着色器。

 

我们尚未做的是添加内置着色器支持的小部件。该演示展示了一些可能的候选对象

可着色的着色器。正如您可能回忆的那样,GdkPaintable 是任何可以“绘制”的非常灵活的接口。着色器当然符合条件。gtk-demo 中的 GskShaderPaintable 使用没有输入纹理的着色器来仅生成像素,我们将其添加到 GtkPicture 小部件以使其显示在小部件树中。

一个着色器容器。这是一个非常简单的容器,可以使用着色器在子部件之上绘制效果。它适用于接受单个输入纹理(用于子部件)的着色器。

一个着色器堆栈。这是一个类似堆栈的容器,它显示多个子部件中的一个,并且在可见子部件更改时使用着色器进行过渡。它适用于需要两个输入纹理(用于旧的和新的活动子部件)的着色器。

值得庆幸的是,在 GTK 4 中创建自定义部件比以前容易得多,因此渲染节点 API 应该足以让你开始进行一些有趣的实验。你当然可以把 gtk4-demo 代码作为起点。

你可以调试它

除了部件之外,着色器支持也已完全集成。GTK inspector 可以像处理其他渲染节点一样处理着色器节点,你可以序列化它们,例如在 gtk4-node-editor 中加载生成的文件

如果你需要查看 GTK 发送到着色器编译器的输入,设置环境变量

GDK_DEBUG=shaders

可能会有所帮助。

接下来是什么?

在这次 OpenGL 冒险之后,我们将重点关注更多新的辅助功能基础设施的落地。

GtkColumnView

在我最近关于 GTK 4 中的列表视图模型的系列文章中,我留下了一个未完成的内容,即对 GtkColumnView 的详细介绍。这将很容易成为该系列中最复杂的部分。我们正在进入 GtkTreeView 的核心地带——任何旨在替代其大部分功能的组件都将是一个复杂的庞然大物。

概述

就像我们对 GtkListView 所做的那样,我们将从一个高层次的概述和一张图片开始。

如果你回顾一下listview 图片,你会记得我们使用列表项工厂为模型中需要显示的每个项创建一个部件。

在一个列视图中,我们需要每个项的多个部件——每一列一个。我们这样做的方式是给每一列自己的列表项工厂。每当我们需要显示一个新项时,我们将来自每个列工厂的部件组合成一个新项的行。

在内部,列视图实际上是使用列表视图来保存行。这很好,因为我在之前的帖子中解释的所有关于项重用以及如何使用列表项工厂的内容都同样适用。

当然,有些事情是不同的。例如,列视图已经组织了大小分配,以便所有行中的部件排列成适当的列。

注意:就像 GtkListView 一样,列视图仅为当前在视图中的模型段创建部件,因此它共享垂直可伸缩性。在水平方向上情况并非如此——每一行都完全填充了每一列的部件,即使它们在左侧或右侧的视图之外。因此,如果你添加大量列,速度会变慢。

标题和其他复杂情况

列对象还包含其他数据,例如标题。列视图使用这些来显示每一列的标题。如果列视图被标记为可重新排序,你可以通过拖放标题部件来重新排列列。如果列被标记为可调整大小,你可以拖动两列之间的边框来调整它们的大小。

如果你注意了,你现在可能会想知道这种大小调整如何与行中的单元格可以是任意部件的事实结合在一起,这些部件期望至少有其最小尺寸可用于绘制其内容。答案是我们正在使用 GTK 4 渲染机制的另一个新功能:部件可以控制如何处理其边界外的绘制(由子部件),使用

 gtk_widget_set_overflow (cell, GTK_OVERFLOW_HIDDEN)

排序、选择和对 treeview 对等功能的追求

由于我们希望在功能上与 GtkTreeview 匹配,因此我们尚未完成。用户喜欢在树状视图中做的另一件事是单击标题,按该列对内容进行排序。GtkColumnView 标题也允许这样做。

你可能还记得上一篇文章,排序是通过将你的数据包装在 GtkSortListModel 中,并为其提供一个合适的排序器对象来完成的。由于我们希望根据你单击的列标题有不同的排序顺序,我们为每一列提供自己的排序器,你可以使用以下方法设置:

gtk_column_view_column_set_sorter (column, sorter)

但是,我们如何从你刚单击的列中获取正确的排序器,并将其附加到排序模型?请记住,排序模型不是我们要传递给列视图的最外层模型,因为它始终是一个选择模型,因此列视图不能自行在排序列表模型上切换排序器。

我们提出的解决方案是使列视图提供一个内部使用列排序器的排序器,使用:

gtk_column_view_get_sorter (view)

你可以在设置模型时将此排序器一次性提供给你的排序模型,然后当用户单击列标题以激活不同的列排序器时,事情会自动更新。

这听起来很复杂,但它的工作效果出奇地好。这种方法的一个好处是,我们实际上可以一次按多个列进行排序——因为我们拥有所有可用的列排序器,并且我们知道你最后单击了哪个。

相比之下,选择处理很容易。它的工作方式与 GtkListView 中的相同。

总结

GtkColumnView 是一个复杂的部件,但我希望这一系列帖子能让你更容易上手使用它。

关于列表模型

在上一篇文章中,我承诺深入探讨列表模型以及 GTK 4 在这方面提供的功能。让我们首先看看 GListModel 接口

struct _GListModelInterface
{
  GTypeInterface g_iface;

  GType    (* get_item_type) (GListModel *list);
  guint    (* get_n_items)   (GListModel *list);
  gpointer (* get_item)      (GListModel *list,
                              guint       position);
};

实现该接口的一个重要部分是,你需要发出
在需要时使用辅助函数发送 ::items-changed 信号,该辅助函数
GLib 为此目的而设

void g_list_model_items_changed (GListModel *list,
                                 guint       position,
                                 guint       removed,
                                 guint       added)

关于此接口需要注意的几件事

  • 非常精简;这使得它易于实现
  • API 以位置为单位,并且仅处理列表成员身份的更改——跟踪项目本身的更改由你自行负责

一个列表模型动物园

GTK 提供了一个相当大的列表模型实现集合。仔细检查后,它们分为几个不同的组。

列表模型构建工具包

第一组可以称为列表模型构建工具包:通过修改或组合你已经拥有的模型来构建新模型的模型。

该组中的第一个模型,GtkSliceListModel,取现有模型的切片,由偏移量和大小给出,并创建一个仅包含这些项的新模型。如果你想在分页视图中呈现一个大列表,这很有用——向前和向后按钮将简单地将偏移量增加或减少大小。切片模型也可以用于逐步填充列表,通过随着时间的推移使切片更大。GTK 在某些地方使用了这种技术。

该组中的下一个模型,GtkFlattenListModel,采用多个列表模型并将它们组合成一个。由于这全部关于列表模型,要组合的模型以列表模型的列表模型形式传递给展平模型。每当需要组合来自多个来源的数据时,这很有用,例如 GTK 对打印对话框中的纸张尺寸所做的操作。

Paper size list in print dialog
一个扁平列表

请注意,原始模型继续存在于展平模型之后,并且它们的更新将按预期由展平列表模型传播。

有时,你的数据在一个列表模型中,但它的形式不太正确。在这种情况下,你可以使用 GtkMapListModel 将原始模型中的每个项替换为不同的项。

具体模型

GTK 及其依赖项包括许多我们自己处理的数据类型的具体模型。

这里的第一个示例是 Pango 对象,它们正在为其数据实现列表模型接口:PangoFontMap 是 PangoFontFamily 对象的列表模型,而 PangoFontFamily 是 PangoFontFace 对象的列表模型。字体选择器正在使用这些模型。

font chooser dialog
一个 Pango 列表模型

接下来的例子是 GtkDirectoryListGtkBookmarkList 对象,它们将在文件选择器中用于表示目录内容和书签。关于这些对象的一个有趣的细节是,它们都需要执行 IO 操作来填充其内容,并且它们会异步执行此操作,以避免长时间阻塞 UI。

此组中的最后一个模型稍微不太具体:GtkStringList 是围绕着非常常见的字符串数组的简单列表模型包装器。这种列表模型经常使用的一个例子是 GtkDropDown。这种情况太常见了,以至于 GtkDropDown 有一个方便的构造函数,它接受一个字符串数组并为您创建 GtkStringList。

GtkWidget *
    gtk_drop_down_new_from_strings (const char * const * strings)

选择

下一组模型通过一个新接口扩展了 GListModel:GtkSelectionModel。对于底层模型中的每个项,GtkSelectionModel 会维护其是否被选中的信息。

我们不会详细讨论此接口,因为您不太可能需要自己实现它,但最重要的几点是

gboolean gtk_selection_model_is_selected (GtkSelectionModel *model)
                                          guint              pos)
GtkBitset *
       gtk_selection_model_get_selection (GtkSelectionModel *model)

因此,您可以获取单个项的选择信息,也可以以位集的形式获取整体选择信息。当然,还有一个 ::selection-changed 信号,其工作方式与 GListModel 的 ::items-changed 信号非常相似。

GTK 有三个 GtkSelectionModel 实现:GtkSingleSelectionGtkMultiSelectionGtkNoSelection,它们在可以同时选择的项的数量上有所不同(1、多个或 0)。

GtkGridView 颜色演示展示了多选的实际应用,包括橡皮筋选择。

 

当您使用 GTK 的新列表小部件时,您很可能会遇到选择模型,因为它们都期望其模型是选择模型。

重要的模型

我想提到的最后一组模型是执行您在列表中期望的典型操作的模型:过滤和排序。这些模型是 GtkFilterListModelGtkSortListModel。它们都使用辅助对象来实现其操作:GtkFilter 和 GtkSorter。这两者都有子类来处理常见情况:排序和过滤字符串或数字,或使用回调。

在 GTK 3.99 的开发过程中,我们在这两个模型上花费了大量精力,并使其能够增量地完成工作,以避免在处理大型模型时长时间阻塞 UI。

GtkListView 单词演示展示了对 500,000 个单词列表的交互式过滤。

剩余的模型

GTK 中还有一些列表模型实现不适合以上任何分组,例如 GtkTreeListModelGtkSelectionFilterModelGtkShortcutController。今天我将跳过这些。

模型无处不在

最后,我将简要列出返回列表模型的 GTK API

  • gdk_display_get_monitors
  • gtk_widget_observe_children
  • gtk_widget_observe_controllers
  • gtk_constraint_layout_observe_constraints
  • gtk_constraint_layout_observe_guides
  • gtk_file_chooser_get_files
  • gtk_drop_down_get_model
  • gtk_list_view_get_model
  • gtk_grid_view_get_model
  • gtk_column_view_get_model
  • gtk_column_view_get_columns
  • gtk_window_get_toplevels
  • gtk_assistant_get_pages
  • gtk_stack_get_pages
  • gtk_notebook_get_pages

总而言之,列表模型在 GTK 4 中无处不在。它们灵活且有趣,您应该使用它们!

GtkListView 入门

一些 GTK4 的早期采用者指出,新的列表小部件不太容易学习。特别是,GtkExpression 和 GtkBuilderListItemFactory 很难理解。这并不奇怪——一个完整的列表小部件,包括列、选择、排序和树结构等,是一个复杂的野兽。

但是,让我们看看是否可以逐一解开这些内容,并使其更易于理解。

概述

让我们从相关组件及其交互的高级视图开始:模型、列表项工厂和列表视图。

它们是创建列表视图时发生的三个方面。

view = gtk_list_view_new (model, factory);

我们使用的模型是 GListModels。这些模型始终包含 GObjects,因此您必须以对象的形式提供数据。这是与 GtkTreeview 的第一个显著区别,后者使用直接包含基本类型的 GtkTreeModels。

对于某些简单的情况,GTK 提供了现成的模型,例如 GtkStringList。但通常情况下,您必须创建自己的模型。幸运的是,GListModel 的接口比 GtkTreeModel 简单得多,因此这并不太难。

列表项工厂的职责是生成一个行小部件,并在列表视图需要时将其连接到模型中的项。

列表视图将创建比填充其可见区域所需的行多几行,以便更好地估计滚动条的大小,并且在您决定滚动视图时提供一些“缓冲区”。

一旦您滚动,我们不一定需要让工厂生成更多行——我们可以回收从另一端滚动出视图的行。

幸运的是,所有这些都会在后台自动发生。您所要做的就是提供一个列表项工厂。

创建项

GTK 提供了两种不同的创建项的方法。您可以手动使用 GtkSignalListItemFactory,或者可以使用 GtkBuilderListItemFactory 从 ui 文件实例化行小部件。

手动方法更容易理解,所以让我们先看看它。

factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", setup_listitem_cb, NULL);
g_signal_connect (factory, "bind", bind_listitem_cb, NULL);

当工厂需要创建新的行小部件时,会发出“setup”信号;当行小部件需要连接到模型中的项时,会发出“bind”信号。

这两个信号都以 GtkListItem 作为参数,这是一个包装器对象,允许您访问模型项(使用 gtk_list_item_get_item()),还允许您传递新的行小部件(使用 gtk_list_item_set_child())。

static void
setup_listitem_cb (GtkListItemFactory *factory,
                   GtkListItem        *list_item)
{
  GtkWidget *label = gtk_label_new ("");
  gtk_list_item_set_child (list_item, label);
}

通常,您的行会比单个标签复杂。您可以创建复杂的小部件并将它们分组在容器中,根据需要。

static void
bind_listitem_cb (GtkListItemFactory *factory,
                  GtkListItem        *list_item)
{
  GtkWidget *label;
  MyObject *obj;

  label = gtk_list_item_get_child (list_item);
  obj = gtk_list_item_get_item (list_item);
  gtk_label_set_label (GTK_LABEL (label),
                       my_object_get_string (obj));
}

如果您的“bind”处理程序连接到项上的信号或执行需要清理的其他操作,则可以使用“unbind”信号来执行该清理。“setup”信号有一个类似的对应项,称为“teardown”。

使用构建器的方式

我们的“setup”处理程序基本上是创建小型小部件层次结构的配方。GTK 有一种更声明式的方法来执行此操作:GtkBuilder ui 文件。这就是 GtkBuilderListItemFactory 的工作方式:您给它一个 ui 文件,当它需要创建一行时,它会实例化该 ui 文件。

ui = "<interface><template class="GtkListItem">...";
bytes = g_bytes_new_static (ui, strlen (ui));
gtk_builder_list_item_factory_new_from_bytes (scope, bytes);

您现在可能想知道:等一下,您正在为我模型中的数千个项中的每一个解析 xml 文件,这不是很耗费资源吗?

对此问题有两种解答

  • 我们不是为每个项字面上解析 xml;我们解析一次,存储回调序列,并在以后重播它。
  • 实际上,GtkBuilder 最耗资源的部分不是 xml 解析,而是对象的创建;回收行有助于解决这个问题。

相对容易看出 ui 文件如何替换“setup”处理程序,但是“bind”呢?在上面的示例中,bind 回调正在获取项的属性(MyObject:string 属性),并使用它们的值来设置小部件的属性(GtkLabel:label 属性)。换句话说,“bind”处理程序正在执行属性绑定。为简单起见,我们在这里只创建了一次性绑定,但我们也可以使用 g_object_bind_property() 来创建持久绑定。

GtkBuilder ui 文件可以设置对象之间的属性绑定,但有一个问题:模型项在 ui 文件中不是“存在”的,它仅在“bind”时稍后与行小部件关联。

这就是 GtkExpression 的用武之地。在其核心,GtkExpression 是一种描述尚未存在的对象之间绑定的方法。在我们的例子中,我们想要实现的是

label->label = list_item->item->string

不幸的是,当它作为 ui 文件的一部分转换为 xml 时,会变得有点笨拙。

<interface>
  <template class="GtkListItem">
    <property name="child">
      <object class="GtkLabel">
        <binding name="label">
          <lookup name="string">
            <lookup name="item">GtkListItem</lookup>
          </lookup>
        </binding>
      </object>
    </property>
  </template>
</interface>

请记住,ui 模板中的类名 (GtkListItem) 用作引用正在实例化的对象的“this”指针。

因此,<lookup name=”item”>GtkListItem</lookup> 表示:已创建的列表项的“item”属性的值。<lookup name=”string”> 表示:该对象的“string”属性。<binding name=”label”> 表示将小部件的“label”属性设置为该属性的值。

由于表达式的工作方式,当列表项工厂将列表项上的“item”属性设置为新值时,所有这些都将被重新评估,这正是我们需要使行小部件回收工作的原因。

专家级困惑

当嵌套使用时,GtkBuilderListItemFactory 和 GtkExpression 可能会变得非常令人困惑——列表项工厂本身可以在 UI 文件中构建,并且可以将它们自己的 UI 文件作为属性给出,因此最终会得到如下结构:

<object class="GtkListView">
  <property name="factory">
    <object class="GtkBuilderListItemFactory">
      <property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
  <property name="child">
  ...
]]>
      </property>
    </object>
  </property>
...

即使是 GTK 专家也可能会对此感到困惑。

我的建议是,在刚开始使用 GtkListView 时避免这种情况——你不必在 UI 文件中创建列表项工厂,并且可以将它的 UI 模板指定为资源,而不是直接嵌入它。

深入了解

今天我们在这里描述的所有内容也适用于网格视图,只需进行少量调整即可。

到目前为止,我们主要关注视图方面。关于模型也有很多要说的。

然后是列视图,它值得单独写一篇文章。

GTK 3.99.1

自从我们发布 GTK 3.99 以来已经过去一个月了,现在是时候发布另一个快照了。这是:https://download.gnome.org/sources/gtk/3.99/gtk-3.99.1.tar.xz

此快照专注于完善和完成。

未完成的部分

我们已经整理了 API 中的一些未完成的部分。

最明显的变化可能是按钮类层次结构的简化。GtkCheckButton 不再从 GtkToggleButton 派生,它们现在是两个独立的窗口小部件,并且都可以分组为互斥的(也称为“单选组”)。在这种新的设置中,实际上不再需要 GtkRadioButton,因此它被删除了。

我们新的列表小部件(GtkListView 和 GtkGridView)的 API 也进行了一些小的调整。我们现在明确要求模型的类型为 GtkSelectionModel,以明确小部件处理选择。我们还删除了额外的“with_factory”构造函数,并在 new() 中只采用一个可为空的工厂参数,如下所示:

GtkWidget * gtk_list_view_new (GtkSelectionModel  *model,
                               GtkListItemFactory *factory);

作为 API 清理的一部分,我们删除了所有 CSS 样式类的定义 – 我们的窗口小部件支持哪些样式类在其文档中定义,并且这些额外的定义实际上没有很好地定义或有用。

现在,我们的主题正在圆角由 GtkFrame 窗口小部件绘制的框架。这要求我们使框架剪辑其子项 – 实际上不是 API 更改,而是一个值得一提的行为更改。

更多演示

过去一个月,我们在 gtk4-demo 上投入了大量精力。

我们已经实现了源代码高亮的现代化。我们现在正在使用 highlight 命令行实用程序。除此之外,这使我们可以对 xml 和 css 进行语法高亮显示,并支持深色主题。

Highlighting XML in a dark theme
高亮显示

演示列表具有更好的过滤和更好的外观。新外观是 Adwaita 现在支持的几个预定义的列表样式之一:丰富列表、导航侧边栏和数据表。

 

Rich List list style
丰富列表
Navigation Sidebar list style
导航侧边栏
Data Table list style
数据表

我们已经从 gtk4-demo 中删除了一些过时的演示,并改进了许多现有演示。这是我们现在的拖放演示的外观

Drag-and-Drop demo
拖放演示

还添加了许多新的演示。这是新的布局管理器和转换演示

性能和其他错误

许多错误已被修复;感谢我们积极的测试人员和错误报告者。

我们最近最终追踪到的一个长期存在的问题导致我们的 GL 渲染器在存在非平凡的投影变换时错误地进行裁剪。现在已经纠正了这个问题(结果可以在上面的转换演示中看到)。

作为前面提到的高亮改进的一部分,gtk_text_view_buffer_insert_markup() 已经变得快得多。此改进的发生仅仅是因为 highlight 实用程序可以生成 Pango 标记。感谢任何实现该功能的人!

我们解决的另一个性能问题是系统上具有大量字体时字体选择器对话框的加载时间。我们现在正在以增量方式填充字体列表。除了此更改之外,调查还导致了 fontconfig 和 Pango 的性能改进,这将使这些库的任何用户受益。

我能开始移植了吗?

答案是:是的!

现在是查看 GTK4、开始移植应用程序并就我们的新旧 API 向我们提供反馈的好时机。我们也渴望看到您在意外的方式中使用 GTK4 的想法 – 我们上面展示的几个演示也许可以给您一些启发。

接下来是什么?

我们希望尽快为我们新的辅助功能接口添加 at-spi 后端;它应该包含在下一个 GTK 快照中。

GTK 3.99

本周,我们发布了 GTK 3.99,这只能意味着一件事:GTK4 真的快来了!

早在二月发布 3.98 时,我们就概述了在制作功能完整的 3.99 版本之前想要实现的功能。这是列表

  • 键盘快捷键的事件控制器
  • 可移动的弹出窗口
  • 行回收列表和网格视图
  • 改进的辅助功能基础设施
  • 动画 API
我们做得怎么样?

我们已将动画 API 从 4.0 阻止列表删除,因为它需要更广泛的内部结构重组,并且我们无法及时完成。但是所有其他功能都已融入到各种 3.98.x 快照中,辅助功能基础设施是最近才实现的最后一部分。

这里已经介绍了一些功能,例如可移动的弹出窗口可伸缩列表。其他功能有望在未来在此处进行详细审查。在那之前,如果您对新的辅助功能基础设施感到好奇,可以看看 Emmanuele 的 GUADEC 演讲

还有什么新功能?

我想强调的一个领域是,为充实新的可伸缩列表基础设施所做的大量工作。我们的过滤器和排序模型现在可以增量执行其工作,因此在后台过滤或排序大型列表时,UI 可以保持响应。

新的 macOS GDK 后端已合并。它仍然有一些粗糙的角落,我们希望在现在到 4.0 版本之间将其消除。

并且修复了许多小的回归,从 spinbutton 大小调整到 treeview 单元格编辑到自动滚动到 Inspector 导航到稍微渲染错误的阴影。

我现在可以移植了吗?

GTK 3.99 是首次查看移植应用程序的绝佳时机。

我们非常感谢那些通过试用移植勇敢地使用 3.96 或 3.98 快照并为我们提供了宝贵反馈的早期采用者。由于进行了如此多的更改,我们在 API 中犯错是不可避免的,并且在我们仍然可以解决问题时获得反馈将真正对我们有所帮助。告诉我们您忘记在文档中涵盖的内容、缺少示例或迁移指南中的空白也非常感谢。

我们知道,某些移植工作将被 GTK 3 的间接依赖性所阻止。例如,如果您使用的是 webkit webview 或 GtkSourceView 或 vte,您可能会发现很难试用 GTK 4。

值得庆幸的是,其中一些库的移植工作已经顺利进行。其他库(例如libgweather)将需要一些工作来将其核心功能与 GTK 3 依赖项分离。

我可以帮忙吗?

如上一节所述,欢迎并有助于提供有关新 API、文档和移植指南的任何反馈。

还有许多其他领域我们可以使用帮助。如果您熟悉 OS X API,您可以在完成 macOS 后端方面发挥真正的作用。

我们还开始集成基于 ANGLE 的 GL 渲染器,但我们的着色器需要先使其与 EGL 一起工作,然后我们才能利用它。对此的帮助将不胜感激。

接下来是什么?

我们致力于在年底之前发布 GTK 4。从现在到那时,我们正在进行更多关于辅助功能后端的工作、改进 macOS 后端、编写文档和示例。

关于 GTK 4 中的列表的更多信息

上一篇文章介绍了在 3.98.5 版本中引入的整体列表视图框架,并展示了一些示例。在这篇文章中,我们将深入探讨一些技术领域,并查看一些相关主题。

GtkTreeView 的一个重要功能是它可以显示树。毕竟,这就是它的名字的由来。GtkListView 强调纯列表,并使处理这些列表更容易。特别是 GListModel API 比 GtkTreeModel 简单得多,这就是为什么自定义树模型实现相对较少,但 GTK 4 已经有了一个完整的列表模型动物园。

但我们有时仍然需要显示树。这方面存在一些复杂性,但我们已经找到了一种方法来做到这一点。

模型

我们需要的第一个要素是一个模型。由于 GListModel 表示项目的线性列表,因此我们必须更加努力地使其处理树。

GtkTreeListModel 通过添加一种展开项目的方式来实现这一点,即按需创建新的子列表模型。GtkTreeListModel 中的项目是 GtkTreeListRow 的实例,它们包装了模型中的实际项目。有一些函数,例如 gtk_tree_list_row_get_children() 用于获取子模型的项目,以及 gtk_tree_list_row_get_item() 用于获取原始项目。GtkTreeListRow 具有一个 :expanded 属性,用于跟踪当前是否显示子项。

树形列表模型的核心是 GtkTreeListModelCreateModelFunc,它接受列表中的一个项目,并返回一个新的列表模型,其中包含应为树中给定项目的子项的相同类型的项目。

这是一个 GSettings 对象的树形列表模型的示例。该函数枚举给定 GSettings 对象的子设置,并为其返回一个新的列表模型

static GListModel *
create_settings_model (gpointer item,
                       gpointer unused)
{
  GSettings *settings = item;
  char **schemas;
  GListStore *result;
  guint i;

  schemas = g_settings_list_children (settings);

  if (schemas == NULL || schemas[0] == NULL)
    {
      g_free (schemas);
      return NULL;
    }

  result = g_list_store_new (G_TYPE_SETTINGS);
  for (i = 0; schemas[i] != NULL; i++)
    {
      GSettings *child = g_settings_get_child (settings, schemas[i]);
      g_list_store_append (result, child);
      g_object_unref (child);
    }

  g_strfreev (schemas);

  return G_LIST_MODEL (result);
}

展开器

我们需要下一个元素是一个显示展开箭头的小部件,用户可以单击该箭头来控制 :expanded 属性。这由 GtkTreeExpander 小部件提供。正如 GtkTreeListRow 项目包装了模型中的底层项目一样,您可以使用 GtkTreeExpander 小部件来包装用于显示项目的小部件。

以下是树形展开器在我们的 GSettings 示例中的实际效果

完整的示例可以在这里找到。

排序

在离开树形结构之前要讨论的最后一个主题是排序。列表通常有多种排序方式:a-z、z-a、忽略大小写等等。列视图通过允许您将排序器与列关联来支持这一点,用户可以通过单击列标题来激活这些排序器。为此的 API 是

gtk_column_view_column_set_sorter (column, sorter)

在对树形结构进行排序时,您通常希望排序顺序应用于树中给定级别的项目,而不是跨级别应用,因为那样会打乱树形结构。GTK 使用 GtkTreeListRowSorter 支持这一点,它包装现有的排序器并使其尊重树形结构

sorter = gtk_column_view_get_sorter (view);
tree_sorter = gtk_tree_list_row_sorter_new (sorter);
sort_model = gtk_sort_list_model_new (tree_model,           
                                      tree_sorter);
gtk_column_view_set_model (view, sort_model);

总之,在新列表小部件中,树形结构的重要性有所降低,它们给机制增加了很多复杂性,但它们在列表视图和列视图中都得到了完全支持

组合框

使用单元格渲染器的更麻烦的领域之一是我们的单选控件:GtkComboBox。这从来都不是一个很好的选择,特别是与嵌套菜单结合使用时。因此,我们渴望在新列表视图机制上尝试 GtkComboBox 的替代方案。

从设计的角度来看,人们一直希望改进组合框,正如 2015 年的这个模型中看到的那样

五年后,我们终于有了替代小部件。它被称为 GtkDropDown。新小部件的 API 尽可能简单,几乎所有工作都由列表模型和项目工厂机制完成。基本上,您可以使用 gtk_drop_down_new() 创建一个下拉菜单,然后为其提供工厂和模型,就完成了。

由于大多数选择都由简单的字符串组成,因此有一个方便的方法可以从字符串数组为您创建模型和工厂

const char * const times[] = {
  "1 minute",
  "2 minutes",
  "5 minutes",
  "20 minutes",
  NULL
};

button = drop_down_new ();
gtk_drop_down_set_from_strings (GTK_DROP_DOWN (button), times);

此便捷 API 与 GtkComboBoxText 非常相似,并且 GtkBuilder 支持也非常相似。您可以在 ui 文件中指定字符串列表,如下所示

<object class="GtkDropDown">
  <items>
    <item translatable="yes">Factory</item>
    <item translatable="yes">Home</item>
    <item translatable="yes">Subway</item>
  </items>
</object>

以下是一些 GtkDropDown 的实际效果

总结

再次强调这一点:所有这些都是全新的 API,我们很乐意听取您关于哪些方面做得好、哪些方面做得不好以及缺少哪些方面的反馈。