We make software for humans. Custom Mac, Windows, iOS and Android solutions in HyperCard, MetaCard, and RunRev LiveCode
HC Tips and Tricks
A simple function can be defined and executed programmatically using a specially reserved object to store the handler. This is similar to the lambda declaration used in Lisp. Example Implementation: (1) Create a background button for storing the in-the-fly function handler. (2) Define a command-that-defines-the-function. The first line of the argument to the defining command is a comma-delimited list of data parameters (or empty) and the rest of the lines are the expressions to evaluate. The last line is the expression to evaluate and return. The function-that-defines-the-function should insert "function" and a generic name before the parameter list, a global variable definition for storing the final result, and append the intermediate forms, a final assignment statement for storing the value of the last form, and a closing "end " and the generic function name. (3) Define a generic calling function name that references the function stored in the button created in Step (1). This function should call the stored function and retrieve the result from the global variable. (4) Define and store the function using the defining-function from Step (2) and call the stored function with the generic named function in Step (3). Example implementation: Create bkgnd btn "*LambdaFunction*" on defLambda paramsAndForms global LambdaResult if (there is a bkgnd btn "*LambdaFunction*") then put (line 1 of paramsAndForms) into paramNames put (line 2 to (the number of lines in paramsAndForms - 1) ¬
of paramsAndForms) into forms set the script of bkgnd btn "*LambdaFunction*" to ¬ "on lambda"&¶mNames&RETURN&"global LambdaResult"&RETURN ¬ &forms&RETURN ¬ &"put"&&(last line of paramsAndForms)&&"into LambdaResult"&RETURN ¬ &"end lambda" end if put EMPTY into LambdaResult end defLambda function lambda global LambdaResult delete last char of paramList if (there is a bkgnd btn "*LambdaFunction*") then put "lambda " into lambdaMsg repeat with i = 1 to (the paramCount) put param(i)&"," after lambdaMsg end repeat send lambdaMsg to bkgnd btn "*LambdaFunction*" put LambdaResult into temp put EMPTY into LambdaResult return temp else return "*ERROR*" end if end lambda defLambda " "&RETURN&"This is a test" put lambda() ==> This is a test.
When you trap the "idle" system message, your normally editable fields don't work anymore. Here is a way around it. In the stack script, enter this: on openfield global KillIdleProcess put true into KillIdleProcess end openfield on closefield global KillIdleProcess put false into KillIdleProcess end closefield on exitfield global KillIdleProcess put false into KillIdleProcess end exitfield At the beginning of your idle handler, put this: on idle global KillIdleProcess if KillIdleProcess is empty then put false into KillIdleProcess if KillIdleProcess then exit idle -- continue with your script end idle These scripts will set a flag when you click in a field. When the flag is set, the idle handler is exited so there is no interaction with fields to deselect the text you're editing. When you are finished editing, the flag is reset to again let the idle handler do it's work.
This function returns the position of the current card in its background, i.e. that this is the 5th card of this background. Note that in a multi-background stack, the 5th card of a bkgnd may be the 20th card of the stack. When on the 20th card, this returns 5. Examining all the cards in a repeat loop is very slow, especially if we are on the last card of a very large background. So instead we do a binary search, "halving" the remaining cards each time we examine a card and fail. We always examine the "middle" card of the remaining cards. After just a few failed attempts, we will match the current card. function getBgCardNum -- what card number are we searching for? put the number of this card into thisCard -- We start our search at the middle card of the bg put (number of cds of this bg div 2) into nthBgCard -- adjust that if this bg has just 1 cd (we can't examine card zero) if nthBgCard = 0 then put 1 into nthBgCard -- set our "halving" variable, used later to halve the cards put nthBgCard into halfTheCards repeat -- runs until the card number of the "middle" bg card -- matches thisCard put the number of card nthBgCard of this bg into cardNum if cardNum = thisCard then return nthBgCard else -- not a match -- adjust the "middle" card of the remaining cards up or down -- by the current size of the remaining cards put halfTheCards div 2 into halfTheCards if halfTheCards = 0 then put 1 into halfTheCards if cardNum > thisCard then subtract halfTheCards from nthBgCard else add halfTheCards to nthBgCard end if end if end repeat end getBgCardNum
I've found it useful to be able to express numbers as ordinals when dealing with dates especially. This simple function takes a number, such as 12, 22, or 3, and returns it with the right ordinal suffix -- say, 12th, 22nd, or 3rd. function ordinals theNr put "st,nd,rd" into tags put the length of theNr into y repeat with z = 1 to 3 if (char y of theNr = z) and (char y - 1 of theNr <> "1") then put item z of tags after theNr exit repeat end if end repeat if theNr is a number then put "th" after theNr return theNr end ordinals
HyperCard's native text-handling routines are not well-suited to the needs of users outside the United States, thanks to the fact that many of these routines ignore the diacritical marks (accents grave, circumflex; cedilla; etc etc) found in a variety of languages other than English. In addition, many of these routines are not case-sensitive (i.e. cannot tell lowercase "a" from uppercase "A"), a quality which is not always desirable even in the 'States. Fortunately, HyperCard isn't *completely* blind to diacriticals and case... and those few text-handling routines which do heed diacriticals et al, the "greater than" and "less than" string comparison functions in particular, can be put to excellent use along this line. The handler immediately following is a case-sensitive, diacritical-sensitive offset function which illustrates how to go about such things in pure HyperTalk. function IntlOffset TargetStr,SearchText put the length of TargetStr - 1 into StrLength put offset (TargetStr,SearchText) into TheOffset repeat until theOffset = 0 put char TheOffset to (TheOffset + StrLength) of SearchText into TempStr if not ((TargetStr < TempStr) or (TargetStr > TempStr)) then exit repeat put numToChar(8) into char TheOffset of SearchText -- untypeable char put offset (TargetStr,SearchText) into TheOffset end repeat return TheOffset end IntlOffset
If you test for a number of flags being empty or not empty, e.g.: if who is not empty or what is not empty or when is not empty then exit handler it's shorter and possibly quicker to concatenate them: if who & what & when is not empty then exit handler
This loads or clears the message box without showing it: on blindMsg txt get blindTyping set blindTyping to true if txt = empty then type numtochar (127) -- delete it else type txt -- replace it set blindTyping to it end blindMsg Call it like this: blindMsg "secret msg" -- put that into msg w/o showing it blindMsg -- empty the msg w/o showing it
To prevent card(s) from being deleted, use the following at the appropriate level, e.g., in the card script for one card or in the bg script for all cards: on deletecard go prev cd end deletecard You will in fact get a glimpse of the previous card.
This appears to be a bug in HyperCard. It may not work in future versions. -- jg
When setting the location of something, this format: set the loc of cd btn "Moving Button" to 256,171 will run a lot faster than this in repeat scripts: set the loc of cd btn "Moving Button" to "256,171"
As of 2.4, the word "object" is a reserved word. There is a case where using the word "object" doesn't cause a problem: on test object put the id of object end test where "object" contains the name of a valid HyperCard object, like "card button 1". As it was explained to me, because the word "object" is the last word in the line, the compiler doesn't try to use it as a reserved word. This next script, however, will fail to compile and you'll get a "Can't understand..." error. on test object put the id of object into foo end test
White Feather Software
The "set" command cannot be nested inside any other "set" commands. The following handler, which calls the "set" command three times to accomplish a single evaluation, will fail: on mouseUp set the locktext of cd fld 1 to (extractItem(2) is empty) end mouseUp function extractItem theItemNum put "one°°three°four°five°six" into data put the itemDelimiter into oldDelim set the itemDelimiter to "°" -- here is the second "set" call put item theItemNum of data into theText set the itemDelimiter to oldDelim -- here is another return theText end extractItem The above routine must be written without nesting in order to work: on mouseUp get extractItem(2) set the locktext of cd fld 1 to (it is empty) end mouseUp
Jacqueline Landman Gay
To randomly sort a list in a field: sort lines of cd fld 1 by random(the number of lines of cd fld 1)
University of New South Wales
You can also apply this to lists in variables, or to items in a line. -- jg
The PowerTools SEARCH XFCN does not provide a method for returning chunk expressions for strings found in variables which are relative to the entire text being searched. This can be accomplished by using the FIND function, a scratch background field, and the foundChunk function. Unlike offset, find allows multiple ways of isolating strings of characters. Assign the text to the scratch field and do a field-specific FIND for the string.
Boeing - Knowledge-Based Product Definition
Tony Root, formerly of HyperPro, came up with this idea which handles the case where you've got many occurrences of two choices and it is inconvenient (scripts are already large) to use if/else statements. Here's one example of using the whichOne function: on mouseUp answer "Do you want Red or Blue?" with "Red" or "Blue" answer whichOne(it,"Red","Get the red balloon.","We don't have any blue balloons.") end mouseUp function whichOne var,test,str1,str2 return item offset(char 1 of (var = test),"tf") of (str1 &","& str2) end whichOne It is not necessary, of course, to return a choice of strings. You could use this function to select one of two different fields, or click on one of two different buttons, go to one of two different cards, etc.
White Feather Software
Repeat loops are a good way to deal with a list of items one at a time (such as "if item j of theNumbers < 3 then delete item j of theNumbers"). But the example I just used also illustrates an error that novices often encounter. If you are removing things from a list as you process them, you will skip some items. Consider what happens when j = 2 and item 2 is removed. Item 3 has now become item 2. The next item to be tested will be the NEW item 3, which was originally item 4. The original item 3 is never examined... Fortunately the solution to this is simple. Examine your list backwards! Instead of "repeat with j = 1 to 10" use "repeat with j = 10 down to 1". This way items are only deleted "behind" the repeat, not "ahead" of it.
If you need to access a menu item but the userlevel is set too low, send the "doMenu" message directly to HyperCard. For example: send "doMenu Delete Card" to HyperCard Sending the doMenu message bypasses any userlevel limit and the necessary presence of the "Delete Card" menu item.
When removing blank lines from a text list, it is customary to check each line within a repeat loop to see if the line is empty, and if so, delete it. It is much faster to use "offset" to check for double returns and delete the second return in each iteration. function noBlankLines theList -- removes blank lines repeat get offset(return & return,theList) if it = 0 then exit repeat delete char it+1 of theList end repeat return theList end noBlankLines
Jacqueline Landman Gay
This: not (there is a stack stackname) is faster than: there is not a stack stackname
The HyperCard function "the stacks" returns a list of all the currently open stacks, one per line. The frontmost stack (i.e., the current stack window) is always the top line of the list. Therefore, the fastest way to retrieve the path to the current stack is: get line 1 of the stacks This gives you a clean path name, without the need to parse out the word "stack", remove quotes, or anything else.
Jacqueline Landman Gay
This great tip was brought to our attention by Elaine Ung
at Apple Computer, Inc. -- jg
If you need to set a variable to true or false based on a certain condition, you can avoid this more cumbersome structure: if the optionKey is down then put true into var else put false into var by concatenating the test into one line like this: put (the optionKey is down) into var This technique works with any condition that evaluates to true or false.
Jacqueline Landman Gay
I've seen a number of stacks that include code of this general sort: on WhateverMsg put the random of 4 into Fred if Fred = 1 then put "Zelda" into George if Fred = 2 then put "Henrietta" into George if Fred = 3 then put "Xanthippe" into George if Fred = 4 then put "Alice" into George end WhateverMsg This is an attempt to re-create the CASE statement within HyperTalk. It works, but there is a better way: on WhateverMsg put the random of 4 into Fred put item Fred of "Zelda,Henrietta,Xanthippe,Alice" into George end WhateverMsg And if you want to do it in *one* line, here's how: on WhateverMsg put item (the random of 4) of "Zelda,Henrietta,Xanthippe,Alice" into George end WhateverMsg
To restrict searches to a single card in a stack, unmark all cards and then mark only the card (or cards) to which you want to restrict the search: find "theword" of marked cds works, as long as the stack is not locked or on read-only media (you can't mark cards in a locked stack.)
Jacqueline Landman Gay
A less obvious, but simpler and faster way to do case-sensitive comparisons in HyperTalk: function CaseSensEquals theString1, theString2 if theString1 < theString2 then return false else if theString1 > theString2 then return false else return true end if end CaseSensEquals Using this function: CaseSensEquals("hiroko","Hiroko") would return false, but: CaseSensEquals("Hiroko","Hiroko") would return true. This works in HyperTalk because the equals operator is case-insensitive, while the greater-than and less-than operators are case-sensitive.
When putting data into columns in a scrolling field it is customary to use a mono-spaced font and "pad" the data with spaces, which are added using a repeat loop. I have found it quicker to replace the repeat loop for adding spaces with something like this: put char 1 to (theWidth-length of theText) of " " before theText where there are enough spaces between the quotes to cover your column width, for worst cases. This puts in the right number of space characters in one go.
If, during a session, you go to a stack that has a custom font installed that is not in your system, you can use the command 'debug rebuild font menu' to allow that font to show up in the Font menu. You only need to use this if you are depending on having the custom font show up in the Font menu. Otherwise, the custom font will display just fine in your fields without rebuilding the menu.
White Feather Software
A most excellent item delimiter to use for importing and exporting text is numToChar(127), the Delete character. If your data has been generated from keyboard input, there's no way anyone has "typed" a delete character into the fields, so this is a very safe delimiter. Since the character has no width, you can add it to visible fields with no visual penalty.
In larger font sizes, the Delete character is represented by a small
box, but in 12-point or smaller sizes, it has no width. -- jg
This function will strip leading and trailing spaces and returns from a string. function noLeadingOrTrailingSpace x return word 1 to (number of words of x) of x end noLeadingOrTrailingSpace
Put a handler in your Home stack script that lets you easily edit the scripts of other stacks without having to go to the other stack. on es edit the script of stack " " -- notice there's a space there end es Then you can type "es" in the message box and HyperCard will prompt you to find the stack " ". Find the stack whose script you want to edit without leaving the stack you're in. Credit for this handler goes to Brady Johnson.
Many times there is a need to switch between stacks to get or save data or perform any number of functions. If you simply issue the command go stack "World's Greatest Stack II" and the stack is not there then HyperCard intercedes with the dialog "Where is..." At this point, the script could continue as planned, or it could end in an error, depending on whether or not the user selected the correct stack from the "Where is..." dialog. The good way to handle this situation is to make sure the stack exists ("if there is a stack...") and go there if it does, or prompt the user and manage the response if it doesn't.