Articles
How LiveCode menus work
A LiveCode menu consists of a set of popdown buttons, grouped together, and placed at the top of the card. The menu items that appear in each menu are just a return-delimited list stored in the button contents. The behavior of each menu is governed by the menu group’s script.
On a Macintosh, if a menu group is not placed on a card, no stack menus appear in the Mac menu bar while that card is current. (There is one exception. It is possible to assign a default menu bar that appears when no other menu group is available. The default menu bar is only effective on Macs; if run on Windows, the stack will not have a menu on that card.) If a stack with no menus is run in the IDE, LiveCode’s developer menus will appear instead. If the stack does have a menu, then its own menus will replace the IDE menus. In the IDE, the developer menus can be accessed by bringing the message box, tool palette, or other native LiveCode stack to the front.
If a stack with no menus is run in a standalone, the Mac menu bar will be empty except for the default Apple and Help system menus.
On Windows machines, LiveCode’s developer menus are displayed in a palette and none of the above applies since there is no system menu bar. A card either has a menu or it doesn’t.
LiveCode menus are objects that take up room at the top of a card. On Windows and Linux, this is standard; menus appear at the top of each window. When the same stack is run on a Mac, LiveCode scrolls the top portion of the card above the top border of the window, out of view, and translates the menus to the system menu at the top of the screen. The body of the window shortens to accomodate only that portion of the card that contains the actual stack content. The card is still technically the same height, but the area that contains the menu group is hidden. LiveCode handles this window resizing transparently between platforms.
The easiest way to create a menu bar is to use the Menu Builder, located in the IDE’s Tools menu. On a Mac, you can see how the menu display works there. After creating a menu bar, tick the checkbox “Set as stack Menu bar” (which sets the editMenus
property) to see how the menu portion of the stack is scrolled out of view and the window height is shortened to accomodate. This setting should be turned on if you will distribute to Mac users. On Windows, it is ignored.
EditMenus false
EditMenus true
Best practice is to create the menu bar first, before any other controls are placed. Because menus take up space at the top of the card, at least a basic group should be created before anything else; you can always edit it later. If the menu group isn’t there from the beginning, you’ll probably have to move all other card objects downward and increase the stack height later to accomodate the menu group.
During development it’s easiest to leave the menu visible in the window, and wait to apply the editMenus
property (“set as stack menu bar”) until deploying to Mac. If the menubar is not visible on the card, what appears to be coordinates “0,0” on a Mac screen will really be more like “0,29”, because the first 29 pixels contain the menu group hidden above the top of the window. Any scripts that work with card coordinates need to account for that. This is most easily done by creating the menu bar early on and laying out all controls while the menu bar is visible in the stack window. That way you can be sure that no controls will scroll off the top on a Mac, and all scripted coordinates will be correct regardless of the deployment platform.
For more information about LiveCode menus, see the editMenus
, defaultMenuBar
and menuBar
properties in the dictionary or the User Guide.
How To Test Your Stacks
(or) Gee, It Doesnât Do That On My Machine
by Jacqueline Landman Gay
HyperActive Software
There is one single thing that defines a top-quality application: everything works. This may sound simplistic, but itâs the bottom line. It doesnât matter how slick your user interface is, or how tricky the scripts are, or how sophisticated the material you are presenting; if everything in the project doesnât work exactly right, your work will be ignoredâor worse, scornedâby others.
Thereâs no secret to creating a great application that people will use and enjoy. Test the application and everything in it, again and again. Run everything through its paces several times and in different orders. Click every button, read and scroll every field, run every script. Show every dialogâand when you do, try every possible response button. Then get all your friends to do the same thing too.
Thatâs it. Everything else I’m about to tell you is just commentary.
Most application errors and mistakes can be prevented by using a few simple testing techniques. Unfortunately, too many authors donât want to take the time to test their work adequately. While the fun may be in designing and scripting an application, remember that everyone elseâs opinion of it will be based on its performance. Users have high expectations of software these days, and they wonât take time to figure out work-arounds to glitches. They’ll just toss your application in the trash instead. Worse yet, they may avoid your work in the future based on one bad experience.
Try the following tips before releasing your stack. You may be surprised what you find. These techniques should ensure that your stack is as bug-free as it can be.
- Pretend you just met your application in a crowded library and you donât know anything about it. Start clicking on (or typing into) everything. This doesnât just mean using the obvious buttons; it means, for example, clicking on locked fields that have scripts, and using all dialog boxes. Repeatedly bring up every dialog box until you have tried all possible responses to it, and donât forget âcancelâ, you need to account for a user wanting to bail out. Donât forget any controls that are hidden right now but that might be visible and used later; activate and use those too. Every button should do exactly as it is labeled (so choose your button names carefully.) Every script should interact cleanly with everything else without causing any errors. Click outside any buttons or fields, right on the card itself, to be sure that nothing unexpected happens. If you’ve created any menus, systematically choose every single menu item, one at a time, to be sure it behaves right.
- Set up some fake data with known content and test with that. Say you have a stack that opens a text file of numbers and totals them. Set up a text file and get a total with your hand calculator. Then run your application on the file and see if you get the same results. Or suppose you have a script that counts the number of times a text string occurs in another stack. Set up the other stack with a known number of repetitions of some text and then see if your script returns the right number. Too often scripters will suppose that if the script doesnât error when it runs, it must be okay. All that means is that you havenât any syntax errors; it doesnât say anything about the accuracy of the results when the script is done. Do tests on several different sets of known data. One set isnât enough; you need to account for all types of variation. In the first example above, you might create a text file containing all numbers except for one line of text. What happens when you run your script on that?
- Once you’ve tried everything, and I mean everything, go back and try it all over again in a different order, preferably the wrong order. This will be incredibly educational. Donât fool yourself into thinking that your users will click all the buttons in the correct order, or even in any logical order (although your stack must present everything in an orderly way.) For example, what happens if a user decides to push a âcalculateâ button before they enter any data into some number fields? Does the app exit gracefully? Or does it error out half finished and leave the stack in an unpredictable state? What happens if users enter letters and other symbols that arenât numbers, or type in numbers in the wrong range (always test-enter numerical data as negative numbers at least once.) What happens to your other scripts when one script fails? Do you get a cascading failure when each succeeding handler looks for information that now doesnât exist, or exists in the wrong format? Try your hardest to enter incorrect information into every field, enter data in the wrong format, and click every button out of logical order. While it is fine if this doesnât produce any usable results, it should definitely not produce any script errors either. If it does, you havenât done enough error-checking in your scripts. Donât think to yourself, âmy stack is aimed at Mensa members with IQâs over 165 and they would never do that sort of thing, so I donât have to check for it.â Trust me, they will. And you do. Whenever you repair a script, no matter how small the change, start the testing process over again. Thereâs no way around it; you have to be sure not only that the change in your script works correctly, but that it works correctly with everything else. You can use some judgment here. If you alter a script that displays information in a field, you probably only have to test that one script to be sure the display is correct. But if any other part of your application uses the data in that field later, then you need to re-test every other script that uses that field information. This may sound tedious, but it is the only way to avoid one of the main causes of errors: the interaction of different elements of your application.
- Get three other people to run your application. This is hugely important. Never, ever release an application that no one else has used. If you donât know three other people, at least get one. If you know more than three, go for as many as you can rope into it. No two people will use your application the same way. Certainly no one will use it exactly the way you think they will. What they do with your app will tell you worlds about your work. Choose people for testing who are as close as possible to the kinds of people who will be downloading and using your application. If your app is for children, use children to test it. If itâs for anthropology professors, find some of those. If you donât know any anthropology professors then you can use your mother if you have to, but donât expect her to find all the anthropology mistakes. She will, however, probably still find a lot of generic errors having to do with application design.
Donât tell your testers what to do. This is a strict rule. Let them read any documentation or Read Me files you will supply with your application, but thatâs all. This is easier said than done, but remember that no one who downloads your software will have you around to give them verbal directions, and your tester shouldnât get any either. So limit yourself to watching, bite your tongue, and take a lot of notes. See what actions they choose to do first, where they get stuck, and how they look around in the app for help if they need it (you did put some help info in there, right?) This will tell you where your design isnât clear. Donât offer any advice unless the other person asks outright, and in that case give as little information as possible to get them back on track. When they’re done, you can ask them questions from your notes if you need to. Ask what they thought was good about the application and what was not so good. Pay attention to what they say and change your app if necessary. Try not to decide that they âdidnât know what they were doingâ and donât dismiss their comments as irrelevant. If your testers canât figure it out, probably no one else can either. - Run your stack on other peopleâs computers, the more the better. If you are distributing cross-platform, you must test on as many variations of your target platforms as you can lay your hands on. How do the fonts look? Does the text overspill your buttons? Is the window too big to show up fully on a small laptop screen, or does it diminish to a postage stamp on a 27″ monitor? How about speed? Is it acceptable on other computers? What flows smoothly on your state-of-the-art machine may stutter or crawl to a halt on an older one.
- Thereâs one last way guaranteed to find every possible obscure bug, hiccup, and glitch in your application: demo the stack to someone else. No matter how much testing you’ve done or how many people have gone over your work, if thereâs one obscure thing that can go wrong, it will gleefully manifest itself when you are trying to show your work to an important personâlike a potential client, your latest love interest, or your anthropology professor. Those tiny little bugs that have been lurking under the paint layer just drooling for a chance to embarrass you will burst suddenly on screen, grinning and winking at each other. If you really, really want to find minute and obscure bugs, try showing off your software with as much pride as possible to someone important.
- Avoid the âitâs my babyâ syndrome. It is tempting, after spending a lot of time with a stack, to want to leave it just as it is, simply because itâs your baby. Be ruthless. Even babies are better for a little discipline. If something isnât appropriate for your applicationâs purpose, take it out, no matter how cute it is or how much time you spent writing it. If your testers complain that fourteen repetitions of The Hamster Dance are a bit excessive, take it to the trash no matter how cute you think it may be. Remain constantly flexible. You donât have to change everything your testers say (after all, they really might not know what they’re doing), but you should sure listen closely when they say it.
If you follow these testing guidelines, you can produce software that is consistently bug-free and reliable. The longer you test, the more people you test with, and the more repeatedly you test, the better your apps get. Even the simplest application can become pure art when it runs smoothly, behaves intuitively, and functions without errors.
Introduction to LiveCode CGIs – A Tutorial
by Jacqueline Landman Gay
HyperActive Software
This tutorial will show you how to create CGIs using LiveCode and its scripting language. CGIs allow your web site to produce dynamic content, process data, and work with stacks while interacting with a web server.
This tutorial assumes you have a minimal understanding of a few basic concepts. You’ll need to know where the main web directory and its CGI sub-directory are located on your server, and you’ll need some familiarity with either an FTP program or a terminal program so that you can set file permissions. Please see the documentation for your FTP program or your operating system if you have questions about setting file permissions, or the location of these directories.
There has been a great deal of discussion about CGIs on the LiveCode Mailing List in the past. The mailing list is a good place to ask questions about anything that is not covered in this tutorial, or sections you may want more information about. If you are not already a member, you can join the LiveCode mailing list at their web site.
Start at the Table of Contents and work your way through the sections in order. If you are already familiar with CGIs written in other languages, you may find that you can skip some sections. However, best results are probably obtained by at least skimming through all parts of the tutorial.
With the introduction of the 4.0 engine, LiveCode no longer supports traditional CGI access. CGIs have been replaced by the remarkable iRev product, which allows direct LiveCode scripting inside web pages, without the need for external CGIs. However, in some cases the more traditional approach is required and in these cases CGIs are still useful. For this tutorial you will need a copy of Rev 3.5 or older. If you have a previous copy of LiveCode (formerly called Revolution,) use that version. If not, and you do have a legal licensed copy of LiveCode, contact me and I’ll see what I can do.
This tutorial will refer to “Revolution” throughout, since that was the last engine that supported CGIs directly at the time of this writing. Since then, LC 7.0 was re-enabled as a CGI engine, so you can also use that version or a later one as well.
Start Tutorial
LiveCode Functions
by Jacqueline Landman Gay
HyperActive Software
Using functions in LiveCode
LiveCode’s programming language is intuitive and easy to learn. It is very much like spoken English in some ways, and many commands can be written exactly as you would say them in natural language syntax. However, it is also a full and expansive programming language in its own right, and as such, it supports all the constructs that are found in any programming language. This includes two basic types of programming routines: command handlers and function handlers. Function handlers are usually referred to simply as “functions”.
Many LiveCode newcomers are not sure what the differences are between function handlers and command handlers. This document explains how functions work, and what the basic difference is between a handler that acts as a function and one that acts as a command.
What is a function?
A function is a handler that does some work and returns the results to the handler that asked for it.
Functions are distinguished syntactically in two ways; a function is called using either the keyword the
or it is called using parentheses. You are probably using functions all the time in LiveCode and may not even know it. LiveCode has hundreds of built-in functions; for example, the date
is a function (using the keyword the
,) which can also be written date()
using the parentheses form. When you use the date
or date()
, LiveCode does an evaluation and sends back today’s current date. This process happens invisibly inside the LiveCode engine, which contacts the OS, gets the system date, and returns it to your script. Then your script can use the date information to do some work.
Some functions need to know a bit of information before they can work; for example, the LiveCode function the number
(also written number()
.) You need to pass a parameter to the function â some extra information â that tells it what you want to count, whether it is buttons, or cards, or graphics, or anything else. If we want to know the number of cards, we pass “cards” to the function this way:
the number of cards
which can also be written:
number(cards)
LiveCode then goes through the stack and adds all the cards to get a total, which is sent back to your script. Your script receives that value and needs to store it into a variable so that it can use it later:
put number(cards) into theNumCds
In this case, “theNumCds” contains a number, which is equal to the number of cards in the stack.
Writing your own functions
If LiveCode does not have a built-in function to do something you want, you can write your own function to do it instead. If your script uses a function you write yourself, the calling handler cannot use the
when using it; the handler must use the parentheses form. Thus, a custom function called myFunction
cannot be called with the myFunction
â you must use myFunction()
instead.
A note about the message path: The message path for functions is identical to the message path for command handlers. You should place your function at the level where any handler that calls it can use it. For example, if your custom function is used only by a single button, then an appropriate place for that function would be in the button script. If many buttons use the function, then it could go in the card script, a group script, or the stack script.
Suppose we want to add two numbers together and get a total. (This is a very simple task, but it makes a good example.) We can write a function called “addNumbers” like this:
function addNumbers num1,num2 put num1 + num2 into theTotal return theTotal end addNumbers
The parameters num1
and num2
are like baskets that hold whatever values the original handler sends. In this case, they will each contain a number. The special word return
tells LiveCode to send the variable theTotal
back to the handler that asked for the information. Note that whenever LiveCode sees the return
keyword, the function will exit immediately afterward. This means that return
is usually â but not always â the last line in the function’s script.
So, this function takes two numbers that are passed to it in the parameters, adds them together, and sends back a total. Now we can write a handler that uses the function:
on myHandler put field "firstNum" into theFirstNumber -- say the field contains 16 put field "secondNum" into theSecondNumber -- say this one is 4 put addNumbers(theFirstNumber,theSecondNumber) into field "sumTotal" end myHandler
This handler will send 16 and 4 to our custom function addNumbers
. The addNumbers
function will catch these two numbers in its parameters num1
and num2
, add them together, and send back 20. The handler myHandler
will receive that 20 and put it into the field “sumTotal”. The field now displays 20.
When you use a function in a handler, you must provide a place for its results to go â a variable usually, or sometimes a field. For example, this will not work:
addNumbers(theFirstNumber,theSecondNumber)
because there is no place for the returned information to be stored. A handler that uses a function must provide a place to put the information that the function sends back. This is true of built-in LiveCode functions too. For example, this will not work:
the date
because there is no place to put the date that is returned. You must provide a place:
put the date into myDate
or:
put the date into field "today"
A handler that uses a custom function must follow the same rule. It must provide a place for the returned information to be stored:
put addNumbers(theFirstNumber,theSecondNumber) into myTotal
Here is another example. This one removes all vowels from a word and returns only the consonants:
on myHandler put getConsonants("chimpanzee") into field 1 end myHandler function getConsonants theWord put "aeiou" into theVowels repeat for each char i in theWord if i is not in theVowels then put i after theChars end repeat return theChars end getConsonants
In this example, the word âchimpanzeeâ is sent to the function getConsonants
, where it is placed automatically into the parameter theWord
. The function checks each character in the word and if it is not a vowel, it puts the character after a list called theChars
. When it is finished checking each character, it returns the list of characters, which automatically gets sent back to the calling handler myHandler
. The characters sent back in this example would be âchmpnzâ, so that is what field 1 will display.
Functions can be as short or as long as you want, and sometimes can be very complicated. But the basics are always the same: a handler asks a function to do some work and often sends some parameters to the function to tell it what to work with. The function does the work and returns the finished calculation to the original handler. The handler can use the finished calculation any way it wants, just as if it were any other variable.
What’s the difference between a function and a command handler?
Not very much, when you get right down to it. They are both just a list of commands that accomplish something. The primary difference is that there is a built-in way for a handler to use the results of a function.
However, command handlers can also return results, in exactly the same way as functions do. The difference is in how the calling handler receives the result. When using a function, the result is sent back automatically and all the calling handler has to do is provide a storage place for it. When using a command handler, the calling handler must instead ask for the result specifically. We could change the addNumbers()
function into a command handler by simply replacing the keyword function
with the keyword on
:
on addNumbers num1,num2 put num1 + num2 into theTotal return theTotal end addNumbers
Now that it is a command handler, the total that has been calculated is not sent back to the calling handler automatically. Instead, the calculation is placed in the system-wide storage area the result
. The calling handler can retrieve the information at will by asking for the result
:
on myHandler put 16 into theFirstNumber put 4 into theSecondNumber addNumbers theFirstNumber,theSecondNumber get the result -- this asks for the total put it into field "sumTotal" end myHandler
Most of the time it is easier to just write the calculation as a function and allow LiveCode to send the result back automatically. However, the above is valid and in some situations may be preferable, though this author cannot think of any good examples of when this would be required.
Note that the result
changes its value frequently and without warning, since a good many LiveCode commands use it to store temporary data â for example, the “ask” and “answer” dialogs use the result
to store the name of the button the user clicks. If you will be retrieving the result
from a command handler, it is a good idea to do so as soon after issuing the command as possible. If you allow too many lines of script to go by before retrieving the result
, you may find that another command has changed its contents and the result
no longer contains what you think it does.
LiveCode/Revolution Scripting Conferences
The Revolution online scripting conferences took place from April to November, 2005, when LiveCode was called Revolution. These conferences were a series of fortnightly moderated online chat sessions, free and open to all. They introduce the basic techniques of programming in LiveCode to the widest possible audience, in a fun, exciting way. Don’t know what a stack is? Don’t know your background from your backyard? These conferences are for you. They will also be excellent refreshers for more experienced scripters.
Update: In 2014, almost 10 years later, these stacks were still recognized as useful and were downloaded by new users regularly. At the request of the community, they were updated slightly to modernize them for LiveCode. The product logo was updated, all references to “Revolution” were changed to “LiveCode” and the color scheme was altered to better match the logo. The original scripts, behaviors, and syntax remain accurate and required no changes, and the stacks retain the original authors’ work.
The scripting conferences were organized and moderated by Jacqueline Landman Gay.
1
|
Stack Structure | Kevin Miller | 30th April | Download |
2
|
Script Anatomy 101 | Mark Talluto | 14th May | Download |
3
|
Controls | Klaus Major | 28th May | Download |
4
|
Message Hierarchy | Richard Gaskin | 11th June | Download |
5
|
Properties | Dan Shafer | 25th June | Download |
6
|
Groups & Backgrounds | Judy Perry | 9th July | Download |
7
|
Script Anatomy 2 | Frank Leahy | 23rd July | Download |
8
|
Text & Chunk Expressions | Alex Tweedly | 6th Aug | Download |
9
|
Menus | Jeanne DeVoto | 20th Aug | Download |
10
|
How to Work With Files | Ken Ray | 3rd Sept | Download |
11
|
Images & Multimedia | Trevor DeVore | 17th Sept | Download |
12
|
Dates & Time | Sarah Reichelt | 1st Oct | Download |
13
|
Debugging | Jerry Daniels | 29th Oct | Download |
14
|
Standalones | Mark Wieder | 12th Nov | Download |
15 | Text Munging and Regex | Alex Tweedly | 26th Nov | Download |
Polling the Mouse in LiveCode
(or) How to wreck perfectly good software
by Jacqueline Landman Gay
HyperActive Software
A mouse() on a hamster wheel
A common scripting technique in any x-talk language is polling the mouse in order to get information about its state so that a script can perform an action. In HyperCard or SuperCard, for example, it is customary when simulating a dragging operation to use a repeat loop to get the mouseLoc
in order to repeatedly set an object to the mouse location, or to check in a loop for a mouse condition to determine when to leave the loop. Here is a common example:
repeat until the mouse is up set the loc of btn "dragBtn" to the mouseLoc end repeat
In MetaCard and LiveCode, this kind of mouse polling is emphatically discouraged. Scott Raney of MetaCard Corporation has this to say about the above repeat construct:
This loop uses 100% of the CPU time, regardless of the speed of the processor, bringing the system to its knees, causing poor feedback for your app, and making your system unresponsive to any other processes running on it. You see, on a fast system, 99.99% of the time through that loop you’re setting the loc to exactly the same coordinate!
I think the biggest problem here is that OSs have evolved way beyond the point where even developers (or at least most of our customers) understand how they work. There’s nothing wrong at all with things like “the mouse” on single-user single-tasking OSs like MacOS, but that’s just not the world we live in anymore…
I’m certainly sympathetic to the “easier way” and “programming for the rest of us” arguments, but none of us can afford to be classed as developers of system-hostile applications… Developing in a high level language will become a stigma that we’ll never live downâŠ
Some of the processes that can slow down or stop when a script uses this kind of processor-intensive repeat loop are: file and printer sharing, HTTP/FTP servers, network management tools, and on UNIX systems (including Mac OS X), people telnetting in from other systems. Another problem on Mac OS X is the spinning rainbow cursor that may appear while the loop is in progress if the operation takes too long â the system puts that up whenever it thinks an application is unresponsive. That means if a user drags an object around the card for a while, or keeps the mouse button depressed for too long while the script is in a loop, the system may think your application has gone dead.
The problem is not limited to just checking the mouseloc
or the mouseâs up or down state. Checking any of the following mouse events within a loop can cause a bottleneck and should be avoided:
- the mouse — i.e., “up” or “down”
- the mouseClick
- the mouseH
- the mouseV
- the mouseLoc
So what do I do?
You may be wondering at this point, “If I shouldn’t be using a repeat loop to determine the state of the mouse, what do I use?”
The answer lies in the mouseMove
system message, sent by the MetaCard/LiveCode engine whenever the user moves the mouse. The mouseMove
message carries two parameters with it: the horizontal and vertical coordinates of the current mouse position. By writing a handler that traps this system message, you can create an OS-friendly script that will not bottleneck the CPU and which will run far more efficiently than any repeat loop.
There is an additional advantage to using the mouseMove
approach: it allows your stack to continue sending events and reporting other user actions simultaneously. Unlike a repeat loop, which locks out everything else until the handler ends, trapping a system message takes an insignificant amount of time and allows other system messages and all your stack’s features to continue operating at the same time. You may not always want to use the other messages that are being sent, but they are there if you need them.
How it works
In the above example, all we want to do is move a button to the same location as the current mouse position, for as long as the mouse button is depressed. If the mouse is “up”, the script should do nothing. A short handler in the button script could take care of it:
on mouseMove x,y if the mouse is down then set the loc of me to x,y end mouseMove
Very easy. At the same time that mouseMove
is catching the position of the mouse and handling the drag, you can also have other handlers running normally to catch various system messages or handle other events. You can think of mouseMove
as a sort of customized, single-purpose idle
message. (Note though that idle
has its own set of processor-hogging problems, and should be avoided at all costs too.)
But wait. The mouseMove
handler is still polling the mouse, checking for its down state repeatedly. So while we have improved the script’s efficiency, it is still using more resources than necessary. We can fix that by adding a variable that stores the state of the mouse, so that the engine won’t have to check it each time. We can set a flag on mouseDown
, and turn it off on mouseRelease
and mouseUp
. While we’re at it, we’ll also add a line to the script that passes the ID of the object we are dragging so that more than one button can respond to the mouseMove
message.
These handlers would go in the card or stack script:
on mouseDown global gDragging,gDragObj,gDeltaX,gDeltaY -- (you could use local script variables instead) if the name of the target contains "button" then put true into gDragging put the long id of the target into gDragObj -- the following adjusts for the mouse position on the object; -- it isn't part of what we're explaining here but it enhances the drag: put item 1 of the loc of gDragObj - the mouseH into gDeltaX put item 2 of the loc of gDragObj - the mouseV into gDeltaY end if end mouseDown on mouseRelease global gDragging put false into gDragging end mouseRelease on mouseUp global gDragging put false into gDragging end mouseUp on mouseMove x,y global gDragging,gDragObj,gDeltaX,gDeltaY if gDragging = true then add gDeltaX to x add gDeltaY to y set the loc of gDragObj to x,y end if end mouseMove
Most HyperCard and SuperCard scripters are familiar with mouseDown
and mouseUp
, but mouseRelease
may be new. The mouseRelease
message is sent to an object when the mouse button goes up but the mouse is no longer within the borders of the object that received the original mouseDown
message. This covers the case where the user may move the mouse faster than the button can keep up. Even if the mouse is released somewhere outside the button, this script will still set the global gDragging
to false.
Now you’ve got a fast, efficient, CPU-friendly set of handlers that won’t bog down the OS or hog system resources. Applications running in the background will still get their fair share of CPU cycles with no performance hit. Your stack will respond much faster and will allow other user actions and scripts to be handled simultaneously. There’s no down side, there’s lots to gain, and your stack will run like a pro.
Reference Sheet: Debugging on Mobile Platforms
Jacqueline Landman Gay
HyperActive Software
Presented at RevLive 2012
Whatâs available out of the box
Debugging on mobile platforms isnât as easy as using the debugger in the IDE but it is possible to get information about things that go wrong if you prepare your stack ahead of time. There are several ways to report errors and script progress. The most-used methods are:
Answer dialog:
Convenient and easy, but dialogs can interfere with script flow. The answer dialog also changes the values of the âitâ variable and âthe resultâ which can cause unexpected results in scripts.
Logging to a field:
Avoids the issue of script interference but requires an additional object in the stack which needs to be removed or hidden later. If the scripts loop through card controls, the extra field gets in the way.
Logging to a file:
A good way to keep a record of events and errors which is workable on Android devices and in the iOS and Android simulators. However, the file system is largely inaccessible on physical iOS devices so log files arenât a good option there.
Logging to stdout (standard output):
This is one of the best methods. It does not interfere with objects or script execution, and cleanup is simple when debugging is done. On iOS, there is no particular setup required, you only need to launch an app. On Android a terminal command is required, but once set up it works very well.
This document explains how to use the last method. When LiveCode encounters an error, or when the put command is issued without a destination, data is sent to stdout. In the IDE, stdout is redirected to the message box. On iOS, stdout can be viewed using the Console app or XCode. On Android it can be seen in a terminal program.
Setting up your stack
There are two debugging handlers to add to your stack script, one to log events on demand, and the other to catch runtime errors. Put these where they will always be in the message path, such as in the stack script of the appâs mainstack.
The logging handler is a variation on Andre Garziaâs useful example, and allows flexible logging to a file, stdout, or almost anything by simply changing a constant. Itâs been modified here to allow the use of an answer dialog. You could enhance it further to log to a field in the stack.
constant kDebug = "msg" -- change destination here on log pText switch kDebug case "msg" put pText break case "file" put pText & cr after \ url ("file:" && the effective filename of this stack & ".log") break case "answer" answer pText break end switch end log
The second handler catches any runtime errors that occur and reports them:
on errorDialog pErr log pErr end errorDialog
Whenever you want to track the state of a variable or the progress of a handler, insert a line similar to one these into the script:
log âsome message hereâ -- for example, âopenstack now runningâ log the params log tUserName -- a variable in the handler
When you are done debugging, either comment out the body of the log handler or put empty into the kDebug
constant. Leave the declaration intact (the âonâ and âendâ lines) so no errors will occur at runtime in case you forget to remove all the âlogâ debug lines in the scripts.
Debugging on iOS
If you are using the iOS simulator, debugging output can be viewed in the Console app. Console ships with OS X and is located in Applications/Utilities.
Launch Console and select âAll messagesâ at the top of the left-side list. To limit the output to your own appâs data, enter any distinctive part of your app ID into the filter field at the top right of the window. Often this is the name of your company or the name of your app.
If you are debugging on a physical device, launch XCode instead. Under your connected device, click the Console icon. Output can be viewed here.
In LiveCode, click the Test button to start your app. Any logging commands in the script will run as you interact with it.
Debugging on Android
Start up the Android emulator if it isnât already running, which automatically launches a terminal application. To debug, open a new terminal window. You canât use the one that opened the emulator.
The adb utility is used for debugging. Itâs located in your Android SDK folder, inside the platform-tools folder. The logcat command starts the logging process. To enter it, provide the full path to the adb app followed by the logcat command, like this:
/android-sdk-macosx/platform-tools/adb logcat
The easiest way to get the full path is to just drag the adb file into the terminal window. Then type a space and the word logcat.
Android produces a great deal of data, most of which wonât be relevant. You can filter much of the output by adding special tags to the terminal command. To filter for LiveCode output, add these filters (all one line, ignore any wrap):
/android-sdk-macosx/platform-tools/adb logcat revandroid:D LiveCode:I *:S
LiveCode Android apps include two libraries, revandroid and LiveCode. The “D” tag means “debug” and “S” means “silent”, so the above command means: report debug data from revandroid, report all info from LiveCode, and mute everything else.
Once youâve started up logcat, click the Test button in LiveCode to launch your standalone. Terminal will log all output received from either a physical device or the emulator.
To stop logcat, type Control-C.
More info about debugging with adb and using filter tags is available in the Android developer docs at http://developer.android.com/guide/developing/tools/adb.html
Deciphering errors
Runtime errors are reported as a series of lines comprised of itemized numbers, like this:
241,3,1,mouseUp
The top line in the series is usually the most significant; the lines below it are the history leading up to the error, in reverse order. The numbers in each line indicate:
error code, script line number, character offset, text hint
The text hint may not always be available. In the IDE the error codes (the first number in the line) are deciphered before theyâre reported, but in mobile debugging youâll need to look them up. The User Samples area available from the LiveCode tool bar contains a LiveCode Error Lookup stack which can be placed in your plugins folder for quick access.
Enter either a single error code into the field in the Basic Lookup, or copy all the error lines and paste them into the Multiline Lookup tab of the lookup stack. Pasting all the lines at once lets you see the entire sequence of events that led up to the failure.
The LiveCode Error Lookup stack retrieves its error codes dynamically from the currently running copy of LiveCode, so it will always be up to date.
Using PayPal with LiveCode CGIs
Setting up a PayPal “Buy Now” button to work with a LiveCode CGI wasn’t easy. It took days of frustration and eventually a good part of the LiveCode mailing list to work it out. The puzzle fell into place when a combination of list posts made me realize that 1) PayPal won’t respond unless it gets a “200 OK” status header, and 2) Apache won’t send any headers until the script exits, but PayPal needs to get the header sooner than that.
The script that follows is the solution. I couldn’t have figured it out without the list’s help, so I’m giving it back to the community. You can use this on your website, but please note that I retain the copyright and it can’t be resold in any form.
The script could be rewritten for use with LiveCode Server. If anyone does that, let me know and I will link to it here. Note that if you will use this with HyperActive’s Zygodact product, you cannot use LC community server because the Zygodact generator is password protected and LC Community Server can’t open it. You will need the Commercial version of LC Server, available from your LC account web page.
How PayPal implements the Instant Notification System
When a customer purchases from your web site, PayPal will send the purchase information to the URL of a script you provide. Your script must return the data exactly as received, with a single parameter added at the front. PayPal compares the data you return with the data it sent, and if they match, it sends a single word: “VERIFIED”. If there is no match (i.e., someone is trying to scam your purchase page) it will send back “INVALID”.
That’s the total communication process. Once your script knows a transaction is verified, it can do whatever it wants with the data.
If PayPal can’t contact your server it will resend the purchase information repeatedly for up to 4 days. The CGI script watches for duplicates and logs them, but doesn’t act on them if it’s already processed the transaction. PayPal will think it hasn’t reached you if it doesn’t get a “Status: 200 OK” acknowledgement, so if for some reason PayPal starts re-sending the same data, you should find out why. PayPal won’t get your responses if port 433 outbound is blocked so check with your ISP if you think that may be the case.
Use the sandbox
PayPal has a sandbox that you should test in. You’ll need to set up a sandbox account and use the IPN simulator to make sure the script is working. That allows you to test without spending any real money, and gives you some feedback about the transactions.
Once your tests work with the simulator, set up a sandbox “business” account and a “customer” account. Log into the business account and make a test button. Put it on an html page and test with it. The button will let you simulate the whole payment process as the “customer.” Then log in as your “business” and view the transaction in the sandbox History pane. It lists all the IPN transactions, their current status, any errors received, and other helpful information.
PayPal has good documentation about using the sandbox:
https://developer.paypal.com/us/cgi-bin/devscr.
Once everything works in the sandbox, you can be fairly confident it will work on your real web site.
Configure a PayPal “Buy Now” button
Go to “My Saved Buttons” in the Merchant Services pane in your PayPal sandbox account, and use PayPal’s button generator to create a Buy Now button. Note the exact item name and ID you use, as you’ll need those in the CGI script. You’ll also need your merchant ID, which is listed in your account profile.
Use the “Customize Advanced Features” section at the bottom of the button generator to add an Instant Notification URL. This goes into the “advanced variables” field at the bottom of the page. The URL should be the path to your LiveCode script in the cgi-bin folder on your server, preceded by “notify_url=”, like this:
notify_url=https://hyperactivesw.com/cgi-bin/paypal.mt
If you want PayPal to return the customer to a “thank you” page after a purchase, then input the URL for that page in the “Take customers to this URL when they finish checkout” field and turn on the checkbox. This URL should be on your main web site somewhere, not in the cgi-bin folder.
I set the URL for “Take customers to this URL when they cancel their checkout” to the same purchase page they left from, but that’s up to you.
When you save the button, PayPal will give you the HTML code for it. Create a purchasing page for your site and paste in the button code. This page goes somewhere on your site where visitors can access it (i.e., not in the CGI folder.)
Overview of required server files
Here’s a summary of the files that must be on your server. They’re explained below.
Description | Example URL |
---|---|
A web page with a PayPal button for purchasing | https://hyperactivesw.com/stewtest/index.html |
A “thank you” web page that customers see after purchase | https://hyperactivesw.com/stewtest/thanks.html |
A text file containing the PayPal CGI script | https://hyperactivesw.com/cgi-bin/paypal.mt |
A form letter with merge variables, for customer auto-replies | https://hyperactivesw.com/cgi-bin/autoreply.txt |
An empty text file for log entries | https://hyperactivesw.com/cgi-bin/paypalLog.txt |
The (Linux) LiveCode/Revolution engine 3.5 or earlier, or the LC 7.x+ runtime/standalone engine (7.x required for 64-bit hosts) | https://hyperactivesw.com/cgi-bin/rev |
Create the cgi script and files
The cgi-bin folder will contain your LiveCode script (traditionally with the extension “.mt” but it can be “.cgi” too.) The folder will also contain the boilerplate reply email for the customer and a log file for tracking purchase data from PayPal. It also needs a copy of the LiveCode engine suitable for the server OS (usually Linux.)
Versions of LC between 3.5 and 7.0 will not function as CGI engines, so you need to choose either a very early copy or a newer one. If you use LC 7.x or later, install the Runtime/Standalone engine, not the IDE engine, and make sure you’ve chosen either the 32-bit or 64-bit version compatible with your hosting service. The engine is named “rev” here for historical reasons but you can rename it if you like. Change the first line of the CGI script to match if you do that.
For general info about CGI scripts and LiveCode, there’s a tutorial here: https://hyperactivesw.com/cgitutorial/index.html. If you’ve never used a LiveCode CGI before, it’s probably a good idea to at least get the basics there. Most of what follows assumes you know a little about it.
The sample CGI script was made for my 5-cent Beef Stew recipe. All the constants at the top must be changed to your own information. If you are working in the PayPal sandbox, some of these constants will be different than the ones for your live web site. For example, your sandbox email and merchant ID are not the same as your real merchant email and ID. The auto-fill items in the sandbox simulator won’t be your items unless you manually edit them. Check all the constant values if something doesn’t work, and remember to change them when you go live on your real web site.
The two script globals gName
and gSerial
are for use with HyperActive Software’s Zygodact serial key generator. They don’t necessarily have to be globals if you’re using another registration system; they can be script locals or handler locals instead. If you aren’t providing a serial key, just omit [[gSerial]]
from your email reply text. You don’t need to change the CGI script.
For Zygodact users: This script will work with the Zygodact key generator for your product. Uncomment these two lines in the startup handler: |
start using stack "gen.rev"
…
|
Place the gen.rev stack into the cgi-bin folder with 755 permissions. That’s all you need to do. You don’t need the sample CGI script that ships with Zygodact. |
The CGI script simply logs the purchase data to the “paypalLog.txt” file. You may want the data to go into a database instead. If so, you’ll need to work out any changes to the script; I was happy with the text log so I didn’t do that. The transaction ID is returned to the main startup handler after checkOrderData
validates it, and PayPal recommends cross-checking its ID with your database. This script doesn’t use it, but if you are using a database you’ll probably want to store the ID for tracking or reference.
The “paypal.mt” CGI script is a text file placed in the cgi-bin folder. Be sure to set its permissions to 755 and make sure you’ve saved the file with unix line endings. The first line of the script must be at the very top of the text file with no blank lines before it.
You can download the CGI script as a zipped text file if you don’t want to copy it from this page.
LiveCode CGI script “paypal.mt”
#!rev -ui
global gName,gSerial
-- these constants are for PayPal transactions:
constant kPrice = 0.05 -- price in the Buy Now button
constant kItemName = "Beef Stew Test" -- Buy Now button item name, exactly
constant kItemNum = "BS100" -- Buy Now item ID
constant kMerchantID = "AB1CDE23F4G5H" -- your merchant ID
constant kMerchantEmail = "salesteam@hyperactivesw.com" -- your PayPal merchant email
-- these constants are for auto-reply emails:
constant kCarbonAddr = "jlg@hyperactivesw.com" -- your email; the blind carbon
-- and any warning messages are sent here
constant kBackupAddr = "private@hyperactivesw.com" -- a backup email for warning
-- messages; can be the same as the carbonAddr. If so, you'll just
-- get 2 copies.
constant kFromAddr = "HyperActive Software <salesteam@hyperactivesw.com>" -- the "From" the
-- customer sees in the auto-reply email
constant kReplySubj = "Beef Stew Test purchase info" -- the subject of the
-- auto-reply email
on startup
if $REQUEST_METHOD = "POST" then -- this is the only kind PayPal sends
read from stdin until empty
put it into tOrderData
-- this next bit only runs if PayPal resends a duplicate notification you've
-- already processed. We just log the duplicate and bail out. But if you see this,
-- PayPal isn't hearing your responses and you should find out why.
if tOrderData is in url ("file:paypalLog.txt") then
LOG "Repeat notification" && the date && the long time && tOrderData
exit startup -- this will cause Apache to throw a "server 500" error
end if
LOG the date && the long time && "Received:" && tOrderData -- raw PayPal data, urlEncoded
put "cmd=_notify-validate&" before tOrderData -- add required response cmd
if "test_ipn=1" is in tOrderData then -- we're in the sandbox
put "https://www.sandbox.paypal.com/cgi-bin/webscr" into tPPAddr
else -- live website
put "https://www.paypal.com/cgi-bin/webscr" into tPPAddr
end if
put "curl --data" &"e& tOrderData "e&& tPPAddr into tPostCmd
put shell(tPostCmd) into tCurlRslt
put last word of tCurlRslt into tResponse -- "VERIFIED" or "INVALID"; ignore the ascii progress meter
put "Status: 200 OK" &cr&cr -- without this, PP will think the server is unresponsive
put "ok" -- probably superfluous but won't hurt
LOG "Response:" && tResponse
if tResponse = "VERIFIED" then -- data is from PayPal
put checkOrderData(tOrderData) into tData -- verify the details & extract the info we need
LOG "Parsed data:" && tData -- log what we extracted
if tData <> "false" then -- valid data
set the itemDel to "|"
put item 1 of tData into gName -- already urldecoded
put item 2 of tData into tEmail -- customer email address
put item 3 of tData into tTransactionID -- not using it here but databases should
put "Content-Type: text/plain" & cr & cr -- add a header
if gName = "" then
LOG "Error: name is missing."
exit startup
end if
# -- start using stack "gen.rev" -- uncomment for Zygodact key generator;
-- other systems should generate a serial key here and populate the gSerial variable with it
LOG gSerial
sendEmail tEmail
# -- stop using stack "gen.rev" -- for Zygodact generators
end if
else if tResponse = "INVALID" then -- something's wrong, email me:
sendEmail kCarbonAddr,"invalid"
end if
else -- not a POST request; just display a vague error
put "Incorrect request" into buffer
put "Content-Type: text/plain" & cr
put "Content-Length:" && the length of buffer & cr & cr
put buffer
end if
end startup
function checkOrderData pData -- ensure that the order should be processed;
-- if so, return the 3 items we need. PayPal sends the data URLEncoded.
put urlDecode(pData) into pData
split pData by "&" and "="
if pData["receiver_email"] <> kMerchantEmail then return false -- not for me
if pData["receiver_id"] <> kMerchantID then return false -- not my PP account
if pData["payment_status"] <> "Completed" then return false -- don't process pending orders
if pData["txn_type"] <> "web_accept" then return false -- not a Buy Now button
if (pData["first_name"] = "" and pData["last_name"] ="") and \
(pData["payer_business_name"] = "") then return false -- no name provided
if pData["mc_gross"] < kPrice then return false -- underpaid; foreign funds are returned in local currency so it's okay to check the USD amount here
if pData["item_name"] <> kItemName then return false -- not my item
if pData["item_number"] <> kItemNum then return false -- not my item number
-- data ok; get user info:
put pData["first_name"] && pData["last_name"] into tName
if tName = "" then put pData["payer_business_name"] into tName
put pData["payer_email"] into tEmail
put pData["txn_id"] into tTransactionID -- PayPal ID for this payment
put "|" into tDel
return tName &tDel& tEmail &tDel& tTransactionID
end checkOrderData
--### email handlers (thank you Andre!)
on sendEmail pEmailAddr,pType
if pType = "invalid" then -- notify me of spoof attempt and bcc a backup email
put "Invalid purchase attempt" into tSubj
put "Invalid response was received from Paypal. Check CGI log." into tMsg
put kBackupAddr into tBcc
else -- email to customer & blind carbon me
put kReplySubj into tSubj
put url ("file:autoreply.txt") into tReplyTxt
put merge(tReplyTxt) into tMsg
put kCarbonAddr into tBcc
end if
mail pEmailAddr,tSubj,tMsg,kFromAddr,tBcc
end sendEmail
function shellEscape pText
repeat for each char tChar in "\`$" & quote -- original included "!" but it interfered with body text
replace tChar with "\" & tChar in pText
end repeat
return pText
end shellEscape
function wrapQ pText -- wrap quotes around text
return quote & pText & quote
end wrapQ
on mail pTo,pSub,pMsg,pFrom,pBcc,pCc -- send email
put "Copy of mail sent:" &cr&cr & pMsg into tBCCMsg
put wrapQ(shellEscape(pTo)) into pTo
put wrapQ(shellEscape(pSub)) into pSub
put wrapQ(shellEscape(pMsg)) into pMsg
put wrapQ(shellEscape(tBCCMsg)) into tBCCMsg
put wrapQ(shellEscape(pFrom)) into pFrom
put wrapQ(shellEscape(pBcc)) into pBcc
# put wrapQ(shellEscape(pCC)) into pCc
get shell("echo -e" && pMsg && "| mail -s" && pSub && pTo && "-- -f" && pFrom)
get shell("echo -e" && tBCCMsg && "| mail -s" && pSub && pBcc && "-- -f" && pFrom) -- bcc sent separately
put it
end mail
--### Logging:
on LOG pData
put "paypalLog.txt" into tFile
open file tFile for append
write pData &cr & cr to file tFile
close file tFile
end LOG
Create the email reply template
The reply template contains the text of the email your customers will receive. You’ll also get a copy of it.
The email template should contain variables for the customer name and serial key using LiveCode merge notation. The two variables gName
and gSerial
are enclosed in double square brackets, which allows LiveCode’s merge
command to substitute the values of those variables when it creates the email. If you aren’t using a serial key, just omit the gSerial
reference from the template. Here’s a sample reply mail:
Dear [[gName]], Thanks for your purchase! You can download Jacque's Beef Stew recipe here: https://hyperactivesw.com/stewtest/Jacque's%20Stew.pdf If you ever feel the need to license your food, use this: Username: [[gName]] Serial: [[gSerial]] Thanks again. I think you'll like the stew. Jacqueline Landman Gay -- Jacqueline Landman Gay | dishwasher@hyperactivesw.com HyperActive Software | https://hyperactivesw.com
Save the auto-reply text file (with unix line endings) as “autoreply.txt”, copy it to the cgi-bin folder, and set its permissions to 755.
Add a log file
This is easy. Just make an empty text file named “paypalLog.txt”, copy it to the cgi-bin folder, and set its permissions to 766. Transactions will be recorded here. If you’re using a database instead, you probably won’t need this file unless you want to keep a raw record of the transactions. Remember to change the “LOG” handler if you omit this file, otherwise the script will error.
Test it
That’s the setup. Try the script in PayPal’s sandbox simulator first. If that works, try a sandbox button on a web page. And if that works, change the values of the script constants and go live!
Links
LiveCode, the easiest, fastest way to program on any OS. A high-level language accompanied by a powerful IDE makes LiveCode a driving force behind modern software development. LiveCode uses a superset of HyperTalk and provides powerful features for serious developers.
LiveCode Journal is a LiveCode journal, full of news, information, member participation, tutorials, and lots of other information. Recommended.
To join the LiveCode Mailing List: Registration Form.
LiveCode Tutorials by Devin Asay at Brigham Young University. A must-read for new LiveCode developers.
To read the LiveCode user forums: https://forums.LiveCode.com/.
Sons of Thunder provides tips, stacks, and useful script snippets for common LiveCode development issues.