我的C語言學習歷程:GUI篇

注:

LCUI項目主頁:http://lcui.org 

開發日誌在主頁上,此CSDN博客的內容將不再更新。



編寫自己的GUI圖形庫

遊戲的編寫已經在N個月前暫停,因爲我又有了個想法:自己寫個GUI庫,這樣,以後寫圖形界面的程序就方便多了,不必每寫個程序就要爲它的圖形界面再寫代碼,直接寫個通用的API就可以了。

因爲在學習機端還沒有能用的GUI庫,Qt呢,其他編譯過Qt程序的機友說在顯示父窗口時會出現段錯誤,能用的就是MessageBox來顯示界面。

而GTK,現在倒是有人編譯成功了,並且編譯了能在學習機上運行的GTK版的QQ客戶端,可是屏幕小,顯示的QQ界面只有一部分,以下是截圖:


這是帖子的鏈接:http://club.noahedu.com/thread-126747-1-2.html

文字顯示成框框,是字體庫的問題,貌似後來解決了。



就這樣,我決定編寫一個適合學習機的GUI圖形庫,這個圖形庫我命名爲LCUI,LC就是我的名字拼音LiuChao的首字母,UI即User Interface(用戶界面)的簡稱。

目前只添加了Picture_Box部件、Label部件,這些部件的名稱、功能,仿照了VC的工具箱中的窗口控件。

頭文件的編寫,我先仿照了FreeType2的頭文件編寫風格,但是,遇到了一些問題,頭文件包含頭文件,循環包含,最終編譯器編譯時一直在循環打印同一個錯誤,關於這個問題,解決方法是使用宏進行頭文件保護,提問的帖子在這:http://topic.csdn.net/u/20111108/14/0247823b-5c99-47da-86b2-aa20640b2580.html

在後來,參考了gtk的頭文件,還是把我的頭文件中的結構體的定義方式改了一下。


圖形的顯示,不再是利用mgaview程序源碼中的write_to_fb函數顯示圖形了,我從裏面直接提取出核心代碼,做了修改,用這個代碼自己操作framebuffer。


窗口的繪製代碼還是比較簡陋,能用就行了,以後再完善一下。


這個是Label部件的使用效果圖:


使用的是微軟雅黑字體,字體圖形的獲取,我用了FreeType2提供的API,這個FreeType2我折騰了很久,還是沒有解決文字對齊問題,網上也沒有相關的代碼,都是清一色的獲取單個文字字形的示例代碼;實現這個窗口的源代碼在這裏:http://blog.csdn.net/liuchao35758600/article/details/6953328

現在倒是解決了文字對齊問題了,可是獲取宋體字體的漢字位圖出了問題,不能顯示漢字,漢字是亂點。

如果有使用過FreeType2的朋友,請提供聯繫方式,想交流一下相關的問題。


這個是Picture_Box部件的使用效果:


Picture_Box部件的尺寸我設置爲最大,也就是窗口內的整個區域,顯示的圖片爲居中顯示,這裏有源代碼:http://blog.csdn.net/liuchao35758600/article/details/7023631

這裏也有:http://lcui.sourceforge.net/bbs/forum.php?mod=viewthread&tid=9&extra=page%3D1

即使是圖片尺寸超過窗口尺寸,也能顯示,只不過是顯示一小塊區域,按方向鍵移動區域,查看其它內容。

圖片的縮放功能還未添加,添加後,可以進行圖片的放大和縮小的操作。


由於Picture_Box部件的實現,我又想做個圖片查看器,這個是現在的效果圖:

使用了Label部件在窗口內的左上角顯示圖片信息,2秒後,逐漸減少Label部件的透明度,直到0(完全透明),方向鍵移動瀏覽範圍,i鍵調出圖片信息標籤(不透明)。

還不能放大和縮小圖片,因爲沒有爲PictureBox部件加入圖像縮放算法。


以上是0.10.0 至 0.12.0版的LCUI圖形庫的編寫過程,沒做過多的說明。

由於閒着蛋疼,在sourceforge.net上搭建了一個自己的網站,並且還建了個論壇,主頁地址:http://lcui.sourceforge.net/

主頁的內容幾乎沒有,網頁的源代碼是從其它網站上抄下來的,自己修改,看到中意的地方就把相關網頁代碼複製過來,然後組合。

網頁html源碼通過w3c驗證,無任何錯誤和警告,但是,css就不保證了。



第1.2.1版,目前正在編寫中,修改了數據類型,什麼Label部件PictureBox部件等,都改成用LCUI_Widget結構體儲存,而不是之前單獨的結構體。

由於數據結構體的改變,各個函數就需要進行相應的修改,數據的處理方式也要改,代碼修改量比較大,花費了幾天的時間來測試、糾錯、完善,終於完成了。

label部件的字體處理做了優化,頻繁打開關閉字體文件來獲取每個字的位圖,效率很低,於是我將字體相關的數據用結構體保存,字體文件的句柄也保存在結構體中;

第一次打開字體文件,獲取完字體後,不會關閉字體文件,這是爲了在下一次獲取字體位圖時能夠跳過打開文件這個步驟,節省時間。

局部刷新機制也做了修改,針對label部件的文本內容的變動,先遍歷原有的文本與新的文本內容進行對比,如果有改動,就重繪這個字體,並將這個字體所在的區域記錄下來,等待局部刷新之,比起之前的處理方式,效率相對來說提高了一些,至少不會因爲改動幾個字或者新增幾個字而全部重繪。


窗口還是矩形窗口,想實現圓角窗口,根據圓的方程,寫了個矩形圓角化的算法,可是,邊緣沒有線條,理論上圓角化的同時會繪製邊緣線,可是實際效果並沒看到邊緣線,暫時先不管了,還是繼續編寫主要功能的代碼。


一個圖形界面,鼠標操作是少不了的,源代碼比較好找:http://blog.csdn.net/liuchao35758600/article/details/7173458

獲取的只有鼠標左右鍵的狀態,不能獲取鼠標滾輪的狀態,以後需要這個時再找解決方法。


能支持鼠標了,我就加了個基本功能:拖動窗口移動,爲了實現局部刷新,又寫了個算法,爲了解決如下圖所示的問題:


根據矩形的座標尺寸以及移動至的新位置的座標,得出需要刷新的兩個矩形AB,刷新這兩個區域的圖形就可以抹去殘餘圖形了。

現在貼出適用於1.2.1版LCUI庫的helloworld源碼:

#include "../include/LCUI.h"

void Exit_LCUI(void *in)
{
	LCUI_Window *win_p = (LCUI_Window*)in;/* 轉換數據類型 */
	Close_Window(win_p); /* 調用Close_Window函數關閉窗口 */
}

int main(int argc,char*argv[])
/* 主函數,程序的入口 */
{
	LCUI_App     this;		  /* LCUI程序 */
	LCUI_Widget  *label;	  /* 使用指向widget部件的指針 */
	LCUI_Window  *main_window;   /* 使用指向窗口的指針 */
	int           width, height; 
	/* 初始化LCUI */
	LCUI_Init(&this); 
	/* 設定默認的字體 */
	Set_Default_Font("../fonts/msyh.ttf"); 

	/* 創建一個LCUI程序窗口 */
	width  = 180; /* 窗口的寬度 */
	height = 110; /* 窗口的高度 */
	
	/* 創建一個窗口,並獲取指向窗口數據的指針 */
	main_window = Create_Window(&this, width, height); 
	Set_Window_Title_Text(main_window, "Hello World!");
	/* 在窗口內創建一個label部件 */
	label = Create_Widget(main_window, LABEL);

	/* 更改label部件中的文本內容 */
	Set_Label_Text(label, "Hello World! \n測試一下!這是中國字! \nabcdefghijklmnopqrstuvwxyz\n1234567890\n!@#$%^&*()_+{}|:\"<>?");
	
	Show_Widget(label); /* 顯示部件 */
	Show_Window(main_window); /* 顯示窗口 */
	/* 將返回鍵與Exit_LCUI函數關聯,當返回鍵被按下後,程序退出 */
	LCUI_Key_Event_Connect(main_window, KEY_ESC, Exit_LCUI, (void*)main_window);
	LCUI_Main(&this); /* 進入主循環 */
	return 0;
}
在學習機端的效果圖如下:


左邊是Qt的鼠標指針,右邊是我的LCUI圖形庫的鼠標指針。

鼠標指針的移動距離是2倍,最小移動距離是2個像素點。


學習機上測試效果還不怎麼滿意,程序從啓動到顯示界面耗時比較長,2秒以內,估計是字體處理拖慢了處理速度,需要進一步優化。


起初,將窗口移動至屏幕以外的區域時,顯示會出問題,於是,將圖形寫入至幀緩衝時要考慮到是否超出顯示範圍,超出範圍的就進行裁剪。


使用的是分別指向窗口和部件的結構體的指針,直接使用結構體會有問題,因爲窗口和部件的結構體數據不是在main函數中保存的。
目前實現了LCUI_Key_Event_Connect函數,原型爲:

int LCUI_Key_Event_Connect(LCUI_Window *win_p, int key_value, void (*func)(void*), void *arg);
主要功能就是將對應鍵值的按鍵產生的事件與函數關聯,當按下被設定的鍵值對應的按鍵後,觸發相應事件,LCUI_Main函數就會調用與該事件關聯的函數,並將之前保存的參數傳過去。
參數只能是一個void*類型的變量,和pthread_create函數原型類似,想要多個不同類型的參數,就需要自己寫個結構體,包含這些變量,之後轉換成void*型,在需關聯的函數裏,把void*型參數轉換成原來的類型。


還有個LCUI_Widget_Event_Connect函數,原型爲:
int LCUI_Widget_Event_Connect(LCUI_Widget *widget, int event, void (*func)(void*), void *arg);

這個是將某個窗口部件產生的事件與函數關聯,比如:
將Exit_LCUI函數與按鈕部件的clicked事件關聯,當鼠標左鍵點擊這個按鈕部件並鬆開後,就會調用Exit_LCUI函數,關閉窗口,並退出LCUI。


功能是這樣的,但相應的處理功能沒完成,窗口沒有添加關閉按鈕,不能靠鼠標關閉窗口。


算是模擬實現了Qt的connect函數的一些功能,至少能滿足現在的需求,之前爲了模擬實現Qt的connect函數,發了個帖子:

http://topic.csdn.net/u/20120115/12/b50cac9e-5f4f-44da-adf8-6e51a87191ce.html

本來是想能夠接受多個參數的,現在暫時只能用一個void*型的變量作爲參數,打算讓LCUI_Key_Event_Connect函數和LCUI_Widget_Event_Connect函數支持可變參數,這樣,與事件關聯的函數的參數就不必是void類型變量,也不必是限制爲1個參數。




需要有一個字體數據隊列來保存已打開的字體文件,避免重複打開同一字體文件。

要有隊列處理函數,用於處理窗口顯示順序隊列、部件顯示順序隊列,具備新增、刪除隊列成員的功能。


經過2天的奮鬥,終於完成了Button部件,能在窗口中使用按鈕了。完成了LCUI_Widget_Event_Connect函數,LCUI_Main函數中,添加了對部件事件的處理。

中途修正了部分代碼的邏輯錯誤,糾正了隊列處理函數出現的錯誤,這個隊列處理函數,用於處理程序窗口的顯示順序、窗口內部件的顯示順序以及部件事件註冊、部件觸發的事件,雖然每種隊列的隊列成員類型不一樣,但大致的處理代碼是一樣的。
現在的按鈕繪製得很簡陋,但大致的處理框架已經完成,想要好一點的按鈕,只需要調整一下按鈕繪製函數繪製的圖形內容即可。

程序源代碼如下:

#include "../include/LCUI.h"

void Exit_LCUI(void *in)
{
	LCUI_Window *win_p = (LCUI_Window*)in;/* 轉換數據類型 */
	Close_Window(win_p); /* 調用Close_Window函數關閉窗口 */
}

int main(int argc,char*argv[])
/* 主函數,程序的入口 */
{
	LCUI_App     this;		  /* LCUI程序 */
	LCUI_Widget	 *label, *button; /* 使用指向widget部件的指針 */
	LCUI_Window	 *main_window;   /* 使用指向窗口的指針 */
	int			 width, height; 
	/* 自定義默認字體文件位置 */
	Set_Default_Font("msyh.ttf");
	/* 初始化LCUI */
	LCUI_Init(&this);  

	/* 創建一個LCUI程序窗口 */
	width  = 240; /* 窗口的寬度 */
	height = 180; /* 窗口的高度 */
	
	/* 創建一個窗口,並獲取指向窗口數據結構體的指針 */
	main_window = Create_Window(&this, width, height); 
	Set_Window_Title_Text(main_window, "Hello World!");
	/* 在窗口內創建一個label部件 */
	label = Create_Widget(main_window, LABEL);
	button = Create_Button_With_Text(main_window, "退出");
	Resize_Widget(button, 80, 30);
	/* 更改label部件中的文本內容 */
	Set_Label_Text(label, "Hello World! \n"
	"測試一下!這是中國字! \n"
	"彩色文字:紅色,綠色,藍色\n"
	"現在的LCUI創建的窗口可以使用按鈕了!\n"
	"雖然按鈕繪製得比較簡單,\n但大致的框架已經完成!");
	/* 爲label部件中指定區域的文本設定顏色 */
	Set_Label_Text_Color(label, 3, 5, 7, Value_To_Color(255,0,0)); 
	Set_Label_Text_Color(label, 3, 8, 10, Value_To_Color(0,255,0)); 
	Set_Label_Text_Color(label, 3, 11, 13, Value_To_Color(0,0,255)); 
	/* 改變部件位置 */
	Set_Widget_Position(label, 5, 5);
	Set_Widget_Position(button, Get_Window_Center_X(main_window) - button->width/2, 115);
	/* 顯示部件以及窗口 */
	Show_Widget(label); 
	Show_Widget(button);
	Show_Window(main_window);
	
	/* 將返回鍵與Exit_LCUI函數關聯,當返回鍵被按下後,程序推出 */
	LCUI_Key_Event_Connect(main_window, KEY_ESC, Exit_LCUI, (void*)main_window);
	/* 將按鈕的左鍵單擊事件與Exit_LCUI函數關聯,當按鈕被按下後,程序推出 */
	LCUI_Widget_Event_Connect(button, Clicked, Exit_LCUI, (void*)main_window);
	
	LCUI_Main(&this); /* 進入主循環 */
	return 0;
}


這是效果圖:


label部件支持彩色文本顯示,部件共有6種事件:

Normal 部件由其它狀態切換至普通狀態時,纔會觸發該事件。
Clicked 當部件被點擊,並且在該部件上釋放按鍵,該事件會被觸發。
Over 當鼠標覆蓋在該部件上的時候,該事件被觸發。
Down 部件被鼠標點擊,但按鍵未被釋放,處於按住狀態,該事件被觸發。
Focus 部件處於焦點狀態,該事件被觸發。(有待完善,有的時候可以和其它狀態重疊)
Disable 部件未被啓用,該事件被觸發。

Button部件在創建的時候就已經註冊了這6個事件,與這些事件關聯的是Update_Widget函數,更新按鈕時,會跟據部件的狀態來繪製相應的部件圖形。



在這之前的測試,都是單個窗口的,在調試我編寫的MessageBox時,發現了一個問題:程序有沒有主窗口?

主窗口關閉了,子窗口也應該被關閉。


手動設定部件在窗口中的位置還真蛋疼,看了一下GTK和QT的程序代碼,有個水平佈局盒子(HBox)和垂直佈局盒子(VBox),用來調整部件在窗口中的佈局的,如果窗口尺寸改變,部件的佈局也跟着改變,說到窗口尺寸改變,正考慮是添加窗口尺寸改變的事件處理,還是在調用相應函數改變窗口尺寸時自動處理佈局,這個佈局盒子,可以被其它佈局盒子包含,也可以包含窗口部件,至於處理方法,想得很糾結,算了,等以後實在是需要這個時再花時間來想吧。


GUI的核心部分還是比較難搞,也就是事件機制,各種事件,各種返回值類型的回調函數,而這些回調函數的參數可能不定個數,不定類型,如何統一?

與事件關聯的多個回調函數,是應該分別開線程一起運行,還是按順序一個一個調用?

萬一出現一個需要等待很久才能退出的回調函數,那整個程序不就卡住了嗎?其它的數據處理工作都暫停?難道這就是傳說中的程序未響應?


至於事件處理,還是一個一個的調用回調函數,要想不暫停事件處理,自己在回調函數裏開線程。


個人認爲如果每種部件都要寫相應的處理函數,會變得很麻煩,代碼編寫量也多,這些部件應該需要有統一的處理函數,方便代碼維護,也減少了代碼編寫量,於是,整理出了一些設計思路:

每個部件創建之初都是透明的。
label部件如果沒有背景圖,那麼字體位圖中不透明的像素點對應部件中的像素點也不透明。如果有背景圖,那透明度爲完全不透明,先填充背景色再混合背景圖,根據背景圖的佈局來做相應的處理,例如:拉伸、縮放、居中、平鋪,最後粘貼字體位圖。除了背景圖,還有一個圖層,這個圖層也可以顯示圖像,但沒有背景圖那樣的處理,只是根據對齊方式來調整圖像的粘貼位置,該圖層與部件圖形混合的時,如果沒有背景圖,就和粘貼文字位圖那樣。
更新該部件時的處理順序爲:
開始-》判斷是否有背景圖-》有就讓部件不透明,否則透明-》判斷是否有要顯示的圖像-》有的話,根據圖像對齊方式來調整圖像位置,之後根據圖像的alpha通道而局部改變部件的alpha通道-》粘貼文字位圖-》結束。


picture_box部件,和label部件一樣,如果沒有背景圖,那麼就全透明。如果有要顯示的圖像,那麼,部件圖形數據中的alpha通道會根據圖像的alpha通道而局部改變,更新該部件時的處理順序爲:
開始-》判斷是否有背景圖-》有就讓部件不透明,否則透明-》判斷是否有要顯示的圖像-》有的話,根據圖像處理模式來處理圖像,之後根據圖像的alpha通道而局部改變部件的alpha通道-》結束。

button部件,和上面幾個部件一樣,但是可以設定風格,有默認風格和自定義風格,默認風格就是根據部件的狀態,繪製不同顏色的背景圖,加上邊框;而自定義,就是自己指定按鈕在不同狀態時所使用的圖形,切換狀態時,就會使用自己定義的按鈕圖形,默認對圖形進行拉伸處理,如果沒有,就會使用默認的按鈕圖形,更新該部件時的處理順序爲:
開始-》判斷是否有背景圖-》有就讓部件不透明,否則透明-》判斷按鈕風格-》默認風格則繪製簡陋按鈕圖形,自定義風格則使用自定義的圖形-》判斷是否有要顯示的圖像-》有的話,根據圖像的對齊方式以及與文本的關係,來處理圖像,否則不處理-》根據字體對齊方式以及與圖像的關係,在對應的位置粘貼字體-》結束。


根據以上思路,得到這些部件處理的共同處,合併了一些代碼,寫了Update_Widget函數,用於更新部件,先處理部件背景圖,之後根據不同的部件類型來調用相應的函數以完成部件的更新。


不久,又添加了部件佈局功能,總共有9個佈局方式:左上、中上、右上、中左、中間、中右、左下、中下、右下,並支持偏移座標,每次改變窗口尺寸時,自動根據每個部件的佈局方式以及偏移座標得出位置,並設定,有的時候並不需要完全是這個佈局,比如:某個部件要在右下角,但是,要與邊緣距離5個像素點,也就是向左移動5個像素點,向上移動5個像素點,那麼,就可以用偏移座標了,當offset_x和offset_y爲負數時,就會在這個以通過佈局方式計算出的xy座標中加上這個偏移值,由於是負數,就相當於減去了。

寫了一個石頭剪刀布的遊戲,用到了label部件、picture_box部件以及button部件,也用到了事件關聯和部件佈局功能,具體請看圖:


在測試遊戲期間,糾正了事件處理、部件圖形更新處理、字體位圖處理、局部圖形刷新處理等功能的代碼所出現的問題。

編寫了一個圖片水平翻轉函數,如上圖所示(第二張),同一張圖片顯示成水平對稱的兩張圖片。

此小遊戲的源代碼在這裏:http://lcui.sourceforge.net/bbs/forum.php?mod=viewthread&tid=12&page=1&extra=#pid15


完善了窗口標題欄刷新功能,窗口尺寸被改變後,標題欄會被重繪,在標題欄上的關閉按鈕也會被刷新。


幾天後,加入了觸屏的支持,用了tslib提供的示例程序的代碼,窗口右上角也添加了關閉按鈕(如下圖所示)。

在調試觸屏功能的過程中,糾正了部件圖形刷新功能,因爲有兩種部件:一種在標題欄,一種在客戶區,兩種部件的圖形刷新要分開處理,混在一起的話,會出現刷新問題。

完善了觸屏點擊處理,之前測試時,點擊一下按鈕,只變成了下凹的狀態,沒有觸發部件被點擊的事件,部件狀態更改的函數的代碼也做了相應更改,因爲這問題是這函數的問題所導致的。

還發現label部件在改變尺寸並移動位置後,存在殘餘圖形,它的更新方式就需要改動:如果有一行文本內容中的一個被字改動,就刷新該字後面一整行的字,單個字進行局部刷新不準確,會有殘餘圖形。

添加一個變量,保存部件位置的類型,用於指示部件是在標題欄還是在用戶區,繪製標題欄按鈕需要用到。


有了觸屏支持,當然要有一個觸屏校準程序,於是就修改tslib源碼目錄裏的test程序的源碼,改成LCUI版的,調試了幾遍,最終完成了,可以看這圖:


窗口風格爲無(NONE),不會繪製窗口標題欄,那個“點擊圓圈中心,筆點校正”(label部件)本來是顯示在中間的,可是測試時,發現圓圈(picture_box部件)移動到中央的過程中,留下了label部件殘餘圖形,這問題暫時先放着,因此,就把label部件的位置調整到圓圈不會覆蓋到的位置。



寫照片查看器的時候,發現我寫的這個GUI庫還有蠻多問題的,爲部件添加背景圖後,段錯誤,不添加的話,label部件中的文字有時會不完整,打開圖片文件後,有時會出現部件排列順序出錯,本來在底層的部件居然顯示在其它部件之上,刷新也是,不僅如此,段錯誤居然不是必然事件了,同一個操作,重複多次,有幾次不同的結果,這幾個不同的結果就是剛剛所說的,這怎麼找BUG?悲劇了。


思考了一下,感覺應該是沒有進行數據保護,之前儲存圖形數據的結構體沒有加入互斥鎖的功能,導致頻繁切換圖形顯示時直接出現段錯誤,因爲我的這個GUI的實現用了多線程,每個窗口的圖形數據是共享的,如果一個線程在使用free函數釋放一個內存空間的同時,另一個線程又對這個內存空間進行讀取數據的操作,那麼就會出現段錯誤。在加了數據保護後,問題解決了,其實就是加了個變量,表示該結構體中的指針是否正在被使用,是的話,用usleep函數等待,直到沒有被用爲止。


經過幾天的奮鬥,問題終於解決了,修改了主循環的圖形數據處理功能,之前的label部件的問題,是由於對部件更新隊列的處理不當,而不是因爲數據同步問題,在更新label部件中的文本時,如果開啓了自動調整大小,那麼就會在生成文字位圖後重新計算尺寸,之後就會調用相關函數來調整部件尺寸,但是調整部件的尺寸需要再次對部件進行更新,更新隊列默認是不添加重複的部件指針的,於是使用了一個函數來強制將這個部件加入至更新隊列,這樣問題就解決了。


編寫了0.12版的照片查看器,在電腦端測試正常,而在學習機裏,程序居然卡住了,在主循環中加上printf函數來打印信息,學習機上的測試結果表明是在部件更新隊列的循環中一直循環,這個問題就是上面所說的問題,調整了部件更新隊列的處理方式就能解決問題。

對按鈕部件使用透明效果時,發現不起作用,於是我就用gdb的watch命令查看按鈕部件的圖形數據結構體中的一個變量,這個變量表示整個圖形的全局透明度,經過變量跟蹤,發現是實現圖形裁剪、圖形縮放的函數有問題,這兩個函數都修改了輸出的圖形數據的全局透明度爲255,(不透明)。

這個是0.12版的程序截圖:


用到了多線程,也就是調用pthread提供的函數實現的,但是,在程序退出時發現了一個問題:在退出LCUI後,創建的線程還在運行,而這些線程還在使用之前LCUI提供的資源,由於LCUI已經退出,之前分配的資源已經無效,這就導致了段錯誤。

爲解決這個問題,暫時添加一個函數,實現標題欄中關閉按鈕的事件關聯,在點擊標題欄中的關閉按鈕後,先撤銷線程,之後再釋放LCUI佔用的內存資源。

之前考慮了爲LCUI添加線程管理功能,使用LCUI提供的函數創建線程,在這個LCUI程序退出時,LCUI會將這個程序創建的所有線程撤銷,然後再釋放LCUI分配給程序的資源佔用的內存空間,但是,僅僅是考慮罷了,看看以後能否用到。


之前的部件只有LabelPictureBox和Button三個,也該考慮添加新部件,完成了下拉菜單、列表框、文本框、滾動條和進度條,就可以寫文件管理器;

進度條最容易實現,其次是滾動條,下拉菜單、列表框和文本框這三個有點難度;

經過仔細研究系統顯示的下拉菜單的效果,費了1周左右的課餘時間,算是實現了下拉菜單,支持多級菜單,一個按鈕點擊後,可以顯示一個菜單,這個菜單顯示的位置,由相關函數根據按鈕的位置和尺寸以及菜單的尺寸來確定菜單顯示位置。

顯示菜單,我用了新窗口,往菜單裏添加選項,就相當於往窗口裏添加部件,在添加的同時,相關代碼會根據窗口內的各個部件的寬度,獲取最大寬度,之後,調整每個部件的寬度,使之一致,窗口的寬度和高度也會調整。

菜單的顯示位置,如果超出了屏幕範圍,就調整位置,使菜單內在屏幕有效範圍內顯示。


由於菜單使用的是窗口,但又與普通窗口不一樣,普通窗口全部關閉後,菜單的窗口也需要關閉,否則,程序就不會退出。

我在結構體中加了個變量,用於區分這兩種窗口,當普通窗口全部關閉後,自動關閉菜單窗口,釋放佔用的內存資源。

可是,完成這些功能後,問題又出現了: *** glibc detected *** double free or corruption (out): 0xXXXXXX

問題出在 釋放 FreeType2打開字體文件後 保存的 相關數據 佔用的內存資源,字體文件只打開過一次,退出LCUI時,只釋放一次,爲什麼在添加下拉菜單後就出問題了呢?


我處理問題有兩種方法:

一,調試程序,分析代碼,查錯誤。

二,修改程序的主要代碼,改變程序的數據處理方式,以與之前使用的方式不一樣的另一種方式工作,相同的結果,可以有不同的實現方法,新的方法很有可能不會出現前一種方法所出現的同一個錯誤,這樣就可以避免前一個方法產生的錯誤了。


有時嫌第一種方法太麻煩,太耗時間和腦力,就考慮第二種方法,思考新的程序運行方案,當然,至少要比之前採用的方案好,否則不就是寫垃圾代碼嗎?



考慮到以後能夠讓程序使用自己添加自定義部件,部件的結構體需要進行修改,大致如下:

添加一個widget結構體指針和一個widget隊列,前者保存父部件指針,後者保存多個子部件的指針。
添加一個void型指針,用於引用自定義部件自己的數據結構體。
 

App結構體(儲存程序相關數據)也要進行修改,window不再是所有部件的容器,它屬於widget類。

添加一個widget類型管理系統,能保存處理該widget的相關函數指針、字符串和所屬程序,要有幾個函數用於對它進行管理:。 

大部分代碼需要進行修改,減少冗餘代碼,總代碼量會減少。

對於部件的處理,先從部件庫中搜索對應類型的部件,之後獲取該類型相關的函數指針,運行之。

部件相關的處理大致是這樣:

處理類型 相關函數
創建並初始化 Create_Widget()
圖形更新 Update_Widget()
尺寸改變 Resize_Widget()
顯示 Show_Widget()
隱藏 Hide_Widget()
銷燬 Free_Widget()

而這些函數大致的工作流程基本一樣:

先進行所有部件都需要進行的相關操作,之後,從部件庫中根據部件類型來搜索對應函數指針,得到函數指針後,就執行它,參數就是部件的LCUI_Widget結構體指針。

例如:

窗口部件,初始化時,Create_Widget()函數會調用相關函數對窗口的私有結構體中的數據進行初始化。

圖形更新,調用Update_Widget()函數會調用相關函數對窗口的圖形數據進行更新。

尺寸改變,在部件尺寸被改變後,Resize_Widget()函數會調用相關函數進行其它操作,考慮到以後窗口的視覺特效的實現,就留下這個函數,等待擴展。
顯示和隱藏也是,改變LCUI_Widget結構體中的公有變量,之後調用相關函數進行後期處理,典型的例子是添加視覺特效。
銷燬,先將已經爲LCUI_Widget結構體中的部分公有變量分配的內存資源釋放掉,然後調用相關函數將該類型的部件的私有數據佔用的內存資源釋放掉。

以上進行操作的函數,只是將函數指針和參數發送至程序的任務任務隊列,讓程序在主循環裏處理這些任務。


添加了“容器”功能,可將一個部件作爲另一個部件的容器,例如:將窗口作爲窗口內部件的容器。


之前寫的代碼只是爲了完成功能而寫,有些功能可以用更好的方法來實現;

花了幾天時間,鼠標事件機制經過了修改,可關聯鼠標事件,鼠標事件有兩個:Click和Move,前者是在鼠標按鍵按下或者鬆開後觸發,後者是在鼠標移動時觸發,觸發後,LCUI會將與事件關聯的函數指針發送至程序的任務隊列中,供程序在主循環中處理,而傳遞給函數指針指向的函數的參數,是Mouse_Event結構體指針,裏面記錄了鼠標按鍵狀態鼠標指針覆蓋到的部件的指針 以及 鼠標指針的全局位置和相對位置。


添加了函數:
int leftbutton(LCUI_Mouse_Event *event)
int rightbutton(LCUI_Mouse_Event *evemt)
返回值:
-1 不是這個鍵
0  釋放
1  按下


添加了一個隊列,用於保存已經按下的按鍵的鍵值,在按下鼠標左鍵後添加該鍵值至隊列,當釋放左鍵後,從隊列中移除左鍵的鍵值,移除成功就觸發鼠標左鍵點擊事件,失敗則表明左鍵沒有按下。如果從隊列中移除鍵值失敗,則不觸發click事件,因爲不存在這個鍵值,說明之前沒有按下這個鍵。
click事件,可得知按鍵的兩個狀態:按下、釋放,也可得知按鍵的鍵值,如果這個鍵沒有按下,就不會觸發click事件。
與click事件關聯的回調函數,在按鍵被按下時和釋放時才調用,傳遞給它的參數是LCUI_Mouse_Event類型,回調函數可以根據這個參數得知某個按鍵是否按下/釋放;參數中還包含當前鼠標指針覆蓋到部件的指針。


函數的遞歸也用了,其實作用還蠻大的,例如:在部件內尋找與指定區域重疊的最頂層的部件,父部件中有子部件,子部件中可以有子部件,而子部件中還可以有子部件,可以變得很深,簡單的用幾個for循環是無法遍歷完每個子部件的指針的。


話說回來,根據以上的描述,好像不知不覺中用了鏈表,雖然之前沒學過鏈表,但是,不完全像鏈表,只是結構有點像。。。貌似又是N叉樹,主分支裏有多個分支,分支裏再分支。。。


幾天時間已經過去,代碼終於修改完成了,上述的功能也已經實現,調試和分析問題花費了絕大部分時間,由於本人的頭腦有點遲鈍,因此浪費了過多的時間。

現在,需要考慮圖形顯示的優化,之前測試窗口的移動的結果還不怎麼滿意,有些地方還是需要改一下,比如:

圖層的合成,這個還可以優化,節省更多的耗時;

局部區域刷新,刷新多個區域時,由於這些區域之間有些會有重疊的區域,而處理時,是把重疊的區域重複刷新了多次,這個沒必要,浪費時間!具體看下圖所示:


圖中所示的是:將4個重疊的區域處理成5個不重疊的區域,這樣就可以減少不必要的時間浪費了,具體算法目前還在思考中。。。


對於多個重疊矩形的處理,在添加矩形時就應該處理掉被多個矩形重疊的區域,具體如下:
每添加一個矩形時,先將它與隊列中的各個矩形對比,對比結果有幾種:
1,不重疊,繼續與下個矩形對比。
2,被其中一個矩形包含,不添加該矩形至隊列,直接退出函數。
3,包含其中一個矩形,將這個矩形從隊列中移除,繼續與下一個矩形對比。
4,與其中一個矩形有重疊,先裁剪矩形區域,然後將不重疊的部分分成兩個矩形,並再次調用本函數以添加至隊列,會用到函數遞歸調用;


重疊矩形區域的裁剪有點難度,需要糾結一段時間。

最近的幾次測試,問題比較多,這個功能暫時先放着不用,以後再來做。



“好的程序是調試出來的”,這句話說得沒錯!

在與BUG鬥爭的這段時間,本人深有體會,gcc提示的那些錯誤倒是不需要管,我通常是先想好思路,之後用代碼描述出來,盼着就是編譯時gcc給的錯誤/警告信息,根據這些來對代碼進行細節上的修改,這其實也不難應付,怕的就是程序運行過程中因邏輯錯誤而導致的段錯誤,以及意料之外的程序運行結果;

調試是少不了的,gdb我也就只會用break設定斷點,back查看程序之前調用了哪些函數,step慢慢執行代碼並覈對運行結果,finish跳出當前函數,watch查看變量的變化。

起重大作用的還是printf函數,調試時都是在某個的位置添加printf函數,輸出變量的內容,查看變量是否正常。




局部區域刷新功能還是需要完善,在繪製父部件中的子部件時,不僅要得出子部件在該區域內的有效顯示區域,還要得出在父部件區域範圍內的有效顯示區域,之後,根據這兩個區域得出子部件實際要裁剪的圖形區域。這是爲了避免子部件超出父部件的範圍時,還能將子部件顯示出來,之前測試按鈕部件時,按鈕尺寸爲0x0,而按鈕中的label部件卻完全顯示出來了。。。


有了個想法:

將結構體的定義隱藏,不能直接訪問結構體的成員變量,只能通過函數庫提供的函數來獲取,這樣,每次修改數據結構後,之前編譯的程序就不會因爲數據結構改變而導致程序無法正常運行了,省去了重新編譯程序的麻煩,但是,這只是想法,暫時不會去實現。


最近發現一個問題,新版的png庫不兼容那些使用舊版png庫的程序。

沒有更新png庫時,我將一個可以創建png圖片文件的代碼整理進我的LCUI的代碼裏,想創建文件,直接傳文件路徑和圖片數據結構體進去,函數就會創建一個png圖片。可是呢,電腦上編譯了最新的png庫源碼,安裝之,再次使用相關函數來創建png圖片文件時,創建出來的圖片內容有問題,幾乎是全黑的,

仔細看官方介紹時發現了他發佈了兩個版本,一個是1.5.10,一個是1.2.49,爲兼容那些使用老版png庫的程序,可以下載libpng-1.2.49。


測試按鈕部件時,又發現一個問題:

在鼠標移到按鈕上的時候,該改變誰的狀態?

理論上,應該改變按鈕的狀態,可實際上,鼠標指針是覆蓋在按鈕裏的label部件上,改變的是按鈕裏的label部件的狀態。

按鈕的各種狀態的切換遇到了一些麻煩,應該添加一個回調函數,用於鼠標跟蹤,跟蹤的目的,是用於處理按鈕的幾種狀態的切換,大致處理流程是這樣的:

如果這個部件不是按鈕,並且有父部件,那麼就一直往上遍歷這個部件的父部件,直到父級部件是按鈕爲止,如果這個部件的父級部件沒有一個是按鈕,那就算了。



按鈕部件和圖片盒子這兩個部件已經完成了,開始添加進度條部件!


測試動態改變label部件的文本內容時發現一個問題:

我創建一個線程,用於動態改變label部件的文本內容,可是,由於搜索不到線程ID,確定不了程序數據結構體指針,導致往程序任務隊列添加任務出錯。


嗯,該添加線程管理功能了,我使用以下結構體:

typedef struct	_Thread_Queue			Thread_Queue;
typedef struct	_Thread_TreeNode		Thread_TreeNode;

/************ 線程隊列 **************/
struct _Thread_Queue
{
	Thread_TreeNode **queue;	/* 儲存隊列成員 */
	int max_num;				/* 最大成員數量 */
	int total_num;				/* 當前成員總數 */
};
/***********************************/


/************ 線程樹的結點 ***************/
struct _Thread_TreeNode
{
	Thread_TreeNode *parent;	/* 父線程結點指針 */
	pthread_t tid;				/* 父線程ID */
	Thread_Queue child;		/* 子線程隊列 */
};
/***************************************/



用於儲存這些線程ID以及關係,與其說是鏈表,不如說是一個“樹”,根線程只有一個,也就是第一個初始化LCUI時的程序的主線程ID,線程可以有子線程,這樣就會產生很多分支,每個結點的分支數量可能不一樣。

由於LCUI還未實現多進程通信,只能靠線程ID來區分哪個操作是哪個程序的,目前打算是將程序以線程的形式運行,而不是以進程的形式運行,主線程爲運行平臺,其它程序就是該平臺上運行的子線程,雖然能實現數據的交換,但是,一旦某個程序出現錯誤,整個進程就會退出。

在使用LCUI提供的線程相關的函數時,會對線程關係樹(我覺得叫這個名字好一些)進行相應操作,比如:

創建線 程,就會先得到創建時的父線程ID,搜索這個樹中的每個結點,如果有匹配的線程ID,就將這個子線程ID加入這個父線程的子線程隊列中;如果沒有匹配的,那就新增一個至主隊列中,對於處理這種數據結構,用函數遞歸來處理還是比較方便的。

而撤銷線程,也類似,先查找,後移除。


創建線程,撤銷線程,阻塞等待線程退出等功能的函數,主要是調用了pthread.h頭文件中聲明的函數來實現的,這些函數只是順便對線程樹中的數據進行修改而已。

在使用我定義的LCUI_Thread_Exit函數時,編譯器會提示它所在的函數需要有返回值,看了pthread_exit的函數聲明,原來在函數聲明後面加上:

__attribute__ ((__noreturn__));
即可讓編譯器給出的這條警告消失。



在這裏只是大致的描述LCUI的設計思路,具體可以等待以後的LCUI的源碼公開時,參考源代碼。


進度條完成了,測試時,發現圖像縮放函數有點小問題,小圖並不能完全拉伸,拉伸後的圖形邊緣部分還有混亂顏色。

效果如下圖所示:

共兩種風格:帶動態效果的,和經典風格。
動態效果的,就是圖中綠色的進度條,裏面有個高亮區域,它會自己移動,就和win7裏的進度條一樣,進度條中有閃光從左至右移動。
該部件可以設定閃光的移動速度,以及它本次移動完後 到 下次從頭開始移動的間隔時間。先設定進度條最大值,再設定當前值,槽中的進度條就會變成相應長度。


又做了個啓動界面,有動畫,如下圖所示:

以後有的程序可能需要這個,在啓動時,先顯示動畫,等待程序初始化完成。
中間一個圈,裏面是字母LC,LC也就是我名字的縮寫。

下面那個雪花動畫,和QQ2012登錄時顯示的動畫一樣,其實就是用了QQ安裝目錄裏的圖片。

想弄成環形波浪動畫,從中間的LC圓圈標誌擴散到四周,可是,涉及到圓形的繪製,有點難度。。。


以下是最近寫的測試程序顯示的效果,用於測試LCUI的基本部件:


Hello World程序源碼在這裏:http://blog.csdn.net/liuchao35758600/article/details/6953328


爲了實現圖形旋轉算法,參考了一些與圖形旋轉相關的資料,最終參考了這裏的代碼:http://read.pudn.com/downloads154/sourcecode/graph/684994/%E5%9B%BE%E5%83%8F%E6%97%8B%E8%BD%AC/%E5%9B%BE%E5%83%8F%E6%97%8B%E8%BD%AC/%E6%BA%90%E4%BB%A3%E7%A0%81/MyDIPView.cpp__.htm

以這代碼爲基礎,修改了一下。


測試結果如圖所示:



還需要完善。

既然有了圖形旋轉算法,那就可以做一個時鐘了。


那個啓動動畫經過修改,現在的效果如下圖所示:


動畫改成旋轉中的綠圈,圍繞LC標誌旋轉,使用了圖形旋轉算法,可是,這個程序在學習機端跑很卡。

這個是時鐘:


用了Go桌面的時鐘部件安裝包裏的圖形素材,尺寸有點大,也就是根據時間來計算指針角度。



爲了以後的源代碼開放,開始研究如何用automake創建共享庫。

我參考了Automake相關文章

Automake中文手冊

Linux下Makefile的automake生成全攻略

用automake建立共享庫(動態鏈接庫)Makefile

使用 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。


2012-5-18

好久沒花時間去寫代碼了,臨近期末,作業也多了。

有了個想法:

使用libxml庫,用於實現xml文件的數據讀寫,懶得再爲配置文件的讀寫功能另寫代碼來實現了,直接用libxml提供的API來完成。

髒矩形矩陣機制正在編寫,目前的髒矩形矩陣創建功能在多次調試後已經完成,測試效果如下圖:



根據屏幕尺寸,生成一個由64x64個髒矩形組成的矩陣,這個測試程序是根據生成的髒矩形矩陣的數據來在屏幕上繪製這些黑白矩形的。

初次測試時,每行矩形繪製完後都有個問題,繪製了一個座標有問題的矩形,經過調試分析後,發現代碼中的變量名用錯,導致用了錯誤的數據生成了錯誤的結果。


接下來就是髒矩形矩陣的裁剪、尺寸調整、複製以及採樣點的座標生成、採樣、對比。還需要寫個函數,用於將各個矩形區域數據處理成互不重疊的,之後再進行相應屏幕區域內容更新。

修改了configure.ac,使configure腳本支持--enable-debug選項,這樣,gcc編譯器在編譯源碼時,加了-g參數,方便以後用gdb來調試代碼。

具體,參考了這篇文章:http://hi.baidu.com/howlwolf/blog/item/f2d2ca13f6cb6229dc540169.html


經過一番調試分析後,髒矩形矩陣的尺寸已經可以調整了,中途出現的問題,發現是自己在realloc時,由於給realloc的第一個參數寫錯(本來是用一指針,卻寫成了二維指針),第二個參數中,sizeof()裏的類型名寫錯(Dirty_Rect寫成了Dirty_Rect*)。在測試過程中,發現有的問題會造成操作系統卡住,響應巨慢!用free命令查看後,發現每次調試卡住後,可用內存越來越多,已使用的內存變少了,這應該是由於測試程序申請了過多的內存空間導致的。


2012-5-19

髒矩形矩陣的採樣點的生成已經完成。


2012-5-21

需要一個定時器,用於每隔一段時間去執行某個函數。

添加了移動對象處理機制,這個移動對象指的是需要改變位置的部件,部件移動前,將部件指針添加至隊列,等待函數來處理並更新部件位置。


2012-6-3

完善了部件嵌套的圖形處理功能,處理多級部件嵌套的圖形顯示時,不會出現圖形顯示錯亂的問題。


2012-6-4

準備做一個鎖屏程序,這是初始的形態:



2012-6-5

修改了鎖屏程序,添加了背景圖,調整了文本的位置:


滑塊用picturebox部件代替,考慮到要有固定的滑動軌跡,widget部件要添加max_pos和min_pos屬性,用於限制部件的最大位置和最小位置,也就是限制了它的可移動範圍。

又修改了一下:


剩下的就是添加鼠標事件處理了。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章