99网
您的当前位置:首页_MTK源码总结

_MTK源码总结

来源:99网
声明:前阶段进行了近两个月的MTK平台上层开发,由于缺乏技术支持,对于整个平台的认识都是通过简略的文档和浅薄的经验摸索出来的。其间整理了一些文档。由于联发科提供的PDF全部是英文,有些名词难以翻译准确,只能凭单方理解和嵌入式开发的词汇习惯进行意译,还请谅解。系列文章均出自原创,肤浅可笑之处,望海涵。

(一)窗体的重画

通过观察可以发现,每个窗体模板都调用这样一个函数:

dm_redraw_category_screen()。这个函数便是显示窗体的函数。它内部的实现是这样的:获得该窗体所包含的组件及它们的属性,再根据组件的类型和属性,调用不同的接口,逐一绘制各组件。详细流程如下图所示:

由上面的流程可见,无论是窗体所包含的组件,还是组件的属性,都是根据模板ID获取的。那么现在摆在面前的有两个问题:一、模板ID是如何传递到这个函数中的;二、模板ID和窗体组件、组件的属性,是如何关联到一起的。 我们逐一解决这两个问题。

一、模板ID是如何传递到这个函数中的

模板ID,是ShowCategory..Screen()过程中,所显示的界面的编号,千万不要与EntryNewScreen(scrID,…) 函数中传入的窗口ID相混淆。它们以“MMI_”为前缀,被定义在枚举型结构MMI_CATEGORY_ID_LIST中,又通过结构体dm_data_struct和它的全局结构体变量g_dm_data,在应用程序中被广泛使用。先看看结构体dm_data_struct的定义:

typedef struct {

S32 s32ScrId; S32 s32CatId; S32 s32flags; }

其中,s32ScrId是当前窗口ID,也就是我们使用EntryNewScreen()时传入的那个参数;而s32CatId才是模板ID;最后的flag,是模板需要显示软键盘、清屏等动作时,所置的标志变量,它在上面提到的那个

dm_redraw_category_screen()函数中被判断。还是来重点看一下第2个结构体成员s32CateId的使用。

以 ShowCategory6Screen() 为例。这个显示模板的函数中,经常可以看到

这样的语句:

dm_data.s32ScrId = (S32)GetActiveScreenId();

dm_data.s32CatId = MMI_CATEGORY6_ID; dm_data.s32flags = 0; dm_setup_data(&dm_data);

再看dm_setup_data()干了什么:

void dm_setup_data(dm_data_struct *dm_data) {

g_dm_data.s32CatId = dm_data->s32CatId;

g_dm_data.s32ScrId = dm_data->s32ScrId; g_dm_data.s32flags = dm_data->s32flags; }

这样,MMI_CATEGORY6_ID就被很自然的赋到g_dm_data.s32ScrId中了,然后随着这个全局变量,顺利的被带到了dm_redraw_category_screen()中。

二、模板ID和窗体组件、组件的属性,是如何关联到一起的

通过分析dm_search_control_set函数,发现窗体模板的组件和属性相关

信息都隐藏在一个宏伟的结构体数组中:g_categories_controls_map。

这个结构体定义dm_category_id_control_set_map_struct定义如下:

typedef struct {

U16 category_id; U8 *control_set_p;

S16 *default_coordinate_set_p; S16 *rotated_coordinate_set_p;

}dm_category_id_control_set_map_struct;

第一个结构体成员,是窗体模板的ID; 第二个结构体成员,是组件数组的首地址;

第三个结构体成员,是默认的组件属性数组的首地址; 第四个结构体成员,是特殊的组件属性数组的首地址。

dm_search_control_set()函数dm_search_coordinate_set()函数就是通过匹配模板ID在结构体数组中分别获取的组件集合和组件属性集合的。

好,到这里,我们刚才提出的两个问题就明确了。

(二)构成窗体的组件的定义

根据上文可以知道,窗体组件的定义与窗体模板ID是通过结构体

dm_category_id_control_set_map_struct关联的。现在来看一看组件数组的结构。下面将以5号模板为例。

const U8 category5[] = { 5,

DM_BASE_LAYER_START, DM_SCR_BG,

DM_BASE_CONTROL_SET1, DM_MULTILINE_INPUTBOX1,

DM_CATEGORY_CONTROLLED_AREA }

在这个组件数组中,第一个字节“5”代表组件的数量;第二个字节开始就是组件的类别的ID了。比如说,DM_BASE_LAYER_START,代表开始使用Layer;DM_SCR_BG表示背景图;DM_BASE_CONTROL_SET1表示窗体的基本组成——状态栏、标题和软按键;DM_MULTILINE_INPUTBOX1是多行输入框;

DM_CATEGORY_CONTROLLED_AREA则是输入法的显示部分。它们被定义在枚举结构mmi_dm_control_ids_enum中。

在MTK环境的原始版本中,一共有99种组件。其后的都是用户自己扩展的。各组件的外观和功用是什么,基本是可以见词知意的,可以在具体使用过程中了解。

(三)各组件的属性定义

仍以5号模板为例,观察组件属性数组。 cosnt S16 coordinate_set[] = {

DM_FULL_SCREEN_COORDINATE_FLAG, DM_CONTENT_COORDINATE_FLAG, DM_FULL_SCREEN_COORDINATE_FLAG }

这两个常量都是代表组件属性的标志。定义在Wgui_draw_manager.h中大家可以观察到,它们都是负数:

#define DM_FULL_SCREEN_COORDINATE_FLAG -10002 #define DM_CONTENT_COORDINATE_FLAG -10004

现在就让我们一起分析一下它们在控件绘制中起到的作用,以及定义成负数的原因。

请回到(图1-1)。通过比对我们可以发现,在dm_redraw_category_screen()函数流程的第6步的循环中,就是通过判断组件的ID来实现逐步绘制窗体的,如: case DM_MULTILINE_INPUTBOX1:

{

dm_setup_and_draw_multiline_inputbox(&UICtrlAccessPtr_p,&dm_cat_scr_info);

先来分析两个参数。联系上面的程序,我们可以知道,UICtrlAccessPtr_p是作为参数传入dm_get_cat_scr_coordinates()函数中的,又作为返回值,完成进入循环前的最后一次更改的,而此前,它指向的是组件属性数组。由于模板需要在其他组件被绘制前绘制窗体本身,因此它使用dm_get_cat_scr_coordinates()提前了解窗体的规格,而组件属性数组的第一个U16,就正是窗体的规格的标志ID:DM_FULL_SCREEN_COORDINATE_FLAG。根据该ID,

dm_get_cat_scr_coordinates()做了分类判断,得到了指向结构体dm_cat_scr_info_struct的变量指针,其结构定义如下: typedef struct {

S16 x1; S16 y1; S16 x2; S16 y2; S16 flags;

}dm_cat_scr_info_struct;

很明显,结构体成员分别是:起始、结束坐标,附加的标志。

在dm_get_cat_scr_coordinates()过后,指针UICtrlAccessPtr_p向后偏移

了两个字节,指向了DM_CONTENT_COORDINATE_FLAG。在绘制

DM_BASE_LAYER_START、DM_SCR_BG、DM_BASE_CONTROL_SET1时,都不需要类似于起始位置、大小这样的属性来支持,因此它们的绘制函数dm_setup_base_layer()、dm_setup_and_draw_scr_bg()、

dm_setup_and_draw_base_control_set()没有访问组件属性数组。而多行输入控件的绘制函数dm_setup_and_draw_multiline_inputbox()、和输入法显示区域的绘制函数dm_setup_and_draw_category_controlled_area() 却不约而同的调用了*UICtrlAccessPtr_p = dm_get_coordinates(*UICtrlAccessPtr_p, &dm_category_controlled_area_info)这一语句来获取组件属性。因此,DM_CONTENT_COORDINATE_FLAG是多行输入控件的属性标志,

DM_FULL_SCREEN_COORDINATE_FLAG 是输入法显示区域的属性标志。

}

最后观察一下dm_get_coordinates()函数(参见下页的图)。在这个函数里,

对属性标志进行了判断,并最终为dm_cat_scr_info_struct结构体变量进行了赋值:

可以看到,在dm_get_coordinates()函数中,对UICtrlAccessPtr_p指向的内容,也就是某一个组件属性标志常量,进行了判断。而后,根据不同情况对dm_coordinate_info的各成员赋了值。

因此,可以说,一个组件属性标志,就代表了一组包括组件坐标和标志在内的一组属性值。但是,要引起注意的是,这些值都是固定的。当组件的位置需要调整的时候,又该怎么办。继续观察dm_get_coordinates()的代码段。 在判断了所有组件属性标志后,出现了一个else判断: else {

dm_coordinate_info->s16X = *UICtrlAccessPtr_p; UICtrlAccessPtr_p ++;

dm_coordinate_info->s16Y = * UICtrlAccessPtr_p; UICtrlAccessPtr_p ++;

dm_coordinate_info->s16Width = *UICtrlAccessPtr_p; UICtrlAccessPtr_p ++;

dm_coordinate_info->s16Height = *UICtrlAccessPtr_p; UICtrlAccessPtr_p ++;

dm_coordinate_info->Flags = *UICtrlAccessPtr_p; UICtrlAccessPtr_p ++; }

也就是说,UICtrlAccessPtr_p指向的值,如果不是任何一个属性标志常量的话,它就被认为是起始X的值,然后指针将被后移2字节,下一个值被认为是起始Y的值,以此类推,直到这个dm_coordinate_info赋值完毕。这就是自定义控件位置的方法。

如果,要定义一组默认值,就可以先在Wgui_draw_manager.h中定义常量,然后在dm_get_coordinates()加入分支,当组件属性中包含这个常量时,就将dm_coordinate_info的各结构体成员赋值。

这就是构造一个窗体模板的原理和方法。利用这些,我们就可以利用GDI接口和GUI组件,随意的画我们自己的界面了,而不用仅仅去调用单调的ShowCategoryXXScreen()了。

本文详细说明了如何建设一个自定义列表窗体模板。原理部分请参见《MTK平台(1)——如何添加一个窗体模板》。

最终实现的是一个字典输入界面。布局为:

该模板不包含业务逻辑,仅提供页面显示和InputBox框输入事件后的ListBox的Redraw事件的注册,以及基本的输入法设置、清空后的返回函数。

一、添加用户自定义列表模板的过程

(一)在g_categories_controls_map[]中加入:

,{MMI_CATEGORY_CUSTOM_LIST,(U8*)custom_define_list,(s16*)coordinate_custom_list,NULL}

const U8 custom_define_list[]= { 5,

DM_BASE_LAYER_START, DM_SCR_BG,

DM_BASE_CONTROL_SET1, DM_SINGLELINE_INPUTBOX1, DM_LIST1 };

const S16 coordinate_custom_list[]= {

DM_FULL_SCREEN_COORDINATE_FLAG,

DM_CUSTOM_DEFINE_INPUTBOX, DM_CUSTOM_DEFINE_LIST

};

//需要定义 //需要定义

(二)在dm_get_coordinates()函数中加入:

//设定列表位置和大小(不要忘记全局变量 MMI_custom_Listbox_x 等的定

else if( *UICtrlAccessPtr_p == DM_CUSTOM_DEFINE_LIST )

义)

{

dm_coordinate_info->s16X = MMI_custom_Listbox_x;

dm_coordinate_info->s16Y = MMI_custom_Listbox_y;

dm_coordinate_info->s16Width = MMI_custom_Listbox_width; dm_coordinate_info->s16Height = MMI_custom_Listbox_height;

dm_coordinate_info->Flags = DM_NO_FLAGS;

UICtrlAccessPtr_p ++ ;

}

//设定输入框位置和大小

else if( *UICtrlAccessPtr_p == DM_CUSTOM_DEFINE_INPUTBOX )

{

dm_coordinate_info->s16X = MMI_custom_inputbox_x ; dm_coordinate_info->s16Y = MMI_custom_inputbox_y; dm_coordinate_info->s16Width = MMI_custom_inputbox_width ;

dm_coordinate_info->s16Height = MMI_custom_inputbox_height; dm_coordinate_info->Flags =

DM_SINGLE_LINE_INPUTBOX_SPECIFIC_HEIGHT;

UICtrlAccessPtr_p ++ ; }

(三)在Wgui_category.c中定义模板显示函数

void ShowCategoryCustomListScreen( U8 *title, U16 title_icon, U16 left_softkey, U16 left_softkey_icon, U16 right_softkey, U16 right_softkey_icon, S32 number_of_items, U8 **list_of_items, U16 *list_of_icons, S32 flags,

S32 highlighted_item, U8 *history_buffer) {

/*----------------------------------------------------------------*/ /* Local Variables /*----------------------------------------------------------------*/ dm_data_struct dm_data; S32 i; U8 h_flag;

/*----------------------------------------------------------------*/ /* Code Body /*----------------------------------------------------------------*/ gdi_layer_lock_frame_buffer();

SetupCategoryKeyHandlers();

*/ */ MMI_title_string = (UI_string_type) title; MMI_title_icon = (PU8) get_image(title_icon); change_left_softkey(left_softkey, left_softkey_icon); change_right_softkey(right_softkey, right_softkey_icon);

//Create List

create_fixed_icontext_menuitems(); associate_fixed_icontext_list(); ShowListCategoryScreen( (UI_string_type) title, get_image(title_icon), get_string(left_softkey), get_image(left_softkey_icon), get_string(right_softkey), get_image(right_softkey_icon), number_of_items);

for (i = 0; i < number_of_items; i++) {

add_fixed_icontext_item((UI_string_type) list_of_items[i], wgui_get_list_menu_icon(i, list_of_icons[i])); }

h_flag = set_list_menu_category_history(MMI_CATEGORY_CUSTOM_LIST, history_buffer);

if (h_flag) {

fixed_list_goto_item_no_redraw(MMI_fixed_list_menu.highlighted_item); } else {

fixed_list_goto_item_no_redraw(highlighted_item); }

//Create Inputbox

memset(custom_single_input_buffer,0,100);

pfnUnicodeStrcpy(custom_single_input_buffer,L\"Custom Category\");

wgui_setup_singleline_inputbox( 0, 0, 240, 320,

custom_single_input_buffer,

pfnUnicodeStrlen(custom_single_input_buffer), MMI_CATEGORY_CUSTOM_LIST, get_string(right_softkey), get_image(right_softkey_icon),

INPUT_TYPE_ALPHANUMERIC_LOWERCASE| INPUT_TYPE_USE_ONLY_ENGLISH_MODES, history_buffer, 0);

register_hide_multitap(wgui_hide_multitap); gdi_layer_unlock_frame_buffer();

ExitCategoryFunction = ExitCategoryCustomListScreen;

dm_setup_category_functions(dm_redraw_category_screen, dm_get_category_history, dm_get_category_history_size);

dm_data.s32ScrId = (S32) GetActiveScreenId(); dm_data.s32CatId = MMI_CATEGORY_CUSTOM_LIST;

//不要忘记该常量MMI_CATEGORY_CUSTOM_LIST的定义

dm_data.s32flags |= DM_CLEAR_SCREEN_BACKGROUND; //dm_data.s32flags |= DM_SHOW_VKPAD;

dm_register_vkpad_callback(CustomList_virtual_keypad_callback); dm_setup_data(&dm_data); dm_redraw_category_screen(); } /* end of ShowCategory353Screen */

void CustomList_virtual_keypad_callback(void) {

#if defined(__MMI_TOUCH_SCREEN__)

mmi_pen_editor_clear_and_show_virtual_keyboard_area(); #endif

gui_show_transparent_image(0,200,GetImage(IMG_H_SELECT_LEFT),0); }

void ExitCategoryCustomListScreen()

{

wgui_close_singleline_inputbox(); }

(四)在singleline_inputbox_multitap_input()函数中添加用户处理key_0~key_9的按键事件的函数:

void (*singleline_inputbox_custom_input_callback) (void) = UI_dummy_function;

void singleline_inputbox_multitap_input(UI_character_type c) {

/*----------------------------------------------------------------*/ /* Local Variables */ /*----------------------------------------------------------------*/

/*----------------------------------------------------------------*/ /* Code Body */ /*----------------------------------------------------------------*/ if (MMI_singleline_inputbox.flags &

UI_SINGLE_LINE_INPUT_BOX_PLUS_CHARACTER_HANDLING) {

if ((MMI_singleline_inputbox.text[0] == '+') &&

(MMI_singleline_inputbox.current_text_p == MMI_singleline_inputbox.text) && (MMI_singleline_inputbox.text_length >=

(MMI_singleline_inputbox.available_length - ENCODING_LENGTH))) {

return; } }

gui_single_line_input_box_insert_multitap_character(&MMI_singleline_inputbox, c); redraw_singleline_inputbox(); singleline_inputbox_input_callback();

singleline_inputbox_custom_input_callback(); }

(五)Wgui_Category.c中添加用户事件定义接口 //右键事件注册

void SetCategoryCustomListRightSoftkeyFunction(void (*f) (void)) {

wgui_singleline_inputbox_RSK_function = f; }

//key_0到key_9按下时的事件注册

extern void (*singleline_inputbox_custom_input_callback) (void); void SetCategoryCustomListNumKeyFunction(void (*f) (void)) { }

singleline_inputbox_custom_input_callback = f ;

//设置InputBox大小

void SetCustomList_Inputbox_Size(S32 p_x , S32 p_y , S32 p_width , S32 p_height ) { }

MMI_custom_inputbox_x = p_x ; MMI_custom_inputbox_y = p_y ;

MMI_custom_inputbox_width = p_width ; MMI_custom_inputbox_height = p_height ;

//设置ListBox大小

void SetCustomList_Listbox_Size(S32 p_x , S32 p_y , S32 p_width , S32 p_height ) { }

MMI_custom_Listbox_x = p_x ; MMI_custom_Listbox_y = p_y ;

MMI_custom_Listbox_width = p_width ; MMI_custom_Listbox_height = p_height ;

二、自定义列表模板的使用方法

1、 调用SetCustomList_Inputbox_Size 和 SetCustomList_Listbox_Size 设置列表框和输入框的大小。

2、 调用显示窗体的接口 ShowCategoryCustomListScreen。

3、 调用右键事件注册函数,注册文本框被清空后的事件(如返回等)SetCategoryCustomListRightSoftkeyFunction。

4、 调用key_0至key_9的事件注册函数,SetCategoryCustomListNumKeyFunction()。

三、参数详细说明

① void SetCustomList_Inputbox_Size(S32 p_x , S32 p_y , S32 p_width , S32 p_height ) 与

void SetCustomList_Listbox_Size(S32 p_x , S32 p_y , S32 p_width , S32 p_height )

p_x , p_y :起始位置 p_width , p_height : 大小。

② void SetCategoryCustomListRightSoftkeyFunction(void (*f) (void))

void SetCategoryCustomListNumKeyFunction(void (*f) (void))

f(void) :函数地址。

③ void ShowCategoryCustomListScreen( U8 *title,

// 标题文本指针 // 标题图标ID // 左键文本ID // 左键图标ID // 右键文本ID // 右键图标ID

// Input输入Buffer // 列表条目数 // 列表项文本指针数组 // 列表项Icon

// 当前高亮显示的列表条目 // 历史记录Buffer

U16 title_icon, U16 left_softkey,

U16 left_softkey_icon, U16 right_softkey, U16 right_softkey_icon, S32 number_of_items, U8 **list_of_items, U16 *list_of_icons, U8 *history_buffer)

U8* custom_single_input_buffer,

S32 highlighted_item,

附:所需更改的文件

wgui.c

wgui_categories.c wgui_draw_manager.c

wgui_inputs.c wgui.h

wgui_categories_defs.h wgui_draw_manager.h CustCoordinate.c

一、什么是History管理

对于我们上层用户而言,经常接触到的History管理是这样的: void EntryFunc()

{

U8 *guiBuffer;

EntryNewScreen( Screen_ID , Exit_Func , Entry_Func , NULL );

guiBuffer = GetCurrGuiBuffer( SCR_ID_WORDMAIN_LIST );

ShowCategroyXXScreen( Title_ID , … , guiBuffer);

}

但是,无论是EntryNewScreen的调用,还是guiBuffer的传入,我们都很少考虑过对这些指针和

函数在GUI的管理起到了什么样的作用。下面我们就要了解,以上的代码与History管理之间存在的关系。

在MTK环境中,每当我们进入一个窗口,系统将先提取前一个窗口需保留的数据。这些数据包括:

1. 窗口ID ;

2. 进入窗口时调用的函数和退出调用的函数 -- Exit_Func 和 Entry_Func ; 3. 组成窗体的控件的属性(如,列表控件当前高亮显示的条目、当前屏的首末条目等)。

举例说明这些数据在实际中是如何被使用的。

假设存在AB两个窗口,A窗口需要保留的数据为data_A。我们先从A窗口进入到B窗口。data_A将在B窗口调用EntryNewScreen()的时候,被压入一个结构类似于栈的数据存储区域;当从B调用GoBackHistory()返回A时,data_A从栈顶被弹出,然后A利用data_A将自身还原到其进入B之前的状态。

这就是History管理的作用。简言之,就是要保持窗口的外观状态。

二、History管理的机制

现在,我们来了解一下前面所说的data_A的数据结构是什么样的。 typedef struct _history {

U16 scrnID; //(1)Screen ID (窗口号) FuncPtr entryFuncPtr;

//(2)EntryNewScreen时要进入的 Entry_Func

U8 inputBuffer[MAX_INPUT_BUFFER];

//(3)没遇到过其使用,都是NULL。

U8 guiBuffer[MAX_GUI_BUFFER];

//(4)窗体中控件的一些需保存的信息的Buffer,通常//在使用

时被转化成各控件自定义的结构体如: list_menu_category_history。 } history;

而存放data_A的类似于堆栈的数据区则以全局变量的形式定义在系统中: historyNode historyData[MAX_HISTORY]; (MAX_HISTORY = 50):

设当前窗口A所对应的数据是historyData[ EntryScreenNum – 1 ] ,那么它是何时、是如何被赋值的?又是何时、如何被使用的?

经过跟踪调试,我们已经知道,在由窗口A进入到窗口B(调用EntryNewScreen)的时候,我们将data_A记录到了historyNode 的结构体变量中。但是,在EntryNewScreen的时候传入的,却是data_B,data_A是如何被记录和使用的呢?

我们摘选EntryNewScreen的子函数中所包含的较核心的代码来说明这个问题。这三段代码是按照现在的排放顺序来执行的。

第一段(history h 可理解为data_A): h.scrnID = scrnID;

// scrnID = currExitScrnID

h.entryFuncPtr = entryFuncPtr; // entryFuncPtr = currEntryFuncPtr pfnUnicodeStrcpy((S8*) h.inputBuffer, (S8*) & nHistory); // nHistory = NULL ; GetCategoryHistory(h.guiBuffer);

//GetCategoryHistory是指向获取//guiBuffer的函数的指针

AddHistory(h); 第二段:

if(currExitFuncPtr) {

//…

//执行Exit_Func

//数据入栈

(*currExitFuncPtr) (); }

第三段(记录Screen_ID,Exit_Func和EntryFunc): currExitScrnID = scrnID; currExitFuncPtr = exitFuncPtr; currEntryFuncPtr = entryFuncPtr; 这样,我们就可以看出,EntryNewScreen函数先将上次执行EntryNewScreen时所记录的currExitScrnID, currEntryFuncPtr以history结构为载体记录入栈;然后执行了记录中的currExitFuncPtr;最后将本窗口的scrnID、exitFuncPtr、entryFuncPtr分别记录入全局变量currExitScrnID、currExitFuncPtr和currEntryFuncPtr,留待下次调用EntryNewScreen时使用。

下面有数据出入栈流程,有兴趣的话可以跟踪一下。以先后顺序代表包含关系,如下: 1.入栈(EntryNewScreen):

(1)U8 EntryNewScreen(U16 newscrnID, FuncPtr newExitHandler, FuncPtr newEntryHandler, void *peerBuf)

(2)static void ExecuteCurrExitHandler(void); (3)void ExecuteCurrExitHandler_Ext(void);

(4)void GenericExitScreen( U16 scrnID , FuncPtr entryFuncPtr ); (5)void AddHistoryReference(history *addHistory); //处理historyData (6)S16 increment(); //更改栈指针 2.出栈(GoBackHistory): (1)void GoBackHistory(void);

(2)static void ExecutePopHistory(void);

//处理historyData

(3)static U8 decrement(void); //更改栈指针

现在我们已经知道了history 的三个结构体成员是如何记录的了,最后来重点看一下history.guiBuffer是如何被记录和使用的。

三、GUI Buffer对控件属性的记录

由上2节我们知道,guiBuffer是窗体中某些控件的需保存的属性的Buffer,通常在使用时被转化成各控件自定义的结构体。如: list_menu_category_history。

现在有几个问题需要我们解答:

1. guiBuffer 指向的Buffer是如何被分配的?该块数据是动态的还是静态的? 2. 这块 Buffer 是何时被写入数据的?

3. 如何释放(动态分配时)或清空(固定地址时)该块 Buffer ?

让我们逐一解答上面的三个问题,以清晰我们对guiBuffer的认识。 1. 答:在void AddHistoryReference(history *addHistory)中,调用

OslMalloc(MAX_GUI_BUFFER)动态申请了一块内存,用来保存在 GenericExitScreen 中获取的history.guiBuffer。[参见出入栈流程]

2. 如何释放(动态分配时)或清空(固定地址时)该块 Buffer ?

答 :在static void decrement (void)函数中,该buffer被释放:

OslMfree(historyData[currHistoryIndex].guiBuffer);。[参见出入栈流程]

3. 答 : 只要一个窗体模板有需要保存状态的控件,它们都调用了这个函数

——dm_setup_category_functions()。函数定义如下: void dm_setup_category_functions( FuncPtr redraw_function,

U8 *(*get_history_function) (U8 *buffer), S32(*get_history_size_function) (void) ) {

//指向窗体重画函数的函数指针

RedrawCategoryFunction = redraw_function;

//指向获取窗体guiBuffer的函数指针

GetCategoryHistory = get_history_function;

//指向获取窗体guiBuffer大小的函数指针

GetCategoryHistorySize = get_history_size_function; }

在只有一个控件的状态需要保存的窗体中,会这样传参给这个函数: dm_setup_category_functions(dm_redraw_category_screen, dm_get_category_history, dm_get_category_history_size);

GenericExitScreen()函数中,将使用 GetCategoryHistory()获取某个控件的GuiBuffer[参见出入栈流程]。如果按照上面的设置,GetCategoryHistory指向了 dm_get_category_history这个函数。看看这个函数做了什么:

control_set_ptr = dm_search_control_set((U16) p_dm_data->s32CatId, &coordinate_set_p); //获取窗体模板内的控件类型数组control_set_ptr u8NoOfUICtrls = control_set_ptr[0]; //获取数组内变量个数,即控件的个数

/*根据控件类型,获取控件的guiBuffer.值得注意的是,1.这里的histroy_Buffer的名称起的不好,应该起名为guiBuffer,不应混淆视听;2.最终history_buffer 将指向模板中定义的最后一个控件的guiBuffer*/

for (u8CtrlCt = 1; u8CtrlCt <= u8NoOfUICtrls; u8CtrlCt++) {

switch (control_set_ptr[u8CtrlCt]) {

case DM_CIRCULAR_MENU1: {

get_circular_menu_category_history((U16) p_dm_data->s32CatId, history_buffer);

break; }

case DM_LIST1: {

get_list_menu_category_history((U16) p_dm_data->s32CatId, history_buffer); break; }

case DM_DYNAMIC_LIST1: {

get_list_menu_category_history((U16) p_dm_data->s32CatId, history_buffer);

break; }

case DM_ASYNCDYNAMIC_LIST1: {

get_list_menu_category_history((U16) p_dm_data->s32CatId, history_buffer);

break; } //... } //... }

而在模版显示函数(ShowCategroyXXScreen)中,则根据 guibuffer 的情况设置控件的属性。如果 guibuffer 不为空,则说明该模板的显示函数是在GoBackHistory()的时候被调用的,而不是进入新窗口时被调用的。那么控件必然有一些保留的属性需要被还原。以6号窗口的List为例。在ShowCategory6Screen()中,调用下面的函数来恢复List设置:

h_flag = set_list_menu_category_history(MMI_CATEGORY6_ID, history_buffer); 这样guiBuffer的Get和Set就统一起来了。

现在,我们已经知道了guiBuffer 所起到的作用。但是,如果一个窗体模板内有两个或两个以上需要记录状态的控件,又该怎么办呢?

ps:该死的字数,只能把剩下的放到《MTK平台(3)——History管理[下]》了。

四、灵活使用guiBuffer

在我们自己设计窗体模板时,经常会出现一个窗体中有多个控件的情况。但是,如果一个窗体中有两个控件、却依然调用dm_get_category_history()获取控件的GuiBuffer的话,就会出现问题。比如,我们在制作CustomList窗体时,初期使用了这样的代码: (1)模板中的组件设置:

const U8 custom_define_list[]=

{

5,

DM_BASE_LAYER_START, DM_SCR_BG,

DM_BASE_CONTROL_SET1, DM_SINGLELINE_INPUTBOX1, DM_LIST1 };

//单行输入控件

//列表控件

(2)窗体显示函数 ShowCategoryCustomListScreen 部分源码: void ShowCategoryCustomListScreen(...,U8 * guiBuffer) {

//...

//根据 MMI_CATEGORY_CUSTOM_LIST 的 guiBuffer,为全局结构体变量

//MMI_fixed_list_menu赋值.

h_flag = set_list_menu_category_history(MMI_CATEGORY_CUSTOM_LIST, guiBuffer); //而后利用MMI_fixed_list_menu,设置list的属性

if (h_flag) {

fixed_list_goto_item_no_redraw(MMI_fixed_list_menu.highlighted_item); } else {

fixed_list_goto_item_no_redraw(highlighted_item); }

//...

//再设置单行输入框的属性

wgui_setup_singleline_inputbox(

0,

0,

240, 320,

custom_single_input_buffer, 50,

MMI_CATEGORY_CUSTOM_LIST, get_string(right_softkey), get_image(right_softkey_icon),

INPUT_TYPE_ALPHANUMERIC_LOWERCASE| INPUT_TYPE_USE_ONLY_ENGLISH_MODES,

guiBuffer,

0);

//其中 wgui_setup_singleline_inputbox 函数中调用了

//set_singleline_inputbox_category_history()来解析guiBuffer //...

dm_setup_category_functions(dm_redraw_category_screen, dm_get_category_history, dm_get_category_history_size);

//...

}

继续使用前3节的假设。窗口A使用了 CustomList 窗体模板。 从A 进入到B 时,

EntryNewScreen函数调用了我们设置的获取guiBuffer函数dm_get_category_history ,它先保存了A中InputBox的属性,再保存A中List的属性 —— 此时它将把输入框的属性覆盖掉。当从B窗口返回到A窗口时,ShowCategoryCustomListScreen()函数先把history_buffer传给了 set_list_menu_category_history , 由于guiBuffer中存储的是List的数据,因此在交付fixed_list_goto_item_no_redraw 进行设置属性的时候,不会出现问题。但

wgui_setup_singleline_inputbox()就会因为guiBuffer中存储的不是输入框存储的数据而出现错误。

因此,权宜之计是,将更改传给wgui_setup_singleline_inputbox的入参: wgui_setup_singleline_inputbox(

0, 0,

240, 320,

custom_single_input_buffer, 50,

MMI_CATEGORY_CUSTOM_LIST, get_string(right_softkey),

get_image(right_softkey_icon),

INPUT_TYPE_ALPHANUMERIC_LOWERCASE | INPUT_TYPE_USE_ONLY_ENGLISH_MODES,

NULL,

0);

这样,虽然死机 bug 避免了,但是特定情况下 InputBox 需要保存的属性,将全部丢失掉.因此,更加合适的做法是,提取 dm_setup_category_functions()中使用的函数接口:

get_singleline_inputbox_category_history get_list_menu_category_history

这样可以分别获取 inputbox 和 list 的属性,然后将得到的两个属性的数据连续存放在一块动态分配的Buffer中.如200号窗口的GetCategroyHistory函数所示: U8 *GetCategory200History(U8 *history_buffer) {

S32 s;

get_list_menu_category_history(MMI_CATEGORY200_ID, history_buffer);

s = sizeof(list_menu_category_history); s = (s + 3) / 4; s *= 4;

get_singleline_inputbox_category_history(MMI_CATEGORY200_ID, (U8*) (history_buffer + s), MMI_current_input_type); return (history_buffer); }

要注意的问题是,系统为guiBuffer分配空间时,依据的是 MAX_GUI_BUFFER,而不是

dm_setup_category_functions()所指定的获取guiBuffer大小的函数GetCategoryHistorySize.而且系统中从未使用过该函数指针所指向的函数。奇怪的是—— 所有窗体模板的制作者都兢兢业业地制作了这个获取guiBuffer大小的函数。在200号窗口里,获取guiBuffer大小的函数如下: S32 GetCategory200HistorySize(void) {

return (((sizeof(list_menu_category_history) + 3) / 4) * 4 +

sizeof(singleline_inputbox_category_history)); }

如果怕出错且不怕麻烦的话,也可以未雨绸缪的写一个这样的函数,但恐怕多半是用不上的。 最后的任务就是在显示窗体时分别获取各控件的guiBuffer,然后将这些GUI_Buffer分别传给各个控件的Set函数.

仍然参看200号窗体的代码实现:

h_flag = set_list_menu_category_history(MMI_CATEGORY200_ID, history_buffer);

//第一个控件的Gui_Buffer

if (h_flag) {

S32 s = sizeof(list_menu_category_history);

s = (s + 3) / 4; s *= 4;

dynamic_list_goto_item_no_redraw(MMI_fixed_list_menu.highlighted_item); wgui_setup_singleline_inputbox(

input_box_x, (input_box_y), input_box_width, search_box_height, buffer,

buffer_max_length, MMI_CATEGORY200_ID, get_string(right_softkey), get_image(right_softkey_icon), INPUT_TYPE_MMI_MULTITAP_BPMF, (U8*) (history_buffer + s), 1);

//第二个控件的GUI_Buffer起始

五、小结

经过这些研究和借鉴.我们在窗体开发工作中所需的技术点已经逐步趋于完善了。后期开发工作中,可能还有一些GUI相关的探索工作需要进行。据现在的情况来看,主要有两方面:

1.inline控件及57号窗体的进一步研究。

必要性很明显。现在使用中的NumberTune就很有必要嵌入到这个57号模板中。对于Inline控

件的管理方式的研究是这个工作的前提。

2.touch panel的相关研究。

主要看控件的touch panel编译开关内的代码实现。将有助于平台移植。

3.现有控件的进一步熟悉。

如果有可利用的现有控件的话,尽量避免移植自己的控件上来。这对于系统的稳定和GUI的移植都

不利。

附1:其他 History Info 结构体 typedef struct _history {

U16 scrnID;

FuncPtr entryFuncPtr;

U8 inputBuffer[MAX_INPUT_BUFFER]; U8 guiBuffer[MAX_GUI_BUFFER]; } history;

typedef struct _historyCallback {

U16 scrnID;

HistoryDelCBPtr historydelCBPtr; } historyCallback;

附2:函数接口

(1)void AddHistory(history addHistory);

//添加历史记录节点

(2)void GoBackHistory(void);

//删除历史记录堆栈顶端节点,并执行EntryFunction(EntryNewScreen指定的函数)

(3)void DeleteNHistory(U16 DeleteCount);

//删除N个节点

(4)U8 GetHistory(U16 ScreenID,history *ptrHistory);

//通过窗口号获取历史记录数据

(5)U8 GoBackToHistory(U16 ScreenID);

//返回到指定的窗口号

(6)void GoBacknHistory(U16 nCount);

//删除nCount个历史记录节点,并执行第nCount+1个EntryFunction

(7)U8* GetCurrGuiBuffer(U16 ScreenID);

//按窗口号获取指定的Gui_Buffer

(8)U8* GetCurrInputBuffer(U16 ScreenID);

//按窗口号获取指定的Input_Buffer

(9)void ExecutecurrHisIndEntryFunc(void);

//获取当前在栈顶的历史记录节点的EntryFunction.

(10)U8 GoBeyondMarkerScr(U16 ScreenID);

//删除从当前窗口号到指定窗口号之间的一切历史记录节点,并执行最近的EntryFuntion

(11)U8 DeleteUptoScrID(U16 ScreenID);

//删除从当前窗口号到指定窗口号之间的一切历史记录节点,但不执行EntryFuntion

因篇幅问题不能全部显示,请点此查看更多更全内容