分享
 
 
 

PalmOS开发教程-7

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

第七章 列表框和排序

在这一章中,将接触到一些新的控件和数据库操作技巧。我们先生成一个窗体来显示contact数据库中的所有记录,然后创建一个下拉框供选择排序标准,最后添加代码进行排序,并使新创建或修改过的记录也能够在列表中正确排列。

保存工程

按我们的习惯先保存工程,步骤如下:

1. 运行Windows浏览器;

2. 找到保存现有工程的文件夹;

3. 按下CTRL-C复制;

4. 选择目的文件夹;

5. 按下CTRL-V将现有工程粘贴到目的文件夹;

6. 将其重命名为容易记忆的名字,我将其命名为Contacts CH.6。

列表框

列表框能够显示许多文本条目并允许从中选择,我们可以对每个条目进行浏览或选择。我们还可以通过调用函数浏览列表或选择条目。当然,我们可以画出自己的列表框,在缺省情况下,列表框由Palm系统绘制。

Contacts.rsrc的内容添加

在这一部分中,我们将向应用程序添加一个新的窗体。此窗体将以列表的形式显示数据库记录,并允许我们浏览或选择。如果选中了其中的一条记录,记录内容将从Contact Detail 窗体上显示出来。

Contact Detail 窗体的内容添加

由于Contact Detail 窗体被调用来返回一条记录内容,所以我们需要添加一个Done按钮。步骤如下:

1. 运行构造器;

2. 打开资源文件Contact.rsrc,它位于工程文件夹的src文件夹里面;

3. 双击打开;

4. 选择Windows | Catalog打开控件模板;

5. 向Contact Detail 窗体拖动一个按钮;

6. 设置按钮属性Object Identifier为Done,Left Origin=1,Top Origin=147,Label为Done;

7. 添加完成后,所得按钮如下图所示,选择右上角的X按钮关闭编辑器。

在Contact List窗体上创建菜单栏

在创建Contact List窗体前,我们先创建一个菜单;这样当设置窗体的属性时,就可以直接写入此菜单栏的ID了。

1. 在Resource Type and Name 列表框中选择Menu Bars并按下CTRL-K,于是一个新的菜单就产生了,将其重命名为Contact List;

2. 双击打开;

3. 拖动Option菜单到Contact List菜单栏上;

4. 完成后应如下图所示,点击右上角的X按钮关闭菜单栏。

创建Contact List窗体

现在我们来创建Contact List 窗体,步骤如下:

1. 点击Forms,并按下CTRL-K创建一个新的窗体;

2. 点击窗体名称将其命名为Contact List;

3. 在窗体编辑器中双击打开Contact List 窗体;

4. 为Contact List 添加标题,并使菜单栏的ID和Contact List 窗体的菜单栏的ID相匹配。

创建列表框(List)

1. 从Catalog窗口中拖动一个列表框(List)到窗体上。它的属性如表7-1所示;

Object Identifier 在资源头文件中,构造器用之代表资源ID

List ID 列表框的资源ID

Left Origin 水平方向上控件的最左端位置

Top Origin 垂直方向上控件的最顶端位置

Width 列表的宽度

Usable 决定此控件是否可见能用,如果没有选中,也可在通过函数调用来实现其可见

Font 列表项的字体

Visible Items 列表框可显示的行数。注意,实际的列表条目数比此值可多可少

List Items 动态的初始化列表条目,如果想添加条目,按CTRL-K

2. 设置属性Object Identifier 为List,Left Origin=0,Top Origin=12,Width=160,Visible Items=12;

3. 完成后窗体如下图所示,按下右上角的X按钮关闭窗体,并选File | Save保存所做修改。

添加内存错误警告

在这一章里,我们将开始添加错误处理代码。在内存溢出时,系统将发出警告信息:

1. 点击Resource Type and Name列表中的Alerts 并按下CTRL-K创建一个新的警报;

2. 将其命名为MemoryError;

3. 双击,在编辑器中打开;

4. 设置属性Alert Type 为Error,标题(title)为Fatal Error,信息(message)为“I have run out of memory.”

5. 完成后,如下图所示:

Contact.c的内容添加

现在添加代码以完成新窗体的功能。首先,先在前几章中异常处理的忽略处添加代码进行异常处理。

异常处理

在这一章中,有很多地方可能会造成内存溢出。因此,我们必须添加严密的异常处理程序。下面我们将利用异常处理器(Error Manager)来完成,在The Palm OS SDK Reference 中对异常处理器有详细的论述。

首先,在PilotMain()前,定义异常退出(Exit)宏:

// CH.7 The error exit macro

#define errorExit(alert) { ErrThrow( alert ); }

异常处理函数ErrThrow()有些与众不同,它与宏ErrTry和ErrCatch()配合作用,类似C++和Java的形式给出异常处理。如果在ErrTry模块中的任何代码调用了ErrThrow()函数,控件就会立即调用ErrCatch()函数。下面我们看看在PilotMain()中做了哪些修改。

在事件loop 产生前先添加宏ErrTry:

// CH.7 Begin the try block

ErrTry {

// CH.2 Our event loop

do

{

结束宏ErrTry并添加ErrCatch() 函数:

// CH.7 End the try block and do the catch block

}

ErrCatch( errorAlert )

{

// CH.7 Display the appropriate alert

FrmAlert( errorAlert );

} ErrEndCatch

此函数将根据ErrThrow()传给ErrCatch()的ID号显示其相应的异常警报。然后,应用程序将在catch模块后正常退出,正常执行关闭数据库诸如此类的操作。最后,PilotMain()将控制权归还给Palm OS。

Contact List 窗体的切换

下面,我们添加代码来实现Contact List 窗体和Contact Detail窗体之间的切换。在PilotMain()中,修改初始化Contact Detail窗体的代码如下:

// CH.7 Choose our starting page

// CH.5 If there are no records, create one

if( numRecords == 0 )

{

newRecord();

FrmGotoForm( ContactDetailForm );

}

else

FrmGotoForm( ContactListForm );

在切换到Contact List窗体时,判断如果数据库中没有记录,就要创建一条新记录;一般情况下,数据库中都是有记录的,我们可以直接调用Contact List窗体。

为使Contact List窗体能正确的初始化,在loop事件中的frmLoadEvent处理代码中再添加case项:

// CH.7 Contact List form

case ContactListForm:

form = FrmInitForm( ContactListForm );

FrmSetEventHandler( form, contactListHandleEvent );

break;

下面的代码是如何处理Contact Detail 窗体中的Done按钮事件:

// CH.7 Done button

case ContactDetailDoneButton:

{

// CH.7 Load the contact list

FrmGotoForm( ContactListForm );

}

break;

为了在窗体间切换,而不是弹出窗体,我们使用了函数FrmGotoform()来初始化。这个函数首先调用frmCoseEvent关闭前一个窗体,然后调用frmLoadEvent和frmOpenEvent打开新的窗体。

Contact List窗体事件处理函数

下面我们为Contact List 窗体来添加事件处理函数。首先,在文件的开头加入原型:

static Boolean contactListHandleEvent( EventPtr event );

另外,还需要两个变量表示给列表框分配内存的句柄:

// CH.7 Contact list variables

static VoidHand hchoices; // CH.7 Handle to packed choices

static VoidHand hpchoices; // CH.7 Handle to pointers

下面是事件处理函数:

// CH.7 Our Contact List form event handler function

static Boolean contactListHandleEvent( EventPtr event )

{

FormPtr form; // CH.7 A form structure pointer

// CH.7 Get our form pointer

form = FrmGetActiveForm();

// CH.7 Parse events

switch( event->eType )

{

// CH.7 Form open event

case frmOpenEvent:

{

// CH.7 Draw the form

FrmDrawForm( form );

// CH.7 Build the list

buildList();

}

break;

// CH.7 Form close event

case frmCloseEvent:

{

// CH.7 Unlock and free things here

MemHandleUnlock( hpchoices );

MemHandleFree( hpchoices );

MemHandleUnlock( hchoices );

MemHandleFree( hchoices );

hchoices = 0;

}

break;

// CH.7 Respond to a list selection

case lstSelectEvent:

{

// CH.7 Set the database cursor to the selected contact

cursor = event->data.lstSelect.selection;

// CH.7 Go to contact details

FrmGotoForm( ContactDetailForm );

}

break;

// CH.7 Respond to a menu event

case menuEvent:

return( menuEventHandler( event ) );

// CH.7 Respond to the popup trigger

case popSelectEvent:

{

// CH.7 If there is no change, we're done

if( sortBy == event->data.popSelect.selection )

return( true );

// CH.7 Modify sort order variable

sortBy = event->data.popSelect.selection;

// CH.7 Sort the contact database by the new criteria

DmQuickSort( contactsDB, (DmComparF*)sortFunc, sortBy );

// CH.7 Rebuild the list

buildList();

}

break;

} // CH.7 End of the event switch statement

// CH.7 We're done

return( false );

}

事件处理函数以十分标准的形式开始:首先建立了一个窗体指针变量,它将在调用frmOpenEvent时被调用;然后窗体被绘制并调用了一个新的函数buildList(),这个函数在下一部分将详细讨论。

我们调用frmCloseEvent释放用来储存列表框内容的内存。之所以在关闭(close)事件中释放内存,是因为只要列表框可见、窗体在使用的时候,这一部分内存就一直被Palm OS使用。在这一点上,列表控件和其它控件有所不同,一般的控件都有自己的内存,而对于列表控件来说,它需要另外分配内存。

下面,讲述一个新的事件处理函数——lstSelectEvent。当列表的条目被选中时,此事件将被触发。变量selection标明哪一个条目被选中,列表得记录从0开始。这样我们使用起来就十分方便了,只要将当前记录等于列表变量selection的值,然后调用Contact Detail窗体,窗体就会使用前面所设置的变量cursor产生正确的记录。

最后,对本程序菜单事件的处理和对Contact Detail窗体的菜单处理几乎相同。这样,对Contact List窗体的处理程序就算完成了。

函数buildList()

下面我们来讨论这个实用函数buildList(),这个函数通过浏览数据库,为每条记录创建一文本字符串,然后使用这些字符串填充List对象的各个条目。

static void buildList( void )

{

FormPtr form; // CH.6 A form structure pointer

Int choice; // CH.7 The list choice we're doing

CharPtr precord; // CH.7 Pointer to a record

Char listChoice[dateStringLength + 1 + // CH.7 We

timeStringLength + 1 + // build

DB_FIRST_NAME_SIZE + // list

DB_LAST_NAME_SIZE]; // choices here

// CH.7 The current list choice

CharPtr pchoices; // CH.7 Pointer to packed choices

UInt offset; // CH.7 Offset into packed strings

VoidPtr ppchoices; // CH.7 Pointer to pointers to choices

// CH.6 Get our form pointer

form = FrmGetActiveForm();

在声明变量后,函数象往常一样获取窗体指针。这里面最有趣的变量是char array ,它将保证列表字符串的连续性。为使阵列(array)有足够的大小来保存任何Palm OS的系统时间和日期,可以使用Palm OS常量dateStringLength和timeStringLength。

// CH.7 Put the list choices in a packed string

for( choice = 0; choice < numRecords; choice++ )

{

// CH.7 Get the record

hrecord = DmQueryRecord( contactsDB, choice );

precord = MemHandleLock( hrecord );

// CH.7 Get the date and time

MemMove( &dateTime, precord + DB_DATE_TIME_START,

sizeof( dateTime ) );

首先,浏览数据库的所有记录,然后获取记录将变量dateTime设置为在所得记录中的时间和日期。注意到我们使用了DmQueryRecord()函数来获取记录句柄。此函数只给我们提供一个只读的记录副本。

下面,我们创建表示记录的字符串:

// CH.7 Clear the list choice string

*listChoice = '\0';

// CH.7 Add the date string if any

if( dateTime.year != NO_DATE )

{

DateToAscii( dateTime.month, dateTime.day,

dateTime.year,

(DateFormatType)PrefGetPreference(

prefDateFormat ), listChoice );

StrCat( listChoice, " " );

}

// CH.7 Add the time string if any

if( dateTime.hour != NO_TIME )

{

TimeToAscii( dateTime.hour, dateTime.minute,

(TimeFormatType)PrefGetPreference(

prefTimeFormat ), listChoice +

StrLen( listChoice ) );

StrCat( listChoice, " " );

}

// CH.7 Append the first name

StrCat( listChoice, precord + DB_FIRST_NAME_START );

StrCat( listChoice, " " );

// CH.7 Append the last name

StrCat( listChoice, precord + DB_LAST_NAME_START );

在这里我们将date,time,first name和last name都写入了字符串。注意到函数DateToAscii()和函数TimeToAscii()运行需要花很多时间。对本程序来说调用这两个函数还算可以,但是如果要提高处理记录的速度,就必须考虑将时间和日期保存在单个记录来提高速度。

下一步,我们将为这些字符串分配内存,这样做是因为列表框在填充新条目时需要一定的格式。

// CH.7 Allocate memory for the list entry string

// CH.7 If this is the first choice

if( hchoices == 0 )

{

// CH.7 Allocate the storage for the choice

if( (hchoices = MemHandleNew(

StrLen( listChoice ) + 1 )) == 0 )

errorExit( MemoryErrorAlert );

// CH.7 Initial offset points to the start

offset = 0;

}

上面就是字符串没有创建时的代码,在给字符串分配内存时,如果失败了,就调用新的错误处理函数来处理。然后我们初始化偏移(offset)说明在哪里将字符串置零。

下面是字符串已经创建好的情况:

else

// CH.7 If this is a subsequent choice

{

// CH.7 Unlock

MemHandleUnlock( hchoices );

// CH.7 Resize

if( MemHandleResize( hchoices, offset +

StrLen( listChoice ) + 1 ) )

errorExit( MemoryErrorAlert );

}

在这里我们将锁定的内存解锁(Unlock),使之能储存下一个字符串。

接着我们将字符串写入包列表(Packed List):

// CH.7 Lock

pchoices = MemHandleLock( hchoices );

// CH.7 Copy the string into the memory

StrCopy( pchoices + offset, listChoice );

offset += StrLen( listChoice ) + 1;

// CH.7 Unlock the record

MemHandleUnlock( hrecord );

}

首先我们锁定主块(chunk),然后将新建字符串拷入。做完这些工作后,解锁我们所使用的数据库记录句柄。注意,我们没有调用函数DmReleaseRecord()是因为我们用函数DmQueryRecord()代替了DmGetRecord()。

循环操作完成后,我们已有了一个包含每个数据库记录的列表字符串包。现在我们将这些选项发送到列表对象显示:

// CH.7 Create a pointer array from the packed string list

if( (hpchoices = SysFormPointerArrayToStrings( pchoices,

numRecords )) == 0 )

errorExit( MemoryErrorAlert );

ppchoices = MemHandleLock( hpchoices );

// CH.7 Set the list choices

LstSetListChoices( getObject( form, ContactListListList ),

ppchoices, numRecords );

// CH.7 Draw the list

LstDrawList( getObject( form, ContactListListList ) );

// CH.7 We're done

return;

}

我们使用函数SysFormPointerArrayToStrings()建立了一个指向所建包列表的指针。这样包列表的指针就确定了。

函数LstSetListChoice()利用包列表的指针来填充每个条目。注意,此函数将清空所有已存在的条目,所以在向此函数发送列表项时,必须是完全的包列表条目。

记住在程序的最后,通过窗体关闭(Form Close)事件来释放所有的内存。

在这一部分的最后,我们绘出了列表框。

调试

在这一类程序的调试中,最好是使用单步调试来检查程序是否能够正常运行。首先调试刚创建的新函数builList()。在函数的开始处设置一个断点,单步运行,看程序能否顺利运行,特别注意一下内存在最后是否得到释放。

从列表中选中一条记录,看是否能从Contact Detail窗体正确显示,再按下Contact Detail窗体上的Done按钮返回。在Contact Detail窗体上新添加一条记录,再从列表中选择此记录,看能否在Contact Detail窗体上正确显示。

排序

这一部分除了讲述排序外,还将接触到两个新控件,即弹出触发按钮(pop-up triggers)和弹出列表框(pop-up lists)。把弹出列表框加入Contact Detail窗体中可允许我们从其中选择不同的排序选项作为排序标准。最后,我们将添加代码创建或修改在Contact Detail窗体的相应记录,使之能够根据排序标准插入到正确的位置。

弹出触发按钮(pop-up triggers)

弹出触发按钮和普通按钮很相似。它们都有一个标签并响应触发事件。它的特殊之处在于它能够和一个弹出列表框相关联。当按下弹出触发按钮时,弹出列表框就会显示。如果选中列表框上的条目,就会产生事件popSelectEvent。当然,我们还可以利用弹出触发按钮和列表框相关联做一些其它的工作。

对Contacts.rsrc内容的添加

现在向Contact List窗体添加弹出列表框。它将允许我们依据三个排序标准进行排序:时间日期、姓(first name)、名(last name)。

1. 运行构造器;

2. 打开资源文件Contact.rsrc,它位于工程文件夹的Src文件夹中;

3. 双击打开Contact List窗体;

4. 选择Windows | Catalog打开控件面板;

5. 从Catalog窗口中拖动一个标签到窗体上。修改属性为:Left Origin=0,Top origin=149,Label为Sort By:;

6. 拖动一个弹出列表框到窗体上。修改属性维:Object Identifier 为SortList,Left Origin=40,Top Origin=125,Visible Items=3。在下面我们会看到它的位置和新添加的弹出触发按钮正好对齐;

7. 单击选中List Items,然后按下CTRL-K产生第一个条目。修改条目文本为Date and Time;

8. 单击选中List Items,然后按下CTRL-K产生第二个条目。修改条目文本为First Name;

9. 单击选中List Items,然后按下CTRL-K产生第三个条目。修改条目文本为Last Name。

创建弹出触发按钮

1. 拖动一个弹出触发按钮到Contact List 窗体上,其属性如表7-2所示;

Object Identifier 在资源头文件中,构造器用之代表资源ID

Popup ID 弹出触发按钮的资源ID

Left Origin 水平方向上控件的最左端位置

Top Origin 垂直方向上控件的最顶端位置

Width 按钮的宽度

Height 按钮的高度

Usable 用来定义控件是否可见及可用,如果不设置,也可在通过函数调用来实现其可见

Anchor Left 决定当文本长度改变时,按钮文本是以左侧还是右侧为锚点扩展,选中时,按钮文本将向右侧扩展。

Font 标签文本的字体

Label 标签上的文本内容

List ID 和弹出触发按钮相关联的弹出列表框的ID

2.修改属性为:Object Identifier为Trigger,Left Origin=40,Top Origin=149,Width=80,Label 为Date and Time,将List ID设置为刚创建的弹出列表框的ID;

3. 在对Contact List窗体修改完毕,其图如下所示。单击窗体编辑器右上角的X按钮关闭,选择File | Save 保存所做修改。

对Contacts.c内容的添加

首先,在文件头添加排序所必需的变量和常量:

// CH.7 The sort order variable and constants

static Int sortBy;

// CH.7 NOTE: These items match the popup list entries!

#define SORTBY_DATE_TIME 0

#define SORTBY_FIRST_NAME 1

#define SORTBY_LAST_NAME 2

变量sortBy表示在三个排序标准中的当前值。三个常量代表了弹出列表框中的三个排序标准,注意使它们和列表框的每个选项相对应。

排序的初始化

为了建立排序标准,必须添加代码来处理popSelectEvent事件,此事件在弹出列表框的列表选项被选中时触发。下面的代码是如何根据所选项进行排序:

case popSelectEvent:

{

// CH.7 If there is no change, we're done

if( sortBy == event->data.popSelect.selection )

return( true );

// CH.7 Modify sort order variable

sortBy = event->data.popSelect.selection;

// CH.7 Sort the contact database by the new criteria

DmQuickSort( contactsDB, (DmComparF*)sortFunc, sortBy );

// CH.7 Rebuild the list

buildList();

}

break;

首先,在排序标准没有改变时,避免重复排序。然后保存排序标准,因为在Contact Detail窗体中当有新记录或修改记录后,排序发生了改变,这时又用到了排序标准。最后调用函数DmQuickSort(),此函数将排序标准sortBy传递给函数sortFunc()。在数据库排序完成后,绘制列表框显示。

下面我们研究一下函数sortFunc()。这个函数可以比较前后两个条目的大小,在将数据库中的两条记录比较后,函数返回一个整数。如果此数大于零,则第一个记录排在前面;如果小于零则第二条记录排在前面;如果为零则说明两记录相同。

// CH.7 This function is called by Palm OS to sort records

static Int sortFunc( CharPtr precord1, CharPtr precord2, Int sortBy )

{

Int sortResult;

// CH.7 Switch based on sort criteria

switch( sortBy )

{

// CH.7 Sort by date and time

case SORTBY_DATE_TIME:

{

DateTimePtr pdateTime1;

DateTimePtr pdateTime2;

Long lDiff;

pdateTime1 = (DateTimePtr)(precord1 + DB_DATE_TIME_START);

pdateTime2 = (DateTimePtr)(precord2 + DB_DATE_TIME_START);

// CH.7 Compare the dates and times

lDiff = (Long)(TimDateTimeToSeconds( pdateTime1 ) / 60 ) -

(Long)(TimDateTimeToSeconds( pdateTime2 ) / 60 );

// CH.7 Date/time #1 is later

if( lDiff > 0 )

sortResult = 1;

else

// CH.7 Date/time #2 is later

if( lDiff < 0 )

sortResult = -1;

else

// CH.7 They are equal

sortResult = 0;

}

break;

代码中首先将时间和日期从所选记录中抽出,然后将其转换成秒进行比较。在这个算法中会将无日期的记录放在列表框的顶部,而将无时间的记录放在相同日期的最底部。由于所转换的秒值有可能超过16位整值,所以我们采用32位长整数来进行比较,相应的设置变量sortResult为16位。

// CH.7 Sort by first name

case SORTBY_FIRST_NAME:

{

sortResult = StrCompare( precord1 + DB_FIRST_NAME_START,

precord2 + DB_FIRST_NAME_START );

}

break;

// CH.7 Sort by last name

case SORTBY_LAST_NAME:

{

sortResult = StrCompare( precord1 + DB_LAST_NAME_START,

precord2 + DB_LAST_NAME_START );

}

break;

调用函数StrCompare()为first name和last name 排序,此函数定义在Developing Palm OS 3.0 Applications Part II:Sytem Management中。它和ANSI C的函数strcmp()十分相似,直接给Palm OS返回一个整值。

排序的记录写入列表框

在移动到另一个记录或退出Contact Detail窗体时,函数getField()都要被调用,因此应该在此函数中添加代码,来保证在添加新记录或修改记录后重新排序。

为达到要求,这些代码应添加在记录内存已被释放但“脏(dirty)”位还没有清除的位置。

// CH.5 If the record has been modified

if( isDirty )

{

CharPtr precord; // CH.5 Points to the DB record

// CH.7 Detach the record from the database

DmDetachRecord( contactsDB, cursor, &hrecord );

// CH.5 Lock the record

precord = MemHandleLock( hrecord );

// CH.5 Get the text for the First Name field

getText( getObject( form, ContactDetailFirstNameField ),

precord, DB_FIRST_NAME_START );

// CH.5 Get the text for the Last Name field

getText( getObject( form, ContactDetailLastNameField ),

precord, DB_LAST_NAME_START );

// CH.5 Get the text for the Phone Number field

getText( getObject( form, ContactDetailPhoneNumberField ),

precord, DB_PHONE_NUMBER_START );

// CH.7 Find the proper position

cursor = DmFindSortPosition( contactsDB, precord, NULL,

(DmComparF*)sortFunc, sortBy );

// CH.5 Unlock the record

MemHandleUnlock( hrecord );

// CH.7 Reattach the record

DmAttachRecord( contactsDB, &cursor, hrecord, NULL );

}

既然只是在记录被改变时重新排序,因此可以先使用isDirty位来判断记录是否已被修改。然后将“脏”的记录从数据库中临时分离出来并将之锁定,最后调用函数DmFindSortPosition()决定应该在列表框的什么地方插入。此函数的前提示列表已经有了正确的排序,由于我们一次只改变或添加一条记录,因此可以满足要求。最后解锁该记录并将之插入到数据库的新位置。

调试

分别在popSelectEvent事件的顶部、函数sortBy()的顶部、函数getField()的if语句的开始处设置断点。检查程序是否能正常运行。下面是一些功能的测试:

l 用三个排序标准分别排序,看数据库是否能根据各个标准正确的排序;

l 向数据库中添加一个记录,看其是否能排在正确的位置;

l 修改一个现存记录,看其是否能排在正确的位置。

下一步做什么

下一章我们将添加更多新的控件,例如表和浏览栏。这一章我们使用列表框显示程序的记录信息已是个不错的方法,下一章我们将修改程序,用表控件来做到这一点。

程序列表

// CH.2 The super-include for the Palm OS

#include <Pilot.h>

// CH.5 Added for the call to GrfSetState()

#include <Graffiti.h>

// CH.3 Our resource file

#include "Contacts_res.h"

// CH.4 Prototypes for our event handler functions

static Boolean contactDetailHandleEvent( EventPtr event );

static Boolean aboutHandleEvent( EventPtr event );

static Boolean enterTimeHandleEvent( EventPtr event );

static Boolean contactListHandleEvent( EventPtr event );

static Boolean menuEventHandler( EventPtr event );

// CH.4 Constants for ROM revision

#define ROM_VERSION_2 0x02003000

#define ROM_VERSION_MIN ROM_VERSION_2

// CH.5 Prototypes for utility functions

static void newRecord( void );

static VoidPtr getObject( FormPtr, Word );

static void setFields( void );

static void getFields( void );

static void setText( FieldPtr, CharPtr );

static void getText( FieldPtr, VoidPtr, Word );

static void setDateTrigger( void );

static void setTimeTrigger( void );

static void setTimeControls( void );

static void buildList( void );

static Int sortFunc( CharPtr, CharPtr, Int );

// CH.5 Our open database reference

static DmOpenRef contactsDB;

static ULong numRecords;

static UInt cursor;

static Boolean isDirty;

static VoidHand hrecord;

// CH.5 Constants that define the database record

#define DB_ID_START 0

#define DB_ID_SIZE (sizeof( ULong ))

#define DB_DATE_TIME_START (DB_ID_START +\

DB_ID_SIZE)

#define DB_DATE_TIME_SIZE (sizeof( DateTimeType ))

#define DB_FIRST_NAME_START (DB_DATE_TIME_START +\

DB_DATE_TIME_SIZE)

#define DB_FIRST_NAME_SIZE 16

#define DB_LAST_NAME_START (DB_FIRST_NAME_START +\

DB_FIRST_NAME_SIZE)

#define DB_LAST_NAME_SIZE 16

#define DB_PHONE_NUMBER_START (DB_LAST_NAME_START +\

DB_LAST_NAME_SIZE)

#define DB_PHONE_NUMBER_SIZE 16

#define DB_RECORD_SIZE (DB_PHONE_NUMBER_START +\

DB_PHONE_NUMBER_SIZE)

// CH.6 Storage for the record's date and time in expanded form

static DateTimeType dateTime;

static Word timeSelect;

#define NO_DATE 0

#define NO_TIME 0x7fff

// CH.7 The error exit macro

#define errorExit(alert) { ErrThrow( alert ); }

// CH.7 Contact list variables

static VoidHand hchoices; // CH.7 Handle to packed choices

static VoidHand hpchoices; // CH.7 Handle to pointers

// CH.7 The sort order variable and constants

static Int sortBy;

// CH.7 NOTE: These items match the popup list entries!

#define SORTBY_DATE_TIME 0

#define SORTBY_FIRST_NAME 1

#define SORTBY_LAST_NAME 2

// CH.2 The main entry point

DWord PilotMain( Word cmd, Ptr, Word )

{

DWord romVersion; // CH.4 ROM version

FormPtr form; // CH.2 A pointer to our form structure

EventType event; // CH.2 Our event structure

Word error; // CH.3 Error word

// CH.4 Get the ROM version

romVersion = 0;

FtrGet( sysFtrCreator, sysFtrNumROMVersion, &romVersion );

// CH.4 If we are below our minimum acceptable ROM revision

if( romVersion < ROM_VERSION_MIN )

{

// CH.4 Display the alert

FrmAlert( LowROMVersionErrorAlert );

// CH.4 PalmOS 1.0 will continuously re-launch this app

// unless we switch to another safe one

if( romVersion < ROM_VERSION_2 )

{

AppLaunchWithCommand( sysFileCDefaultApp,

sysAppLaunchCmdNormalLaunch, NULL );

}

return( 0 );

}

// CH.2 If this is not a normal launch, don't launch

if( cmd != sysAppLaunchCmdNormalLaunch )

return( 0 );

// CH.5 Create a new database in case there isn't one

if( ((error = DmCreateDatabase( 0, "ContactsDB-PPGU", 'PPGU', 'ctct',

false )) != dmErrAlreadyExists) && (error != 0) )

{

// CH.5 Handle db creation error

FrmAlert( DBCreationErrorAlert );

return( 0 );

}

// CH.5 Open the database

contactsDB = DmOpenDatabaseByTypeCreator( 'ctct', 'PPGU',

dmModeReadWrite );

// CH.5 Get the number of records in the database

numRecords = DmNumRecords( contactsDB );

// CH.5 Initialize the record number

cursor = 0;

// CH.7 Choose our starting page

// CH.5 If there are no records, create one

if( numRecords == 0 )

{

newRecord();

FrmGotoForm( ContactDetailForm );

}

else

FrmGotoForm( ContactListForm );

// CH.7 Begin the try block

ErrTry {

// CH.2 Our event loop

do

{

// CH.2 Get the next event

EvtGetEvent( &event, -1 );

// CH.2 Handle system events

if( SysHandleEvent( &event ) )

continue;

// CH.3 Handle menu events

if( MenuHandleEvent( NULL, &event, &error ) )

continue;

// CH.4 Handle form load events

if( event.eType == frmLoadEvent )

{

// CH.4 Initialize our form

switch( event.data.frmLoad.formID )

{

// CH.4 Contact Detail form

case ContactDetailForm:

form = FrmInitForm( ContactDetailForm );

FrmSetEventHandler( form, contactDetailHandleEvent );

break;

// CH.4 About form

case AboutForm:

form = FrmInitForm( AboutForm );

FrmSetEventHandler( form, aboutHandleEvent );

break;

// CH.6 Enter Time form

case EnterTimeForm:

form = FrmInitForm( EnterTimeForm );

FrmSetEventHandler( form, enterTimeHandleEvent );

break;

// CH.7 Contact List form

case ContactListForm:

form = FrmInitForm( ContactListForm );

FrmSetEventHandler( form, contactListHandleEvent );

break;

}

FrmSetActiveForm( form );

}

// CH.2 Handle form events

FrmDispatchEvent( &event );

// CH.2 If it's a stop event, exit

} while( event.eType != appStopEvent );

// CH.7 End the try block and do the catch block

}

ErrCatch( errorAlert )

{

// CH.7 Display the appropriate alert

FrmAlert( errorAlert );

} ErrEndCatch

// CH.5 Close all open forms

FrmCloseAllForms();

// CH.5 Close the database

DmCloseDatabase( contactsDB );

// CH.2 We're done

return( 0 );

}

// CH.4 Our Contact Detail form handler function

static Boolean contactDetailHandleEvent( EventPtr event )

{

FormPtr form; // CH.3 A pointer to our form structure

VoidPtr precord; // CH.6 Points to a database record

// CH.3 Get our form pointer

form = FrmGetActiveForm();

// CH.4 Parse events

switch( event->eType )

{

// CH.4 Form open event

case frmOpenEvent:

{

// CH.2 Draw the form

FrmDrawForm( form );

// CH.5 Draw the database fields

setFields();

}

break;

// CH.5 Form close event

case frmCloseEvent:

{

// CH.5 Store away any modified fields

getFields();

}

break;

// CH.5 Parse the button events

case ctlSelectEvent:

{

// CH.5 Store any field changes

getFields();

switch( event->data.ctlSelect.controlID )

{

// CH.5 First button

case ContactDetailFirstButton:

{

// CH.5 Set the cursor to the first record

if( cursor > 0 )

cursor = 0;

}

break;

// CH.5 Previous button

case ContactDetailPrevButton:

{

// CH.5 Move the cursor back one record

if( cursor > 0 )

cursor--;

}

break;

// CH.5 Next button

case ContactDetailNextButton:

{

// CH.5 Move the cursor up one record

if( cursor < (numRecords - 1) )

cursor++;

}

break;

// CH.5 Last button

case ContactDetailLastButton:

{

// CH.5 Move the cursor to the last record

if( cursor < (numRecords - 1) )

cursor = numRecords - 1;

}

break;

// CH.5 Delete button

case ContactDetailDeleteButton:

{

// CH.5 Remove the record from the database

DmRemoveRecord( contactsDB, cursor );

// CH.5 Decrease the number of records

numRecords--;

// CH.5 Place the cursor at the first record

cursor = 0;

// CH.5 If there are no records left, create one

if( numRecords == 0 )

newRecord();

}

break;

// CH.5 New button

case ContactDetailNewButton:

{

// CH.5 Create a new record

newRecord();

}

break;

// CH.7 Done button

case ContactDetailDoneButton:

{

// CH.7 Load the contact list

FrmGotoForm( ContactListForm );

}

break;

// CH.6 Date selector trigger

case ContactDetailDateSelTrigger:

{

// CH.6 Initialize the date if necessary

if( dateTime.year == NO_DATE )

{

DateTimeType currentDate;

// CH.6 Get the current date

TimSecondsToDateTime( TimGetSeconds(),

&currentDate );

// CH.6 Copy it

dateTime.year = currentDate.year;

dateTime.month = currentDate.month;

dateTime.day = currentDate.day;

}

// CH.6 Pop up the system date selection form

SelectDay( selectDayByDay, &(dateTime.month),

&(dateTime.day), &(dateTime.year),

"Enter Date" );

// CH.6 Get the record

hrecord = DmQueryRecord( contactsDB, cursor );

// CH.6 Lock it down

precord = MemHandleLock( hrecord );

// CH.6 Write the date time field

DmWrite( precord, DB_DATE_TIME_START, &dateTime,

sizeof( DateTimeType ) );

// CH.6 Unlock the record

MemHandleUnlock( hrecord );

// CH.6 Mark the record dirty

isDirty = true;

}

break;

// CH.6 Time selector trigger

case ContactDetailTimeSelTrigger:

{

// CH.6 Pop up our selection form

FrmPopupForm( EnterTimeForm );

}

break;

}

// CH.5 Sync the current record to the fields

setFields();

}

break;

// CH.5 Respond to field tap

case fldEnterEvent:

isDirty = true;

break;

// CH.3 Parse menu events

case menuEvent:

return( menuEventHandler( event ) );

break;

}

// CH.2 We're done

return( false );

}

// CH.4 Our About form event handler function

static Boolean aboutHandleEvent( EventPtr event )

{

FormPtr form; // CH.4 A pointer to our form structure

// CH.4 Get our form pointer

form = FrmGetActiveForm();

// CH.4 Respond to the Open event

if( event->eType == frmOpenEvent )

{

// CH.4 Draw the form

FrmDrawForm( form );

}

// CH.4 Return to the calling form

if( event->eType == ctlSelectEvent )

{

FrmReturnToForm( 0 );

// CH.4 Always return true in this case

return( true );

}

// CH.4 We're done

return( false );

}

// CH.6 Our Enter Time form event handler function

static Boolean enterTimeHandleEvent( EventPtr event )

{

FormPtr form; // CH.6 A form structure pointer

static DateTimeType oldTime; // CH.6 The original time

// CH.6 Get our form pointer

form = FrmGetActiveForm();

// CH.6 Switch on the event

switch( event->eType )

{

// CH.6 Initialize the form

case frmOpenEvent:

{

// CH.6 Store the time value

oldTime = dateTime;

// CH.6 Draw it

FrmDrawForm( form );

// CH.6 Set the time controls

setTimeControls();

}

break;

// CH.6 If a button was repeated

case ctlRepeatEvent:

// CH.6 If a button was pushed

case ctlSelectEvent:

{

Word buttonID; // CH.6 The ID of the button

// CH.6 Set the ID

buttonID = event->data.ctlSelect.controlID;

// CH.6 Switch on button ID

switch( buttonID )

{

// CH.6 Hours button

case EnterTimeHoursPushButton:

// CH.6 Minute Tens button

case EnterTimeMinuteTensPushButton:

// CH.6 Minute Ones button

case EnterTimeMinuteOnesPushButton:

{

// CH.6 If no time was set

if( dateTime.hour == NO_TIME )

{

// CH.6 Set the time to 12 PM

dateTime.hour = 12;

dateTime.minute = 0;

// CH.6 Set the controls

setTimeControls();

}

// CH.6 Clear the old selection if any

if( timeSelect )

CtlSetValue( getObject( form, timeSelect ),

false );

// CH.6 Set the new selection

CtlSetValue( getObject( form, buttonID ), true );

timeSelect = buttonID;

}

break;

// CH.6 Up button

case EnterTimeTimeUpRepeating:

{

// CH.6 If there's no time, do nothing

if( dateTime.hour == NO_TIME )

break;

// CH.6 Based on what push button is selected

switch( timeSelect )

{

// CH.6 Increase hours

case EnterTimeHoursPushButton:

{

// CH.6 Increment hours

dateTime.hour++;

// CH.6 If it was 11 AM, make it 12 AM

if( dateTime.hour == 12 )

dateTime.hour = 0;

// CH.6 If it was 11 PM, make it 12 PM

if( dateTime.hour == 24 )

dateTime.hour = 12;

}

break;

// CH.6 Increase tens of minutes

case EnterTimeMinuteTensPushButton:

{

// CH.6 Increment minutes

dateTime.minute += 10;

// CH.6 If it was 5X, roll over

if( dateTime.minute > 59 )

dateTime.minute -= 60;

}

break;

// CH.6 Increase minutes

case EnterTimeMinuteOnesPushButton:

{

// CH.6 Increment minutes

dateTime.minute++;

// CH.6 If it is zero, subtract ten

if( (dateTime.minute % 10) == 0 )

dateTime.minute -= 10;

}

break;

}

// Revise the controls

setTimeControls();

}

break;

// CH.6 Down button

case EnterTimeTimeDownRepeating:

{

// CH.6 If there's no time, do nothing

if( dateTime.hour == NO_TIME )

break;

// CH.6 Based on what push button is selected

switch( timeSelect )

{

// CH.6 Decrease hours

case EnterTimeHoursPushButton:

{

// CH.6 Decrement hours

dateTime.hour--;

// CH.6 If it was 12 AM, make it 11 AM

if( dateTime.hour == -1 )

dateTime.hour = 11;

// CH.6 If it was 12 PM, make it 11 PM

if( dateTime.hour == 11 )

dateTime.hour = 23;

}

break;

// CH.6 Decrease tens of minutes

case EnterTimeMinuteTensPushButton:

{

// CH.6 Decrement minutes

dateTime.minute -= 10;

// CH.6 If it was 0X, roll over

if( dateTime.minute < 0 )

dateTime.minute += 60;

}

break;

// CH.6 Decrease minutes

case EnterTimeMinuteOnesPushButton:

{

// CH.6 Decrement minutes

dateTime.minute--;

// CH.6 If it is 9, add ten

if( (dateTime.minute % 10) == 9 )

dateTime.minute += 10;

// CH.6 If less than zero, make it 9

if( dateTime.minute < 0 )

dateTime.minute = 9;

}

break;

}

// CH.6 Revise the controls

setTimeControls();

}

break;

// CH.6 AM button

case EnterTimeAMPushButton:

{

// CH.6 If no time was set

if( dateTime.hour == NO_TIME )

{

// CH.6 Set the time to 12 AM

dateTime.hour = 0;

dateTime.minute = 0;

// CH.6 Set the controls

setTimeControls();

}

// CH.6 If it is PM

if( dateTime.hour > 11 )

{

// CH.6 Change to AM

dateTime.hour -= 12;

// CH.6 Set the controls

setTimeControls();

}

}

break;

// CH.6 PM button

case EnterTimePMPushButton:

{

// CH.6 If no time was set

if( dateTime.hour == NO_TIME )

{

// CH.6 Set the time to 12 PM

dateTime.hour = 12;

dateTime.minute = 0;

// CH.6 Set the controls

setTimeControls();

}

// CH.6 If it is AM

if( dateTime.hour < 12 )

{

// CH.6 Change to PM

dateTime.hour += 12;

// CH.6 Set the controls

setTimeControls();

}

}

break;

// CH.6 No Time checkbox

case EnterTimeNoTimeCheckbox:

{

// CH.6 If we are unchecking the box

if( dateTime.hour == NO_TIME )

{

// CH.6 Set the time to 12 PM

dateTime.hour = 12;

dateTime.minute = 0;

// CH.6 Set the controls

setTimeControls();

// CH.6 Set the new selection

timeSelect = EnterTimeHoursPushButton;

CtlSetValue( getObject( form, timeSelect ),

true );

}

else

// CH.6 If we are checking the box

dateTime.hour = NO_TIME;

// CH.6 Set the controls

setTimeControls();

}

break;

// CH.6 Cancel button

case EnterTimeCancelButton:

{

// CH.6 Restore time

dateTime = oldTime;

// CH.6 Return to calling form

FrmReturnToForm( 0 );

}

// CH.6 Always return true

return( true );

// CH.6 OK button

case EnterTimeOKButton:

{

VoidPtr precord; // CH.6 Points to the record

// CH.6 Lock it down

precord = MemHandleLock( hrecord );

// CH.6 Write the date time field

DmWrite( precord, DB_DATE_TIME_START, &dateTime,

sizeof( DateTimeType ) );

// CH.6 Unlock the record

MemHandleUnlock( hrecord );

// CH.6 Mark the record dirty

isDirty = true;

// CH.6 Return to the Contact Details form

FrmReturnToForm( 0 );

// CH.6 Update the field

setTimeTrigger();

}

// CH.6 Always return true

return( true );

}

}

break;

}

// CH.6 We're done

return( false );

}

// CH.7 Our Contact List form event handler function

static Boolean contactListHandleEvent( EventPtr event )

{

FormPtr form; // CH.7 A form structure pointer

// CH.7 Get our form pointer

form = FrmGetActiveForm();

// CH.7 Parse events

switch( event->eType )

{

// CH.7 Form open event

case frmOpenEvent:

{

// CH.7 Draw the form

FrmDrawForm( form );

// CH.7 Build the list

buildList();

}

break;

// CH.7 Form close event

case frmCloseEvent:

{

// CH.7 Unlock and free things here

MemHandleUnlock( hpchoices );

MemHandleFree( hpchoices );

MemHandleUnlock( hchoices );

MemHandleFree( hchoices );

hchoices = 0;

}

break;

// CH.7 Respond to a list selection

case lstSelectEvent:

{

// CH.7 Set the database cursor to the selected contact

cursor = event->data.lstSelect.selection;

// CH.7 Go to contact details

FrmGotoForm( ContactDetailForm );

}

break;

// CH.7 Respond to a menu event

case menuEvent:

return( menuEventHandler( event ) );

// CH.7 Respond to the popup trigger

case popSelectEvent:

{

// CH.7 If there is no change, we're done

if( sortBy == event->data.popSelect.selection )

return( true );

// CH.7 Modify sort order variable

sortBy = event->data.popSelect.selection;

// CH.7 Sort the contact database by the new criteria

DmQuickSort( contactsDB, (DmComparF*)sortFunc, sortBy );

// CH.7 Rebuild the list

buildList();

}

break;

} // CH.7 End of the event switch statement

// CH.7 We're done

return( false );

}

// CH.3 Handle menu events

Boolean menuEventHandler( EventPtr event )

{

FormPtr form; // CH.3 A pointer to our form structure

Word index; // CH.3 A general purpose control index

FieldPtr field; // CH.3 Used for manipulating fields

// CH.3 Get our form pointer

form = FrmGetActiveForm();

// CH.3 Erase the menu status from the display

MenuEraseStatus( NULL );

// CH.4 Handle options menu

if( event->data.menu.itemID == OptionsAboutContacts )

{

// CH.4 Pop up the About form as a Dialog

FrmPopupForm( AboutForm );

return( true );

}

// CH.3 Handle graffiti help

if( event->data.menu.itemID == EditGraffitiHelp )

{

// CH.3 Pop up the graffiti reference based on

// the graffiti state

SysGraffitiReferenceDialog( referenceDefault );

return( true );

}

// CH.3 Get the index of our field

index = FrmGetFocus( form );

// CH.3 If there is no field selected, we're done

if( index == noFocus )

return( false );

// CH.3 Get the pointer of our field

field = FrmGetObjectPtr( form, index );

// CH.3 Do the edit command

switch( event->data.menu.itemID )

{

// CH.3 Undo

case EditUndo:

FldUndo( field );

break;

// CH.3 Cut

case EditCut:

FldCut( field );

break;

// CH.3 Copy

case EditCopy:

FldCopy( field );

break;

// CH.3 Paste

case EditPaste:

FldPaste( field );

break;

// CH.3 Select All

case EditSelectAll:

{

// CH.3 Get the length of the string in the field

Word length = FldGetTextLength( field );

// CH.3 Sound an error if appropriate

if( length == 0 )

{

SndPlaySystemSound( sndError );

return( false );

}

// CH.3 Select the whole string

FldSetSelection( field, 0, length );

}

break;

// CH.3 Bring up the keyboard tool

case EditKeyboard:

SysKeyboardDialogV10();

break;

}

// CH.3 We're done

return( true );

}

// CH.5 This function creates and initializes a new record

static void newRecord( void )

{

VoidPtr precord; // CH.5 Pointer to the record

// CH.7 Create the database record and get a handle to it

if( (hrecord = DmNewRecord( contactsDB, &cursor,

DB_RECORD_SIZE )) == NULL )

errorExit( MemoryErrorAlert );

// CH.5 Lock down the record to modify it

precord = MemHandleLock( hrecord );

// CH.5 Clear the record

DmSet( precord, 0, DB_RECORD_SIZE, 0 );

// CH.6 Initialize the date and time

MemSet( &dateTime, sizeof( dateTime ), 0 );

dateTime.year = NO_DATE;

dateTime.hour = NO_TIME;

DmWrite( precord, DB_DATE_TIME_START, &dateTime,

sizeof( DateTimeType ) );

// CH.5 Unlock the record

MemHandleUnlock( hrecord );

// CH.5 Clear the busy bit and set the dirty bit

DmReleaseRecord( contactsDB, cursor, true );

// CH.5 Increment the total record count

numRecords++;

// CH.5 Set the dirty bit

isDirty = true;

// CH.5 We're done

return;

}

// CH.5 A time saver: Gets object pointers based on their ID

static VoidPtr getObject( FormPtr form, Word objectID )

{

Word index; // CH.5 The object index

// CH.5 Get the index

index = FrmGetObjectIndex( form, objectID );

// CH.5 Return the pointer

return( FrmGetObjectPtr( form, index ) );

}

// CH.5 Gets the current database record and displays it

// in the detail fields

static void setFields( void )

{

FormPtr form; // CH.5 The contact detail form

CharPtr precord; // CH.5 A record pointer

Word index; // CH.5 The object index

// CH.5 Get the contact detail form pointer

form = FrmGetActiveForm();

// CH.5 Get the current record

hrecord = DmQueryRecord( contactsDB, cursor );

// CH.6 Initialize the date and time variable

precord = MemHandleLock( hrecord );

MemMove( &dateTime, precord + DB_DATE_TIME_START,

sizeof( dateTime ) );

// CH.6 Initialize the date control

setDateTrigger();

// CH.6 Initialize the time control

setTimeTrigger();

// CH.5 Set the text for the First Name field

setText( getObject( form, ContactDetailFirstNameField ),

precord + DB_FIRST_NAME_START );

// CH.5 Set the text for the Last Name field

setText( getObject( form, ContactDetailLastNameField ),

precord + DB_LAST_NAME_START );

// CH.5 Set the text for the Phone Number field

setText( getObject( form, ContactDetailPhoneNumberField ),

precord + DB_PHONE_NUMBER_START );

MemHandleUnlock( hrecord );

// CH.5 If the record is already dirty, it's new, so set focus

if( isDirty )

{

// CH.3 Get the index of our field

index = FrmGetObjectIndex( form, ContactDetailFirstNameField );

// CH.3 Set the focus to the First Name field

FrmSetFocus( form, index );

// CH.5 Set upper shift on

GrfSetState( false, false, true );

}

// CH.5 We're done

return;

}

// CH.5 Puts any field changes in the record

void getFields( void )

{

FormPtr form; // CH.5 The contact detail form

// CH.5 Get the contact detail form pointer

form = FrmGetActiveForm();

// CH.5 Turn off focus

FrmSetFocus( form, -1 );

// CH.5 If the record has been modified

if( isDirty )

{

CharPtr precord; // CH.5 Points to the DB record

// CH.7 Detach the record from the database

DmDetachRecord( contactsDB, cursor, &hrecord );

// CH.5 Lock the record

precord = MemHandleLock( hrecord );

// CH.5 Get the text for the First Name field

getText( getObject( form, ContactDetailFirstNameField ),

precord, DB_FIRST_NAME_START );

// CH.5 Get the text for the Last Name field

getText( getObject( form, ContactDetailLastNameField ),

precord, DB_LAST_NAME_START );

// CH.5 Get the text for the Phone Number field

getText( getObject( form, ContactDetailPhoneNumberField ),

precord, DB_PHONE_NUMBER_START );

// CH.7 Find the proper position

cursor = DmFindSortPosition( contactsDB, precord, NULL,

(DmComparF*)sortFunc, sortBy );

// CH.5 Unlock the record

MemHandleUnlock( hrecord );

// CH.7 Reattach the record

DmAttachRecord( contactsDB, &cursor, hrecord, NULL );

}

// CH.5 Reset the dirty bit

isDirty = false;

// CH.5 We're done

return;

}

// CH.5 Set the text in a field

static void setText( FieldPtr field, CharPtr text )

{

VoidHand hfield; // CH.5 Handle of field text

CharPtr pfield; // CH.5 Pointer to field text

// CH.5 Get the current field handle

hfield = FldGetTextHandle( field );

// CH.5 If we have a handle

if( hfield != NULL )

{

// CH.5 Resize it

if( MemHandleResize( hfield, StrLen( text ) + 1 ) != 0 )

errorExit( MemoryErrorAlert );

}

else

// CH.5 Allocate a handle for the string

{

hfield = MemHandleNew( StrLen( text ) + 1 );

if( hfield == NULL )

errorExit( MemoryErrorAlert );

}

// CH.5 Lock it

pfield = MemHandleLock( hfield );

// CH.5 Copy the string

StrCopy( pfield, text );

// CH.5 Unlock it

MemHandleUnlock( hfield );

// CH.5 Give it to the field

FldSetTextHandle( field, hfield );

// CH.5 Draw the field

FldDrawField( field );

// CH.5 We're done

return;

}

// CH.5 Get the text from a field

static void getText( FieldPtr field, VoidPtr precord, Word offset )

{

CharPtr pfield; // CH.5 Pointer to field text

// CH.5 Get the text pointer

pfield = FldGetTextPtr( field );

// CH.5 Copy it

DmWrite( precord, offset, pfield, StrLen( pfield ) );

// CH.5 We're done

return;

}

// CH.6 Set the Contact Detail date selector trigger

static void setDateTrigger( void )

{

FormPtr form; // CH.5 The contact detail form

// CH.6 Get the contact detail form pointer

form = FrmGetActiveForm();

// CH.6 If there is no date

if( dateTime.year == NO_DATE )

{

CtlSetLabel( getObject( form, ContactDetailDateSelTrigger ),

" " );

}

else

// CH.6 If there is a date

{

Char dateString[dateStringLength];

// CH.6 Get the date string

DateToAscii( dateTime.month, dateTime.day, dateTime.year,

(DateFormatType)PrefGetPreference( prefDateFormat ), dateString );

// CH.6 Set the selector trigger label

CtlSetLabel( getObject( form, ContactDetailDateSelTrigger ),

dateString );

}

// CH.6 We're done

return;

}

// CH.6 Set the Contact Detail time selector trigger

static void setTimeTrigger( void )

{

FormPtr form; // CH.5 The contact detail form

// CH.6 Get the contact detail form pointer

form = FrmGetActiveForm();

// CH.6 If there's no time

if( dateTime.hour == NO_TIME )

{

CtlSetLabel( getObject( form, ContactDetailTimeSelTrigger ),

" " );

}

else

// CH.6 If there is a time

{

Char timeString[timeStringLength];

// CH.6 Get the time string

TimeToAscii( dateTime.hour, dateTime.minute,

(TimeFormatType)PrefGetPreference( prefTimeFormat ), timeString );

// CH.6 Set the selector trigger label

CtlSetLabel( getObject( form, ContactDetailTimeSelTrigger ),

timeString );

}

// CH.6 We're done

return;

}

// CH.6 Set the controls in the Enter Time form based on dateTime

static void setTimeControls( void )

{

FormPtr form;

ControlPtr hourButton;

ControlPtr minuteTensButton;

ControlPtr minuteOnesButton;

ControlPtr amButton;

ControlPtr pmButton;

ControlPtr noTimeCheckbox;

Char labelString[3];

SWord hour;

// CH.6 Get the form

form = FrmGetActiveForm();

// CH.6 Get the control pointers

hourButton = getObject( form, EnterTimeHoursPushButton );

minuteTensButton = getObject( form,

EnterTimeMinuteTensPushButton );

minuteOnesButton = getObject( form,

EnterTimeMinuteOnesPushButton );

amButton = getObject( form, EnterTimeAMPushButton );

pmButton = getObject( form, EnterTimePMPushButton );

noTimeCheckbox = getObject( form, EnterTimeNoTimeCheckbox );

// CH.6 If there is a time

if( dateTime.hour != NO_TIME )

{

// CH.6 Update the hour

hour = dateTime.hour % 12;

if( hour == 0 )

hour = 12;

CtlSetLabel( hourButton,

StrIToA( labelString, hour ) );

// CH.6 Update the minute tens

CtlSetLabel( minuteTensButton,

StrIToA( labelString, dateTime.minute / 10 ) );

// CH.6 Update the minute ones

CtlSetLabel( minuteOnesButton,

StrIToA( labelString, dateTime.minute % 10 ) );

// CH.6 Update AM

CtlSetValue( amButton, (dateTime.hour < 12) );

// CH.6 Update PM

CtlSetValue( pmButton, (dateTime.hour > 11) );

// CH.6 Uncheck the no time checkbox

CtlSetValue( noTimeCheckbox, false );

}

else

// If there is no time

{

// CH.6 Update the hour

CtlSetValue( hourButton, false );

CtlSetLabel( hourButton, "" );

// CH.6 Update the minute tens

CtlSetValue( minuteTensButton, false );

CtlSetLabel( minuteTensButton, "" );

// CH.6 Update the minute ones

CtlSetValue( minuteOnesButton, false );

CtlSetLabel( minuteOnesButton, "" );

// CH.6 Update AM

CtlSetValue( amButton, false );

// CH.6 Update PM

CtlSetValue( pmButton, false );

// CH.6 Uncheck the no time checkbox

CtlSetValue( noTimeCheckbox, true );

}

// CH.6 We're done

return;

}

// CH.7 Builds the contact list

static void buildList( void )

{

FormPtr form; // CH.6 A form structure pointer

Int choice; // CH.7 The list choice we're doing

CharPtr precord; // CH.7 Pointer to a record

Char listChoice[dateStringLength + 1 + // CH.7 We

timeStringLength + 1 + // build

DB_FIRST_NAME_SIZE + // list

DB_LAST_NAME_SIZE]; // choices here

// CH.7 The current list choice

CharPtr pchoices; // CH.7 Pointer to packed choices

UInt offset; // CH.7 Offset into packed strings

VoidPtr ppchoices; // CH.7 Pointer to pointers to choices

// CH.6 Get our form pointer

form = FrmGetActiveForm();

// CH.7 Put the list choices in a packed string

for( choice = 0; choice < numRecords; choice++ )

{

// CH.7 Get the record

hrecord = DmQueryRecord( contactsDB, choice );

precord = MemHandleLock( hrecord );

// CH.7 Get the date and time

MemMove( &dateTime, precord + DB_DATE_TIME_START,

sizeof( dateTime ) );

// CH.7 Clear the list choice string

*listChoice = '\0';

// CH.7 Add the date string if any

if( dateTime.year != NO_DATE )

{

DateToAscii( dateTime.month, dateTime.day,

dateTime.year,

(DateFormatType)PrefGetPreference(

prefDateFormat ), listChoice );

StrCat( listChoice, " " );

}

// CH.7 Add the time string if any

if( dateTime.hour != NO_TIME )

{

TimeToAscii( dateTime.hour, dateTime.minute,

(TimeFormatType)PrefGetPreference(

prefTimeFormat ), listChoice +

StrLen( listChoice ) );

StrCat( listChoice, " " );

}

// CH.7 Append the first name

StrCat( listChoice, precord + DB_FIRST_NAME_START );

StrCat( listChoice, " " );

// CH.7 Append the last name

StrCat( listChoice, precord + DB_LAST_NAME_START );

// CH.7 Allocate memory for the list entry string

// CH.7 If this is the first choice

if( hchoices == 0 )

{

// CH.7 Allocate the storage for the choice

if( (hchoices = MemHandleNew(

StrLen( listChoice ) + 1 )) == 0 )

errorExit( MemoryErrorAlert );

// CH.7 Initial offset points to the start

offset = 0;

}

else

// CH.7 If this is a subsequent choice

{

// CH.7 Unlock

MemHandleUnlock( hchoices );

// CH.7 Resize

if( MemHandleResize( hchoices, offset +

StrLen( listChoice ) + 1 ) )

errorExit( MemoryErrorAlert );

}

// CH.7 Lock

pchoices = MemHandleLock( hchoices );

// CH.7 Copy the string into the memory

StrCopy( pchoices + offset, listChoice );

offset += StrLen( listChoice ) + 1;

// CH.7 Unlock the record

MemHandleUnlock( hrecord );

}

// CH.7 Create a pointer array from the packed string list

if( (hpchoices = SysFormPointerArrayToStrings( pchoices,

numRecords )) == 0 )

errorExit( MemoryErrorAlert );

ppchoices = MemHandleLock( hpchoices );

// CH.7 Set the list choices

LstSetListChoices( getObject( form, ContactListListList ),

ppchoices, numRecords );

// CH.7 Draw the list

LstDrawList( getObject( form, ContactListListList ) );

// CH.7 We're done

return;

}

// CH.7 This function is called by Palm OS to sort records

static Int sortFunc( CharPtr precord1, CharPtr precord2, Int sortBy )

{

Int sortResult;

// CH.7 Switch based on sort criteria

switch( sortBy )

{

// CH.7 Sort by date and time

case SORTBY_DATE_TIME:

{

DateTimePtr pdateTime1;

DateTimePtr pdateTime2;

Long lDiff;

pdateTime1 = (DateTimePtr)(precord1 + DB_DATE_TIME_START);

pdateTime2 = (DateTimePtr)(precord2 + DB_DATE_TIME_START);

// CH.7 Compare the dates and times

lDiff = (Long)(TimDateTimeToSeconds( pdateTime1 ) / 60 ) -

(Long)(TimDateTimeToSeconds( pdateTime2 ) / 60 );

// CH.7 Date/time #1 is later

if( lDiff > 0 )

sortResult = 1;

else

// CH.7 Date/time #2 is later

if( lDiff < 0 )

sortResult = -1;

else

// CH.7 They are equal

sortResult = 0;

}

break;

// CH.7 Sort by first name

case SORTBY_FIRST_NAME:

{

sortResult = StrCompare( precord1 + DB_FIRST_NAME_START,

precord2 + DB_FIRST_NAME_START );

}

break;

// CH.7 Sort by last name

case SORTBY_LAST_NAME:

{

sortResult = StrCompare( precord1 + DB_LAST_NAME_START,

precord2 + DB_LAST_NAME_START );

}

break;

}

// CH.7 We're done

return( sortResult );

}

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有