Newton 2.x Q&A Category: Application Design

Copyright © 1997 Newton, Inc. All Rights Reserved. Newton, Newton Technology, Newton Works, the Newton, Inc. logo, the Newton Technology logo, the Light Bulb logo and MessagePad are trademarks of Newton, Inc. and may be registered in the U.S.A. and other countries. Windows is a registered trademark of Microsoft Corp. All other trademarks and company names are the intellectual property of their respective owners.


For the most recent version of the Q&As on the World Wide Web, check the URL: http://www.newton-inc.com/dev/techinfo/qa/qa.htm
If you've copied this file locally, click here to go to the main Newton Q&A page.
This document was exported on 7/23/97.

Application Design


How to Prevent an Application from Becoming a Backdrop (8/8/93)

Q: Is there a way an application can request not to be a backdrop application?

A: Yes, adding a non-nil 'noBackdrop slot to the base view will stop an application from becoming a backdrop application.


The Newton Device Reboots When Turned On (8/9/93)

Q: My application has a really tight loop which can take more than a dozen seconds to finish. If the user turns off the Newton while my application is running, nothing happens at first, but finally the Newton turns off. The Newton device reboots when turned on. Why?

A: The reboot is happening because of a design goal. When the Newton OS learns the user wants to do a power off, the OS checks with the running application and says "Please get ready to shut down." If there is no response within about ten seconds the OS assumes that the process could be in an infinite loop. Since the user wants to turn off the Newton device, the OS terminates the application. When powering back up, there is no real clean state to return to, so the OS causes a reboot.

To work around this problem break up long processes so they can run in an viewIdleScript. In general, applications should release the CPU now and then so the OS can do clean up operations.


Optimizing Base View Functions (9/15/93)


Q: I've got this really tight loop that executes a "global" function. The function isn't really global, it's defined in my base view and the lookup time to find it slows down my code. Is there anything I can do to optimize it?

A: If the function does not use inheritance or "self", you can speed things up by doing the lookup explicitly once before executing the loop, and using the call statement to execute the function within the body of the loop.

Here's some code you can try inside the Inspector window:
    f1 := {myFn: func() 42};
    f2 := {_parent: f1};
    f3 := {_parent: f2};
    f4 := {_parent: f3};
    f5 := {_parent: f4};
    f5.test1 := func ()
        for i:=1 to 2000 do call myFn with ();
    f5.test2 := func() begin 
        local fn := myFn; 
        for i:=1 to 2000 do call fn with (); 
        end

    /* executes with a noticeable delay */
    f5:test1();

    /* executes noticeably faster */
    f5:test2();

Use this technique only for functions that don't use inheritance or the self keyword.

Note for MacOS programmers: this trick is analogous to the MacOS programming technique of using GetTrapAddress to get a trap's real address and calling it directly to avoid the overhead of trap dispatch.


Code Optimization (9/15/93)

Q: Does the compiler in the Newton Toolkit reorder expressions or fold floating point constants? Can the order of evaluation be forced (as with ANSI C)?

A: The current version of the compiler doesn't do any serious optimization, such as eliminating subexpressions, or reordering functions; however, this may change in future products. (Note: NTK 1.6 added constant folding, so for example 2+3 will be replaced with 5 by the compiler.) In the meantime, you need to write your code as clearly as possible without relying too heavily on the ordering of functions inside expressions.

The current version of the NTK compiler dead-strips conditional statements from your application code if the boolean expression is a simple constant. This feature allows you to compile your code conditionally.

For example, if you define a kDebugMode constant in your project and have in your application a statement conditioned by the value of kDebugMode, the NTK compiler removes the entire if/then statement from your application code when the value of kDebugMode is NIL.

constant kDebugMode := true;         // define in Project Data
if kDebugMode then Print(...);     // in application code


When you change the value of the kDebugMode constant to NIL, then the compiler strips out the entire if/then statement.


Global Name Scope (6/7/94)

Q: What is the scope of global variables and functions?

A: In NewtonScript, global functions and variables are true globals. This means that if you create global functions and global variables, you might get name clashes with other possible globals. As this system is dynamic, you can't do any pre-testing of existing global names.

Here are two recommended solutions in order to avoid name space problems:

Use your signature in any slot you create that is outside of the domain of your own application.

Unless you really want a true global function or variable, place the variable or function inside your base view template. You are actually able to call this function or access this variable from other applications, because the base view is declared to the root level.

If you really need to access the function or variable from a view that is not a descendent of your base view (like a floater that is a child of the root view), you might do something like:

    if getroot().|MyBaseView:MySIG| then
        begin
            getroot().|MyBaseView:MySIG|:TestThisView();
            local s := getroot().|MyBaseView:MySIG|.BlahSize;
        end;


How to Prevent an Application From Opening (6/9/94)

Q: I do not want my application to open sometimes, for example because the screen size is too small, or because the Newton OS version is wrong. What's the best way to prevent it?

A: Check for whatever constraints or requirements you need early, if not in the installScript, then in the viewSetupFormScript for the application's base view. In your case, you can do some math on the frame returned from GetAppParams to see if the screen is large enough to support your application.

If you do not want the application to open, do the following:
• Call Notify to tell the user why your application cannot run.
• Set the base view's viewBounds so it does not appear, use
RelBounds(-10, -10, 0, 0) so the view will be off-screen.
• Possibly set (and check) a flag so expensive startup things do not happen.
• Possibly set the base view's viewChildren and stepChildren slots to NIL.
• call AddDeferredSend(self, 'Close, nil) to close the view.


How to Create a Polite Backdrop Application (1/19/96)

Q: How do I get backdrop behavior in my application?

A: Backdrop behavior is given to you for free. If your applications close box is based on protoCloseBox or protoLargeCloseBox then your close box will automatically hide itself if your application is the backdrop application. If you also use newtStatusBar as your status bar proto, the appropriate buttons will shift to fill the gap left by the missing close box. Note that you do not have to use the NewtApp framework to use the newtStatusBar proto.

The system will automatically override the Close and Hide methods so your application cannot be closed.

If you need to know which application is the backdrop application, you can find the appSymbol of the current backdrop app with GetUserConfig('blessedApp).

Here are some tips on being a polite backdrop application:
• Your application should be full-screen. (Set "Styles" as the backdrop to see why.)
• A polite backdrop application will also add the registered auxiliary buttons to its status bar. See the "Using Auxiliary Buttons" in the Newton Programmers Guide (Chapter 18.)


How to Respond to Changes From a Keyboard (2/6/96)

Q: I open a custom keyboard to edit my view. How can I tell that the keyboard has been closed so that I can process the potentially modified contents of the view?

A: The viewChangedScript for the view will be called each time the user does something to modify the view. For keyboards, this means the script is called each time the user taps a key. This is the only notification that is provided to indicate the view contents have changed.

There are no hooks you can use to tell you when standard keyboards have closed. If you implement your own keyboard, you could provide a viewQuitScript or other custom code to explicitly notify the target that the keyboard is going away, but we do not recommend this. (There may be a hardware keyboard attached, a system keyboard may be open, or the user may be writing into your view. It is a mistake to assume that the only way to modify your view is through your own keyboard.)

If the processing you need to do is lengthy and would interfere with normal typing on the keyboard, you can arrange it so the processing won't start for a few seconds. This usually gives the user time to type another key, which can then further delay the processing.

To make this "watchdog timer" happen, use the idle mechanism as your timer. Put the code to process the changes in the viewIdleScript (or call it from the viewIdleScript.) In the viewChangedScript, if the 'text slot has changed, use :SetupIdle(<delay>) to arrange for the viewIdleScript to be called in a little while.

If :SetupIdle(<delay>) happens again before the first delay goes by (perhaps because the user typed another key,) the idle script will be called after the new delay. The older one is ignored. SetupIdle resets the timer each time it's called.

Don't forget to have the viewIdleScript return NIL so it won't be called repeatedly.


How to Test Your Application (2/7/96)

Q: Before I ship my application, what should I test?

A: Although there is no complete answer, the following is a quick outline of things that should be tested to ensure compatibility with the Newton OS. Items that are OS or Locale specific are noted. Also note that this list only covers current Apple MessagePad devices.

This is something to help you think of other areas to test. Covering the areas in this list should improve the stability of your application, but is not guaranteed to make it stable and fool-proof.

This list does not cover the functionality of the application itself. That is, it is not a test plan for your application.

1. Versions (Latest supported system updates)
See Current versions of MessagePad devices in the Misc. Q&A

2. Basic Functional Testing
2.1. Launch and use app from internal RAM, memory card, locked memory card, in rotated mode

3. Data Manipulation
3.1. Create and store data in internal RAM
3.2. Create and store data to memory card
3.3. Delete data from internal RAM
3.4. Delete data from memory card
3.5. Move data from internal RAM to memory card and vice versa
3.6. Duplicate data
3.7. Find data with app frontmost
3.8. Find data in app using Find All from paperroll
3.9. Find data in all editable fields
3.10. Check the app name in the Find slip when "Selected" is checked, and check that the app name is correct for the radio button in the Find slip
3.11. If the app implements custom find, make sure other types of find (selected and everywhere) still work.
3.12. Select and Copy data to and from clipboard
3.13. Backup to memory card and restore to different Newton device. Verify that data is intact.
3.14. Backup via NBU and restore to different Newton device. Verify that data is intact.
3.15. File data into folders (if supported.)

4. Communications
4.1. Print data to serial printer and network printer
4.2. Fax data
4.3. Beam data to another 2.x Newton device
4.4. Beam data to a 1.x Newton
4.5. Backup and restore data and app to memory card
4.6. Backup and restore data and app with NBU

5. Exception Testing (all of the following should cause exceptions)
5.1. Create new data to locked memory card
5.2. Delete data from locked memory card
5.3. Move data from internal memory to locked card
5.4. Beam data to a Newton device that does not have the expected application
5.5. With application running from memory card, unlock card with application open.
5.6. With application installed on memory card, unlock card with application closed.
5.7. Install application on memory card, run application, create data, close application, remove memory card.
5.8. Turn power off while application is running (PowerOff handler?)
5.9. Attempt to create new data with store memory full.
5.10. Run application with low frames heap (us HeapShow to reserve memory)
5.11. If appropriate, run application with low system heap.

6. Misc.
6.1. Does application work if soup is entirely deleted from Storage folder in Extras?
6.2. Delete application. Does any part stay behind? (icons? menus? etc.)
6.3. Check store memory and frames heap, install application, check store memory and frames heap. Do this several times and check for consistency
6.4. Do 6.3. and also check store and frames memory after removing application. Is all/most of the memory restored?
6.5. Check frames heap. Launch & use application. Check heap. Close application. Check heap.
6.6. Does the application add anything to the Preferences App?
6.7. Does the application add Prefs and Help to the "i" icon?
6.8. Does the application add anything to Assist, How Do I?
6.9. Launch with pager card installed
6.10. Check layout issues on MP100 vs. MP110 screen sizes (if application runs in 1.x.)
6.11. If multiple applications are bundled together, open all at the same time, check to see that the applications together aren't using too much frames heap.
6.12. Open, use, and close the application many times. Check frames heap afterward to check for leaks.
6.13. If application has multiple components and components can be removed separately, verify that application does the right thing when components are missing.
6.14. Test application immediately after cold resets and warm resets.

7. Compatibility
7.1. After application is installed and run, do the built-in applications work:
Names, Dates, To Do List, Connection, InBox, OutBox, Calls, Calculator, Formulas, Time Zones, Clock, Styles, Help, Prefs, Owner Info, Setup, Writing Practice.
7.2. If the application can be the backdrop (this is the default case)
7.2.1 Do the built-in applications continue to work? The list is as in 7.1. and Extras.
7.2.2 Do printing and faxing work?
7.2.3 Run through the other tests in this document with your application as backdrop.
7.3. If the application can operate in the rotated mode
7.3.1. Perform all tests with the application in rotated mode as well.
7.3.2. Check that screen layouts look correct.


How To Make An Application the Backdrop (8/23/96)

Q: Is there a way to programmatically change the backdrop application?

A: Yes, but only if the "new" backdrop application is one of your applications. Do not set another application to be the backdrop application. GetRoot():BlessApp(appSymbol) will close the current backdrop application and open the new backdrop application if necessary. Note that appSymbol must be a valid application symbol of a current installed and active application. BlessApp does NOT verify that an application can become the backdrop (for instance, it doesn't check the 'noBackdrop flag for an application). For this reason, BlessApp must only be used on your own applications. See the Newton DTS Q&A, "How to Prevent an Application from Becoming a Backdrop" for more information about the 'noBackdrop flag.

Do not call BlessApp from within a part's installScript or removeScript. If you want to do something like this, use a delayed call to use BlessApp.


Children of Exported Protos Are Missing (9/20/96)

Q: I have a template that is based on a user proto imported by my package. When the view based on my template opens, none of the exported proto's children show up. What is going on?

A: When you create children of templates in Newton Toolkit, they are collected in the stepChildren slot of the base view of the template file. If both the exported and importing template have children, they will both have a stepChildren slot. The result is that the stepChildren slot of the importing prototemplate is masking the one in the exported proto. The instantiated view does not collect all the children from the entire proto chain (though NTK does do this at compile time for user proto templates).

The solution for exported user protos with stepChildren is to add a viewSetupChildrenScript to either your exported proto template or the importer that collects all of the stepChildren into a runtime stepChildren array.

// AFTER setting up stepChildren, views which "import" this proto
// must call inherited:?viewSetupChildrenScript();
exporter.viewSetupChildrenScript := func()
  begin
    // get the current value of the "extra" kids
    // ...unless the importer added NO kids, in which case, these are OURS
    local extraKids := stepChildren;
        
    local items := clone(extraKids);
    local kids;
    
    local whichFrame := self; 
    
    while (whichFrame) do
      begin
        // get kids, but NOT using inheritance
        kids := GetSlot(whichFrame, 'stepChildren); 
        
        // copy any extra stepChildren (but if NO extra kids are defined, don't copy twice!)
        if kids and kids <> extraKids then 
          ArrayMunger(items, 0, 0, kids, 0, nil);
          
        // go deeper into the _proto chain (or set whichFrame to nil)
        whichFrame := whichFrame._proto; 
      end;        

    stepChildren := items;
  end;


Note that you will have similar problems with declared children. If you have declared children you will also need to collect the stepAllocateContext slot.


How to Override the Standard Button Bar (11/22/96)

Q: What's the proper way to override the button bar, especially to cover up the buttons on the Newton OS 2.1 devices?

A: We don't recommend that typical applications obscure, cover up, or otherwise modify the standard button bar. From a user interface standpoint, it's a bad idea, because it can make the unit look unfamiliar or (in extreme cases) unusable. Some applications, typically those created for vertical markets, are designed to "take over" the interface, in which case it may be permissible to cover or disable the button bar.

GetRoot().buttons.soft will be non-nil if there is a "soft" button bar, that is, the button bar is located on the drawable screen. GetRoot():LocalBox() returns the rectangle that encloses the screen and the tablet. GetAppParams() returns information about the useful area of the screen, excluding the soft or hard button bar. Used together, this information will allow you to implement any combination of button bar disabling and/or button bar obscuring.

(There is also an Newton 2.1 OS function called KillStdButtonBar. That API is designed for use when you want to actually remove the button bar so you can replace it - probably with a floating slip. It is a fairly expensive call and does a lot of things you don't need if all you want to do is take over the screen. We recommend avoiding that call if possible.)

If the goal is simply to maximize the visible area of the base view, then the button bar should be obscured only for devices with a soft button bar (for instance, a MessagePad 2000). On devices with a "hard" button bar (for instance, a MessagePad 130), the root view encompasses a larger area than the LCD display because the input tablet is larger to account for the "hard" button bar. Drawing is limited to the screen so applications wouldn't increase their visible area by covering the "hard" button bar.

The "soft" button bar can simply be covered by your application's base view. The only trick is properly detecting if there is a soft button bar, and finding out where on the screen it happens to be. This code will give you the largest drawable application box, covering any soft button bar but not covering any hard buttons.
    local params := GetAppParams();
    if GetRoot().Buttons.soft then
        self.viewBounds := OffsetRect(UnionRect(params.appArea, params.buttonBarBounds),
            -params.appAreaGlobalLeft, -params.appAreaGlobalTop)
    else
        self.viewBounds := params.appArea;


If the goal is to to prevent users from accessing the buttons, then the button bar should be obscured regardless of whether or not it is on the LCD screen. On units with a hard button bar, you must take into account the fact that part of the base view will be off-screen. (For instance, don't place your close box under the silk-screened buttons.) A simple way to accomplish this is by having a child view whose bounds are the appArea and locating the rest of the application within that child.

Note that on some Newton devices (for instance, the eMate 300), the buttons are not located in a view at all. On these devices, covering the entire tablet does not prevent the user from accessing the buttons (for instance, opening up the Extras Drawer).

Below is some sample code you can add to your base view's viewSetupFormScript to cover the entire tablet:

    local params := GetAppParams();
    self.viewBounds := GetRoot():LocalBox();
    if params.appAreaGlobalLeft then
        self.viewBounds := OffsetRect(self.viewBounds, -params.appAreaGlobalLeft, -params.appAreaGlobalTop);


Creating Large Strings (1/3/97)

Q: What is the best way to create a really large string?

A: The best way is to create the string as a virtual binary object (VBO). VBOs are described in the chapter "Data Storage and Retrieval" in the Newton Programmer's Guide.

To create a string as a VBO, you first need to create a VBO of class 'string. Next, you need to use the global function BinaryMunger to munge an empty string into the VBO. This will properly prepare the binary object to be used as a NewtonScript string.

Finally, use the global function StrMunger as often as needed to copy new string data into the VBO. Here is a code example:

// Prepare a VBO to be the string
local myString := GetDefaultStore():NewVBO( 'string, Length("") );
BinaryMunger( myString, 0, nil, "", 0, nil );

StrMunger( myString, StrLen( myString ), nil, "My new string", 0, nil );
// Repeat with more data if necessary...


Note that unlike the C language's stdio library function, the NewtonScript StrLen function does not need to traverse the string to determine the string length, so you probably don't need to worry about performance hits from its usage.

Not all NewtonScript routines will necessarily "preserve" the VBO nature of large strings. For instance, if you concatenate strings using the Stringer global function or the & or && operators, the result is currently a non-VBO string. Be aware that if you accidentally create a very large non-VBO string, the code may throw a "out of NewtonScript heap memory" evt.ex.outofmem exception.


Strategy for Saving Modified Data (4/8/97)

Q: What's the best way to save modified data to a soup? For example, if I try to save the contents of a clEditView every time some change is made, typing is very slow. So, when should it be saved?

A: The best way we've found is to start a timer every time a change is made, and save the data when the timer expires. If a change is made before the timer expires, you can reset the timer. This way, the longer operation of saving to a soup won't happen until after the user pauses for a few seconds, but data will still be protected from resets. The data should also be saved when the views that edit it are closed, or when switching data items.

The timer can be implemented several ways. If no view is available, AddDelayedCall or AddDelayedSend can be used. The OS also provides AddProcrastinatedSend and AddProcrastinatedCall, which more or less implement the timer-resetting feature for you.

The best way to implement the timer when views are available is using the viewIdleScript. The viewIdleScript is preferred over AddProcrastinatedCall/Send because of better management of the event queue by the OS. When you call SetupIdle to start an idle timer, any existing idle timer is reset. Procrastinated calls/sends aren't currently implemented by resetting an existing timer, but rather by creating a delayed event which fires for each call and then checking a flag when the timer expires to see if it's the last one.

Where these methods are implemented depends on what layer of your code manages the soup entry. With the NewtApp model, the Entry layer manages the data, and each view in the Data layer is responsible for stuffing the modified data in the target frame, which is usually a soup entry. The entry layer implements StartFlush to start the timer, and EndFlush is called when the timer expires and which should ensure that the data is saved to the soup.

Your StartFlush equivalent could be implemented something like this:
    StartFlush: func()
        begin
            self.entryDirty := TRUE;
            :SetupIdle(5000);    // 5 second delay
        end;


Your viewIdleScript would look something like this:
    viewIdleScript: func()
        begin
            :EndFlush();
             nil;            // return NIL to stop the idler until next StartFlush
        end;


And your EndFlush equivalent would look something like this:
    EndFlush: func()
        if self.entryDirty then
            begin
                // getting data from editView may not
                // be necessary at this point

myEntry.editViewData := <editView/self>.viewChildren;
                EntryChangeXmit(myEntry, kAppSymbol);
                self.entryDirty := nil;
            end;


Implementing EndFlush as a separate method rather than just putting the contents in the viewIdleScript makes it easy to call the method from the viewQuitScript or viewPostQuitScript, to guarantee that changes are saved when the view is closed. (The viewIdleScript may not have been called if the user makes a change then immediately taps the close box or overview or whatever.)


Detecting/Preventing Package Installation (4/18/97)

Q: How can I keep my package from being installed on some devices that may not support it? How can I find out if my package has just been downloaded or Put Away, as opposed to being installed as a result of card insertion or resetting?

A: In Newton 2.0 and later releases, frame parts (including form/application parts and auto parts) can have a DoNotInstall function. This function is called when the package containing the part is activated for the first time as a result of downloading to the unit, putting away to the Extras Drawer, or installation with API functions such as SuckPackageFromBinary or SuckPackageFromEndpoint. The function is not called when the package is installed as a result of card insertion, resetting the unit, or moving the package from one store to another using the Extras Drawer filing. This function is incorrectly mentioned in the Newton Programmer's Guide and Newton Programmer's Reference as DoNotInstallScript. The proper name of the function is simply DoNotInstall.

The function takes no arguments. If the function for any part in a package returns non-nil, the entire package will not be installed on the device. This provides a convenient way to prevent installation on devices that do not support your package. It's considered bad form to simply fail to install and provide no notification to the user. We recommend at least using GetRoot():Notify(...) to display a message explaining why you are not installing.

The function is not copied to internal memory before executing. You must take care to EnsureInternal anything this function leaves behind to avoid the "Grip of Death" problems (the error with the alert "The package 'MyApp' still needs the card you removed...").

To create this function for a part, use SetPartFrameSlot. For example, to create a package that will not install on any unit after the year 2000 (because the world will have ended anyway), do the following:

    SetPartFrameSlot('DoNotInstall, func()
        if Time() >= 50492160 then
            begin
                GetRoot():Notify(kNotifyAlert, "Millenial",
                    "This application, and all existence, has expired.");
                true;    // return non-nil to prevent install
            end);


Note that this does nothing to prevent packages that were installed before the year 2000 from continuing to function.

Because this script executes only when a package is first installed on a given device, it may be used to set a flag that can be used by other parts of your application to do things like suggest user registration, go into demo mode, show some extra help, etc. It's probably not appropriate as a way to initialize user preferences or create initial data. Operations like that are best done by checking each time the package is installed or launched, and initializing then if it hasn't been done. This is the case because a user may install your package on a card, then remove that card from one machine and insert it into a different machine that has not previously been used with your application. The part's InstallScript will execute in this case, but the part's DoNotInstall function will not.


NEW: Creating About Slips for Extensions (4/27/97)

Q: The default behavior of tapping on an icon in the Extensions folder is to bring up a generic notification. How can I make it so that tapping on it brings up my own informational slip?

A: Here are the steps to follow if you want an Extension to display a custom slip when it is tapped:

1) Change your part to be a form/application part instead of an auto part. This is necessary so that a slip can be displayed when the icon is tapped.
2) Add a layout to your project that is just a floater with the information you want to display. Mark this layout as the main layout. A view based on this floater will open when a user taps on your part's icon.
3) Add the following code to a text file in your project. This will ensure that the icon will show up in the Extensions folder and not the Unfiled Icons folder:
SetPartFrameSlot('labels, '_extensions);

Note that form/application parts and auto parts have two important differences in the behavior of their InstallScript part method.
* The InstallScript for a form/application part only takes one argument, whereas the InstallScript of an auto part takes two arguments.
* The InstallScript of a form/application part is EnsureInternal'ed, an auto part's InstallScript is not.


NEW: Adding a DeletionScript to Your Project (5/12/97)

Q: When I add a DeletionScript to my part, it doesn't get called when the user scrubs the package. Why is DeletionScript not called?

A: You are probably incorrectly adding the DeletionScript to your part. The DeletionScript must be defined in a manner different from the part's InstallScript or RemoveScript. You must explicitly set the DeletionScript slot in your part frame using NTK's SetPartFrameSlot global function. Here is an example:
SetPartFrameSlot('DeletionScript, func() print("Howdy!") );

Note that you will have to use SetPartFrameSlot for any part slot other than InstallScript or RemoveScript.