PalmOS开发教程-7

    技术2022-05-11  91

    第七章 列表框和排序
         在这一章中,将接触到一些新的控件和数据库操作技巧。我们先生成一个窗体来显示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(), ¤tDate ); // 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 ); }

    最新回复(0)