实现 GTK+ 几何结构管理模型由两个阶段组成:
- 请求(requisition):在此阶段,容器遍历它的 child,并询问每个 child 所需的空间。
- 分配(allocation):在此阶段,为容器指定子矩形像素空间,并将其划分给它的 child。
要注意,这两个阶段都是递归的,有些 child 本身也是容器。
gtk.Widget(所有 PyGTK 部件的基类)的一些 foo 方法在内部调用虚方法 do_foo。目前,这一点没有明确整理成文档,但是本文和 小节中的例子应该清楚地展示了这一点。同样,我们的实现覆盖了其中一些 do_foo 虚方法。
请求 请求阶段通过以下方法实现:
清单 9. WTable.do_size_request1
2
3
4
5
6
7
8
9
10
11
12
13
14
| def do_size_request(self, oreq):
reqMgrs = (req.Manager(), req.Manager()) # (X,Y)
for child in self.gChildren:
request = child.widget.size_request() # compute!
for xyi in (X, Y):
be = child.lrtb[xyi]
glue1d = child.glue.xy[xyi]
sz = request[xyi] + glue1d.base()
rr = req.RangeSize(be, sz)
reqMgrs[xyi].addRangeReq(rr)
self.reqs = map(lambda m: m.solve(), reqMgrs) # (X,Y)
bw2 = 2 * self.border_width
oreq.width = min(sum(self.reqs[X]) + bw2, self.maxsize[0])
oreq.height = min(sum(self.reqs[Y]) + bw2, self.maxsize[1])
|
其他注意作为演示,这里忽略了很多可能的改进。例如,参数验证和 Python 的标准的、可选的方法字符串文档均被忽略。
类似地,我们并没有优雅地处理用户错误,例如检查无效的区间。
Glue 对象可以安全地被共享,因为 WTable 既不会扩大它们,也不会更改它们的值。
wGrow 权重值主要用于扩展给定的额外空间。对于像素不足的特殊情况,实际上需要元素有更大的 wGrow,从而缩小得更少。 因此,可以以递减函数的方式转换这些数字。而更聪明的 Glue 类可以有显式的缩小权重。
它通过 oreq.width 和 oreq.height 字段返回值。 reqMgrs 对象收集 child 所需的大小,然后调用类 req.Manager 的 solve() 方法。它确定并返回每个列和行所需的大小。现在,我们感兴趣的是通过 oreq 返回的总数。注意,即使我们得到每个列和行的大小的解决方法,需求可能是以行和列的区间 的形式给出的。区间可能包含不止一个列或一个行。
分配 在此阶段,您得到一部分珍贵的屏幕空间。这里所得到的分配是之前询问的空间请求和祖先(ancestor)容器策略(其中之一就是桌面窗口管理器,它可以以用户交互的方式调整顶级窗口的大小)的结果。
如果分配的空间刚好符合建议的 WTable 需求,那么只需按照前一步中的 req.Manager 类提供的方法在 WTable 的 child 之间分配空间。 否则,会得到额外的空间,或者出现像素不足,后一种情况较少见。在这两种情况下,都需要对差额进行分配。
glue 和 widget 的 wGrow 值现在被用作伪(pseudo)需求;可以以类似的方法收集和解决这些需求。这一次,单位不是像素,而是相对权重,它们的相对份额被用于扩展或缩小列和行(参见 )。
清单 10. WTable.do_size_allocate1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| def do_size_allocate(self, allocation):
self.allocation = allocation
allocs = (allocation.width, allocation.height)
alloc_offsets = (self.border_width, self.border_width)
self.crSizes = [None, None] # 2-lists: columns size, rows sizes.
self.offsets = [None, None] # alloc_offsets + partial sums of crSizes
for xyi in (X, Y):
a = allocs[xyi]
reqs = self.reqs[xyi]
gWeights = self._getGrowWeights(xyi)
self.crSizes[xyi] = given = self._divide(allocs[xyi], reqs, gWeights)
offsets = len(given) * [None]
offsets[0] = alloc_offsets[xyi]
for oi in range(1, len(offsets)):
offsets[oi] = offsets[oi - 1] + given[oi - 1]
self.offsets[xyi] = offsets
for child in self.gChildren:
self._allocate_child(child)
if self.flags() & GTK.REALIZED:
self.window.move_resize(*allocation)
|
对于清单 10 中的步骤,do_size_allocate() 方法:
- 确定每个维上的每个分段(列或行)的大小。将这些大小的部分和与 allocation.x 和 allocation.y 相加,便得到偏移量。
- 确定行和列的分配大小之后,就可以设置每个 child 的分配。同样要注意,一个 child 不一定就占用一个 “单元”,它可能占用由一个行和列区间 组成的一个矩形。
最后,调用 window.move_resize() 方法。注意,allocation 参数被加上 * 前缀,以利用 gtk.gdk.Rectangle 类提供的排序方法(参见 中的 fields 元素,它用于自动生成定义 PyGdkRectangle_Type 的 C 代码。这将 allocation 转换成与类 class gtk.gdk.Window 的 move_resize() 方法匹配的元组)。
表的空间分配划分 在前面的请求阶段中,确定了每个列和行所需的像素大小。现在需要划分一个给定的总分配。如果刚好与需求相符,那么可以直接使用它们,否则需要进行调整,加上或减去(较少见)额外的像素。
应该根据权重来划分差额。同样,用户不会显式地将权重赋给列和行,而是通过一个 “Glue” 将 wGrow 值赋给每个 child。这样做可以在 req.Manager 类的帮助下推算出所需的权重。
清单 11. WTable._getGrowWeights1
2
3
4
5
6
7
8
9
10
11
12
| def _getGrowWeights(self, xyi):
wMgr = req.Manager()
for child in self.gChildren:
be = child.lrtb[xyi]
glue1d = child.glue.xy[xyi]
rr = req.RangeSize(be, glue1d.total_grow_weight())
wMgr.addRangeReq(rr)
wMgr.solve()
gws = wMgr.reqs
if sum(gws) == 0:
gws = len(gws) * [1] # if zero weights then equalize
return gws
|
对于 “cake” 分配空间(清单 12),根据需求和权重对其进行划分,以划分差额。注意,当像素不足时,要对权重进行转换,这样较大的权重损失较小。另外,还需要注意全 0 权重的情况,这种情况被视作全等。
清单 12. WTable._divide1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| def _divide(self, cake, requirements, growWeights):
n = len(requirements) # == len(growWeights)
given = requirements[:] # start with exact satisfaction
reqTotal = sum(requirements)
delta = cake - reqTotal
if delta < 0: # rarely, "invert" weights
growWeights = map(lambda x: max(growWeights) - x, growWeights)
if sum(growWeights) == 0:
growWeights = n * [1]; # equalize
i = 0
gwTotal = sum(growWeights)
while gwTotal > 0 and delta != 0:
add = (delta * growWeights + gwTotal/2) / gwTotal
gwTotal -= growWeights
given += add
delta -= add
i += 1
return given
|
child 的空间分配 现在,列和行的分配已确定,并且用 crSizes 和 offsets 表示,接下来为每个 child 分配空间就很简单了。惟一要考虑的是为 child 提供的分配空间与它的需求之间的差额。
清单 13. WTable._allocate_child1
2
3
4
5
6
7
8
9
10
11
12
13
| def _allocate_child(self, child):
offsetsxy = [None, None]
req = list( child.widget.get_child_requisition() ) # pre-calculated
for xyi in (X, Y):
segRange = child.lrtb[xyi]
g1d = child.glue.xy[xyi]
supply = sum( self.crSizes[xyi][segRange[0]: segRange[1]] )
(oadd, cadd) = g1d.place(req[xyi], supply)
offsetsxy[xyi] = self.offsets[xyi][ segRange[0] ] + oadd
req[xyi] += cadd
allocation = gdk.Rectangle(x=offsetsxy[0], y=offsetsxy[1],
width=req[0], height=req[1])
child.widget.size_allocate(allocation)
|
为了确定 child 与边距要增长(或缩小)多少,使用 Glue1D.place 方法。对于每个 child,该方法被调用两次:每个 (X,Y) 维调用一次。对于一个给定的维,有 3 个 wGrow 值要考虑:两个边(左和右,或者上和下)和 child 值本身。
清单 14. Glue1D.place1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| def place(self, cneed, supply):
pads = self.base()
need = cneed + pads
delta = supply - need;
if delta >= 0:
gwTotal = self.total_grow_weight()
oadd = self.springs[0].pad
if gwTotal == 0:
cadd = delta
else:
oadd += round_div(delta * self.springs[0].wGrow, gwTotal)
cadd = round_div(delta * self.wGrow, gwTotal)
else: # rare
shrink = -delta
if pads >= shrink: # Cutting from the pads is sufficient
oadd = round_div(self.springs[0].pad * delta, pads)
cadd = 0
else:
oadd = 0
cadd = delta # reduce the child as well
return (oadd, cadd)
|
Glue1D.place 方法返回整数 (oadd,cadd) 的一个二元组。
- oadd 被加到偏移量中。同样,这意味着第一个边距(左边距或上边距)将增长多少。
- cadd 被加到 child 的 widget 分配空间(宽度或高度)中。
它使用前面提到的 total_grow_weight() 方法和下面的 round_div 方法(参见 中的 PEP 238):
清单 15. round_div1
2
| def round_div(n, d):
return (n + d/2) / d
|
|