LCUI 0.12.3 开发日志

发表于2012年01月22日

之前的部件只有Label、PictureBox和Button三个,也该考虑添加新部件,完成了下拉菜单、列表框、文本框、滚动条和进度条,就可以写文件管理器。

进度条最容易实现,其次是滚动条,下拉菜单、列表框和文本框这三个有点难度。

经过仔细研究系统显示的下拉菜单的效果,费了1周左右的课余时间,算是实现了下拉菜单,支持多级菜单,一个按钮点击后,可以显示一个菜单,这个菜单显示的位置,由相关函数根据按钮的位置和尺寸以及菜单的尺寸来确定菜单显示位置。

显示菜单,我用了新窗口,往菜单里添加选项,就相当于往窗口里添加部件,在添加的同时,相关代码会根据窗口内的各个部件的宽度,获取最大宽度,之后,调整每个部件的宽度,使之一致,窗口的宽度和高度也会调整。

菜单的显示位置,如果超出了屏幕范围,就调整位置,使菜单内在屏幕有效范围内显示。

由于菜单使用的是窗口,但又与普通窗口不一样,普通窗口全部关闭后,菜单的窗口也需要关闭,否则,程序就不会退出。

我在结构体中加了个变量,用于区分这两种窗口,当普通窗口全部关闭后,自动关闭菜单窗口,释放占用的内存资源。

可是,完成这些功能后,又出现问题了: *** glibc detected *** double free or corruption (out): 0xXXXXXX

问题出在 释放 FreeType2打开字体文件后 保存的 相关数据 占用的内存资源,字体文件只打开过一次,退出LCUI时,只释放一次,为什么在添加下拉菜单后就出问题了呢?

我处理问题有两种方法:

  1. 调试程序,分析代码,查错误。
  2. 修改程序的主要代码,改变程序的数据处理方式,以与之前使用的方式不一样的另一种方式工作,相同的结果,可以有不同的实现方法,新的方法很有可能不会出现前一种方法所出现的同一个错误,这样就可以避免前一个方法产生的错误。

有时嫌第一种方法太麻烦,太耗时间和脑力,就考虑第二种方法,思考新的程序运行方案,当然,至少要比之前采用的方案好,否则不就是写垃圾代码吗?

考虑到以后能够让程序使用自己添加自定义部件,部件的结构体需要进行修改,大致如下:

添加一个widget结构体指针和一个widget队列,前者保存父部件指针,后者保存多个子部件的指针。

添加一个void型指针,名为private,用于引用自定义部件自己的数据结构体。

App结构体(储存程序相关数据)也要进行修改,window不再是所有部件的容器,它属于widget类。

添加一个widget类型管理系统,能保存处理该widget的相关函数指针、字符串和所属程序,要有几个函数用于对它进行管理。

大部分代码需要进行修改,减少冗余代码,总代码量会减少。

对于部件的处理,先从部件库中搜索对应类型的部件,之后获取该类型相关的函数指针,运行之。

部件相关的处理大致是这样:

处理类型      相关函数 
创建并初始化  Create_Widget()
图形更新      Update_Widget()
尺寸改变      Resize_Widget()
显示          Show_Widget()
隐藏          Hide_Widget()
销毁          Destroy_Widget()

而这些函数大致的工作流程基本一样:先进行所有部件都需要进行的相关操作,之后,从部件库中根据部件类型来搜索对应函数指针,得到函数指针后,就执行它,参数就是部件的LCUI_Widget结构体指针。例如:

以上进行操作的函数,只是将函数指针和参数发送至程序的任务任务队列,让程序在主循环里处理这些任务。

添加了“容器”功能,可将一个部件作为另一个部件的容器,例如:将窗口作为窗口内部件的容器。

之前写的代码只是为了完成功能而写,有些功能可以用更好的方法来实现;

花了几天时间,鼠标事件机制经过了修改,可关联鼠标事件,鼠标事件有两个:Click和Move,前者是在鼠标按键按下或者松开后触发,后者是在鼠标移动时触发,触发后,LCUI会将与事件关联的函数指针发送至程序的任务队列中,供程序在主循环中处理,而传递给函数指针指向的函数的参数,是Mouse_Event结构体指针,里面记录了鼠标按键状态、鼠标指针覆盖到的部件的指针 以及 鼠标指针的全局位置和相对位置。

添加了几个函数,用于判断鼠标左右键的状态。

添加了一个队列,用于保存已经按下的按键的键值,在按下鼠标左键后添加该键值至队列,当释放左键后,从队列中移除左键的键值,移除成功就触发鼠标左键点击事件,失败则表明左键没有按下。如果从队列中移除键值失败,则不触发click事件,因为不存在这个键值,说明之前没有按下这个键。

click事件,可得知按键的两个状态:按下、释放,也可得知按键的键值,如果这个键没有按下,就不会触发click事件。

与click事件关联的回调函数,在按键被按下时和释放时才调用,传递给它的参数是LCUI_Mouse_Event类型,回调函数可以根据这个参数得知某个按键是否按下/释放;参数中还包含当前鼠标指针覆盖到部件的指针。

函数的递归也用了,其实作用还蛮大的,例如:在部件内寻找与指定区域重叠的最顶层的部件,父部件中有子部件,子部件中可以有子部件,而子部件中还可以有子部件,可以变得很深,简单的用几个for循环是无法遍历完每个子部件的指针的。

话说回来,根据以上的描述,好像不知不觉中用了链表,虽然之前没学过链表,但是,不完全像链表,只是结构有点像。。。貌似又是N叉树,主分支里有多个分支,分支里再分支。。。

几天时间已经过去,代码终于修改完成了,上述的功能也已经实现,调试和分析问题花费了绝大部分时间,由于本人的头脑有点迟钝,因此浪费了过多的时间。

现在,需要考虑图形显示的优化,之前测试窗口的移动的结果还不怎么满意,有些地方还是需要改一下,例如:

图层的合成,这个还可以优化,节省更多的耗时;

局部区域刷新,刷新多个区域时,由于这些区域之间有些会有重叠的区域,而处理时,是把重叠的区域重复刷新了多次,这个没必要,浪费时间!具体看下图所示:

processing_rects

图中所示的是:

将4个重叠的区域处理成5个不重叠的区域,这样就可以减少不必要的时间浪费了,具体算法目前还在思考中。。。

对于多个重叠矩形的处理,在添加矩形时就应该处理掉被多个矩形重叠的区域,具体如下:

每添加一个矩形时,先将它与队列中的各个矩形对比,对比结果有几种:

  1. 不重叠,继续与下个矩形对比。
  2. 被其中一个矩形包含,不添加该矩形至队列,直接退出函数。
  3. 包含其中一个矩形,将这个矩形从队列中移除,继续与下一个矩形对比。
  4. 与其中一个矩形有重叠,先裁剪矩形区域,然后将不重叠的部分分成两个矩形,并再次调用本函数以添加至队列,会用到函数递归调用;

重叠矩形区域的裁剪有点难度,需要纠结一段时间。

最近的几次测试,问题比较多,这个功能暂时先放着不用,以后再来做。

好的程序是调试出来的!

在与BUG斗争的这段时间,本人深有体会,gcc提示的那些错误倒是不需要管,我通常是先想好思路,之后用代码描述出来,盼着就是编译时gcc给的错误/警告信息,根据这些来对代码进行细节上的修改,这其实也不难应付,怕的就是程序运行过程中因逻辑错误而导致的段错误,以及意料之外的程序运行结果;

调试是少不了的,gdb我也就只会用break设定断点,back查看程序之前调用了哪些函数,step慢慢执行代码并核对运行结果,finish跳出当前函数,watch查看变量的变化。

起重大作用的还是printf函数,调试时都是在某个的位置添加printf函数,输出变量的内容,查看变量是否正常。

局部区域刷新功能还是需要完善,在绘制父部件中的子部件时,不仅要得出子部件在该区域内的有效显示区域,还要得出在父部件区域范围内的有效显示区域,之后,根据这两个区域得出子部件实际要裁剪的图形区域。这是为了避免子部件超出父部件的范围时,还能将子部件显示出来,大致处理方式如下图所示:

area_refresh

之前测试按钮部件时,按钮尺寸为0×0,而按钮中的label部件却完全显示出来了。

有了个想法:

将结构体的定义隐藏,不能直接访问结构体的成员变量,只能通过函数库提供的函数来获取,这样,每次修改数据结构后,之前编译的程序就不会因为数据结构改变而导致程序无法正常运行了,省去了重新编译程序的麻烦,这想法是解决libpng的一些问题而产生的,因为新版本的libpng隐藏了结构体,不能直接访问结构体中的成员。但是,这只是想法,暂时不会去实现。

最近发现一个问题,新版的png库不兼容那些使用旧版png库的程序。

没有更新png库时,我将一个可以创建png图片文件的代码整理进我的LCUI的代码里,想创建文件,直接传文件路径和图片数据结构体进去,函数就会创建一个png图片。可是呢,电脑上编译了最新的png库源码,安装之,再次使用相关函数来创建png图片文件时,创建出来的图片内容有问题,几乎是全黑的,仔细看官方介绍时发现他发布了两个版本,一个是1.5.10,一个是1.2.49,为兼容那些使用老版png库的程序,可以下载libpng-1.2.49。

测试Button部件时,又发现一个问题:

在鼠标移到按钮上的时候,该改变谁的状态?

理论上,应该改变按钮的状态,可实际上,鼠标指针是覆盖在按钮里的label部件上,改变的是按钮里的label部件的状态。

按钮的各种状态的切换遇到了一些麻烦,应该添加一个回调函数,用于鼠标跟踪,跟踪的目的,是用于处理按钮的几种状态的切换,大致处理流程是这样的:

如果这个部件不是按钮,并且有父部件,那么就一直往上遍历这个部件的父部件,直到父级部件是按钮为止,如果这个部件的父级部件没有一个是按钮,那就算了。

Button和PictureBox这两个部件已经完成了,开始添加进度条部件!

测试动态改变label部件的文本内容时发现一个问题:

我创建一个线程,用于动态改变label部件的文本内容,可是,由于搜索不到线程ID,确定不了程序数据结构体指针,导致往程序任务队列添加任务出错。

该添加线程管理功能了,我用了几个结构体,用于储存这些线程ID以及关系,与其说是链表,不如说是一个“树”,根线程只有一个,也就是第一个初始化LCUI时的程序的主线程ID,线程可以有子线程,这样就会产生很多分支,每个结点的分支数量可能不一样。

由于LCUI还未实现多进程通信,只能靠线程ID来区分哪个操作是哪个程序的,目前打算是将程序以线程的形式运行,而不是以进程的形式运行,主线程为运行平台,其它程序就是该平台上运行的子线程,虽然能实现数据的交换,但是,一旦某个程序出现错误,整个进程就会退出。

在使用LCUI提供的线程相关的函数时,会对线程关系树(我觉得叫这个名字好一些)进行相应操作,例如:

创建线程,就会先得到创建时的父线程ID,搜索这个树中的每个结点,如果有匹配的线程ID,就将这个子线程ID加入这个父线程的子线程队列中;如果没有匹配的,那就新增一个至主队列中,对于处理这种数据结构,用函数递归来处理还是比较方便的。

而撤销线程,也类似,先查找,后移除。

创建线程,撤销线程,阻塞等待线程退出等功能的函数,主要是调用了pthread.h头文件中声明的函数来实现的,这些函数只是顺便对线程树中的数据进行修改而已。

在使用我定义的LCUI_Thread_Exit函数时,编译器会提示它所在的函数需要有返回值,看了pthread_exit的函数声明,原来在函数声明后面加上:attribute ((noreturn));即可让编译器给出的这条警告消失。

在这里只是大致的描述LCUI的设计思路,具体可以等待以后的LCUI的源码公开时,参考源代码。

进度条完成了,测试时,发现图像缩放函数有点小问题,小图并不能完全拉伸,拉伸后的图形边缘部分还有混乱颜色。

效果如下图所示:

progressbar

共两种风格:带动态效果的,和经典风格。

动态效果的,就是图中绿色的进度条,里面有个高亮区域,它会自己移动,就和win7里的进度条一样,进度条中有闪光从左至右移动。

该部件可以设定闪光的移动速度,以及它本次移动完后到下次从头开始移动的间隔时间。先设定进度条最大值,再设定当前值,槽中的进度条就会变成相应长度。

又做了个启动界面,有动画,如下图所示:

loading

以后有的程序可能需要这个,在启动时,先显示动画,等待程序初始化完成。

中间一个圈,里面是字母LC,LC也就是我名字的缩写。

起初是用雪花动画,和QQ2012登录时显示的动画一样,其实就是用了QQ安装目录里的图片。但后来就把动画改成了旋转中的绿圈,围绕LC标志旋转,使用了图形旋转算法,可是,这个程序在学习机端跑很卡。

以下是最近写的测试程序显示的效果,用于测试LCUI的基本部件:

new_helloworldlabel

为了实现图形旋转算法,参考了一些与图形旋转相关的资料,最终参考了这里的代码,以这代码为基础,修改了一下。

测试结果如图所示:

image_rotate_beforeimage_rotate_after

旋转后,不平滑,还需要完善。

既然有了图形旋转算法,那就可以做一个时钟了:

clock_widget

用了Go桌面的时钟部件安装包里的图形素材,尺寸有点大,也就是根据时间来计算指针角度。

为了以后的源代码开放,开始研究如何用automake创建共享库。

我参考了Automake相关文章:

  1. Automake中文手册
  2. Linux下Makefile的automake生成全攻略
  3. 用automake建立共享库(动态链接库)Makefile
  4. 使用 GNU Libtool 创建库

最终算是完成了LCUI的Makefile创建,这样,以后发布LCUI的源代码,只要用cd命令进入源码根目录,./configure运行congfigure脚本,进行环境检测,检测通过后, 用make命令将项目源码编译, 用make install将已经编译的项目安装。

如果你想用Automake制作自己的项目的Makefile,可以参考我的源码包里的相关文件。

该添加具体日期了,看看进展。

2012-4-25

为了这个开源项目有个好的主页,最近花了一点时间重新修改了网站的网页,网页的某些效果的实现,借用了一些网站的网页源码并稍加修改,项目的主页:http://lcui.sourceforge.net/

http://savannah.gnu.org注册了账号,但是,网页时英文的,还不知道怎么用它。

2012-4-26

在对菜单部件进行调试的过程中,发现了一些BUG:

  1. 程序主循环中,每次都要处理每个部件中的矩形数据队列,不管这个队列中是否有成员,都会进行全面扫面,浪费了时间,降低了程序效率;解决方法是:添加一个变量作为标志,如果往部件添加矩形数据,这个标志值被改变,等到处理程序的每个部件的矩形数据时,就会根据这个标志的值来判断是否需要进行处理,处理完后,该标志复位。
  2. 改变部件的容器时,由于部件创建时默认容器是屏幕,部件的指针保存在程序的部件队列中,又由于相关功能的函数只在它的父部件有效时才转移它之前的容器中的数据,

因此,改变部件的容器后,程序的部件队列中残留了该部件的数据,导致了处理时出现的错误;解决方法是:在不满足父部件指针有效时,就将它在程序的部件队列中的数据移除。

2012-5-1

发现了几个BUG:

  1. 鼠标指针在label部件上左右移动时,有时会把label部件的图形数据抹去。问题原因是判断矩形重叠时出了点问题,假设有矩形A(2,5,183,18), 矩形B(170,4,12,19),如果传递给函数的参数顺序是矩形A和矩形B,结果是重叠;如果是矩形B和矩形A,结果却是不重叠。
  2. 部件图形的叠加有不足之处,只考虑到子部件不在父部件的有效显示范围内需进行的裁剪,由于部件可以层层嵌套,有时部件会超出容器显示范围,需要进行裁剪,如果只考虑子部件和上一级容器,没考虑到该容器的上级容器,这会造成部件图形显示出问题。
  3. 测试照片查看器时,效果不怎么让人满意,尤其是图像缩放的效果,看来,不能与LCUI的项目源码同时发布了,只有等下个版本的LCUI。

LCUI 0.12.3版本的源代码已公开,遵循GPLv2开源协议。