Mottek: 2011-01-09

Autoparts with tntk

Autoparts for the Newton are packages which do not have any visible user interface, but add functionality behind the scenes. They are usually listed in the Extensions folder of the Extras drawer.

It is possible to use tntk to create autoparts, and this blog entry demonstrates how to add a module (which admittedly doesn't do much) to the NiftyDrop backdrop application.

The example source can be found on SourceForge.

Project File

The tntk project file is very similar to a project file for a regular application, the only difference is that it uses the part type auto instead of form.

Below you can see that the project contains only one part, which has the type auto and is made up of only one source file, main.newt.

{
    parts: [
        {
            main: "main.newt",
            files: [],
            type: 'auto
        }
    ],

The package name definition and the reference to the platform file are next:

    name: "NewtAutopartExample:40hz",
    platform: "/Applications/Newton/NTK 1.6.4/Platforms/Newton 2.1"
}

The platform file is part of the Newton Toolkit, and tntk uses it to find predefined constants such as proto names.

Main View

The only source file the example uses is main.cpp.

The first part of the file are common definitions of constants (the naming convention I chose is to start constants with a k, methods with an M and fields with an f):

constant kAppSymbol := '|NewtAutopartExmple:40Hz|;

constant kNiftySym := '|NiftyDrop:HyprMynd|;
constant kNiftyRegistry := '|Registry:NiftyDrop:HyprMynd|;
constant kModuleSym := '|Nifty:NewtAutopartExmple:40Hz|;

Next follows a function used to add a module to NiftyDrop:

constant kAddToRegistry := func (module) begin
    local reg := GetGlobals().(kNiftyRegistry);
    local sym := EnsureInternal (module.symbol);
    if not reg then begin
        reg := TotalClone ({
            modules: {},
            prefs: {},
            infoItems: {}
        });
        GetGlobals().(kNiftyRegistry) := reg;
    end;
    reg.modules.(sym) := module;
end;

The function creates NiftyDrop's module registry if it doesn't exist already, and uses the new module's symbol as a slot name to refer to the module code.

A function to remove a module from NiftyDrop is next, it simply removes the slot from the module registry and also from the set of active modules:

constant kRemoveFromRegistry := func (sym) begin
    local reg := GetGlobals().(kNiftyRegistry);

    RemoveSlot (reg.modules, sym);
    p := GetAppPrefs (kNiftySym, {});
    SetRemove (p.activeModules, sym);
    RemoveSlot (p, sym);
    EntryChangeXmit (p, nil);
end;

Next up is the actual module view. As the other elements defined so far, it is a constant, declared for later use. The view itself is just a gray rectangle with the default size of 100x60:

constant kModuleView := {
    viewClass: clView,
    viewFlags: vVisible,
    viewFormat: vfFillGray,
    viewJustify: vjParentLeftH + vjParentTopV,
    viewBounds: {left: 0, top: 0, right: 100, bottom: 60},

NiftyDrop expects a number of additional slots, most importantly the name and symbol:

    resizeable: true,
    symbol: kModuleSym,
    name: "Example Module",
    minWidth: 100,
    minHeight: 60,
    widthIncr: 1,
    heightIncr: 1,

};

The view defined in kModuleView offers no user interaction and has no other visible elements than just a gray rectangle. It would be very simple to add new views in an array in the stepChildren slot of the view.

All of the code comes together in the install and remove scripts. The Newton Toolkit uses a slightly different naming convention and shifts some code around. Instead of the InstallScript and RemoveScript function used in the NTK, I'm using a slightly different setup below.

First, the script run at installation time adds the module to the module registry, and let's NiftyDrop know about it:

{
    devInstallScript: func (partFrame, removeFrame) begin
        call kAddToRegistry with (kModuleView);
        if Visible (GetRoot().(kNiftySym)) then GetRoot().(kNiftySym):prefsChanged ('moduleAdded, nil);
    end,

The script run at removal time (e.g. when freezing or deleting the extension, or removing the card it is stored on) closes NiftyDrop and then removes the module from the registry:

    devRemoveScript: func(partFrame) begin
        if Visible (GetRoot().(kNiftySym)) then GetRoot().(kNiftySym):Close ();
        call kRemoveFromRegistry with (kModuleSym);
    end,

The actual installation script runs the devInstallScript and returns the removal script to the system:

    InstallScript: func (partFrame) begin
        local removeFrame := EnsureInternal ({removeScript: partFrame.devRemoveScript});
        partFrame:devInstallScript (partFrame, removeFrame);
        return removeFrame;
    end,
};

Notes

tntk is creating NewtonScript packages in a quite unique fashion using NEWT/0. Technically, a tntk program is exectuted as NewtonScript, and the return value of the program is input to the package creation step. That means that a program is not just compiled, but it generates itself. The return value is the code between the last pair of opening and closing braces in the example above, i.e. a frame containing the installation and removal scripts. These scripts in turn refer to the rest of the code.

One caveat with this process is that NEWT/0 captures the complete execution environment and returns it. Since it is executing a function, and since NewtonScript functions are proper closures, care must be taken to not pull in objects which are not needed. If in the example above the elements are not declared as constants, they will be passed to any function within the code as the environment for the function's execution, which in extreme cases causes the NewtonScript interpreter to run out of memory.

An improvement of the package generation and compilation step would be to use NEWT/0 only for function bodies, and use a more traditional code generation process to generate the package code.