1 line
151 KiB
C
Executable File
1 line
151 KiB
C
Executable File
/* Copyright (c) 2017, Computer History Museum
|
||
All rights reserved.
|
||
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to
|
||
the limitations in the disclaimer below) provided that the following conditions are met:
|
||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||
disclaimer in the documentation and/or other materials provided with the distribution.
|
||
* Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products
|
||
derived from this software without specific prior written permission.
|
||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE
|
||
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||
DAMAGE. */
|
||
|
||
#include "nickexp.h"
|
||
#define FILE_NUM 6
|
||
/* Copyright (c) 1990-1992 by the University of Illinois Board of Trustees */
|
||
/**********************************************************************
|
||
* expansion of nicknames
|
||
**********************************************************************/
|
||
|
||
#pragma segment NickExp
|
||
|
||
#pragma mark ========== Declarations ==========
|
||
|
||
|
||
#pragma mark constants & defines
|
||
|
||
#define NICK_TABLE_ALLOC_BLOCK_COUNT 20 /* number of NicknameTableEntry elements to add when we grow a NicknameTable */
|
||
|
||
#undef NE_PARAM_VALIDATION /* DEBUG *//* if defined, some extra stuff gets validated for debugging purposes (requires that MacsBug be installed) *//* FINISH *//* REMOVE */
|
||
#undef NTA_PREVENT_EXACT_MATCH /* if defined, nickname type-ahead won't complete to nicknames matching the prefix exactly */
|
||
|
||
|
||
#pragma mark globals
|
||
|
||
//
|
||
// These are global representations of the current Preference settings
|
||
//
|
||
|
||
Boolean gNicknameTypeAheadEnabled = false; // true if nickname type-ahead is turned on
|
||
Boolean gNicknameWatcherImmediate = false; // true if nickname type-ahead should expand the nickname on keyDown, as opposed to during idle time
|
||
Boolean gNicknameWatcherWaitKeyThresh = false; // true if nickname type-ahead should wait for KeyThresh ticks before doing the expansion
|
||
Boolean gNicknameWatcherWaitNoKeyDown = false; // true if nickname type-ahead should wait until there are no keyDown events in the Event Queue before doing the expansion
|
||
Boolean gNicknameHilitingEnabled = false; // true if nickname hilighting is turned on
|
||
Boolean gNicknameAutoExpandEnabled = false; // true if nickname expansion is turned on
|
||
Boolean gNicknameCacheEnabled = false; // true if nickname caching is turned on
|
||
|
||
//
|
||
// A few more globals... Semi-preference related, but not field related either
|
||
//
|
||
|
||
Str255 gRecipientDelimiterList;
|
||
long gTypeAheadIdleDelay = 15; // delay, in ticks, that we wait from the last keystroke before we do nickname type-ahead processing
|
||
long gHilitingIdleDelay = 15; // delay, in ticks, that we wait from the last keystroke before we do nickname type-ahead processing
|
||
|
||
//
|
||
// Globals we _may_ want to move into the PeteExtra record...
|
||
//
|
||
|
||
Str255 gTypeAheadPrefix; // prefix of current nickname type-ahead selection; this is the text the user actually typed, and isn't included in the selection
|
||
Str255 gTypeAheadSuffix; // suffix of current nickname type-ahead selection; this is the part that we inserted and selected ourselves
|
||
NicknameTableHdl gTypeAheadNicknameTable; // alphabetized NicknameTableHdl of all nicknames which match the current prefix
|
||
long gTypeAheadNicknameTableIndex; // index of the entry in gTypeAheadNicknameTable of the nickname used for the current nickname type-ahead selection
|
||
|
||
|
||
//
|
||
// Constants that could conceivably become preferences, but are now constants to eliminate evil globals
|
||
//
|
||
|
||
#define kMinCharsForTypeAhead 1
|
||
|
||
|
||
/************************************************************************
|
||
* 7/17/96 ALB
|
||
* Performance update for very large nickname files.
|
||
* - Each nickname is no longer stored in a separate handle. They
|
||
* are all stored in one handle with an offset to each one
|
||
* - Addresses, notes, and view data are no longer kept in a separate handle
|
||
************************************************************************/
|
||
|
||
|
||
|
||
#pragma mark ========== Utility routines ==========
|
||
Boolean AppearsInWhichAliasFile(uLong addrHash,short which);
|
||
|
||
|
||
#ifdef NE_PARAM_VALIDATION
|
||
/* DEBUG *//* REMOVE */
|
||
void NEValidateWin(MyWindowPtr win)
|
||
|
||
{
|
||
WindowPtr winWP = GetMyWindowWindowPtr (win);
|
||
OSErr err;
|
||
PETEHandle pte;
|
||
MessHandle messH;
|
||
PeteExtraHandle peteExtra;
|
||
UHandle textHdl;
|
||
|
||
if (!win)
|
||
{
|
||
DebugStr("\pnil win");
|
||
return;
|
||
}
|
||
pte = NickPeteToWatch (win);
|
||
if (!PeteIsValid(pte))
|
||
{
|
||
DebugStr("\pnil pte");
|
||
return;
|
||
}
|
||
if (GetWindowKind(winWP)==COMP_WIN || GetWindowKind(winWP)==MESS_WIN) {
|
||
messH = Win2MessH(win);
|
||
if (!messH)
|
||
{
|
||
DebugStr("\pnil messH");
|
||
return;
|
||
}
|
||
}
|
||
peteExtra = (PeteExtraHandle)PETEGetRefCon(PETE, pte);
|
||
if (!peteExtra)
|
||
{
|
||
DebugStr("\pnil peteExtra");
|
||
return;
|
||
}
|
||
err = PeteGetRawText(pte, &textHdl);
|
||
if (err)
|
||
{
|
||
lDebugLong("\perr on PeteGetRawText = #", err);
|
||
return;
|
||
}
|
||
if (!textHdl)
|
||
{
|
||
DebugStr("\pnil textHdl");
|
||
return;
|
||
}
|
||
}
|
||
|
||
void NEValidatePte(PETEHandle pte)
|
||
|
||
{
|
||
if (!PeteIsValid(pte))
|
||
{
|
||
DebugStr("\pnil pte");
|
||
return;
|
||
}
|
||
NEValidateWin ((*PeteExtra(pte))->win);
|
||
}
|
||
|
||
#endif
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* move to stringutil.c? */
|
||
/************************************************************************
|
||
* NicknameRawEqualString
|
||
************************************************************************/
|
||
Boolean NicknameRawEqualString(ConstStr255Param str1, ConstStr255Param str2)
|
||
|
||
{
|
||
/* FINISH *//* as per Mr. Cannon's useful response, rewrite this to not use any Apple routines! */
|
||
|
||
if (str1[0] != str2[0])
|
||
return false;
|
||
return (memcmp(str1 + 1, str2 + 1, str1[0]) ? false : true); /* FINISH *//* use something else? */
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* move to stringutil.c? */
|
||
/************************************************************************
|
||
* NicknameNormalizeString
|
||
************************************************************************/
|
||
void NicknameNormalizeString(Str255 theString, Boolean caseSens, Boolean diacSens, Boolean stickySens)
|
||
|
||
{
|
||
/* FINISH *//* as per Mr. Cannon's useful response, rewrite this to not use any Apple routines! */
|
||
/* nothing funny, Martin. FurrinSort controls whether the script manager should be used for this
|
||
sort of thing. Big performance gains if not. SD */
|
||
|
||
if (!caseSens && !diacSens)
|
||
UppercaseStripDiacritics(theString + 1, theString[0], smRoman); /* FINISH *//* support scripts other than smRoman */
|
||
else if (!caseSens)
|
||
MyUpperText(theString + 1, theString[0]); // uses script manager or not, depending on FurrinSort
|
||
else if (!diacSens)
|
||
StripDiacritics(theString + 1, theString[0], smRoman); /* FINISH *//* support scripts other than smRoman */
|
||
if (!stickySens)
|
||
StripStickyCharacters(theString);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* move to stringutil.c? */
|
||
/************************************************************************
|
||
* NicknameEqualString
|
||
************************************************************************/
|
||
Boolean NicknameEqualString(ConstStr255Param str1, ConstStr255Param str2, Boolean caseSens, Boolean diacSens, Boolean stickySens)
|
||
|
||
{
|
||
Str255 str1_, str2_;
|
||
|
||
|
||
if (caseSens && diacSens && stickySens)
|
||
return NicknameRawEqualString(str1, str2);
|
||
|
||
BlockMoveData(str1, str1_, str1[0] + 1);
|
||
BlockMoveData(str2, str2_, str2[0] + 1);
|
||
NicknameNormalizeString(str1_, caseSens, diacSens, stickySens);
|
||
NicknameNormalizeString(str2_, caseSens, diacSens, stickySens);
|
||
return NicknameRawEqualString(str1_, str2_);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* move to stringutil.c? */
|
||
/************************************************************************
|
||
* EqualStringPrefix - returns true if the string passed in prefixStr
|
||
* is equal to the beginning of otherStr. If caseSens is false,
|
||
* differences in case will be ignored. If diacSens is false,
|
||
* differences in diacritical markings (<28>, <20>, <20>, etc.) will be ignored.
|
||
* If stickySens is false, differences in sticky (non-breaking)
|
||
* vs. non-sticky spaces and dashes will be ignored.
|
||
*
|
||
* This routine will not move or purge memory, although it will drool if
|
||
* it is exposed to doughnuts for prolonged periods of time.
|
||
************************************************************************/
|
||
/* FINISH *//* we may now be allocating memory, so adjust the comment */
|
||
Boolean EqualStringPrefix(Str255 prefixStr, Str255 otherStr, Boolean caseSens, Boolean diacSens, Boolean stickySens)
|
||
|
||
{
|
||
Str255 otherStr_;
|
||
|
||
|
||
if (prefixStr[0] > otherStr[0])
|
||
return false;
|
||
BlockMoveData(otherStr, otherStr_, otherStr[0] + 1);
|
||
otherStr_[0] = prefixStr[0];
|
||
return NicknameEqualString(prefixStr, otherStr_, caseSens, diacSens, stickySens);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* move to stringutil.c? */
|
||
/************************************************************************
|
||
* CharIsTypingChar
|
||
************************************************************************/
|
||
Boolean CharIsTypingChar(unsigned char keyChar, unsigned char keyCode)
|
||
|
||
{
|
||
#pragma unused (keyChar)
|
||
|
||
if ((keyCode >= 0x60) && (keyCode <= 0x65))
|
||
return false;
|
||
if ((keyCode >= 0x71) && (keyCode <= 0x7A))
|
||
return false;
|
||
switch (keyCode)
|
||
{
|
||
case 0x35:
|
||
case 0x67:
|
||
case 0x69:
|
||
case 0x6B:
|
||
case 0x6D:
|
||
case 0x6F:
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
|
||
#pragma mark ========== Nickname data routines ==========
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* move to nickmng.c */
|
||
/************************************************************************
|
||
* GetNicknameEmailAddressPStr - this routine fetches the Pascal string
|
||
* of the e-mail address for a nickname. The nickname is specified by
|
||
* aliasIndex and nicknameIndex; aliasIndex is the index of the alias
|
||
* (i.e. (*Aliases)[aliasIndex]), and nicknameIndex is the index of the
|
||
* nickname within the alias referenced by aliasIndex. If there is
|
||
* more than one address stored with that nickname, all of the addresses
|
||
* are all returned, delimited by commas. If the address field for that
|
||
* nickname contains nicknames, no attempt is made to expand those
|
||
* nicknames (if you are looking to do this, try using ExpandAliases).
|
||
* The resulting string is returned in emailAddressStr, and a pointer
|
||
* to this string is also returned as the function result.
|
||
*
|
||
* WARNING: The caller is responsible for making sure that
|
||
* RegenerateAllAliases was called prior to calling this routine.
|
||
************************************************************************/
|
||
PStr GetNicknameEmailAddressPStr(short aliasIndex, short nicknameIndex, PStr emailAddressStr)
|
||
{
|
||
Handle emailAddress;
|
||
unsigned long emailAddressSize;
|
||
|
||
|
||
emailAddressStr[0] = 0;
|
||
|
||
emailAddress = GetNicknameData(aliasIndex, nicknameIndex, true, true);
|
||
if (!emailAddress)
|
||
return emailAddressStr;
|
||
emailAddressSize = GetHandleSize(emailAddress);
|
||
if (!emailAddressSize)
|
||
return emailAddressStr;
|
||
if ((emailAddressSize == 1) && (**emailAddress == ',')) /* this is what GetNicknameData returns if the address field is empty */
|
||
return emailAddressStr;
|
||
if (emailAddressSize > 255)
|
||
emailAddressSize = 255;
|
||
BlockMoveData(*emailAddress, emailAddressStr + 1, emailAddressSize);
|
||
emailAddressStr[0] = emailAddressSize;
|
||
|
||
return emailAddressStr;
|
||
}
|
||
|
||
/************************************************************************
|
||
* GetNicknamePhrasePStr - given an alias, get the according-to-hoyle name/phrase
|
||
************************************************************************/
|
||
PStr GetNicknamePhrasePStr(short aliasIndex, short nicknameIndex, PStr nameStr)
|
||
{
|
||
// is there an explicit name field?
|
||
GetNicknameTrueNamePStr(aliasIndex,nicknameIndex,nameStr,false);
|
||
|
||
// No name field. Combine first and last names.
|
||
if (!*nameStr)
|
||
{
|
||
Str255 firstName,lastName;
|
||
Str31 tag;
|
||
|
||
GetTaggedFieldValueStr (aliasIndex,nicknameIndex, GetRString (tag, ABReservedTagsStrn + abTagFirst), firstName);
|
||
GetTaggedFieldValueStr (aliasIndex,nicknameIndex, GetRString (tag, ABReservedTagsStrn + abTagLast), lastName);
|
||
|
||
JoinFirstLast (nameStr, firstName, lastName);
|
||
}
|
||
|
||
// always return it, even if it's not meaningful
|
||
return nameStr;
|
||
}
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* move to nickmng.c */
|
||
/************************************************************************
|
||
* GetNicknameTrueNamePStr - this routine fetches the Pascal string
|
||
* of the name field for a nickname. The nickname is specified by
|
||
* aliasIndex and nicknameIndex; aliasIndex is the index of the alias
|
||
* (i.e. (*Aliases)[aliasIndex]), and nicknameIndex is the index of the
|
||
* nickname within the alias referenced by aliasIndex. If the name field
|
||
* for the specified nickname is empty, the function will return the
|
||
* nickname, surrounded by international quotes (<28> and <20>). The resulting
|
||
* string is returned in trueNameStr, and a pointer to this string is
|
||
* also returned as the function result.
|
||
*
|
||
* WARNING: The caller is responsible for making sure that
|
||
* RegenerateAllAliases was called prior to calling this routine.
|
||
************************************************************************/
|
||
PStr GetNicknameTrueNamePStr(short aliasIndex, short nicknameIndex, PStr trueNameStr, Boolean okToMakeItUp)
|
||
{
|
||
OSErr err;
|
||
Str255 nameStr;
|
||
Str255 nameFieldTag; /* the "notes" section of nickname data contains the true name; this string is used to tag that data */
|
||
Boolean nicknameNameEmpty;
|
||
|
||
|
||
trueNameStr[0] = 0;
|
||
|
||
nameStr[0] = 0;
|
||
GetRString(nameFieldTag, NickManKeyStrn + 1); /* get tag for Name field in nickname data */
|
||
err = NickGetDataFromField(nameFieldTag, nameStr, aliasIndex, nicknameIndex, !okToMakeItUp, false, &nicknameNameEmpty);
|
||
if (err)
|
||
return trueNameStr;
|
||
BlockMoveData(nameStr, trueNameStr, nameStr[0] + 1);
|
||
|
||
return trueNameStr;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* move to nickmng.c */
|
||
/************************************************************************
|
||
* GetExpandedNicknamePStr - this routine extracts the name field and
|
||
* e-mail address from a nickname, and builds them into a string. If
|
||
* both are present, the string is built into the format of
|
||
* True Name <addr@domain.xxx>, and if one of those fields is empty, then
|
||
* this function simply returns the non-empty field. The returned output
|
||
* should be suitable for simple display of this info in the user
|
||
* interface, but is not appropriate for generating actual text to go
|
||
* into the recipient fields of a message header.
|
||
*
|
||
* The nickname is specified by aliasIndex and nicknameIndex; aliasIndex
|
||
* is the index of the alias (i.e. (*Aliases)[aliasIndex]), and
|
||
* nicknameIndex is the index of the nickname within the alias referenced
|
||
* by aliasIndex. The resulting string is returned in
|
||
* expandedNicknameStr, and a pointer to this string is also returned as
|
||
* the function result.
|
||
*
|
||
* This function gets the strings for the name field and e-mail address
|
||
* by calling GetNicknameTrueNamePStr and GetNicknameEmailAddressPStr.
|
||
* For more details, see the comments for those routines.
|
||
*
|
||
* WARNING: The caller is responsible for making sure that
|
||
* RegenerateAllAliases was called prior to calling this routine.
|
||
************************************************************************/
|
||
PStr GetExpandedNicknamePStr(short aliasIndex, short nicknameIndex, PStr expandedNicknameStr)
|
||
{
|
||
Str255 nameStr;
|
||
Str255 emailAddressStr;
|
||
Str255 formatInfo;
|
||
StringPtr onlyString;
|
||
|
||
|
||
expandedNicknameStr[0] = 0;
|
||
|
||
GetNicknameTrueNamePStr(aliasIndex, nicknameIndex, nameStr, true);
|
||
GetNicknameEmailAddressPStr(aliasIndex, nicknameIndex, emailAddressStr);
|
||
|
||
if (nameStr[0] && emailAddressStr[0])
|
||
{
|
||
GetRString(formatInfo, ADD_REALNAME);
|
||
utl_PlugParams(formatInfo, expandedNicknameStr, emailAddressStr, nameStr, nil, nil);
|
||
}
|
||
else
|
||
{
|
||
onlyString = nameStr[0] ? nameStr : emailAddressStr;
|
||
BlockMoveData(onlyString, expandedNicknameStr, onlyString[0] + 1);
|
||
}
|
||
|
||
return expandedNicknameStr;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* move to nickmng.c */
|
||
/************************************************************************
|
||
* GetNicknameRecipientText - this routine allocates a handle and fills
|
||
* it with the expansion of the specified nickname. The returned output
|
||
* is suitable for inserting into a recipient field in a message header.
|
||
* If there are embedded nicknames, they will be recursively expanded.
|
||
* If there are nicknames which have more than one address, they will
|
||
* be expanded using group syntax (GroupName: addr1, addr2, ... addrN;)
|
||
*
|
||
* The nickname is specified by aliasIndex and nicknameIndex; aliasIndex
|
||
* is the index of the alias (i.e. (*Aliases)[aliasIndex]), and
|
||
* nicknameIndex is the index of the nickname within the alias referenced
|
||
* by aliasIndex. The resulting handle is returned in
|
||
* *recipientText; the handle size is set to the exact size of the text.
|
||
*
|
||
* The function returns noErr if it succeeded, or a non-zero error code
|
||
* if an error occurred.
|
||
*
|
||
* WARNING: The caller is responsible for making sure that
|
||
* RegenerateAllAliases was called prior to calling this routine.
|
||
************************************************************************/
|
||
OSErr GetNicknameRecipientText(PETEHandle pte, short aliasIndex, short nicknameIndex, UHandle *recipientText)
|
||
{
|
||
OSErr err;
|
||
Str255 nicknameStr;
|
||
long nicknameLen;
|
||
UHandle nicknameTxtHdl;
|
||
UHandle resultTxtHdl;
|
||
|
||
|
||
*recipientText = nil;
|
||
|
||
GetNicknameNamePStr(aliasIndex, nicknameIndex, nicknameStr);
|
||
if (!nicknameStr[0])
|
||
return paramErr;
|
||
nicknameLen = nicknameStr[0] + 1;
|
||
nicknameTxtHdl = NewHandleClear(nicknameLen + 5); /* FINISH *//* why +5? */
|
||
if (!nicknameTxtHdl)
|
||
return MemError();
|
||
BlockMoveData(nicknameStr, *nicknameTxtHdl, nicknameLen);
|
||
|
||
err = PeteExpandAliases(pte, &resultTxtHdl, nicknameTxtHdl, 0, true);
|
||
DisposeHandle(nicknameTxtHdl);
|
||
if (err)
|
||
return err;
|
||
|
||
CommaList(resultTxtHdl);
|
||
|
||
*recipientText = resultTxtHdl;
|
||
return noErr;
|
||
}
|
||
|
||
|
||
#pragma mark ========== Nickname search routines ==========
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* rename back to FindNickname */
|
||
/* WARNING *//* There is another routine called FindNickname in nickae.c. These two
|
||
routines are unrelated. The other FindNickname was implemented first,
|
||
but I chose to use this routine name anyways. This routine MUST
|
||
be declared as static in order for it to work properly. */
|
||
/************************************************************************
|
||
* NEFindNickname
|
||
************************************************************************/
|
||
Boolean NEFindNickname(Str255 nicknameSearchStr, Boolean caseSens, Boolean diacSens, Boolean stickySens, short *foundAliasIndex, short *foundNicknameIndex)
|
||
{
|
||
Str255 nicknameSearchStr_;
|
||
Boolean found;
|
||
long numAliases;
|
||
long numNicknames;
|
||
short aliasIndex;
|
||
short nicknameIndex;
|
||
NickStructHandle curAliasData;
|
||
Str255 curNicknameStr;
|
||
Str255 curNicknameStr_;
|
||
|
||
|
||
if (foundAliasIndex)
|
||
*foundAliasIndex = -1;
|
||
if (foundNicknameIndex)
|
||
*foundNicknameIndex = -1;
|
||
|
||
numAliases = NAliases;
|
||
|
||
BlockMoveData(nicknameSearchStr, nicknameSearchStr_, nicknameSearchStr[0] + 1);
|
||
NicknameNormalizeString(nicknameSearchStr_, caseSens, diacSens, stickySens);
|
||
|
||
found = false;
|
||
aliasIndex = 0;
|
||
nicknameIndex = 0;
|
||
while (!found && (aliasIndex < numAliases))
|
||
{
|
||
if (curAliasData = (*Aliases)[aliasIndex].theData)
|
||
{
|
||
numNicknames = GetHandleSize_(curAliasData)/sizeof(NickStruct);
|
||
while (!found && (nicknameIndex < numNicknames))
|
||
{
|
||
if (!(*curAliasData)[nicknameIndex].deleted)
|
||
{
|
||
GetNicknameNamePStr(aliasIndex, nicknameIndex, curNicknameStr);
|
||
if (curNicknameStr[0])
|
||
{
|
||
BlockMoveData(curNicknameStr, curNicknameStr_, curNicknameStr[0] + 1);
|
||
NicknameNormalizeString(curNicknameStr_, caseSens, diacSens, stickySens);
|
||
if (NicknameRawEqualString(nicknameSearchStr_, curNicknameStr_))
|
||
found = true;
|
||
}
|
||
}
|
||
if (!found)
|
||
nicknameIndex++;
|
||
}
|
||
}
|
||
if (!found)
|
||
{
|
||
nicknameIndex = 0;
|
||
aliasIndex++;
|
||
}
|
||
}
|
||
|
||
if (found)
|
||
{
|
||
if (foundAliasIndex)
|
||
*foundAliasIndex = aliasIndex;
|
||
if (foundAliasIndex)
|
||
*foundNicknameIndex= nicknameIndex;
|
||
}
|
||
return found;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* for this and its similar routines, pre-normalize prefixStr_, so that we don't re-do it each time we call EqualStringPrefix */
|
||
/************************************************************************
|
||
* FindNextNicknamePrefix - used to search through the aliases for a
|
||
* nickname which starts with the prefix passed in prefixStr. It stops
|
||
* and returns at each match found. The string compare is insensitive
|
||
* to case, diacritical markings, and special non-breaking characters.
|
||
*
|
||
* If a match was found, the function returns true, and returns the
|
||
* indices of the alias and nickname that identify the match in
|
||
* *foundAliasIndex and *foundNicknameIndex. If there are no more matches,
|
||
* or if startAliasIndex is out of range, the function returns false,
|
||
* and returns -1 in *foundAliasIndex and *foundNicknameIndex.
|
||
*
|
||
* The search begins with the alias specified by startAliasIndex and
|
||
* the nickname specified by startNicknameIndex. When you first start
|
||
* a search, pass in zero for both of these parameters. If
|
||
* startNicknameIndex specifies an index beyond the number of nicknames
|
||
* in the alias specified by startAliasIndex, that's okay; in this case,
|
||
* the search will be continued in the next alias. This means that you
|
||
* can cycle through all nicknames beginning with prefixStr by initially
|
||
* passing in zero for startAliasIndex and startNicknameIndex, and then
|
||
* making subsequent calls, passing in the previously returned value of
|
||
* *foundAliasIndex for startAliasIndex, and passing in 1 + the value
|
||
* returned in *foundNicknameIndex for startNicknameIndex, and repeating
|
||
* this until the function returns false.
|
||
*
|
||
* If you pass in false for allowExactMatch, this routine won't return
|
||
* nicknames which are exactly the same as prefixStr (i.e. if prefixStr
|
||
* is "Frank" and the nickname is "Frank"). If you pass true for this
|
||
* parameter, then all matches are returned, including an exact match.
|
||
*
|
||
* WARNING: The caller is responsible for making sure that
|
||
* RegenerateAllAliases was called prior to calling this routine.
|
||
************************************************************************/
|
||
Boolean FindNextNicknamePrefix(Str255 prefixStr, short startAliasIndex, short startNicknameIndex, short *foundAliasIndex, short *foundNicknameIndex, Boolean allowExactMatch, Boolean findInAllFiles)
|
||
{
|
||
long prefixLen;
|
||
Str255 prefixStr_;
|
||
Boolean found;
|
||
long numAliases;
|
||
long numNicknames;
|
||
long noAddressHashValue;
|
||
short aliasIndex;
|
||
short nicknameIndex;
|
||
NickStructHandle curAliasData;
|
||
Str255 nicknameStr;
|
||
|
||
*foundAliasIndex = -1;
|
||
*foundNicknameIndex = -1;
|
||
|
||
noAddressHashValue = NickHashString ("\p"); // The hash value for an empty expansion
|
||
|
||
numAliases = NAliases;
|
||
|
||
if ((startAliasIndex < 0) || (startAliasIndex >= numAliases) || (startNicknameIndex < 0))
|
||
return false;
|
||
|
||
/* make a local copy of prefixStr, and if the last two characters are ':;', remove them from the string */
|
||
prefixLen = prefixStr[0];
|
||
BlockMoveData(prefixStr, prefixStr_, prefixLen + 1);
|
||
if ((prefixStr_[prefixLen - 1] == ':') && (prefixStr_[prefixLen] == ';'))
|
||
prefixStr_[0] -= 2;
|
||
|
||
found = false;
|
||
aliasIndex = startAliasIndex;
|
||
nicknameIndex = startNicknameIndex;
|
||
while (!found && (aliasIndex < numAliases))
|
||
{
|
||
curAliasData = (*Aliases)[aliasIndex].theData;
|
||
numNicknames = curAliasData ? GetHandleSize_(curAliasData)/sizeof(NickStruct) : 0;
|
||
while (!found && (nicknameIndex < numNicknames))
|
||
{
|
||
if (!(*curAliasData)[nicknameIndex].deleted)
|
||
{
|
||
GetNicknameNamePStr(aliasIndex, nicknameIndex, nicknameStr);
|
||
if (nicknameStr[0] && EqualStringPrefix(prefixStr_, nicknameStr, false, true, false) && (allowExactMatch || (prefixStr_[0] != nicknameStr[0]))) {
|
||
if (!PrefIsSet (PREF_ALLOW_EMPTY_NICK_EXPANSIONS)) {
|
||
if ((*curAliasData)[nicknameIndex].addressOffset != -1 && (*curAliasData)[nicknameIndex].hashAddress != noAddressHashValue)
|
||
found = true;
|
||
}
|
||
else
|
||
found = true;
|
||
}
|
||
}
|
||
if (!found)
|
||
nicknameIndex++;
|
||
}
|
||
if (!found)
|
||
{
|
||
nicknameIndex = 0;
|
||
if (findInAllFiles)
|
||
aliasIndex++;
|
||
else
|
||
aliasIndex = numAliases;
|
||
}
|
||
}
|
||
|
||
if (found)
|
||
{
|
||
*foundAliasIndex = aliasIndex;
|
||
*foundNicknameIndex= nicknameIndex;
|
||
}
|
||
return found;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* FindNextNicknameTablePrefix - used to search through the nicknames
|
||
* in a NicknameTableHdl for a nickname which starts with prefixStr.
|
||
* It stops and returns at each match found. The string compare is
|
||
* insensitive to case, diacritical markings, and special non-breaking
|
||
* characters. This routine is comperable to FindNextNicknamePrefix,
|
||
* except that it searches a nickname table instead of the master
|
||
* nickname data.
|
||
*
|
||
* If a match was found, the function returns true, and returns the
|
||
* index of the matching table entry in *foundEntryIndex. If there are
|
||
* no more matches, or if startEntryIndex is out of range, the function
|
||
* returns false, and returns -1 in *foundEntryIndex.
|
||
*
|
||
* The search begins with the entry specified by startEntryIndex. When
|
||
* you first start a search, pass in zero for this parameter. To search
|
||
* the entire nickname table, keep making subsequent calls, passing in
|
||
* 1 + the value returned in *foundEntryIndex for startEntryIndex, and
|
||
* repeating this until the function returns false.
|
||
*
|
||
* If you pass in false for allowExactMatch, this routine won't return
|
||
* nicknames which are exactly the same as prefixStr (i.e. if prefixStr
|
||
* is "Frank" and the nickname is "Frank"). If you pass true for this
|
||
* parameter, then all matches are returned, including an exact match.
|
||
************************************************************************/
|
||
Boolean FindNextNicknameTablePrefix(Str255 prefixStr, NicknameTableHdl nicknameTable, long startEntryIndex, long *foundEntryIndex, Boolean allowExactMatch)
|
||
{
|
||
long prefixLen;
|
||
Str255 prefixStr_;
|
||
Boolean found;
|
||
long numEntries;
|
||
long entryIndex;
|
||
NicknameTableEntryPtr entryPtr;
|
||
StringPtr nicknameStrPtr;
|
||
SInt8 origState;
|
||
|
||
|
||
*foundEntryIndex = -1;
|
||
|
||
numEntries = (**nicknameTable).numEntries;
|
||
|
||
if ((startEntryIndex < 0) || (startEntryIndex >= numEntries))
|
||
return false;
|
||
|
||
/* make a local copy of prefixStr, and if the last two characters are ':;', remove them from the string */
|
||
prefixLen = prefixStr[0];
|
||
BlockMoveData(prefixStr, prefixStr_, prefixLen + 1);
|
||
if ((prefixStr_[prefixLen - 1] == ':') && (prefixStr_[prefixLen] == ';'))
|
||
prefixStr_[0] -= 2;
|
||
|
||
origState = HGetState((Handle)nicknameTable);
|
||
HLock((Handle)nicknameTable);
|
||
|
||
found = false;
|
||
entryIndex = startEntryIndex;
|
||
entryPtr = &(**nicknameTable).table[entryIndex];
|
||
while (!found && (entryIndex < numEntries))
|
||
{
|
||
nicknameStrPtr = entryPtr->entryStr;
|
||
if (nicknameStrPtr[0] && EqualStringPrefix(prefixStr_, nicknameStrPtr, false, true, false) && (allowExactMatch || (prefixStr_[0] != nicknameStrPtr[0])))
|
||
found = true;
|
||
else
|
||
{
|
||
entryIndex++;
|
||
entryPtr++;
|
||
}
|
||
}
|
||
|
||
HSetState((Handle)nicknameTable, origState);
|
||
|
||
if (found)
|
||
*foundEntryIndex = entryIndex;
|
||
return found;
|
||
}
|
||
|
||
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknamePrefixOccursNTimes - returns true if there are at least nTimes
|
||
* nicknames beginning with the string prefixStr
|
||
************************************************************************/
|
||
Boolean NicknamePrefixOccursNTimes(Str255 prefixStr, long nTimes, short aliasFileToScan)
|
||
{
|
||
OSErr err;
|
||
short aliasIndex, nicknameIndex;
|
||
long count;
|
||
Boolean found, finished;
|
||
|
||
|
||
err = RegenerateAllAliases(false); /* FINISH *//* shouldn't we be passing in true? */
|
||
if (err)
|
||
return false;
|
||
|
||
aliasIndex = aliasFileToScan == kNickScanAllAliasFiles ? 0 : aliasFileToScan;
|
||
nicknameIndex = 0;
|
||
count = 0;
|
||
finished = false;
|
||
|
||
while (!finished && (count < nTimes))
|
||
{
|
||
found = FindNextNicknamePrefix(prefixStr, aliasIndex, nicknameIndex, &aliasIndex, &nicknameIndex, true, aliasFileToScan == kNickScanAllAliasFiles);
|
||
if (found)
|
||
{
|
||
count++;
|
||
nicknameIndex++;
|
||
}
|
||
else
|
||
finished = true;
|
||
}
|
||
|
||
return count >= nTimes;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* FindNicknameTableIndex - this routine returns the entry index
|
||
* (zero-based) of the entry in the nickname table passed in
|
||
* nicknameTable which has an alias index of aliasIndex and a
|
||
* nickname index of nicknameIndex. If the specific combination of
|
||
* aliasIndex/nicknameIndex is not found in the table, the function
|
||
* returns -1.
|
||
************************************************************************/
|
||
long FindNicknameTableIndex(NicknameTableHdl nicknameTable, short aliasIndex, short nicknameIndex)
|
||
{
|
||
NicknameTablePtr tablePtr;
|
||
long numEntries;
|
||
long entryIndex;
|
||
NicknameTableEntryPtr entryPtr;
|
||
|
||
|
||
tablePtr = *nicknameTable;
|
||
numEntries = tablePtr->numEntries;
|
||
entryIndex = 0;
|
||
entryPtr = &tablePtr->table[0];
|
||
while (entryIndex < numEntries)
|
||
{
|
||
if ((aliasIndex == entryPtr->aliasIndex) && (nicknameIndex == entryPtr->nicknameIndex))
|
||
return entryIndex;
|
||
else
|
||
{
|
||
entryIndex++;
|
||
entryPtr++;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
long FindFirstNonHistoryNicknameTableIndex (NicknameTableHdl nicknameTable)
|
||
|
||
{
|
||
NicknameTablePtr tablePtr;
|
||
NicknameTableEntryPtr entryPtr;
|
||
long tableIndex,
|
||
numEntries;
|
||
|
||
tablePtr = *nicknameTable;
|
||
entryPtr = &tablePtr->table[0];
|
||
numEntries = tablePtr->numEntries;
|
||
for (tableIndex = 0; tableIndex < numEntries; ++tableIndex) {
|
||
if (!IsHistoryAddressBook (entryPtr->aliasIndex))
|
||
return (tableIndex);
|
||
entryPtr++;
|
||
}
|
||
return (0);
|
||
}
|
||
|
||
#pragma mark ========== Nickname table routines ==========
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* GetNicknameTableEntry - given a nickname table passsed in
|
||
* nicknameTable, this function returns in *aliasIndex and *nicknameIndex
|
||
* the alias index and nickname index of the table entry whose index is
|
||
* passed in entryIndex. If entryIndex is out of range, the function
|
||
* returns -1 in *aliasIndex and *nicknameIndex.
|
||
************************************************************************/
|
||
void GetNicknameTableEntry(NicknameTableHdl nicknameTable, long entryIndex, short *aliasIndex, short *nicknameIndex)
|
||
{
|
||
NicknameTablePtr tablePtr;
|
||
NicknameTableEntryPtr entryPtr;
|
||
|
||
|
||
tablePtr = *nicknameTable;
|
||
if ((entryIndex < 0) || (entryIndex >= tablePtr->numEntries))
|
||
{
|
||
*aliasIndex = -1;
|
||
*nicknameIndex = -1;
|
||
return;
|
||
}
|
||
|
||
entryPtr = &tablePtr->table[entryIndex];
|
||
*aliasIndex = entryPtr->aliasIndex;
|
||
*nicknameIndex = entryPtr->nicknameIndex;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NewNicknameTable - create a new empty nickname table, and return it
|
||
* in *nicknameTable. A nickname table is a convenient way to build a
|
||
* list of nicknames for temporary internal usage. Currently, it's
|
||
* being used for building the list of nicknames to display in the
|
||
* nicknames sticky popup menu. The function returns noErr if it
|
||
* succeeds, or a non-zero error code if it fails.
|
||
************************************************************************/
|
||
OSErr NewNicknameTable(NicknameTableHdl *nicknameTable)
|
||
{
|
||
NicknameTableHdl newTable;
|
||
NicknameTablePtr newTablePtr;
|
||
|
||
newTable = (NicknameTableHdl)NewHandleClear(sizeof(NicknameTable));
|
||
if (!newTable)
|
||
{
|
||
*nicknameTable = nil;
|
||
return MemError();
|
||
}
|
||
newTablePtr = *newTable;
|
||
newTablePtr->logicalSize = newTablePtr->physicalSize = sizeof(NicknameTable);
|
||
*nicknameTable = newTable;
|
||
return noErr;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* DisposeNicknameTable - dispose of the nickname table specified
|
||
* in nicknameTable
|
||
************************************************************************/
|
||
void DisposeNicknameTable(NicknameTableHdl nicknameTable)
|
||
{
|
||
DisposeHandle((Handle)nicknameTable);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* CompactNicknameTable - compacts the handle containing the nickname
|
||
* table passed in nicknameTable, so that it uses no more memory than
|
||
* is needed. Returns noErr if it succeeds, or a non-zero error code
|
||
* if an error occurs.
|
||
************************************************************************/
|
||
OSErr CompactNicknameTable(NicknameTableHdl nicknameTable)
|
||
{
|
||
OSErr err;
|
||
long logicalSize;
|
||
|
||
|
||
logicalSize = (**nicknameTable).logicalSize;
|
||
if ((**nicknameTable).physicalSize == logicalSize)
|
||
return noErr;
|
||
SetHandleSize((Handle)nicknameTable, logicalSize);
|
||
err = MemError();
|
||
if (err)
|
||
return err;
|
||
(**nicknameTable).physicalSize = logicalSize;
|
||
return noErr;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* GrowNicknameTable - routine used by AddNicknameTableEntry to manage
|
||
* the handle containing a nickname table. Rather than calling
|
||
* SetHandleSize to increase the handle's size each time we add an
|
||
* entry, we grow the handle in chunks. The nickname table is specified
|
||
* in nicknameTable. entryCount indicates the number of entries the
|
||
* caller is planning to add; if used carefully, you could pass in a
|
||
* negative number for this parameter, but you really shouldn't do that.
|
||
* The routine will adjust the NicknameTableHdl so that entryCount
|
||
* entries, can be added, and grow the handle if needed.
|
||
*
|
||
* The function returns noErr if it succeeds, or a non-zero error code
|
||
* if an error occurred.
|
||
************************************************************************/
|
||
OSErr GrowNicknameTable(NicknameTableHdl nicknameTable, long entryCount)
|
||
{
|
||
OSErr err;
|
||
NicknameTablePtr tablePtr;
|
||
long neededSize;
|
||
long newPhysicalSize;
|
||
|
||
|
||
tablePtr = *nicknameTable;
|
||
neededSize = tablePtr->logicalSize + (entryCount * sizeof(NicknameTableEntry));
|
||
if (tablePtr->physicalSize >= neededSize)
|
||
{
|
||
tablePtr->logicalSize = neededSize;
|
||
return noErr;
|
||
}
|
||
|
||
newPhysicalSize = tablePtr->physicalSize;
|
||
while (newPhysicalSize < neededSize)
|
||
newPhysicalSize += NICK_TABLE_ALLOC_BLOCK_COUNT * sizeof(NicknameTableEntry);
|
||
SetHandleSize((Handle)nicknameTable, newPhysicalSize);
|
||
err = MemError();
|
||
if (err)
|
||
return err;
|
||
|
||
tablePtr = *nicknameTable;
|
||
tablePtr->logicalSize = neededSize;
|
||
tablePtr->physicalSize = newPhysicalSize;
|
||
return noErr;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* AddNicknameTableEntry - add a nickname to the nickname table passed
|
||
* in nicknameTable. aliasIndex is the index of the alias
|
||
* (i.e. (*Aliases)[aliasIndex]), and nicknameIndex is the index of the
|
||
* nickname within the alias referenced by aliasIndex. newEntryStr is
|
||
* the string that will be entered in this entry for the specified
|
||
* nickname; it is not necessarily the nickname itself or its expansion,
|
||
* it's for whatever string you want to associate with that nickname.
|
||
*
|
||
* If you pass true for sortedInsert, the position where the entry is
|
||
* inserted is based on an alpha-numeric sort using newEntryStr. If you
|
||
* pass false for sortedInsert, the entry is just appended to the end of
|
||
* the table.
|
||
*
|
||
* If the function succeeds, it returns noErr. If it fails, it will
|
||
* return a non-zero error code, and the nickname table will remain
|
||
* unchanged.
|
||
************************************************************************/
|
||
OSErr AddNicknameTableEntry(NicknameTableHdl nicknameTable, short aliasIndex, short nicknameIndex, Str255 newEntryStr, Boolean sortedInsert)
|
||
{
|
||
OSErr err;
|
||
Str255 newEntryStr_normalized;
|
||
Str255 curEntryStr_normalized;
|
||
NicknameTablePtr tablePtr;
|
||
NicknameTableEntryPtr entryPtr;
|
||
long newEntryIndex;
|
||
Boolean foundInsertionPoint;
|
||
long entryIndex;
|
||
SInt8 origState;
|
||
long first, last;
|
||
short result;
|
||
|
||
|
||
err = GrowNicknameTable(nicknameTable, 1);
|
||
if (err)
|
||
return err;
|
||
|
||
origState = HGetState((Handle)nicknameTable);
|
||
HLock((Handle)nicknameTable);
|
||
tablePtr = *nicknameTable;
|
||
tablePtr->numEntries++;
|
||
newEntryIndex = tablePtr->numEntries - 1;
|
||
|
||
if (sortedInsert)
|
||
{
|
||
/* we do the search for the proper insertion point backwards, so that the search will be optimized
|
||
in the event that the caller is adding items in alpha-numeric sorted order */
|
||
|
||
BlockMoveData(newEntryStr, newEntryStr_normalized, newEntryStr[0] + 1);
|
||
StickyPopupNormalizeString(newEntryStr_normalized);
|
||
foundInsertionPoint = false;
|
||
first = 0;
|
||
last = newEntryIndex-1;
|
||
entryIndex = 0;
|
||
entryPtr = tablePtr->table;
|
||
result = -1;
|
||
while (first<=last)
|
||
{
|
||
entryIndex = (first+last)/2;
|
||
entryPtr = &tablePtr->table[entryIndex];
|
||
BlockMoveData(entryPtr->entryStr, curEntryStr_normalized, entryPtr->entryStr[0] + 1);
|
||
StickyPopupNormalizeString(curEntryStr_normalized);
|
||
result = StickyPopupCompareString(newEntryStr_normalized, curEntryStr_normalized, false);
|
||
if (!result) break; // found exact match
|
||
else if (result<0) last = entryIndex-1;
|
||
else first = entryIndex+1;
|
||
}
|
||
if (result<0) {entryIndex--;entryPtr--;} // last one we looked at was greater than we are, so insert before
|
||
entryIndex++;
|
||
entryPtr++;
|
||
if (entryIndex < newEntryIndex)
|
||
BlockMoveData(entryPtr, entryPtr + 1, (newEntryIndex - entryIndex) * sizeof(NicknameTableEntry));
|
||
}
|
||
else
|
||
{
|
||
entryIndex = newEntryIndex;
|
||
entryPtr = &tablePtr->table[newEntryIndex];
|
||
}
|
||
|
||
entryPtr->aliasIndex = aliasIndex;
|
||
entryPtr->nicknameIndex = nicknameIndex;
|
||
BlockMoveData(newEntryStr, entryPtr->entryStr, newEntryStr[0] + 1);
|
||
|
||
HSetState((Handle)nicknameTable, origState);
|
||
return noErr;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* BuildNicknameTable - builds a sorted nickname table of all nicknames
|
||
* beginning with a specific prefix string. prefixStr is the prefix
|
||
* string to search for. If showExpansions is set to true, then the
|
||
* entryStr in the table (and thus the popup) will include the expansion
|
||
* of the nicknames. If allowExactMatch is set to true, then the table
|
||
* will include nicknames which match prefixStr exactly (this parameter
|
||
* is passed in as the allowExactMatch parameter for calls to
|
||
* FindNextNicknamePrefix; see the comments for that routine for more
|
||
* details). The resulting table is returned via *builtNicknameTable.
|
||
* The table is sorted using the default sorting method of
|
||
* AddNicknameTableEntry. The function returns noErr if it succeeds,
|
||
* or a non-zero error code if it fails.
|
||
*
|
||
* You can limit the table to contain only those nicknames from a
|
||
* particular alias file by passing an alias index in 'inAliasFile', or
|
||
* -1 if you wish to include nicknames appearing in all alias files.
|
||
************************************************************************/
|
||
OSErr BuildNicknameTable(Str255 prefixStr, short inAliasFile, Boolean showExpansions, Boolean allowExactMatch, NicknameTableHdl *builtNicknameTable)
|
||
{
|
||
OSErr err;
|
||
NicknameTableHdl nicknameTable;
|
||
short aliasIndex;
|
||
short nicknameIndex;
|
||
Str255 nicknameStr;
|
||
Boolean finished;
|
||
|
||
*builtNicknameTable = nil;
|
||
|
||
err = NewNicknameTable(&nicknameTable);
|
||
if (err)
|
||
return err;
|
||
|
||
err = RegenerateAllAliases(false); /* FINISH *//* shouldn't we be passing in true? */
|
||
if (err)
|
||
{
|
||
DisposeNicknameTable(nicknameTable);
|
||
return err;
|
||
}
|
||
|
||
finished = false;
|
||
aliasIndex = inAliasFile == kNickScanAllAliasFiles ? 0 : inAliasFile;
|
||
nicknameIndex = 0;
|
||
while (!finished)
|
||
{
|
||
finished = !FindNextNicknamePrefix (prefixStr, aliasIndex, nicknameIndex, &aliasIndex, &nicknameIndex, allowExactMatch, inAliasFile == kNickScanAllAliasFiles);
|
||
if (!finished)
|
||
{
|
||
if (showExpansions)
|
||
GetExpandedNicknamePStr(aliasIndex, nicknameIndex, nicknameStr);
|
||
else
|
||
GetNicknameNamePStr(aliasIndex, nicknameIndex, nicknameStr);
|
||
err = AddNicknameTableEntry(nicknameTable, aliasIndex, nicknameIndex, nicknameStr, true);
|
||
if (err)
|
||
{
|
||
DisposeNicknameTable(nicknameTable);
|
||
return err;
|
||
}
|
||
nicknameIndex++;
|
||
}
|
||
if (EventPending()) {err = userCanceledErr; break;} // bail if something else to do
|
||
}
|
||
|
||
|
||
if (!err) err = CompactNicknameTable(nicknameTable);
|
||
if (err)
|
||
{
|
||
DisposeNicknameTable(nicknameTable);
|
||
return err;
|
||
}
|
||
|
||
BlockMoveData(prefixStr, (**nicknameTable).prefixStr, prefixStr[0] + 1);
|
||
|
||
*builtNicknameTable = nicknameTable;
|
||
return noErr;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* BuildNicknameTableSubset - builds a sorted nickname table of all
|
||
* nicknames in an already existing nickname table beginning with a
|
||
* specific prefix string. This routine is useful when you need to build
|
||
* a nickname table, and there is an existing table which contains
|
||
* nicknames of a prefix that's the same as the beginning of the new
|
||
* prefix; this way, instead of conducting a full search of all nicknames,
|
||
* the data to search is already significantly narrowed down. Aside from
|
||
* the fact that this routine searches for matches in a nickname table as
|
||
* opposed to the master nickname data, this routine is essentially the
|
||
* same as BuildNicknameTable.
|
||
*
|
||
* prefixStr is the prefix string to search for. srcNicknameTable is the
|
||
* nickname table to search in. Unlike BuildNicknameTable, this routine
|
||
* does not have a showExpansions parameter, and it is assumed that
|
||
* srcNicknameTable was built with showExpansions set to false. If
|
||
* allowExactMatch is set to true, then the table will include nicknames
|
||
* which match prefixStr exactly (this parameter is passed in as the
|
||
* allowExactMatch parameter for calls to FindNextNicknameTablePrefix;
|
||
* see the comments for that routine for more details). The resulting
|
||
* table is returned via *builtNicknameTable. The table is sorted using
|
||
* the default sorting method of AddNicknameTableEntry. The function
|
||
* returns noErr if it succeeds, or a non-zero error code if it fails.
|
||
************************************************************************/
|
||
OSErr BuildNicknameTableSubset(Str255 prefixStr, NicknameTableHdl srcNicknameTable, Boolean allowExactMatch, NicknameTableHdl *builtNicknameTable)
|
||
{
|
||
OSErr err;
|
||
NicknameTableHdl nicknameTable;
|
||
NicknameTablePtr srcNicknameTablePtr;
|
||
NicknameTableEntryPtr entryPtr;
|
||
long srcEntryIndex;
|
||
Boolean finished;
|
||
SInt8 origState;
|
||
|
||
|
||
*builtNicknameTable = nil;
|
||
|
||
err = NewNicknameTable(&nicknameTable);
|
||
if (err)
|
||
return err;
|
||
|
||
origState = HGetState((Handle)srcNicknameTable);
|
||
HLock((Handle)srcNicknameTable);
|
||
srcNicknameTablePtr = *srcNicknameTable;
|
||
|
||
finished = false;
|
||
srcEntryIndex = 0;
|
||
while (!finished)
|
||
{
|
||
finished = !FindNextNicknameTablePrefix(prefixStr, srcNicknameTable, srcEntryIndex, &srcEntryIndex, allowExactMatch);
|
||
if (!finished)
|
||
{
|
||
entryPtr = &srcNicknameTablePtr->table[srcEntryIndex];
|
||
err = AddNicknameTableEntry(nicknameTable, entryPtr->aliasIndex, entryPtr->nicknameIndex, entryPtr->entryStr, true);
|
||
if (err)
|
||
{
|
||
HSetState((Handle)srcNicknameTable, origState);
|
||
DisposeNicknameTable(nicknameTable);
|
||
return err;
|
||
}
|
||
srcEntryIndex++;
|
||
}
|
||
}
|
||
|
||
HSetState((Handle)srcNicknameTable, origState);
|
||
|
||
err = CompactNicknameTable(nicknameTable);
|
||
if (err)
|
||
{
|
||
DisposeNicknameTable(nicknameTable);
|
||
return err;
|
||
}
|
||
|
||
BlockMoveData(prefixStr, (**nicknameTable).prefixStr, prefixStr[0] + 1);
|
||
|
||
*builtNicknameTable = nicknameTable;
|
||
return noErr;
|
||
}
|
||
|
||
|
||
#pragma mark ========== Nickname popup routines ==========
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* TruncateNicknamePopupItems - this routine is used by
|
||
* DoNicknameStickyPopup to insure that the pixel width of the entries
|
||
* in the popup won't exceed a certain length. If the item name needs
|
||
* to be truncated, this routine will truncate just the minimum amount
|
||
* needed, and add an ellipses character (<28>) to the end of the item name.
|
||
* The routine truncates so that the popup will, at a maximum, be just
|
||
* a little wider than the window from which it is being evoked. This
|
||
* routine currently only supports Roman scripts.
|
||
*
|
||
* nicknameTable is a table of nicknames already built by
|
||
* DoNicknameStickyPopup, containing the list of items to be displayed
|
||
* in the popup. fontNum and fontSize specify the font family ID and
|
||
* font point size in which the popup will be displayed. win specifies
|
||
* the window with which the popup is associated.
|
||
************************************************************************/
|
||
void TruncateNicknamePopupItems (NicknameTableHdl nicknameTable, short fontNum, short fontSize, MyWindowPtr win)
|
||
|
||
{
|
||
WindowPtr winWP = GetMyWindowWindowPtr (win);
|
||
Rect winRect;
|
||
long maxWidth;
|
||
NicknameTablePtr tablePtr;
|
||
long numItems;
|
||
long curItem;
|
||
NicknameTableEntryPtr curItemPtr;
|
||
StringPtr curItemStrPtr;
|
||
long curItemWidth;
|
||
char truncateChar;
|
||
long truncateCharWidth;
|
||
long neededGain, currentGain;
|
||
GrafPtr origPort;
|
||
CGrafPtr tempPort;
|
||
Ptr p;
|
||
SInt8 origState;
|
||
|
||
/* FINISH *//* support scripts other than smRoman */
|
||
|
||
/* make sure we aren't desperately low on memory */
|
||
p = NewPtr(4096);
|
||
if (!p)
|
||
return;
|
||
DisposePtr(p);
|
||
|
||
GetWindowPortBounds(winWP,&winRect);
|
||
maxWidth = winRect.right - winRect.left; /* this will actually allow the popup to be slightly wider than the window */
|
||
|
||
origState = HGetState((Handle)nicknameTable);
|
||
HLock((Handle)nicknameTable);
|
||
tablePtr = *nicknameTable;
|
||
|
||
GetPort(&origPort);
|
||
MyCreateNewPort(tempPort);
|
||
TextFont(fontNum);
|
||
TextSize(fontSize);
|
||
|
||
truncateChar = ellipsesChar;
|
||
truncateCharWidth = CharWidth(truncateChar);
|
||
|
||
numItems = tablePtr->numEntries;
|
||
for (curItem = 0, curItemPtr = &tablePtr->table[0]; curItem < numItems; curItem++, curItemPtr++)
|
||
{
|
||
curItemStrPtr = curItemPtr->entryStr;
|
||
curItemWidth = StringWidth(curItemStrPtr);
|
||
if (curItemWidth <= maxWidth)
|
||
continue;
|
||
neededGain = (curItemWidth + truncateCharWidth) - maxWidth;
|
||
currentGain = 0;
|
||
while ((currentGain < neededGain) && (curItemStrPtr[0] > 1))
|
||
{
|
||
currentGain += CharWidth(curItemStrPtr[curItemStrPtr[0]]);
|
||
curItemStrPtr[0]--;
|
||
}
|
||
curItemStrPtr[++curItemStrPtr[0]] = truncateChar;
|
||
}
|
||
|
||
HSetState((Handle)nicknameTable, origState);
|
||
DisposePort(tempPort);
|
||
SetPort(origPort);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* CalcNicknamePopupLoc - calculates where the top-left corner of the
|
||
* nicknames sticky popup should be located. win is the window in which
|
||
* the user is typing; the popup will be positioned so that its top-left
|
||
* corner is just next to the top-right corner of the current text
|
||
* selection or text insertion point for the window passed in win, unless
|
||
* the current selection was created by nickname type-ahead, in which
|
||
* case the popup will be positioned with its top-left corner over the
|
||
* top-left corner of the text selection. The location is returned
|
||
* in *popupLoc, and is in global coordinates.
|
||
*
|
||
* (jp) Added a bit of code to return a 'teflon' rectangle. This is a
|
||
* rectangle we want to avoid obstructing when displaying the sticky
|
||
* popup.
|
||
************************************************************************/
|
||
void CalcNicknamePopupLoc (PETEHandle pte, Point *popupLoc, Rect *teflon)
|
||
|
||
{
|
||
ComponentResult result;
|
||
GrafPtr origPort;
|
||
Boolean hasTypeAheadSel;
|
||
PETEDocInfo info;
|
||
Point position,
|
||
startPos;
|
||
LHElement lineHeight;
|
||
|
||
if (!PeteIsValid(pte)) {
|
||
position.v = position.h = 50; /* return something that will be mostly harmless */
|
||
return;
|
||
}
|
||
|
||
hasTypeAheadSel = CurSelectionIsTypeAheadText (pte);
|
||
|
||
result = PETEGetDocInfo(PETE, pte, &info);
|
||
if (!result)
|
||
result = PETEOffsetToPosition(PETE, pte, (hasTypeAheadSel ? info.selStart : info.selStop), &position, nil);
|
||
|
||
// (jp) additions for teflon rect
|
||
if (!result)
|
||
result = PETEOffsetToPosition (PETE, pte, 0, &startPos, &lineHeight);
|
||
if (!result)
|
||
SetRect (teflon, startPos.h - 2, startPos.v, position.h, startPos.v + lineHeight.lhHeight);
|
||
|
||
if (result)
|
||
position.v = position.h = 0;
|
||
GetPort(&origPort);
|
||
SetPort (GetMyWindowCGrafPtr((*PeteExtra(pte))->win));
|
||
LocalToGlobal(&position);
|
||
LocalToGlobal((Point*)teflon);
|
||
LocalToGlobal((Point*)teflon + 1);
|
||
SetPort(origPort);
|
||
*popupLoc = position;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* DoNicknameStickyPopup - run the nicknames sticky popup menu. The menu
|
||
* is built to have a list of all nicknames which begin with the prefix
|
||
* passed in prefixStr. If you pass in true for showExpansions, the
|
||
* menu items will contain the expansions of the nicknames. win is the
|
||
* window with which the popup is associated; you should pass it the
|
||
* window that the user is currently typing in. If the user selects a
|
||
* nickname, the alias index and nickname index are returned in
|
||
* *resultAliasIndex and *resultNicknameIndex, respectively. The
|
||
* function returns true if the user selected a valid nickname, and it
|
||
* returns false if the user did not select a nickname (i.e. cancel the
|
||
* popup by clicking outside of the popup or pressing Command-Period).
|
||
************************************************************************/
|
||
Boolean DoNicknameStickyPopup(PETEHandle pte, Boolean showExpansions, Str255 prefixStr, short *resultAliasIndex, short *resultNicknameIndex)
|
||
{
|
||
OSErr err;
|
||
NicknameTableHdl nicknameTable;
|
||
NicknameTablePtr tablePtr;
|
||
NicknameTableEntryPtr entryPtr;
|
||
short fontNum, fontSize;
|
||
StickyPopupHdl popup;
|
||
Rect teflon;
|
||
Point popupLoc;
|
||
long result;
|
||
Str255 scratchStr;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return (false);
|
||
|
||
*resultAliasIndex = -1;
|
||
*resultNicknameIndex = 0;
|
||
|
||
err = noErr;
|
||
result = 0;
|
||
|
||
/* this is what prefixStr looks like if we get called from FinishAlias with no prefix */
|
||
if ((prefixStr[0] == 1) && (prefixStr[1] == ' '))
|
||
prefixStr[0] = 0;
|
||
|
||
err = BuildNicknameTable (prefixStr, GetAliasFileToScan (pte), showExpansions, true, &nicknameTable);
|
||
if (err)
|
||
goto Exit0;
|
||
|
||
fontNum = FontID;
|
||
fontSize = FontSize;
|
||
GetFontName(fontNum, &scratchStr);
|
||
if (!scratchStr[0])
|
||
fontNum = kFontIDGeneva;
|
||
if (fontSize < 4)
|
||
fontSize = 9;
|
||
|
||
TruncateNicknamePopupItems (nicknameTable, fontNum, fontSize, (*PeteExtra(pte))->win);
|
||
|
||
err = NewStickyPopup(fontNum, fontSize, &popup);
|
||
if (err)
|
||
goto Exit1;
|
||
|
||
HLock((Handle)nicknameTable);
|
||
tablePtr = *nicknameTable;
|
||
err = AddEntriesToStickyPopup(popup, &tablePtr->table[0].entryStr, sizeof(NicknameTableEntry), tablePtr->numEntries, -1);
|
||
if (err)
|
||
goto Exit2;
|
||
HUnlock((Handle)nicknameTable);
|
||
|
||
CalcNicknamePopupLoc (pte, &popupLoc, &teflon);
|
||
PushCursor(arrowCursor);
|
||
result = StickyPopupSelect(popup, popupLoc.v, popupLoc.h, 1, true, &teflon);
|
||
PopCursor();
|
||
if (result)
|
||
{
|
||
entryPtr = &((**nicknameTable).table[result - 1]);
|
||
*resultAliasIndex = entryPtr->aliasIndex;
|
||
*resultNicknameIndex = entryPtr->nicknameIndex;
|
||
}
|
||
|
||
|
||
Exit2:
|
||
DisposeStickyPopup(popup);
|
||
Exit1:
|
||
DisposeNicknameTable(nicknameTable);
|
||
Exit0:
|
||
if (err)
|
||
{
|
||
SysBeep(5);
|
||
return false;
|
||
}
|
||
return result ? true : false;
|
||
}
|
||
|
||
|
||
#pragma mark ========== Nickname expansion routines ==========
|
||
|
||
|
||
/************************************************************************
|
||
* ExpandAliases - take an address list (as from SuckAddresses), and
|
||
* expand it using the alias list
|
||
************************************************************************/
|
||
OSErr ExpandAliases(BinAddrHandle *addresses,BinAddrHandle fromH,short depth,Boolean wantComments)
|
||
{
|
||
Str255 autoQual;
|
||
EAL_VARS_DECL;
|
||
|
||
GetPref(autoQual,PREF_AUTOQUAL);
|
||
return(ExpandAliasesLow(addresses,fromH,depth,wantComments,autoQual,EAL_VARS));
|
||
}
|
||
|
||
|
||
OSErr ExpandAliasesLow(BinAddrHandle *addresses,BinAddrHandle fromH,short depth,Boolean wantComments,PStr autoQual,EAL_VARS_FORM)
|
||
{
|
||
int err=0;
|
||
BinAddrHandle toH,spewHandle;
|
||
long offset;
|
||
long theHandleSize = fromH ? GetHandleSize_(fromH):0;
|
||
Handle theExpansion = nil;
|
||
Boolean group;
|
||
short which,index;
|
||
Boolean nameEmpty = false;
|
||
Boolean isGroup = false;
|
||
short tempSize;
|
||
|
||
if (++depth > MAX_DEPTH)
|
||
{
|
||
GetRString(tempStr,ALIA_LOOP);
|
||
PCopy(junk,*fromH);
|
||
MyParamText(tempStr,junk,"","");
|
||
(void) ReallyDoAnAlert(OK_ALRT,Stop);
|
||
if (addresses != nil)
|
||
*addresses = nil;
|
||
return(1);
|
||
}
|
||
|
||
if (!(err=RegenerateAllAliases(false)))
|
||
{
|
||
toH = NuHTempBetter(0L);
|
||
if (!toH)
|
||
err=MemError();
|
||
else
|
||
{
|
||
for (offset=0;!err && offset < theHandleSize && (*fromH)[offset]; offset += (*fromH)[offset]+2)
|
||
{
|
||
/*
|
||
* get rid of the extraneous stuff
|
||
*/
|
||
if (err=SuckPtrAddresses(&spewHandle,LDRef(fromH)+offset+1,(*fromH)[offset],False,True,False,nil)) break;
|
||
UL(fromH);
|
||
if (spewHandle)
|
||
{
|
||
LDRef(spewHandle);
|
||
if (group = (*spewHandle)[**spewHandle]==':')
|
||
{
|
||
err=PtrPlusHand_((*fromH)+offset,toH,(*fromH)[offset]+2);
|
||
if (err) break;
|
||
theExpansion = nil;
|
||
}
|
||
else
|
||
{
|
||
theExpansion = FindNickExpansionFor(*spewHandle,&which,&index);
|
||
|
||
if (CompareRawToExpansion (spewHandle, theExpansion))
|
||
theExpansion = nil;
|
||
|
||
// Don't expand if group syntax, ALB 10/8/96
|
||
if (theExpansion && GetHandleSize_(theExpansion) == **spewHandle)
|
||
{
|
||
Handle tempExpansionHandle = nil,tempSpewHandle = nil;
|
||
long expansionSize = GetHandleSize_(theExpansion);
|
||
long spewSize = **spewHandle;
|
||
Byte theEndChar = '\0';
|
||
tempExpansionHandle = NuHandle(expansionSize);
|
||
tempSpewHandle = NuHandle(spewSize );
|
||
if (!tempExpansionHandle || ! tempSpewHandle)
|
||
goto breakTheLoop;
|
||
BlockMoveData(*theExpansion,*tempExpansionHandle,expansionSize);
|
||
BlockMoveData(*spewHandle + 1,*tempSpewHandle,spewSize);
|
||
if (err=PtrPlusHand(&theEndChar,tempExpansionHandle,1))
|
||
{
|
||
ZapHandle(tempExpansionHandle);
|
||
ZapHandle(tempSpewHandle);
|
||
goto breakTheLoop;
|
||
}
|
||
if (err=PtrPlusHand(&theEndChar,tempSpewHandle,1))
|
||
{
|
||
ZapHandle(tempExpansionHandle);
|
||
ZapHandle(tempSpewHandle);
|
||
goto breakTheLoop;
|
||
}
|
||
|
||
|
||
if (striscmp(*tempExpansionHandle,*tempSpewHandle) == 0)
|
||
{
|
||
err=PtrPlusHand_((*fromH)+offset,toH,(*fromH)[offset]+2);
|
||
ZapHandle(spewHandle);
|
||
UL(theExpansion);
|
||
ZapHandle(tempExpansionHandle);
|
||
ZapHandle(tempSpewHandle);
|
||
goto breakTheLoop;
|
||
}
|
||
|
||
ZapHandle(tempExpansionHandle);
|
||
ZapHandle(tempSpewHandle);
|
||
}
|
||
}
|
||
}
|
||
ZapHandle(spewHandle);
|
||
if (theExpansion)
|
||
{
|
||
BinAddrHandle lookup = nil,result = nil;
|
||
err = SuckPtrAddresses(&lookup,LDRef(theExpansion),
|
||
GetHandleSize_(theExpansion),wantComments,True,False,nil);
|
||
UL(theExpansion);
|
||
|
||
if (lookup)
|
||
{
|
||
/*
|
||
* now, expand the addresses in the expansion
|
||
*/
|
||
if (err=ExpandAliasesLow(&result,lookup,depth,wantComments,autoQual,EAL_VARS)) break;
|
||
isGroup = HandleEndsWithR(fromH,GROUP_DONT_HIDE);
|
||
ZapHandle(lookup);
|
||
|
||
/*
|
||
* add the expanded aliases to the new list
|
||
*/
|
||
if (!err && result)
|
||
{
|
||
SetHandleBig_(result,GetHandleSize_(result)-1);
|
||
HLock(result);
|
||
err = MemError();
|
||
tempSize = GetHandleSize_(result);
|
||
if (err)
|
||
break;
|
||
|
||
// Check to see if we have a group of nicknames or just a single one.
|
||
if (!isGroup && tempSize > *result[0] + 2)
|
||
isGroup = true;
|
||
|
||
GetRString(junk,NickManKeyStrn+1); // Read in tag for Name field
|
||
|
||
// Get data from "name" field.
|
||
NickGetDataFromField(junk,tempStr,which,index,false,false,&nameEmpty); // Go snag data from the notes field
|
||
|
||
// If the name field was empty, join the first and last names to form the name
|
||
if (nameEmpty) {
|
||
Str255 firstName,
|
||
lastName,
|
||
tag;
|
||
|
||
GetTaggedFieldValueStr (which, index, GetRString (tag, ABReservedTagsStrn + abTagFirst), firstName);
|
||
GetTaggedFieldValueStr (which, index, GetRString (tag, ABReservedTagsStrn + abTagLast), lastName);
|
||
|
||
JoinFirstLast (tempStr, firstName, lastName);
|
||
if (*tempStr)
|
||
nameEmpty = false;
|
||
}
|
||
|
||
// Only add the name field if we have a name and want comments.
|
||
if (*tempStr && !isGroup && !nameEmpty && wantComments) // We just have a single name
|
||
{
|
||
ShortAddr(shortAddress,LDRef(result));
|
||
UL(result);
|
||
CanonAddr(buffer,shortAddress,tempStr);
|
||
err = PtrPlusHand(buffer,toH,*buffer + 1);
|
||
if (err)
|
||
break;
|
||
tempStr[0] = 0; // Tack on a null
|
||
err = PtrPlusHand(tempStr,toH,1);
|
||
if (err)
|
||
break;
|
||
}
|
||
|
||
// Only add the name field if we're at the highest expansion level and we have a name
|
||
// and we want comments.
|
||
// I don't believe that group syntax within group syntax is valid
|
||
else if (*tempStr && isGroup && !nameEmpty && depth == 1 && wantComments) // We have a list of names
|
||
// Put into Name: addresses; format (group syntax)
|
||
{
|
||
Quote822(junk,tempStr,true);
|
||
PCopy(tempStr,junk);
|
||
|
||
PCat(tempStr,"\p:");
|
||
err = PtrPlusHand(tempStr,toH,*tempStr + 1);
|
||
if (err)
|
||
break;
|
||
*tempStr = 0;
|
||
PtrPlusHand(tempStr,toH,1);
|
||
PCopy(tempStr,"\p;");
|
||
err = HandAndHand(result,toH);
|
||
if (err)
|
||
break;
|
||
err = PtrPlusHand(tempStr,toH,*tempStr + 1);
|
||
if (err)
|
||
break;
|
||
*tempStr = 0;
|
||
err = PtrPlusHand(tempStr,toH,1); // Tack on null;
|
||
if (err)
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
err = HandAndHand(result,toH);
|
||
if (err)
|
||
break;
|
||
}
|
||
|
||
|
||
}
|
||
else if (!err)
|
||
{
|
||
err = MemError();
|
||
if (err)
|
||
break;
|
||
}
|
||
}
|
||
UL(result);
|
||
ZapHandle(result);
|
||
|
||
}
|
||
else if (!group)
|
||
{
|
||
//Str255 temp;
|
||
|
||
/*
|
||
* original was NOT an alias; just copy it
|
||
*/
|
||
/* if it is "me" then the user has not defined me as a nickname.
|
||
just put in the return address. */
|
||
LDRef(fromH);
|
||
if (EqualStrRes((*fromH)+offset,ME))
|
||
{
|
||
GetReturnAddr(tempStr, wantComments);
|
||
if (!wantComments) // strip <>'s
|
||
{
|
||
if (*tempStr && tempStr[1]=='<')
|
||
{
|
||
--*tempStr;
|
||
BMD(tempStr+2,tempStr+1,*tempStr+1);
|
||
}
|
||
if (*tempStr && tempStr[*tempStr]=='>')
|
||
{
|
||
tempStr[*tempStr] = 0;
|
||
--*tempStr;
|
||
}
|
||
}
|
||
err = PtrPlusHand_(tempStr, toH, tempStr[0]+2);
|
||
}
|
||
else if (*autoQual)
|
||
{
|
||
BinAddrHandle qualified;
|
||
if (err = SuckPtrAddresses(&qualified,(*fromH)+offset+1,(*fromH)[offset],True,False,True,nil)) break;
|
||
err = PtrPlusHand_(LDRef(qualified),toH,**qualified+2);
|
||
ZapHandle(qualified);
|
||
}
|
||
else
|
||
err=PtrPlusHand_((*fromH)+offset,toH,(*fromH)[offset]+2);
|
||
if (err)
|
||
break;
|
||
HUnlock(fromH);
|
||
}
|
||
breakTheLoop: ;
|
||
}
|
||
}
|
||
}
|
||
if (!err)
|
||
{
|
||
SetHandleBig_(toH,GetHandleSize_(toH)+1);
|
||
if (!(err=MemError())) (*toH)[GetHandleSize_(toH)-1] = 0;
|
||
}
|
||
if (err)
|
||
{
|
||
ZapHandle(toH);
|
||
if (err!=1) WarnUser(ALLO_EXPAND,err);
|
||
}
|
||
if (addresses != nil)
|
||
*addresses = toH;
|
||
return(err);
|
||
}
|
||
|
||
|
||
#pragma mark ========== Message parsing routines ==========
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* add comments, make static */
|
||
/* WARNING *//* may move or purge memory *//* FINISH *//* really? */
|
||
/* FINISH *//* all callers need to check to see if we hit the end of the text and/or field */
|
||
long FindNextRecipientStart(Handle textHdl, long startOffset)
|
||
{
|
||
long textLen;
|
||
Str255 delimiterList;
|
||
UPtr curDelim;
|
||
long delimCount;
|
||
Boolean hitDelimiter;
|
||
UPtr curChar;
|
||
long curOffset;
|
||
|
||
|
||
BlockMoveData("\p,:;", delimiterList, 4); /* FINISH *//* use resource */
|
||
delimiterList[++delimiterList[0]] = returnChar;
|
||
|
||
textLen = GetHandleSize(textHdl); /* FINISH *//* just make this the end of that recipient field, or use PETEGetTextLen */
|
||
|
||
/* Go forward until we hit a non-space character. If that character is a recipient delimiter, then go on to the next
|
||
non-space character following that delimiter (which may be another delimiter). If at any point we hit the end
|
||
of the text block, stop and return the end offset. */
|
||
curOffset = startOffset;
|
||
curChar = *textHdl + curOffset;
|
||
|
||
/* FINISH *//* in all spots, change this to check curOffset first, so that we don't read bytes past the end of a block */
|
||
while ((*curChar == ' ') && (curOffset < textLen))
|
||
{
|
||
curChar++;
|
||
curOffset++;
|
||
}
|
||
/* FINISH *//* make sure we do this check anywhere in the code where this comes up */
|
||
if (curOffset >= textLen)
|
||
return curOffset;
|
||
|
||
hitDelimiter = false;
|
||
delimCount = delimiterList[0];
|
||
curDelim = delimiterList + 1;
|
||
while (!hitDelimiter && delimCount--)
|
||
{
|
||
if (*curChar == *curDelim)
|
||
hitDelimiter = true;
|
||
else
|
||
curDelim++;
|
||
}
|
||
if (!hitDelimiter)
|
||
return curOffset;
|
||
|
||
curChar++;
|
||
curOffset++;
|
||
|
||
while ((*curChar == ' ') && (curOffset < textLen))
|
||
{
|
||
curChar++;
|
||
curOffset++;
|
||
}
|
||
|
||
return curOffset;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* add comments, make static */
|
||
/* WARNING *//* may move or purge memory *//* FINISH *//* really? */
|
||
/* FINISH *//* all callers need to check to see if we hit the end of the text and/or field */
|
||
/* FINISH *//* we must return startOffset if errors occur */
|
||
long FindRecipientStartInText(Handle textHdl, long startOffset, Boolean skipLeadingSpaces)
|
||
{
|
||
UPtr textPtr;
|
||
Str255 delimiterList;
|
||
UPtr curDelim;
|
||
long delimCount;
|
||
Boolean hitDelimiter;
|
||
UPtr curChar;
|
||
long curOffset;
|
||
|
||
|
||
BlockMoveData("\p,:;", delimiterList, 4); /* FINISH *//* use resource */
|
||
delimiterList[++delimiterList[0]] = returnChar;
|
||
|
||
textPtr = *textHdl;
|
||
|
||
/* Back up until we hit a character that's a recipient delimiter, or we hit the first character in the text block.
|
||
If we've hit a colon, it may be the colon following the label of a recipient field (i.e. the colon in "To:"), or it
|
||
may be the colon in a group syntax entry (i.e. "GroupName:addr1,addr2,addr3;"). */
|
||
curOffset = startOffset - 1;
|
||
curChar = textPtr + curOffset;
|
||
hitDelimiter = false;
|
||
while (!hitDelimiter && (curOffset >= 0))
|
||
{
|
||
delimCount = delimiterList[0];
|
||
curDelim = delimiterList + 1;
|
||
while (!hitDelimiter && delimCount--)
|
||
{
|
||
if (*curChar == *curDelim) /* FINISH *//* spots like this do continous dereferencing of *curChar */
|
||
hitDelimiter = true;
|
||
else
|
||
curDelim++;
|
||
}
|
||
if (hitDelimiter)
|
||
continue;
|
||
|
||
curChar--;
|
||
curOffset--;
|
||
}
|
||
curChar++;
|
||
curOffset++;
|
||
|
||
if (skipLeadingSpaces)
|
||
while ((*curChar == ' ') && (curOffset < (startOffset - 1)))
|
||
{
|
||
curChar++;
|
||
curOffset++;
|
||
}
|
||
|
||
return curOffset;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* add comments, make static */
|
||
/* WARNING *//* may move or purge memory *//* FINISH *//* really? */
|
||
/* FINISH *//* all callers need to check to see if we hit the end of the text and/or field */
|
||
/* FINISH *//* we must return startOffset if errors occur */
|
||
long FindRecipientEndInText(Handle textHdl, long startOffset, Boolean skipTrailingSpaces)
|
||
{
|
||
long textLen;
|
||
UPtr textPtr;
|
||
Str255 delimiterList;
|
||
Str15 prefix;
|
||
UPtr curDelim;
|
||
long delimCount;
|
||
Boolean hitDelimiter;
|
||
Boolean isFCC;
|
||
UPtr curChar;
|
||
long curOffset;
|
||
|
||
GetRString(prefix,FCC_PREFIX);
|
||
|
||
BlockMoveData("\p,:;", delimiterList, 4); /* FINISH *//* use resource */
|
||
delimiterList[++delimiterList[0]] = returnChar;
|
||
|
||
textLen = GetHandleSize(textHdl); /* FINISH *//* just make this the end of that recipient field, or use PETEGetTextLen */
|
||
textPtr = *textHdl;
|
||
|
||
/* Go forward until we hit a character that's a recipient delimiter, or we hit the last character in the text block. */
|
||
curOffset = startOffset;
|
||
curChar = textPtr + curOffset;
|
||
hitDelimiter = false;
|
||
isFCC = false;
|
||
while (!hitDelimiter && (curOffset < textLen))
|
||
{
|
||
delimCount = delimiterList[0];
|
||
curDelim = delimiterList + 1;
|
||
while (!hitDelimiter && delimCount--)
|
||
{
|
||
// jp - Check to see if we're processing an Fcc -- denoted by "<22>: (actually, using the FCC_PREFIX)
|
||
if (!isFCC && *curChar == 0x22)
|
||
if (curOffset + prefix[0] + 1 < textLen)
|
||
if (!memcmp (curChar + 1, &prefix[1], prefix[0]) && *(curChar + prefix[0] + 1) == ':')
|
||
isFCC = true;
|
||
|
||
// jp - Ignore colons as long as we are processing an Fcc
|
||
if (*curChar == *curDelim) {
|
||
if (!isFCC || (isFCC && *curChar != ':'))
|
||
hitDelimiter = true;
|
||
}
|
||
else
|
||
curDelim++;
|
||
}
|
||
if (hitDelimiter)
|
||
continue;
|
||
|
||
curChar++;
|
||
curOffset++;
|
||
}
|
||
curChar--;
|
||
curOffset--;
|
||
|
||
if (skipTrailingSpaces)
|
||
while ((*curChar == ' ') && (curOffset > startOffset))
|
||
{
|
||
curChar--;
|
||
curOffset--;
|
||
}
|
||
|
||
curOffset++;
|
||
|
||
// For safety...
|
||
if (curOffset > textLen)
|
||
curOffset = textLen;
|
||
|
||
return curOffset;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* add comments, make static */
|
||
/* WARNING *//* may move or purge memory */
|
||
/* FINISH *//* all callers need to check to see if we hit the end of the text and/or field */
|
||
long FindNicknameStartInText(Handle textHdl, long startOffset, Boolean recipientField)
|
||
{
|
||
UPtr textPtr;
|
||
Str255 delimiterList;
|
||
UPtr curDelim;
|
||
long delimCount;
|
||
Boolean hitDelimiter;
|
||
UPtr curChar;
|
||
long curOffset;
|
||
Size textHdlSize;
|
||
|
||
if (!textHdl)
|
||
return (0);
|
||
|
||
textHdlSize = GetHandleSize (textHdl);
|
||
if (!textHdlSize)
|
||
return (0);
|
||
|
||
GetRString(delimiterList, ALIAS_VERBOTEN); /* characters forbidden in a nickname */
|
||
/* WARNING *//* assumes delimiterList[0] <= 253 */
|
||
delimiterList[++delimiterList[0]] = returnChar;
|
||
if (!recipientField)
|
||
delimiterList[++delimiterList[0]] = ' '; /* don't allow spaces in nicknames if we're not in a recipient field */
|
||
|
||
textPtr = *textHdl;
|
||
|
||
/* Back up until we hit a character that's not allowable in a nickname, or we hit the first character in the text block.
|
||
If we've hit a colon, it may be the colon following the label of a recipient field (i.e. the colon in "To:"), or it
|
||
may be the colon in a group syntax entry (i.e. "GroupName:addr1,addr2,addr3;"). */
|
||
curOffset = startOffset - 1;
|
||
curChar = textPtr + curOffset;
|
||
hitDelimiter = false;
|
||
while (!hitDelimiter && (curOffset >= 0))
|
||
{
|
||
delimCount = delimiterList[0];
|
||
curDelim = delimiterList + 1;
|
||
while (!hitDelimiter && delimCount--)
|
||
{
|
||
if (*curChar == *curDelim)
|
||
hitDelimiter = true;
|
||
else
|
||
curDelim++;
|
||
}
|
||
if (hitDelimiter)
|
||
continue;
|
||
|
||
curChar--;
|
||
curOffset--;
|
||
}
|
||
curChar++;
|
||
curOffset++;
|
||
|
||
if (recipientField)
|
||
while ((*curChar == ' ') && (curOffset < (startOffset - 1) && curOffset < textHdlSize))
|
||
{
|
||
curChar++;
|
||
curOffset++;
|
||
}
|
||
|
||
return curOffset;
|
||
}
|
||
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* finish comments */
|
||
/************************************************************************
|
||
* GetNicknamePrefixFromField - given the window passed in win, this
|
||
* function gets the characters typed so far in the current PETE field,
|
||
* and returns them in prefixStr. This returned string is appropriate
|
||
* for doing nickname completion.
|
||
************************************************************************/
|
||
OSErr GetNicknamePrefixFromField (PETEHandle pte, Str255 prefixStr, Boolean ignoreSelection, Boolean useNicknameTypeAhead)
|
||
{
|
||
OSErr err;
|
||
Handle textHdl;
|
||
long selStart, selEnd;
|
||
long startOffset;
|
||
long prefixLen;
|
||
Boolean recipientField;
|
||
|
||
/* FINISH *//* need special handling if selection was created by type-ahead */
|
||
/* FINISH *//* if ignoreSelection is false, then it should return prefix + selection, and we can remove the param useNicknameTypeAhead */
|
||
/* FINISH *//* is anyone even calling this with useNicknameTypeAhead set to true? How about ignoreSelection set to false? */
|
||
|
||
prefixStr[0] = 0;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return paramErr;
|
||
|
||
if (useNicknameTypeAhead && HasNickCompletion (pte) && CurSelectionIsTypeAheadText (pte))
|
||
{
|
||
BlockMoveData (gTypeAheadPrefix, prefixStr, gTypeAheadPrefix[0] + 1);
|
||
BlockMoveData (gTypeAheadSuffix, prefixStr + prefixStr[0] + 1, gTypeAheadSuffix[0]);
|
||
prefixStr[0] += gTypeAheadSuffix[0];
|
||
return noErr;
|
||
}
|
||
|
||
err = PeteGetTextAndSelection(pte, &textHdl, &selStart, &selEnd);
|
||
if (err)
|
||
return err;
|
||
if (ignoreSelection)
|
||
selEnd = selStart;
|
||
|
||
if (selStart != selEnd) /* if a range of text is selected */
|
||
{
|
||
prefixLen = selEnd - selStart;
|
||
if (prefixLen > 255)
|
||
prefixLen = 255;
|
||
BlockMoveData(*textHdl + selStart, prefixStr + 1, prefixLen);
|
||
prefixStr[0] = prefixLen;
|
||
return noErr;
|
||
}
|
||
|
||
recipientField = HasNickSpaces (pte);
|
||
if (IsCompWindow(GetMyWindowWindowPtr((*PeteExtra(pte))->win)) && IsAddressHead(CompHeadCurrent(pte)))
|
||
recipientField = true;
|
||
startOffset = FindNicknameStartInText(textHdl, selStart, recipientField);
|
||
|
||
prefixLen = selStart - startOffset;
|
||
if (!prefixLen)
|
||
return noErr;
|
||
if (prefixLen > 255)
|
||
prefixLen = 255;
|
||
BlockMoveData(*textHdl + startOffset, prefixStr + 1, prefixLen);
|
||
prefixStr[0] = prefixLen;
|
||
|
||
return noErr;
|
||
}
|
||
|
||
|
||
#pragma mark ========== Finish nickname routines ==========
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* FinishAliasUsingTypeAhead - complete a partially typed nickname; for
|
||
* use in cases where nickname type-ahead has already done some work.
|
||
*
|
||
* Pass the window which contains the text to be finished in win; this
|
||
* must be a composition window, and the current selection must be in
|
||
* one of the recipient fields.
|
||
*
|
||
* If you pass true for wantExpansion, then rather than putting in the
|
||
* nickname, this routine will enter the full expansion of the nickname.
|
||
* If you pass true for allowPopup and there are two or more nicknames
|
||
* ending with the current nickname prefix, then the nickname sticky
|
||
* popup menu will come up and allow the user to specify which nickname
|
||
* they want to use. If you pass true for insertComma, then a comma
|
||
* will be inserted following the inserted text if it is appropriate
|
||
* to do so.
|
||
*
|
||
* This function is specifically for completing a nickname when a
|
||
* nickname type-ahead selection exists in the specified window
|
||
* (i.e. CurSelectionIsTypeAheadText(win) returns true). For general
|
||
* nickname completion, you should call FinishAlias.
|
||
*
|
||
* The function returns noErr if it succeeds, or a non-zero error code
|
||
* if it fails.
|
||
************************************************************************/
|
||
|
||
OSErr FinishAliasUsingTypeAhead (PETEHandle pte, Boolean wantExpansion, Boolean allowPopup, Boolean insertComma)
|
||
|
||
{
|
||
MyWindowPtr win;
|
||
WindowPtr winWP;
|
||
Str255 nicknameStr;
|
||
UHandle recipientText;
|
||
Handle text;
|
||
Ptr insertTextPtr;
|
||
OSErr theError;
|
||
long insertTextLen,
|
||
replaceStart,
|
||
replaceEnd;
|
||
short aliasIndex,
|
||
nicknameIndex;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return (paramErr);
|
||
|
||
if (!((*PeteExtra(pte))->nick.fieldCheck ? (*(*PeteExtra(pte))->nick.fieldCheck) (pte) : HasNickCompletion (pte)))
|
||
return (false);
|
||
|
||
if (!CurSelectionIsTypeAheadText (pte))
|
||
return (paramErr);
|
||
|
||
aliasIndex = GetTypeAheadAliasIndex (pte);
|
||
nicknameIndex = GetTypeAheadNicknameIndex (pte);
|
||
|
||
if (allowPopup && NicknamePrefixOccursNTimes (gTypeAheadPrefix, 2, GetAliasFileToScan (pte)))
|
||
if (!DoNicknameStickyPopup (pte, wantExpansion, gTypeAheadPrefix, &aliasIndex, &nicknameIndex))
|
||
return (noErr);
|
||
|
||
if (theError = RegenerateAllAliases (false)) /* FINISH *//* shouldn't we be passing in true? */
|
||
return (theError);
|
||
|
||
// This code used to be executed well down in the code. Trouble is, gTypeAheadPrefix is cleared
|
||
// whenever a nickname file is saved... for instance, when nicknames are cached. As a result, the
|
||
// insertion target for nicknames that were cached and tthe completed was incorrect.
|
||
replaceStart = GetTypeAheadSelStart (pte) - gTypeAheadPrefix[0];
|
||
replaceEnd = GetTypeAheadSelEnd (pte);
|
||
|
||
recipientText = nil;
|
||
if (wantExpansion) {
|
||
if (theError = GetNicknameRecipientText (pte, aliasIndex, nicknameIndex, &recipientText))
|
||
return (theError);
|
||
|
||
insertTextLen = GetHandleSize (recipientText);
|
||
|
||
// If there is no expansion present, we're done... Unless!! We're also watching for either completion or hiliting...
|
||
if (!insertTextLen) {
|
||
if ((gNicknameTypeAheadEnabled && HasNickCompletion (pte)) || (gNicknameHilitingEnabled && HasNickHiliting (pte)))
|
||
wantExpansion = false;
|
||
else
|
||
{ ZapHandle(recipientText); return (noErr); }
|
||
}
|
||
}
|
||
// (jp) Note that this is the same case as above, but required since we might
|
||
// turn off 'wantExpansion' if there was no expansion
|
||
if (wantExpansion) {
|
||
if (insertComma) {
|
||
SetHandleSize (recipientText, ++insertTextLen);
|
||
if (theError = MemError ()) {
|
||
DisposeHandle ((Handle) recipientText);
|
||
return (theError);
|
||
}
|
||
*(*recipientText + insertTextLen - 1) = ',';
|
||
}
|
||
|
||
MoveHHi ((Handle) recipientText);
|
||
HLock ((Handle) recipientText);
|
||
insertTextPtr = *recipientText;
|
||
}
|
||
else {
|
||
GetNicknameNamePStr (aliasIndex, nicknameIndex, nicknameStr);
|
||
insertTextPtr = nicknameStr + 1;
|
||
insertTextLen = nicknameStr[0];
|
||
|
||
if (insertComma) {
|
||
if (insertTextLen < 255) {
|
||
nicknameStr[++nicknameStr[0]] = ',';
|
||
++insertTextLen;
|
||
}
|
||
else {
|
||
ZapHandle(recipientText);
|
||
recipientText = NewHandle (++insertTextLen);
|
||
if (!recipientText)
|
||
return (MemError ());
|
||
BlockMoveData (insertTextPtr, *recipientText, insertTextLen - 1);
|
||
*(*recipientText + insertTextLen - 1) = ',';
|
||
MoveHHi ((Handle) recipientText);
|
||
HLock ((Handle) recipientText);
|
||
insertTextPtr = *recipientText;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* need to grab these values before calling ResetNicknameTypeAhead */
|
||
//
|
||
// We are now grabbing these values ealier in the code... By this point
|
||
// the Type Ahead Prefix has been cleared by the nickname caching code.
|
||
//
|
||
// replaceStart = GetTypeAheadSelStart (pte) - gTypeAheadPrefix[0];
|
||
// replaceEnd = GetTypeAheadSelEnd (pte);
|
||
|
||
theError = PETEClearUndo (PETE, pte);
|
||
|
||
// (jp) Turn off the highlighting before substituting text
|
||
// ...but only if the text being finished is an expansion
|
||
if (!theError && wantExpansion && insertTextLen)
|
||
theError = SetNicknameHiliting (pte, replaceStart, replaceEnd, false);
|
||
|
||
win = (*PeteExtra(pte))->win;
|
||
winWP = GetMyWindowWindowPtr (win);
|
||
if (!theError && gNicknameTypeAheadEnabled && HasNickCompletion (pte))
|
||
PeteSelect (win, pte, replaceStart, replaceEnd);
|
||
|
||
if (!theError)
|
||
(void) PeteInsertPtr (pte, kPETECurrentSelection, insertTextPtr, insertTextLen);
|
||
|
||
ResetNicknameTypeAhead (pte);
|
||
NicknameWatcherModifiedField (pte);
|
||
|
||
if (wantExpansion && gNicknameCacheEnabled)
|
||
if ((GetWindowKind(winWP) == COMP_WIN || GetWindowKind(winWP) == MESS_WIN))
|
||
CompGatherRecipientAddresses (Win2MessH (win), true);
|
||
else
|
||
if (!PeteGetTextAndSelection (pte, &text, nil, nil))
|
||
SetNickCacheAddresses (pte, text);
|
||
|
||
ZapHandle (recipientText);
|
||
|
||
return (theError);
|
||
}
|
||
|
||
/* MJN *//* FINISH *//* should indices be short or long? */
|
||
|
||
/************************************************************************
|
||
* FinishAlias - finish a partially completed alias
|
||
************************************************************************/
|
||
void FinishAlias (PETEHandle pte, Boolean wantExpansion, Boolean allowPopup, Boolean dontUndo)
|
||
{
|
||
Str63 lcd,
|
||
tempStr,
|
||
word;
|
||
Str31 nameStr;
|
||
Handle text;
|
||
UPtr spot,
|
||
begin,
|
||
end;
|
||
OSErr theError;
|
||
long offset,
|
||
index,
|
||
start,
|
||
stop,
|
||
pStart;
|
||
short found,
|
||
foundIndex,
|
||
whichAlias,
|
||
foundAlias;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
found = 0;
|
||
foundIndex = 0;
|
||
foundAlias = -1;
|
||
|
||
/* MJN *//* we need to do special-handling if the selection was created by nickname type-ahead */
|
||
if (CurSelectionIsTypeAheadText (pte)) {
|
||
(void) FinishAliasUsingTypeAhead (pte, wantExpansion, allowPopup, false);
|
||
return;
|
||
}
|
||
|
||
if (PeteGetTextAndSelection (pte, &text, &start, &stop))
|
||
return;
|
||
|
||
begin = *text;
|
||
// Is there a selection?
|
||
if (start != stop) {
|
||
spot = begin + start;
|
||
end = begin + stop - 1;
|
||
}
|
||
else
|
||
// Is it an address field?
|
||
if ((*PeteExtra(pte))->nick.fieldCheck ? (*(*PeteExtra(pte))->nick.fieldCheck) (pte) : HasNickCompletion (pte)) {
|
||
spot = end = begin + start - 1;
|
||
while (spot >= begin && *spot != ',' && *spot != '(' && *spot != ':')
|
||
spot--;
|
||
spot++;
|
||
while (*spot == ' ' && spot < end)
|
||
spot++;
|
||
if (spot > end)
|
||
return;
|
||
}
|
||
else {
|
||
// plain old
|
||
spot = end = begin + start - 1;
|
||
while (spot >= begin && *spot > ' ' && *spot != ',' && *spot != '(')
|
||
spot--;
|
||
spot++;
|
||
if (spot > end)
|
||
return;
|
||
}
|
||
*word = MIN (sizeof (word) - 1, end - spot + 1);
|
||
BlockMoveData (spot, word + 1, *word);
|
||
TrimAllWhite (word);
|
||
if (!*word)
|
||
return;
|
||
|
||
if (RegenerateAllAliases (false))
|
||
return;
|
||
|
||
found = 0;
|
||
for (whichAlias = 0; whichAlias < NAliases; whichAlias++) {
|
||
offset = 0;
|
||
while ((index = FindAPrefix (whichAlias, word, offset)) >= 0) {
|
||
if (!found) {
|
||
foundAlias = whichAlias;
|
||
GetNicknameNamePStr (whichAlias, index, lcd);
|
||
if (theError = MemError ()) {
|
||
WarnUser (MEM_ERR, theError);
|
||
return;
|
||
}
|
||
foundIndex = index;
|
||
}
|
||
#ifdef TWO
|
||
else {
|
||
GetNicknameNamePStr (whichAlias, index, tempStr);
|
||
LCD (lcd, tempStr);
|
||
}
|
||
/* MJN *//* FINISH *//* I think this next bit was supposed to follow the call to GetNicknameNamePStr */
|
||
if (theError = MemError()) {
|
||
WarnUser (MEM_ERR, theError);
|
||
return;
|
||
}
|
||
#endif
|
||
/* MJN *//* FINISH *//* what should this do if we're bringing up the nickname sticky popup? */
|
||
if (found == 1 && !allowPopup) // More than one matched.
|
||
SysBeep (20);
|
||
++found;
|
||
offset = index + 1;
|
||
if (EqualString (lcd, word, False, True))
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
out:
|
||
|
||
/* WARNING */ /* by this time, text (a Handle) may have been relocated, so the actual values
|
||
of begin, end, and spot may not point to the proper address; however, it is
|
||
safe to use them for the purposes of calculating offsets, as is done below. */
|
||
|
||
// Only one match
|
||
if (found == 1) {
|
||
if (!dontUndo)
|
||
PetePrepareUndo (pte, peUndoPaste, spot - begin, end - begin + 1, &pStart, nil);
|
||
PeteSelect (nil, pte, spot - begin, end - begin + 1);
|
||
GetNicknameNamePStr (foundAlias, foundIndex, nameStr);
|
||
InsertAlias (pte, nil, nameStr, wantExpansion, spot - begin, dontUndo);
|
||
}
|
||
#ifdef TWO
|
||
/* MJN *//* run nickname sticky popup menu */
|
||
else
|
||
if (found > 1 && allowPopup) {
|
||
if (DoNicknameStickyPopup (pte, wantExpansion, word, &foundAlias, &foundIndex)) {
|
||
if (!dontUndo)
|
||
PetePrepareUndo (pte, peUndoPaste, spot - begin, end - begin + 1, &pStart, nil);
|
||
PeteSelect (nil, pte, spot - begin, end - begin + 1);
|
||
GetNicknameNamePStr (foundAlias, foundIndex, nameStr);
|
||
InsertAlias (pte, nil, nameStr, wantExpansion, spot - begin, dontUndo);
|
||
}
|
||
}
|
||
else
|
||
if (found > 1 && *lcd) {
|
||
if (!dontUndo)
|
||
PetePrepareUndo (pte, peUndoPaste, spot - begin, end - begin + 1, &pStart, nil);
|
||
PeteSelect (nil, pte, spot - begin, end - begin + 1);
|
||
PeteInsertPtr (pte, -1, lcd + 1, *lcd);
|
||
if (!dontUndo)
|
||
PeteFinishUndo (pte, peUndoPaste, pStart, -1);
|
||
|
||
NicknameWatcherModifiedField (pte); /* MJN */
|
||
}
|
||
#endif
|
||
}
|
||
|
||
/************************************************************************
|
||
* FindAPrefix - find a prefix in a list of aliases
|
||
************************************************************************/
|
||
long FindAPrefix(short which,PStr word,long startIndex)
|
||
{
|
||
Str63 local,tempStr; // ALB 9/10/96, can't use Str31 because LCD adds a null to the end
|
||
long stop;
|
||
long i;
|
||
|
||
if (!(*Aliases)[which].theData)
|
||
return (-1);
|
||
|
||
stop = (GetHandleSize_((*Aliases)[which].theData)/sizeof(NickStruct));
|
||
if (startIndex >= stop)
|
||
return (-1);
|
||
|
||
PSCopy(local,word);
|
||
if (local[*local]==';' && local[*local-1]==':') *local -= 2;
|
||
|
||
|
||
for (i=startIndex;i<stop;i++)
|
||
{
|
||
if (!(*((*Aliases)[which].theData))[i].deleted)
|
||
{
|
||
GetNicknameNamePStr(which,i,tempStr);
|
||
if (*tempStr && EqualString(LCD(tempStr,local),local,false,false) && !(*((*Aliases)[which].theData))[i].deleted)
|
||
return (i);
|
||
}
|
||
}
|
||
|
||
return(-1);
|
||
|
||
}
|
||
|
||
/************************************************************************
|
||
* InsertAlias - insert an alias in a window
|
||
************************************************************************/
|
||
void InsertAlias (PETEHandle pte, HSPtr hs, PStr nameStr, Boolean wantExpansion, long pStart, Boolean dontUndo)
|
||
|
||
{
|
||
PETEStyleEntry pse;
|
||
MyWindowPtr win;
|
||
WindowPtr winWP;
|
||
PersHandle pers;
|
||
BinAddrHandle wordH,
|
||
list;
|
||
Handle text;
|
||
long spot,
|
||
tempSize,
|
||
addressSize,
|
||
selStart,
|
||
selEnd;
|
||
short len;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
win = (*PeteExtra(pte))->win;
|
||
winWP = GetMyWindowWindowPtr (win);
|
||
spot = hs ? hs->stop : -1;
|
||
|
||
// If we don't want the nickname expansion, just copy in the alias name
|
||
if (!wantExpansion) {
|
||
len = *nameStr;
|
||
|
||
if (HasNickHiliting (pte) && gNicknameHilitingEnabled)
|
||
if (!PeteGetTextAndSelection (pte, nil, &selStart, &selEnd))
|
||
(void) SetNicknameHiliting (pte, selStart, selEnd, true);
|
||
|
||
// (jp) 1/24/00 This is a temporary fix for the problem that "turns off" spell checking
|
||
// following expansion of nicknames that contain a space. The spell scanner
|
||
// keys on the space as the end of spell checking -- though the syle is not
|
||
// yet complete. For now I'm commenting out the call to PeteInsertPtr and
|
||
// doing it normally while turning off the spell bit.
|
||
// (void) PeteInsertPtr (pte, spot, nameStr + 1, len);
|
||
PeteStyleAt (pte, spot, &pse);
|
||
pse.psStyle.textStyle.tsLock = 0;
|
||
pse.psStartChar = 0;
|
||
pse.psStyle.textStyle.tsLabel &= ~pSpellLabel;
|
||
**Pslh = pse;
|
||
(void) PETEInsertTextPtr(PETE,pte,spot,nameStr + 1,len,Pslh);
|
||
// End of hack.
|
||
|
||
if (!dontUndo)
|
||
(void) PeteFinishUndo (pte, peUndoPaste, pStart, spot == -1 ? kPETECurrentSelection : spot + len);
|
||
|
||
if (hs)
|
||
hs->stop += len;
|
||
}
|
||
else {
|
||
addressSize = *nameStr + 1;
|
||
wordH = NuHandle (addressSize + 5);
|
||
if (!wordH) {
|
||
WarnUser (MEM_ERR, MemError ());
|
||
return;
|
||
}
|
||
else {
|
||
BlockMoveData (nameStr, *wordH, nameStr[0] + 1);
|
||
(*wordH)[**wordH + 1] = (*wordH)[**wordH + 2] = 0;
|
||
|
||
pers = PersList;
|
||
if ((GetWindowKind(winWP)==COMP_WIN ||
|
||
GetWindowKind(winWP)==MESS_WIN))
|
||
pers = PERS_FORCE (MESS_TO_PERS (Win2MessH (win)));
|
||
PushPers (pers);
|
||
|
||
list = nil;
|
||
PeteExpandAliases (pte, &list, wordH, 0, true);
|
||
PopPers ();
|
||
ZapHandle(wordH);
|
||
|
||
// (jp) Bad ju-ju ahead... if the list is zero length, we correctly do not replace the existing typing
|
||
// with nothing (what would be the point?), but we incorrectly lose the caret.
|
||
if (list) {
|
||
tempSize = GetHandleSize_(list);
|
||
if (tempSize == 1 && strlen (LDRef (list)) == 0) {
|
||
ZapHandle(list);
|
||
// ... and here's how we're fixing the ju-ju. Basically, set the selection to the end and finish the undo.
|
||
(void) PeteGetTextAndSelection (pte, nil, nil, &selEnd);
|
||
PeteSelect (nil, pte, selEnd, selEnd);
|
||
if (!dontUndo)
|
||
PeteFinishUndo (pte, peUndoPaste, pStart, spot == -1 ? kPETECurrentSelection : spot);
|
||
}
|
||
else
|
||
UL(list);
|
||
}
|
||
|
||
if (list) {
|
||
CommaList (list);
|
||
|
||
if (wantExpansion)
|
||
if (!PeteGetTextAndSelection (pte, nil, &selStart, &selEnd))
|
||
(void) SetNicknameHiliting (pte, selStart, selEnd, false);
|
||
|
||
len = GetHandleSize (list);
|
||
PeteInsert (pte, spot, list);
|
||
if (!dontUndo)
|
||
PeteFinishUndo (pte, peUndoPaste, pStart, spot == -1 ? kPETECurrentSelection : spot + len);
|
||
if (hs)
|
||
hs->stop += len;
|
||
ZapHandle (list);
|
||
}
|
||
if (gNicknameCacheEnabled)
|
||
if ((GetWindowKind(winWP) == COMP_WIN || GetWindowKind(winWP) == MESS_WIN))
|
||
CompGatherRecipientAddresses (Win2MessH (win), true);
|
||
else
|
||
if (!PeteGetTextAndSelection (pte, &text, nil, nil))
|
||
SetNickCacheAddresses (pte, text);
|
||
}
|
||
}
|
||
// (jp) We only worry about this if we are given a HeaderSpec... And we're only given a
|
||
// HeaderSpec when we're inserting an address into a composition window. So...
|
||
// we don't worry about other types of windows here. (Still, let's check for safety)
|
||
if (hs && GetWindowKind(winWP) == COMP_WIN)
|
||
if (CompHeadCurrent (pte) == hs->index)
|
||
PeteSelect (win, pte, hs->stop, hs->stop);
|
||
NicknameWatcherModifiedField (pte); /* MJN */
|
||
}
|
||
|
||
/**********************************************************************
|
||
* IsNickname - is something a nickname?
|
||
**********************************************************************/
|
||
Boolean IsNickname(PStr name,short which)
|
||
{
|
||
Boolean result = false;
|
||
|
||
if (!(*Aliases)[which].theData) return false;
|
||
|
||
LDRef((Handle)(*Aliases)[which].theData);
|
||
if (NickMatchFound((*Aliases)[which].theData,NickHash(name),name,which)>=0)
|
||
result = true;
|
||
else
|
||
result = false;
|
||
|
||
UL((Handle)(*Aliases)[which].theData);
|
||
|
||
return result;
|
||
}
|
||
|
||
/************************************************************************
|
||
* FindNickExpansionLo - find the first expansion that matches with plugin specification
|
||
************************************************************************/
|
||
short FindNickExpansionForLo(UPtr name,long hash,short *theWhich,Boolean pluginNicks)
|
||
{
|
||
short index;
|
||
short which;
|
||
|
||
for (which=0;which<NAliases;which++)
|
||
{
|
||
if (pluginNicks == IsPluginAddressBook (which))
|
||
{
|
||
index = NickMatchFound(((*Aliases)[which].theData),hash,name,which);
|
||
if (index >= 0)
|
||
{
|
||
*theWhich = which;
|
||
return index;
|
||
}
|
||
}
|
||
}
|
||
return -1; // Not found
|
||
}
|
||
|
||
/************************************************************************
|
||
* FindNickExpansionFor - find the first expansion that matches
|
||
************************************************************************/
|
||
Handle FindNickExpansionFor(UPtr name,short *theWhich,short *theIndex)
|
||
{
|
||
short index = -1;
|
||
long hash;
|
||
|
||
*theWhich = -1;
|
||
*theIndex = -1;
|
||
|
||
if (!name || !*name || *name > sizeof(Str31) - 1)
|
||
return (nil);
|
||
|
||
hash = NickHash(name);
|
||
index = FindNickExpansionForLo(name,hash,theWhich,true); // Search plug-in nicknames first
|
||
if (index < 0)
|
||
// Not found, search standard nicknames
|
||
index = FindNickExpansionForLo(name,hash,theWhich,false);
|
||
|
||
*theIndex = index;
|
||
|
||
if ( index>= 0) // If it is a nickname, then return the address information
|
||
return (GetNicknameData(*theWhich,index,true,true));
|
||
else
|
||
return (nil);
|
||
|
||
}
|
||
|
||
|
||
#pragma mark ========== Nickname type-ahead routines ==========
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* ResetNicknameTypeAhead - reset all of the global variables used to
|
||
* handle nickname type-ahead
|
||
*
|
||
* WARNING: this routine can get called when gNicknameTypeAheadEnabled is
|
||
* set to false
|
||
************************************************************************/
|
||
void ResetNicknameTypeAhead (PETEHandle pte)
|
||
|
||
{
|
||
if (gTypeAheadNicknameTable) {
|
||
DisposeNicknameTable (gTypeAheadNicknameTable); /* FINISH *//* make sure this won't hurt any caching that we do */
|
||
gTypeAheadNicknameTable = nil;
|
||
}
|
||
gTypeAheadNicknameTableIndex = -1;
|
||
|
||
gTypeAheadPrefix[0] = 0;
|
||
gTypeAheadSuffix[0] = 0;
|
||
|
||
gTypeAheadIdleDelay = gNicknameWatcherWaitKeyThresh ? LMGetKeyThresh() : 0;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
if (gNicknameHilitingEnabled && CurSelectionIsTypeAheadText (pte) && HasNickHiliting (pte))
|
||
HilitingRangeCheckInclude (pte, GetTypeAheadSelStart (pte) - gTypeAheadPrefix[0], GetTypeAheadSelEnd (pte));
|
||
|
||
/* FINISH *//* these two globals really only need to get reset every CompOpen */
|
||
|
||
ClearTypeAhead (pte);
|
||
SetTypeAheadKeyTicks (pte, 0); /* this resets idling, do we really want to do this? */
|
||
|
||
SetTypeAheadSelStart (pte, -1);
|
||
SetTypeAheadSelEnd (pte, -1);
|
||
|
||
/* NOTE: TypeAheadPrevSelEnd doesn't get reset here, because SetNicknameTypeAheadText calls this routine, and we need to
|
||
preserve TypeAheadPrevSelEnd across calls to SetNicknameTypeAheadText */
|
||
SetTypeAheadAliasIndex (pte, -1);
|
||
SetTypeAheadNicknameIndex (pte, -1);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* CurSelectionIsTypeAheadText - returns true if the current text
|
||
* selection in the window passed in win was created by nickname
|
||
* type-ahead. To return true, the following conditions have to be met:
|
||
*
|
||
* - the window must be a composition window
|
||
* - the MyWindowPtr and windex for win must match that in
|
||
* TypeAheadWin and TypeAheadWindex
|
||
* - selection must be in a recipient field
|
||
* - selection range must match that in TypeAheadSelStart and
|
||
* TypeAheadSelEnd
|
||
* - text of current selection must match that in gTypeAheadSuffix
|
||
* - text preeceeding current selection must match that in
|
||
* gTypeAheadPrefix
|
||
*
|
||
* The function will also return false in the unlikely event that
|
||
* PETE returns an error.
|
||
*
|
||
* This function ignores any pref settings which have to do with
|
||
* nickname type-ahead.
|
||
************************************************************************/
|
||
|
||
Boolean CurSelectionIsTypeAheadText (PETEHandle pte)
|
||
|
||
{
|
||
Str255 prefixStr;
|
||
Str255 suffixStr;
|
||
Handle textHdl;
|
||
long suffixLen,
|
||
selStart,
|
||
selEnd;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return (false);
|
||
|
||
// Verfify that this field accepts nickname completion
|
||
if (!((*PeteExtra(pte))->nick.fieldCheck ? (*(*PeteExtra(pte))->nick.fieldCheck) (pte) : HasNickCompletion (pte)))
|
||
return (false);
|
||
|
||
// Get the current selection and compare it against our own typing. We do this because the
|
||
// user could have changed our typeahead selection via the mouse manual text selection.
|
||
if (PeteGetTextAndSelection (pte, &textHdl, &selStart, &selEnd))
|
||
return (false);
|
||
|
||
if ((selStart != GetTypeAheadSelStart (pte)) || (selEnd != GetTypeAheadSelEnd (pte)))
|
||
return (false);
|
||
|
||
if (suffixLen = selEnd - selStart) {
|
||
BlockMoveData (*textHdl + selStart, suffixStr + 1, suffixLen);
|
||
suffixStr[0] = suffixLen;
|
||
if (!NicknameEqualString (suffixStr, gTypeAheadSuffix, true, true, true))
|
||
return (false);
|
||
}
|
||
else
|
||
if (gTypeAheadSuffix[0])
|
||
return (false);
|
||
|
||
(void) GetNicknamePrefixFromField (pte, prefixStr, true, false);
|
||
if (!prefixStr[0]) /* will be true if GetNicknamePrefixFromField returns an error code */
|
||
return (false);
|
||
if (!NicknameRawEqualString (prefixStr, gTypeAheadPrefix))
|
||
return (false);
|
||
|
||
return (true);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* add comments */
|
||
/************************************************************************
|
||
* OKToInitiateTypeAhead
|
||
************************************************************************/
|
||
Boolean OKToInitiateTypeAhead (PETEHandle pte)
|
||
{
|
||
Boolean charIsDelimiter;
|
||
UPtr curDelim;
|
||
long delimCount;
|
||
Handle textHdl;
|
||
long selStart, selEnd;
|
||
long curOffset;
|
||
long start,
|
||
end;
|
||
UPtr curChar;
|
||
unsigned char testChar;
|
||
|
||
if (!((*PeteExtra(pte))->nick.fieldCheck ? (*(*PeteExtra(pte))->nick.fieldCheck) (pte) : HasNickCompletion (pte)))
|
||
return (false);
|
||
|
||
if (!GetNickFieldRange (pte, &start, &end))
|
||
return (false);
|
||
|
||
if (PeteGetTextAndSelection (pte, &textHdl, &selStart, &selEnd))
|
||
return (false);
|
||
|
||
curOffset = selStart;
|
||
curChar = *textHdl + curOffset;
|
||
charIsDelimiter = false;
|
||
while (!charIsDelimiter && (curOffset > start))
|
||
{
|
||
curChar--;
|
||
curOffset--;
|
||
testChar = *curChar;
|
||
if (testChar == '@')
|
||
return false;
|
||
delimCount = gRecipientDelimiterList[0];
|
||
curDelim = gRecipientDelimiterList + 1;
|
||
while (!charIsDelimiter && delimCount--)
|
||
{
|
||
if (testChar == *curDelim)
|
||
charIsDelimiter = true;
|
||
else
|
||
curDelim++;
|
||
}
|
||
}
|
||
|
||
curOffset = selEnd;
|
||
curChar = *textHdl + curOffset;
|
||
while ((curOffset < end) && (*curChar == ' '))
|
||
{
|
||
curChar++;
|
||
curOffset++;
|
||
}
|
||
if (curOffset >= end)
|
||
return true;
|
||
|
||
testChar = *curChar;
|
||
charIsDelimiter = false;
|
||
delimCount = gRecipientDelimiterList[0];
|
||
curDelim = gRecipientDelimiterList + 1;
|
||
while (!charIsDelimiter && delimCount--)
|
||
{
|
||
if (testChar == *curDelim)
|
||
charIsDelimiter = true;
|
||
else
|
||
curDelim++;
|
||
}
|
||
return charIsDelimiter;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* SetNicknameTypeAheadText - routine for doing nickname type-ahead
|
||
* (automatic nickname completion while typing). Before calling this
|
||
* routine, you need to determine what characters the user has typed
|
||
* so far, and what nickname it matches. Pass the user's typing into
|
||
* prefixStr, and use aliasIndex and nicknameIndex to specify the
|
||
* nickname. Pass in the MyWindowPtr into which the user is typing in
|
||
* win. If you have pre-built a nickname table to be used with
|
||
* type-ahead, pass it in tableHdl, and pass in the associated entry
|
||
* index in tableIndex. If you pass in nil for tableHdl, this routine
|
||
* will build a new table, if necessary. If you pass in -1 for tableHdl,
|
||
* it will force a new table to be built, even if it normally wouldn't
|
||
* be necessary.
|
||
*
|
||
* This routine will get the nickname specified by aliasIndex and
|
||
* nicknameIndex, subtract the text in prefixStr from the beginning of
|
||
* it, insert the remaining characters at the text insertion point, and
|
||
* then select the newly inserted text.
|
||
*
|
||
* Only one window can have a nickname type-ahead selection at a time.
|
||
* If you call this routine for a second window when a first window
|
||
* already has a nickname type-ahead selection, the text and selection
|
||
* range in the first window will remain unaltered, but will no longer
|
||
* be considered a nickname type-ahead selection.
|
||
*
|
||
* This function ignores any pref settings which have to do with
|
||
* nickname type-ahead.
|
||
*
|
||
* This routine will intentionally fail if the current text insertion
|
||
* point is not in a recipient field, or if a range of text is selected.
|
||
* The one exception to this is that if a range of text is selected, but
|
||
* it was created by a prior call to SetNicknameTypeAheadText, the
|
||
* selected text will be deleted, and replaced with new text.
|
||
*
|
||
* Calling this routine sets up all of the nickname type-ahead globals,
|
||
* so that we can later test to see if the current selection came from
|
||
* calling this routine. To test this, call CurSelectionIsTypeAheadText.
|
||
*
|
||
* The function returns noErr if it succeeds, or a non-zero error code
|
||
* if it fails. If it fails, the nickname type-ahead globals will all
|
||
* be reset, even if the prior selection was created by calling this
|
||
* routine and is still intact after the failed attempt to change it.
|
||
* If the error occurred from calling PETE, the text in the window may
|
||
* have been altered, and you are not guaranteed as to what state it is
|
||
* in; this should not present a major problem, as the typical loss to
|
||
* the user would be that the undo buffer will be clear or the selection
|
||
* might not get recognized as nickname type-ahead text for future
|
||
* keyboard operations.
|
||
************************************************************************/
|
||
OSErr SetNicknameTypeAheadText (PETEHandle pte, Str255 prefixStr, short aliasIndex, short nicknameIndex, NicknameTableHdl tableHdl, long tableIndex)
|
||
|
||
{
|
||
NicknameTableHdl prevTypeAheadNicknameTable;
|
||
Str255 nicknameStr,
|
||
prefixStr_,
|
||
suffixStr;
|
||
Handle textHdl;
|
||
OSErr theError,
|
||
scratchErr;
|
||
long suffixLen,
|
||
selStart,
|
||
selEnd;
|
||
Boolean hadTypeAheadSel;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return (paramErr);
|
||
|
||
// Copy the prefix string
|
||
BlockMoveData (prefixStr, prefixStr_, prefixStr[0] + 1);
|
||
|
||
// If the current selection contains type ahead text and the nickname has not changed, just return
|
||
hadTypeAheadSel = CurSelectionIsTypeAheadText (pte);
|
||
if (hadTypeAheadSel && (aliasIndex == GetTypeAheadAliasIndex (pte)) && (nicknameIndex == GetTypeAheadNicknameIndex (pte)) && NicknameRawEqualString(prefixStr, gTypeAheadPrefix))
|
||
return (noErr);
|
||
|
||
// Make a copy of the nick table handle (but not the contents), then make the global
|
||
// nil so that the table is not disposed when resetting the type ahead globals
|
||
prevTypeAheadNicknameTable = gTypeAheadNicknameTable;
|
||
gTypeAheadNicknameTable = nil; /* to prevent ResetNicknameTypeAhead from disposing of it */
|
||
ResetNicknameTypeAhead (pte);
|
||
|
||
if (!hadTypeAheadSel && !OKToInitiateTypeAhead (pte))
|
||
return (paramErr);
|
||
|
||
if (!((*PeteExtra(pte))->nick.fieldCheck ? (*(*PeteExtra(pte))->nick.fieldCheck) (pte) : HasNickCompletion (pte)))
|
||
return (paramErr);
|
||
|
||
if (theError = PeteGetTextAndSelection (pte, &textHdl, &selStart, &selEnd))
|
||
return (theError);
|
||
|
||
if ((selStart != selEnd) && !hadTypeAheadSel) /* FINISH *//* should we do this? what's the harm? maybe this should be removed. */
|
||
return (paramErr);
|
||
|
||
/* FINISH *//* just dig it out of the table! */
|
||
if (theError = RegenerateAllAliases (false)) /* FINISH *//* shouldn't we be passing in true? */
|
||
return (theError);
|
||
|
||
GetNicknameNamePStr (aliasIndex, nicknameIndex, nicknameStr);
|
||
|
||
#ifdef NTA_PREVENT_EXACT_MATCH
|
||
/* never set type-ahead text if the prefix is an exact match with the specified nickname */
|
||
if (prefixStr_[0] == nicknameStr[0])
|
||
return (noErr);
|
||
#endif
|
||
|
||
suffixLen = nicknameStr[0] - prefixStr_[0];
|
||
#ifdef NTA_PREVENT_EXACT_MATCH
|
||
if (suffixLen <= 0)
|
||
return (noErr);
|
||
#else
|
||
if (suffixLen < 0) /* unexpected */
|
||
return (noErr);
|
||
#endif
|
||
|
||
if (suffixLen)
|
||
BlockMoveData (nicknameStr + 1 + nicknameStr[0] - suffixLen, suffixStr + 1, suffixLen);
|
||
suffixStr[0] = suffixLen;
|
||
|
||
if ((prefixStr_[0] + suffixStr[0]) > 255) /* unlikely, but we'll check for this, just in case */
|
||
return (noErr);
|
||
|
||
/* FINISH *//* HUH??? How can this ever be true, if prefixStr+suffixStr is nicknameStr, which we know is always <= 255 chars? Enquiring minds want to know! */
|
||
|
||
/* FINISH */
|
||
// make sure we don't already have a special selection from us
|
||
|
||
theError = PETESetRecalcState (PETE, pte, false);
|
||
if (!theError)
|
||
theError = PETEClearUndo (PETE, pte);
|
||
if (!theError)
|
||
theError = PeteInsertPtr (pte, kPETECurrentSelection, suffixStr + 1, suffixLen);
|
||
|
||
/* FINISH *//* is this the right thing to do? */
|
||
if (!theError && suffixLen)
|
||
PeteSelect ((*PeteExtra(pte))->win, pte, selStart, selStart + suffixLen);
|
||
|
||
scratchErr = PETESetRecalcState(PETE, pte, true);
|
||
if (!theError)
|
||
theError = scratchErr;
|
||
if (!theError)
|
||
theError = PeteGetTextAndSelection (pte, &textHdl, &selStart, &selEnd);
|
||
if (theError)
|
||
return (theError);
|
||
|
||
SetTypeAheadSelStart (pte, selStart);
|
||
SetTypeAheadSelEnd (pte, selEnd);
|
||
BlockMoveData(prefixStr_, gTypeAheadPrefix, prefixStr_[0] + 1);
|
||
BlockMoveData(suffixStr, gTypeAheadSuffix, suffixStr[0] + 1);
|
||
SetTypeAheadAliasIndex (pte, aliasIndex);
|
||
SetTypeAheadNicknameIndex (pte, nicknameIndex);
|
||
|
||
// If we need to, fore a rebuild of the nickname table
|
||
if (tableHdl == (NicknameTableHdl) (-1)) {
|
||
tableHdl = nil;
|
||
if (prevTypeAheadNicknameTable) {
|
||
DisposeNicknameTable(prevTypeAheadNicknameTable);
|
||
prevTypeAheadNicknameTable = nil;
|
||
}
|
||
}
|
||
if (tableHdl) {
|
||
if (prevTypeAheadNicknameTable && (tableHdl != prevTypeAheadNicknameTable))
|
||
DisposeNicknameTable(prevTypeAheadNicknameTable);
|
||
gTypeAheadNicknameTable = tableHdl;
|
||
gTypeAheadNicknameTableIndex = tableIndex;
|
||
}
|
||
else
|
||
if (prevTypeAheadNicknameTable && NicknameRawEqualString(prefixStr, (**prevTypeAheadNicknameTable).prefixStr)) {
|
||
gTypeAheadNicknameTable = prevTypeAheadNicknameTable;
|
||
gTypeAheadNicknameTableIndex = FindNicknameTableIndex(gTypeAheadNicknameTable, aliasIndex, nicknameIndex);
|
||
}
|
||
else {
|
||
if (prevTypeAheadNicknameTable && EqualStringPrefix((**prevTypeAheadNicknameTable).prefixStr, prefixStr, true, true, true)) {
|
||
#ifdef NTA_PREVENT_EXACT_MATCH
|
||
theError = BuildNicknameTableSubset (prefixStr, prevTypeAheadNicknameTable, false, &gTypeAheadNicknameTable);
|
||
#else
|
||
theError = BuildNicknameTableSubset (prefixStr, prevTypeAheadNicknameTable, true, &gTypeAheadNicknameTable);
|
||
#endif
|
||
DisposeNicknameTable (prevTypeAheadNicknameTable);
|
||
}
|
||
else {
|
||
if (prevTypeAheadNicknameTable)
|
||
DisposeNicknameTable(prevTypeAheadNicknameTable);
|
||
#ifdef NTA_PREVENT_EXACT_MATCH
|
||
theError = BuildNicknameTable (prefixStr, GetAliasFileToScan (pte), false, false, &gTypeAheadNicknameTable);
|
||
#else
|
||
theError = BuildNicknameTable (prefixStr, GetAliasFileToScan (pte), false, true, &gTypeAheadNicknameTable);
|
||
#endif
|
||
}
|
||
if (theError) {
|
||
ResetNicknameTypeAhead (pte);
|
||
return (theError);
|
||
}
|
||
|
||
gTypeAheadNicknameTableIndex = FindNicknameTableIndex (gTypeAheadNicknameTable, aliasIndex, nicknameIndex);
|
||
}
|
||
if ((gTypeAheadNicknameTableIndex < 0) || (gTypeAheadNicknameTableIndex >= (**gTypeAheadNicknameTable).numEntries))
|
||
gTypeAheadNicknameTableIndex = 0;
|
||
|
||
return (noErr);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameTypeAheadKey - process a keystroke from the user's typing in
|
||
* a composition window, and do the appropriate handling for nickname
|
||
* type-ahead (automatic nickname completion while typing). Pass in the
|
||
* window in which the user is typing in win, and a pointer to the
|
||
* EventRecord for the keystroke in event. win must point to a
|
||
* composition window.
|
||
*
|
||
* The function returns true if no further processing of the keystroke
|
||
* is needed, and false if the caller should proceed and handle the
|
||
* keystroke on its own. The function may do some processing, and still
|
||
* return false. The function may also modify the event record before
|
||
* returning false. In any case, if the function returns false, you must
|
||
* continue with processing, and if you copied any data out of the
|
||
* EventRecord prior to calling this routine, you may need to re-extract
|
||
* those fields.
|
||
*
|
||
* This function has no effect if the text insertion point is not in a
|
||
* recipient field, or if the pref for nickname type-ahead is turned off.
|
||
* It is assumed that this routine will only be called from CompKey, and
|
||
* therefore, this function will not check to validate that the window
|
||
* passed in win is a composition window.
|
||
*
|
||
* Calling this routine will most likely do a reset of the nickname
|
||
* type-ahead globals.
|
||
*
|
||
* This routine doesn't actually do the automatic insertion of the
|
||
* nickname completion; that is done by NicknameTypeAheadIdle or
|
||
* NicknameTypeAheadKeyFollowup. Calling this routine will set a flag,
|
||
* if appropriate, to let NicknameTypeAheadIdle or
|
||
* NicknameTypeAheadKeyFollowup know that it needs to process the user's
|
||
* new keystroke.
|
||
************************************************************************/
|
||
|
||
Boolean NicknameTypeAheadKey (PETEHandle pte, EventRecord* event)
|
||
|
||
{
|
||
OSErr theError;
|
||
long tableIndex;
|
||
short aliasIndex,
|
||
nicknameIndex;
|
||
unsigned char keyChar; /* ASCII of keystroke */
|
||
unsigned char keyCode; /* virtual key code of keystroke */
|
||
Boolean hasTypeAheadSel;
|
||
|
||
// Verify that this field can accept type ahead right now
|
||
if (!NicknameTypeAheadValid (pte))
|
||
return (false);
|
||
if (!((*PeteExtra(pte))->nick.fieldCheck ? (*(*PeteExtra(pte))->nick.fieldCheck) (pte) : HasNickCompletion (pte)))
|
||
return (false);
|
||
|
||
// Check to see whether or not the current selection was created by type ahead,
|
||
// or if it is okay to start type ahead typing.
|
||
hasTypeAheadSel = CurSelectionIsTypeAheadText (pte);
|
||
if (!hasTypeAheadSel)
|
||
if (!OKToInitiateTypeAhead (pte))
|
||
return (false);
|
||
|
||
keyChar = event->message & charCodeMask;
|
||
keyCode = (event->message & keyCodeMask) >> 8;
|
||
|
||
// If a command-key combination aside from cmd-period is typed,
|
||
// pass it back to the caller.
|
||
if ((event->modifiers & cmdKey) && keyChar != periodChar)
|
||
return (false);
|
||
|
||
// Make some adjustments to help our switch statement find it's way...
|
||
if (keyCode == clearKey)
|
||
keyChar = delChar;
|
||
else
|
||
if (keyCode == escKey)
|
||
keyChar = escChar;
|
||
|
||
switch (keyChar) {
|
||
case backSpace:
|
||
case delChar:
|
||
// On backspace, delete, clear (or forward delete), we reset the type ahead variables
|
||
// then pass back false so that the keystroke will be processed by PETE, thereby
|
||
// deleting the current selection
|
||
ResetNicknameTypeAhead (pte);
|
||
return (false);
|
||
break;
|
||
case tabKey: // (jp) Added tab support here in b73 to change case immediately
|
||
case returnChar:
|
||
case enterChar:
|
||
// On the enter or return keys we auto-complete the nickname, then reset the type
|
||
// ahead globals.
|
||
if (hasTypeAheadSel) {
|
||
(void) FinishAliasUsingTypeAhead (pte, CanWeExpand (pte, event), false, false);
|
||
ResetNicknameTypeAhead (pte);
|
||
}
|
||
return (keyChar == tabKey ? false : hasTypeAheadSel);
|
||
break;
|
||
case periodChar:
|
||
// If there exists a selection and the user hits cmd-period, pass the event back
|
||
// and handle it as though the user has pressed delete or backspace.
|
||
if (hasTypeAheadSel && (event->modifiers & cmdKey)) {
|
||
event->modifiers &= ~cmdKey;
|
||
event->message &= ~(keyCodeMask | charCodeMask);
|
||
event->message |= (clearKey << 8) | clearChar;
|
||
ResetNicknameTypeAhead (pte);
|
||
}
|
||
else {
|
||
NeedTypeAhead (pte);
|
||
SetTypeAheadKeyTicks (pte, TickCount ());
|
||
}
|
||
return (false);
|
||
break;
|
||
case escChar:
|
||
if (hasTypeAheadSel) {
|
||
event->modifiers &= ~cmdKey;
|
||
event->message &= ~(keyCodeMask | charCodeMask);
|
||
event->message |= (clearKey << 8) | clearChar;
|
||
ResetNicknameTypeAhead (pte);
|
||
return (false);
|
||
}
|
||
break;
|
||
case commaChar:
|
||
case optionCommaChar:
|
||
// Complete the nickname on a comma or opt-comma, then reset the type ahead variables
|
||
if (hasTypeAheadSel) {
|
||
(void) FinishAliasUsingTypeAhead (pte, CanWeExpand (pte, event), false, true);
|
||
ResetNicknameTypeAhead (pte);
|
||
}
|
||
return (hasTypeAheadSel);
|
||
break;
|
||
case upArrowChar:
|
||
case downArrowChar:
|
||
// On an up or down arrow we cycle through the matching nicknames (in the proper direction)
|
||
// if there is a current type ahead selection
|
||
if (hasTypeAheadSel) {
|
||
SetTypeAheadPrevSelEnd (pte, -1);
|
||
if (gTypeAheadNicknameTable) {
|
||
tableIndex = gTypeAheadNicknameTableIndex;
|
||
if (keyChar == upArrowChar) {
|
||
if (--tableIndex < 0)
|
||
tableIndex = (**gTypeAheadNicknameTable).numEntries - 1;
|
||
}
|
||
else
|
||
if (keyChar == downArrowChar)
|
||
if (++tableIndex >= (**gTypeAheadNicknameTable).numEntries)
|
||
tableIndex = 0;
|
||
|
||
GetNicknameTableEntry (gTypeAheadNicknameTable, tableIndex, &aliasIndex, &nicknameIndex);
|
||
if (!((aliasIndex == GetTypeAheadAliasIndex (pte)) && (nicknameIndex == GetTypeAheadNicknameIndex (pte)))) {
|
||
SetTypeAheadPrevSelEnd (pte, GetTypeAheadSelEnd (pte));
|
||
theError = SetNicknameTypeAheadText (pte, gTypeAheadPrefix, aliasIndex, nicknameIndex, gTypeAheadNicknameTable, tableIndex);
|
||
}
|
||
else
|
||
SysBeep (5);
|
||
}
|
||
else
|
||
SysBeep (5);
|
||
// Once we've changed selections, we're no longer checking for type ahead (apparently)
|
||
ClearTypeAhead (pte);
|
||
return (true);
|
||
}
|
||
else
|
||
ResetNicknameTypeAhead (pte);
|
||
return (hasTypeAheadSel);
|
||
break;
|
||
case leftArrow:
|
||
case rightArrow:
|
||
// On left or right arrow we just reset typeahead and let PETE handle the keystroke
|
||
ResetNicknameTypeAhead (pte);
|
||
return (false);
|
||
break;
|
||
default:
|
||
// As long as we didn't attempt to enter a control key, we'll handle it
|
||
if (keyChar >= ' ') {
|
||
NeedTypeAhead (pte);
|
||
SetTypeAheadKeyTicks (pte, TickCount ());
|
||
return (false);
|
||
}
|
||
break;
|
||
}
|
||
return (false);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameTypeAheadKeyFollowup - do followup work for processing a
|
||
* keystroke from the user's typing in a composition window, and do the
|
||
* appropriate handling for nickname type-ahead (automatic nickname
|
||
* completion while typing). Pass in the window in which the user is
|
||
* typing in win. win must point to a composition window.
|
||
*
|
||
* This function is used to handle nickname type-ahead when immediate
|
||
* completion is turned on, via the global Boolean
|
||
* gNicknameWatcherImmediate. When this flag is on, we want to do the
|
||
* nickname completion immediately after the user's keystroke, instead
|
||
* of waiting until the next time NicknameTypeAheadIdle gets called.
|
||
* This routine just calls NicknameTypeAheadIdle itself, and temporarily
|
||
* sets the global gNicknameWatcherWaitKeyThresh to force
|
||
* NicknameTypeAheadIdle to do its processing.
|
||
*
|
||
* This function has no effect if the text insertion point is not in a
|
||
* recipient field, or if the pref for nickname type-ahead is turned off.
|
||
* It is assumed that this routine will only be called from CompKey, and
|
||
* therefore, this function will not check to validate that the window
|
||
* passed in win is a composition window.
|
||
*
|
||
* Calling this routine will most likely do a reset of the nickname
|
||
* type-ahead globals.
|
||
************************************************************************/
|
||
void NicknameTypeAheadKeyFollowup (PETEHandle pte)
|
||
{
|
||
Boolean origNicknameWatcherWaitKeyThresh;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
if (!gNicknameTypeAheadEnabled || !gNicknameWatcherImmediate || !HasNickCompletion (pte))
|
||
return;
|
||
|
||
origNicknameWatcherWaitKeyThresh = gNicknameWatcherWaitKeyThresh;
|
||
gNicknameWatcherWaitKeyThresh = false;
|
||
|
||
// Give idle time immediately
|
||
NicknameTypeAheadIdle (pte);
|
||
|
||
gNicknameWatcherWaitKeyThresh = origNicknameWatcherWaitKeyThresh;
|
||
}
|
||
|
||
|
||
//
|
||
// NicknameTypeAheadValid
|
||
//
|
||
// Verify that...
|
||
// - we got a PETE
|
||
// - that the setting for type ahead is set
|
||
// -- whether or not we're in a field that handles type ahead
|
||
|
||
Boolean NicknameTypeAheadValid (PETEHandle pte)
|
||
|
||
{
|
||
if (!PeteIsValid(pte))
|
||
return (false);
|
||
if (!gNicknameTypeAheadEnabled)
|
||
return (false);
|
||
if (!HasNickCompletion (pte))
|
||
return (false);
|
||
return (true);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameTypeAheadIdle - do idle-time nickname type-ahead processing
|
||
* for the window passed in win. Pass in the window the user has been
|
||
* typing in in win; this must be a composition window.
|
||
*
|
||
* This routine handles a user's typing after they have finished typing
|
||
* a few characters from the beginning of a nickname. The actual
|
||
* processing of keystrokes is handled by NicknameTypeAheadKey.
|
||
*
|
||
* Since the user may be typing several characters, this routine will
|
||
* not process the user's typing until there are no more keyDown events
|
||
* in the event queue and at least gTypeAheadIdleDelay ticks have passed
|
||
* since the last keystroke. Once this occurs, and if the user has typed
|
||
* a valid nickname prefix, this routine calls SetNicknameTypeAheadText
|
||
* to do the actual insertion of text into the window.
|
||
*
|
||
* This function has no effect if the text insertion point is not in a
|
||
* recipient field, or if the pref for nickname type-ahead is turned off.
|
||
* It is assumed that this routine will only be called from CompIdle, and
|
||
* therefore, this function will not check to validate that the window
|
||
* passed in win is a composition window.
|
||
************************************************************************/
|
||
void NicknameTypeAheadIdle (PETEHandle pte)
|
||
|
||
{
|
||
EventRecord scratchEvent;
|
||
NicknameTableHdl newTable;
|
||
Str255 prefixStr;
|
||
OSErr theError;
|
||
long curTicks,
|
||
tableIndex,
|
||
oldLastTypeAheadKeyTicks, // SD
|
||
selStart,
|
||
selEnd;
|
||
short aliasIndex,
|
||
nicknameIndex;
|
||
|
||
// Verify that this field can accept type ahead right now
|
||
if (!NicknameTypeAheadValid (pte))
|
||
return;
|
||
if (!((*PeteExtra(pte))->nick.fieldCheck ? (*(*PeteExtra(pte))->nick.fieldCheck) (pte) : HasNickCompletion (pte)))
|
||
return;
|
||
|
||
// Has type ahead started? If not, why bother, y'know?
|
||
if (!TypeAhead (pte))
|
||
return;
|
||
|
||
// Is it time for a type ahead check?
|
||
curTicks = TickCount();
|
||
if (gNicknameWatcherWaitKeyThresh && (curTicks < (GetTypeAheadKeyTicks (pte) + gTypeAheadIdleDelay)))
|
||
return;
|
||
|
||
// But wait... What if the user is still typing?
|
||
if (gNicknameWatcherWaitNoKeyDown && EventAvail (keyDownMask, &scratchEvent))
|
||
return;
|
||
|
||
// If there is a selection -- reset the type ahead variables and bail
|
||
if (!PeteGetTextAndSelection (pte, nil, &selStart, &selEnd))
|
||
if (selStart != selEnd) {
|
||
ResetNicknameTypeAhead (pte);
|
||
return;
|
||
}
|
||
|
||
UseFeature (featureNicknameWatching);
|
||
|
||
oldLastTypeAheadKeyTicks = GetTypeAheadKeyTicks (pte); // SD -- in case we need to put it back
|
||
SetTypeAheadKeyTicks (pte, curTicks); // Why?
|
||
ClearTypeAhead (pte);
|
||
|
||
// Get the character typed so far into the field
|
||
theError = GetNicknamePrefixFromField (pte, prefixStr, true, false);
|
||
if (theError || !prefixStr[0] || (prefixStr[0] < kMinCharsForTypeAhead))
|
||
return;
|
||
|
||
// If the prefix matches only one nickname, we're done! Yippee!!
|
||
if (!NicknamePrefixOccursNTimes (prefixStr, 1, GetAliasFileToScan (pte)))
|
||
return;
|
||
|
||
// When the prefix matches multiple nicknames we'll need to build a table of matches (remember,
|
||
// this is being done at idle time) that will be used by the sticky popup or the up/down arrow
|
||
// keys to cycle through nicknames that match the typed prefix.
|
||
if (gTypeAheadNicknameTable && EqualStringPrefix ((**gTypeAheadNicknameTable).prefixStr, prefixStr, true, true, true))
|
||
#ifdef NTA_PREVENT_EXACT_MATCH
|
||
theError = BuildNicknameTableSubset (prefixStr, gTypeAheadNicknameTable, false, &newTable);
|
||
#else
|
||
theError = BuildNicknameTableSubset (prefixStr, gTypeAheadNicknameTable, true, &newTable);
|
||
#endif
|
||
else
|
||
#ifdef NTA_PREVENT_EXACT_MATCH
|
||
theError = BuildNicknameTable (prefixStr, GetAliasFileToScan (pte), false, false, &newTable);
|
||
#else
|
||
theError = BuildNicknameTable (prefixStr, GetAliasFileToScan (pte), false, true, &newTable);
|
||
|
||
// if the table build is cancelled because the user does something, try again later SD
|
||
// by changing the type ahead variable back to "need checking" status and setting the
|
||
// type ahead key ticks back to it's original value
|
||
if (theError == userCanceledErr) {
|
||
NeedTypeAhead (pte);
|
||
SetTypeAheadKeyTicks (pte, oldLastTypeAheadKeyTicks);
|
||
}
|
||
|
||
if (!theError) {
|
||
// (jp) 12-2-99 To default the table to an item not in the history list
|
||
tableIndex = FindFirstNonHistoryNicknameTableIndex (newTable);
|
||
// tableIndex = 0;
|
||
GetNicknameTableEntry (newTable, tableIndex, &aliasIndex, &nicknameIndex);
|
||
(void) SetNicknameTypeAheadText (pte, prefixStr, aliasIndex, nicknameIndex, newTable, tableIndex);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameTypeAheadFinish - if a nickname type-ahead selection exists,
|
||
* finish it; this is the equivalent as if the user had manually pressed
|
||
* Return or Enter while the window was active.
|
||
*
|
||
* Pass the window you want the call to operate on in win. If you pass
|
||
* in nil for win, and a nickname type-ahead selection exists, this
|
||
* function will operate on whatever window contains the type-ahead
|
||
* selection.
|
||
*
|
||
* Since a nickname type-ahead selection is essentially a mode, and since
|
||
* there is no visual feedback to indicate that it exists, and since it's
|
||
* a pretty complex concept, the type-ahead selection should be finished
|
||
* whenever the user takes their attention away from that particular
|
||
* recipient field. This includes actions like switching between fields
|
||
* in the message, making non-textual changes to the message,
|
||
* de-activating the message's window, etc. Although it shouldn't hurt
|
||
* anything to leave a type-ahead selection unfinished in such a
|
||
* situation, it is best for the sake of UI to do so, anyways.
|
||
*
|
||
* This function ignores any pref settings which have to do with
|
||
* nickname type-ahead.
|
||
************************************************************************/
|
||
void NicknameTypeAheadFinish (PETEHandle pte)
|
||
{
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
if (CurSelectionIsTypeAheadText (pte))
|
||
(void) FinishAliasUsingTypeAhead (pte, CanWeExpand (pte, nil), false, false);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* ToggleNicknameTypeAhead - toggle the state of the global
|
||
* gNicknameTypeAheadEnabled, and if we've just turned it on, do an
|
||
* expansion of the current recipient, if appropriate.
|
||
*
|
||
* Calling this routine does not actually change the pref setting itself
|
||
* (PREF_NICK_EXP_TYPE_AHEAD), but rather just changes the global. The
|
||
* global will get reset each time the composition window receives an
|
||
* activate/deactivate event, so this is just a temporary mechanism,
|
||
* intended for toggling the state while typing in a recipient field.
|
||
************************************************************************/
|
||
void ToggleNicknameTypeAhead (PETEHandle pte)
|
||
|
||
{
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
gNicknameTypeAheadEnabled = !gNicknameTypeAheadEnabled;
|
||
ResetNicknameTypeAhead (pte);
|
||
if (gNicknameTypeAheadEnabled) {
|
||
NeedTypeAhead (pte);
|
||
if (gNicknameWatcherImmediate)
|
||
NicknameTypeAheadKeyFollowup (pte);
|
||
}
|
||
}
|
||
|
||
|
||
#pragma mark ========== Nickname hiliting routines ==========
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* ResetNicknameHiliting
|
||
************************************************************************/
|
||
void ResetNicknameHiliting (PETEHandle pte)
|
||
|
||
{
|
||
gHilitingIdleDelay = gNicknameWatcherWaitKeyThresh ? LMGetKeyThresh() : 0;
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
ClearHilite (pte);
|
||
ClearRefresh (pte);
|
||
SetHilitingKeyTicks (pte, 0);
|
||
SetHilitingStart (pte, -1);
|
||
SetHilitingEnd (pte, -1);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* SetNicknameHiliting
|
||
************************************************************************/
|
||
OSErr SetNicknameHiliting (PETEHandle pte, long startOffset, long endOffset, Boolean hilite)
|
||
|
||
{
|
||
PETEDocInfo peteInfo;
|
||
OSErr theError;
|
||
short peteDirtyWas;
|
||
Boolean winDirtyWas;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return (paramErr);
|
||
|
||
theError = noErr;
|
||
|
||
// save the dirty bits
|
||
peteDirtyWas = PeteIsDirty (pte);
|
||
winDirtyWas = (*PeteExtra(pte))->win->isDirty;
|
||
|
||
if (hilite && ((*PeteExtra(pte))->nick.fieldCheck ? (*(*PeteExtra(pte))->nick.fieldCheck) (pte) : HasNickHiliting (pte)))
|
||
theError = PeteLabel (pte, startOffset, endOffset, pNickHiliteLabel, pNickHiliteLabel);
|
||
else
|
||
theError = PeteNoLabel (pte, startOffset, endOffset, pNickHiliteLabel);
|
||
|
||
if (!theError)
|
||
theError = PETEGetDocInfo (PETE, pte, &peteInfo);
|
||
if (!theError)
|
||
if (endOffset == peteInfo.selStart)
|
||
if (hilite)
|
||
theError = PeteNoLabel (pte, endOffset, endOffset, pNickHiliteLabel);
|
||
|
||
// restore the dirty bits
|
||
if (!theError) {
|
||
PETEMarkDocDirty(PETE,pte,peteDirtyWas);
|
||
(*PeteExtra(pte))->win->isDirty = winDirtyWas;
|
||
}
|
||
return (theError);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameHilitingUpdateRange
|
||
************************************************************************/
|
||
OSErr NicknameHilitingUpdateRange (PETEHandle pte, long startOffset, long endOffset)
|
||
{
|
||
Str255 recipientTestStr;
|
||
Handle textHdl;
|
||
OSErr theError;
|
||
long curOffset,
|
||
curRecipientStartOffset,
|
||
curRecipientEndOffset,
|
||
curRecipientLen,
|
||
foundOffset;
|
||
Boolean recipientIsNickname,
|
||
hasTypeAheadSel;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return paramErr;
|
||
|
||
theError = PeteGetRawText (pte, &textHdl);
|
||
|
||
if (!theError) {
|
||
hasTypeAheadSel = CurSelectionIsTypeAheadText (pte);
|
||
|
||
curOffset = startOffset;
|
||
while (!theError && curOffset < endOffset) {
|
||
foundOffset = FindNextRecipientStart (textHdl, curOffset);
|
||
if (foundOffset > curOffset)
|
||
(void) SetNicknameHiliting (pte, curOffset, foundOffset, false); /* ignore errors */
|
||
if (foundOffset >= endOffset)
|
||
return (noErr);
|
||
|
||
curOffset = foundOffset;
|
||
foundOffset = FindRecipientEndInText (textHdl, curOffset, true);
|
||
curRecipientStartOffset = curOffset;
|
||
curRecipientEndOffset = foundOffset;
|
||
curRecipientLen = curRecipientEndOffset - curRecipientStartOffset;
|
||
|
||
if (curRecipientLen <= 0)
|
||
continue;
|
||
|
||
/* NOTE *//* normal nickname completion is sensitive to diacritical marks and sticky characters, but type-ahead is not, so we have to special-case type-ahead */
|
||
if (hasTypeAheadSel && (curRecipientEndOffset == GetTypeAheadSelEnd (pte)) && (curRecipientStartOffset == (GetTypeAheadSelStart (pte) - gTypeAheadPrefix[0])))
|
||
recipientIsNickname = true;
|
||
else {
|
||
MakePStr (recipientTestStr, *textHdl + curRecipientStartOffset, curRecipientLen);
|
||
recipientIsNickname = NEFindNickname (recipientTestStr, false, true, true, nil, nil);
|
||
}
|
||
|
||
theError = SetNicknameHiliting (pte, curRecipientStartOffset, curRecipientEndOffset, recipientIsNickname); /* ignore errors */
|
||
|
||
curOffset = curRecipientEndOffset;
|
||
}
|
||
}
|
||
return (noErr); // Ignore errors
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameHilitingUpdateField
|
||
************************************************************************/
|
||
OSErr NicknameHilitingUpdateField (PETEHandle pte)
|
||
|
||
{
|
||
Handle textHdl;
|
||
OSErr theError,
|
||
scratchErr;
|
||
long selStart,
|
||
selEnd;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return (paramErr);
|
||
|
||
if (!((*PeteExtra(pte))->nick.fieldCheck ? (*(*PeteExtra(pte))->nick.fieldCheck) (pte) : HasNickHiliting (pte)))
|
||
return (paramErr);
|
||
|
||
ResetNicknameHiliting (pte);
|
||
|
||
if (!gNicknameHilitingEnabled)
|
||
return (NicknameHilitingUnHiliteField (pte));
|
||
|
||
if (theError = PETESetRecalcState (PETE, pte, false))
|
||
return (theError);
|
||
|
||
if ((*PeteExtra(pte))->nick.fieldHilite)
|
||
(*(*PeteExtra(pte))->nick.fieldHilite) (pte, true);
|
||
else {
|
||
theError = PeteGetTextAndSelection (pte, &textHdl, &selStart, &selEnd);
|
||
if (!theError)
|
||
theError = NicknameHilitingUpdateRange (pte, 0, GetHandleSize (textHdl));
|
||
}
|
||
|
||
scratchErr = PETESetRecalcState (PETE, pte, true);
|
||
if (!theError)
|
||
theError = scratchErr;
|
||
|
||
PeteWhompHiliteRegionBecausePeteWontFixIt (pte);
|
||
|
||
return (theError);
|
||
}
|
||
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameHilitingUnHiliteField
|
||
************************************************************************/
|
||
OSErr NicknameHilitingUnHiliteField (PETEHandle pte)
|
||
|
||
{
|
||
Handle textHdl;
|
||
OSErr theError,
|
||
scratchErr;
|
||
long selStart,
|
||
selEnd;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return (paramErr);
|
||
|
||
if (!((*PeteExtra(pte))->nick.fieldCheck ? (*(*PeteExtra(pte))->nick.fieldCheck) (pte) : HasNickHiliting (pte)))
|
||
return (paramErr);
|
||
|
||
ResetNicknameHiliting (pte);
|
||
|
||
if (theError = PETESetRecalcState(PETE, pte, false))
|
||
return (theError);
|
||
|
||
if ((*PeteExtra(pte))->nick.fieldHilite)
|
||
(*(*PeteExtra(pte))->nick.fieldHilite) (pte, false);
|
||
else {
|
||
theError = PeteGetTextAndSelection (pte, &textHdl, &selStart, &selEnd);
|
||
if (!theError)
|
||
theError = PeteNoLabel (pte, 0, GetHandleSize (textHdl), pNickHiliteLabel);
|
||
}
|
||
|
||
scratchErr = PETESetRecalcState(PETE, pte, true);
|
||
if (!theError)
|
||
theError = scratchErr;
|
||
|
||
PeteWhompHiliteRegionBecausePeteWontFixIt (pte);
|
||
return (theError);
|
||
}
|
||
|
||
|
||
/************************************************************************
|
||
* NicknameHilitingUpdateAllFields
|
||
************************************************************************/
|
||
void NicknameHilitingUpdateAllFields(void)
|
||
|
||
{
|
||
WindowPtr winWP;
|
||
MyWindowPtr win;
|
||
PETEHandle pte;
|
||
|
||
winWP = FrontWindow_();
|
||
|
||
// Loop through all the windows and fields, updating the hilite
|
||
while (winWP) {
|
||
win = GetWindowMyWindowPtr (winWP);
|
||
if (IsKnownWindowMyWindow (winWP))
|
||
if (IsWindowVisible(winWP))
|
||
for (pte = win->pteList; pte; pte = PeteNext (pte))
|
||
if (HasNickHiliting (pte))
|
||
(void) NicknameHilitingUpdateField (pte);
|
||
winWP = GetNextWindow (winWP);
|
||
}
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameHilitingUnHiliteAllFields
|
||
************************************************************************/
|
||
void NicknameHilitingUnHiliteAllFields (void)
|
||
|
||
{
|
||
WindowPtr winWP;
|
||
MyWindowPtr win;
|
||
PETEHandle pte;
|
||
|
||
winWP = FrontWindow_();
|
||
|
||
// Loop through all the windows and fields, updating the hilite
|
||
while (winWP) {
|
||
win = GetWindowMyWindowPtr (winWP);
|
||
if (IsMyWindow (winWP))
|
||
if (IsWindowVisible(winWP))
|
||
for (pte = win->pteList; pte; pte = PeteNext (pte))
|
||
if (HasNickHiliting (pte))
|
||
(void) NicknameHilitingUnHiliteField (pte);
|
||
winWP = GetNextWindow(winWP);
|
||
}
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* HilitingRangeCheckInsert
|
||
************************************************************************/
|
||
void HilitingRangeCheckInsert (PETEHandle pte, long startOffset, long insertSize)
|
||
|
||
{
|
||
long headerStartOffset,
|
||
headerEndOffset;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
if (RefreshPending (pte))
|
||
return;
|
||
|
||
NeedHilite (pte);
|
||
|
||
if (GetHilitingStart (pte) == -1) {
|
||
SetHilitingStart (pte, startOffset);
|
||
SetHilitingEnd (pte, startOffset + insertSize);
|
||
return;
|
||
}
|
||
|
||
if (startOffset >= GetHilitingEnd (pte))
|
||
SetHilitingEnd (pte, startOffset + insertSize);
|
||
else {
|
||
SetHilitingEnd (pte, GetHilitingEnd (pte) + insertSize);
|
||
if (startOffset < GetHilitingStart (pte))
|
||
SetHilitingStart (pte, startOffset);
|
||
}
|
||
|
||
if (GetNickFieldRange (pte, &headerStartOffset, &headerEndOffset)) {
|
||
headerEndOffset += insertSize;
|
||
if ((GetHilitingStart (pte) < headerStartOffset) || (GetHilitingEnd (pte) > headerEndOffset))
|
||
NeedRefresh (pte);
|
||
}
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* HilitingRangeCheckDelete
|
||
************************************************************************/
|
||
void HilitingRangeCheckDelete(PETEHandle pte, long startOffset, long endOffset)
|
||
{
|
||
long headerStartOffset,
|
||
headerEndOffset,
|
||
swap;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
if (RefreshPending (pte))
|
||
return;
|
||
|
||
if (endOffset < startOffset) {
|
||
swap = startOffset;
|
||
startOffset = endOffset;
|
||
endOffset = swap;
|
||
}
|
||
|
||
NeedHilite (pte);
|
||
|
||
if (GetHilitingStart (pte) == -1) {
|
||
SetHilitingStart (pte, startOffset);
|
||
SetHilitingEnd (pte, startOffset);
|
||
return;
|
||
}
|
||
|
||
if (startOffset < GetHilitingStart (pte))
|
||
SetHilitingStart (pte, startOffset);
|
||
if (endOffset > GetHilitingEnd (pte))
|
||
SetHilitingEnd (pte, startOffset);
|
||
else
|
||
SetHilitingEnd (pte, startOffset + GetHilitingEnd (pte) - endOffset);
|
||
|
||
/* FINISH *//* do this sort of stuff in keyDown instead of here */
|
||
if (GetNickFieldRange (pte, &headerStartOffset, &headerEndOffset)) {
|
||
headerEndOffset -= endOffset - startOffset;
|
||
if ((GetHilitingStart (pte) < headerStartOffset) || (GetHilitingEnd (pte) > headerEndOffset))
|
||
NeedRefresh (pte);
|
||
}
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* HilitingRangeCheckInclude
|
||
************************************************************************/
|
||
void HilitingRangeCheckInclude (PETEHandle pte, long startOffset, long endOffset)
|
||
{
|
||
long headerStartOffset,
|
||
headerEndOffset;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
if (RefreshPending (pte))
|
||
return;
|
||
|
||
NeedHilite (pte);
|
||
|
||
if (GetHilitingStart (pte) == -1) {
|
||
SetHilitingStart (pte, startOffset);
|
||
SetHilitingEnd (pte, endOffset);
|
||
return;
|
||
}
|
||
|
||
if (startOffset < GetHilitingStart (pte))
|
||
SetHilitingStart (pte, startOffset);
|
||
if (endOffset > GetHilitingEnd (pte))
|
||
SetHilitingEnd (pte, endOffset);
|
||
|
||
if (GetNickFieldRange (pte, &headerStartOffset, &headerEndOffset))
|
||
if ((GetHilitingStart (pte) < headerStartOffset) || (GetHilitingEnd (pte) > headerEndOffset))
|
||
NeedRefresh (pte);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameHilitingKey
|
||
************************************************************************/
|
||
void NicknameHilitingKey (PETEHandle pte, EventRecord* event)
|
||
|
||
{
|
||
PETEDocInfo peteInfo;
|
||
UPtr curDelim;
|
||
long typeAheadStartOffset,
|
||
delimCount;
|
||
unsigned char keyChar;
|
||
unsigned char keyCode;
|
||
Boolean charIsDelimiter;
|
||
|
||
// Verify that this field can accept hiliting right now
|
||
if (!NicknameHilitingValid (pte))
|
||
return;
|
||
|
||
// Reset the tick mark for hiliting keystrokes
|
||
SetHilitingKeyTicks (pte, TickCount ());
|
||
|
||
// If a hiliting check or refresh is pending, bail.
|
||
if (HilitePending (pte) && RefreshPending (pte))
|
||
return;
|
||
|
||
// All command keys get passed on
|
||
if (event->modifiers & cmdKey)
|
||
return;
|
||
|
||
if (PETEGetDocInfo (PETE, pte, &peteInfo))
|
||
return;
|
||
|
||
keyChar = event->message & charCodeMask;
|
||
keyCode = (event->message & keyCodeMask) >> 8;
|
||
|
||
switch (keyChar) {
|
||
case leftArrow:
|
||
case rightArrow:
|
||
break;
|
||
case upArrowChar:
|
||
case downArrowChar:
|
||
if (CurSelectionIsTypeAheadText (pte)) {
|
||
if (GetTypeAheadPrevSelEnd (pte) == -1) {
|
||
NeedHilite (pte);
|
||
NeedRefresh (pte);
|
||
}
|
||
else {
|
||
typeAheadStartOffset = GetTypeAheadSelStart (pte) - gTypeAheadPrefix[0];
|
||
|
||
if (HilitePending (pte) && ((GetHilitingStart (pte) == typeAheadStartOffset) && (GetHilitingEnd (pte) == GetTypeAheadPrevSelEnd (pte))))
|
||
SetHilitingEnd (pte, GetTypeAheadSelEnd (pte));
|
||
else {
|
||
if (GetTypeAheadPrevSelEnd (pte) < GetTypeAheadSelEnd (pte))
|
||
HilitingRangeCheckInsert (pte, GetTypeAheadPrevSelEnd (pte), GetTypeAheadSelEnd (pte) - GetTypeAheadPrevSelEnd (pte));
|
||
else if (GetTypeAheadPrevSelEnd (pte) > GetTypeAheadSelEnd (pte))
|
||
HilitingRangeCheckDelete (pte, GetTypeAheadSelEnd (pte), GetTypeAheadPrevSelEnd (pte));
|
||
HilitingRangeCheckInclude (pte, typeAheadStartOffset, GetTypeAheadSelEnd (pte));
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case tabKey:
|
||
case returnChar:
|
||
case enterChar:
|
||
if (HilitePending (pte))
|
||
NeedRefresh (pte);
|
||
break;
|
||
case backSpace:
|
||
case clearKey:
|
||
case delChar:
|
||
if (peteInfo.selStart != peteInfo.selStop)
|
||
HilitingRangeCheckDelete (pte, peteInfo.selStart, peteInfo.selStop);
|
||
else if (keyChar == delChar)
|
||
HilitingRangeCheckDelete (pte, peteInfo.selStart, peteInfo.selStart + 1);
|
||
else
|
||
HilitingRangeCheckDelete (pte, peteInfo.selStart - 1, peteInfo.selStart);
|
||
break;
|
||
default:
|
||
if (CharIsTypingChar (keyChar, keyCode)) {
|
||
if (peteInfo.selStart != peteInfo.selStop)
|
||
HilitingRangeCheckDelete (pte, peteInfo.selStart, peteInfo.selStop);
|
||
|
||
delimCount = gRecipientDelimiterList[0];
|
||
curDelim = gRecipientDelimiterList + 1;
|
||
while (!charIsDelimiter && delimCount--)
|
||
if (keyChar == *curDelim)
|
||
charIsDelimiter = true;
|
||
else
|
||
curDelim++;
|
||
|
||
if (charIsDelimiter) {
|
||
HilitingRangeCheckInsert (pte, peteInfo.selStart, 1);
|
||
HilitingRangeCheckInclude (pte, peteInfo.selStart - 1, peteInfo.selStart + 2);
|
||
}
|
||
else
|
||
HilitingRangeCheckInsert (pte, peteInfo.selStart, 1);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameHilitingKeyFollowup
|
||
************************************************************************/
|
||
void NicknameHilitingKeyFollowup (PETEHandle pte)
|
||
{
|
||
Boolean origNicknameWatcherWaitKeyThresh;
|
||
|
||
if (!NicknameHilitingValid (pte))
|
||
return;
|
||
|
||
if (!gNicknameWatcherImmediate)
|
||
return;
|
||
|
||
origNicknameWatcherWaitKeyThresh = gNicknameWatcherWaitKeyThresh;
|
||
gNicknameWatcherWaitKeyThresh = false;
|
||
NicknameHilitingIdle (pte);
|
||
gNicknameWatcherWaitKeyThresh = origNicknameWatcherWaitKeyThresh;
|
||
}
|
||
|
||
|
||
//
|
||
// NicknameHilitingValid
|
||
//
|
||
// Verify that...
|
||
// -- we got a PETE
|
||
// -- that the setting for hiliting is set
|
||
// -- whether or not we're in a field that handles hiliting
|
||
|
||
Boolean NicknameHilitingValid (PETEHandle pte)
|
||
|
||
{
|
||
if (!PeteIsValid(pte))
|
||
return (false);
|
||
if (!gNicknameHilitingEnabled)
|
||
return (false);
|
||
if (!((*PeteExtra(pte))->nick.fieldCheck ? (*(*PeteExtra(pte))->nick.fieldCheck) (pte) : HasNickHiliting (pte)))
|
||
return (false);
|
||
return (true);
|
||
}
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameHilitingIdle
|
||
************************************************************************/
|
||
void NicknameHilitingIdle (PETEHandle pte)
|
||
|
||
{
|
||
EventRecord scratchEvent;
|
||
Handle textHdl;
|
||
long curTicks,
|
||
startOffset,
|
||
endOffset;
|
||
|
||
// Verify that this field can hilite right now
|
||
if (!NicknameHilitingValid (pte))
|
||
return;
|
||
|
||
// Has anything happened that would require a hilite check?
|
||
if (!HilitePending (pte))
|
||
return;
|
||
|
||
// Is it even time for a hilite check?
|
||
curTicks = TickCount ();
|
||
if (gNicknameWatcherWaitKeyThresh && (curTicks < (GetHilitingKeyTicks (pte) + gHilitingIdleDelay)))
|
||
return;
|
||
|
||
// But wait... What if the user is still typing?
|
||
if (gNicknameWatcherWaitNoKeyDown && EventAvail(keyDownMask, &scratchEvent))
|
||
return;
|
||
|
||
SetHilitingKeyTicks (pte, curTicks);
|
||
|
||
UseFeature (featureNicknameWatching);
|
||
|
||
// If a full field refresh is pending, update the field and reset the hiliting globals
|
||
if (RefreshPending (pte)) {
|
||
(void) NicknameHilitingUpdateField (pte);
|
||
ResetNicknameHiliting (pte); /* FINISH *//* where's the right place to call this? */
|
||
return;
|
||
}
|
||
|
||
// When a refresh is not pending we'll refresh only the highlighted recipient
|
||
if (PeteGetRawText (pte, &textHdl))
|
||
return;
|
||
|
||
if ((*PeteExtra(pte))->nick.fieldHilite)
|
||
(*(*PeteExtra(pte))->nick.fieldHilite) (pte, true);
|
||
else {
|
||
startOffset = FindRecipientStartInText (textHdl, GetHilitingStart (pte), true);
|
||
endOffset = FindRecipientEndInText (textHdl, GetHilitingEnd (pte), true);
|
||
(void) NicknameHilitingUpdateRange (pte, startOffset, endOffset);
|
||
}
|
||
|
||
// (void) NicknameHilitingUpdateRange (pte, startOffset, endOffset); /* ignore errors */
|
||
ResetNicknameHiliting (pte); /* FINISH *//* where's the right place to call this? */
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* InitNicknameHiliting
|
||
************************************************************************/
|
||
|
||
void InitNicknameHiliting (void)
|
||
|
||
{
|
||
PETELabelStyleEntry pls;
|
||
long styleCode;
|
||
|
||
Zero (pls);
|
||
|
||
// Label for nickname highlighting
|
||
pls.plValidBits = peColorValid | peFaceValid;
|
||
pls.plLabel = pNickHiliteLabel;
|
||
|
||
styleCode = GetRLong (NICK_STYLE);
|
||
if (styleCode & 0x00010000)
|
||
GetRColor (&pls.plColor, NICK_COLOR);
|
||
pls.plFace = (long) styleCode & (long) 0x00000000FF;
|
||
PETESetLabelStyle (PETE, nil, &pls);
|
||
|
||
ResetNicknameHiliting (nil);
|
||
}
|
||
|
||
|
||
#pragma mark ========== Nickname Watcher routines ==========
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameWatcherKey
|
||
************************************************************************/
|
||
Boolean NicknameWatcherKey (PETEHandle pte, EventRecord* event)
|
||
|
||
{
|
||
Boolean typeAheadResult;
|
||
|
||
if (!PeteIsValid(pte))
|
||
return (false);
|
||
|
||
typeAheadResult = NicknameTypeAheadKey (pte, event);
|
||
NicknameHilitingKey (pte, event);
|
||
return (typeAheadResult);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameWatcherKeyFollowup
|
||
************************************************************************/
|
||
void NicknameWatcherKeyFollowup (PETEHandle pte)
|
||
|
||
{
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
NicknameTypeAheadKeyFollowup (pte);
|
||
NicknameHilitingKeyFollowup (pte);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameWatcherIdle
|
||
************************************************************************/
|
||
void NicknameWatcherIdle (PETEHandle pte)
|
||
|
||
{
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
NicknameTypeAheadIdle (pte);
|
||
NicknameHilitingIdle (pte);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameWatcherFocusChange
|
||
************************************************************************/
|
||
void NicknameWatcherFocusChange (PETEHandle pte)
|
||
|
||
{
|
||
if (NicknameTypeAheadValid (pte))
|
||
NicknameTypeAheadFinish (pte);
|
||
|
||
if (NicknameHilitingValid (pte))
|
||
if (HilitePending (pte))
|
||
NeedRefresh (pte);
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameWatcherModifiedField
|
||
************************************************************************/
|
||
void NicknameWatcherModifiedField (PETEHandle pte)
|
||
|
||
{
|
||
if (NicknameHilitingValid (pte)) {
|
||
NeedHilite (pte);
|
||
NeedRefresh (pte);
|
||
}
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* NicknameWatcherMaybeModifiedField
|
||
************************************************************************/
|
||
void NicknameWatcherMaybeModifiedField (PETEHandle pte)
|
||
|
||
{
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
if (!((*PeteExtra(pte))->nick.fieldCheck ? (*(*PeteExtra(pte))->nick.fieldCheck) (pte) : HasNickScanCapability (pte)))
|
||
return;
|
||
|
||
if ((!gNicknameTypeAheadEnabled && (!gNicknameHilitingEnabled || !HasNickHiliting (pte))) || !HasNickCompletion (pte))
|
||
return;
|
||
|
||
NicknameWatcherModifiedField (pte);
|
||
}
|
||
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* InitNicknameWatcher
|
||
************************************************************************/
|
||
OSErr InitNicknameWatcher (void)
|
||
|
||
{
|
||
// Initialize the globals
|
||
gTypeAheadPrefix[0] = 0;
|
||
gTypeAheadSuffix[0] = 0;
|
||
|
||
gTypeAheadNicknameTable = nil;
|
||
gTypeAheadNicknameTableIndex = -1;
|
||
|
||
BlockMoveData("\p,:;", gRecipientDelimiterList, 4); /* FINISH *//* use resource */
|
||
gRecipientDelimiterList[++gRecipientDelimiterList[0]] = returnChar;
|
||
|
||
// Make sure we reset type ahead and hiliting
|
||
ResetNicknameTypeAhead (nil);
|
||
RefreshNicknameWatcherGlobals (false);
|
||
InitNicknameHiliting ();
|
||
return noErr;
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* SetDefaultNicknameWatcherPrefs
|
||
************************************************************************/
|
||
void SetDefaultNicknameWatcherPrefs(void)
|
||
|
||
{
|
||
/* we ignore errors here, since an error probably means something is really screwed up,
|
||
and failure just means the pref isn't set to its idea setting */
|
||
(void) SetPref(PREF_NICK_EXP_TYPE_AHEAD, YesStr);
|
||
(void) SetPref(PREF_NICK_HILITING, YesStr);
|
||
(void) SetPref(PREF_NICK_CACHE, NoStr);
|
||
(void) SetPref(PREF_NICK_WATCH_IMMED, YesStr);
|
||
(void) SetPref(PREF_NICK_WATCH_WAIT_KEYTHRESH, NoStr);
|
||
(void) SetPref(PREF_NICK_WATCH_WAIT_NO_KEYDOWN, YesStr);
|
||
}
|
||
|
||
#pragma mark ========== Nickname Cache routines ==========
|
||
|
||
|
||
Boolean NicknameCachingValid (PETEHandle pte)
|
||
|
||
{
|
||
if (!PeteIsValid(pte))
|
||
return (false);
|
||
if (!gNicknameCacheEnabled || !HasNickCaching (pte))
|
||
return (false);
|
||
return (true);
|
||
}
|
||
|
||
|
||
//
|
||
// NicknameCachingScan
|
||
//
|
||
// Compares a new cache of addresses against the existing cache.
|
||
// Differences become candidates for inclusion in the nickname cache.
|
||
//
|
||
|
||
void NicknameCachingScan (PETEHandle pte, Handle newAddresses)
|
||
|
||
{
|
||
PStr string,
|
||
end;
|
||
long len;
|
||
|
||
if (!NicknameCachingValid (pte))
|
||
return;
|
||
|
||
if (PrefIsSet (PREF_NICK_CACHE_NOT_ADD_TYPING))
|
||
return;
|
||
|
||
if (!(*PeteExtra(pte))->nick.addresses)
|
||
return;
|
||
|
||
string = LDRef (newAddresses);
|
||
LDRef ((*PeteExtra(pte))->nick.addresses);
|
||
end = string + GetHandleSize (newAddresses);
|
||
len = GetHandleSize ((*PeteExtra(pte))->nick.addresses);
|
||
while (string < end && *string) {
|
||
if (!FindPStr (string, *((*PeteExtra(pte))->nick.addresses), len))
|
||
CacheRecentNickname (string);
|
||
string += *string + 2;
|
||
}
|
||
UL ((*PeteExtra(pte))->nick.addresses);
|
||
UL (newAddresses);
|
||
}
|
||
|
||
//
|
||
// FindPStr
|
||
//
|
||
// Finds Pascal strings in a block of memory that itself MUST be an
|
||
// array of null-terminated Pascal strings (pretty specialized, huh).
|
||
//
|
||
|
||
UPtr FindPStr (PStr string, UPtr spot, Size len)
|
||
|
||
{
|
||
UPtr end;
|
||
|
||
end = spot + len;
|
||
while (spot < end) {
|
||
if (StringSame (string, spot))
|
||
return (spot);
|
||
spot += *spot + 2;
|
||
}
|
||
return (nil);
|
||
}
|
||
|
||
|
||
#pragma mark ========== Nickname refresh routines ==========
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/* FINISH *//* rename to watcher */
|
||
/************************************************************************
|
||
* RefreshNicknameWatcherGlobals - reset global variables for nickname
|
||
* expansion from preferences
|
||
************************************************************************/
|
||
|
||
void RefreshNicknameWatcherGlobals (Boolean refreshFields)
|
||
|
||
{
|
||
Boolean prevNicknameHilitingEnabled,
|
||
nicknameWatchingSupported;
|
||
|
||
nicknameWatchingSupported = HasFeature (featureNicknameWatching);
|
||
|
||
prevNicknameHilitingEnabled = gNicknameHilitingEnabled;
|
||
|
||
gNicknameTypeAheadEnabled = nicknameWatchingSupported && PrefIsSet (PREF_NICK_EXP_TYPE_AHEAD);
|
||
gNicknameWatcherImmediate = nicknameWatchingSupported && PrefIsSet (PREF_NICK_WATCH_IMMED);
|
||
gNicknameWatcherWaitKeyThresh = nicknameWatchingSupported && PrefIsSet (PREF_NICK_WATCH_WAIT_KEYTHRESH);
|
||
gNicknameWatcherWaitNoKeyDown = nicknameWatchingSupported && PrefIsSet (PREF_NICK_WATCH_WAIT_NO_KEYDOWN);
|
||
gNicknameHilitingEnabled = nicknameWatchingSupported && PrefIsSet (PREF_NICK_HILITING);
|
||
gNicknameAutoExpandEnabled = PrefIsSet (PREF_NICK_AUTO_EXPAND);
|
||
gNicknameCacheEnabled = nicknameWatchingSupported && !PrefIsSet (PREF_NICK_CACHE);
|
||
|
||
gTypeAheadIdleDelay = gNicknameWatcherWaitKeyThresh ? LMGetKeyThresh() : 0;
|
||
gHilitingIdleDelay = gNicknameWatcherWaitKeyThresh ? LMGetKeyThresh() : 0;
|
||
|
||
if (!gNicknameTypeAheadEnabled)
|
||
ResetNicknameTypeAhead (nil);
|
||
|
||
if (!gNicknameHilitingEnabled)
|
||
ResetNicknameHiliting (nil);
|
||
|
||
// Check to see if the hiliting has changed and refresh all fields if it has
|
||
if (refreshFields && gNicknameHilitingEnabled != prevNicknameHilitingEnabled)
|
||
if (gNicknameHilitingEnabled)
|
||
NicknameHilitingUpdateAllFields ();
|
||
else
|
||
NicknameHilitingUnHiliteAllFields ();
|
||
}
|
||
|
||
|
||
/* MJN *//* new routine */
|
||
/************************************************************************
|
||
* InvalCachedNicknameData
|
||
************************************************************************/
|
||
void InvalCachedNicknameData(void)
|
||
|
||
{
|
||
ResetNicknameTypeAhead (nil);
|
||
if (gNicknameHilitingEnabled)
|
||
NicknameHilitingUpdateAllFields (); /* ignore errors */
|
||
}
|
||
|
||
|
||
#pragma mark ========== Final comments ==========
|
||
|
||
|
||
/* MJN *//* FINISH *//* if hiliting is on, call refreshheader on activate for compwin */
|
||
/* MJN *//* FINISH *//* set final color and style for nickname hiliting */
|
||
|
||
//
|
||
// GetNickFieldRange
|
||
//
|
||
// Returns false if the current field (for example, in the case of a composisiton
|
||
// window header) cannot _really_ accept nickname stuff.
|
||
|
||
Boolean GetNickFieldRange (PETEHandle pte, long *start, long *end)
|
||
|
||
{
|
||
Handle textHdl;
|
||
long selStart,
|
||
selEnd;
|
||
|
||
*start = 0;
|
||
*end = 0;
|
||
|
||
if (!HasNickScanCapability (pte))
|
||
return (false);
|
||
|
||
if ((*PeteExtra(pte))->nick.fieldRange)
|
||
return ((*(*PeteExtra(pte))->nick.fieldRange) (pte, start, end));
|
||
else
|
||
if (!PeteGetTextAndSelection (pte, &textHdl, &selStart, &selEnd))
|
||
*end = GetHandleSize (textHdl);
|
||
return (true);
|
||
}
|
||
|
||
|
||
//
|
||
// SetNickScanning
|
||
//
|
||
// Call this routine to setup a PETE field to support various flavors of nickname scanning.
|
||
// nickHighlight : perform nickname highlighting in this field
|
||
// nickComplete : do nickname auto-completion in this field
|
||
// nickExpand : nickname expansion is supported by this field
|
||
// nickCache : nickname caching is supported by this field
|
||
//
|
||
|
||
void SetNickScanning (PETEHandle pte, NickScanType nickScan, short aliasIndex, NickFieldCheckProcPtr nickFieldCheck, NickFieldHiliteProcPtr nickFieldHilite, NickFieldRangeProcPtr nickFieldRange)
|
||
|
||
{
|
||
if (!PeteIsValid(pte))
|
||
return;
|
||
|
||
if (HasFeature (featureNicknameWatching)) {
|
||
(*PeteExtra(pte))->nick.scan = nickScan;
|
||
(*PeteExtra(pte))->nick.aliasIndex = aliasIndex;
|
||
(*PeteExtra(pte))->nick.fieldCheck = nickFieldCheck;
|
||
(*PeteExtra(pte))->nick.fieldHilite = nickFieldHilite;
|
||
(*PeteExtra(pte))->nick.fieldRange = nickFieldRange;
|
||
}
|
||
}
|
||
|
||
|
||
void SetNickCacheAddresses (PETEHandle pte, Handle addresses)
|
||
|
||
{
|
||
if (NicknameCachingValid (pte) && addresses) {
|
||
ZapHandle ((*PeteExtra(pte))->nick.addresses);
|
||
(*PeteExtra(pte))->nick.addresses = addresses;
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// CanWeExpand
|
||
//
|
||
// Checks to see whether or not we're currently able to expand a nickname.
|
||
// Takes into account the nick scanning attributes of the field, the
|
||
// nick scanning preferences, and modifier keys.
|
||
//
|
||
|
||
Boolean CanWeExpand (PETEHandle pte, EventRecord* event)
|
||
|
||
{
|
||
Boolean yesWeCan;
|
||
|
||
if (yesWeCan = HasNickExpansion (pte)) {
|
||
yesWeCan = gNicknameAutoExpandEnabled;
|
||
if (event)
|
||
if (event->modifiers & optionKey)
|
||
yesWeCan = !yesWeCan;
|
||
}
|
||
return (yesWeCan);
|
||
}
|
||
|
||
|
||
//
|
||
// CacheRecentNickname
|
||
//
|
||
// The loose logic...
|
||
//
|
||
// Check to see if this nickname is already in the cache file
|
||
// True: Update it's cache date and we're done
|
||
// False: Check to see if there is room in the cache
|
||
// True: Add this nickname
|
||
// False: Trim the cache to one less than the max
|
||
// Add this nickname
|
||
|
||
|
||
void CacheRecentNickname (PStr possibleNickname)
|
||
|
||
{
|
||
NickStructHandle aliases;
|
||
BinAddrHandle addresses,
|
||
shortAddress;
|
||
Handle notes;
|
||
Str255 firstName,
|
||
lastName;
|
||
Str63 suggestedNickname,
|
||
realName;
|
||
long hashName,
|
||
hashAddress,
|
||
index;
|
||
short foundAliasIndex,
|
||
foundNicknameIndex,
|
||
historyAB,
|
||
which;
|
||
Boolean wannaSave,
|
||
cacheWasDirty;;
|
||
|
||
if (!HasFeature (featureNicknameWatching))
|
||
return;
|
||
|
||
UseFeature (featureNicknameWatching);
|
||
|
||
historyAB = FindAddressBookType (historyAddressBook);
|
||
if (*possibleNickname && historyAB >= 0) {
|
||
cacheWasDirty = (*Aliases)[historyAB].dirty;
|
||
// First, determine the suggested nickname from the drivel we've been passed
|
||
addresses = nil;
|
||
if (!SuckPtrAddresses (&addresses, &possibleNickname[1], *possibleNickname, true, true, false, nil))
|
||
if (addresses) {
|
||
if (!PrefIsSet (PREF_CACHE_ATLESS_NICKS) && !PIndex (*addresses, '@'))
|
||
return;
|
||
NickSuggest (addresses, suggestedNickname, realName,false,HIST_NICK_FMT);
|
||
// Once we have a safe'n'sane nickname, go to work...
|
||
if (suggestedNickname[0]) {
|
||
// Check to see if the suggested nickname is already present in a nickname file besides the cache file
|
||
foundAliasIndex = -1;
|
||
// If we did NOT find the nickname in any address book, or if it was found in the history list... maybe cache it.
|
||
if (!NEFindNickname (suggestedNickname, false, true, true, &foundAliasIndex, &foundNicknameIndex) || IsHistoryAddressBook (foundAliasIndex))
|
||
// Check to see if this nickname is already in the cache file
|
||
if (aliases = (*Aliases)[historyAB].theData) {
|
||
hashName = NickHash (suggestedNickname);
|
||
wannaSave = false;
|
||
if ((index = NickMatchFound (aliases, hashName, suggestedNickname, historyAB)) >= 0) {
|
||
// We have to pull the addresses into memory, otherwise we think they've been purged since the nickname is dirty
|
||
if (GetNicknameData (historyAB, index, true, true)) {
|
||
// Update it's cache date since we found it hanging around the cache file
|
||
(*aliases)[index].cacheDate = LocalDateTime ();
|
||
(*aliases)[index].addressesDirty = true;
|
||
SetAliasDirty(historyAB);
|
||
wannaSave = true;
|
||
}
|
||
}
|
||
else {
|
||
shortAddress = nil;
|
||
// As a last check... see if the address is already present in a nickname file
|
||
// Or... if we're dealing with Adobe-esque bogus address books, try to find the
|
||
// shortAddress in the Nickname field.
|
||
if (!SuckPtrAddresses (&shortAddress, LDRef (addresses) + 1, **addresses, false, true, false, nil)) {
|
||
#ifdef DEBUG
|
||
short testWhich;
|
||
short testIndex;
|
||
NickExpFindNickFromAddr(LDRef(shortAddress),&testWhich,&testIndex);
|
||
#endif
|
||
hashAddress = NickHashString (LDRef (shortAddress));
|
||
index = -1;
|
||
for (which = 0; which < NAliases && index < 0; ++which) {
|
||
index = NickAddressMatchFound ((*Aliases)[which].theData, hashAddress, *shortAddress, which);
|
||
if ((*Aliases)[which].containsBogusNicks && index == -1)
|
||
index = NickMatchFound ((*Aliases)[which].theData, hashAddress, *shortAddress, which);
|
||
}
|
||
|
||
ASSERT(testIndex==index);
|
||
UL (shortAddress);
|
||
|
||
if (index == -1) {
|
||
// Trim any excess off of the end of the cache
|
||
TrimNicknameCache (aliases, 1);
|
||
// Add this nickname
|
||
// (jp) 4/12/00 This is NOT the most elegant solution to bug 3041 (save changes alert when nothing in
|
||
// the address book has changed except history). I tried fixing this the right way by
|
||
// passing 'true' for doSave, which of course did nothing. I tried a couple of other
|
||
// things as well to correctly save changes inside of AddNickname, but no matter what
|
||
// you do the various dirty bits for nicknames, address books or the address book window
|
||
// are out of sync, I think due to a bug in SaveCurrentAlias -- but I'm a little scared
|
||
// make sweeping changes (or even subtle changes) in there.
|
||
//
|
||
// Instead, I'm taking the coward's way out and I'm simulating a manual address book save
|
||
// as long as the address book is open and the nickname was successfully added.
|
||
ParseFirstLast (realName, firstName, lastName);
|
||
notes = CreateSimpleNotes (realName, firstName, lastName);
|
||
if (NewNickLow (shortAddress, notes, historyAB, suggestedNickname, false, nrAdd, false) >= 0) {
|
||
if (!(*Aliases)[historyAB].collapsed)
|
||
ABTickleHardEnoughToMakeYouPuke ();
|
||
SetAliasDirty(historyAB);
|
||
wannaSave = true;
|
||
}
|
||
}
|
||
}
|
||
ZapHandle (shortAddress);
|
||
}
|
||
if (wannaSave && !cacheWasDirty)
|
||
if (SaveIndNickFile (historyAB, true))
|
||
ABClean ();
|
||
}
|
||
}
|
||
ZapHandle (addresses);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/**********************************************************************
|
||
* NickExpFindNickFromAddr - given an address, find the nickname it belongs
|
||
* to, if possible
|
||
**********************************************************************/
|
||
OSErr NickExpFindNickFromAddr(PStr addrStr,short *outWhich,short *outIndex)
|
||
{
|
||
uLong hashAddress;
|
||
short index = -1;
|
||
short which;
|
||
|
||
hashAddress = NickHashString(addrStr);
|
||
index = -1;
|
||
for (which = 0; which < NAliases; ++which) {
|
||
index = NickAddressMatchFound ((*Aliases)[which].theData, hashAddress, addrStr, which);
|
||
if ((*Aliases)[which].containsBogusNicks && index == -1)
|
||
index = NickMatchFound ((*Aliases)[which].theData, hashAddress, addrStr, which);
|
||
if (index>=0) break;
|
||
}
|
||
|
||
*outWhich = which;
|
||
*outIndex = index;
|
||
return index==-1 ? fnfErr : noErr;
|
||
}
|
||
|
||
void TrimNicknameCache (NickStructHandle aliases, short spaceNeeded)
|
||
|
||
{
|
||
long cacheSize,
|
||
threshold,
|
||
index;
|
||
short historyAB;
|
||
|
||
if (!HasFeature (featureNicknameWatching))
|
||
return;
|
||
|
||
historyAB = FindAddressBookType (historyAddressBook);
|
||
cacheSize = HandleCount (aliases);
|
||
threshold = GetRLong (NICK_CACHE_SIZE) - spaceNeeded;
|
||
|
||
while (cacheSize-- > threshold) {
|
||
index = FindOldestCacheMember (aliases);
|
||
(*aliases)[index].addressesDirty = true;
|
||
(*aliases)[index].deleted = true;
|
||
RemoveNickFromAddressBookList (historyAB, index, false);
|
||
SetAliasDirty(historyAB);
|
||
}
|
||
}
|
||
|
||
short FindOldestCacheMember (NickStructHandle aliases)
|
||
|
||
{
|
||
register NickStructPtr nickPtr;
|
||
register uLong oldestTimestamp;
|
||
register short oldestTimeStampIndex,
|
||
index,
|
||
numAliases;
|
||
|
||
oldestTimestamp = 0xFFFFFFFF; // Way of in the future...
|
||
oldestTimeStampIndex = 0;
|
||
|
||
numAliases = HandleCount (aliases);
|
||
nickPtr = *aliases;
|
||
for (index = 0; index < numAliases; ++index, ++nickPtr)
|
||
if (!nickPtr->deleted && nickPtr->cacheDate < oldestTimestamp) {
|
||
oldestTimestamp = nickPtr->cacheDate;
|
||
oldestTimeStampIndex = index;
|
||
}
|
||
return (oldestTimeStampIndex);
|
||
}
|
||
|
||
|
||
//
|
||
// CompareRawToExpansion
|
||
//
|
||
// Compares a raw address string (Pascal-style, stored in a handle, as you might
|
||
// get back from SuckPtrAddresses) to a Handle of a text that has not yet been
|
||
// massaged for extra spaces.
|
||
//
|
||
// Keep your fingers crossed...
|
||
//
|
||
|
||
Boolean CompareRawToExpansion (StringHandle raw, Handle expansion)
|
||
|
||
{
|
||
Str255 temp;
|
||
Size expSize;
|
||
Boolean same;
|
||
|
||
same = false;
|
||
if (raw && expansion) {
|
||
expSize = GetHandleSize_ (expansion);
|
||
if (**raw <= expSize) {
|
||
MakePStr (temp, *expansion, GetHandleSize_ (expansion));
|
||
if ((*temp = RemoveChar (' ', temp + 1, *temp)) == **raw) {
|
||
same = StringSame (temp, LDRef (raw));
|
||
UL (raw);
|
||
}
|
||
}
|
||
}
|
||
return (same);
|
||
}
|
||
|
||
/************************************************************************
|
||
* AppearsInAliasFile - does a nickname appear in an address book?
|
||
************************************************************************/
|
||
Boolean AppearsInAliasFile(PStr address,PStr file)
|
||
{
|
||
Str255 shortAddr;
|
||
uLong addrHash;
|
||
|
||
ShortAddr(shortAddr,address);
|
||
MyLowerStr(shortAddr);
|
||
addrHash = Hash(shortAddr);
|
||
|
||
return HashAppearsInAliasFile(addrHash,file);
|
||
}
|
||
|
||
Boolean HashAppearsInAliasFile(uLong addrHash,PStr file)
|
||
{
|
||
short which;
|
||
FSSpec spec;
|
||
|
||
for (which=NAliases;which;which--)
|
||
{
|
||
spec = (*Aliases)[which-1].spec;
|
||
if (file==nil || StringSame(file,spec.name))
|
||
if (AppearsInWhichAliasFile(addrHash,which-1)) return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/************************************************************************
|
||
* AppearsInWhichAliasFile - does a nickname appear in an address book, but hashed and found
|
||
************************************************************************/
|
||
Boolean AppearsInWhichAliasFile(uLong addrHash,short which)
|
||
{
|
||
uLong *hash, *end;
|
||
|
||
if (!(*Aliases)[which].addressHashes.data) BuildAddressHashes(which);
|
||
if (!(*Aliases)[which].addressHashes.data) return false;
|
||
|
||
hash = (uLong*)(*(*Aliases)[which].addressHashes.data);
|
||
end = (uLong*)(*(*Aliases)[which].addressHashes.data + (*Aliases)[which].addressHashes.offset);
|
||
|
||
for (;hash<end;hash++)
|
||
if (*hash==addrHash) return true;
|
||
|
||
return false;
|
||
}
|