ESG Framework 1.0 beta 3 ...is a FaceSpan template intended to make doing some actions easier. Right now it includes: Bringing "Where is the application?" dialogs under your control, making your application safer to use and preventing the user from picking apps that won't work with your application. Providing an area to initialize and clear out your globals, along with making it easy for you to prevent your application from trying to work without going through its initialization routine. Making localization easier by providing a string lookup mechanism, and providing a way to use menus without having to remember indices or refer to their text names. Loading and saving preference files as script objects, including a way to make it automatic when your app is launched and quit. Dialogs that replace the Display Dialog command (bringing the dialog process under your control.) FS dialogs are safe to call even if your app is floating on another app. Prefab Splash Screen and About dialogs (which you can change to look any way you want, of course). Requirements FaceSpan 3.0.1 or higher. ESG Framework requires the Finder for some operations to succeed. If you are running in an environment where you quit the Finder, some operations will not work. Notably, this includes saving and restoring prefs, as well as avoiding the "Where is the application?" dialog. We highly recommend you do not quit the Finder in an ESG Framework-based app. Using the ESG Framework's Features Introduction (Getting there! No more features, honest!) The invalid Constant The invalid constant («constant OthrEdon» for use in plugins) is used by FaceSpan in many places. Some Framework routines return invalid when an error has occurred that the routine can't handle in any other way. The routines that do this will say so in their descriptions. Make sure you check the return value and handle it. (I wouldn't expect you to see invalid in most running apps, but you should handle it anyway.) Resolving tell app calls with the Alias Method One way to avoid seeing the Where is the Application? dialog is by creating aliases to the appropriate applications in the folder with your app. The aliases must be named the same as the name of the application on the machine the application originally came from. To make this work, you have to create a list of records in the ƒSplashScreen dialog. You set this list in a property, myAppInfo. The records you use (called appRecs) have this format: {appCode:(creator code of theApp), shortName:2, fullName:"Name Of App On Your System" mustExist:false} Here's what Netscape's appRec might look like: {appCode:"MOSS", shortName:2, fullName:"Netscape Communicator", mustExist:true} The appCode is the app's creator code. The shortName is an index into your string table (tblMessages, accessed with LookupMessage() ) which is the name the framework uses in dialogs. It should be the least specific name for the application that will still fit your application's needs (remembering that newer versions of the app may have been released after your application). The fullName is the name as it appears in tell application "fullName" texts on your machine. This does not have to match the app's name on the user's machine, but it will match the name used in the alias file. If mustExist is true, the app will stop running if it/the user can't find the file. If mustExist is false, your app will continue if it isn't found. Saving and Loading Preferences The Framework automatically reloads your preferences at startup. You are responsible for calling WritePrefs() to save your preferences. One way to do so is to put a call to WritePrefs() in the quit handler. There are also three prefs-related routines you will want to look at: PrefsNotReadable() This is called when your app tried to read in a preferences file, but it failed. If you want to warn the user or otherwise do something about your preference file failing, here's the place to do it. Note that the app will continue to open (it will init with default preferences). CopyPrefsToApp() This is called by ReadPrefs() to take the loaded preferences script object and put it in your globals or whatever you plan on doing with preferences. This routine must successfully initialize whatever preferences structures your application uses (the application will continue opening even if ReadPrefs indicates it had a problem). CopyAppToPrefs() This is called by WritePrefs() to take the app's current preferences and place them in a clean preferences script object. Preference File Name The name of your preferences file is stored in item 10 of the FrameWork string table in ƒSplashScreen. Localizable Menus The Framework uses the gMenus global to enable you to easily work with menus without knowing what their current titles are. The gMenus global is initalized from the storage item siMenuInfo, which is one you should alter. In siMenuInfo, each menu should have a property which is its index in the menu bar. Each menu item is a list of indices which track down the heirarchy. For example, the menu item File->Page Setup from the Development Environment would look like: property mFile : 2 property miFilePageSetup : {mFile, 11} The sub-menu item Object->Alignment->Align Lefts would look like: property mObject : 4 property mObjectAlignment : 17 property miObjectAlignmentAlignLefts : {mObject, mAlignment, 1} As they get longer, you'll definitely want to abbreviate some things. :) You can also make lists of menu item properties which can be passed to routines which enable/disable/check/uncheck the entire list for you: property milExample : {miFilePageSetup, miObjectAlginmentAlignLefts} There are a number of routines to make it easy to use these routines: GetMenuRef(fwMenuReference) GetMenuRef(fwMenuReference) will return an object reference for the specified menu or menu item from the menu reference defined in gMenus. You can use the reference to change properties of the menu item (checked/enabled/menu text/etc.) If the menu you ask for doesn't exist, GetMenuRef returns invalid. SameMenu(menuReference, fwMenuReference) SameMenu will return true if the two references are to the same menu, false if they're not or one of them doesn't exist. You'll use this in your on chosen handler(s). menuReference is a FaceSpan menu object reference (menu item x of menu y), while fwMenuReference is one of the properties in gMenus. EnableMenu(menuList) DisableMenu(menuList) CheckMenu(menuList) UncheckMenu(menuList) These four routines take either a Framework menu reference or a list of Framework menu references and sets their properties as indicated. SetEnabled(menuList, newVal) SetChecked(menuList, newVal) These let you control the new value (for example, if it's a variable). Again, the routines take a list of references or just one and the new value for the appropriate property. SetMenuName(fwMenuReference, newName) This sets the menu/menu item's name. Don't forget that your constants are all stored in gMenus, so you need to refer to them like this: SetChecked(milExample of gMenus, true) While this might seem to be a pain to set up, it should be fairly easy to write a script that will look at a menu heirarchy from in FaceSpan and create the properties for you. Look for it in the next version of Framework. Routines provided for you (there are others, and many routines are documented in the source itself) LookupMessage(messageIndex) LookupFWMessage(messageIndex) These two commands lookup messages in your message list or the framework's message list (respectively). By using these wherever you would have used a string literal (as in "This is a String Literal"), it's very easy to localize your application. DoDialog(dlgRec) DoErrorDialog(dlgRec) Call this when you would have used display dialog to use FaceSpan dialogs instead. This prevents any possible problems using modals when your app isn't frontmost (FaceSpan dialogs are always safe; OSAX-dialogs may not be.) DoMessageDialog(messageText) This does a DoDialog() with just a message and an OK button, common when reporting errors or other status information. IsOK(someText) IsCancel(someText) IsQuit(someText) IsYes(someText) IsNo(someText) These are useful to see which button was pressed in your DoDialog(). These use localized versions of OK/Cancel/Quit/etc. for comparison. Since DoDialogs default to OK and Cancel for the buttons, your DoDialog calls might wind up looking like this: set theResult to DoDialog({dlgText:LookupMessage(3)}) if IsOK(dlgButton of theResult) -- blah blah end if GetOK() GetCancel() GetQuit() GetYes() GetNo() These return the localized OK/Cancel/Quit/etc. text, useful for shortening your DoDialog lines. ReadPrefs() WritePrefs() These two routines make it easy for you to read and write your application's preferences to files in the preferences folder. The default Init() routine calls ReadPrefs(). All you have to do is make sure WritePrefs() is called when you want to save your preferences. InitApp() InitApp() should be called by every planned or possible accidental entry point in your application. This includes run, open, reopen, chosen, and any events your app expects to receive from other applications/users writing scripts. If InitApp returns false, the app could not initialize properly and should quit. If InitApp returns true, either the app was already initialized or it successfully initialized. ƒSplashScreen:Init() This is where all the initialization for your application should be placed. This is where the framework looks for the applications you want to tell to and handles making sure FaceSpan can find them. This is called once and only once per run by InitApp(), unless someone is deliberately trying to hose your application. AppFound() AppFound lets you ask if an application is available for you to use. You can ask by the creator code or name of the app. Things to look for when starting a new app Change the properties of the Project Script to fit your application. Look everywhere for the word "Framework" and substitute in your app's name. It would be much nicer if Framework came with a script that did this for you. Search the scripts for "FIX THIS". These words are found in various places where you will probably have to alter the template to make your app work the way you want it to. Edit the text in the About Dialog. Things to do when you are ready to distribute your application If you're going to distribute a run-only application that uses preferences, you should also copy your siBlankPrefs script into the Script Editor, save as a run-only script object, then load script that object into the siBlankPrefs storage item. That way people can't read your preferences files either. This script should work when run from the Message window: set contents of storage item "siBlankPrefs" to load script file "yourFile" Once you do this, you will not be able to open the storage item unless you clear it out (set contents of storage item "siBlankPrefs" to "", or something similar). kDebug is a property in the Project Script that tells the framework whether it should report errors in framework-related things (like the DoDialog command) or if it should just hide these problems. It also tells the framework to do some things automatically (like closing and hiding the disclosure triangle in the Splash Screen). Set kDebug to false when shipping. Things you shouldn't do You cannot tell to any outside application (except the Finder) before InitApp() successfully completes. You cannot put any tell statements to outside applications in any script loaded before the aliases are set up in ƒSplashScreen's Init() handler. This should only include the Project Script and ƒSplashScreen. In general, you should not change the contents of table "tblFWMessages", except to fix app names or localize the text. These messages are used by the framework. Generally, you shouldn't change the contents of storage item "siFrameworkRoutines", but advanced users may want to replace routines in it with different methods (preferences, for example). The names of any of the storage items that come with Framework need to be left alone as well. Lastly, you shouldn't change the standard four icons that come with without updating any places in the Framework they appear with a decent replacement. The ESG Framework was meant to run as a complete application. There is no good way to prevent the "Where is the Facespan Extension?" dialog in a mini app, and if your app is in the background when it appears it will crash. (This is true of any mini app, not just ones made with this framework.) When you are working in the Development Environment, make sure you close all of your windows before Running your app. If the SplashScreen is open it can mess up your app. (This is true of any FaceSpan app, not just ones made with this framework.) Future Ideas and Other Notes From experiences in creating AmSt 1.3, here's some info about what worked well and what didn't in AmSt 1.3 Here's what seems to work well: Menus Localizable Messages Select All mechanism The Alias Method (for preventing the Where Is...? dialog) Here's what hasn't been working so well: Init() is slow. I suspect this is due to using the Finder to create and verify aliases. Unless I want to commit to an OSAX that allows faster creation and verification of alias files (unlikely), this won't change. It does work properly, however. Redirects and utility functions in the Project Script. While it's very nice to have these there (because it reduces typing for the popular routines) it lowers encapsulation and forces globals into the Project Script. Also, these are a security risk, since they're all potential entry points into your app. Preferences as script files. They work, but to make them bulletproof you need to either trap every access to preferences with something that resets them to defaults or when loading preferences scan the data and verify that the various items are what you expect. Loading prefs via an OSAX that can write AS structures may be better, but it is probably even more work than verification on load. (If nothing else, you have to write a load and a store. In the current scheme you assume that all data is valid when you store.) Named globals in scripts and script apps are easy to find (since the base source is public). If someone wants to mess with your app they can do so fairly easily. This could possibly be handled in a "create final app" script by scrambling the names of the globals? The various Debug functions don't work as well as I'd hoped, though it helped debug some things in the Framework. These might all disappear by 1.0. One of the goals of the Framework is for you to be able to move to a new version of the Framework when it is released without having to do a huge pile of work. This requires separating a number of pre-written partial functions (preferences, for example) into pre-written and user-changable sections. This requires enhancements to various parts of the Framework that will make it require less altering to fit the application and/or moving things that need to be altered out of the Framework routines (this would free up space in the Project Script, and look nicer to less-experienced FaceSpan creators). This implies more encapsulation of the Framework routines in siFWRoutines and the Framework section of the Project Script. (The real goal is for you to only have to copy your windows, artwork, forms, and non-framework storage items over, cut and paste the text from the top of the Project Script to the top of the new Project Script, and fix the menus.) It'd be even nicer if some kind of script could do this for you... Scripts that automate many of the common changes/additions/whatnot that need to be made to the Framework. One would be a script that will take your menu structure and build a siMenuInfo script for you. This should be somewhat simple now. Clean up ƒSplashScreen's script. Perhaps create a Shutdown() routine in ƒSplashScreen, and have quit call that by default? The default Shutdown could bring SplashScreen back to the front with a message "shutting down", call WritePrefs, close all open windows, then return true if everything worked, or false if not. Either/or tags on creating aliases. (So your app requires Netscape or Internet Explorer or both, but needs one.) A Framework-based Plugin loading system. Need to add more error trapping in critical areas. (Most notably preferences.) ReadPrefs should take a file, and if that file is not 'invalid' it tries to read preferences from that file. (The easy way to handle someone opening a preferences file.) Multiple-user operation. The Alias Method is not multiple-user compatible at this time, because it needs to write to its own folder and applications are not guaranteed to be able to write to their own folder in the multiple-user setup. Even before OS9, At Ease would prevent this from working, but if all you're concerned about is OS9, you can just disable alias creation and use the new OS9 methods to make your scripts compile and run safely. I need to look at the function structure used in making aliases. It's already become pretty messy. A decent way to check for Scripting Additions (probably by name, as the easiest way to check to see if it's actually responding is to do something harmless that requires the OSAX.) Finish the Generic Select All handler, including checking for appropriate selection abilities. Additions ideas: An Outline Listbox made through creative scripting and the List with Metas form. If it ever exists, it will be clunky and slow. A prototype "Wizard/Assistant" dialog, made by faking it with a tab panel. A Status Window, with update functions and logging. ...and I'm always looking for more ideas! Version History Changed in 1.0b3 Ran into a problem with setting the titled property of windows at runtime. This has been disabled in this version of the Framework (the DoDialog() windows have had their titled properties set to false). I'm not sure if this means two different sets of windows (to have titled and non-titled) or picking a generic title if one is not provided (the app's name might be a good choice). Added siMenuInfo, gMenus, and the localizable menu setup. There is no more QuitApp(), you just add the code directly to the quit handler. Quit, like chosen and run, is sufficiently unlikely to change to make getting rid of the abstraction feasible. Unfortunately, this makes it harder to determine some data (say, if the app successfully initialized before quit was called.) Added a SetStatus() routine to Set and draw the Status label in ƒSplashScreen. Framework now comes with another project: "Framework Additions". This will contain useful windows/dialogs that you can just cut and paste into your Framework project. The first one is an RGB Color Picker. Added VerifyApps() to the Framework Routines. Call this to have the Framework reverify the app aliases. (This isn't really that useful, because usually by the time you'd want to call it it's too late, but if you want it you've got it.) You can now specify that an application is not required. The Framework will look for it but will continue on if it isn't there. Added a routine (AppFound) to look up which apps were found during the resolution process. You can use either the creator code or the name on the development machine to look it up. The Framework now asks the Finder for an application file of the appropriate creator code, and uses that file if it is found. You can easily disable both the confirmation dialog and the entire searching with the Finder by changing some constants. Changed how we check for running in the Development Environment. Added DoMessageDialog(), described above. Added Select All to the Edit menu, and added a default way to handle Select All. Read the comments in the project script for more info. Added a generic error trap in the Project Script for show balloon (then disabled it, see below). If you didn't know, it's possible to get an error back from show balloon if you have a script attached to an object that handles show balloon and the user moves the mouse really fast. This error trap prevents returning any error from show balloon. Most show balloon handlers exist to change the text of the show balloon (say, when a window item is disabled). Disabled the show balloon idea, because if you hover over a window item that doesn't have balloon text assigned to it, the cursor switches between the beach ball and the arrow. Doh! (You can see this in AmSt's shipped code.) Changed in 1.0b2 Moved the short name part of an appRec into the string tables, so that it can be localized. The Alias creation routines will now make new aliases if the name no longer matches. (It ignores aliases that are the right creator code but the name doesn't match...) Added scanning for applications using the Finder. Added automatic hiding or showing of the text lists in ƒSplashScreen based on kDebug. Moved much of ReadPrefs() and WritePrefs() into siFrameworkRoutines. They now call a few routines in the Project Script that the app designer might modify. The overall effect is a smaller Project Script. Fixed a bug in and the name of "About FrameworkŠ" (it was calling a routine that no longer exists.) Fixed a naming error in ƒSplashScreen: the caution icon should be named icnYourIcon instead of icnESGLabs. Added a bunch of quick accessor functions for the most common Framework strings: OK, Cancel, Quit, Yes, and No. Fixed the positioning of the string tables (you could see the top of them in the Alias Method demo app). Legal Info The ESG Framework is provided as freeware for FaceSpan developers to use as they see fit. I'd appreciate it if (somewhere in your documentation) you mentioned your app is based on the ESG Framework and include a pointer to the website so others can get a copy if they might find it useful. Hopefully you will find this useful, and I'd appreciate any feedback you have. Michael Miller ESG Labs http://www.esglabs.com/ techsupp@esglabs.com June 1, 2000