/*********************************************************************** * * FUNCTION: FntSetFont * * DESCRIPTION: This routine set the current font. If the font is a valid * font but doesn't exist, the stdFont is used. * * PARAMETERS: fontID id of the font to make the active font * * RETURNED: ID of the current font prior to the change. * * REVISION HISTORY: * Name Date Description * ---- ---- ----------- * art 12/14/94 Initial Revision * roger 9/26/97 Now substitutes the stdFont for missing fonts * SCL 12/10/97 Rewrote to support new (separate) font tables * SCL 1/23/98 Now uses stdFont if passed an invalid font * bob 3/5/99 Update GState values with new font * ***********************************************************************/ FontID FntSetFont (FontID fontID) { FontID newFontID = fontID; FontID oldFontID; // See if fontID is a valid system font or app font ID if ( !FntIsAppDefined(newFontID) ? (newFontID >= UINumSysFonts) : (newFontID-fntAppFontCustomBase >= UINumAppFonts) ) { ErrNonFatalDisplay("Invalid font"); newFontID = stdFont; } oldFontID = UICurrentFontID; UICurrentFontID = newFontID; // Set the new font or a default one. if ( FntIsAppDefined(newFontID) ) { UICurrentFontPtr = UIAppFontPtr[newFontID-fntAppFontCustomBase]; } else { UICurrentFontPtr = UISysFontPtr[newFontID]; } if (UICurrentFontPtr == NULL) UICurrentFontPtr = UISysFontPtr[stdFont]; GState.drawStateP->fontId = UICurrentFontID; GState.drawStateP->font = UICurrentFontPtr; ErrNonFatalDisplayIf (UICurrentFontPtr == NULL, "Invalid font"); return oldFontID; }
/*********************************************************************** * * FUNCTION: StrChr * * DESCRIPTION: This routine looks for a character within a string. It * behaves the same as the standard C library function strchr, except * that it works correctly with multi-byte character encodings. * * PARAMETERS: * str - string to search * chr - the character to search for * * RETURNED: pointer to the first occurance of character in str, or NULL * if not found. * * HISTORY: * 08/18/95 ron Created by Ron Marianetti * 05/07/96 rsf Fixed to MemHandle searching for '\0' * 10/24/96 rsf Check chr * 08/12/97 ADH Revised Algorithm * 08/26/98 kwk Changed chr param from Int16 to WChar. Fixed up * sign extension problem. * 05/13/00 kwk Made it work for all char encodings, not just Latin. * ***********************************************************************/ Char* StrChr(const Char* str, WChar chr) { Char c = (Char)chr; register const Char* srcP = str; ErrNonFatalDisplayIf(str == NULL, "NULL string passed"); // Correct for sign extension. This will happen if the caller passes // a single byte >= 0x80, which the compiler will sign extend. if (chr >= 0xFF80) { ErrNonFatalDisplay("Sign extended character passed to StrChr"); chr = chr & 0x00FF; } // See if we need to do the slower (multi-byte) search case. if (chr > kTxtMaxAlwaysSingleByte) { Char buffer[maxCharBytes + 1]; UInt16 len = TxtSetNextChar(buffer, 0, chr); if (len > 1) { buffer[len] = '\0'; return(StrStr(str, buffer)); } } // Be sure to handle chr == '\0' correctly do { if (*srcP == c) { // If the character we're searching for could be part of a // multi-byte character, then we need to do an extra check // to make sure we didn't find part of a multi-byte character. // Since most people use StrChr to search for tabs, returns, // etc. this typically never gets executed. if (chr > kTxtMaxNeverMultiByte) { UInt32 charStart, charEnd; UInt32 charOffset = srcP - str; TxtCharBounds(str, charOffset, &charStart, &charEnd); if (charStart == charOffset) { return((Char*)srcP); } } else { return((Char*)srcP); } } } while (*srcP++ != 0); return(NULL); } // StrChr
/*********************************************************************** * * FUNCTION: FntWCharWidth * * DESCRIPTION: This routine returns the width of the specified character, * which can be any valid character character. If the specified * character does not exist within the current font, the Missing Char * Symbol will be substituted. * * PARAMETERS: ch character whose width is desired * * RETURNED: width of the specified character, in pixels * * HISTORY: * 05/12/00 kwk Created by Ken Krugler from TxtCharWidth. * 05/17/00 kwk Add call to TxtCharIsValid on debug ROMs. * ***********************************************************************/ Int16 FntWCharWidth(WChar iChar) { Char buffer[maxCharBytes]; if (iChar >= 0xFF80) { ErrNonFatalDisplay("Sign extended char passed to FntWCharWidth"); iChar &= 0x00FF; } // On debug ROMs, make sure the character is valid (e.g. not a // virtual character). ErrNonFatalDisplayIf(!TxtCharIsValid(iChar), "Invalid char passed to FntWCharWidth"); return(FntCharsWidth(buffer, TxtSetNextChar(buffer, 0, iChar))); } // FntWCharWidth
/************************************************************ * * FUNCTION: PrvConvertIToH <internal> * * DESCRIPTION: convert an integer to hexadecimal ascii * * PARAMETERS: Integer to convert and string pointer to store results * * RETURNS: s * * HISTORY: * 07/04/95 RM Created by Ron Marianetti * 07/31/97 AdamH Modified to return the end of the string to work with * StrVPrintF. * 04/02/98 roger Added sizeModifier and fixed to emit less than 8 digits when not a long * 11/08/99 kwk Non-fatal alert if unknown sizeModifier. * *************************************************************/ static Char * PrvConvertIToH(Char * s, UInt32 number, SizeModifierType sizeModifier) { UInt32 mask; UInt32 digit; UInt16 digitCount; // Check for err ErrNonFatalDisplayIf(s == NULL, "NULL string passed"); switch (sizeModifier) { case sizeLong: digitCount = 8; break; case sizeShort: digitCount = 4; number <<= 16; break; case sizeInt: digitCount = 4; number <<= 16; break; default: ErrNonFatalDisplay("Invalid sizeModifier"); digitCount = 8; break; } mask = 0xF0000000; while (digitCount-- > 0) { digit = (number & mask) >> 28; if (digit < 0x0A) *s++ = digit + '0'; else *s++ = digit - 10 + 'A'; number <<= 4; } *s = '\0'; return s; }
/*********************************************************************** * * FUNCTION: SelectTime * * DESCRIPTION: Display a form showing a start and end time. * Allow the user to change the time and then * return them. * pTimeDiff. * * PARAMETERS: pStartTime - pointer to TimeType * pEndTime - pointer to TimeType * untimed - true if there isn't a time. * title - string containing the title * startOfDay - used when "All Day" button selected. * endOfDay - our used when "All Day" button selected. * startOfDisplay - first hour initially visible * * RETURNED: True if the time was changed by the user. * The first three parameters may change. * * REVISION HISTORY: * Name Date Description * ---- ---- ----------- * roger 12/2/94 Initial Revision * trev 08/12/97 made non modified passed variables constant * css 06/08/99 added new parameter & "All Day" button for gromit change. * ***********************************************************************/ Boolean SelectTime (TimeType * startTimeP, TimeType * endTimeP, Boolean untimed, const Char * titleP, Int16 startOfDay, Int16 endOfDay, Int16 startOfDisplay) { Int16 firstHour; Char startTimeText [timeStringLength]; Char endTimeText [timeStringLength]; Char timeChars [timeStringLength]; TimePtr timeP; FormType *originalForm, *frm; ListPtr hoursLst, minutesLst; ControlPtr startTimeCtl, endTimeCtl; EventType event; Boolean confirmed = false; MemHandle hoursItems; TimeType startTime, endTime, timeDiff; TimeFormatType timeFormat; ChangingTimeType changingTime; // Get the time format from the system preerances; timeFormat = (TimeFormatType)PrefGetPreference(prefTimeFormat); // Because this routine only deals with minutes in five minute // intervals we convert the proposed times from those passed. startTime.hours = startTimeP->hours; startTime.minutes = startTimeP->minutes; endTime.hours = endTimeP->hours; endTime.minutes = endTimeP->minutes; TimeDifference (&endTime, &startTime, &timeDiff); // Make sure the end time is displayable (clips at 11:55 pm) AdjustTimes (&startTime, &endTime, &timeDiff, changingStartTime); // Clear the buffer that holds written characters. *timeChars = 0; startOfDisplay = min (startOfDisplay, 12); originalForm = FrmGetActiveForm(); frm = (FormType *) FrmInitForm (TimeSelectorForm); FrmSetActiveForm (frm); hoursLst = FrmGetObjectPtr (frm, FrmGetObjectIndex (frm, TimeSelectorHourList)); minutesLst = FrmGetObjectPtr (frm, FrmGetObjectIndex (frm, TimeSelectorMinuteList)); startTimeCtl = FrmGetObjectPtr (frm, FrmGetObjectIndex (frm, TimeSelectorStartTimeButton)); endTimeCtl = FrmGetObjectPtr (frm, FrmGetObjectIndex (frm, TimeSelectorEndTimeButton)); // Set list to use either 12 or 24 hour time hoursItems = SysFormPointerArrayToStrings ( ((timeFormat == tfColon24h) || (timeFormat == tfDot24h)) ? (Char *) Hours24Array() : (Char *) Hours12Array(), 24); LstSetListChoices (hoursLst, MemHandleLock(hoursItems), 24); LstSetTopItem (hoursLst, startOfDisplay); // Used to do LstMakeItemVisible (hoursLst, startTime.hours); no longer. if (! untimed) { LstSetSelection (hoursLst, startTime.hours); LstSetSelection (minutesLst, startTime.minutes / 5); CtlSetValue (startTimeCtl, true); changingTime = changingStartTime; } else { // The hour list is dynamically created and doesn't have a selection LstSetSelection (minutesLst, noListSelection); changingTime = changingNoTime; } // Set the start and end time buttons to the current times or blank them // if No Time is selected. SetTimeTriggers (startTime, endTime, startTimeText, endTimeText, timeFormat, untimed); // This needs to be taken out when SelectTimeV33 goes away. It allows for backward // compatibility for this change of adding the end of day variable as a parameter. if (endOfDay != noDisplayOfAllDay) { FrmShowObject (frm, FrmGetObjectIndex (frm, TimeSelectorAllDayButton)); } FrmSetTitle (frm, (Char *) titleP); FrmDrawForm (frm); while (true) { EvtGetEvent (&event, evtWaitForever); if (SysHandleEvent ((EventType *)&event)) continue; if (event.eType == appStopEvent) { // Cancel the dialog and repost this event for the app EvtAddEventToQueue(&event); confirmed = false; break; } // Handle these before the form does to overide the default behavior if (changingTime == changingNoTime && event.eType == lstEnterEvent && event.data.lstEnter.listID == TimeSelectorMinuteList) { SndPlaySystemSound(sndError); continue; } FrmHandleEvent (frm, &event); // If the start or end time buttons are pressed then change // the time displayed in the lists. if (event.eType == ctlSelectEvent) { // "Ok" button pressed? if (event.data.ctlSelect.controlID == TimeSelectorOKButton) { confirmed = true; } // "Cancel" button pressed? else if (event.data.ctlSelect.controlID == TimeSelectorCancelButton) { break; } // Start time button pressed? else if (event.data.ctlSelect.controlID == TimeSelectorStartTimeButton) { if (changingTime != changingStartTime) { if (changingTime == changingNoTime) { SetTimeTriggers (startTime, endTime, startTimeText, endTimeText, timeFormat, false); } CtlSetValue (endTimeCtl, false); LstSetSelection (hoursLst, startTime.hours); LstSetSelection (minutesLst, startTime.minutes / 5); changingTime = changingStartTime; } else CtlSetValue(startTimeCtl, true); } // End time button pressed? else if (event.data.ctlSelect.controlID == TimeSelectorEndTimeButton) { if (changingTime != changingEndTime) { if (changingTime == changingNoTime) { SetTimeTriggers (startTime, endTime, startTimeText, endTimeText, timeFormat, false); } CtlSetValue(startTimeCtl, false); LstSetSelection (hoursLst, endTime.hours); LstSetSelection (minutesLst, endTime.minutes / 5); changingTime = changingEndTime; } else CtlSetValue(endTimeCtl, true); } // No time button pressed? else if (event.data.ctlSelect.controlID == TimeSelectorNoTimeButton) { if (changingTime != changingNoTime) { if (changingTime == changingStartTime) CtlSetValue(startTimeCtl, false); else CtlSetValue(endTimeCtl, false); SetTimeTriggers (startTime, endTime, startTimeText, endTimeText, timeFormat, true); LstSetSelection (hoursLst, noListSelection); LstSetSelection (minutesLst, noListSelection); changingTime = changingNoTime; } // Get us out of this form display now. confirmed = true; } // All day button pressed? else if (event.data.ctlSelect.controlID == TimeSelectorAllDayButton) { if (changingTime != changingNoTime) { if (changingTime == changingStartTime) CtlSetValue(startTimeCtl, false); else CtlSetValue(endTimeCtl, false); LstSetSelection (hoursLst, noListSelection); LstSetSelection (minutesLst, noListSelection); changingTime = changingNoTime; } // No matter what, the minutes are 0 for both because only the hour is registered for start/end // times. startTime.minutes = 0; endTime.minutes = 0; startTime.hours = startOfDay; endTime.hours = endOfDay; // Set the times to the new times. SetTimeTriggers (startTime, endTime, startTimeText, endTimeText, timeFormat, true); // Get us out of this form display now. First set the changing time to anything but the changingNoTime value // so that the pointers at the end of this function get set correctly. changingTime = changingStartTime; confirmed = true; } // Clear the buffer that holds written characters. *timeChars = 0; } // If either list is changed then get the new time. If the // start time is changed then change the end time so that the // the time difference remains the same. If the end time is // changed then make sure the end time isn't before the start // time. Also calculate a new time difference. else if (event.eType == lstSelectEvent) { // First, get the info from the list which has changed. if (event.data.lstSelect.listID == TimeSelectorHourList) { if (changingTime == changingStartTime) startTime.hours = (UInt8) LstGetSelection (hoursLst); else if (changingTime == changingEndTime) endTime.hours = (UInt8) LstGetSelection (hoursLst); else if (changingTime == changingNoTime) { startTime.hours = (UInt8) LstGetSelection (hoursLst); SetTimeTriggers (startTime, endTime, startTimeText, endTimeText, timeFormat, false); CtlSetValue (endTimeCtl, false); LstSetSelection (minutesLst, startTime.minutes / 5); changingTime = changingStartTime; } } else if (event.data.lstSelect.listID == TimeSelectorMinuteList) { if (changingTime == changingStartTime) startTime.minutes = (UInt8) LstGetSelection (minutesLst) * 5; else if (changingTime == changingEndTime) endTime.minutes = (UInt8) LstGetSelection (minutesLst) * 5; else if (changingTime == changingNoTime) { ErrNonFatalDisplay("lstEnterEvent not being filtered."); } } if (AdjustTimes (&startTime, &endTime, &timeDiff, changingTime)) { if (changingTime == changingStartTime) { TimeToAscii (startTime.hours, startTime.minutes, timeFormat, startTimeText); CtlSetLabel (startTimeCtl, startTimeText); } else if (changingTime == changingEndTime) { LstSetSelection (hoursLst, startTime.hours); LstSetSelection (minutesLst, startTime.minutes / 5); } } TimeToAscii(endTime.hours, endTime.minutes, timeFormat, endTimeText); CtlSetLabel(endTimeCtl, endTimeText); // Clear the buffer that holds written characters. *timeChars = 0; } // Handle character written in the time picker. else if (event.eType == keyDownEvent) { if (changingTime == changingEndTime) { timeP = &endTime; firstHour = startTime.hours; } else { timeP = &startTime; firstHour = startOfDisplay; } // If a backspace character was written, change the time picker's // current setting to "no-time". if (event.data.keyDown.chr == backspaceChr) { *timeChars = 0; if (changingTime != changingNoTime) { if (changingTime == changingStartTime) CtlSetValue (startTimeCtl, false); else CtlSetValue (endTimeCtl, false); SetTimeTriggers (startTime, endTime, startTimeText, endTimeText, timeFormat, true); LstSetSelection (hoursLst, noListSelection); LstSetSelection (minutesLst, noListSelection); changingTime = changingNoTime; } } // A linefeed character confirms the dialog box. else if (event.data.keyDown.chr == linefeedChr) { confirmed = true; } // If next-field character toggle between start time in end // time. else if (event.data.keyDown.chr == nextFieldChr) { *timeChars = 0; if (changingTime == changingStartTime) { CtlSetValue (startTimeCtl, false); CtlSetValue (endTimeCtl, true); changingTime = changingEndTime; } else { CtlSetValue (endTimeCtl, false); CtlSetValue (startTimeCtl, true); changingTime = changingStartTime; } } // If a valid time character was written, translate the written // character into a time and update the time picker's UI. else if (TranslateTime (timeFormat, event.data.keyDown.chr, firstHour, timeChars, timeP)) { if (changingTime == changingNoTime) { changingTime = changingStartTime; CtlSetValue (startTimeCtl, true); } AdjustTimes (&startTime, &endTime, &timeDiff, changingTime); SetTimeTriggers (startTime, endTime, startTimeText, endTimeText, timeFormat, false); LstSetSelection (hoursLst, timeP->hours); LstSetSelection (minutesLst, timeP->minutes / 5); } } // Has the dialog been confirmed. if (confirmed) { if (changingTime != changingNoTime) { *startTimeP = startTime; *endTimeP = endTime; } else { TimeToInt(*startTimeP) = noTime; TimeToInt(*endTimeP) = noTime; } break; } } FrmEraseForm (frm); FrmDeleteForm (frm); MemHandleFree (hoursItems); FrmSetActiveForm(originalForm); return (confirmed); }
/*********************************************************************** * * FUNCTION: GetCharsWidth * * DESCRIPTION: This routine returns width of a character that is reference * via a font mapping table. * * PARAMETERS: charsP - pointer to the character to measure * charSizeP - length of the character measured (return value) * * RETURNED: width of the character in pixels * * HISTORY: * 03/02/98 art Created by Art Lamb. * 07/16/98 kwk Re-enabled check for invalid low byte before assuming * that a missing character is single byte. * 11/30/00 kwk Alter flow so that valid double-byte check is done if * low-byte is in range but undefined (e.g. 0x7F). * ***********************************************************************/ static Int16 GetCharsWidth (const Char* charsP, Int16* charSizeP) { Int16 ch; Int32 index; Int16 width; Int16 charSize; Int16 firstChar; Int16 lastChar; FontPtr fontP; FontTablePtr fontListP; FontMapPtr fontMap; FontCharInfoType * charInfo; charSize = 1; index = *(UInt8 *)charsP; fontMap = (FontMapPtr) (UICurrentFontPtr+1); if ((UICurrentFontPtr->fontType & fntAppFontMap) == fntAppFontMap) fontListP = GAppSubFontListPtr; else fontListP = UIFontListPtr; fontP = fontListP [fontMap[index].value]; if (fontMap[index].state == fntStateNextIsChar) { charsP++; charSize = 2; } firstChar = fontP->firstChar; lastChar = fontP->lastChar; charInfo = (FontCharInfoType *) (&fontP->owTLoc) + fontP->owTLoc; ch = *(UInt8 *)charsP++; *charSizeP = charSize; if (ch >= firstChar && ch <= lastChar) { width = charInfo[ch - firstChar].width; if (width != fntMissingChar) return(width); } // If the character is a double byte character and the low byte // is invalid then the low byte is the first byte of the // next character. // DOLATER kwk - This means that we're no longer truely // int'l, as we've got checks for character size = 2 here. Better would // be to use the font map table to decide if the byte is valid. width = charInfo[lastChar - firstChar +1].width; if ((charSize == 2) && ((TxtByteAttr(ch) & byteAttrLast) == 0)) { #if (ERROR_CHECK_LEVEL == ERROR_CHECK_FULL) if ((GIntlData->intlFlags & kStrictChecksFlag) != 0) { ErrNonFatalDisplay("Measuring width of invalid double-byte character"); } #endif *charSizeP = 1; } return (width); }
/*********************************************************************** * * FUNCTION: PrvMultibyteWrap * * DESCRIPTION: Given a string, determine the number of characters that * can be displayed within the specified width. We assume that the * current font is a multi-byte font, and that FntWordWrap has * already screened out odd cases. * * PARAMETERS: * iStringP -> pointer to a null-terminated string * iMaxWidth -> maximum line width in pixels * * RETURNED: * length of the line in bytes * * HISTORY: * 05/29/00 kwk Created by Ken Krugler from Art's original code. * 06/19/00 kwk Re-wrote to be faster - do all character processing * here, versus calling other routines. * 07/29/00 kwk Catch case of not even one character fitting on a line. * 11/29/00 kwk Report non-fatal alert if wrapping invalid double-byte * and strict Intl Mgr flag is set. * 11/30/00 kwk If invalid double-byte, keep using sub-font and invalid * low byte to match behavior of draw/measure code. * ***********************************************************************/ static UInt16 PrvMultibyteWrap(const Char* iStringP, UInt16 iMaxWidth) { const Char* textP = iStringP; const FontMapType* fontMapP; UInt16 lineWidth = 0; UInt16 charSize; UInt32 offset; // The font map follows the regular font header information. fontMapP = (const FontMapType*)(UICurrentFontPtr+1); // As soon as lineWidth is == or > iMaxWidth, we can stop looping. When // we call TxtGetWordWrapOffset, we only back up the break offset if we // exceeded the iMaxWidth limit. Note that this isn't the same as the // original FntWordWrap algorithm, which would always back up even if // lineWidth == iMaxWidth, thus not using the full width of the display. while (lineWidth < iMaxWidth) { UInt8 curChar = *textP++; // A null (end of string) immediately terminates processing, and // the offset is before (to the left of) the null byte. if (curChar == chrNull) { return(textP - iStringP - 1); } // A linefeed immediately terminates processing, and the offset is // to the right of the linefeed character. else if (curChar == chrLineFeed) { return(textP - iStringP); } // A tab's width is the distance from the current line width to the // next tab-stop position (currently fixed at 20 pixels). else if (curChar == chrTab) { lineWidth += fntTabChrWidth - (lineWidth % fntTabChrWidth); charSize = 1; } // We've got a regular character, so figure out its width. else { const FontType* fontP = UIFontListPtr[fontMapP[curChar].value]; const FontCharInfoType* charInfoP; UInt8 firstChar; UInt8 lastChar; Int8 width; // See if we've got a multi-byte character. if (fontMapP[curChar].state == fntStateNextIsChar) { curChar = *textP++; if ((curChar == chrNull) #if (ERROR_CHECK_LEVEL == ERROR_CHECK_FULL) || ((TxtByteAttr(curChar) & byteAttrLast) == 0)) #else || (false)) #endif { // We've got an invalid low byte, treat it as a single byte // character. Note that we'll continue to use the sub-font, // and the invalid character index, since the font should // have a missing character defined at that location. charSize = 1; textP--; #if (ERROR_CHECK_LEVEL == ERROR_CHECK_FULL) if ((GIntlData->intlFlags & kStrictChecksFlag) != 0) { ErrNonFatalDisplay("Wrapping an invalid double-byte character"); } #endif } else { charSize = 2; } } else {
/*********************************************************************** * chooses an displays the requested form ***********************************************************************/ static Boolean AppHandleEvent( EventPtr eventP) { UInt16 formId; FormPtr frmP; switch (eventP->eType) { case frmLoadEvent: // Load the form resource. formId = eventP->data.frmLoad.formID; frmP = FrmInitForm(formId); FrmSetActiveForm(frmP); // Set the event handler for the form. The handler of the currently // active form is called by FrmHandleEvent each time is receives an // event. switch (formId) { case FORM_main: FrmSetEventHandler(frmP, MainFormHandleEvent); break; case FORM_courselist: FrmSetEventHandler(frmP, CourseListHandleEvent); break; case FORM_evt_det: FrmSetEventHandler(frmP, EditTimeFormHandleEvent); break; case FORM_course_det: FrmSetEventHandler(frmP, EditCourseFormHandleEvent); break; case FORM_settings: FrmSetEventHandler(frmP, SettingsFormHandleEvent); break; case FORM_coursetypes: FrmSetEventHandler(frmP, CourseTypeFormHandleEvent); break; case FORM_exams: FrmSetEventHandler(frmP, ExamsFormHandleEvent); break; case FORM_exam_details: FrmSetEventHandler(frmP, ExamDetailsFormHandleEvent); break; case NewNoteView: FrmSetEventHandler(frmP, NoteViewHandleEvent); break; case FORM_alarm_sets: FrmSetEventHandler(frmP, AlarmFormHandleEvent); break; default: ErrNonFatalDisplay("Invalid Form Load Event"); break; } break; default: return false; } return true; }
/*********************************************************************** * * FUNCTION: RepeatDrawDescription * * DESCRIPTION: This routine draws the text description of the current * repeat type and frequency. * * The description is created from a template string. The * repeat type and frequency determines which template is * used. The template may contain one or more of the * following token: * ^d - day name (ex: Monday) * ^f - frequency * ^x - day of the month ordinal number (ex: 1st - 31th) * ^m - month name (ex: July) * ^w - week ordinal number (1st, 2nd, 3rd, 4th, or last) * * PARAMETERS: frm - pointer to the repeat dialog box * * RETURNED: nothing * ***********************************************************************/ void RepeatDrawDescription(FormType* frm) { UInt8 repeatOn; UInt16 i; UInt16 len; UInt16 freq; UInt16 dayOfWeek; UInt16 templateId = repeatNoneString; UInt16 repeatOnCount = 0; Char* descP = NULL; Char* resP = NULL; Char* saveResP = NULL; MemHandle descH = NULL; MemHandle resH = NULL; RepeatInfoType repeat; FieldType* fld = GetObjectPointer(frm, RepeatDescField); FldEraseField (fld); /* Get the current setting of the repeat ui gadgets. */ RepeatGetUIValues(frm, &repeat); /* ** Determine which template string to use. The template string is ** used to format the description string. Note that we could add ** a soft constant which tells us whether we need to use different ** strings for freq == 1 case (depends on language), thus saving space. */ freq = repeat.repeatFrequency; switch (repeat.repeatType) { case repeatNone: templateId = repeatNoneString; break; case repeatHourly: if (freq == 1) /* "Every hour" */ templateId = everyHourRepeatString; else /* "Every [other | 2nd | 3rd...] hour" */ templateId = hourlyRepeatString; break; case repeatDaily: if (freq == 1) /* "Every day" */ templateId = everyDayRepeatString; else /* "Every [other | 2nd | 3rd...] day" */ templateId = dailyRepeatString; break; case repeatWeekly: if (freq == 1) /* "Every week on [days of week]" */ templateId = everyWeekRepeat1DayString; else templateId = weeklyRepeat1DayString; /* ** Generate offset to appropriate string id, ** based on # of days that we need to append. */ for (i = 0; i < daysInWeek; i++) { if (repeat.repeatOn & (1 << i) ) repeatOnCount++; } templateId += repeatOnCount - 1; break; case repeatMonthlyByDate: if (freq == 1) /* "The ^w ^d of every month" */ templateId = everyMonthByDateRepeatString; else templateId = monthlyByDateRepeatString; break; case repeatMonthlyByDay: if (freq == 1) templateId = everyMonthByDayRepeatString; else templateId = monthlyByDayRepeatString; break; case repeatYearly: if (freq == 1) templateId = everyYearRepeatString; else templateId = yearlyRepeatString; break; default: ErrNonFatalDisplay("Unknown repeat type"); break; } /* ** Allocate a block to hold the description and copy the template ** string into it. */ resH = DmGetResource(strRsc, templateId); resP = MemHandleLock(resH); descH = MemHandleNew(MemPtrSize(resP)); ASSERT(descH); descP = MemHandleLock(descH); StrCopy(descP, resP); MemHandleUnlock(resH); /* Substitute the month name string for the month name token. */ resH = DmGetResource(strRsc, repeatMonthNamesString); resP = MemHandleLock(resH); for (i = 1; i < d.frm_date.month; i++) resP = StrChr(resP, spaceChr) + 1; len = StrChr(resP, spaceChr) - resP; descP = SubstituteStr(descP, monthNameToken, resP, len); MemHandleUnlock(resH); /* Substitute the day name string for the day name token. */ if ((repeatOnCount == 1) || (repeat.repeatType == repeatMonthlyByDay)) templateId = repeatFullDOWNamesString; else templateId = repeatShortDOWNamesString; resH = DmGetResource(strRsc, templateId); resP = MemHandleLock(resH); if (repeat.repeatType == repeatWeekly) { dayOfWeek = repeat.repeatStartOfWeek; repeatOn = repeat.repeatOn; saveResP = resP; while (StrStr (descP, dayNameToken)) { for (i = 0; i < daysInWeek; i++) { if (repeatOn & (1 << dayOfWeek)) { repeatOn &= ~(1 << dayOfWeek); break; } dayOfWeek = (dayOfWeek + 1 + daysInWeek) % daysInWeek; } resP = saveResP; for (i = 0; i < dayOfWeek; i++) resP = StrChr(resP, spaceChr) + 1; len = StrChr(resP, spaceChr) - resP; descP = SubstituteStr(descP, dayNameToken, resP, len); } } else { dayOfWeek = DayOfWeek (d.frm_date.month, d.frm_date.day, d.frm_date.year/* + firstYear*/); for (i = 0; i < dayOfWeek; i++) resP = StrChr(resP, spaceChr) + 1; len = StrChr(resP, spaceChr) - resP; descP = SubstituteStr(descP, dayNameToken, resP, len); } MemHandleUnlock (resH); /* ** Substitute the repeat frequency string for the frequency token. Note that ** we do something special for 2nd (other), since the gender of 'other' changes ** for some languages, depending on whether the next word is day, month, week, ** or year. */ if (freq == 2) { Char otherFreqName[16]; const UInt16 index = repeat.repeatType - repeatNone; SysStringByIndex(freqOrdinal2ndStrlID, index, otherFreqName, sizeof(otherFreqName)); descP = SubstituteStr(descP, frequenceToken, otherFreqName, StrLen(otherFreqName)); } else { resH = DmGetResource(strRsc, freqOrdinalsString); resP = MemHandleLock(resH); for (i = 1; i < freq; i++) resP = StrChr(resP, spaceChr) + 1; len = StrChr(resP, spaceChr) - resP; descP = SubstituteStr(descP, frequenceToken, resP, len); MemHandleUnlock(resH); } /* ** Substitute the repeat week string (1st, 2nd, 3rd, 4th, or last) ** for the week ordinal token. */ if (repeat.repeatType == repeatMonthlyByDay) { resH = DmGetResource(strRsc, weekOrdinalsString); resP = MemHandleLock(resH); for (i = 0; i < repeat.repeatOn / daysInWeek; i++) resP = StrChr (resP, spaceChr) + 1; len = StrChr(resP, spaceChr) - resP; descP = SubstituteStr(descP, weekOrdinalToken, resP, len); MemHandleUnlock(resH); } else { /* make sure the week ordinal token really doesn't appear */ ErrNonFatalDisplayIf(StrStr(descP, weekOrdinalToken) != NULL, "week ordinal not substituted"); } /* ** Substitute the repeat date string (1st, 2nd, ..., 31th) for the ** day ordinal token. */ resH = DmGetResource(strRsc, dayOrdinalsString); resP = MemHandleLock(resH); for (i = 1; i < d.frm_date.day; i++) resP = StrChr(resP, spaceChr) + 1; len = StrChr(resP, spaceChr) - resP; descP = SubstituteStr(descP, dayOrdinalToken, resP, len); MemHandleUnlock(resH); /* Draw the description. */ MemHandleUnlock(descH); FldFreeMemory(fld); FldSetTextHandle(fld, descH); FldDrawField(fld); }
/*********************************************************************** * * FUNCTION: SelectTimeZone * * DESCRIPTION: Display a form showing a time zone and allow the user * to select a different time zone. This is the time zone * dialog as seen in Date & Dime panel * * PARAMETERS: * ioTimeZoneP <-> pointer to time zone to change * ioLocaleInTimeZoneP <-> Ptr to locale found in time zone. * titleP -> String title for the dialog. * showTimes -> True => show current and new times * anyLocale -> True => ignore ioLocaleInTimeZoneP on entry. * * RETURNED: * true if the OK button was pressed (in which case *ioTimeZoneP and * *ioCountryInTimeZoneP might be changed). * * HISTORY: * 03/02/00 peter Created by Peter Epstein. * 04/03/00 peter Allow NULL currentTimeP. * 04/12/00 peter API changed to get rid of trigger text * 04/14/00 peter Update current & new time as time passes * 07/31/00 kwk Use SysTicksPerSecond() routine vs. sysTicksPerSecond macro. * kwk Re-wrote to use set of resources (name, offset, country), * scrollbar vs. arrows, etc. * 08/01/00 kwk Support scroll-to-key. Fixed scrollbar/list sync bugs. * 08/02/00 kwk New API w/ioCountryInTimeZoneP and anyCountry parameters. * kwk Call FrmHandleEvent _after_ our event handling code has * decided that it doesn't want to handle the event, not before. * 08/03/00 kwk Call LstSetListChoices before calling LstGetVisibleItems, * as otherwise accessing the time zone picker from the * Setup app (when <showTimes> is false) gives you a two- * line high display because LstGetVisibleItems returns 0. * 08/18/00 kwk Play error sound if user writes letter that doesn't * match any entries. * kwk Don't select item if doing scroll-to-view for entry * that matches the letter the user wrote. * 08/21/00 kwk Scroll-to-view with text entry now scrolls to the top * of the list, versus the middle. * 10/09/00 peter Get rid of scroll bar and let list do the scrolling. * 11/17/00 CS Change ioCountryInTimeZoneP to ioLocaleInTimeZoneP, * (and anyCountry to anyLocale, but that doesn't matter), * since CountryType is only a UInt8, and this may * change someday. * ***********************************************************************/ Boolean SelectTimeZone(Int16 *ioTimeZoneP, LmLocaleType* ioLocaleInTimeZoneP, const Char *titleP, Boolean showTimes, Boolean anyLocale) { FormType* originalForm; FormType* dialog; EventType event; Boolean confirmed = false; Boolean done = false; Boolean adjustTimes = false; Boolean foundLocale = false; MemHandle currentTimeHandle, newTimeHandle; ListPtr listP; Int16 oldTimeZone, newTimeZone, testTimeZone; LmLocaleType newTimeZoneLocale; Int16 delta, closestDelta, timeZoneIndex, closestIndex; DateTimeType currentTime, newTime; TimeZoneEntryType* tzArrayP; UInt16 numTimeZones; MemHandle tzNamesH; if (showTimes) { TimSecondsToDateTime(TimGetSeconds(), ¤tTime); } oldTimeZone = *ioTimeZoneP; newTimeZone = oldTimeZone; newTimeZoneLocale = *ioLocaleInTimeZoneP; originalForm = FrmGetActiveForm(); dialog = (FormType *) FrmInitForm (TimeZoneDialogForm); listP = FrmGetObjectPtr (dialog, FrmGetObjectIndex (dialog, TimeZoneDialogTimeZoneList)); if (titleP) { FrmSetTitle (dialog, (Char *) titleP); } FrmSetActiveForm (dialog); // We need to call LstSetListChoices before calling LstSetHeight below, as otherwise // LstGetVisibleItems will return 0. tzArrayP = PrvCreateTimeZoneArray(&tzNamesH, &numTimeZones); LstSetListChoices(listP, (Char**)tzArrayP, numTimeZones); if (showTimes) { currentTimeHandle = MemHandleNew(timeStringLength + 1 + dowLongDateStrLength + 1); ErrFatalDisplayIf (!currentTimeHandle, "Out of memory"); newTimeHandle = MemHandleNew(timeStringLength + 1 + dowLongDateStrLength + 1); ErrFatalDisplayIf (!newTimeHandle, "Out of memory"); PrvSetTimeField(dialog, TimeZoneDialogCurrentTimeField, currentTimeHandle, ¤tTime, false); } else { // Hide the current and new time. FrmHideObject(dialog, FrmGetObjectIndex (dialog, TimeZoneDialogCurrentTimeLabel)); FrmHideObject(dialog, FrmGetObjectIndex (dialog, TimeZoneDialogCurrentTimeField)); FrmHideObject(dialog, FrmGetObjectIndex (dialog, TimeZoneDialogNewTimeLabel)); FrmHideObject(dialog, FrmGetObjectIndex (dialog, TimeZoneDialogNewTimeField)); // Make the list show more items to take up extra the space. LstSetHeight(listP, LstGetVisibleItems(listP) + extraTimeZonesToShowWhenNoTimes); } // Find the time zone in the list closest to the current time zone, and that // matches <*ioLocaleInTimeZoneP> if <anyLocale> is false. closestDelta = hoursInMinutes * hoursPerDay; // so big that all others will be smaller for (timeZoneIndex = 0; timeZoneIndex < numTimeZones; timeZoneIndex++) { Boolean checkDelta = anyLocale; testTimeZone = tzArrayP[timeZoneIndex].tzOffset; delta = Abs(testTimeZone - oldTimeZone); if (!anyLocale) { if (tzArrayP[timeZoneIndex].tzCountry == ioLocaleInTimeZoneP->country) { // If we haven't previously found a matching locale, reset the // delta so that this entry overrides any previous best entry. if (!foundLocale) { foundLocale = true; closestDelta = hoursInMinutes * hoursPerDay; } checkDelta = true; } // If we haven't yet found a matching locale, go for the closest delta. else { checkDelta = !foundLocale; } } // If we want to check the time zone delta, do it now. if (checkDelta && (delta < closestDelta)) { closestIndex = timeZoneIndex; closestDelta = delta; } } // Scroll so that time zone is in the center of the screen and select it if it's an exact match. LstSetTopItem(listP, max(0, closestIndex - (LstGetVisibleItems(listP) / 2))); if ((closestDelta == 0) && (anyLocale || foundLocale)) { LstSetSelection(listP, closestIndex); if (showTimes) { newTime = currentTime; PrvSetTimeField(dialog, TimeZoneDialogNewTimeField, newTimeHandle, &newTime, false); } } else { LstSetSelection(listP, noListSelection); } LstSetDrawFunction(listP, PrvTimeZoneListDrawItem); FrmDrawForm (dialog); while (!done) { Boolean handled = false; EvtGetEvent(&event, SysTicksPerSecond()); // so we can update the current and new time if (SysHandleEvent ((EventType *)&event)) { continue; } if (event.eType == nilEvent) { if (showTimes) { PrvUpdateTimeFields( dialog, ¤tTime, &newTime, currentTimeHandle, newTimeHandle, TimeZoneDialogCurrentTimeField, TimeZoneDialogNewTimeField); } } else if (event.eType == ctlSelectEvent) { handled = true; switch (event.data.ctlSelect.controlID) { case TimeZoneDialogOKButton: // Set the new time zone. *ioTimeZoneP = newTimeZone; *ioLocaleInTimeZoneP = newTimeZoneLocale; done = true; confirmed = true; break; case TimeZoneDialogCancelButton: done = true; break; default: ErrNonFatalDisplay("Unknown control in form"); break; } } // User tapped on a time zone in the list. else if (event.eType == lstSelectEvent) { UInt16 localeIndex; ErrNonFatalDisplayIf(event.data.lstSelect.listID != TimeZoneDialogTimeZoneList, "Unknown list in form"); newTimeZone = tzArrayP[event.data.lstSelect.selection].tzOffset; newTimeZoneLocale.country = tzArrayP[event.data.lstSelect.selection].tzCountry; newTimeZoneLocale.language = lmAnyLanguage; if (LmLocaleToIndex(&newTimeZoneLocale, &localeIndex) == errNone) { if (LmGetLocaleSetting( localeIndex, lmChoiceLocale, &newTimeZoneLocale, sizeof(newTimeZoneLocale))) { ErrNonFatalDisplay("Can\'t get locale"); } } adjustTimes = showTimes; handled = true; } else if (event.eType == keyDownEvent) { if (!TxtCharIsHardKey(event.data.keyDown.modifiers, event.data.keyDown.chr)) { // Hard scroll buttons if (EvtKeydownIsVirtual(&event)) { if (event.data.keyDown.chr == vchrPageUp) { handled = true; LstScrollList(listP, winUp, LstGetVisibleItems(listP) - 1); } else if (event.data.keyDown.chr == vchrPageDown) { handled = true; LstScrollList(listP, winDown, LstGetVisibleItems(listP) - 1); } } else if (TxtCharIsPrint(event.data.keyDown.chr)) { Int16 index; handled = true; index = PrvSearchTimeZoneNames(tzArrayP, numTimeZones, event.data.keyDown.chr); if (index != noListSelection) { Int16 delta = index - listP->topItem; if (delta < 0) { LstScrollList(listP, winUp, -delta); } else if (delta > 0) { LstScrollList(listP, winDown, delta); } } else { SndPlaySystemSound(sndError); } } } } else if (event.eType == appStopEvent) { EvtAddEventToQueue (&event); done = true; break; } // If we didn't handle the event, give the form code a crack at it. // This simulates the "normal" method of installing an event handler // for a form, which gets called, and then if it returns false, the // FrmHandleEvent routine gets called. if (!handled) { FrmHandleEvent(dialog, &event); } // If something changed, and we need to update our time display, // do it now. if (adjustTimes) { adjustTimes = false; newTime = currentTime; TimAdjust(&newTime, (Int32)(newTimeZone - oldTimeZone) * minutesInSeconds); PrvSetTimeField(dialog, TimeZoneDialogNewTimeField, newTimeHandle, &newTime, true); } } // end while true if (showTimes) { FldSetTextHandle(FrmGetObjectPtr (dialog, FrmGetObjectIndex (dialog, TimeZoneDialogCurrentTimeField)), NULL); FldSetTextHandle(FrmGetObjectPtr (dialog, FrmGetObjectIndex (dialog, TimeZoneDialogNewTimeField)), NULL); MemHandleFree(currentTimeHandle); MemHandleFree(newTimeHandle); } FrmEraseForm (dialog); FrmDeleteForm (dialog); FrmSetActiveForm(originalForm); PrvDeleteTimeZoneArray(tzArrayP, tzNamesH); return confirmed; } // SelectTimeZone