Scripts/HiveOff
Hiving off the JWD interface
This is a technique to split off a wd-based UI into a separate app.
The "glue" for the 2 parts of your app: File:Facelink.ijs
The "duty cycle" supporting facelink.ijs (residing in _z_ locale): File:Faceduty.ijs
Rationale
J701 was released in Jan 2011 without support for the popular wd-based interface (11!:0) of j602. Users were expected to port their own j602 apps to use either JGTK or the JHS framework.
This policy, perfectly justifiable though it is from the vendor's pov, can leave a critical utility stranded in the world of j602. Converting an app to the JHS framework (or to JGTK) is not straightforward and can necessitate an entire reimplementation of the script.
Bill Lam has recently released a prototype wd-emulator for JGTK (Feb 2012) which will make porting an app a lot easier.
Why might you want to hive-off a script's UI as a separate asynchronous app?
- As a stop-gap measure to get a key j602 script operating under j701
- To preserve a finely-tuned j602 UI in the j7 environment (JHS or JGTK)
- As an intermediate stage to replacing a wd-based UI with one written in Cocoa / Objective-C (the pro language for UI implementation on the Apple Macintosh)
- to build a collection of stock reusable UIs which are independent of the JVERSION hosting the app proper.
Case Study
File:Tempcon.ijs is a straightforward JWD sample script built using j602's Form Editor.
Three fields, Celsius, Fahrenheit and Kelvin, accept new numerals. Whichever field is edited, pressing Enter will update the other two accordingly.
A further edit field shows an integer: the decimal places in the temperature fields. It can be overtyped, or two arrow buttons step it.
We split it into two communicating parts: File:Tempface.ijs (the bare UI) and File:Tempserv.ijs (the faceless app).
Note that the wd-calls comprising the UI have not actually been stripped out of tempserv.ijs, but merely disabled. This is recommended to retain comparative diagnostics during development.
To run the two parts, load them into distinct independent j602 sessions. Both scripts run on load. They communicate via two scripts which are written and refreshed by the two apps: ~temp/face_in.ijs and ~temp/face_out.ijs. The boxed names of the two files are the nouns: FACEIN and FACEOUT, and that is how we'll refer to the files.
Method
Determine the wd-buffers which define the state of the UI
In this example the important buffers are degc degf degk sig, to which we add: TITLE.
Ensure that these buffers are kept in-step with their controls. (In a typical JWD UI these are allowed to get out-of-step, because it is thought not to matter.) To do this, examine every wd-statement and ensure that both screen and buffer are updated together.
For example:
updeg=: 3 : 0 NB. update degree fields 'C F K'=. 3 {. y wd 'psel fm; set degc *',degc=: dp C wd 'psel fm; set degf *',degf=: dp F wd 'psel fm; set degk *',degk=: dp K )
instead of:
updeg=: 3 : 0 NB. update degree fields 'C F K'=. 3 {. y wd 'psel fm; set degc *',dp C wd 'psel fm; set degf *',dp F wd 'psel fm; set degk *',dp K )
Split the script
Make 2 copies of tempcon.ijs, renaming them tempserv.ijs and tempface.ijs. Place at the head of each script:
load '~proj/facelink.ijs' BUFALL=: 'degc degf degk sig TITLE' CLIENT_z_=: 0 NB. in tempface.ijs this should be CLIENT_z_=: 1
Rewrite fm_run in each script
Modify the UI-creating verb: fm_run as appropriate for the two scripts.
In tempcon.ijs fm_run was originally like this:
fm_run=: 3 : 0 NB. Init and run the app fm_close'' wd FM NB. initialize form here (BUFALL)=: <'' wd 'set sig *',sig=: ,'2' changec 100 wd 'pshow;' wd 'psel fm; pn *',TITLE=: 'Temp Conversion' )
In tempserv.ijs rewrite fm_run
- to disable creation of the UI (fm)
- insert user hooks in the code of faceduty.ijs (loaded by: facelink.ijs) by overriding the verbs die_z_ and doframe_z_, and possibly also resurrect_z_.
- start the duty cycle with run'' (equivalent to duty_cycle_z_ 1)
fm_run=: 3 : 0 NB. Init and run the app NB. fm_close'' wd=. empty wd FM NB. initialize form here BUFALL=: 'degc degf degk sig TITLE' (BUFALL)=: <'' wd 'set sig *',sig=: ,'2' changec 100 wd 'pshow;' wd 'psel fm; pn *',TITLE=: 'Temp Conversion' die_z_=: mydie_base_ doframe_z_=: mydoframe_base_ run'' )
In tempface.ijs rewrite fm_run
- to initialise the buffers in a more useful way (for test purposes)
- insert user hooks in the code of faceduty.ijs (loaded by: facelink.ijs) by overriding the verbs die_z_ and doframe_z_, and possibly also resurrect_z_.
- start the duty cycle with run'' (equivalent to duty_cycle_z_ 1)
fm_run=: 3 : 0 NB. Init and run the app TITLE=: '(fm)' doframe_z_=: mydoframe__ fm_close'' wd FM NB. initialize form here (BUFALL)=: <'<UNSET>' NB. wd 'set sig *',sig=: ,'2' NB. changec 100 wd 'pshow;' wd 'psel fm; pn *',TITLE=: 'Temp Conversion' run'' )
Provide a verb (wup) to refresh the screen
In tempface.ijs write a verb wup to take each of the main buffers and write their contents back to the screen.
This verb will be called by doframe_z_ in the duty cycle.
wup=: 3 : 0 NB. update window from bufs try. wd 'psel fm' catch. return. end. wd 'set degc *',degc wd 'set degf *',degf wd 'set degk *',degk wd 'set sig *',sig )
Write the code-hooks for the duty cycle
The main code-hook is the verb: mydoframe.
In tempface.ijs this takes the form:
mydoframe=: 3 : 0 NB. locale version if. (y-:1) or xin'' do. load FACEIN xin 0 wup'' end. try. wd 'psel fm; pn *',TITLE,brack CYCLE catch. end. )
In tempserv.ijs this takes the form:
mydoframe=: 3 : 0 NB. tempserv version, overrides doframe_z_ if. y-:_1 do. NB. last pass (exit) HARDLOOP_z_=: 0 NB. to stop hardloop if in use return. end. NB. service new message (FACEOUT) from loface ... if. (y-:1) or xout'' do. xout 0 load FACEOUT NB. receive bufs+instr from face sendlink '' NB. send back updated bufs end. )
Modify the control handlers
The control handlers must be altered in tempface.ijs to talk to the server instead of running verbs in the face app.
The example should make the task clear:
NB. fm_sigup_button=: stepup NB. fm_sigdown_button=: stepdown NB. fm_degf_button=: changef NB. fm_degc_button=: changec NB. fm_degk_button=: changek NB. fm_sig_button=: stepnone fm_sigup_button=: sendlink bind 'stepup' fm_sigdown_button=: sendlink bind 'stepdown' fm_degf_button=: sendlink bind 'changef' fm_degc_button=: sendlink bind 'changec' fm_degk_button=: sendlink bind 'changek' fm_sig_button=: sendlink bind 'stepnone'
How it works
Both halves of the old app use the same "glue" script(s), referring to CLIENT_z_ to determine whether it is the server or the hived-off UI.
But each half works the same way. We will describe it for tempface.ijs:
- Write the buffer contents to the script FACEOUT (using verb: bufscript)
- Flag FACEOUT as "to-go" (using: xout 1)
- Watch for the other script (FACEIN) to be flagged "to-go", then read the script and flag it as read (using: xin 0)