7.1.9 移动窗口

对于一个典型的桌面应用来说,用户可以通过拖动窗口的标题栏来移动窗口。这里所谓的“拖动”的具体动作是:在标题栏上按下鼠标左键并保持,然后移动鼠标,直到释放鼠标。

因此,整个移动窗口的过程可以划分为三个阶段:

1)用户在标题栏上按下鼠标左键并保持;

2)用户移动鼠标;

3)用户释放鼠标,移动结束。

为此,结构体Client中设计了布尔变量moving,当用户在标题栏内按下鼠标左键时,moving将被置为True。当鼠标移动事件发生时,如果变量moving为True,那么我们就可以断定用户是在移动窗口。一旦用户释放了鼠标,winman将moving更改为False。

1.按下鼠标

一旦收到鼠标按下事件,winman首先要找到鼠标事件发生在哪个窗口对象上,winman中封装的函数wm_find_client_by_frame就是做这件事的。找到了具体的窗口对象后,则调用这个窗口对象的指针button_press指向的函数,代码如下:

7.1.9 移动窗口 - 图1

我们还是以标准窗口对象为例,其处理鼠标按下事件的函数是normal_client_button_press,代码如下:

7.1.9 移动窗口 - 图2

7.1.9 移动窗口 - 图3

函数normal_client_button_press检查鼠标事件中的成员subwindow是否是标题栏,如果是,则设置窗口对象的moving为True。读者可能有个疑问,如果用户只是虚晃一枪,马上又释放了鼠标呢?那也没有什么影响,因为winman在鼠标释放的事件处理函数中,将把这个值重置为False。

同时,函数normal_client_button_press将鼠标事件发生的位置记录到窗口对象中的anchor_x和anchor_y中。注意,winman记录的位置使用的是窗口内部坐标,是相对于窗口原点的。后面将以这个点为参考,计算窗口移动后的新位置。

2.移动鼠标

在收到鼠标移动通知后,winman首先还是要找到具体的窗口对象,然后调用这个窗口的函数指针motion指向的函数,代码如下:

7.1.9 移动窗口 - 图4

以标准窗口对象为例,其处理鼠标移动事件的函数是normal_client_motion,代码如下:

7.1.9 移动窗口 - 图5

7.1.9 移动窗口 - 图6

函数normal_client_motion首先检查窗口对象中的变量moving。如果moving为True,则说明用户正在拖动标题栏。因此,其根据当前的鼠标所在的位置以及在鼠标按下时所在的位置,即窗口对象中的变量anchor_x和anchor_y,计算出窗口新的位置,具体的计算方法参见图7-11。然后调用Xlib的函数XMoveWindow移动窗口。

7.1.9 移动窗口 - 图7

图 7-11 窗口移动位置计算

看过上面的实现,读者可能会有个疑问:一定要设置moving吗?不可以在移动时根据事件中的子窗口(subwindow)是否是标题栏来判断是否是在拖动标题栏吗?即将下面的语句:

7.1.9 移动窗口 - 图8

更改为:

7.1.9 移动窗口 - 图9

答案是不可以。原因是,如果鼠标移动较慢,那么在移动时鼠标指针会一直落在标题栏中,这没有问题。但是如果用户移动得非常快,窗口移动的速度跟不上鼠标,这时鼠标所在的子窗口,即鼠标移动事件中的成员subwindow,或者是0,或者是其他窗口,总之,不再是标题栏了。

3.释放鼠标

在收到鼠标释放事件后,winman首先找到具体的窗口对象,然后调用这个窗口的函数指针button_release指向的函数,代码如下:

7.1.9 移动窗口 - 图10

以标准窗口对象为例,其处理鼠标释放事件的函数是normal_client_button_release,代码如下:

7.1.9 移动窗口 - 图11

当鼠标释放时,表明用户已经停止移动窗口,winman将窗口对象中的moving标志清除。