-- FILTER INPUT CHARACTERS -- -- -- Last revised 27 January 2004, James Newton -- -- NOTES FOR DEVELOPERS -- -- DISTINGUISHING SIMILAR CHARACTERS -- Try this experiment in the Message window: -- -- put "a" = "A" -- -- 1 -- -- When Director compares two letters directly, it does so in a case- -- insensitive fashion: an uppercase letter is considered identical to -- its lowercase equivalent. So how do you distinguish between "a" -- and "A"? This behavior uses three different techniques: character -- comparison, ASCII code numbers and the case sensitive getPos() -- function. -- Examples of each: -- -- put "a" < "A" or "a" > "A" -- TRUE if characters are different -- -- 1 -- -- put charToNum("a") = charToNum("A") -- FALSE if different -- -- 0 -- -- put["a"].getPos("A") -- Zero if string is not found in the list -- -- 0 -- -- Here, I favour the list technique, because it also allows me to -- handle accented characters elegantly. -- -- Alex Zavatone (Zav) discovered a bug with getPos() on Macintosh in -- Director 8.5.1 and prior versions which gives a false positive for -- [numToChar(245)].getPos("i"). However, this does not affect the -- behavior. -- -- CHANGING CASE -- It is fairly simple to change the case of standard, unaccented -- letters: you simply convert to ASCII with charToNum(), then -- add/subtract 32. (See the mChangeCase function). Accented letters -- are not treated the same way on Macintosh and on PCs... and they -- are not neatly placed at regular distances either. I have opted -- for creating two lists of accented characters, each with the same -- accented-letter order: one in uppercase and the other in lower -- case. I use the case-sensitive getPos() function to find the -- accented letter in one list, and the getAt() function to return -- the equivalent letter in the other list. -- UNPRINTABLE CHARACTERS -- Certain keys don't type letters on the screen: TAB and BACKSPACE, -- RETURN and ENTER, "delete" and the arrow keys. These last five -- don't even have a Director constant (DELETE, UP, ...) with which to -- identify them, so I have had to test for them with the keycode. My -- behavior lets you use all these keys... but it makes a special case -- for RETURN and ENTER, in case you wish to trap them for some other -- business. -- HISTORY -- -- 14 Oct 1998: written for the D7 Behaviors Palette by James Newton -- 16 Nov 1998: ErrorAlert modified, Filter and Insert handlers -- separated -- 11 Jan 2000: added isOKToAttach. Removed unnecessary error -- checking. -- 1 Feb 2000: chris walcott - modified pUPPERCASEAccents and -- pLowercaseAccents tables to use numToChar functions. -- 11 Feb 2002: JN - corrected pUPPERCASEAccents and -- pLowercaseAccents for Macintosh, corrected -- character insertion, updated variable and handler -- names, added handler comments -- 27 Jan 2004: JN separated mCreateAccentLists() from mInitialize() -- and added new() and ForceCase() methods so that the -- behavior can also be used as an instance for -- converting strings. -- PROPERTIES -- property spriteNum -- author-defined parameters property allowedCharacters property expectedCase property changeCase property allowRETURN property beepOnError -- private properties property pLowercaseAccents property pUPPERCASEAccents property pMember property pMemberType -- EVENT HANDLERS -- on new(me) ----------------------------------------------------------- -- ACTION: Initializes pLowercaseAccents and pUPPERCASEAccents -------------------------------------------------------------------- me.mCreateAccentLists() return me end new on beginSprite(me) --------------------------------------------------- -- ACTION: Initializes pMember, pMemberType and adjusts -- allowedCharacters -------------------------------------------------------------------- me.mInitialize() end beginSprite on keyDown(me) ------------------------------------------------------- -- ACTION: Checks if the entered character is allowed, and converts -- its case if necessary -------------------------------------------------------------------- me.mFilter() end keyDown -- PUBLIC METHOD -- on ForceCase(me, aString, aCase) ------------------------------------- -- INPUT: should be a string -- may be #upper or #lower. Any other value will -- invert the current case of each character. -- OUTPUT: Returns a string with the case converted as required. -------------------------------------------------------------------- tString = "" if stringP(aString) then i = the number of chars in aString repeat while i tChar = me.mChangeCase(char 1 of aString, aCase) delete char 1 of aString put tChar after tString i = i - 1 end repeat end if return tString end ForceCase -- CUSTOM HANDLERS -- on mCreateAccentLists(me) -------------------------------------------- -- SENT BY new() -- ACTION: Creates a list of uppercase and lowercase accented -- characters and a list of all allowed characters, in -- numerical format. The lists are first created as WYSIWYG, -- then converted to charNums. -------------------------------------------------------------------- if the platform contains "Macintosh" then pUPPERCASEAccents = ["Á", "À", numToChar(229), "Ä", "Ã", "Å", "Ç", "É", "È", "Ê", "Ë", "Í", "Ì", "Î", "Ï", "Ñ", "Ó", "Ò", "Ô", "Ö", "Õ", "Ú", "Ù", "Û", "Ü", "Æ", "Ø", "Œ", "Ÿ"] else -- On Windows, in versions of Director prior to 8.5, "Â" works -- as a continuation character, just like "¬" pUPPERCASEAccents = ["Á", "À", numToChar(194), "Ä", "Ã", "Å", "Ç", "É", "È", "Ê", "Ë", "Í", "Ì", "Î", "Ï", "Ñ", "Ó", "Ò", "Ô", "Ö", "Õ", "Ú", "Ù", "Û", "Ü", "Æ", "Ø", "Œ", "Ÿ"] end if pLowercaseAccents = ["á", "à", "â", "ä", "ã", "å", "ç", "é", "è", "ê", "ë", "í", "ì", "î", "ï", "ñ", "ó", "ò", "ô", "ö", "õ", "ú", "ù", "û", "ü", "æ", "ø", "œ", "ÿ"] -- Convert these accented characters to their associated charNums i = pUPPERCASEAccents.count repeat while i pUPPERCASEAccents[i] = charToNum(pUPPERCASEAccents[i]) i = i - 1 end repeat i = pLowercaseAccents.count repeat while i pLowercaseAccents[i] = charToNum(pLowercaseAccents[i]) i = i - 1 end repeat end mCreateAccentLists on mInitialize(me) --------------------------------------------------- -- SENT BY beginSprite() -- ACTION: creates a list of uppercase and lowercase accented -- characters and a list of all allowed characters -------------------------------------------------------------------- pMember = sprite(me.spriteNum).member pMemberType = pMember.type pMember.editable = TRUE if allowRETURN then put RETURN&ENTER after allowedCharacters end if allowedCharacters = me.mGetCharacterList(allowedCharacters) end mInitialize on mFilter(me) ------------------------------------------------------- -- SENT BY keyDown() -- ACTION: lets allowed characters through, converts accepted -- characters to the correct case and blocks all other -- input -------------------------------------------------------------------- if [48,51,117,115,116,119,121,123,124,125,126].getPos(the keyCode) then -- TAB, backspace, delete, page keys, arrow keys pass end if tKey = the key if allowedCharacters.getPos(tKey) then -- The character is allowed pass else if changeCase then -- Try to use the alternative case tKey = me.mChangeCase(tKey) if allowedCharacters.getPos(tKey) then me.mInsert(tKey) exit end if end if -- If we get here, input was refused if beepOnError then beep end if end mFilter on mInsert(me, aKey) ----------------------------------------------- -- SENT BY mFilter() -- INPUT: is an acceptable character -- ACTION: places at the appropriate place in the member -------------------------------------------------------------------- -- Find where the insertion point or selection is if pMemberType = #field then tStartChar = the selStart tEndChar = the selEnd else tStartChar = pMember.selection[1] tEndChar = pMember.selection[2] end if -- Place aKey at the insertion point or in place of the selection tText = pMember.text if tStartChar = tEndChar then put aKey after char tStartChar of tText else put aKey into char tStartChar + 1 to tEndChar of tText end if -- Update the member pMember.text = tText -- Place the insertion point just after the inserted character if pMemberType = #field then the selEnd = tStartChar + 1 the selStart = tStartChar + 1 else pMember.selection = [tStartChar + 1, tStartChar + 1] end if end mInsert on mGetCharacterList(me, aString) ------------------------------------ -- SENT BY mInitialize() -- INPUT: is a string selection of acceptable characters -- OUTPUT: a case-sensitive list of all allowed characters -------------------------------------------------------------------- tCharacterList = [] tCharCount = aString.char.count repeat with i = 1 to tCharCount tChar = aString.char[1] delete aString.char[1] if tCharacterList.getPos(tChar) then next repeat end if case expectedCase of "UPPERCASE and lowercase": -- Ensure that both upper and lower case are included tOtherCase = me.mChangeCase(tChar) if tOtherCase < tChar or tOtherCase > tChar then -- Add the alternative case too if not tCharacterList.getPos(tOtherCase) then tCharacterList.append(tOtherCase) end if end if "UPPERCASE ONLY": tChar = me.mChangeCase(tChar, #upper) "lowercase only": tChar = me.mChangeCase(tChar, #lower) otherwise end case tCharacterList.append(tChar) end repeat return tCharacterList end mGetCharacterList on mChangeCase(me, aChar, lowerOrUPPER) ------------------------------ -- SENT BY mFilter(), mGetCharacterList() -- INPUT: is a single character string -- is #lower or #upper or VOID, which means -- toggle to the other case -- OUTPUT: the equivalent character in the appropriate case -------------------------------------------------------------------- tCharNum = charToNum(aChar) case (lowerOrUPPER) of #lower, #upper: -- continue otherwise -- Convert to the other case if pUPPERCASEAccents.getPos(tCharNum) then -- Uppercase accent lowerOrUPPER = #lower else if tCharNum < 91 then -- Not lowercase ascii character (may be punctuation or digit) lowerOrUPPER = #lower else lowerOrUPPER = #upper end if end case case (lowerOrUPPER) of #upper: -- Convert to UPPERCASE if necessary tAccent = pLowercaseAccents.getPos(tCharNum) if tAccent then tCharNum = pUPPERCASEAccents.getAt(tAccent) aChar = numToChar(tCharNum) else if tCharNum > 96 and tCharNum < 123 then aChar = numToChar(tCharNum - 32) end if #lower: -- Convert to lower case if necessary tAccent = pUPPERCASEAccents.getPos(tCharNum) if tAccent then tCharNum = pLowercaseAccents.getAt(tAccent) aChar = numToChar(tCharNum) else if tCharNum > 64 and tCharNum < 91 then aChar = numToChar(tCharNum + 32) end if end case return aChar end mChangeCase -- BEHAVIOR DESCRIPTION AND PARAMETERS -- on isOKToAttach(me, aSpriteType, aSpriteNum) if aSpriteType = #graphic then case sprite(aSpriteNum).member.type of #field, #text: return TRUE end case end if return FALSE end isOKToAttach on getPropertyDescriptionList(me) tPropertyList = [:] tPropertyList[ \ #allowedCharacters] = [ \ #comment: "Accepted characters", \ #format: #string, \ #default: "abcdefghijklmnopqrstuvwxyz 0123456789" \ ] tPropertyList[ \ #expectedCase] = [ \ #comment: "", \ #format: #string, \ #range: [ \ "UPPERCASE and lowercase", \ "UPPERCASE ONLY", \ "lowercase only", \ "case as entered above" \ ], \ #default: "UPPERCASE and lowercase" \ ] tPropertyList[ \ #changeCase] = [ \ #comment:"Correct case automatically?", \ #format: #boolean, \ #default: TRUE \ ] tPropertyList[ \ #allowRETURN] = [ \ #comment: "Allow RETURN and ENTER characters?", \ #format: #boolean, \ #default: TRUE \ ] tPropertyList[ \ #beepOnError] = [ \ #comment:"Beep if input character is invalid?", \ #format: #boolean, \ #default: TRUE \ ] return tPropertyList end getPropertyDescriptionList on getBehaviorDescription(me) return \ "FILTER INPUT CHARACTERS"&RETURN&RETURN&\ "Enter the appropriate characters in the Behavior Parameters dialog (Don't forget to take the space character into account)."&\ RETURN&RETURN&\ "This behavior can also be used to force the case of input characters. It can handle all standard accents... if the font you are using can display them."&RETURN&RETURN&\ "The option to 'correct case automatically' can be used to override the Caps Lock."&RETURN&RETURN&\ "You can choose to convert letters automatically to either upper or lower case, or you can accept a mix of upper and lower case characters and limit input to those particular letters in that particular case (select the 'case as entered' option)."&RETURN&RETURN&\ "PERMITTED MEMBER TYPES:"&RETURN&\ "Field and Text members"&RETURN&RETURN&\ "PARAMETERS:"&RETURN&\ "* Accepted characters"&RETURN&\ "* Correct case automatically? (TRUE | FALSE)"&RETURN&\ "* Allow RETURN and ENTER characters? (TRUE | FALSE)"&RETURN&\ "* Beep if input character is invalid? (TRUE | FALSE)" end getBehaviorDescription on getBehaviorTooltip(me) return \ "Use with Field and Text members."&RETURN&RETURN&\ "Limit the characters that the user"&RETURN&\ "can type into a Text or Field member."&RETURN&\ "Invalid characters do not appear"&RETURN&\ "(a system beep may be provoked instead)."&RETURN&\ "Option: automatically convert accepted"&RETURN&\ "characters to the chosen case." end getBehaviorTooltip