SIBO 'C' Software Development Kit OBJECT ORIENTED PROGRAMMING GUIDE Version 2.30 March 1, 1999 (C) Copyright Psion PLC 1994-95 All rights reserved. This manual and the programs referred to herein are copyrighted works of Psion PLC, London, England. Reproduction in whole or in part, including utilization in machines capable of reproduction or retrieval, without express written permission of Psion PLC, is prohibited. Reverse engineering is also prohibited. The information in this document is subject to change without notice. Psion and the Psion logo are registered trademarks, and Psion, Psion MC, Psion HC, Psion Series 3, Psion Series 3a and Psion Workabout are trademarks of Psion PLC. TopSpeed is a registered trademark of Clarion Software Corporation. Intel 8086 and 80286 are registered trademarks of Intel Corporation. IBM, IBM XT and IBM AT are registered trademarks of International Business Machines Corp. Microsoft and MS-DOS are registered trademarks of Microsoft Corporation. Apple and Macintosh are registered trademarks of Apple Computer Inc. VAX and VMS are registered trademarks of Digital Equipment Corporation. Brief is a registered trademark of Underware Inc. Psion PLC acknowledges that some other names referred to are registered trademarks. 6102 0016 03 CONTENTS 1 Introduction.............scccsccssscrsssrseesscersseeseeceseessseeseessessseesseesseesseesseesseessesscessceescesseeesessseseseesoeees 1-1 Basic-concepts a2) Sn iedstiis tien letshegtad eta iedetedindeentectshdiad om ietsientan ied teedomeitetes 1-3 Classesi si tesvidiesivansaisvedte sities eaten ea er oleae beset aanneaeeSy 1-3 OBject Creat On x. 24.05. t0es Ah ail eistect at aitee thnk aie ditaetas eit at a aie 1-3 Comiponent-objects acc. .senicacisier di atinivaiinlenias aim cmevganeieie 1-3 OBject destruct On’ ye crs cor sep adeceeeeeces ze veloee ce oeees hte adapaveg bees bt scenanh feet ede potegenepeentites 1-4 Cates ories .acfeiecayacltegscetegigaetbeuiy esd ahs ces edipdasd canneehbadipduel caoydehbgtbdust cgueassbagepdee eae 1-4 Category handles and category NUMDETS .............eeeeeeseeesseeeeneeceseeeesaeeesseereneeeeee 1-5 Message: passin yi. o.vin sri diskite, sites high i niai rain teie aii aee lees 1-5 Notation and CONVENTIONS ...........eseeeeeeecsscessseecsseeceseeeesseecsscecssceceeecesaeeesseecsaeecsseeeeneeeenaes 1-6 Category numbers: s..2.7.5cc.c.gyasbedyieibessyaeeben pies decsvaeibedipiasledbyheibege yous beheaesbaspaeh ben aeey 1-6 CASS MAMeS 5.02.62 oxb veut ovav sue aah caiuteses taut seh sallow saint dou vauet ot sGtna Distant Werem editors statements 1-6 Class numbers i3.s.ie2 iatisevairates avg ial eacavai nei asisaiel Gummdaeteioas 1-6 Method attics. ses: exec teeeedecstegelore deus eat tegatana ledet hte dale erecat ete dadesem pedal ty dotegenvrestetes 1-6 Method function Names ...........eecesseceeseecssceceseecsseeeesseecsaeecsseeceseeeesaeeesaeesseessneeeesaes 1-6 Messages and message NUMDETS ............ceseccesseeesseecsseecsscecesaeeesaeecsaeecseeceseeeesaeessaeers 1-6 Object handles 23. .:3.th:eaviseids ain ev hi eae hi ei avi aed 1-6 Method function prototypes ............cccessccceessscceeeseeeeeceeneeeeeseeeeceesaeeeseenaeeeseeneeeeesneeeess 1-7 Class :dia stam sis0c3 381 cee Bivieleepteek Given begin epee bese Giyaidesiaet eeyaitesientens 1-7 PrOSrammMmins OPtONS 1562: ccs cdetecey sis ocbetest see caad oes Met ah aie taal ae Glad od at ae aN cleat aeons 1-8 Using existing object Libraries... eee eescecsscecesceeesseecsseecsseeceseeeesaeecsaeerseessseeeesaes 1-8 Defining application-specific Classes .........cseesecesseecesseecsneecseeceseeeesseecsaeecsaeessseeeesaes 1-8 Creating and using a DYL.... eee eesecsnceceseeceseeeesaeecsseecsseeceseeeesaeecsaeersaeessneeeesaes 1-8 NS 19 EL WiIMA 5.0: Sasi shasta shied sees ate hes one Mat ook Ua ak ote atta tata cau seated ante caveats atte y 1-8 The basic HWIM application component Objects ...........eseeeseeceseeeeseeeeeecsneecsneeeesaeeesaeers 1-9 The application Manager ..........eeseesecceseeceseeeseecsseecssceceseecesaeeesaeecsaeecseesseeesseeeesaes 1-9 RESOUICES wu. seitesiei atheist AS Sheesh pease RGU iba ang balgetareeehey 1-10 The window server ObjeCt 2: sb esos cas teed seh shtd eke tuetsche shad oats tees cake stleddsbint ccbested sh denteets 1-10 The command manager ...........cescceseccesseecsscecsseecsseeeesaeecsseecsseeceseeeesaeecsaeesseessseeeesaes 1-10 THe Clem t: Wind Ow se sad: ccecscad. hates Ques cette dakar s Mes date sey ates Suge eatbees datewersvatedepslttivestoaes 1-10 THE On GNC ioc sieccescesd caeedaes ceaededbedseiavvedandesbedandevvesardesbederdeseilerdevbiderdesvigetdesbudendevsddeabens 1-11 Menus Dat cscs et ext a ited aha eh ti a es ee al Lit nt at at eat na Sat 1-11 Dials ces cessciccsecdaacedscees ceatedas coandesuevacddaccsaadaevevadadeucvaa deauevaadéveisaacess aadedevdeaacenvetaea tes 1-11 aN 0) 0) 8Cer:18 (0) 0) (6) Os (oh) ee 1-12 The required files’ sii...cisetccssacktccsedckh ceevicanccovdchncdeveceoeeuveceh eueydcboedundevbedendepuesandesesavengdavdvataes 1-12 Cates ory Tle asics ket ece ties feb ea Niataect Su Alta e Sict ie Roh Let Soe Rt ot ie oe A ist Bak 1-12 Source file8s; oi. fastesvsite ei neaveei fat dai eavdeieiiel dei os vdgioioss dos wanes Muara aint 1-14 Method: funrcti otis: ises cere, staceveracethtng stun cseteceteat potter edb tet bvny scanned beet htes atone eebeetedes 1-14 Malt. :isc.sseiiynitesiietiinkiebratainebeniialanehaipial eeaaieiel dite ae 1-15 Application start-Up ............cesecssecssceceseeceseceesenecsscecsscecseecnsseesasecanecsaeeceseeceaseesaneesaners 1-16 Resource externals files: :.0::.chi en eehi oeeiionr i nvidia aie vdinii aida ave 1-16 Application Resource file 0.0... ceeeeecessseecsseecsscecesseeesaeecsseecsseeceaeeesaeecsaeessaeessseeeesaes 1-16 System resource file ...3..sc.tce.ssevs ccspeeh besapdeskcdepdaineghs Qesbeeipaaibesepdebiegupeesvesd seit ceiyosbegetees 1-17 Miscellaneous: files si. s.ttsccievied iui tect aeesttond Mie eect batons tat enchants Hatem lant ant ewes 1-17 We Oishii cs eves eet hs een as setae Pa en ate ete aen ues eae ate a 1-17 PCG sHES StS istrefi cetl a select terest iate eM etal tps ti aatet rou dede basal dl odes Mls tatltds on, 1-17 OBJECT ORIENTED PROGRAMMING GUIDE 2 Building an Object Oriented Application .................ccssssscsssssscssscseessscsecsssccessssssessssseseesseees 2-1 An example application.............::ccccesccceesseceeeseneeeceseneeeceeeeceeeeaeeecsenneeeeeeaeeeeeenaeeeeeenneeeenees 2-2 The-example Source sccccteeecsacer ecg egestas vated adeeb wee eased ned ebb devien bebe oobeeviae ets 2-2 Building the example application 0.0.00... ceeceeseesseecenceceseeeesseessseecsacesseeseseeeesaeessaeers 2-5 3 Building a Dynamic Library...............ccsscccsssccssssssscsssecsssecsssssssssesssssenssscsssscscsssscsssssssseneseonese 3-1 AM example DY Visi sssccievestces shea hegoedevs coh iuen Soebeaus bc otanea He ovansacashechadzuyecuusaub aces Sasseoevoasesus aeteey 3-1 The example sourcevishs cst eties ste davis iva Aundiiibits aii datiie davdias a disses 3-1 Building the example DY Lis .cissseis ct cecescesscivese ctvus cots ebeeseh Stuve fesgekescen stnbe subsebvoaes sveedvlse 3-2 Usins the example: DYL s.4c:sicsssicisdasassgsazesadahosinas aoasndashesesanassondauseaeaeaianeandaneastest 3-3 A DYL that supplies the ROOT Class 0.0... ee ceeeseecsseeceseeeeseeecsaeecsaeesseesssaeeesaeecsaeesseeeees 3-4 Building DYLs into an application .......... eee eeeeeeseeecesneecseecsseecssceeesaeecsaeesseessseeeesaeessaeers 3-5 DYIb ddd-fle lists: sc. s2trie etal ee Paha Peak enh aten een hel 3-5 Accessing a builtin: DY Lig. si siete isuesids sahepeoneaeteaticteaeeheauetenbactupeetaaisteaiaaisgeatesyecenteas 3-5 4 An HWIM Example - Hello World.................ccsssccssssssccsscscecssccsescsscccessscsessssccsesssscsesssscseessoees 4-1 THE Cate SOry Ales, el weesees ent odes becbece eset cane eopetedasah dus Sestete date toate beet dedatenenep abs tedoreterte eds 4-3 The resourcé:externals fle: c12.2..5cc.5s:00 case ees beeiyces ig desdecovenbiQigdustesayoesbezerasibesupbesecneysuanessete 4-3 PRE TESOULCE Me drs sas Seesece cen hed cect ane eels te ces ties ses Cee oabvtgict ont ayn tests eet ote auch oiuboss ae esteates 4-4 The-source:cod@sc:e.t34 hi sarentetien tavaniatdisordn atau terginatasicovein ni asenineas 4-4 Buildings thesappl ata ons. siec cesses cag soe eauue tes oced eas est otit-cesaues sued viet onyocay pant sat sautedeppectiett ey, ss 4-6 5 Commands and Command MenuG............cscccrssercsrsserssersserssersserssesscessserssssessssssesessseeeseees 5-1 The Command Manager .2-s. 56.5 ics cess2edes002 Fes cove ta ves ces Suse redsteSesoes dean suvadeSecovs des pevitdedorovsiia Phe 5-1 Addins commarid OptlOns?ss.:..sc.sesssavksesieshdesapeenesieteuhseeaeeana sutenddaanpsasaaiboesigaaseeanasuicagses 5-2 Series: 3a shifted accelerators .......c.sccsccseceehseeudvesesebersiosessontonsaerssodvodsensagnerssecussostesenens 5-5 Series 3a command Option QrOUPING...........: ee eseceeseecesseecseeceseeeeseeeesaeecsaeerseeesseeeesaes 5-6 Sharing method function Code ........ cee eeeeeesseessneessscecsseecsseecesaeeesaeecsaeecseeceneeeesaeeesaeeseaeers 5-6 Changing the text of am OptiOn ........ eee eeeeeeseeceseeeeseeeesseessseecsseeceeecesaeeesaeecsaeesseeeeteeeesaes 5-7 Disabling. menu: Opt On. o..s6.5éss-cchsccugued Sestceui cies doeks aviewes se eeqedbacteesh ccvsstent sot eoubonstorenedcbeosbed 5-9 Changing the number of options in 2 MCNU ......... ee eeeeeeeeeeeseseeceseeeesaeecsseecsaeecsseeeesaeeesaeers 5-10 Displaying a status WINdOW...........ceeseesseessneeesseecesceeesseecsseecseecsseecesaeeesaeecsaeesseeeeneeeenaes 5-11 Application-specific initialisation ..........eeceeeeeesneecseeeseecseecssceceeeeeseeesaeecsaeecseessneeesaeens 5-12 Replacin ga ment Dates. sisish.ctseust ciate euskal g 5-12 Accelerators for replacement menu bal ............ceeeeeesseecsseeeseeeceseeeeseeecsaeesseessneeeesaes 5-13 SUBDMEN US sai bs coe liesd og sevesces Staas cov Pees ech saute Covbaube Cob souks Covboibetebseres Pevbchbededscnus east abedetstaeedy 5-14 Shutdown Messages. nics sieciusdavseestsaeceuslatiedetiataceasladncstisaaentamodstietaadetateestiansendatedel 5-14 GO: WINGOWS 6. cuvasssccesasssscesondsovecndscoeveddsosesoassusavosdensasossensavesdundedessunsesesdnndesesenodesesdeasesosnsavesecseadeseenea 6-1 Wiridow usage rm WIM cissecccic tes tevgeuet bev sinh sep eeuteeystotentppeutesesstes ests anteds pote reupeesteneiens 6-2 The draw/redraw mechanism .............:cceescccesecsseceeseeeesseecsseecsscecsseecesaeeesaeecsaeessneeseteeeesaes 6-2 Drawin ssa: WindOw 2. costes rata bat elie ast eked Red nai ait tl een estates 6-3 Lodger: windows s):2cnsiuihnehiineiinnehsl ab ei ards aie hil ei avi eee 6-4 RESIZINS A: WITLCOW 0. oo eso sup sat edeesee) ents edetedepsget ant ete levestceboasdednteaipbeetegdensntpdent-cevededestprastig 6-4 Window emphasis. s:y:..2c,25.500yshbesietest cesphei oldie duel aeyhehbeshbdeel euphbnite hed die aehibenieane. 6-5 T DIALOGS wcvisccveusdsccvsacdssuvededsouvessisensadsdeagDhossebedesesenstsoedenciededensisestessdesedvnssesdvaaseseivenseosieasdesessansoesses 7-1 Wsitig dialog BOXES :i:iscisicteitatiesathotaetalissateathonietaoesiathonistaocetdotendateectdationtaticasttas 7-2 Default dialog behaviour ............ccccccceesccecesssceceeeeseeeecesneeeeseaeeeceseneeecseeeeesseaeeeseeaees 7-2 Dialogs and resource: files: sc Jerieth din Mahi laet Atenas bases ts 7-3 Teaunichimg: ai dial Og: cesz eed os sive cavtan csta eh yeh 2th sects Seen teeta oss tacos feeiteesseta deen ea deeseeds Seondeass 7-4 CONTENTS Simple: dial ows ‘creche seessb aschistesztees cay stbetee steko cudidinetova voxs cus adivs ieapeve eobsdeveteniees Savseebess 7-5 Dynamically initialised dialogs... ceeeeeseeesseeesseesssceceseeeesaeeesaeecsaeecsacesseeseneeeesaes 7-5 Further dynamic imitialisation ............cccecccccsssccceeeencecceseneeeeeeeeecesnneeecssneeeeesseeessenees 7-7 Retrieving dialog results: s.:¢. s/.ccscci des deidspeehe se uae desicetesodeaphaashsvdasohep haath setesv heeds AA 7-9 Dialogs with and without "WAIT"... cee eeeeseeceseeeeseecseecseeceseeeesaeecsaeesseeesseeeesaes 7-9 Controlling the width of a dialog ....... ee eeseseseecsseeeeseecseecsseecsseeeesseecsaeesseessneeessaes 7-9 SUBAIAaLG SS. .5 555 shat Hess eh LSed oh ee Gh Sad abs Sesh Shag eect oTiebink ceeded a died eos culodes 7-11 8 Dialog Controls ...............sssccssssssccsscscecssscscessscsccessscccessssssesssscccesssscssesssscssssssscsesssscssessssesessssocsoes 8-1 TOXt WINdOWS 0: c.cic.ccessduadccseashd cevgdsatecevisetccsgdcatecovdshsccovdcen cdevicencdoudcancdevadhe sda vdcenedenecnaceuucess 8-2 Und tialiSati Of s.3 222 cee vhics ceeckies See cteessehhiah eee hehehe eect etaae Leche Se ehacas este ae aae te anes 8-3 Seth Ge sssssslhei ereheisty Ga tesieiie Saree astai oni sesegi ist Mei oavaieesl Go dan eaeeren 8-4 SCHIST Oe ale esac as oceg oak Pretty sosegeat east tenotes sah Petath te, odatne esto as edetach Pela rt natal None aat asi 8-4 ChoiGe LSts 30... scccessceiedesdesigdestesaadestagseduatageedvalccoplsatesondseledevdces cds dene cdovdeabcdevcanendevsagendevices 8-5 Ti tialiSati Off vic cee ese eect ce tees ide cabs ea ntec deme dace van chbebeata ese ses biveum deedlecscdeeted arent te 8-5 Seth Osinb seis od eerste eee ad coi. eee ee 8-6 WS CHISTING2 3o5 sles catch dah ca teae eahe de vata esta testa hoveses ate bastadevedes sing buet awit etetedvehigtats detetoves iantanse 8-6 Push buttons and action lists ...........ccccscccceesscceeseseceeeeeseeeeeeeeeeeeeseeeeeseaeeeceeeneeeeseneeeeeseneeeess 8-7 Ini tialiSati Gs: 0 oe. tics cet eie ee eee tester alte tite ae led teen Geeta dae ed ee als 8-8 Setting and sensi G voiiss5) deveisisies eaveniei aa avaniai aa rareatasereiiot eater 8-9 Edit BOXES 2 i oii 0en ctiscass Biveleatbicocelec. fede cecedeldedesevtve dated. dedauesseceditededestdcastaleds tolsebuceee do’ 8-9 Initialisati onss.cccc.tcgeeiessciades tediedestecaedestedevasat sdavdial cdevdsetecavdee ledepiaas cabs dead egevdeabeasvecabees 8-9 DENG. i5 vs Rot Seth Od ks kd malt ea, edict SS oh, A Cael Sod aio, oat Lod Ahan cad Se 8-10 SENSI Ses} sei tvhel er eiee ai ae ee ee eis 8-10 LONG numeric Cito «2.0... eeecceeeececeesenceeceeenececeeaneeceseneeecesneeeeneaeeeeeseaeeeeseeeeesetaeeeeeenees 8-10 Tnitialisati onie.ssceccescacecesveciecesscdieveane davdetcdeveeadadavdvas (devasetccavdcatedevicenedevdcae cdovacsdedsveceeees 8-11 DEMING sis SNCs 6 Rita tin Galatta eat a tis Ge lt a el tls erin at ad ol 8-11 SONSING seo ios oni eaitav girs i ciineevdgass tase se veel stat ates cesvdei seston mavdnerauamaveseyaiaeie 8-11 Integer Mumeric Cd tor ys... csscie.socee saa state vegacet tes lop eet ccv gesntieaty eout seeodetestgvanteespetebantpsanteven th 8-11 Tnitialisati on i..ecccscgeuiess ccsadesdesieienncesadestedevdsabadonduededevdaescdevdae Ueduvdcab cevdcab cdevdcabceevecdees 8-12 DENG So et At ee sttd el ceed Met ee SORA ae a eA et au a A ea cal SC ated Sa 8-12 SENSI Ge53,0 Bei eieesied. hier ei viei oui sae es vigd nite ea vagiese ro eave aire? 8-12 WORD numeric CIOL «2.2... eeeeecceeeeceeeesecceeeeeneeeceeeneeecesnececesaeeceeseaeeeceenaeeeeseeeeessneeeeeess 8-12 Tnitialisati Ons: s.ccc.: catsicsuesiedeascdeaveaus doedestcdevesabadovdsas (dsvashtecovdcabedevachbedevdcas cdevachoedevecdbees 8-13 CPIM Ges Vi cacti os alta Abate itt Bt a Alia lid ee a Bll A, lid eM t an Aly 8-13 SENSING 3.343): aiiravgiitet Gumaydgisteet Gai seevigi sist dei caval iest ans maraeat dames au? 8-13 Rance TumerntcieditOn sss. deci fovea state eves enhe vetored vadethte gatos Meus cot htegadateve avetbee daleateeeeatyaes 8-13 Initialisati oni. .acc.ccgeeiesscdedestedeeiestaseedssbedsedsatsduedval cdevdsetecsvdceledepieabcduy dead cdevdcabeesveaevens 8-14 DCL Gl i vise Ae eet oe sh ee Sh ch et elt, eld Saks, tn taal Sad tit, ome act fal ol ad oe 8-14 SENSING: s3.o} eee nevi oe ihe ai pene a eon a a eee 8-14 Floating POmmt CQItOL: 2) sc cckesestecedstacesle bests cepadadsip beets dedaceoahy best devededeatg beste deportes wate benteven eds 8-15 Initialisati Oni .:.sccc..sscecessececeasccieveand sovdesncdeeesanadavivas cdevisenccavdcaneduvseancdevicancdevedsocesvecaeees 8-15 CEM Sei sk se het ee lta tee ee alte Dts oe lat at Sd et Rt i fe, ih aed ahi 8-15 SENSING sid. ce fei eaveassieteiineevgiast a tesso nee edeistanates cesvdeisvasiasoi Pavdnersrtianaveeiey auntie 8-16 Date/tim@ editor oc eeleledenaescevesacteccenevngsaetetedetsnevsesinsore poansacegeiet oes dolste dogs invecessansevsgsaasevedent 8-16 Tmitialisati Onis. s.cccccccteiess cccadestedeedenncdeadestadeedsad cdonduadedevdaescdvvdae leduvicab cevydeadcdevdcabcesvechedee 8-16 DCU Ges Sa vk A etsee she es cache Mee cee Stal ali al Seal ut es ct ah ee cau SNC ated See 8-17 SENSING ey scHiecieiv hei eiy eles ai ee sae eee veo ene ieee 8-18 Hatitude/Loneitude dior: 2.5 cc.cedssicesieivetecedasadeiie beste dedecteshy bake dedecea ests texte deveceseetpataverers 8-18 Tnitialisati Ons: sccccecaiesuecieseascaeateabecaedesidevesadadovisas cdevisbtecopicarcdevienbedovicanedevachbedveceeees 8-18 SEEING sh ses si ache tee cae Peltha ch AO th oR 8 cecal Mt a et a Rll ON, it Ni a ll 8-19 SENSING. sécdsshei ees, Havieantet eu mesvdeiseme i egso seven stat tsi casvdeisias totes wander umes aaatee 8-19 Pile Name Edits. Pevedlodedeuseideveda tle etegeddecuedddetuee ed deead ede gelaesdddeiatecddelaue igbiete Blunts deans evedest 8-19 Trnitialisati on ss. sccc.tcgcsiestgaiedes vedeeiestedandestadavieabadovduadsdspdaetcduvise ledepdeabeeevdcadesevdcabedevecaeees 8-20 DEIN Gs Ath A et Se sth a oe Batok Det eth d lad Sekt Sn tael Sod tok, A al hh att tek 8-20 SENSIN Oss ).v)i eevee. vind) serene vai pane al a eevee ee Rie 8-20 File name: CHOICE MISE. oicccscseiesecsatadececedassevssdedecucetessentedadeducecesbantsdevedacedessintetedadsdedevsensatensd’ 8-21 Initialisati on st. ccccescscecesseciecesscteveaut sovdeancdevesanadavdvas cesvisetecavdcaresuvicnnccevicancdevechocdsvecseces 8-21 DCN Get ahs Arte GA Ate Seale hat te RON eA Ge SM RR RO, a A hi oat a ley 8-22 SCMSIN Sse. ssdeceihes pea va leet eae eevdaieets edie deevdelea i dor eve lpinanevaiye baie veesey ine 8-22 iii OBJECT ORIENTED PROGRAMMING GUIDE DACHVE. ODJOCHS:s.c.casssscevondsovecnnssesevesdsasesoesensevossensasdesensadosdansedesseneedessandesesenstesesdeasesosnsavesocsoasessenea 9-1 Active objects and asynchronous requests ..........:::sscccescccesseeescesseeceseecssaeessaeeesseessseeeesaes 9-1 Active object: priorities::.:2:..:8.iecrc dint ih esi baein redid pb aie haiyinee ds 9-2 Application respOmsivVeness............::ccsscccsseccesseesseecseeceeecsscecesaeeesaeecsaeecseessneesssaeeesaes 9-2 Background processing...........scccsscccssscessseecsscecsscecsseeeesaeecsaeecseecssaeeesseeesaeesseeesseeeesaes 9-2 GPOPS so esccted Seccce i ootea sobs eek oes beeteded otesistesaccts decctenstesbeenedadetesstesdentedusesenedes beecodedasetodeeees 9-3 A. SIMPLE TM OL si. 3.05 avecieieescdledes te caeieaucdagucabidandead cdsvesanecovdal cdsyasetedapicas cdevscneccevacad edevecsoae ee 9-3 10 Error Handling and Error Recove rJy...............sccsscccssscsssscsssecssscsssecsssecssssssssssesesesssscssssosees 10-1 Errors during initialisation .............cccccesecccceessceeesescecesneeeceeneeeecseeeeeeseaeeeeneeeeseeeeeesseeeeess 10-1 Getieralerror: TECOVELY Fs .0.. ces cick sckeoes Sees gabe cet iss Savi guekh chug des Havhouche sede deasenbeuahseuseousdeca ceed egebenn 10-2 The roll-back principle .0...........ccccsccceesecceeeesneceeeeeeeeecescecesseaeeeeesnaeeesecsseeeessnaeeeseeaees 10-2 Roll-back for component Objects ............::cceeessceeeeeneeeceeseeeeeeeeeeceenneeececseeeeesesaeeeeteanees 10-3 Other resources in an object's PrOPerty...........cceeeecceeseseceeeeeceeceeneeeeeseeeeessneeeesseeeeees 10-4 Using the: CLEANUP Wistes cess cccisecectea ices tetdeetnecnes chcceebeekac bhevslcceibeseesseaniosententocuensntes 10-5 Interactions with system COde ...........::cccceesccceessnceeeeseeeeeeeeeeeeeseeeecesneeeeeseaeeeeeseaeeeseenneeeeneas 10-5 11 File-based Applications.................csccscccsssssscssccsccsscssccssscssessscsseesssssesssscscessscessesssscessssscssesens 11-1 Start-up IMitlalisati OM? 2.2305. ceet sel et sce cel coke eons ceed ede ioee cas ode divieceestntoat WM eceentte dete 11-1 Opening and creating files. i120: 3.0s05. cesses essicda covescancceseda covedcneceevdeda cevvccaasessiagaseseiandeestieseed 11-2 Switch files Messages .0.........c:cccceeeseceeseceeeesseeeeeeeneeeceesaeeeeeseaeeecseeeceeeeeeeeesneeeeesneeeess 11-3 Saving PIES vecccvecdccssseadceseddoeesvisvecs das devavievveseedes cede rievedsedeaevderdcvigaadeveudladevscdeadevtectebes ads 11-3 Application termination............:.ccsccceeeesceceeseceeeeeeeeeeeaeeeceseneeeceseeecsaeeeceenaeeeeeeneeeeeneas 11-3 Shutdown Messages .:ses.eceiseccbicsessrcedescciceseasescedesettccseaaaa coda sete cebvecnucsseacauensvaeea cebeeenvens 11-3 12. Edit: Wind OWS i sssccseccssectecsssecteccssoctesssccsansssocsaassoecoansssensanssoeveadcsoautasdsousdesesonsissesonsseseseacscecsunase’ 12-1 Introduction to EDWIN ............cccccccceseseeeecccceceeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeees® 12-1 Dialogs and edit windows contrasted ............c::cccssesccceeeseeeeeeeneeeceseeeeeceseeeeeesaeeeeseaees 12-1 The NOTES example program .......... eee eeeesccceesnececesseeecessaeeecessneeecesseeesessaeeesesaaees 12-2 The HelloWorld program for edit WiNdOWS............:::cccsssccceenseeeeeneeeeeeeeeeeeenaeeesneeeeeeees 12-2 The EHELLO category file’. isis s.c; ensohebidaedies Wadi sidap ies Aasteadedinn Aacheaiderdice Anis 12-3 Initialisation code in EHELLO ...0....... ccc cceceeceeeeeeesseseeeessessneceeeeeeeeeeeeeeeeeeeeeeeeeceeeeeeeeees 12-4 Other:code:in: FHBLL Oo sc5s.cenkess Seance ieeduidewasosncaiiecedacaboasasieteekasteooatenieeeeneseonetdsaetote 12-5 Simple: useOf BIO WIUIN foci oi25 eh spke cis sateen se POiSeSh sh cubed Soke bu Sooetc Shane ba dbasfoegh Sih ba eaatoessathy 12-5 Initialising an instance Of EDWIN...........:.eseeseesseeceseecesceceseeeneeeesaeecsaeecsaeeeseeeneeenes 12-5 The landlord of the edit Window ..........cccccccccccccccccccececececeeeeeeeeeeeeeeeeeeeeeeeceeeeeeeeeeeeeess 12-6 The IN_EDWIN and IN_EDWIN_X data structs.................cccccseesssesseesesessesseseseeeseees 12-6 The Ig_set_id_pos method ...........eeccceeeeccceeesneceeeenceeeeeeeeecesnneeecsseeceeseaeeeessnneeeeseeeeess 12-7 Other edit window initialisation flags ......... ee eee eeeseeeeeeeneesseecseeceseeceeeeeseeeesaeessaeers 12-7 A note on the CONTENTS field in the IN-EDWIN struct ..................::::::sseesseeeeeeeee 12-8 Values of special characters in the text ..........eeseescesescecsseceseeessaeceaeecsaeesseeesneeeeaeees 12-9 A note on the MAXLEN field in the IN_EDWIN struct...................ccccssseeeesseeeeeeeeeees 12-9 The wn_sense method .................cccccseeeeeseesssesseseeeeesseseesstsessesssessesesessestessssstssseseeeeeees 12-9 The wn. Set Methods 2.523 ce.sssetsocseed Qesseeteoesetd cexeaetecebend Buyeacteoubedd eueesetacteeged uote 12-10 The: wiikey: methods)ssisccivsiseissstesioccesdisissntesieccautassoestasiecasitaadeeatesiacasbiasvesdagiceusdaaioess 12-10 The wn_emphasise method ...............ccesecsccessseceeeeeneeeeeenceeeeeeeneeecesnneeecesneeecessaeeeeeeaees 12-11 The wn_ draw method ..........cccccccccccesseeecccccccceesecccccsssceusseecccsssseuesseecccsssseuuccesssseeeneess 12-11 Additional EDWIN methods. ..................cccceeeeesseeeceeeeeccscsssssssssssseeceeeeeeeceeeeeeeseeeeeeeeees 12-11 The ew_imsert method ..............cccccsssssssssccccecesssvessvccsenesccceccesesssvssscccceeensssvscesscesensnees 12-11 The ew_find method... cece cccceseeeccccccccceesseccccssseuccceccssseeeseeccesssseeueeecsesssseeeneess 12-11 The-ewareplace:- methods jis sess sg5s avisees Asa sees ousebies fesweeesovsaeees sasesdasovntiees ua ieeasse teers seans 12-12 The:éw replace root accessed by: self£->appman accessed by: se1£->hwimman If a class supplies no additional property then the corresponding element of the struct does not exist. 1-6 1 INTRODUCTION The object handle may optionally be declared as a pointer to any superclass. Thus the pointer in the above example may be declared as a pointer to either pR_Root (which gives access to only the Root component) or PR_APPMAN (with access to Root and appman property). It may also declared as a vorp * in which case, obviously, there is no access to any of the object's property. Method function prototypes The description of each method in the documentation contains a function prototype that specifies the nature of any return value and the parameters with which the method is called. By convention, the listed parameters always exclude the object handle that is passed as the first parameter to every method function. It also does not show the method function number, which is passed to the message-sending functions such as p_send, but is removed by the message-sending mechanism and is never passed to a method function. For example, a wn_emphasise method for the class w1n, described in the documentation by the prototype: VOID wn_emphasise(UINT flag); would be invoked by, for example: p_send3 (hand, O_WN_EMPHASISE, TRUE) ; where hand is the handle of an object of the win class and o_wIN_EMPHASISE is the method number. This corresponds to a method function declared in C source code as: METHOD VOID win_wn_emphasise(PR_WIN *self,UINT flag) { } Class diagrams The class diagrams in this manual broadly follow the notation as used in Object Oriented Analysis and Design with Applications, second edition, by Grady Booch (The Benjamin/Cummings Publishing Company Inc, 1994). A class is represented as shown below, where the class name is written inside the symbol. An underlined name represents an imported class, that is, a class whose class definition is in an external category. foi eee fp "eS: 7 - ed) ie Relationships between classes may be that one class subclasses another, or that a class uses another. The superclass/subclass relationship is represented by an arrow joining the two classes, as illustrated in the following diagram. ma a GRO aaa te 7 supe lass y subi ass. = =) ie ad Le Ce A ‘using’ relationship is illustrated in the following diagram, where class A uses class B. The using relationship may represent aggregation, meaning that class B is a component of class A, or may simply indicate that class A sends messages to class B. a Coe es a an See, ee ee eae La In some cases, where the two classes may be considered co-equals, or where the two classes send messages to each other, the relationship may be shown as a simple association, without the circle that indicates the direction of the relationship. YS ea Has ea a a ae ee Mee OF a OBJECT ORIENTED PROGRAMMING GUIDE Programming options An application can make use of OOP techniques in one of a number of different ways. This section explains the main options that are available. Using existing object libraries At the most straightforward level, an otherwise non-object oriented C application can simply create and use one or more objects from existing object libraries. A typical example of this kind of usage is described in the Interface to the ISAM library section of the Introduction chapter of the ISAM Reference manual. The example code in that section illustrates how to create an instance of a class from an external category and send messages to it. Note that, since the ISAM DYL is not in the ROM, it has to be explicitly loaded before being linked. In principle, this technique can be used with any class from an existing object library. In practice, however, for the reasons given in the later discussion of the use of HWIM, it is restricted to classes that do not depend on the user interface. The ISAM example is typical in this respect, in that uses the console services to supply its user interface. The technique is particularly suitable for creating and using instances of classes such as the variable array (container) classes in OLIB. In such a case, where the class library being used is in the ROM, there is no need to load it before linking (with, say, a p_linklib(0) call). One of the main advantages of this technique is that it requires very little additional knowledge, other than the details of the particular class or classes that are being used. Since the bulk of the application's code will not use Object Oriented techniques, the gains are relatively modest. There are savings in application code size, since the application does not have to duplicate the object library code that it uses. Defining application-specific classes Rather than simply using an existing class, an application may define one or more application-specific classes. These classes may, by subclassing, add value to other existing library classes or may be totally new classes (although a 'new' class is, in fact, a subclass of the OLIB root class). Such an application may also make use of existing object libraries, as described above. Although it is possible to write a fully object oriented application of this type, a typical application will still be largely written in non-object oriented code. As for applications that simply use existing classes, the technique is better suited to classes that do not depend on the user interface. A simple example of an application of this type appears in the Building an Object Oriented Application chapter of this manual. The additional knowledge that is required to create and build an application of this type is entirely contained within that chapter. Creating and using a DYL Instead of using the classes of an existing object library, an application can use objects in a custom object library (or DYL). The way to create and use a custom DYL is described in the Building a Dynamic Library chapter of this manual. The classes in the DYL may be any combination of 'new’' classes or subclasses of existing library classes. This technique may be combined with the use both of custom libraries and of custom classes in the application itself, as described above. In addition to the advantages of the previous techniques, writing one or more parts of an application as separate DYLs allows for easier code sharing and reuse and allows large applications to be written (a single code segment may not exceed 64 kbytes). Using HWIM A developer who wishes to create an application with an object oriented user interface should make use of the HWIM class library. This case is qualitatively different from those described above, in that many of the classes in the HWIM library are designed to be used together, and are not particularly suited to being used in isolation. Some of the classes rely on the existence of instances of other classes and on certain specific initialisation having been performed. This is the reason why the techniques described above are not recommended for the HWIM user interface classes. 1 INTRODUCTION An HWIM application always contains a basic framework of objects, briefly described in the following section, to provide those features that are common to all HWIM applications. These features include the provision of command menus and dialogs, the handling of multiple event sources and the direction of keyboard events to the appropriate object(s) within the application. Such an application requires a specific form of start-up code in its main() function to create the basic framework and perform the necessary initialisation. The exact form of this start-up code is described later in this chapter. Many of the HWIM application framework classes can be used directly, but an application will always define and use application-specific classes, including subclasses of the classes supplied by HWIM. The application may also, of course, use or subclass the classes from other object libraries - either the standard libraries that are in the ROM, or application-specific DYLs. A simple example of such an application is described in the An HWIM Example - Hello World chapter. The construction of more complex applications is essentially the topic of the rest of this manual. The basic HWIM application component objects There are five main static objects (by static we mean an object that exists for the lifetime of the application) provided by HWIM; the application manager, the application's resources, the window server object, the command manager and the client window. A sixth static component that is usually present in non-trivial applications is the engine. The menu bar and, optionally, one or more dialog boxes are transient objects, being created when they are required and destroyed on completion of their function. The application manager The application manager provides the framework for an application, including its main event-scheduling loop. It is the first object to be created during the start-up of an HWIM application and performs all standard start-up and initialisation. As part of this initialisation it creates a window server active object (and hence a command manager - see later) and opens the system resource file and the application's own resource file. In addition, the application manager supplies methods for manipulating other standard system components and adding further optional components. — — avolication ~ manager — ae oo | a kes 7 eso! ces gO server ~~ ) ca Rue oe The application manager and the window server object together form the central core of the application. From the application programmer's point of view they may be considered as a single entity that provides the application's main event-handling loop and a range of system services. The separation of this functionality between two objects represents a division of labour; the window server object deals with the user interface and the application manager handles those aspects that are independent of the user interface. (Although it is beyond the scope of this manual, it is worth pointing out that an application with no user interface can be constructed around the application manager alone.) The HWIM library supplies the xwimman application manager class, which is a subclass of the OLIB appmaN Class (see the OLIB Reference manual). An instance of this class is normally created and initialised from the application's main (). It is rarely subclassed by an application, the main exception being that of a multi-lingual application, which will need to modify the mechanism that loads the application resource file. An application may subclass awrmman by replacing existing methods. In the interests of future compatibility, application-specific subclasses should not add methods or property. An application that adds methods or property to the application manager is not guaranteed to run on future versions of machines in the Series 3 range. The handle of the application manager is globally available via the magic static w_am. OBJECT ORIENTED PROGRAMMING GUIDE Resources All standard HWIM applications are assumed to have access to two resource files - the system resource file (in the ROM) and an application resource file. These are opened automatically during initialisation of the application manager, which also provides methods to access individual resources. There is rarely any need for an application programmer to subclass the resource file class. The application resource file must contain the resources that provide the accelerator keypresses and the text of the application's menu bar and pull-down menus. In addition to these essential items, it may also contain resources for any application dialogs and other application-specific text. The objects representing the resource files (instances of the OLIB rscr1te class) are normally only accessed via application manager methods. The window server object The window server is an active object that acts as the source of those events (keypresses, redraws etc) that are sent to the application by the window server process. On receipt of such an event the window server object directs it to the appropriate object, normally either a window (the client window or a dialog box) or the objects involved in the command execution mechanism. An instance of a window server object is automatically created during the initialisation of the application manager. As part of its standard initialisation the window server object creates and initialises a command manager. “~~ te = C window’ ~ server ) ee ft <.. ran = Hey, C command client / smanager, ~ window j eageee y wae The HWIM library supplies the wszrv window server object class which is a subclass of the OLIB active class. HWIM applications will almost invariably subclass wsERv , replacing its ws_dyn_init method, to provide application-specific initialisation. Part of this initialisation will be the creation and initialisation of a client window. An application is free to subclass wsERv by replacing existing methods. In the interests of future compatibility, application-specific subclasses should not add methods or property. An application that adds methods or property to the window server object class is not guaranteed to run on future versions of machines in the Series 3 range. The handle of the window server object is globally available via the magic static w_ws. The command manager The command manager supplies the functionality to execute the command options that may be selected from the application's pull-down menus. A command manager instance is automatically created during the initialisation of the window server active object. The HWIM library supplies the comman command manager class, which provides the basic skeleton for a command manager. Although a very simple application could make direct use of an instance of the comman class, HWIM applications will normally subclass comman, replacing one or more of the supplied methods and adding application-specific methods and property. The handle of the command manager is stored within the window server object's property and is available Vla w_ws->wserv.com. The client window All standard HWIM applications are assumed to have a main window, designated as the client window to provide the principal view of the application's data. This window will receive messages from the window server active object in response to window server events (such as keypresses). The client window must be explicitly created and initialised by application-specific code, normally from within the window server object's ws_dyn_init method. 1 INTRODUCTION The functionality of the client window varies widely from application to application and much of an application's code will be associated, directly or indirectly, with the client window. For this reason, the HWIM library provides very general window classes that will normally be extensively subclassed in most applications. The handle of the client window is stored within the window server object's property and is available via w_ws->wserv.cli. The engine The engine is an application-specific class that is normally created at the same time as the client window. A typical engine will subclass root. Engines are further discussed in the Application Design chapter. — — C client = / ~ window L — Ue ENT es gf engire / zd ad An application programmer may choose to make the handle of the engine globally available, for example, by storing it in one of the magic statics in the range Datapp1 to DatApp7. Menu bar An instance of the application's menu bar class is automatically created by the window server object whenever the menu bar must be displayed (for example, when the Menu key is pressed) and is destroyed when the menu bar disappears. fer window ' ~ server ) Laer je men bar. s ) Lge The menu bar class contains the mechanism to convert a menu selection into the appropriate command manager message and is unlikely to be subclassed in any application. The menu bar is normally only manipulated by means of methods of the window server object. Dialogs A dialog is usually created from application-specific code in a command manager method that is called in response to the selection of a command menu option. (The command manager actually makes use of the window server object's ws_do_dial1 method, which is used to start all dialogs). — command kd manaden U = a — — 2 dialo box = ae Ae Such a dialog may simply use the supplied picgox class or may be an application-specific subclass. The use of dialogs is discussed in some detail in the Dialogs and Dialog Controls chapters. OBJECT ORIENTED PROGRAMMING GUIDE The handle of a current dialog will normally be stored in the magic static DatDialogPtr and many dialog utility functions (see the HWIM Utility Functions chapter of the HWIM Reference manual) make use of this fact. Otherwise, access to a dialog's handle is an application-specific matter. Application overview The following diagram illustrates the overall basic class structure of a typical HWIM aplication and shows the principal relationships between the classes mentioned above. Sei z anolication manager PSE ee LC ~ jo OR Gr a ee, ¢ reso ces. C window ’ y men _ bar = ) ~ server se ) ee Mn / Eee Dai ; er e Pies! ee / tient = 7 la comman lalo Ox / cllen ~ manager a 7 ) a yagew j Na Ne a — — a enc ie / * ) oe? These relationships are discussed further throughout this manual and in the OLIB Reference and HWIM Reference manuals. There is further general discussion on the general structure of an HWIM application in the Application Design chapter of this manual. The required files To create an object oriented application the writer's task consists of constructing a number of files which will be input to the build process. A number of tasks must be performed which can be broadly defined as follows: e building the required classes by defining their property and methods; further, deciding whether any of the required classes can be subclassed from existing classes, thereby re-using existing software e providing the functionality for a number of method functions that will be called by system code e writing a short main() that creates and initialises an application manager, supplying it with one or more of a set of options e building the resource file(s) and optionally, the Icon file, the Add files list and the Shell data file. The following sections describe the structure and content of the files required to create an object oriented application in more detail and broadly correspond to the tasks outlined above. Category file The category file defines the application-specific classes - methods, property and associated defined constants and structs. As part of the process of building an application, the category file is translated with the aid of the CTRAN tool. The output from the translation process is a C source file (which is also compiled, ready for linking into the application) one or more generated include files, each with a .g extension and an external file with a .ext extension. Other files may optionally be generated. The external file contains information about this category which will be needed when translating any other category which makes an external reference to this one. 1-12 1 INTRODUCTION The content of a category file is best explained in conjunction with the following short example. A more complete explanation is contained in Appendix A. Here we shall concentrate on the basic content of a class definition. The file header contains the category name, external category references (the order of which determines the external category number sequence) and a number of included header files: A demonstration cat file IMAGE demo ! External reference to OLIB library EXTERNAL olib INCLUDE p_std.h INCLUDE p_object.h INCLUDE varray.g required, in this case, for knowledge of VAFLAT This is followed by one or more class definitions, each of which follows the general model illustrated below: CLASS dummy root the class name and its superclass Dummy class definition, as an illustration only { Methods follow... REPLACE destroy free buffer and supersend ADD dm_init create VAFLAT component and allocate buffer DEFER dm_sub defined by a subclass... CONSTANTS auxiliary symbolic constants { ! for the buffer DUMMY_BUF_SIZE 128 allocated buffer size ! for the VAFLAT component DUMMY_GRAN 16 } TYPES contains auxiliary structs { typedef struct /* comments here are exceptional */ { TEXT *buf; pointer to allocated buffer UWORD len; } DUMMY_BUF; } PROPERTY 1 { PR_VAFLAT *array; the component VAFLAT instance DUMMY_BUF buffer; } } The crass keyword introduces a class definition. It is followed by the name of the class and then the name of the parent superclass. The above example defines the class pummy which is a direct subclass of the Root class. The layout of a class definition is significant; apart from leading whitespace, which is ignored, it must follow the pattern that is illustrated above - and in the class definitions given elsewhere. The class definition of each subclass lists its additional methods and any additional property. It may also, as in the above example, include the definitions of auxiliary structures and constants used by that class. There are many further examples of class definitions throughout this and other manuals (in the OLIB Reference manual, for example). The class definition may include any number of method declarations,! introduced by the app, REPLACE or DEFER keywords. Each of these is followed by a method name. The method declarations may be followed by one of each of the constants, Types and property keywords. 'Subject to a maximum of 255 methods, including those inherited from superclasses. OBJECT ORIENTED PROGRAMMING GUIDE The method declaration keywords have the following meanings: ADD declare a method in addition to the methods provided by the superclass. The name must be unique in relation to all other methods in this category, or any externally referenced categories. Although not compulsory, the name conventionally starts with a short prefix related to the name of the class in which it is introduced. REPLACE declare a method whose functionality is to replace that of a method supplied by a superclass. The name must be that of an existing method in the superclass inheritance tree. DEFER declare an additional method as for app, except that the functionality of the method is not defined by the current class and is expected to be provided by a subclass (using REPLACE). A class containing one or more pEFERred methods is known as an abstract class and, in general, no instances of such a class will ever be created.? It is recommended that each method name be followed by a concise descriptive comment. The constants keyword introduces a list of symbolic constant definitions, each consisting of the symbol name (conventionally in upper case) followed by the numeric value. The value may be an expression involving symbolic constants defined earlier, either in the category file itself, or in any included file. The expression itself must not contain any whitespace. The types keyword introduces a list of C language typedef struct definitions, whose layout should follow that given in the example category file. (Many further examples may be found in the class definitions shown for each class in, say, the OLIB Reference manual.) The property keyword introduces a list of data element declarations to be included in the struct that defines the class property. The form of this struct is described in appendix A of this manual. This keyword may optionally be followed by a literal number (expressions may not be used) that specifies how many component items listed in the property are to be sent an automatic pestTRoy message when an instance of the class is destroyed. This assumes that, for a value ncomp, the first ncomp items in the additional property for the class are either nuLL or handles (pointers to instances) of component objects (as defined earlier in this chapter). In the above example, pummy's component var.at instance will be automatically destroyed when pummy recelves a DESTROY Message. Source files Method functions A method function must be supplied for each added or replaced method in each application-specific class. The method functions may be supported by auxiliary (or utility) functions - that is, normal C functions called from within the method functions. These functions may be in a separate file or in the same file as the method functions. The method functions themselves may be organised into C files in any suitable way. Normally, all the method functions for a particular class will be grouped into one file, but this is not a requirement. It may be convenient to group together all the methods of a related set of objects, and a simple application may have all its method functions in a single source file. Where a file contains a mixture of method functions and auxiliary functions, it is conventional to put all the auxiliary functions at the top of the file, followed by the method functions. One advantage of this scheme is that it minimises the number of compiler directives needed to establish the correct calling conventions for the different function types. This may be extended to include other function calling conventions so that, in general, a source file will have the following form: 2There is no formal requirement for all p—ErzRred methods to be REPLACE and it is acceptable to create an instance of such a class provided that it is known that no pErErRred method will ever be called. Window subclasses, for example, do not need to REPLACE all pEFERred methods of the HWIM win superclass. 1-14 1 INTRODUCTION <'normal' functions> #pragma ENTER_CALL #pragma CDECL #pragma METHOD_CALL Failing to declare the correct calling convention for a function will cause unpredictable run-time errors when the function is called. Main The main() of an object oriented application that uses the HWIM library takes the following form: #include GLDEF_C VOID main(VOID) { IN_HWIMMAN app; IN_WSERV ws; VOID *handle; p_linklib(0); app. flags=FLG_APPMAN_RSCFILE|FLG_APPMAN_SRSCFILE|FLG_APPMAN_CLEAN; app.wserv_cat=p_getlibh (CAT_MYAPP_MYAPP) ; app.wserv_class=C_MYAPPWS; ws.com_cat=p_getlibh (CAT_MYAPP_HWIM) ; ws.com_class=C_COMMAN; handle=p_new (CAT_MYAPP_HWIM, C_HWIMMAN) ; p_send4 (handle, O_AM_INIT, &app, &ws) ; } The call to p_link1lib dynamically links the application category with its externally referenced DYL categories. These consist of those categories that are declared with ExTERNAL statements in the application's category file (together with any further ExTERNAL references that these categories contain). In general, all such externally referenced categories must be loaded (for example, by calling p_loadlib) before the call to p_1ink1ib is made. In the above example code, it is assumed that all such external references are to the DYLs (HWIM, OLIB etc.) in the ROM. Such DYLs are deemed to be loaded by default, so no calls to p_1oad1ib are required. If the application uses one or more application-specific categories that are not in the ROM it may be necessary, depending on how and when they are used, to load them at this point, before the call to p_1ink1ib. A failure to link the appropriate categories can cause a wide range of object-related run-time errors. The flags field of the 1n_Hwrmman struct specifies a range of options that are used during initialisation of the (qwrmman) application manager. The basic range of flags is described in the APPMAN Application Manager Class chapter of the OLIB Reference manual, and additional flags are described in the HWIMMAN Application Manager chapter of the HWIM Reference manual. The three flags used in the above code, specifying that the application uses the system resource file, an application resource file and a CLEANUP object, are mandatory for all HWIM applications. By default an application will be built to run on the Series 3 and will run in compatibility mode on the Series 3a. onRINg FLG_APPMAN_FULLSCREEN into the app. flags field specifies that the application should not run in compatibility mode on the Series 3a. The wserv_cat and wserv_class fields of the 1N_Hwrmman struct must be set to the category and class numbers of the application's window server object. As indicated in the example, all applications will use an application-specific subclass of the wsErv class that is supplied by HWIM. OBJECT ORIENTED PROGRAMMING GUIDE Similarly, the com_cat and com_ciass fields of the 1n_wsERv struct must be set to the category and class numbers of the application's command manager that is created during the initialisation of the window server object. The example specifies the use of comman itself: normally an application will use an application-specific subclass of comman. The final action is to create and initialise the application's application manager (typically, as in this case, the Hwimman class supplied by the HWIM category). The application manager locates and opens the resource files, creates and initialises both the window server object and command manager and starts the application's main event-processing loop. All further interaction with application-specific code is via calls from system code to a range of method functions. The first such call is to the window server object's ws_dyn_init method, which is assumed to perform all necessary application-specific initialisation. Note that the application will never return from the sending of the am_rin1tT message to the application manager. Resource externals file The resource externals file, with a .re extension, is a source file that contains a list of those defined constants that are referenced by the application resource file. Once the developer has created a.re source file, the re.bat batch file is used to generate a .rg file. The .rg file can then be #included in the application resource file, thus avoiding the need to #include a large number of separate header files. The resource externals file is not strictly necessary, since the information it contains is present in one or more of the header files used by the application. In larger applications, the use of such a file can reduce the time taken to perform a resource compilation. It also reduces the risk of out of memory situations occurring by effectively passing only those definitions required for successful resource compilation. The resource externals file is also useful if the resource file is to be translated. The (usually small) .rg file can be supplied to the translator together with the resource file, rather than having to supply a (usually large) number of other header files. The translator can then easily make a test compilation of the translated resource file, with consequent savings in time and effort. A ..re source file simply contains a list of those defined constants that are needed in the compilation of the application's resource file (together with includes of the header files in which they are defined). Each item in the list is the name of a defined constant with an additional leading underscore. A commonly occuring item in a resource externals file is the defined constant for a command menu method number, since such a method number is needed to construct the resource for the pull-down menu option that selects the execution of that method. Suppose, for example, that an application's resource file refers to the o_com_ExiT method number, defined in comman.g. The resource externals file would contain the lines: #include _O_COM_EXIT After processing this file by means of re. bat, the generated .rg file will contain the line: #define O_COM_EXIT 7 Application Resource file Application resource files contain the text strings used in the application's command menus, dialogs, text messages and so on. In object oriented programming, such strings must be kept separate from code. In general, resource files are useful for the following main reasons: e having data in a resource file rather than in the code reduces the amount of memory used by the running application e resource files make it easier to write language-independent applications In essence, any resource item within a resource file can be identified by a unique number. This number is usually assigned to a symbolic constant that is published in a header file generated by the resource compiler. Including this header file in a source file allows code to reference the resources. The structure of resource files is described in much greater detail in the Resource Files chapter in the Additional System Information document. Further information can also be found in the HWIM Resource Files chapter in this manual. 1-16 1 INTRODUCTION System resource file The system resource file is built into the ROM and is functionally similar to application resource files. However it contains common resources, including many system dialogs, the text for standard information, error messages and basic help. The general structure of the system resource file is similar to that of application resource files and is described in the Resource Files chapter in the Additional System Information. Again, further information can be found in the HWIM Resource Files chapter in this manual. Miscellaneous files Icon An application can be represented by an Icon. Although not mandatory, the system screen will refuse to install the application unless it contains one. In this situation, the application can still be run from RunImg but an empty icon boundary will be displayed. An Icon is normally placed in a .pic file and can be produced in a variety of ways: e using the Jconed demonstration application that can be built using the HWIF part of the SDK, or the Series 3a Iconeda application that is installed into the \sibosdk\s3atool directory. e using the window server tool wspcx.exe on the .pcx output of a PC program such as Windows PaintBrush The format of .pic files is given in the Bitmaps section in the Window Server Reference manual. For further information on Icons, see the Series 3/3a Programming Guide. Add files list An add files list is a text file with extension .af] which contains from one to four filenames. In essence, as part of the process of building an application, the files referred to in this list are combined with the application's .img file to produce a larger .img file. The list can refer to a .pic file for an Icon, a .rsc file for an application's resource file and so on. For further information on add files, see the chapter Building an Application in the General Programming manual. Shell data file The Shell data file is a file which can be included in the add files list (and therefore embedded into the application). It specifies information required by the System Screen application (also known as the Sheil). For example, it tells the Shell the expected extensions of any files to be edited and the default directory of these files. This information is specified at compile time. For further information on the Shell data file, see the Communicating with the System Screen chapter in the Series3 Programming Guide. CHAPTER 2 BUILDING AN OBJECT ORIENTED APPLICATION The process of building an application that contains object oriented code is very similar to that used for building non-object oriented multi-file programs. The basic mechanisms of compiling and linking the various modules are as described in the Building an Application chapter of the General Programming Manual. Perhaps the most obvious difference is that, in addition to the file(s) containing source code, an object oriented program also requires a category file, described in appendix A of this manual. The application must also supply a method function for each additional or replacement method declared in the category file. For clarity, it is generally preferable to use a separate file for the source code of the method functions of each subclass. This has the added advantage that it also tends to reduce the amount of data in the included files, particularly if the category file is separated into a number of sub-category files (see Appendix A - Category Files). It is, however, perfectly acceptable to combine the method functions of two or more subclasses in a single file, particularly if they are closely related, or if they share some common functionality. The process of generating a .img file is illustrated in the following diagram. Classes CAT class info G = Methods .C The category file is translated, producing an object file and one or more .g files. The .g files are included, as required, into the .c files containing the method functions. These files are compiled, to produce object files, in the same way as for any other .c file. The .img file is formed by linking these object files and the object file that results from category translation. From version 5.00 of CTRAN a file of type .cif is also produced from the .cat file; this is used for Windows development, such as producing custom controls for Oval. The processing of the category file is shown in more detail in the following diagram. The CTRAN category translator tool actually generates a .c file that contains, in source form, the data for the class descriptors of each class in the category. This file has to be compiled to produce the class descriptors in object form. Since class descriptors have to be in the code space of a process, the object file has to be further processed. This processing is performed by the OBJCONV tool, which modifies all data in the object file so that it will be loaded into the process code space when the application runs. OBJECT ORIENTED PROGRAMMING GUIDE Classes CAT Compiler aan OBJCONV The entire process, from category file to converted object file, is performed by the supplied ct.bat batch file. You only need to be aware of the underlying mechanisms because of the .c file that is generated in this process. This file has the same name as the .cat file so that, for example, the category file mycat.cat will generate the file mycat.c. You should therefore avoid creating a separate C source file ( a method source file, for example) with the same name as the application's category file, otherwise it will be overwritten during the category translation process. An object oriented application (.app file) is constructed from a .img file by adding an icon, a shell data file and a resource file, in the same way as is described in the Series 3 Programming Guide. An example application This example application (the source of which, on installation, is copied into a \sibosdk\oopdemo directory) prints specified directory listings to the screen. It is based on the (non-object oriented) p_prndir example mentioned in the Building an Application chapter of the General Programming Manual. It uses object oriented techniques to store the file names in a variable array object. This adds value by allowing the names to be ordered so that the list can be displayed in alphabetical order, with all directory file names appearing first. To avoid obscuring the main principles of building an object oriented application, this example makes minimal use of the object libraries built into the Series 3 and Series 3a. It does not, for example, use any of the user interface mechanisms provided by the HWIM library and is therefore not a typical example of an object oriented application. Its user interface uses the console device as used in many of the straight C examples (that is, using neither the HWIM or the HWIF libraries) given in, say, the PLIB Reference manual. The example described in the chapter An HWIM Application - Hello World makes use of the user interface objects and thus forms a better model for a real application. The example source The source for this application consists of three files: prndir.cat the prrLIstT object class definition dirlist.c the prRLIst method functions dirmain.c main() and auxiliary functions The category file, prndir.cat, is as follows: IMAGE prndir EXTERNAL olib INCLUDE varray.g INCLUDE p_file.h CLASS dirlist vaxvar Directory list { REPLACE va_test sort by various criteria TYPES { typedef struct { P_INFO info; TEXT name [P_FNAMESIZE]; } DIRLIST_ITEM; 2 BUILDING AN OBJECT ORIENTED APPLICATION The method function file, dirlist.c, contains only one method function, the replacement for the va_test method: /* DIRLIST #7 #include #include #pragma METHOD_CALL METHOD INT dirlist_va_test (PR_DIRLIST *self,RC_VAXVAR *precl,RC_VAXVAR *prec2) /* Firstly perform a 2 way test on file or dir name records. Order dir names before file names. Otherwise order names alphabetically. Return 0 if equal, <0 if *precl is before *prec2, >0 if after. /, { FAST DIRLIST_ITEM *pl,*p2; FAST INT ret; ret=0; pl=(DIRLIST_ITEM *)precl-—->buf; p2=(DIRLIST_ITEM *) prec2->buf; if ((pl->info.statusé&P_FADIR) * (p2->info.status&P_FADIR) ) ret=(pl—->info.status&P_FADIR) ?-1:+1; else /* both records either dir or file names */ ret=p_scmp (&p1->name[0], &p2->name[0]); if (self->varoot.key.desc) ret=(-ret); /* reverse result if required */ return (ret); } The file dirmain.c, listed below, uses the console device for obtaining keyboard input and displaying its output. Provided a valid directory name is typed in, the code builds, in an instance of the prru1st variable array class, a corresponding directory listing. The file names are stored and displayed in alphabetical order. Press Enter at the input prompt to exit the program. /* DIRMAIN ay #include #include LOCAL_D VOID *dcb=NULL; LOCAL_D VOID *hand; LOCAL_C VOID error(TEXT *msg, INT errno) { TEXT bb[E_MAX_ERROR_TEXT_SIZE]; p_close(dcb); dcb=NULL; p_errs (&bb[0],errno) ; p_printf("%Ss: %s",msg, &bb[0]); } LOCAL_C VOID panic(TEXT *msg, INT errno) { error (msg,errno); p_leave (0); } OBJECT ORIENTED PROGRAMMING GUIDE LOCAL_C VOID PrintDirLine (TEXT *name, P_INFO *pinfo) { P_DAYSEC ds; P_DATE dt; TEXT *p,b[40]; p=&b[0]; if (pinfo->status&P_FAVOLUME) p=p_scpy(p,"Vol,"); if (pinfo->status&P_FADIR) p=p_scpy(p,"Dir,"); if (pinfo->status&P_FAMOD) p=p_scpy (p, "Mod, "); if (! (pinfo->status&P_FAWRITE) ) p=p_scpy (p, "Read,"); if (pinfo->status&P_FASYSTEM) p=p_scpy (p,"Sys,"); if (pinfo->status&P_FAHIDDEN) p=p_scpy (p,"Hid,"); if (*(p-1)==',') *-—p=0; p_sttods (&pinfo->modst, &ds) ; p_dstodt (&ds, &dt) ; p_printf£("S- 12s S7lu %02u-%02u-%S02u %02u:%02u Ss", name, pinfo->size,dt.day+1l,dt.month+1,dt.year,dt.hour,dt.minute, &b[0]); } LOCAL_C VOID MakeDirList (TEXT *dir) { INT ret; DIRLIST_ITEM d; RC_VAXVAR rec; p_send2 (hand, O_VA_RESET) ; if ((ret=p_open (&dcb, dir, P_FDIR) ) !=0) panic("Failed to open directory file",ret); while (! (ret=p_iow(dcb, P_FREAD, &d.name[0],&d.info) )) { rec.buf=(UBYTE *) &d; rec.len=sizeof (d. info) +p_slen(&d.name[0]) +1; p_send4 (hand, O_VA_INSERTISQ, érec, &i) ; } p_close(dcb); dcb=NULL; if (ret !=E_FILE_EOF) panic("Failed to read directory",ret); 2 BUILDING AN OBJECT ORIENTED APPLICATION GLDEF_C INT CDECL DoDirLists (VOID) { UWORD i, count; DIRLIST_ITEM *pd; TEXT name [P_FNAMESIZE]; hand=f_newsend (CAT_PRNDIR_PRNDIR, C_DIRLIST,O_VA_INIT,16); while (p_getl(">", &name[0],P_FNAMESIZBE) ) { MakeDirList (&éname[0]); if (! (count=p_send2 (hand, O_VA_COUNT) ) ) p_printf("No files found"); else { for (i=0;iname[0], &pd->info) ; } } } p_send2 (hand, O_DESTROY) ; return (0); } GLDEF_C INT main(VOID) { INT err; p_linklib(0); /* Link to OLIB */ if ((err=p_enter((VOID *)DoDirLists) ) !=0) error ("Called p_leave",err) ; return (0); } Note that a standard console application is not suitable for running on an EPOC emulator. See the code in the Using the example DYL section of the Building a Dynamic Library chapter for how to adapt console code so that it is suitable to run on an emulator. Building the example application In order to illustrate the various stages in building an object oriented application, the description of the building of the example application makes use of a number of separate batch files. An alternative would be to make more use of the TopSpeed development environment and this approach is used in the building of the Hello World example, described in the chapter An HWIM Application - Hello World. The first step in building the application is to translate the category file, prndir.cat, using the tool ctran.exe. This generates a number of files, including a prndir.g include file and a prndir.c C language source file. The generated .c file must be compiled and the resulting .obj file must be converted so that its class descriptor data is located in the code segment, using ecobj.exe. The entire process is conveniently performed by means of the supplied batch file, ct.bat (in \sibosdk\sys) whose content is: @echo off ctran %1 -e..\include -x..\include -g..\include -c -l -s -v if errorlevel 1 goto end call cc $1 ecobj %1 send The meaning of the various flags that can be passed to ctran.exe are explained in appendix A of this manual. Note that the above batch file is set up assuming that the source code is in a \sibosdk\oopdemo directory and that all include files are assumed to be found in a ..\include directory (which is also the destination for include files generated by ctran). You must ensure that the current TopSpeed redirection file, ts.red, is set up to correspond with the location of all include files. In particular, the redirection file must include a line such as: Reg = .; ..\ INCLUDE; OBJECT ORIENTED PROGRAMMING GUIDE There is no need to include the location of generated .ext files in ts.red, since these are only referenced by ctran. The remaining .c source files must be compiled as normal, for example, by using the same cc.bat file as is suitable for being called from ct.bat: @echo off call checkvid tsc %1l.c /fpunnamed /%jpivids% This assumes the presence of an unnamed.pr project file of a similar form to that used when compiling non-object oriented programs, for example: #system epoc img #set epocinit=iplib #model small jpi #compile %main #link %main Finally the application must be linked. A suitable /f-bat link batch file is: @echo off call checkvid tsc %l.pr /1 /%jpivids which is called as: lf prndir assuming the presence of a prndir.pr file containing: system epoc img set epocinit=iplib model small jpi pragma link (olib.1lib) pragma link (prndir) pragma link (dirlist) pragma link (dirmain) link prndir This generates a prndir.img file which may be copied to a SIBO machine and executed as any other .img file. Note that, during the link process, two warning messages are always displayed, warning of duplicate tables. These messages should be ignored. Note that an object oriented application must be linked with the PLIB library since none of the object oriented mechanisms are supported by CLIB. The entire build process may be summarised in, say, a bldpdir.bat batch file (not supplied) as follows: call ct prndir Call. ce-dirlist call cc dirmain 1f prndir CHAPTER 3 BUILDING A DYNAMIC LIBRARY A dynamic library (DYL) is built from a category file and one or more method function source files in a similar way to the building of an application. A DYL differs from an application in the following ways: e it has no specific entry point and thus does not require a main function e it may not contain any static data e its category file should start with a LIBRARY statement e itis linked with a different startup module A DYL must be built with the PLIB (rather than CLIB) library, although you may, as usual, use a mix of PLIB and CLIB function calls. You should not use true floating point arithmetic in a DYL, but you may use the PLIB functions that avoid the 8087 emulator, described in the Floating Point chapter of the PLIB Reference manual. An example DYL This dynamic library (the source of which, on installation, is copied into a \sibosdk\oopdemo directory) contains one class, providing a method to sort an array of integers, using the quicksort service provided by PLIB. The example source The source for this application consists of two files: sort.cat the sort object class definition isort.c the tsort method function The category file, sort.cat, is as follows: LIBRARY sort EXTERNAL olib INCLUDE olib.g CLASS isort root { ADD sort Sort a given list of integers } OBJECT ORIENTED PROGRAMMING GUIDE The method function file, isort.c, contains only one method function: / * SORT.C */ include define IdataBuf ((INT *) DataBuf) LOCAL_C INT OrdFunc(INT i, INT j,VOID *DataBuf) /* Order function used in qsort */- { return (IdataBuf [i]-IdataBuf[j]); } LOCAL_C VOID ExchFunc(INT i, INT j,VOID *DataBuf) /* Exchange function used in qsort a { INT k; k=IdataBuf [i]; IdataBuf [i]=IdataBuf[j]; IdataBuf [j]=k; } #pragma METHOD_CALL METHOD VOID isort_sort(PR_ROOT *self,INT *start,INT num) { p_qsort (num, OrdFunc, ExchFunc, start) ; } Building the example DYL As in the previous chapter, in order to illustrate the various stages, a number of separate batch files are used. Again, an alternative would be to use the more integrated approach as is used in the building of the Hello World example, described in the chapter An HWIM Application - Hello World. The first step in building the DYL is to translate the category file, sort.cat, using the tool ctran.exe. This generates a number of files, including a sort.g include file and a sort.c C language source file. As in the case of building an application, the generated .c file must be compiled and the resulting .obj file must be converted so that its class descriptor data is located in the code segment. The entire process is again conveniently performed by means of the same ct.bat batch file as is used for application category files. See the Building an Object Oriented Application chapter for further details. The remaining .c source files (in this case, only isort.c) must be compiled as normal, using the same cc.bat file as is suitable for compiling application source files. Finally the DYL must be linked. As for building an application, the link is controlled by a project file, in this case sort.pr: #system epoc dyl #set epocinit=iplib #model small jpi #pragma link (olib.1lib) #pragma link (sort) #pragma link (isort) #link sort The significant difference between a project file for a DYL and that for an object oriented application is that the file type is declared as ay1, rather than img. Again, it is essential that the PLIB library be used. 3 BUILDING A DYNAMIC LIBRARY You may use the same /f-bat link batch file as for linking applications, but you may wish to use the following [fc.bat variant: @echo off if exist %1.dyl del %1.dyl call checkvid tsc Sl.pr /1 /Sjpivids This ensures that any failure in the link process does not result in an older version of the DYL being left. It is called as: Life. sort The entire build process may be summarised in a dylbld.bat batch file, as follows: call ct sort call ce isort lfc sort Using the example DYL The following code, in runsort.c, illustrates a simple application that uses sort.dyl to sort the contents of an array of ten integers. /* RUNSORT.C yf, #include #include GLREF_D VOID *DatCommandPtr; GLDEF_D P_RECT _DefScreenRect; LOCAL_D INT array[] = {10,1,5,7,9,3,6,8,4,2}; #pragma save, ENTER_CALL LOCAL_C INT RunSort (HANDLE dyl) { VOID *sort; sort=f_newlibh (dyl,C_ISORT) ; p_send4 (sort,O_SORT, &array[0],10); p_send2 (sort,O_DESTROY) ; return (0); } #pragma restore GLDEF_C INT main(VOID) { INT err,i; HANDLE dyl; TEXT buf [P_FNAMESIZE]; p_fparse("sort.dyl",DatCommandPtr, &buf[0],NULL) ; err=p_loadlib (&buf[0],&dyl, TRUE) ; if (!'!err) { _DefScreenRect.t1.x=0 _DefScreenRect.tl.y=0; _DefScreenRect.br.x=40; /* 40 columns */ _DefScreenRect.br.y=8; /* 8 rows */ ; /* set console window size */ err=p_enter2 (RunSort,dyl); p_unloadlib(dyl) ; for (i=0;i<10;i++) p_printf("%sd",array[i]); } return(err); } In this example the DYL is loaded and linked by the call to p_loadiib. Since the DYL name is parsed with DatCommanaPtr, sort.dyl will be expected to be found in the directory from which runsort is executed. 3-3 OBJECT ORIENTED PROGRAMMING GUIDE Reporting is via an automatically opened console window, whose size is set to be suitable for the Series 3 screen. Note that the code to set the console window size, using _DefScreenRect, 1s only necessary when you want to override the default size, or when the application is to be run on an emulator. If you include this code in an application, ignore the spurious warning of duplication that is given by the linker. A DYL that supplies the ROOT class Since OLIB contains classes that provide many basic services, most DYLs will either reference or subclass one or more OLIB classes. They will therefore need to declare an external reference to OLIB, as in the previous example. In that example, however, the external reference is necessary only because the rsort class subclasses the Root class provided by the OLIB dynamic library. In such a case it may be more reasonable for a DYL to define its own Root class and be independent of OLIB. The following sample code provides the same integer sorting functionality as the previous example, but without requiring an external reference to OLIB. The category file, rsort.cat, 1s as follows: LIBRARY rsort INCLUDE p_std.h INCLUDE p_object.h CLASS root { ADD destroy ADD sort Sort a given list of integers PROPERTY { P_OBJECT pc; Class link } } The class definition of Root duplicates that of the OLIB root class with, in this case, the addition of the sort method. The method function source file, root.c is almost identical with that of isort.c, the only significant difference being that the method function is renamed to root_sort. /* ROOT.C */ include define IdataBuf ((INT *)DataBuf) LOCAL_C INT OrdFunc(INT i, INT j,VOID *DataBuf) /* Order function used in qsort */ { return (IdataBuf [i]-IdataBuf[j]); } LOCAL_C VOID ExchFunc(INT i, INT j,VOID *DataBuf) /* Exchange function used in qsort a: { INT k; k=IdataBuf [i]; IdataBuf [i]=IdataBuf[j]; IdataBuf [j]=k; } 3 BUILDING A DYNAMIC LIBRARY #pragma METHOD_CALL METHOD VOID root_sort (PR_ROOT *self, INT *start,INT num) { p_qsort (num, OrdFunc, ExchFunc, start) ; } Note that there is no need to provide the root_destroy method function, since this is supplied by the PLIB library. Building the DYL follows exactly as in the previous example, using the link project file, rsort.pr: #system epoc dyl #set epocinit=iplib #model small jpi #pragma link (olib.1lib) #pragma link (rsort) #pragma link (root) #1link rsort Building DYLs into an application One or more DYLs may be combined into a .img or a .app file. The technique is similar to the add-file technology that can combine a resource file, an icon and a shell data file with a .img file. A significant difference is that whereas add-files are limited to a maximum of four add-files, there is no limit on the number of DYLs that may be added. The main advantage of building DYLs into an application is that there is then no danger of the various files becoming separated, or of an essential DYL being accidentally deleted. There can never be any confusion over the location of a DYL and if the application is present, its DYLs must also be present. DYL add-file lists A DYL add-file list is a text file with a .dfl extension, containing a list of the names of the DYLs that are to be combined with a .img file. For example, the Series 3a Spreadsheet has a .dfl file with the content: hfl.dyl hgf.dyl hgp.dyl hdb.dyl hta.dyl .dyl hpr.dyl hrg.dyl hvw.dyl hso.dyl HHONDHHHADWH YN DD =) ion BK to build ten DYLs into the Spreadsheet application. When any .pr project file is invoked that leads to the building of a .img file, a check is made for the existence of a .dfl file with the same name as the application. If this file exists, the DYLs it lists are automatically built into the .img file, in the order in which they are listed. Accessing a built-in DYL A DYL that is built into an application is accessed by use of the functions p_openiib and p_loadfilelib., as in the following example. The required DYL is specified in a call to p_loadfilelib by an index, counting from zero, where the numbering order is determined by the order in which the DYLs are listed in the .dfl file. The example code, dylsort.c, is a variant of runsort.c, described earlier. It has exactly the same action as the earlier example, but sort.dyl is built into the resulting .img file, rather than being a separate file. /* DYLSORT.C */ #include #include OBJECT ORIENTED PROGRAMMING GUIDE GLREF_D VOID *DatCommandPtr; GLDEF_D P_RECT _DefScreenRect; LOCAL_D INT array[] = {10,1,5,7,9,3,6,8,4,2}; #pragma save, ENTER_CALL LOCAL_C INT RunSort (HANDLE dyl) { VOID *sort; sort=f_newlibh (dyl,C_ISORT) ; p_send4 (sort,O_SORT, &array[0],10); p_send2 (sort, O_DESTROY) ; return (0); } #pragma restore GLDEF_C INT main(VOID) { INT err,i; HANDLE dyl; VOID *dcb; p_openlib(&dcb,DatCommandPtr); /* open application .img file for DYL access */ err=p_loadfilelib(dcb,0,&dyl,TRUE); /* load the first (and only) DYL */ if (!'err) { _DefScreenRect.t1l.x=0; _DefScreenRect.tl.y=0; _DefScreenRect.br.x=40; _DefScreenRect.br.y=8; peprintL ("Setting ...");7 err=p_enter2 (RunSort,dyl); p_unloadlib(dyl) ; for (i=0;i<10;i+=2) p_printf£("s4d %4d",array[i],array[it1]); p_getch(); } return(err); } The only difference in the code between this example and runsort.c is in the first two lines of main(). In contrast with the earlier example, there is no need to parse the application's full file specification (pointed to by DatcommanapPtr) with the DYL file name, since the file containing the DYL is the application file itself. The DYL add-file list is dylsort.dfl, which contains the single line: sort.dyl The application can be built in a similar way to runsort, that is, by: cc dylsort 1f dylsort Running edump.exe, by typing: edump dylsort produces the following output, showing the presence of the built-in sort.dyl. 3 BUILDING A DYNAMIC LIBRARY EDump V4.30F (09/11/93) Copyright (C) Psion PLC 1989-92 LOC: :E:\SIBOSDK\OOPDEMO\DYLSORT.IMG IMAGE file data Image version = 200F Code Segment = 0330 (bytes) Initial IP = 0000 Stack = 1000 (bytes) Data = 0070 (bytes) Heap = 0800 (bytes) Data Segment = 1870 (bytes) Initialized data = 0050 (bytes) Code checksum = 765A Data checksum = 06B7 Code Version = 100F Priority = 0080 Header size = 0040 (bytes) Dyl count = 0001 Dyl table offset = 000005E0 Dyl 00 SORT.DYL offset = 000003C0 Image file size = 000005F2 (bytes) CHAPTER 4 AN HWIM EXAMPLE - HELLO WORLD This chapter describes a minimal HWIM application. The application displays a bordered window containing the text "Hello world" and has a menu bar that offers a single Exit option. It is written so that it will run on either the Series 3 or the Series 3a (in compatibility mode). Note that this application is not intended to exercise the full potential of OOP. Instead, it gives a "feel" for the construction of an OOP application and, while its broad structure will be described, a full understanding may not be apparent until later chapters in this manual have been read. The example code does, however, provide the basic framework of all HWIM applications and may be used as a starting point for the construction of more complex applications. The source of this application is supplied in the \sibosdk\oopdemo directory that can be installed from the C SDK disks. The main source files for the application are listed below, and are described in more detail in the following sections. Category file, hello.cat IMAGE hello EXTERNAL olib EXTERNAL hwim INCLUDE hwimman.g CLASS hellows wserv window server active object { REPLACE ws_dyn_init } CLASS hellobw bwin a simple bordered window { REPLACE wn_init REPLACE wn_draw } Resource file, hello.rss /* HELLO.RSS English resource file for Hello World application */ #include #include RESOURCE WSERV_INFO hello_accs { menbar_id=hello_menbar; first_com=O0_COM_EXIT; accel={'x'}; /* Exit */ } OBJECT ORIENTED PROGRAMMING GUIDE RESOURCE MENU_BAR hello_menbar { items= { MENU_BAR_ITEM { menu_id=special_menu; mb_item="Special"; } } RESOURCE MENU special_menu { items = { MENU_ITEM { com_id=O_COM_EXIT; mn_item="Exit"; } di } Source code, o_hello.c /* O_HELLO.C my #include #include GLREF_D WSERV_SPEC *wserv_channel; GLDEF_C VOID main (VOID) { IN_HWIMMAN app; IN_WSERV ws; p_linklib(0); app. flags=FLG_APPMAN_RSCFILE|FLG_APPMAN_SRSCFILE|FLG_APPMAN_CLEAN; app.wserv_cat=p_getlibh (CAT_HELLO_HELLO) ; ws.com_cat=p_getlibh (CAT_HELLO_HWIM) ; app.wserv_class=C_HELLOWS; ws.com_class=C_COMMAN; p_send4 (p_new (CAT_HELLO_HWIM, C_HWIMMAN) ,O_AM_INIT, &app, &ws) ; } #pragma METHOD_CALL METHOD VOID hellows_ws_dyn_init (PR_HELLOWS *self) { self->wserv.cli=f_new (CAT_HELLO_HELLO, C_HELLOBW) ; p_send2 (self—->wserv.cli,O_WN_INIT)j; } METHOD VOID hellobw_wn_init (PR_HELLOBW *self) { W_WINDATA wd; wd.extent.tl.x=0; wd.extent.tl.y=0; wd.extent .width=wserv_channel->conn.info.pixels.x; wd.extent .height=wserv_channel-—>conn.info.pixels.y; p_send5 (self, O_WN_CONNECT, NULL, W_WIN_EXTENT, &wd) ; p_send3 (self,O_WN_VISIBLE, WV_INITVIS) ; p_send3 (self, O_WN_EMPHASISE, TRUE) ; } METHOD VOID hellobw_wn_draw(PR_HELLOBW *self) { p_supersend2 (self,O_WN_DRAW) ; gPrintText (50,50,"Hello World",11); } 4 AN HWIM EXAMPLE - HELLO WORLD The category file The application's category file is as follows: IMAGE hello EXTERNAL olib EXTERNAL hwim INCLUDE hwimman.g CLASS hellows wserv window server active object { REPLACE ws_dyn_init } CLASS hellobw bwin a simple bordered window { REPLACE wn_init REPLACE wn_draw } The imac statement identifies the file as being one that will create a category for an application (img or .app) file. Note that the external references to the OLIB and HWIM DYLs are mandatory for all HWIM applications. The category file defines two application-specific classes, HELLows and HELLOBW. These are subclasses of the HWIM wserv window server active object and pwn bordered window classes respectively. These two subclasses add no property or new methods; they just replace methods that are defined in a superclass. The swin class is described in more detail in the Windows chapter. The resource externals file The resource file, described in the next section, contains a reference to the o_com_Ex1T method number. This symbol is defined in the hwimman.g include file (itself generated by the translation of the category containing the hwimman class). A resource externals file, with file name hello.re, should be written, containing the following lines: #include _O_COM_EXIT The file hello.rg is generated by the re. bat batch file by typing: re hello and contains the single line: #define O_COM_EXIT 7 This process has extracted the definition of the symbol o_com_exi1t from the hwimman.g include file, so that the generated .rg file can be #included in the application resource file instead of the larger hwimman.g. OBJECT ORIENTED PROGRAMMING GUIDE The resource file The resource file, hello.rss, is one of the simplest possible HWIM application resource files: /* HELLO.RSS English resource file for Hello World application ae A #include #include RESOURCE WSERV_INFO hello_accs { menbar_id=hello_menbar; first_com=O_COM_EXIT; accel={'x"'}; /* Exit */ } RESOURCE MENU_BAR hello_menbar { items= { MENU_BAR_ITEM { menu_id=special_menu; mb_item="Special"; } ‘i; } RESOURCE MENU special_menu { items = { MENU_ITEM { com_id=O_COM_EXIT; mn_item="Exit"; } i } In addition to hello.rg, it includes the HWIM resource header file, hwim.rh, that defines the standard resource structures used by HWIM applications (for example, the wszERv_1nFo resource structure). This resource file contains three resources that must be present in all HWIM application resource files, being used by the HWIM command menu mechanism. The first resource must always be a wsERV_INFo resource structure. Its reference name (in this case, hello_accs) 1s normally not relevant. The menbar_id element must refer to a following mzenu_BAR resource structure that defines the content of the application's menu bar, and first_com Is set to the method number of a method of the application's command manager (usually, as in this case, o_coM_EXIT). The acce1 element defines one or more accelerator keypresses. Each accelerator may be used to invoke a command menu option by executing a command manager method function. In this example there is only one accelerator, Psion-X, which executes the command manager's exit method (with method number O_COM_EXIT). The menu_gar resource defines a menu bar containing a single menu, with menu name "Special", and an associated pull-down menu defined in the menu resource structure referenced by special_menu. This pull- down menu contains only the single menu option "Exit". The source code The code is sufficiently brief that there is no advantage in writing it as a number of separate C modules. All the code is in the file o_hello.c (so named to distinguish it from the hello.c that is generated during the translation of hello.cat). 4-4 4 AN HWIM EXAMPLE - HELLO WORLD /* O_HELLO.C af #include #include GLREF_D WSERV_SPEC *wserv_channel; GLDEF_C VOID main(VOID) { IN_HWIMMAN app; IN_WSERV ws; p_linklib(0); app. flags=FLG_APPMAN_RSCFILE|FLG_APPMAN_SRSCFILE|FLG_APPMAN_CLEAN; app.wserv_cat=p_getlibh (CAT_HELLO_HELLO) ; ws.com_cat=p_getlibh (CAT_HELLO_HWIM) ; app.wserv_class=C_HELLOWS; ws.com_class=C_COMMAN; p_send4 (p_new (CAT_HELLO_HWIM, C_HWIMMAN) ,O_AM_INIT, &app, &ws) ; } #pragma METHOD_CALL /* APPLICATION-SPECIFIC WINDOW SERVER OBJECT */ METHOD VOID hellows_ws_dyn_init (PR_HELLOWS *self) { self—->wserv.cli=f_new (CAT_HELLO_HELLO, C_HELLOBW) ; p_send2 (self-—>wserv.cli,O_WN_INIT) ; } /* BORDERED CLIENT WINDOW */ METHOD VOID hellobw_wn_init (PR_HELLOBW *self) { W_WINDATA wd; wd.extent.tl.x=0; wd.extent.tl.y=0; wd.extent .width=wserv_channel->conn.info.pixels.x; wd.extent .height=wserv_channel-—>conn.info.pixels.y; p_send5 (self, O_WN_CONNECT, NULL, W_WIN_EXTENT, &wd) ; p_send3 (self,O_WN_VISIBLE, WV_INITVIS) ; p_send3 (self, O_WN_EMPHASISE, TRUE) ; } METHOD VOID hellobw_wn_draw(PR_HELLOBW *self) { p_supersend2 (self,O_WN_DRAW) ; gPrintText (50,50,"Hello World",11); } Main The main() function is basically as specified in the Source files section of the Introduction chapter. Note that, since the window server active object is subclassed by the application, it is indicated to be in the local category (category number caT_HELLO_HELLO). In contrast, the application uses the basic command manager supplied by HWIM and thus in the HWIM external category (category number caT_HELLO_HWIM). The method functions are preceded by: #pragma METHOD_CALL to ensure the correct calling convention. Window server object The ws_dyn_init method supplied for the HzLLows class simply creates an instance of the HELLOBW bordered window subclass and sends it a wn_InrT message. The window is set to be the application's client window by writing its handle to the window server active object's wserv.cli property. OBJECT ORIENTED PROGRAMMING GUIDE Client window Initialisation of the bordered client window sets the window's size to the screen dimensions, as read from the wsERV_sPEc struct pointed to by the magic static wserv_channe1. Most application-specific window classes will supply a wn_init method that, at some stage, sends a wN_connEcT message. Normally, as in the present case, the client window is a top-level window (it has no parent) as indicated by the nuLu parameter to the wN_conNECT message. The window is made visible and set to the emphasised state by sending wN_v1sIBLE and wN_EMPHASISE messages. These messages are processed by superclass method functions (described in the Windows chapter of this manual). The wn_draw method is not explicitly called by application code, but will be called directly from system code whenever the window must be drawn (such as when it first becomes visible). See also the The draw/redraw mechanism section of the Windows chapter. After supersending the wn_praw message (handled by the swin superclass, to draw the window's border) the text "Hello World" is drawn, using the window server function gPrintText. Building the application Unlike the simple examples in earlier chapters, the "Hello World" example provides a good basic model for most object oriented applications written for the Series 3 and Series 3a machines. The mechanism supplied for building this example, described below, is suitable for use with any such application. It uses the TopSpeed Make mode to invoke the project system, in order to minimise the amount of compilation and linking required to make sure that the .img file is up-to-date. The .pr file for the "Hello World" application is hello.pr and is listed below. system epoc img set epocinit=iplib model small jpi abort on set version=0x100F set priority=0x80 set heapsize=0x80 compile %main.cat if tremake #or (%main.rg #older Smain.re) #or (%Smain.rg #older %main.g) #then run "re %main" no_window no_abort endif if (Smain.rsg #older %main.rss) #or (%main.rsg #older %Smain.rg) #then run "rs %main" no_window no_abort endif if Smain.rzc #older %Smain.rsg #then run "rch $main" no_window no_abort endif compile o_hello.c if Smain.shd #older %main.ms #then #run "makeshd %main" no_window no_abort #file delete %smain.img endif if (Smain.img #older %main.afl) #or (%main.img #older %main.pic) #then #file delete %Smain.img endif pragma link(olib.1lib) pragma link (hwim.1lib) link %Smain The TopSpeed project system takes account of the dependencies in the application's C source files but, as can be seen in this example, the .pr file must explicitly take into account the dependencies of all other files that are needed to build the application. 4 AN HWIM EXAMPLE - HELLO WORLD The hello.pr project file is suitable for use as a model for building any object oriented application using the HWIM library. In the vast majority of cases the main change will be to replace the line: #compile %o_hello.c with one or more #compile statements to compile the various C source files that contain the application's code. In this example the variables version, priority and heapsize are set (as hexadecimal numbers) to their default values - they will be given these values automatically if they are not set in the .pr file. Only in exceptional circumstances will you need to alter the application's priority, but you should set both the version number and the heap size as a matter of course. See the General System Services chapter of the PLIB Reference manual for a description of the format of a version number. The heap size is measured in paragraphs (16-byte units) and should normally be set to a value that is not less than the amount of the heap used when the application is started up (one way of determining this is to use the Spy application, described in the Series 3/3a Programming Guide). There is more information about the use of these three variables in the Building an Application chapter of the General Programming Manual. The version of tsprj.txt supplied with the SDK contains a compiler entry to handle the translation of category files. It is therefore important to copy this file into your \ts\sys directory and run tscfg.exe, as described in the Installation chapter of the General Programming Manual, even if you are upgrading from an earlier version of the SDK. This entry, which is listed below, takes into account the creation dates of the category file and the .g file that is generated from it when determining if the category file needs translating. #declare_compiler cat= '#split %%srce #set make=%%remake #if Stmake #or %sname.g #older S%name.cat #then #run "ctran %%name -c -s -v -l -e..\include\ -x..\include\ -g..\include\" no_window no_abort #rundll TSC S%name.c %%name.obj #run "ecobj S%name.obj" no_window no_abort #endif #pragma link(%%name.obj)' If you include application-specific header files in the application's category file you will need to add an explicit check in your .pr file to ensure that the application is rebuilt properly. If, for example, your category file contains the line: NCLUDE myheader.h you will need to insert the lines: if myheader.h #older %main.cat #then #file delete %main.g endif immediately before the line: compile %Smain.cat Apart from the #pragma link statements to include the standard libraries (OLIB and HWIM) the remainder of the .pr file takes care of the dependencies of the application on the remaining component files in a fairly straightforward way. If you develop an application that contains built-in DYLs you will need to add further checks on the creation dates of the .dfl DYL add-file list and the DYLs that it lists. A suitable check for the .dfl file would be: #if Smain.img #older %main.dfl #then #file delete %Smain.img #endif OBJECT ORIENTED PROGRAMMING GUIDE and a check for mydyl.dyl is: #if Smain.img #older mydyl.dyl #then #file delete %Smain.img #endif Any such lines should be inserted immediately before the first #pragma link statement. The "Hello World" application is built by typing: make hello This makes use of a make.bat batch file, whose contents is as follows: @echo off call checkvid if not exist %l.pr goto error tscx /m %1 /%jpivids tscx /m %1 /%jpivids goto end :error echo Project file %1.pr does not exist send Note that this batch file uses tscx, rather than tsc, so that full use may be made of the extended memory of your PC. If you do not have extended memory, you should replace each occurrence of tscx with tsc. The batch file uses two passes of the TopSpeed project system. This is necessary because the project system does not take into account dependencies that change dynamically as a result of the ‘compilation’ of the Psion-specific source files. This will not normally result in any unnecessary repetitions of compilation or linking. The result of typing make hello is the creation of hello.img. The final step in producing the "Hello World" application is (optionally) to rename hello.img as hello.app. Variants You may like to experiment with making the following variations to the supplied example code: e To make the application take advantage of the larger screen size on the Series 3a, or the flag FLG_APPMAN_FULLSCREEN into the flag values assigned to app. flags iN main(). e =©In the HELLOWS ws_dyn_init method, the lines: self—->wserv.cli=f_new (CAT_HELLO_HELLO, C_HELLOBW) ; p_send2 (self-—>wserv.cli,O_WN_INIT); could be replaced by the more compact: self—->wserv.cli=f_newsend (CAT_HELLO_HELLO, C_HELLOBW, O_WN_INIT) ; e In the HELLOBW wn_init method, replace p_send3 (self, O_WN_VISIBLE, WV_INITVIS) ; with the equivalent, but more compact: hiInitVis (self); e Modify the bordered window's wn_draw method to centre the text in the window, in a way that works for both the Series 3 and Series 3a, by reading the screen dimensions from wserv_channel->conn.info.pixels e §=Put the "Hello World" text in the resource file and load it from there (for guidance, see the examples in the Commands and Command Menus chapter). CHAPTER 5 COMMANDS AND COMMAND MENUS The command manager The command manager is created and initialised by system code during the start-up process, immediately before the window server active object receives a WS_DYN_INIT message. If the creation of the command manager fails the application process will terminate immediately. If the command manager is created successfully, its handle is accessible via the handle of the window server active object, held in the magic static w_ws. This static should be declared as!: GLREF_D PR_WSERV *w_ws; and the command manager's handle is then w_ws->wserv.com. See also the utility function hwservCcomSend that is used by system code to send messages to the command manager. The HWIM cowman class definition (whose associated generated header file is comman.g) is as follows: CLASS comman root Superclass of all command managers ADD com_init=p_dummy User's own initialisation ADD com_statwin=p_dummy Toggle permanent status window ADD com_accl_check=p_true Called whenever an accelerator is matched ADD com_menu=p_dummy Called whenever a menu is pulled down ADD com_mode_change=p_dummy W_KEY_MODE received ADD com_file_change=p_dummy The core code for open or new ADD com_exit Message sent here on exit accelerator CONSTANTS { O_COM_SYS_LAST O_COM_EXIT } } The supplied methods of the command manager are called by system code. As is apparent from the class definition, the supplied method functions do very little. Although there is no need to replace any of these methods, they are intended to be replaced, as necessary, in an application-specific subclass. The supplied com_exit method, for example, simply calls p_exit (0). While this is sufficient for a simple application, the method will usually be replaced - particularly if the application is file-based. The uses of most of these methods, and the circumstances under which the corresponding messages are sent by system code are discussed later in this chapter and are fully described in the in the Command Manager chapter of the HWIM Reference manual. The com_file_change and com_exit methods are also discussed in the File Based Applications chapter of this manual. 'Depending on the application, it may be more appropriate to declare w_ws as a pointer to an instance of an application-specific subclass of wsERV. OBJECT ORIENTED PROGRAMMING GUIDE Adding command options Any HWIM application that has a more complex set of commands than that of the "Hello World" example applicaton will need to supply more extensive menu bar and pull-down menu resources. It will also need to subclass the comman command manager class. The basic mechanism for extending the number of command options involves the following steps: e Extend the menu bar and pull-down menu resources in the application resource file to include a menu item for each command. e Subclass the command manager to add a new method for each additional command and supply a method function for each method.? The methods must appear consecutively in the command manager's class definition and normally must immediately follow the last (com_exit) method of the comman superclass. e Include an accelerator in the first resource (a wsERV_INFo resource) in the application resource file for each additional command. The list of accelerators must obey the following rules: e There must be a unique accelerator for every command option that appears in the menus. e The number of accelerators in the list must match with a contiguously declared set of command manager methods. e The accelerators must be listed in the same order as the method declarations in the command manager class. Note, however, that there need be no relation between the order of the accelerators and the order in which the commands appear in the command menus. The Exit option, for example, conventionally appears as the last item in the last menu, although it is normally first in the list of accelerators. The sending of the appropriate message to the command manager when a command menu option is selected, either by means of an accelerator keypress or via the pull-down menus, is handled by system code in the window server active object and requires no additional application-specific code. The method functions that correspond to the menu options are not expected to return a value and should be declared as voip functions. In general these functions do not take parameters, but may optionally make use of a single INT parameter, containing the function's method number, this being passed in system-generated messages (see the later Sharing method function code section). Example The following example extends the "Hello World" example to provide two additional command options in a separate pull-down menu. The additional commands switch the display to one of two alternative messages. The source code is supplied in the files hello2.rss, hello2.cat and o_hello2.c. The resource file is as follows: /* HELLO2.RSS English resource file af, #include #include RESOURCE WSERV_INFO hello_accs { menbar_id=hello_menbar; first_com=O_COM_EXIT; accel={'x', /* Exit */ ‘ht, /* Hello */ ou a /* Bye */ 2Note that, if appropriate, several commands may share the code of a single method function. This alternative is described later. 5-2 5 COMMANDS AND COMMAND MENUS RESOURCE MENU_BAR hello_menbar { items= { MENU_BAR_ITEM { menu_id=message_menu; mb_item="Message"; hy MENU_BAR_ITEM { menu_id=special_menu; mb_item="Special"; } RESOURCE MENU message_menu { items = { MENU_ITEM { com_id=O_HCM_HELLO; mn_item="Say hello"; hy MENU_ITEM { com_id=O_HCM_BYE; mn_item="Say goodbye"; } } RESOURCE MENU special_menu { items = { MENU_ITEM { com_id=O_COM_EXIT; mn_item="Exit"; } RESOURCE STRING hello_message {str="Hello World"; } RESOURCE STRING bye_message {str="Goodbye"; } The list of accelerators contains three items, and the menu bar has two pull-down menus; a 'Message' menu and a ‘Special’ menu. The ‘Message’ menu contains the two options 'Say hello’ and 'Say goodbye’, associated with the command manager methods nem_hello and hcm_bye respectively. The two text messages to be displayed are included in the resource file, rather than appearing as data in the source code. The category file defines a HELLO2 category (the tmacE statement 1s IMAGE hello2) that contains an additional class definition for the HELLOc™ class - a subclass of the HWIM comman class: CLASS hellocm comman command manager { ADD hcm_hello ADD hcm_bye } Note that the order of declaration of the methods must agree with the order of the associated accelerators in the resource file. OBJECT ORIENTED PROGRAMMING GUIDE The corresponding command manager method functions are: METHOD VOID hellocm_hcm_hello(PR_HELLOCM *self) { p_send3 (w_ws—>wserv.cli,O_WN_SET, FALSE) ; } METHOD VOID hellocm_hcm_bye (PR_HELLOCM *self) { p_send3 (w_ws->wserv.cli,O_WN_SET, TRUE) ; } Both of these methods send a wN_szeT message to the client window, whose handle is available as w_ws->wserv.cli (similar to accessing the command manager itself, as described earlier). Note that these actions correspond to a design decision that the client window should itself be responsible for recording which message to display. The client window class thus needs to replace the wn_set method and specify an item of property to record the message state, as follows: CLASS hellobw bwin bordered client window { REPLACE wn_init REPLACE wn_draw REPLACE wn_set PROPERTY { UWORD isbye; } } The client window wn_draw method uses the state of its isbye property to determine which of the two text resources to display: METHOD VOID hellobw_wn_draw(PR_HELLOBW *self) { UINT resid; TEXT buf [40]; resid=self-—>hellobw.isbye ? BYE_MESSAGE:HELLO_MESSAGE; p_send4 (w_am,O_AM_LOAD_RES_BUF, resid, &buf[0]); /* load the resource */ p_supersend2 (self,O_WN_DRAW) ; /* draw the border */ gPrintText (50,50, &buf[0],p_slen(&buf[0])); } Note that the line: p_send4 (w_am, O_AM_LOAD_RES_BUF, resid, &buf[0]); could be replaced by the following use of the hLoadResBuf utility function: hLoadResBuf (resid, &ébuf[0]); The wn_set method sets or clears this property and causes the window to be drawn with the appropriate message: METHOD VOID hellobw_wn_set (PR_HELLOBW *self,UINT isbye) { self—>hellobw.isbye=isbye; p_send2 (self, O_WN_DODRAW) ; } Note that the application does not explicitly write to the isbye property on initialisation of the window. It takes advantage of the fact that property is zero filled when an object is created. Essentially the same effect could be achieved by replacing the line: p_send2 (self, O_WN_DODRAW) ; with the window server call: wiInvalidateWin(self-—>win.id) ; 5-4 5 COMMANDS AND COMMAND MENUS This would result in the client window receiving, at some future time, a wN_REDRAw message. This technique might prove useful if two or more things could change that require the window to be redrawn. If all such changes invalidate the window, there is less likelihood that each change will cause the window to be redrawn. In this example the difference between the two alternatives is not noticeable. Invalidating the window causes inter-process messages to be sent between the application and the window server, whereas the sending of the wn_popRaw message does not and will, in general, result in the window being updated more responsively. Which of these two techniques to use depends to a large extent on the requirements of a particular application. The code of main() must also be modified slightly to take into account the use of an application-specific command manager: GLDEF_C VOID main(VOID) { IN_HWIMMAN app; IN_WSERV ws; #ifndef EPOC GLREF_D P_DEVICE p_file; GLREF_D P_DEVICE p_serial; p_inst (&p_file, &p_serial,NULL_D); #endif p_linklib(0); app. flags=FLG_APPMAN_RSCFILE|FLG_APPMAN_CLEAN|FLG_APPMAN_SRSCFILE; app.wserv_cat=p_getlibh (CAT_HELLO2_HELLO2) ; ws.com_cat=p_getlibh (CAT_HELLO2_HELLO2) ; app.wserv_class=C_HELLOWS; ws.com_class=C_HELLOCM; p_send4 (p_new (CAT_HELLO2_HWIM, C_HWIMMAN) ,O_AM_INIT, &app, &ws) ; } As mentioned before, replace the line: app. flags=FLG_APPMAN_RSCFILE|FLG_APPMAN_CLEAN|FLG_APPMAN_SRSCFILE; iN main() with the line: app. flags=FLG_APPMAN_RSCFILE|FLG_APPMAN_CLEAN|FLG_APPMAN_SRSCFILE|FLG_APPMAN_FULLSCREEN; to take advantage of the larger screen on the Series 3a. Series 3a shifted accelerators The Series 3a machine introduced the capability to use shifted accelerators. A shifted accelerator is declared by using an upper case character in the accelerator list of the wszeRv_INnFo resource. For example, if the wseRv_1nro resource in hello2.rss is replaced by: RESOURCE WSERV_INFO hello_accs { menbar_id=hello_menbar; first_com=O0_COM_EXIT; accel={'X', /* Exit */ hit, /* Hello */ poke hy /* Bye */ } then the 'Exit' command will be selected by pressing Shift-Psion-X, and will be represented as such in the ‘Special’ menu. An application may use any combination of shifted and unshifted accelerators and may use both the shifted and unshifted versions of any or all alphabetic keys. A number of the built-in applications use, for example, the unshifted Psion-X for a normal 'Exit' option and the shifted Shift-Psion-X for an 'Exit, lose changes' option. In consequence, a Series 3a application may have up to 26 additional options in its command menus, compared with a Series 3. Because of the additional key depression that is needed to use a shifted accelerator, unshifted accelerators should, where possible, be used in preference to shifted accelerators. By convention, however, a Series 3a application that has a 'Diamond' menu should use shifted accelerators for the options in this menu (and, also by convention, the accelerators should, if at all possible, match the first letters of the items shown in the Diamond list in the application's status window). OBJECT ORIENTED PROGRAMMING GUIDE Series 3a command option grouping On the Series 3a, the options in a pull-down menu can be divided into logically connected groups by means of one or more horizontal lines. These divisions have a purely cosmetic function, as a guide to the user, and have no impact on application code. A dividing line is specified in a menu resource struct by oring the flag BREAK_LINE_FOLLOws with the command ID of a Menu_1TEm resource struct. For example, if the message_menu MENU resource in hello2.rss is replaced by: RESOURCE MENU message_menu { items = { MENU_ITEM { com_id=0_HCM_HELLO|BREAK_LINE_FOLLOWS; mn_item="Say hello"; Ly MENU_ITEM { com_id=O_HCM_BYE; mn_item="Say goodbye"; } i } the pull-down menu will have a horizontal dividing line between the 'Say hello’ and 'Say goodbye’ options. There is no limit on the number of divisions that may appear in a menu. Excessive use should, however, be avoided since it can lead to a cluttered and confusing appearance to the menu. Sharing method function code All messages sent by system code to the command manager as the result of selecting a menu option or using an accelerator (that is, the com_exit method and any following methods added by a subclass) are sent by a call to the utility function nwservcomsend. The code for this utility function is effectively as follows: VOID hWservComSend(INT comid) ; { p_send3 (w_ws->wserv.com, comid, comid) ; } Note that the parameter comia, which is the appropriate method number, is passed as an additional parameter and is thus available to the method function. This can be used to advantage when two or more methods have very similar method functions, as do the two command manager methods introduced in the previous example. The command manager class definition could have been written so that both methods share the same method function: CLASS hellocm comman command manager { ADD hcm_hello ADD hcm_bye=hellocm_hcm_hello } The method function code could then be written as: METHOD VOID hellocm_hcm_hello(PR_HELLOCM *self, INT comid) { if (comid==0_HCM_HELLO) p_send3 (w_ws->wserv.cli,O_WN_SET, FALSE) ; else p_send3 (w_ws—>wserv.cli,O_WN_SET, TRUE) ; 5 COMMANDS AND COMMAND MENUS or, more simply: METHOD VOID hellocm_hcm_hello(PR_HELLOCM *self, INT comid) { p_send3 (w_ws—>wserv.cli,O_WN_SET, comid==O_HCM_BYE) ; } Clearly, there is very little advantage in this particular case, since the two methods are so simple. Nevertheless, the technique can frequently be used to advantage in real applications. Changing the text of an option The options to set the message text in the previous example could be replaced by a single option that toggles between the two messages. To be effective, the text of the option should also change, depending on which message is currently visible. Each time a pull-down menu is displayed, its content is reloaded from the resource file. The command manager's com_menu method is called immediately before a pull-down menu is displayed, but after the menu's text has been loaded.A command manager subclass may therefore replace this method to change the text of one or more items. Continuing from the previous (hello2) example, we can use a single method instead of the two methods com_hello and com_bye. The class definition of HELLocm thus becomes: CLASS hellocm comman command manager { REPLACE com_menu ADD hcm_hello } The resource file needs to be changed to remove an accelerator and the menu item for the 'Say goodbye' option. An additional string resource is required to specify the alternative text for the option. The relevant resources become: RESOURCE WSERV_INFO hello_accs { menbar_id=hello_menbar; first_com=O_COM_EXIT; accel={'x', /* Exit */ dee /* Hello/Bye */ } RESOURCE MENU message_menu { items = { MENU_ITEM { com_id=O_HCM_HELLO; mn_item="Say goodbye"; } i } RESOURCE STRING say_hello {str="Say hello"; } All other resources are unchanged. In all cases where menu text is replaced, the text that appears in the mzNu_ITEM resource determines the amount of memory allocated for the menu item data and so must be the longest text of all the possible alternatives. Thus, in this case, it is important that the text in the option corresponding to the message number 0_HCM_HELLO is "Say goodbye" and that the replacement resource is the shorter "Say hello". The reason is that the menu resource is loaded into memory as a sequence of items of fixed length, determined by the size of the items in the resource. It is therefore possible to overwrite an element with a shorter replacement, but an attempt to replace it with a longer element will overwrite part of the following item. OBJECT ORIENTED PROGRAMMING GUIDE A suitable com_menu method function is as follows: #include METHOD VOID hellocm_com_menu(PR_HELLOCM *self, INT menu_num, PR_VAROOT *array) { UBYTE *p; if (menu_num==0) { if (p_send2 (w_ws->wserv.cli,O_WN_SENSE) ) { p=(UBYTE *)p_send3 (array, O_VA_PREC, 0); hLoadResBuf (SAY_HELLO, p+2); /* +2 skips byte count and method number */ } } The method is passed the menu number (counting from zero for the leftmost menu in the menu bar) of the pull-down menu that is about to appear and the handle of an OLIB variable array containing the data for each of the options in that menu. Each element of this array consists of a leading length byte that indicates the (fixed) length of a following mznu_item struct, defined in pulldown.g as: typedef struct { UBYTE com_id; /* command manager method number */ TEXT mn_txt[1]; /* option text, as a zero terminated string */ } MENU_ITEM; The com_menu method tests if the relevant menu (in this case, the first menu, with menu number zero) is about to appear. If so, it further tests whether the text needs to be changed, indicated by the client window's isbye property being set (that is, the window is displaying "Goodbye" and so the menu option should be 'Say hello'). The additional client window wn_sense method is described below. Provided both conditions are met, the address of the appropriate array element (in this case the first - and only - element, whose index is zero) is found by sending the array object a va_pRec message. Finally, the replacement zero terminated text string is loaded from the resource file to overwrite the original text that starts at a two byte offset within the array element. An additional client window method, wn_sense, is needed so that the command manager can determine the client window's state. The client window class definition must become: CLASS hellobw bwin bordered client window { REPLACE wn_init REPLACE wn_draw REPLACE wn_set REPLACE wn_sense PROPERTY { UWORD isbye; } } and a suitable method function could be: METHOD UINT hellobw_wn_sense (PR_HELLOBW *self) { return (self—>hellobw.isbye) ; } The com_menu method may be used to replace the text of any number of menu options in any number of menus. Any one call to the method will, of course, only replace the text of items in the one pull-down menu indicated by the menu_num parameter. If the application is translated into one or more other languages, it is important to ensure that the text in the menu resource is the longest in each translation. To avoid the possibility that the the string to be replaced may be different in different languages, it is safer to create stRING resources for every alternative and replace the text in all cases. 5 COMMANDS AND COMMAND MENUS In the current example, the resource file would therefore include an additional say_Byz resource and, ideally, a note to the translator, as follows: RESOURCE MENU message_menu { items = { MENU_ITEM { com_id=O_HCM_HELLO; mn_item="Say goodbye"; /* TRANSLATOR: use the longer of the two strings in SAY_HELLO & SAY_BYE */ } i } RESOURCE STRING say_hello {str="Say hello"; } RESOURCE STRING say_bye {str="Say goodbye"; } The com_menu method is also modified to replace the text in both cases: #include METHOD VOID hellocm_com_menu(PR_HELLOCM *self, INT menu_num, PR_VAROOT *array) { UBYTE *p; INT rid; if (menu_num==0) { p=(UBYTE *)p_send3 (array, O_VA_PREC, 0); rid = p_send2 (w_ws-—>wserv.cli,O_WN_SENSE) ? SAY_HELLO : SAY_BYE; hLoadResBuf (rid, p+2) ; } Disabling a menu option Many applications may, from time to time, enter a state in which one or more command options do not represent valid operations. A common example is for a 'Copy' option that copies a highlighted section of data to a clipboard. This option is clearly inappropriate if none of the data is highlighted. The recommended way of handling such a situation is to display an information message that explains why the option is not valid. In the case of copying, for example, the built-in applications display a "Nothing to copy" information message. One way of implementing this is to add a validity check at the start of each of the relevant command manager methods. This is adequate if only one command is affected, but if it applies to a number of options this can lead to much duplicated code. A more efficient solution may be to subclass the com_accl_check method. Whenever a menu option is selected, either from a pull-down menu or by an accelerator keypress, the command manager first receives a coM_ACCL_CHECK message, passing the method number of the message that corresponds to the selected command option. Only if the com_acc1_check method function returns a TRUE Value is the command manager sent (via hWservComSena) the message that executes the selected command option. Suppose an application has a com_copy method in its mycom command manager, used to copy a highlighted region. A suitable com_acc1_check method function would be of the form: OBJECT ORIENTED PROGRAMMING GUIDE METHOD INT mycom_com_accl_check (PR_MYCOM *self, INT comid) { if (comid==O0_COM_COPY) ( if (!RangeHighlighted() ) { hInfoPrint (-NOTHING_TO_COPY) ; return (FALSE) ; } } return (TRUE) ; } where the RangeHighlighted function is assumed to return TRuE only if a range is highlighted (possibly detecting this case by sending some form of SENSE message to the appropriate object). Note that hInfoPrint is passed a negative resource ID, indicating that the resource with ID NoTHING_TO_coPy is located in the system resource file (see the Resource Files chapter). In the case of Copy it may be more efficient to perform the test within the com_copy method function, as follows: METHOD VOID mycom_com_copy (PR_MYCOM *self) { if (!CopyRangeTo Clip()) hiInfoPrint (-NOTHING_TO_COPY) ; } where CopyRangeToClip is assumed to return TRUE only if a highlighted range has been copied to a clipboard. Which of these two methods to use will depend on the exact circumstances in a particular application. Changing the number of options in a menu In some circumstances one or more commands options may be disabled for the greater part of the time. An example of this is the Spell check option of the built-in word processor. This option is not available unless the Spell-checker application has been purchased and installed on the machine. It would be inappropriate to handle such an option in the way described in the previous section. A better way is to display the option in a command menu only if the option may validly be selected. This involves subclassing both the com_menu and com_accl_check methods. The content of a pull-down menu is loaded from the application's resource file into an allocated memory cell whose size is determined by the size of the resource. It is therefore not an easy task to add items dynamically, once the resource has been loaded. It is, however, quite simple to remove items. The recommended technique for varying the number of options in a menu is thus to include an entry for each such option in the appropriate menu resource, and provide it with an accelerator as normal. The item may then be removed, if necessary, from the pull-down menu by replacing the com_menu method. Selection of the option by its accelerator (which does not involve the pull-down menu) must also be disabled, if necessary, by a suitable replacement com_accl_check method. Suppose that an application's 'Optional' command option, with a Psion-O accelerator, is the third item in the fifth menu of an application and is executed by a com_opt ional method in a mycom subclass of the command manager. The wszeRv_INFo resource and the appropriate menu resource would contain the following: RESOURCE WSERV_INFO my_accs ig Py /* Optional */ 5 COMMANDS AND COMMAND MENUS RESOURCE MENU fifth_menu { items = { MENU_ITEM { com_id=O_COM_OPTIONAL; mn_item="Optional"; hy. i } The com_menu and com_acc1_check methods would need to be of the form: METHOD VOID mycom_com_menu(PR_MYCOM *self, INT menu_num, PR_VAROOT *array) { if (menu_num==4) /* in fifth menu */ { if (!OptionalCommandIsValid() ) p_send3 (array,O_VA_DELETE,2); /* delete third item */ } METHOD INT mycom_com_accl_check(PR_MYCOM *self, INT comid) { if (comid==0_COM_OPTIONAL) ( if (!OptionalCommandIsValid() ) return (FALSE) ; } return (TRUE) ; } Displaying a status window The command manager is sent a com_sTATWIN message when the application receives a Control-Menu keypress. The supplied com_statwin method does nothing, so the default action of an application is to ignore this keypress. An application that wishes to respond to the Control-Menu keypress by altering the state of its status window should subclass this method. A Series 3 application may record (generally in an element of property) the current state of visibility of its status window and toggle its visibility by appropriate calls to either wsEnable OF wsDisable. Before changing the status window it should adjust the size of its client window display to fill the space that will not be occupied by the status window. A Series 3a application has the option of switching between a large, small or no status window. The following code illustrates how the com_statwin method may be used to cycle around the three possible states. Note that this code assumes that the client window has a wn_change_width method that adjusts the width of the window, given the (signed) amount by which the width needs to be changed. Possible code for such a method is given in the Windows chapter of this manual. METHOD VOID wpcman_com_statwin(PR_WPCMAN *self) { INT winType, delta; P_EXTENT oldExtent; P_EXTENT newExtent; winType=wInquireStatusWindow(-1, &£0ldExtent) ; if (winType==W_STATUS_WINDOW_OFF) winType=W_STATUS_WINDOW_BIG; else if (winType==W_STATUS_WINDOW_SMALL) winType=W_STATUS_WINDOW_OFF; else winType=W_STATUS_WINDOW_SMALL; wiInquireStatusWindow (winType, &newExtent) ; delta=(oldExtent.width-newExtent.width) ; p_send3 (w_ws—>wserv.cli,O_WN_CHANGE_WIDTH, delta) ; wStatusWindow (winType) ; } OBJECT ORIENTED PROGRAMMING GUIDE Application-specific initialisation The command manager's com_init method is intended to be replaced in applications that need some form of application-specific command manager initialisation. Many applications will not need to replace this method. Application-specific initialisation may be necessary, for example, to create and initialise component objects used in the execution of one or more command options. Note that the command manager is created and initialised (by being sent a com_INIT message) immediately before the window server object is sent a ws_DyN_INIT message. This is important because it means that at the time the com_init method function is executed, the client window does not yet exist (since an application creates and initialises its client window in the ws_dyn_init method). The com_init method may not therefore make any direct reference to the client window or any components that it may create. Replacing a menu bar An application may, in some circumstances, wish to make a permanent or temporary replacement of its menu bar. An example is an application that can switch between, say, a graphic and a text display and requires a different set of command menu options for each view. Alternatively, an application may operate under a number of aliases (see the Aliasing applications section of the Communicating with the System Screen chapter of the Series 3 Programming Guide) and require a different set of command menu options for each alias. The initial menu bar of an application is set up by system code, during the initialisation of the window server object, immediately before the creation and initialisation of the command manager. The data for the initial menu bar is read from the wsERV_INFo resource that must be the first item of the application's resource file. This struct specifies the resource ID of the mznu_par resource that contains the menu bar text, the accelerators for each option and the command manager method number of the method to be associated with the first of these accelerators. The menu bar resource, in turn, contains the resource ids of the menu resources that contain the pull-down menus associated with the menu bar items. For each alternative menu, the application resource file must contain a separate wsERV_INFO resource, its associated MENU_BAR resource and any additional menu resources (two or more menu bars may share a single menu resource that is common to them). These additional resources may appear at any position, and in any order, in the application's resource file. To change the menu bar, an application should send the window server active object a ws_SET_MENUBAR message, passing the resource ID of the new wszRv_inro resource. If, for example, an application has an alternate menu defined in its resource file as: RESOURCE WSERV_INFO alternate_menubar { } it would change its menu bar by means of code of the form: VOID *pOldMenBar; pOldMenBar = p_send3 (w_ws, O_WS_SET_MENUBAR, ALTERNATE_MENUBAR) ; The method returns a pointer to an allocated memory cell that contains the data for the original menu bar. If this is not to be restored the application should free this memory by calling, for example: p_free (pOldMenBar) ; If, however, the application is intending to restore the original menu at some future time it should preserve this pointer. The original menu bar is restored by the message: p_send3 (w_ws, O_WS_RESET_MENUBAR, pOldMenBar) ; Neither of these two messages cause the new or the restored menu bar to be displayed. The appropriate menu will be made visible as normal when the Menu key is next pressed. A permanent replacement of a menu bar, say for an aliased application, may be made at any time during application-specific initialisation. Suitable locations for the code ar either the command manager's com_init method or the window server object's ws_dyn_init method. 5 COMMANDS AND COMMAND MENUS Note that any alias information text that was passed to the application in its start-up command line is pointed to by a text pointer in the Hwrmman application manager's property, accessed through the w_am magic static by w_am->hwimman.aliasinfo. In order to gain such access, application code must declare w_am as a pointer to an instance of HwImMmaN: GLREF_D PR_HWIMMAN *w_am If the application subclasses Hw1mman the declaraton may, of course, be as a pointer to an instance of the application-specific subclass. Accelerators for replacement menu bars The accelerators for a replacement menu bar must obey the same set of rules that are obeyed by accelerators for the main menu bar: e There must be a unique accelerator for every command option that appears in the menus. e The number of accelerators in the list must match with a contiguously declared set of command manager methods. e The accelerators must be listed in the same order as the method declarations in the command manager class. If the two menu bars do not share any common command options, the situation is quite simple. Suppose, for example, that an application has a main menu bar that uses com_exit (with accelerator 'x') and application-specific command manager methods com_one and com_two (with accelerators 'a' and 'b' respectively). Suppose that a replacement menu bar uses command manager methods com_three, com_four and com_five (with accelerators 'c’, 'd' and 'e'). The application's command manager class definition could contain method declarations such as: REPLACE com_exit; ADD com_one; ADD com_two; ADD com_three; ADD com_four; ADD com_five; The wsERV_INFo resource for the main menu bar would then be of the form: RESOURCE WSERV_INFO main_accs { menbar_id=main_menbar; first_com=O0_COM_EXIT; accel={'x', /* Exit */ he» /* com_one */ Voldy /* com_two */ } and that for the replacement menu bar would be: RESOURCE WSERV_INFO replace_accs { menbar_id=replace_menbar; first_com=O0_COM_THREE; accel={'c', /* com_three */ tay /* com_four */ "e'}; /* com_five */ } The situation is only slightly more complicated if the two menu bars share common options. Suppose that the main menu bar is as before, but that the replacement menu bar uses com_exit, com_three, com_four and com_five. In this case the main menu bar resource would be exactly as before, but the replacement menu bar resource would change to: OBJECT ORIENTED PROGRAMMING GUIDE RESOURCE WSERV_INFO replace_accs { menbar_id=replace_menbar; first_com=O_COM_EXIT; accel={'x', /* Exit */ Uy /* dummy, to skip com_one */ Pt /* dummy, to skip com_two */ Yon, /* com_three */ yo Rae /* com_four */ ‘e'}; /* com_five */ } The trick is to note that the rules do not prevent the accelerator list from containing more items than the number of command options that appear in the menus. Since accelerator characters are not validated during compilation of the resource file, there is nothing to prevent the appearance of duplicated or illegal accelerator characters in the list, provided they will never be matched with commands at run-time. It is convenient to use the exclamation mark (!) as a marker for dummy entries since HWIM code will never recognise it as an accelerator. Any other character that is not a valid accelerator may be used. The principle can be extended to provide any number of replacement menu bars (the Series 3a Word application, for example, has three replacement menu bars, for use by program, text and memo editors). The same technique could also be used to match accelerators with a non-contiguous set of command manager methods for the main menu bar, but it is usually more convenient (and always possible) to make the main menu bar command manager methods a contiguous set that immediately follows com_exit. Submenus An application may make a temporary replacement of its menu bar to implement a submenu by sending the window server object a ws_Do_suBMENU message. An example of this, taken from the Series 3 Spreadsheet application, is illustrated below. File Edit View Search Range | Special ) Print setup Jump to page The method will normally be called from the command manager method corresponding to a main menu option. As for the ws_set_menubar method, ws_do_submenu requires the resource ID of a WSERV_INFO resource. Note that, as illustrated above, commands in a submenu may have names and/or accelerators that duplicate those appearing in the main menu. One use of submenus is therefore to provide a means of exceeding an application's normal limit on the number of its commands. A typical call would be of the form: p_send3 (w_ws, O_WS_DO_SUBMENU, SUBMENU_ACCS) ; and causes the submenu to become visible, ready for selecting one of its commands. Note that, unlike ws_set_menubar, this method does not return a pointer. The current main menu is restored automatically when the submenu menu bar ceases to be visible. Shutdown messages The System Screen may send an application a Shutdown message at any time, unless the application has explicitly elected not to receive such messages. It may do this by adding 4000 to its type number in its shell data file (see Application type numbers in the Communicating With the System Screen chapter of the Series 3 Programming Guide). An HWIM application receives a Shutdown request from the System Screen in the form of a normal COM_EXIT message to its command manager. It should handle this in exactly the same way as when the user explicitly selects the application's Exit option (the application actually has no means of distinguishing between the two cases). The default com_exit method supplied by the comman class is quite adequate for an application that is not file-based. See the File-based Applications chapter for the required behaviour of com_exit when the application maintains an open file. An application may indicate that it is temporarily unable to accept a Shutdown message by setting the Dat Locked reserved static to be non-zero. This will generally only be relevant for a file-based application: a typical case is while an application is performing an extended operation that must run to completion, such as saving a file. The application must ensure that it clears Dat Locked again, as soon as possible. 5-14 CHAPTER 6 WINDOWS An HWIM window object represents a rectangular region on the screen, in which data may be displayed. The concept of a window is discussed in the /ntroduction and Windows chapters of the Window Server Reference manual. In addition, drawing to a window requires a knowledge of the Graphics Output chapter of the Window Server Reference manual. The HWIM window classes add a relatively modest layer of functionality over the window functions described in the Window Server Reference manual and their use relies heavily on the concepts and techniques described in that manual. In consequence, this chapter contains explanations of only a few additional mechanisms. The window classes themselves, listed below, are fully described in the Windows chapter of the HWIM Reference manual. WIN The ultimate window superclass. BWIN A subclass of win that draws a standard border around the window. An application's client window is normally a subclass of Bw1n. LODGER A pseudo-window that subclasses win. Such a window is assumed to occupy a rectangular region within another window. The enclosing window (referred to as the landlord) will receive all messages (notably wn_kEy and wN_pRaw messages) but will normally delegate all processing for that rectangle to the lodger. Lodger windows are used extensively by system code - perhaps the most common usage is for the controls and prompts that form the components of a dialog box. The relationships between the window classes is illustrated in the following class diagram. — o~ [rs — 7 — 3h win / / bwin / a ) = a Ne Me oe ee oe We y wswin ji Z lodger / = e .} os 2) kee Me In addition to the methods and property in the application's code and data segments, a window has an associated data structure in the window server's resources. One of the elements in this structure is the window's handle and thus the window server is able to direct redraw events to a particular window in an application. The structure is created by the call to wcreat ewindow that occurs (in the window's wn_connect method) when a newly created instance of win is initialised. The window server provides a set of functions that operate on such data structures, each data structure being uniquely identified by a window id returned by wCreateWindow. Since a uniquely identifiable data structure with a set of functions that operate on it is effectively an object, the window server resources associated with a window can be considered as a component object. The existence of the window server resources is indicated in the above class diagram by the 'using' relationship between win and a notional wswin (Window Server WINdow) class. OBJECT ORIENTED PROGRAMMING GUIDE Window usage in HWIM In general, an HWIM application uses a number of different windows. Some windows may exist for the entire lifetime of the application, for example, an application's main display window. Others may exist for a short time, such as the windows used to display a menu bar, a pull-down menu or a dialog box. An HWIM application is expected to have one particular window, designated the client window, that (generally) exists for the lifetime of the application and provides the main view of the application's data. This window should be created as part of the application's initialisation, in the wseRv window server active object's ws_dyn_init method, with its handle being written to the wserv.cli property element. See, for example, the creation of a bordered client window in the "Hello World" example application. All HWIM windows subclass the w1n class, which is thus the ultimate superclass of all windows. The window classes supplied by HWIM are abstract classes and will normally need to be subclassed in order to create useful window objects. In contrast with the mechanisms supplied for the handling of command menus and dialogs, the HWIM library provides relatively few 'solutions' for the display of data in a window (but see the Edit Windows chapter). For example, an application can normally simply use the supplied dialog box class or, at most, replace one or two methods. It will, however, usually have to subclass the window classes to provide a variety of application-specific mechanisms. The draw/redraw mechanism This section (and, indeed, the whole of this chapter) is written under the assumption that a window has been created without a back-up bitmap. An HWIM application does not normally create backed-up windows and will therefore have to handle redraw events. The main advantage that this confers on an application is that drawing to such a window is faster and much less memory is used for each window. See the Windows and Redrawing sections of the Introduction chapter of the Window Server Reference manual for the background to drawing and redrawing the content of a window. All or part of the content of a window may have to be drawn for one of the following basic reasons: e the application's displayed data has changed e all or part of the window has been exposed, for example, by the disappearance of an overlying dialog, or by the application being brought to the foreground. In both cases the drawing is ultimately handled by the window's wn_draw method, but the mechanism by which this is invoked differs in the two cases. The first case generally results from some operation within the application itself, such as a keypress or the execution of a command. Normally the application will be aware that such a change has taken place and can explicitly initiate the drawing. Most applications can do so by sending the window a wn_popRaw message. This creates a temporary graphics context with default properties, sends a wN_pRAw message and then destroys the temporary graphics context. The graphics context may be modified, if necessary, in the window's wn_draw method. The second case may occur at any time, without the application necessarily being aware of any particular need to do any redrawing. The window server process will, however, send the aplication a wmM_REDRAW inter-process message, indicating that a particular area of a specific window needs to be redrawn. This is processed by the application's window server object which then sends a wN_REDRAW message to the specified window (omitting to pass on the update rectangle that is passed to the wn_redraw method). As for the wn_dodraw method, a temporary graphics context is created around a wn_pRaw message, which may be processed exactly as in the previous case. At the level of the wn_draw method, a window can not (and does not need to) distinguish between the two cases. In fact, as an alternative to sending the window a wN_popRaw message when its data has changed, the application could simply invalidate the window (by calling wInvalidateWin). System code will ensure that the window eventually receives a wN_REDRAW message. In many cases this may be simpler from the point of view of coding, but is generally less efficient. It may lead to poor responsiveness, particularly in applications that make rapid changes to their data. 6 WINDOWS An HWIM application is free to replace any or all of the wn_redraw, wn_dodraw and wn_draw methods if necessary. The mechanism whereby both wn_redraw and wn_dodraw invoke wn_draw has been found to be convenient, but is not mandatory. Since the update rectangle passed to the wn_redraw method is not passed in the wn_pRaw message, the wn_draw method must draw the whole of the window. This is not as bad as it sounds, since the drawing is always clipped by the update rectangle, so pixels outside that rectangle will never be physically set or cleared. Normally, it is the setting or clearing of pixels that takes the most time in any drawing operation. If, in a particular application, the time taken to calculate what has to be drawn becomes dominant, it may be worth taking the update rectangle into consideration. In this case the wn_redraw method should be replaced, either to handle the drawing itself, or to pass on the update rectangle to a replacement wn_draw method. In such a case, if the wn_dodraw method is used, it may also need to be replaced to specify a rectangular drawing region. Drawing a window When implementing a window's wn_draw method, there are at least two strategies that may be followed, depending to some extent on the nature of the window's contents. The first case is where the contents densely fill the window, particularly if the content is frequently changing (and especially if only a small part changes at any one time). An archetypal application of this type is the built-in Word application. In such a case the prime consideration is to avoid clearing all or part of the window before drawing its content, so that the drawing is 'flicker-free’. The basic strategy in such a case is to: e create the window with its background attribute set to w_wIn_BACK_NonE and, if appropriate, W_WIN_BACK_GREY_NONE, e ensure that the win_draw method draws to every pixel in the window. If this strategy is adopted, it is acceptable (but possibly wasteful of processing power) to initiate drawing, following any change of the data, by calling wInvalidateRect OF wInvalidatewin. Any drawing of existing, unchanged, content will not be visible to the user since the existing content will not be erased before being overdrawn. This approach is incompatible with the use of a bordered window to display the data, since a bordered window needs to clear its background when drawing itself. If the content needs to be shown with a surrounding border, the solution is to use two windows, as illustrated below. The bordered window is the parent of the smaller superposed window, indicated in the diagram by a dotted line. The child window, an instance of a subclass of win, displays the content and should be initialised with a non-clearing background, as described earlier. The bordered window is an instance of (a subclass of) swin and will be initialised to clear its background. When the bordered window draws itself, drawing will be clipped to exclude the area covered by the child window and will therefore draw the border and clear the area between the border and the child window. Both windows will independently receive system-generated wN_REDRAW messages, so there is no need for either window to pass WN_REDRAW (or WN_DRAW) messages to the other. Some messages may need to be passed between the two windows. These may include resize messages if the windows are allowed to change their dimensions, wN_EMPHASIS and WN_KEY messages. OBJECT ORIENTED PROGRAMMING GUIDE Although this strategy can be followed with all windows, it may be inconvenient in cases where a window is sparsely filled, especially if the content is restricted to specific rectangles and does not change frequently. A second strategy is feasible in such a case, exemplified by the Record application that is described in the Application Design chapter of this manual. In this case, even if the window is to display a border, a single window is used, initialised to clear its background. The advantage over the previous technique is that the wn_draw method does not have to draw to every pixel in the window but can, for example, draw specific items of text with, say, gPrintBoxText. The method relies on the fact that drawing caused by receipt of a wN_REDRAW message will be clipped to a specific rectangle and will therefore not redraw existing unchanged text. Unlike in the previous method, however, the application must not use wInvalidateWin to force a redraw following any change in the application's data. Invalidating the window would cause the whole window to be cleared before being drawn again and users would not be impressed. Instead, the application should draw directly to the rectangle, or rectangles, that are affected by the change. See the description of the Record application, and the application's source code that is supplied with this SDK, for further details. The main visible difference between the two techniques occurs when an overlying window, such as a dialog or pull-down menu, disappears. In the second case the rectangular area previously occupied by the window will be cleared before the contents are drawn, whreas in the first case the redrawn contents will directly replace the remains of the previous window. Lodger windows A lodger window is defined to be any window that subclasses LopGER. A lodger window does not have its own independent window server data structure, and therefore does not have an independent window server ID. Such a window is intended to occupy a rectangular region within another window, referred to as the lodger window's landlord, and shares the window ID of the landlord window. In other respects a lodger window has broadly similar behaviour to a normal window, supporting a similar set of drawing and key processing messages. A lodger window can be considered to take over the responsibility for drawing the content of a rectangular region of the landlord window. The fact that a lodger window shares the ID and window server data structure of its landlord has a number of significant consequences: e A lodger window will never receive a wN_REDRAw message. It is therefore the responsibility of the landlord to ensure that its lodgers are drawn when appropriate. The normal way of doing this is to replace the landlord's wn_draw method with code that sends wn_pRaw messages to all of its lodgers, in addition to drawing any areas of the landlord that are not covered by lodger windows. e Drawing in a lodger window is clipped by the enclosing landlord window boundary and is not confined to the area of the lodger window itself. In consequence, a lodger window has a duty never to draw outside its bounding rectangle. e Using lodger window components instead of normal windows is more efficient in terms of memory usage. This can be significant in the case of a complex compound window (a dialog box, for example, can easily need ten or more component sub-windows). e Acompound window with lodger components will scroll more smoothly - the scroll mechanism operates on only the single 'real' window and does not require drawing messages to be sent to any of the component lodger windows (except for those that have freshly exposed regions to draw). Resizing a window The following suggestion for a wn_resize method illustrates a simple way to change the position and size of a window without changing any other attribute: METHOD VOID mywin_wn_resize(PR_MYWIN *self, P_EXTENT *pext) { W_WINDATA wd; wd.extent=*pext; wSetWindow (self->win.id,W_WIN_EXTENT, é&wd) ; } 6 WINDOWS S3/S3a client windows generally do not change size once they have been created and made visible. The main exception is a change in width corresponding to a change in the state of any permanent status window. Thus a common requirement is to change the width, without changing the position or height, as in the following example for a possible wn_change_width method: METHOD VOID mywin_wn_change_width(PR_MYWIN *self, INT delta) { W_WINDATA wd; wiInquireWin (self—>win.id, &wd) ; wd.extent .width+=delta; wSetWindow (self->win.id,W_WIN_EXTENT, &wd) ; } Window emphasis Emphasis is used to indicate which of several windows is the one with which the user is currently interacting. System code will send the application's client window a wN_EMPHASISE message to turn emphasis on or off; for example, when a dialog appears or disappears. Application code is not normally expected to send WN_EMPHASISE messages itself, except under the circumstances described below. A window will usually respond to a wN_EMPHASISE message by changing its appearance in some way. The normal technique is to set or clear, as appropriate, the pR_wIN_EMPHASISED flag in the window's win.flags property and then trigger a redraw, either by calling winvalidatewin or by sending itself a WN_DODRAW message. In either case the window's wn_draw method will subsequently be executed. This can then test the pR_wIN_EMPHaASTSED flag and draw the window as appropriate. Common means of showing that a window is not emphasised are: ¢ a window with a shadowed border is drawn without its shadow e any highlighted region may be drawn without its highlight e a window that has a text cursor does not display its cursor The last of these three differs from the other two in that it must not be done in the window's wn_draw method. The reason is that there is only ever one cursor visible on the screen at any one time and the window server function weraseTextCursor erases the text cursor regardless of the window in which it appears. An unemphasised window will receive wn_praw messages (for example, from the wn_redraw method) and so a call to weraseTextCursor from within the wn_draw method could result in the removal of the text cursor from another currently emphasised window. An application can avoid the possibility of 'stealing' another window's text cursor by confining all calls to wlextCursor and weraseTextCursor to be from within a window's wn_emphasise method. When changing emphasis from one window to another, system code always sends a WN_EMPHASISE, FALSE message (which may call weraseTextCursor) to the window losing emphasis before sending a WN_EMPHASISE, TRUE message (which will, if necessary, call wrextcursor) to the window gaining emphasis. The currently emphasised window will thus be guaranteed to display its text cursor, if it has one. A window that contains one or more child windows may delegate all or part of the processing of a WN_EMPHASISE message to its child windows. An example of this is shown in the behaviour of a dialog box. On receipt of a wN_EMPHASISE message, a dialog box changes the appearance of its border and then sends a wN_EMPHASISE message to one of its items (the one with 'focus'). A dialog box also illustrates the case where application code may send wN_EMPHASISE messages other than in response to such a message sent by system code. When a user presses the up or down arrow keys, a dialog responds by changing the focus from one dialog item to another. As part of this process WN_EMPHASISE messages are sent to the two items concerned. A similar process occurs when switching between the Find window and the main display window in the Database application. In such a situation it is important to obey the rule that a wN_EMPHASISE, FALSE message must be sent to the window losing emphasis before sending a WN_EMPHASISE, TRUE Message to the window gaining emphasis. CHAPTER 7 DIALOGS A dialog box displays the current values of one or more data items and, in general, allows the user to modify one or more of these values. This chapter describes the basic mechanisms provided in HWIM for the creation and operation of dialogs. Dialog boxes may be created and used at a number of levels; this chapter is intended to describe the more common uses that cover the great majority of cases. Application-specific dialogs use an instance of (a subclass of) pLcBox which is a subclass of the BwIN bordered window class, via the pLccHAIN abstract class, as indicated in the following class diagram. These classes are fully described in the Dialog Boxes chapter of the HWIM Reference manual. fo — SE haw re win / / bwin / > a S _) Lo ees per en e a f digchain / y dlgbox / = a) SS 2) Nee Ne Some of the pLcBox methods are complex, having to cope with a variety of cases. For most purposes it is not necessary to understand these methods in detail - the essential information for most uses will be found in this chapter. The Dialog Controls chapter contains the essential information about the standard dialog components that are supplied in the HWIM library. In an HWIM application the most common use of a dialog is as a result of the user selecting a command from a command menu. In response to an Open file command, for example, a dialog would be presented to allow the user to specify the name (and possibly the type) of the file to be opened. There are a number of system dialogs that may be run by specific wsERv methods, such as ws_error_dialog, ws_query_dialog and ws_format_dialog, described in the WSERV Class chapter of the HWIM Reference manual. Application-specific dialogs are usually started by means of the window server object's ws_do_dial method, or the equivalent hLaunchDial utility function. All HWIM dialogs are modal, that is, while the dialog is visible the user can interact with the application only via that dialog; the application enters a 'mode' such that all attempts to interact with, say the menu bar are disallowed. This mode terminates when the user satisfactorily completes the dialog. An HWIM dialog box consists of a bordered window containing between one or more lines, or items. Each item may be plain text, a control, or a combination of a plain text prompt and a control. Each control may be an instance of one of many different classes, including: e achoice list, allowing selection of one of a list of options e an action list, containing one or more buttons (this may only be used as the last item in a dialog) e an integer (WORD or LONG) numeric editor e a floating point numeric editor OBJECT ORIENTED PROGRAMMING GUIDE e a time editor e a date editor e ascrolling or non-scrolling text editor e a secret data input box e a filename choice list e a filename editor e an application-specific control A dialog written for the Series 3 (or for the Series 3a in compatibility mode) may contain up to seven items, which may be divided into two groups by a single underline. A Series 3a dialog can display up to nine items, any number of which may be underlined. Note that the inclusion of an action list, which occupies two lines, reduces the number of items that may be displayed. On the Series 3a the inclusion of more than one underline may also reduce the possible number of items. In the majority of dialogs a single underline is used to separate the first item, designated to be the dialog title, from those that follow. The title is normally, but not necessarily, plain text. Dialog box items are generally accessed by an index number. The items are numbered, with the first item being item 0, in the order in which they are added to the dialog box (which is also the order in which they are displayed). Using dialog boxes In all cases, the dialog must ultimately be started up by means of the wsERV ws_do_dial method. This may, however, be indirect, such as when using the hLaunchDial utility function or an equivalent application-specific function. All sample code in this section assumes that the application category file is myapp.cat. Default dialog behaviour One item in the dialog box generally has focus, that is, it receives all keys (via a wN_KEY message to its control) directed to the dialog box, except for those, listed below, that are processed by the dialog box itself. On receipt of a key, a control may elect to absorb all further keys directed to the dialog box. Provided that one of the dialog's component controls has not elected to absorb all keys directed to the dialog box: e the up and down cursor keys respectively move focus to the previous or the next item in a cyclic fashion e the page up and page down keys respectively move the focus to the first or the last item that is capable of receiving focus e the Enter key terminates the dialog. If the dialog has a result buffer (that is digbox.rbuf is not NULL) the value of digbox.current is written to the first worp of this buffer e the Esc key terminates the dialog. If the dialog has a result buffer (that is digbox.rbuf is not NULL) a value of w_KEY_ESCAPE is written to the first worp of this buffer If no control is absorbing all keys and the dialog contains an action list of buttons as its last item, this behaviour is modified. In this case all incoming keys are first offered to the action list and if the key matches one of the buttons in the action list the dialog is terminated. If the dialog has a result buffer (that 1S dlgbox.rbuf is not NuLL) the uppercased key code is written to the first worp of this buffer. Unless it matches a button in the action list, the Enter key is ignored. Only if the key is not recognised by the action list is it then offered for processing as described above. 7 DIALOGS This action may be further varied by other digbox. flags values as follows: e if the pLGBox_NoTIFY_ALL_act flag is set, keys that do not match a button in the action list also cause the dialog to terminate e if the DLGBox_REPORT_ACT_HORIZz flag is set, the value written to the result buffer will be the index of the matching button (the leftmost button has an index of zero, and non-matching keys give a value of -1) rather than the key code Other variations are generally accomplished by subclassing pLGBox, and some such variations are described later. Dialogs and resource files The content of a dialog is normally specified by a resource file item, using dialog box resource file structures that are defined in hwim.rh. These resources also use definitions of constants (in hwim.rg) that are derived from the HWIM category file. All resource files that contain dialog resources must contain the following two lines before the definition of any dialog resource: #include #include The piatoc resource struct is defined in hwim.rh as: STRUCT DIALOG { WORD flags=0; TEXT title=""; LEN BYTE STRUCT controls[]; /* array of CONTROL resource items only */ } and the contro. resource struct is defined as: STRUCT CONTROL LEN { WORD flags=0; BYTE class; /* class of control */ TEXT prompt=""; /* Prompt for item */ STRUCT info; /* eg CHLIST, TXTMESS, EDWIN, or NCEDIT */ } A simple dialog, consisting of a title, a fixed message and a 'Continue' button, might be defined by the following resource, assumed to be in a myapp.rss source file: RESOURCE DIALOG simple_dialog { flags=DLGBOX_NO_DDP; title="Dialog title"; /* an empty string means that the dialog has no title */ controls= { CONTROL { class=C_TEXTWIN; flags=DLGBOX_ITEM_CENTRE |DLGBOX_ITEM_DEAD; info=TXTMESS { flags=IN_TEXTWIN_AL_CENTRE; str="This is a message"; ad } és CONTROL { class=C_ACLIST; info=ACLIST { rid=-SYS_AC_CONTINUE; /* the ID of a system action list resource */ hi OBJECT ORIENTED PROGRAMMING GUIDE In use, this will display (in compatibility mode on the Series 3a) the following dialog, which exits when the user presses the Esc key. Dialog title This if 3 message Continue Esc | The dialog resource uses a one-button action list defined by the the system resource sys_Ac_CONTINUE (see the HWIM Resource Files chapter) and is based on the following hwim.rh resource structs: STRUCT TXTMESS /* Initialising struct for a text window */ { WORD flags=0; TEXT str=""; /* message defaults to empty string */ } STRUCT ACLIST /* Initialising struct for action list */ { LINK rid; } together with the pIALoc and contro structs, described earlier. Note that, in general, a dialog resource contains three levels of flags data: the highest level, associated with the whole dialog box, can contain a combination of the DLGBOX_Xxx flags that may appear in digbox. flags. Exceptionally, the example sets D is no advantage in writing its handle to patDialogptr (but it would do no harm to omit this flag). Although the dialog contains an action list as its last item, there is no need to set DLGBOX_ACTION_LIST since this happens automatically LGBOX_NO_ppP. This is done because the dialog does not use any dialog utility functions, so there the second level, associated with a particular control, can be a combination of the DLGBOX_ITEM_xxx flags that may appear in the flags field of each of the dialog's component items. In the above example, the text message control is to be centred in the dialog box and not selectable with the cursor keys finally, there may be a set of flags (usually 1n_xxx) specific to the initialisation of the particular class of which the control is an instance. In the example, the IN_TEXTWIN_AL_CENTRE flag ensures that the text is centre aligned in its control (a text window) Launching a dialog A dialog is launched by means of the wsERV ws_do_dial method. This loads the dialog data from a resource file, and then initialises and runs the dialog. It is used as shown below: p_send5 (w_ws,O_WS_DO_DIAL, p_getlibh(cat),class,pdata) ; where cat and class are respectively the category and class numbers of the dialog box class (DLGBox or a subclass of pLGBox) that is to be run, and pdata is a pointer to a pL_pata struct, defined in hwimman.g as: typedef struct { UWORD id; /* resource ID of a DIALOG resource*/ VOID *rbuf; /* address of result buffer, or NULL */ PR_DLGBOX **pdlg; /* address of where to write handle of dialog, or NULL */ } DL_DATA; An alternative is to use the hLaunchDial utility function: INT hLaunchDial(P_CATID cat, INT class, DL_DATA *pdata) ; This function is described in the HWIM Utility Functions chapter of the HWIM Reference manual, and examples of its use appear in the following text. 7 DIALOGS Simple dialogs A simple dialog, for the purposes of this section, is one that uses the piGBox class, rather than subclassing it. Such a dialog is easy to define and run, but suffers from the following limitations: e the initial values it displays are entirely determined by the (static) resource file data: the dialog data may not be determined dynamically from current values stored in the application e knowledge of the final state of the dialog is limited to the default information that is written to a result buffer - effectively only indicating which keypress terminated the dialog Despite these restrictions, such dialogs may be useful, say, to make a specific warning with a characteristic appearance, or to elicit multiple choice responses (but bear in mind the system-supplied Error and Query dialogs, run by wszrv methods). As an example, the dialog defined earlier may be run using code as follows: #include #include #include /* resource file generated header file */ LOCAL_C VOID RunDlgNoResponse() { DL_DATA data; data.id=SIMPLE_DIALOG; data. rbuf=NULL; data.pdlg=NULL; hLaunchDial (CAT_MYAPP_HWIM, C_DLGBOX, &data) ; } Should you wish to know whether the dialog was terminated by pressing Enter or Esc, you could use the following alternative: INT RunDlgWithResponse() { WORD result; DL_DATA data; data.id=SIMPLE_DIALOG; data.rbuf=&result; data.pdlg=NULL; hLaunchDial (CAT_MYAPP_HWIM, C_DLGBOX, &data) ; return (result) ; } The return value is w_KEY_EScapE only if the dialog was exited by pressing Esc. To go beyond the range of possibilities discussed above, pLGBox must be subclassed. In the majority of cases this will involve no more than supplying replacements for one or both of the di_dyn_init and dl_key methods. Dynamically initialised dialogs The di_dyn_init method is intended to be used for setting the initial values of dialog box controls dynamically, as opposed to the static initialisation, from data in a resource file, used by simple dialogs. Suppose, for example, that an application needs to use a dialog, similar to the one described earlier, but able to display one of two alternative text messages. Such a dialog might use two string resources and a dialog resource as follows: OBJECT ORIENTED PROGRAMMING GUIDE RESOURCE STRING dial_msg_1l { str="Message one"; } RESOURCE STRING dial_msg_2 { str="Message two"; } RESOURCE DIALOG message_dialog { controls= { CONTROL /* the title */ { class=C_TEXTWIN; flags=DLGBOX_ITEM_CENTRE | DLGBOX_ITEM_DEAD | DLGBOX_ITEM_UNDERLINED; info=TXTMESS { flags=IN_TEXTWIN_AL_CENTRE; str="Dialog title"; i hy CONTROL /* the message */ { class=C_TEXTWIN; flags=DLGBOX_ITEM_CENTRE | DLGBOX_ITEM_DEAD; info=TXTMESS { flags=IN_TEXTWIN_AL_CENTRE; ‘i ae CONTROL { class=C_ACLIST; info=ACLIST { rid=—-SYS_AC_CONTINUE; i hi } Note that the control that is to contain the message does not specify a message, and so has the default of an empty string, since (in the case of this example) the message text is always replaced. In addition, this dialog resource uses an alternative way of specifying the dialog title, compared with the previous example. Although it takes up a few more bytes than in the previous case, it allows the possibility for the title to be left as the default empty string in cases where, for example, the dialog title itself is to be generated. The dialog could subclass pucBox as follows: CLASS msgdial dlgbox { REPLACE dl_dyn_init } with the corresponding message function to set the message text: METHOD VOID msgdial_dl_dyn_init (PR_MSGDIAL *self) { INT flag; TEXT buf[40]; flag=* (WORD *) self-—>dlgbox.rbuf; hLoadResBuf (flag?DIAL_MSG_1:DIAL_MSG_2, &buf[0]); hDigSetText (1, &buf[0]); } This method uses the dialog utility function npigsetText to set the text for the TexTw1n item with index number | (the message). This function, which is described in the Dialog box utilities section of the HWIM Utility Functions chapter of the HWIM Reference manual, assumes that the dialog's handle is stored in DatDialogPtr. Thus, in contrast with the previous example, the resource for this dialog must not set the DLGBOX_NO_ppp flag. 7 DIALOGS In this case, the flag to select the required message string is passed to the dialog code in the dialog box result buffer. A possible means of running this dialog is: LOCAL_C INT RunMessageDlg(INT flag) { WORD result; DL_DATA data; result=flag; data.id=SIMPLE_DIALOG; data.rbuf=&result; data.pdlg=NULL; hLaunchDial (CAT_MYAPP_MYAPP,C_MSGDIAL, &data) ; return (result) ; } On completion of the dialog, the worp pointed to by data. rbuf (that is, result) will be overwritten, as in the previous example, with a value indicating how the dialog was terminated. The essential aspect of this example is that a dialog item can be modified by sending the appropriate WN_SET message (in this case, encapsulated in an HWIM utility function) from the dialog's di_dyn_init method. This technique is not limited to setting the text of a message, but can be used to modify the content of almost any dialog control (some exceptions are discussed in the following section). Examples of such dynamic initialisation are: e setting the currently selected item in a choice list, e — setting the initial value to be displayed in a numeric editor, e — setting the initial content of a text editor, e supplying replacement text for an item's prompt. A dialog that needs to dynamically initialise several controls may use the technique described above, with a dlgbox.rbuf that points to a structure containing the initialisation data for all the controls, although this may require a considerable amount of code to set up the data. An alternative would be to allow the dl_dyn_init method to obtain its data directly from other objects (ideally, via sensing methods) or from static variables. A possible technique is to use the result buffer pointer to point to a particular structure, which may be in the property of some other object. The method chosen in a particular circumstance may depend on the trade-off between such factors as the amount of code needed, the clarity of the code and the preservation of modularity. Further dynamic initialisation In addition to changing an item's content, a number of other operations may be carried out in the dialog's dl_dyn_init method. In addition to allowing a dialog's initial appearance to match the current status of the application, some of these additional techniques enable two or more similar dialogs to share the same resource and/or code. Operations that may be performed in the a1_dyn_init method include: e locking or unlocking an item by sending the dialog a pL_1TEM_Lock message, e dimming or undimming an item by sending the dialog a pL_1TEM_pIM message, e replacing an item by sending the dialog a pL_ITEM_REPLACE message, ¢ appending one or more additional items to the end of a dialog by sending the dialog pL_1TEM_app Of DL_ITEM_APPEND messages. Note that there is no explicit means of removing an item from a dialog, once it has been added. It is, however, possible, during the initialisation of a dialog, selectively to prevent an item in the dialog's resource from being added, and this effectively implements the removal an item. To do this, you will need to replace the dialog's d1_item_adda method. The technique is illustrated by example code in the description of the d1_item_add method in the Dialog Boxes chapter of the HWIM Reference manual. OBJECT ORIENTED PROGRAMMING GUIDE Changing some aspects of a dialog may require a little ingenuity. It is not, for example, apparent that the button resource for an action list (referenced by its resource ID in the dialog's resource) can be changed dynamically during the start-up of a dialog. This resource can, however, be replaced in the following way. Suppose that the button resource shown below is to be used as a replacement in some dialog's action list control. RESOURCE ACLIST_ARRAY ac_replace { button = { PUSH_BUT { keycode=W_KEY_SPACE; str="First"; hy PUSH_BUT { keycode=W_KEY_DELETE_LEFT; str="Second"; } de } An opportunity to replace the button resource in a dialog's action list control is provided by setting DLGBOX_ITEM_APPL_CAT in the flags field of the relevant conrRo resource, as shown below: RESOURCE DIALOG aclist_dialog { controls= { CONTROL { flags=DLGBOX_ITEM_APPL_CAT; class=C_ACLIST; info=ACLIST { rid=—-SYS_AC_CONTINUE; i ‘i } Normally this flag is set only if the dialog control class is defined in the application's category. In the absence of this flag, the control is assumed to be one provided by HWIM and an instance is created from the HWIM category. Setting the pLcBox_ITEM_appL_cat flag causes the dialog's d1_item_new method to be called after the loading of the dialog resource. Normally this method is used to create an instance of the class from the application's category, rather than from HWIM. In the current example, the pLcBox_1TEM_APPL_catT flag is set even though the acurst class is in the HWIM category. The normal intention is subverted and the pL_1TEM_NEW message is used principally as a means of allowing application code to run between the loading of the resource and the creation of an instance of the control. The code required to overwrite the button resource ID and create the instance of ACLIST, assuming that the application's category is called MYAPP, is as follows: METHOD PR_LODGER *mydial_dl_item_new(AD_DLGBOX *par) { TEXT *p; if (...) /* application-dependent condition */ { p=&par->prompt [0]; /* par points to the item's loaded resource */ pt=p_slen(p) +1; /* p points to the button resource ID */ ((IN_ACLIST *)p)->rid=AC_REPLACE; /* overwrite the resource ID */ } return (f_new(CAT_MYAPP_HWIM,C_ACLIST)); /* create an instance of ACLIST */ } This technique may be used in any case where modifications need to be made to the resource data between loading the resource and creating an instance of a control, provided such changes to not affect the length of the resource. 7-8 7 DIALOGS Retrieving dialog results In general, it is not sufficient merely to know which keypress caused the dialog to terminate. Most dialogs gather information from the user and this information must be communicated to the rest of the application. Such a dialog will generally set pLGBox_RBUF_FILLED iN digbox. flags, to prevent system code from writing to *digbox.rbuf. The dialog may then safely write its own specific results to that location. The collection of the results is normally done by a replacement di_key method. The di_key method is called from the wn_key method when the dialog box is potentially about to terminate, in the following circumstances: e when the keypress w_KEY_RETURN is received, provided digbox. flags contains DLGBOX_NOTIFY_ENTER, but not DLGBOX_ACTION_LIST OF DLGBOX_SMALL_ACTION_LIST e when the keypress w_kEY_EscapE is received, provided digbox. flags contains DLGBOX_NOTIFY_ESCAPE e the dialog contains an action list, dlgbox. flags contains DLGBOX_ACTION_LIST (or DLGBOX_SMALL_ACTION_LIST) and a keypress matches one of the buttons in the action list e for all keypresses, provided the dialog contains an action list and digbox. flags contains DLGBOX_NOTIFY_ALL_AcT as well as DLGBOX_ACTION_LIST (Of DLGBOX_SMALL_ACTION_LIST) A typical di_key method will sense the data in one or more of the dialog's component controls, write this data into a buffer or structure pointed to by digbox.rbuf and return wN_KEY_CHANGED. The choices available for transferring information to other objects within the application are similar to those already discussed for the di_dyn_init method. The method may test the keypress that caused it to be called, since this is passed as a parameter. It may, as a result of this test - or of other tests on the values of one or more component controls - return the value WN_KEY_NO_CHANGE to Indicate that the dialog box should not be terminated. Dialogs with and without 'WAIT' By default, the wszERV ws_do_diai method (and hence the hhaunchDial utility function) will not return until the dialog terminates and the dialog box has been destroyed. The processing of the dialog's results may therefore be divided between the d1_key method (which could, in this case, be viewed as a simple collector of the data) and code that immediately follows a call to, say, hLaunchDial. Most of the system-supplied dialogs use this technique since it allows application-specific code to follow the generic processing performed in the dialog's di_key method. If digbox. flags contains DLGBOX_NO_WwAIT, the ws_do_dial method (and hiaunchDial) will return as soon as the dialog is launched, rather than waiting until the dialog is destroyed. In such a case none of the processing of the dialog completion can be performed by code following a call to, say, hLaunchDial, since the dialog is still running at that time. All the completion processing must be done in the dialog's di_key method. Although this technique is more difficult to handle, it does have a number of advantages for the application writer, one of the more significant being that it is less expensive in terms of stack use. Controlling the width of a dialog The width of a dialog is normally set - following its initialisation and before it is made visible, in the dl_set_size method - so that it exactly fits the widest of its components. In cases where the size of a component may vary after the dialog becomes visible, this may not be adequate. An example of such a situation would be a dialog containing a centred text message that shows a page count used, say, to report the progress of document printing. The message may have to display page numbers up to 999, but would normally start at page 1. If the dialog box width is determined for the initial message "Page 1", it may be too narrow to display "Page 999". Two possible solutions are described below. OBJECT ORIENTED PROGRAMMING GUIDE If the maximum width of an item can be easily determined, the dialog can be forced to the required width by replacing the di_ing_minsize method. Using the above example, the dialog resource could contain the following initialisation data for a page count control: CONTROL { class=C_TEXTWIN; flags=DLGBOX_ITEM_CENTRE | DLGBOX_ITEM_DEAD; info=TXTMESS { flags=IN_TEXTWIN_AL_CENTRE; str="Page 1"; hi iy A suitable di_ing_minsize method could be of the form: METHOD VOID mydlg_dl_inq_minsize(PR_MYDLG *self, INT *pCent, INT *pPmpt,INT *pCtl); { *pCent=gTextWidth (WS_FONT_SYSTEM, G_STY_NORMAL, "Page 999",8); i In an application that may have to be translated into one or more different languages, the width should be dtermined from text that is contained in the application's resource file. An alternative, if suitable for a particular application, would be to hard code an explicit maximum width. In this case it may be necessary to publish the maximum width (say, by means of a comment in the source of the application's resource file) for the benefit of translators or future developers. An alternative solution is to replace the d1_set_size method itself. In the present example this is possibly a better solution, since it reuses the code used elsewhere to set the page number. In this case the resource file initialises the control to its maximum size: CONTROL { class=C_TEXTWIN; flags=DLGBOX_ITEM_CENTRE | DLGBOX_ITEM_DEAD; info=TXTMESS { flags=IN_TEXTWIN_AL_CENTRE; str="Page 999"; he ian Assuming that this control immediately follows a dialog title (that is, it has an index number of 1) and that the resource file also contains a string resource of the form: RESOURCE STRING page_num_str {str="Page %u"; } the required code could then be: LOCAL_C VOID SetPage(INT pagenum) { TEXT buf[10]; hAtos (&buf [0] ,PAGE_NUM_STR, pagenum) ; hDigSetText (1, &buf[0]); } #pragma METHOD_CALL METHOD VOID mydlg_dl_set_size(PR_MYDLG *self); { p_supersend2 (self,O_DL_SET_SIZE); /* sets width for largest case */ SetPage(1); /* now set initial page number value */ } On first being made visible the dialog displays the text "Page 1" but is wide enough to display "Page 999". Subdialogs 7 DIALOGS A dialog box may contain one or more items that can 'explode' into a separate subdialog, displayed over the main dialog. An example is the Margins line of the Print setup dialog as used, for example, in the Word application. The controt resource for this dialog item is as follows: CONTROL { class=C_TEXTWIN; prompt="Margins"; info=TXTMESS { stra"; flags=IN_TEXTWIN_POPOUT; ad Ir The dialog is shown, with the Margins item highlighted, in the following diagram: ‘Page size... ‘Header... ‘Footer... ‘Paging control... ‘Printer model... ‘Printer device... Print setup 1, No, 1,2,3 Canon BJ-18e Plis The significant points about the above contro. resource are that the control is an instance of the TExTWwIN class and that it is initialised with the In_TExTwIN_PopPout flag. By convention, the prompt (which is also an instance of TexTwin) for such an item terminates with an ellipsis. The text of the control, by convention, shows a summary of the current values of the information that may be modified by the subdialog and should therefore be set dynamically, in either the d1_dyn_init or the dl_set_size methods. Which method is the most appropriate depends on the nature of the summary text to be displayed. If it is of fixed width the resource text may be a null string (as it is in the above example) and the replacement text may safely be set in the d1_dyn_init method. If the summary text is of variable size, the resource file should contain a string that is guaranteed to be the longest that can be displayed. This text should be replaced in the di_set_size method, after supersending the pL_sET_s1zEz message, so that the dialog is guaranteed to be wide enough. A contrRot resource that uses this technique is shown below. CONTROL { class=C_TEXTWIN; prompt="Font"; info=TXTMESS { str="MMMMMMMMMMMMMMMMMMMM 00"; flags=IN_TEXTWIN_POPOUT; i /* max font name + space + max font size */ by The IN_TEXTWIN_Popourt flag ensures that the dialog item will respond to the Tab key by sending the dialog a DL_LAUNCH_SUB message (and to any other key by displaying the sys_popouT_HELP information message which, in English, is "Press Tab to change this item"). The main dialog's d1_launch_sub method launches the subdialog in exactly the same way as any other dialog is launched, that is by means of either the window server object's ws_do_dial method, or the hLaunchDial utility function. OBJECT ORIENTED PROGRAMMING GUIDE The following diagram illustrates the Margins subdialog launched from the Print setup dialog. Print setup Margins (inches) mm et ‘Left 1.25 ‘Right 1.25 ‘Bottom = 1.25 ‘Printer device... P.lis \. A parameter to d1_launch_sub is the index number of the dialog item that launched the subdialog. When launching the Margins subdialog, for example, this parameter has a value of 2. Processing within the subdialog is no different from that needed for a main dialog. On completion of the subdialog, the return to the main dialog is handled automatically by system code. CHAPTER 8 DIALOG CONTROLS This chapter describes the use of the dialog controls provided by the HWIM object library. These controls are used as components of dialog boxes, allowing a wide variety of dialogs to be constructed. The following diagram, for example, shows a simple dialog constructed from a title and a prompted numeric editor control. Position to-do entry A control is implemented as an instance of a dialog control class: dialog control classes are not usually subclassed since the base class usually provides sufficient functionality and flexibility for most dialogs. Dialog control classes ultimately subclass the LopcEr class and thus inherit its property and methods. The initial appearance and behaviour of a dialog control are primarily determined by the (static) content of a CONTROL resource (in the application resource file). They may be dynamically modified by an optional WN_SET message sent from the d1_dyn_init method of the dialog box itself. The use of dynamic initialisation allows the control to be modified to take into account the current state of the application (in the above example the initial value will have been set to correspond to the position of an item in the current to-do list). The preferred method of sensing and setting a dialog control is to use the wn_sense and wn_set methods of the dialog box, as in the following code fragment: p_send4 (DatDialogPtr,O_WN_SET, control_index, pset) The control is identified by its index control_index. It is passed a pointer, pset, to an appropriate sE_xxx struct that holds the replacement data. The reserved static DatDialogPtr is assumed to point to the dialog; an assumption that is made throughout this chapter. It will always be true unless the dialog that owns the control sets the pR_WIN_No_pppP flag in its win. flags property (see the Dialogs chapter for further details). The alternative method of sensing/setting a dialog control is to use the wn_sense and wn_set methods of the particular dialog control, as in the following example code fragment. PR_LODGER *hand; hand=(PR_LODGER *)p_send3 (DatDialogPtr,O_DL_INDEX_TO_HANDLE, cont rol_index) p_send3 (hand, O_WN_SET,pset) The first method is clearly preferable. On termination of a dialog, the associated data can be sensed by means of a wn_sense method call from within the dialog box adi_key method. This is the normal practice for any but the simplest of dialogs. The HWIM object library also includes some convenient utility functions that can perform the more common dialog control sensing and setting actions. The full set of available functions is described in the Dialog box utilities section of the HWIM Utility Functions chapter. Each type of component control has an associated sz_xxx struct, used for both setting and sensing the control's data. In almost all cases the control allows selective setting of its data: which items of data that are set is determined by the value of a flags field in the appropriate sz_xxx struct. The flags field has no effect on sensing: the wn_sense method always senses all relevant data. OBJECT ORIENTED PROGRAMMING GUIDE Text windows The textwin.g header file should be included when using a text window control. A TEXTWIN text window control is widely used to provide dialog titles, prompts for controls, and simple messages (and exceptionally to launch sub-dialogs). Hello message The above dialog could be created with the following pra.oc resource. RESOURCE DIALOG demodlg { title="Hello"; flags=0; controls= { CONTROL { class=C_TEXTWIN; flags=DLGBOX_ITEM_CENTRE |DLGBOX_ITEM_DEAD; info=TXTMESS { flags=IN_TEXTWIN_AL_CENTRE; str="message"; de di } The first item in this dialog is the title and is specified by the resource line. title="Hello"; This creates an underlined centred item, with index zero, displaying the appropriate text. Note that the title is, in fact, simply a TExTw1n control with index zero. The above line is exactly equivalent to including the following controt resource as the first item in the dialog: RESOURCE CONTROL demonstration { class=C_TEXTWIN; flags=DLGBOX_ITEM_CENTRE | DLGBOX_ITEM_DEAD | DLGBOX_ITEM_UNDERLINED; info=TXTMESS { flags=IN_TEXTWIN_AL_CENTRE; str="Hello"; i } As a result the title can easily be replaced dynamically using the npigsetText utility function called from within, say, the dl1_dyn_init method of the dialog. The second item in the demodig resource is a simple text window and is specified by the contro struct: CONTROL { class=C_TEXTWIN; flags=DLGBOX_ITEM_CENTRE | DLGBOX_ITEM_DEAD; info=TXTMESS { flags=IN_TEXTWIN_AL_CENTRE; str="message"; i } where the IN_TEXTWIN_AL_CENTRE flag ensures that the text is centred within the control. Note also the DLGBOX_ITEM_CENTRE flag that ensures that the control itself is centred within the dialog. 8 DIALOG CONTROLS A TEXTWIN control can also have a prompt as shown in the following example. Hello Prompt message This dialog could be specified with the following resource: RESOURCE DIALOG demodlg { title="Hello"; flags=0; controls= { CONTROL { class=C_TEXTWIN; flags=DLGBOX_ITEM_DEAD; prompt="Prompt"; info=TXTMESS { flags=IN_TEXTWIN_AL_CENTRE; str="message"; hi hi } As in this example, the text of a prompt is specified by a line of the form: prompt="Prompt"; The text of a prompt is left aligned automatically, and is normally preceded by a bullet symbol , to indicate that the content of the corresponding control can be modified. The bullet symbol can be removed permanently by setting the pLcBox_1TEM_pEanp flag. Specifying prompt text does not necessarily mean that the prompt will appear. If, in addition to a prompt, a control also has the pLGBox_ITEM_CENTRE flag set (as in the earlier example) the prompt will be suppressed.This is, however, an abnormal case and is not a recommended technique, since it can lead to unexpected behaviour. A dialog set up in this way, and with the pLcBox_1TEM_DEap flag left clear, has the rather strange appearance shown in the following diagram. By initialising a text window used as dialog box control with the 1n_TExTw1IN_Popout flag, it may be used to trigger a subdialog, as described in the previous chapter. Initialisation The initial content and appearance of a text window control are specified by means of a rxTmEss resource struct: STRUCT TXTMESS { WORD flags=0; TEXT str=""; } The str element specifies the initial text and flags may contain any sensible ored combination of: IN_TEXTWIN_AL_LEFT align text left IN_TEXTWIN_AL_RIGHT align text right IN_TEXTWIN_AL_CENTRE centre text IN_TEXTWIN_BOLD text in bold typeface IN_TEXTWIN_POPOUT launch a subdialog on receipt of a Tab key OBJECT ORIENTED PROGRAMMING GUIDE Setting The sz_TEXTWIN struct is used for setting and sensing text window property. typedef struct { INT flags; UWORD state; TEXT *buf; UWORD len; }SE_TEXTWIN; When setting a text window, a sensible combination of the following values should be ored into the flags field, to indicate which aspects are to be set (or cleared). SE_TEXTWIN_ALIGN setting or clearing an alignment flag SE_TEXTWIN_BOLD setting or clearing the bold typeface flag SE_TEXTWIN_TEXT setting the text For each flag that is not set, the corresponding data will not be changed by the wn_set method. If setting the text, a pointer to the replacement text must be written to *but and the length of the text must be written to len. Any sensible combination of the following flags may be placed in the state element: PR_TEXTWIN_AL_LEFT text is left aligned PR_TEXTWIN_AL_RIGHT text is right aligned PR_TEXTWIN_AL_CENTRE text is centred PR_TEXTWIN_BOLD text is in a bold typeface The following example sets, for the item with item number index (assumed to be a text window) the text to the string pointed to by str. It also clears any PR_TEXTWIN_BOLD flag and sets PR_TEXTWIN_AL_RIGHT (clearing PR_TEXTWIN_AL_LEFT and PR_TEXTWIN_AL_CENTRE in the process). No other flags that may exist in the text window's property are affected. LOCAL_C VOID SetTextWin(INT index, TEXT *str) { SE_TEXTWIN set; set. flags=SE_TEXTWIN TEXT |SE TEXTWIN. ALIGN |SE_TEXTWIN_BOLD; set.state=PR_TEXTWIN_AL_ RIGHT; /* PR_TEXTWIN_BOLD is not set, so will be cleared */ set. buf=str; set.len=p_slen(str); p_send3 (DatDialogPtr,O_WN_SET, index, &set) ; } It is rare for application code to set more than the text of an instance of TExtw1n used as a dialog box component. In such a case the hp1gSetText utility function may be used. As mentioned earlier, this utility function can also be used to replace the text of the title by passing an index of zero. The text window's flags are generally either set on initialisation (usually from In_TExTWIN_xxx values set in a resource item) or are set or cleared by system code (see, for example, the pLGBox d1_item_lock and dl_item_dim methods). Sensing Sensing a text window writes a pointer to the text and the text length to the but and 1en elements of an SE_TEXTWIN Struct. It does not provide any information about the text window's state flags. A typical call is as follows: SE_TEXTWIN set; p_send3 (DatDialogPtr,O_WN_SENSE, index, &set) ; A text window must not be sensed if it contains no text. 8 DIALOG CONTROLS Choice lists The chlist.g header file should be included when using a choice list control. A choice list dialog control presents the user with a list of choice items only one of which is visible and hence selected. The selection can be changed by the following means: e using the left and right arrow keys e using first letter matching e via a pop-out expanded view obtained with the tab key e or optionally, via incremental matching with a sequence of key presses (the control must be specially configured to allow incremental matching by use of the appropriate flag - see below). In the following two illustrations a dialog containing three choice lists is shown. In the right hand picture the user has pressed the Tab key to obtain the pop-out expanded view for the first, highlighted, choice list. Stule for entry Stule for entry +No+ c(Noy ‘Italic No ‘Italic Yes ‘Underline No ‘Underline Wo A choice list control, allowing the user to select one of four presidents of the United States, could be defined with the following two resources: RESOURCE MENU presidents { items= { CHOICE_ITEM {str="Kennedy";}, CHOICE_ITEM {str="Johnson";}, CHOICE_ITEM {str="Nixon"; }, CHOICE_ITEM {str="Ford"; } i } RESOURCE CONTROL demonstration { class=C_CHLIST; flags=DLGBOX_ITEM_NOTIFY_CHANGED; prompt="President"; info=CHLIST{rid=presidents; }; } In this case, the dialog control would initially display "Kennedy", surrounded by a pair of little arrows, to the right of the "President" prompt. Initialisation The cuit1st resource struct specifies the initial content and appearance of a choice list: STRUCT CHLIST { LINK rid=0; BYTE nsel=0; BYTE flags=0; } OBJECT ORIENTED PROGRAMMING GUIDE The ria element identifies the menu resource that contains the list of selections as an array of cHOICE_ITEM structs: RESOURCE MENU example_menu { items= { CHOICE_ITEM {str="zero";}, CHOICE_ITEM {str="one";}, CHOICE_ITEM {str="two"; } ‘i } The cHorck_ITEm structs are indexed according to the order in which they are listed in the menu resource. Thus the first has index zero, the second has index one, and so on. In hwim.rh the cHoIcE_ITE™ struct is defined as follows: STRUCT CHOICE_ITEM /* choice list item */ BYTE { TEXT str=""; /* identification text */ } The nse1 element of a cHLIsT resource struct specifies the index number of the initially selected item. The flags element may optionally contain the 1n_CHLIST_INCREMENTAL flag, to allow choice list selection to be made using incremental key matching. Note that this option can not be set dynamically. Setting A choice list is set by passing a pointer to an sz_CHLIST struct to the wn_set method typedef struct { UWORD set_flags; /* which fields are significant */ PR_VAROOT *data; /* pointer to array containing data */ UWORD nsel; /* index of current item */ } SE_CHLIST; The property to be set is indicated by oring one or more of the following flags into the flags field of the above struct. SE_CHLIST_NSEL the index of the current item is to be set. SE_CHLIST_DATA the data is to be replaced. The replacement data is a string array - see variable arrays in the OLIB Reference manual. SE_CHLIST_RETAIN data should not be destroyed on destruction of the choice list control - once set this flag cannot be cleared. The content of a choice list can be set dynamically, say, from the dialog's di_dyn_init method. However, changing the content of a choice list once the dialog has become visible is not recommended, since the width of the dialog box is set on initialisation. If the choice list content must be replaced, then care should be taken to ensure that the text does not become too wide for the dialog box to display. Sensing A choice list is sensed by passing a pointer to an sE_CHLIST struct to the wn_sense method. The SE_CHLIST struct is defined above. Both nsei and data are sensed. 8 DIALOG CONTROLS Push buttons and action lists The aclist.g header file should be included when using an action list control. An action list control presents the user with a horizontal list of one or more push-button options, as illustrated in the following diagram: Alarm details ¢Yes> ‘Time before 66:45 ‘Alarm at 45:55 pm ‘Days previous 64 ‘Sound Leloup Test sound Confirm [Menu] (_Enter_| This action list consists of the two buttons, and their accompanying labels "Test sound" and "Confirm", at the bottom of the dialog (note that the action list must either be the last of a series of controls, or it must appear on its own).An action list, allowing the user to select either Yes or No, could be defined with the following resource: RESOURCE ACLIST_ARRAY yes_or_no { button= { PUSH_BUT { keycode=—'n'; str="No"; }y PUSH_BUT { keycode='y'; str="Yes"; } ‘i The push-button is defined by a text string str that appears above the button, and a keycode indicating the key to be pressed by the user. For non-special keys, the uppercased key symbol will appear on the push-button. For special keys, the keycode is conveniently specified by a symbolic constant. The symbolic constants, and the text that will appear on the push-button, are as follows: W_KEY_RETURN "Enter" W_KEY_ESCAPE "Esc" W_KEY_DELETE_LEFT "Del" W_KEY_SPACE "Space" W_KEY_UP W_KEY_DOWN W_KEY_RIGHT W_KEY_LEFT W_KEY_TAB "Tab" W_KEY_MENU "Menu" OBJECT ORIENTED PROGRAMMING GUIDE For example, the leftmost button in the dialog, illustrated above, could be defined with the following PUSH_BUT resource. RESOURCE PUSH_BUT test_sound_button { keycode=W_KEY_MENU; str="Test sound"; } A negative keycode (as in an earlier example) indicates that the escape key can also be used to obtain the same effect (note that this is not possible if the escape key has already been assigned). The yes_or_no resource, defined earlier, could be used to create a dialog, asking the user to press 'y' or 'n', as follows: RESOURCE DIALOG get_answer { title="Accept changes ?" flags=DLGBOX_NOTIFY_ESCAPE | DLGBOX_RBUF_FILLED; controls= { CONTROL { class=C_ACLIST; info=ACLIST { rid=yes_or_no; ‘i ‘i } The same effect could be obtained by using the sys_Nno_ves resource defined in the system resource file. There is an alternative form of the above action list, which is useful when space is at a premium, as illustrated in the following diagram. (E)End (R)Replace (S)Skip (ADA As in this case, the compact, or small, action list is not usually accompanied by any other controls. A small action list uses the same acLIST_ARRay resource as the standard action list. The example given above, yes_or_no, could be used to create a dialog consisting of a small action list, and no other controls, as follows: RESOURCE DIALOG get_answer { flags=PR_WIN_FORCE_BOTTOM; controls= { CONTROL { class=C_SMACLIST; info=ACLIST { rid=yes_or_no; di ‘i } The only difference, between the dialog resources for the two action lists, is the class: the standard action list is an instance of the acuist class whereas the small action list is an instance of the smactist class. Initialisation The appearance and behaviour of a push button are specified with a pusH_BuT resource. STRUCT PUSH_BUT BYTE { WORD keycode; TEXT str; /* text associated with button */ } 8 DIALOG CONTROLS One or more buttons are in turn collected into an array: the first button in the array has index zero, the second has index one, and so on. The first button will appear on the far left of the control. STRUCT ACLIST_ARRAY rid { LEN BYTE STRUCT button[]; /* array of push_buttons */ } This array is included as a dialog control by means of the acurst struct. STRUCT ACLIST /* Initialising struct for action list */ { LINK rid; } The resources are the same for both the standard and the small action lists. There are no flags associated with this control, since there is nothing to change on initialisation, or via setting. Setting and sensing There is nothing that can usefully be set or sensed. Edit boxes The edwin.g header file should be included when using an edit box control. An edit box control presents an editable string which may be wholly or partially visible. A wide range of options are available for customising this control: see the resources section. In the following example an edit box is displaying "rabbit burrow". Find rabbit burrow ‘Direction Forwards ‘Case sensitive No An edit box control accepting up to 40 characters, including tabs, with 20 visible at any one time could be created using the following resource: RESOURCE CONTROL { class=C_EDWIN; prompt="string"; info=EDWIN { Sstrarns £lags=IN_EDWIN_VULEN_CHARACTERS | IN_EDWIN_ACCEPT_TABS; maxlen=40; vulen=20; i; } Initialisation The initial content and appearance of an edit box are specified by an Epw1N resource struct. STRUCT EDWIN /* edit box */ { WORD vulen; /* ignored unless either _VULEN_ flag set */ WORD flags=0; WORD maxlen; TEXT str=""; } where maxien specifies the default edit box width, and the maximum length of string that may be edited. OBJECT ORIENTED PROGRAMMING GUIDE The behaviour of an edit box is specified by oring one or more of the following flags into the flags member of the Epwin struct. N_EDWIN_DIALLABLE indicates that the edit box should accept the telephone symbol via the shift+dial key combination. N_EDWIN_ACCEPT_TABS indicates that the edit box should accept tabs. N_EDWIN_AUTO_CUR_END indicates that the text, when first displayed, is not to be highlighted and that the cursor is to be placed at the rightmost position. N_EDWIN_NO_AUTOSELECT indicates that the text, when first displayed, is not to be highlighted and that the cursor is to be placed at the leftmost position. N_EDWIN_VULEN_CHARACTERS indicates that the width of the edit box is specified, in characters, in member vulen of the EpwIn struct. N_EDWIN_VULEN_PIXELS indicates that the width of the edit box is specified, in pixels, in member vulen of the EpwIn struct. Setting The sz_EDwIN struct is used for setting an edit box. struct { TEXT *buf; UWORD len; } SE_EDWIN; The following code fragment demonstrates the setting of an edit box, assumed to be the item with index 2: SE_EDWIN set; TEXT buf[11]; p_scpy (&buf[0],"Hello world"); set .buf=&buf [0]; set.len=p_slen(set.buf); p_send4 (DatDialogPtr,O_WN_SET,2,&set) ; Sensing The sz_Epwtn struct defined above is also used for sensing an edit box. The following code fragment demonstrates the sensing of an edit box, again assuming that it is the item with index 2: SE_EDWIN sense; p_send4 (DatDialogPtr,O_WN_SENSE,2,é&sense) ; LONG numeric editor The ncedit.g header file should be included when using a long numeric editor control. A long numeric editor control presents an editable long integer value. In the following diagram, the long numeric editor control has a current value of five hundred thousand. Demonstration ‘LONG 566648 A numeric editor control could be created using the following resource: RESOURCE CONTROL { class=C_LNCEDIT; prompt="LONG"; info=LNCEDIT { low=1; high=700000; current=500000; i 8 DIALOG CONTROLS Initialisation The initial content of a long numeric integer is specified by means of an LNCEDIT resource struct. STRUCT LNCEDIT /* long number edit box */ { LONG current = 0; LONG low = 0; /* lowest allowed value */ LONG high = 10000000; /* highest allowed value */ } where the current value may not be less than iow or greater than high. Setting The sz_LNcEDIT struct is used for setting a long integer numeric editor. typedef struct { LONG value; LONG low; LONG high; UWORD flags; } SE_LNCEDIT; The property to be set is indicated by oring one or more of the following flags into the flags field of the above struct SE_LNCEDIT_VALUE indicates that the current value is to be set SE_LNCEDIT_LOW indicates that the lower limit is to be set SE_LNCEDIT_HIGH indicates that the upper limit is to be set The following code fragment illustrates the setting of a long integer numeric editor, assumed to be the item with index 4: SE_LNCEDIT set; set .flags=SE_LNCEDIT_VALUE|SE_LNCEDIT_HIGH; set.value=100; set .high=50000; p_send4 (DatDialogPtr,O_WN_SET, 4, &set) ; Sensing A pointer to a Lone is used for sensing the current value. The low and nigh values can not be sensed. The following code fragment demonstrates the sensing of a long numeric editor, again assuming it to be the item with index 4: LONG value; p_send4 (DatDialogPtr,O_WN_SENSE, 4, évalue) ; Integer numeric editor The ncedit.g header file should be included when using an integer numeric editor control. An integer numeric editor control presents an editable unsigned integer value. In the following diagram the integer numeric editor has a current value of one. Position to-do entry An integer numeric editor control could be created using the following example resource: RESOURCE CONTROL { class=C_NCEDIT; prompt="Number of cars"; info=NCEDIT { low=0; high=6; current=4; ‘i OBJECT ORIENTED PROGRAMMING GUIDE Initialisation The initial content of an integer numeric editor are specified by means of an ncEDIT resource struct: STRUCT NCEDIT /* UWORD number edit box */ { UWORD current = 0; UWORD low = 0; /* lowest allowed value */ UWORD high = 65535; /* highest allowed value */ } where the current value may not be less than 1ow or greater than nigh. Setting The sz_NcEpDIT struct is used for setting an integer numeric editor. typedef struct { UWORD value; UWORD low; UWORD high; UWORD flags; } SE_NCEDIT; The property to be set is indicated by oring one or more of the following flags into the fags field of the above struct. SE_NCEDIT_VALUE indicates that the current value is to be set. SE_NCEDIT_LOW indicates that the lower limit is to be set. SE_NCEDIT_HIGH indicates that the upper limit is to be set. The following code fragment demonstrates the setting of an integer numeric editor, assuming it to be the item with index 4: SE_NCEDIT set; set .flags=SE_NCEDIT_VALUE | SE_NCEDIT_HIGH; set .high=100; set.value=10; p_send4 (DatDialogPtr,O_WN_SET, 4, &set) ; Sensing A pointer to a uworp is used for sensing the current value. The low and nigh values can not be sensed. The following code fragment demonstrates the sensing of an integer numeric editor, again assuming that it is the item with index 4: UWORD value; p_send4 (DatDialogPtr,O_WN_SENSE, 4, &évalue) ; WORD numeric editor The ncedit.g header file should be included when using a word numeric editor control. A word numeric editor control presents an editable signed integer value. In the following example the word numeric editor has a current value of minus one hundred. A word numeric editor control could be created using the following resource: RESOURCE CONTROL { class=C_WNCEDIT; prompt="Temperature"; info=WNCEDIT { current=-100; i 8 DIALOG CONTROLS Initialisation The initial content and appearance of a word numeric editor are specified by means of a wNcEDIT resource struct: STRUCT WNCEDIT /* numeric control edit box (signed words) */ { WORD current = 0; WORD low = -32768; /* lowest allowed value */ WORD high = 32767; /* highest allowed value */ } where the current value may not be less than 1ow or greater than high. Setting The sz_wNcEDIT struct is used for setting the word numeric editor. typedef struct WORD value; WORD low; WORD high; WORD flags; } SE_WNCEDIT; The property to be set is indicated by oring one or more of the following flags into the fags field of the above struct. SE_WNCEDIT_VALUE indicates that the current value is to be set. SE_WNCEDIT_LOW indicates that the lower limit is to be set. SE_WNCEDIT_HIGH indicates that the upper limit is to be set. The following code fragment demonstrates the setting of a word numeric integer, assuming it to be the item with index 3: SE_WNCEDIT set; set .flags=SE_WNCEDIT_VALUE|SE_WNCEDIT_LOw; set.low=4; set.value=10; p_send4 (DatDialogPtr,O_WN_SET, 3, &set) ; Sensing A pointer to a worp is used for sensing the current value. The low and nigh values can not be sensed. The following code fragment demonstrates the sensing of a word integer numeric editor, again assuming that it is the item with index 3: WORD value; p_send4 (DatDialogPtr,O_WN_SENSE, 3, évalue) ; Range numeric editor The rgedit.g header file should be included when using a range numeric editor control. A range numeric editor control presents two editable unsigned words specifying upper and lower values of a range. In the following example a range numeric editor is shown with a lower value of thirty and an upper value of sixty. A range numeric editor control could be defined using the following resource: OBJECT ORIENTED PROGRAMMING GUIDE RESOURCE CONTROL { class=C_RGEDIT; prompt="Age range"; info=RGEDIT { value_1=100; value_2=200; de } Initialisation The initial content of a range numeric editor is specified by means of an RcEDIT resource struct: STRUCT RGEDIT /* range editor */ { WORD low=1; /* lowest allowed value */ WORD value_1=1; /* lower value of range */ WORD value_2=9999; /* higher value of range */ WORD high=9999; /* highest allowed value */ } where value_1 must be less than or equal to value_2 and both values may not be less than 1ow or greater than high. Setting The sz_RGEDIT Struct is used for setting the range numeric editor. typedef struct { UWORD value[4]; UWORD flags; } SE_RGEDIT; The value array is indexed as follows: IX_RGEDIT_LOW index of lower limit in value array. IX_RGEDIT_VALUE_1 index of lower current value in value array. IX_RGEDIT_VALUE_2 index of upper current value in value array. IX_RGEDIT_HIGH index of upper limit in value array. The property to be set is indicated by oring one or more of the following flags into the flags field of the above struct. SE_RGEDIT_LOW indicates that the lower limit is to be set. SE_RGEDIT_VALUE_1 indicates that the current lower value is to be set. SE_RGEDIT_VALUE_2 indicates that the current upper value is to be set. SE_RGEDIT_HIGH indicates that the upper limit is to be set. The following code fragment demonstrates the setting of a range numeric integer, assuming that it is the item with index 2: SE_RGEDIT set; set. flags=SE_RGEDIT_VALUE 1|SE RGEDIT_VALUE_2; set.value [IX_RGEDIT_VALUE_1]=4; set .value [IX_RGEDIT_VALUE_2]=10; p_send4 (DatDialogPtr,O_WN_SET,2,&set); Sensing The sz_RGEDIT struct (see above) is used for sensing the range numeric editor: all four members of the struct are sensed. The following code fragment demonstrates the sensing of a range numeric editor, again assuming it to be the item with index 2: SE_RGEDIT sense; p_send4 (DatDialogPtr,O_WN_SENSE, 2, &sense) ; 8-14 8 DIALOG CONTROLS Floating point editor The fltedit.g header file should be included when using a floating point editor control. A floating point editor control presents an editable floating point number. In the following example two floating point editors are shown with current values of 21.00 and 29.70. Page size (cm) ‘Page size Custom ‘Width ‘Height 29.76 ‘Orientation Portrait A floating point editor control could be defined using the following resource: RESOURCE CONTROL { class=C_FLEDIT; prompt="Width"; info=FLEDIT { current=21.00; low=0.0; high=40.0; ndec=2; ‘i } Initialisation The initial content and appearance of a floating point editor are specified by means of an FLTEDIT resource struct: STRUCT FLTEDIT /* floating point edit box */ { DOUBLE current=0.0; DOUBLE low =-9.9999999999e99; /* lower bound */ DOUBLE high =9.9999999999e99; /* upper bound */ BYTE vulen=5; /* width of editor in characters */ BYTE ndec=0; /* P_DTOB_GENERAL */ } where the current value may not be less than 1ow or greater than high. The current value is displayed to ndec decimal places in a box of width vuien characters. If ndec is zero, the current value is displayed in general format as defined for the PLIB routine p_dtob (see the Plib Reference manual). Setting The sz_FLEDIT struct is used for setting the floating point editor. typedef struct { DOUBLE current; /* current value */ DOUBLE low; /* lower bound */ DOUBLE high; /* upper bound */ WORD set_flags; /* which fields to set */ } SE_FLTEDIT; The property to be set is indicated by oring one or more of the following flags into the set_flags field of the above struct. SE_FLTEDIT_VALUE indicates that the current value is to be set. SE_FLTEDIT_LOW indicates that the lower limit is to be set. SE_FLTEDIT_HIGH indicates that the upper limit is to be set. OBJECT ORIENTED PROGRAMMING GUIDE The following code fragment demonstrates the setting of a floating point numeric integer, assuming that it is to be the item with index 2: SE_FLTEDIT set; set .set_flags=SE_FLTEDIT_VALUE|SE_FLTEDIT_LOW; set.low=4; set.value=10.3; p_send4 (DatDialogPtr,O_WN_SET,2,&set) ; Sensing A pointer to a pouBLz is used for sensing the current value. The low and high values can not be sensed. The following code fragment demonstrates the sensing of a range integer numeric editor, again assuming it to be the item with index 2: DOUBLE current; p_send4 (DatDialogPtr,O_WN_SENSE,2,¤t) ; Date/time editor The dtedit.g header file should be included when using a date/time editor control. The date/time editor presents either an editable date, an editable time or an editable duration. In the following example the upper date/time editor is showing the time in am/pm format in hours, minutes and seconds, the lower editor is showing the date. Set time and date Goo 28:03 pm ‘Date 11/18/1993 A date/time editor showing a duration of one hour and ten minutes, with upper and lower limits of 23h59m and OhOm could be defined using the following resource: RESOURCE CONTROL { class=C_DTEDIT; prompt="Time left"; info=DTEDIT { flags=IN_DTEDIT_HHMM_D]|IN_DTEDIT_INIT; current=4200; low=0; high=86340; i } Initialisation The initial content of a date/time editor is specified by means of a pTEDIT resource struct: STRUCT DTEDIT /* combined date/time editor */ { WORD flags; LONG current; LONG low; /* lowest allowed value */ LONG high; /* highest allowed value */ } where the current value may not be less than 1ow or greater than high. Note that any supplied values of current, low and high will be ignored, and a set of default values used, if f1ags does not contain IN_DTEDIT_INIT. The format of the date/time editor must be specified by including one of the following values in the flags member. 8 DIALOG CONTROLS IN_DTEDIT_DDMMYYYY initialise as a date editor to display a date in day, month and year format: the current, low and high dates are specified as days elapsed since 01/01/1900. Note that the control can not display dates before 01/01/1980. See the MFNE subclass examples section of the Numeric Editors chapter of the HWIM Reference manual for an example of a date editor with an extended range. IN_DTEDIT_HHMMSS initialise as a time editor to display a time as hours, minutes and seconds. The current, low and high times are specified in seconds elapsed since midnight. IN_DTEDIT_HHMM initialise as a time editor to display a time as hours and minutes. The current, low and high times are specified in seconds elapsed since midnight. N_DTEDIT_HHMMSS_D initialise as a time editor to display a duration as hours, minutes and seconds. The current, low and high durations are specified in seconds. N_DTEDIT_HHMM_D initialise as a time editor to display a duration as hours and minutes. The current, low and high durations are specified in seconds. N_DTEDIT_HHMMSS_ND __initialise as a time editor to display a negative duration as hours, minutes and seconds. The current, low and high durations are specified in seconds. N_DTEDIT_HHMM_ND initialise as a time editor to display a negative duration as hours and minutes. The current, low and high durations are specified in seconds. Setting The sz_pTED1IT struct is used for setting the date/time editor. typedef struct UWORD flags; LONG value; LONG low; LONG high; } SE_DTEDIT; The property to be set is indicated by oring one or more of the following flags into the fags field of the above struct. SE_DTEDIT_VALUE indicates that the current value is to be set. SE_DTEDIT_LOW indicates that the lower limit is to be set. SE_DTEDIT_HIGH indicates that the upper limit is to be set. The interpretation of value, low and high depends on the type of data that the control is set to display, as specified in the Jnitialisation section above. The following code fragment demonstrates the setting of a date/time editor, assuming that it is the item with index 2: ULONG stime; P_DAYSEC ds; SE_DTEDIT set; stime=p_date(); p_sttods (&stime, &ds) ; set.value=ds.days; set. flags=SE_DTEDIT_VALUE; p_send4 (DatDialogPtr,O_WN_SET,2,&set); Note that this code assumes that the date/time editor has been initialised to display a date, by setting the initialisation flag IN_DTEDIT_DDMMyyyy. OBJECT ORIENTED PROGRAMMING GUIDE Sensing The sz_DTEDIT Struct is used for sensing the date/time editor: typedef struct UWORD flags; LONG value; LONG low; LONG high; } SE_DTEDIT; Note, however, that the pTEDIT wn_sense method only writes to the value member. The low and high values can not be sensed. The following code fragment demonstrates the sensing of a date/time editor, again assuming it to be the item with index 2: SE_DTEDIT sense; p_send4 (DatDialogPtr,O_WN_SENSE, 2, é&sense) ; The Latitude/Longitude editor The /ledit.g header file should be included when using a latitude/longitude editor control. A latitude/logitude editor presents either an editable latitude or an editable longitude. Both the latitude and the longitude are expressed in degrees and minutes followed by a single character specifying the cardinal point i.e. N, W, S or E. In the following example the upper latitude/longitude editor is showing a longitude and the lower editor is showing a latitude. ‘Add city ‘Country Denmark ‘Longitude H1B°1HE ‘Latitude 656°88 N ‘Area code ‘GMT offset 1:46 ‘Zone European A latitude editor control that initially displayed 56° 8 N could be defined using the following resource: RESOURCE CONTROL { class=C_FLEDIT; flags=IN_LLEDIT_LATITUDE; prompt="Latitude"; info=LLEDIT { value=3368; i } Initialisation The initial content and appearance of a latitude/longitude editor are specified by means of an LLEDIT resource struct: STRUCT LLEDIT /* lat long editor */ { WORD flags; /* Latitude or longitude */ WORD value=0; /* The default value */ } where the latitude/longitude value is in minutes of arc: a positive sign indicating a latitude/longitude in the northern /western hemisphere, and a negative sign indicating a latitude/longitude in the southern /eastern hemisphere. Thus -604 corresponds to a latitude of 10° 4 S or a longitude of 10° 4 E. 8-18 8 DIALOG CONTROLS The behaviour of a latitude/longitude editor is specified by assigning one of the following flags to the flags member. IN_LLEDIT_LATITUDE display value as a latitude. IN_LLEDIT_LONGITUDE display value as a longitude. Setting The sz_LLEDIT struct is used for setting the latitude/longitude editor. typedef struct { WORD value; } SE_LLEDIT; The following code fragment demonstrates the setting of a date/time editor, assuming it to be the item with index 2: SE_LLEDIT set; set .value=3368; p_send4 (DatDialogPtr,O_WN_SET,2,&set); Sensing A pointer to an sz_LLEDIT struct is used for sensing the current value. The following code fragment demonstrates the sensing of a latitude/longitude editor, again assuming that it is the item with index 2: SE_LLEDIT sense; p_send4 (DatDialogPtr,O_WN_SENSE, 2, é&sense) ; File name editor The files. g header file should be included when using a file name editor control. A file name editor presents the user with a file name that may be edited with the keyboard or with the pop-out file selector (via the Tab key). A wide range of options is provided for customising the behaviour of file name editors: these options are described in the resources section below. It is commonly used when saving an edited file, for example a document created with the Word application. It is not usually used for opening an already existing file for which a file name choice list is better suited. A file name editor is generally supplied together with a pack selector, that is placed immediately below, as in the following example: the selector is specified by oring the pLGBox_NEEDS_PAcx flag into the flags member of the contro. struct (see below). Save icon as pic file t Name JF asel.pic| * Disk Internal A file name editor control could be defined using the following resource: RESOURCE CONTROL { class=C_FNEDIT; flags=DLGBOX_ITEM_NEEDS_PACK; prompt=""; info=FNEDIT { flags=IN_FNEDIT_STANDARD; fname="easel.pic"; hi OBJECT ORIENTED PROGRAMMING GUIDE Initialisation The initial content and appearance of a file name editor are specified by means of an FNEDIT resource struct: STRUCT FNEDIT /* filename editor */ { BYTE flags=0; TEXT fname=""; } The behaviour of the file name editor is specified by oring a suitable combination of the following flags into the £1ags member of the above struct. N_FNEDIT_STANDARD set fname to be the file name pointed to by patUsedPathNamePtr: typically the name of the application's current file. N_FNEDIT_ALLOW_DIRS allow the name of a directory with the file name. N_FNEDIT_JUST_DIRS allow only the name of a directory and no file name. N_FNEDIT_FORCE_NXIST disallow existing files. N_FNEDIT_NO_AUTOQUERY do not query on an existing file (by default the control prompts the user before accepting the name of an already existing file, to help prevent accidental deletion/overwriting). IN_FNEDIT_ACCEPT_NULL allow the null string. IN_FNEDIT_SET_DEFEXT set the default file extension to that of the file name specified in the FNEDIT resource struct. IN_FNEDIT_CAN_WILDCARD allow wildcards. Setting The file name may be set by passing a pointer to a character string to the wn_set method. The character string should contain the file name terminated by a zero character. Note that any extension present in the name string will be displayed in the control, even if it is the control's default extension. To obey the guidelines concerning the display of an application's default filename extension, the caller is responsible for checking for the presence of the default extension in the string and removing it before setting the file name editor control. The default extension may be set by passing a pointer to a character string to the wn_set method. The first character must be 0x01. The following characters can either be a file name, or a file extension preceded by a full stop character. Only the extension is significant. The following code fragment demonstrates the setting of the default extension, assuming that the rNEDIT control is the item with index | in the dialog: TEXT buf [P_FNAMESIZE]; buf [0]=1; p_scpy (&buf[1],".DBF") ; p_send4 (DatDialogPtr,O_WN_SET,1,é&buf[0]); Sensing A buffer, of size at least p_rnames1zz bytes, is used for sensing the current full file specification from a file name editor control, as illustrated by the following code fragment, again assuming it to be the item with index 1: TEXT buf [P_FNAMESIZE]; p_send4 (DatDialogPtr,O_WN_SENSE,1, &buf[0]); 8 DIALOG CONTROLS File name choice list The files. g header file should be included when using a file name choice list control. A file name choice list presents the user with a choice of files with the selection being made with the keyboard arrow keys, via letter matching or by pressing Tab to display the pop-out file selector. A wide range of options is provided for customising the behaviour of a file name choice list: these options are described in the resources section below. A file name choice list is always supplied with a pack selector that is placed immediately below as in the following example: the selector is specified by oring the pLGBox_NEEDS_pack flag into the flags member of the contRox struct (see below). Open file ¢ gelacuti+ ' Disk Internal A file name choice list is commonly used when opening an existing file. It is not usually used for saving an edited/modified file for which a file name editor is better suited. A file name choice list control could be defined using the following resource: RESOURCE CONTROL { class=C_FNSELWN; flags=DLGBOX_ITEM_NEEDS_PACK; info=FNSELWN { fname=""; i } Initialisation The initial content and behaviour of a file name choice list control are defined by an rNSELWN resource struct as follows: STRUCT FNSELWN /* filename selector */ { BYTE flags=0; TEXT fname=""; } The behaviour of a file name choice list control is specified by oring one or more of the following flags into the £1ags member of the above struct N_FNSELWN_STANDARD select the file specified by the patusedPathNamePtr reserved static: typically points to the application's current file. N_FNSELWN_SHOW_DIRS show directory names. N_FNSELWN_HIDE_FILES hide directory names. N_FNSELWN_RESTRICT_LIST display only files with extension matching the default. N_FNSELWN_CAN_TAG allow file tagging: file tagging is carried out with the file name choice list - pressing the '+' and '-' keys tags and untags a file respectively. N_FNSELWN_ACCEPT_NULL accept the null string. N_FNSELWN_SET_DEFEXT set the default extension to that of the file specified in the rnsELWN struct. N_FNSELWN_CAN_WILDCARD allow wildcards. OBJECT ORIENTED PROGRAMMING GUIDE Setting The file name may be set by passing a pointer to a character string to the wn_set method. The character string should contain the file name terminated by a zero character. Wildcards in the file name are allowed. The default extension may be set by passing a pointer to a character string to the wn_set method. The first character must be 0x01. The following characters can either be a file name, or a file extension preceded by a full stop character. Only the extension is significant. The following code fragment demonstrates the setting of the file name in an rnsELwn control, assuming that it is the item with index 3: TEXT buf [P_FNAMESIZE]; p_scpy (&buf [0], "DATABASE.DBF") ; p_send4 (DatDialogPtr,O_WN_SET, 3, &buf[0]); Note that, in normal usage, it should not be necessary to set the file name in the di_dyn_init method of a dialog. Sensing A buffer, of size at least p_rNames1zz bytes, is used for sensing the full file specification of the currently selected file. The following code fragment demonstrates the sensing a file name choice list, again assuming the control to be the item with index 3: TEXT buf [P_FNAMESIZE]; p_send4 (DatDialogPtr,O_WN_SENSE, 3, &buf[0]); CHAPTER 9 ACTIVE OBJECTS An active object represents an event source and is, by definition, an instance of any class that has acTIvE as an ancestor in its inheritance chain. This chapter provides a simple introduction to the use of active objects: for further information on the precise mechanisms involved, see The APPMAN Application Manager Class, The Active Class and Active Objects, and following chapters in the OLIB Reference manual. All HWIM applications contain at least one active object, which is a subclass of the HWIM wserv window server active object class. This active object represents the source of events directed to the application by the window server process. An application may create additional active objects to represent other event sources. A simple example would be to implement a timer, so that the application will, from time to time, receive timer expiry events. A prioritised queue of an application's active objects is maintained by the application manager. A significant part of the application manager's function is in its event loop, which manages this queue, associating the occurrence of an event with the appropriate active object and sending it a message. An active object must be added to this queue when it is created. This may be done explicitly by the code that creates the active object, or it may be included in the initialisation (that is, in the ao_init method) of the active object itself. Active objects and asynchronous requests Before an event can occur it must be requested by a program. The way to do this is to make an asynchronous request, as explained in the Asynchronous Requests and Semaphores chapter of the PLIB Reference manual. In consequence there is a strong connection between the making of asynchronous requests and active objects. An active object is, in fact, the standard way of making and processing asynchronous requests in HWIM applications. Since the application manager contains a mechanism with the express purpose of scheduling the events marking the completion of asynchronous requests that are encapsulated in active objects, you are recommended to use active objects for all asynchronous operations in an HWIM application. The diagram opposite illustrates the general form of any event loop. Testing for the event that has completed is usually done by polling the status words of all the possible requests. The application manager's event loop follows this general pattern, except that the code that requests an event and the code that processes its completion are provided by the ao_queue and ao_run methods respectively of one or more active objects. On completion of a request, the application manager polls the active objects in its queue to determine which one can process the event, and explicitly sends it an ao_RuUN message. In contrast, the application manager's event loop contains no explicit code to request an event and relies on its active objects to do this. Since an HWIM application always has at least one active object - its window server active object - there will always be something to make such a request. Note that there is an implied restriction that there should be only a single asynchronous request in an ao_queue method, and there should be no such request made in the ao_run method. OBJECT ORIENTED PROGRAMMING GUIDE All ao_run methods must return the value RUN_ACTIVE_uUsED (defined in appman.g) to signify that the event has been consumed. An active object has a status word (active.status) in its property and it is this status word that the application manager polls to determine which active object is to be sent an ao_RuN message. In order to assist the polling mechanism, an active object has a further item of property, active.isactive, Which must be set to a TRUE value when an asynchronous request is made. It is automatically cleared when the active object is sent an ao_RUN message. Active object priorities When an active object is created, it must be given a priority (by setting its active.priority property) before it is added to the application manager's queue. If, at any time, the requests from two or more active objects have completed, the one with highest priority will be the first to be sent an ao_RuN message. A priority is a signed byte and therefore must lie in the range +127 (highest priority) to -128 (lowest priority). A number of standard priorities are defined in appman.g and some of the more significant of these are explained below. PRIORITY_ACTIVE_IPCS +80 - for inter-process communication PRIORITY_ACTIVE_WSERV +60 - the priority of the window server active object PRIORITY_ACTIVE_SERIAL +20 - for serial port communications PRIORITY_ACTIVE_FILES -20 - for reading or writing files PRIORITY_ACTIVE_REPEATER -40 - for timers, animation, etc. PRIORITY_ACTIVE_PRINT -60 - for communication with a printer PRIORITY_ACTIVE_COMPUTE -100 - for background computation These values are supplied as guidelines and you are not compelled to follow them precisely. However, in order to ensure the application's responsiveness to redraws and the keyboard, most active objects should be given priorities that do not exceed that of the window server active object. Application responsiveness An active object can not be given a chance to run (by being sent an ao_RuN message) until the execution of a previous ao_RUN message (by this, or any other active object) has terminated. Thus an active object, even one of low priority, that performs extensive processing in its ao_run method will reduce the application's responsiveness to other events. It is the programmer's responsibility to ensure that the processing done in any one call to an active object's ao_run method is restricted to a reasonable amount. An active object that, for example, is being used to write a file to an SSD should not write the whole file in one operation, but should divide the writing into a number of relatively small sections. One way of doing this is to construct a buffer containing a section of the file and write the contents of this buffer to the file by means of an asynchronous write request in the ao_queue method. On receipt of an AO_RUN message, signifying that the write has completed, the process can be repeated, provided there is still part of the file that has not been written. An alternative approach would be to use a technique similar to the background processing mechanism, described below. In this case writing to the file would be performed synchronously, from within the ao_run method. A disadvantage to this second technique is that, if writing to a remote device, the write could take an extended time to complete and thus may compromise the responsiveness of the application. Background processing An active object is ideally suited to breaking down a long computation into a sequence of small sections and may be used for this purpose, even if the process does not involve making asynchronous requests. Examples of where this technique may be useful are the formatting of a large amount of text, or the recalculation of all the cells of a spreadsheet. The arp. class is supplied in the OLIB library as a basis for this type of use. The supplied class subclasses acTIve to replace the ac_init method with code that sets a priority of PRIORITY_ACTIVE_comPuTE and adds itself to the application manager's active object queue. It uses the default (active class) ao_queue method, which simply sets active.isactive to TRUE and generates an event by calling p_iosignal. 9-2 9 ACTIVE OBJECTS The arpue class must be subclassed to replace the ao_run method with one to perform a unit of processing and, if processing is not complete, send itself an ac_quEUE message. Errors Apart from errors during initialisation (for, example, failure to open a channel to a device) errors that result in p_leave being called are expected only to occur in the ao_run method. Thus, all operations that could potentially fail, such as the allocation of memory from the heap, should be performed from within this method, with a call to p_leave if an error occurs. Where possible, it is more effective to use the £_xxx functions, such as £_alloc, f£_new OF f£_send. If failure is possible in any asynchronous request that is made in the ac_queue method, the value of active.status should be checked for an error value in the ao_run method. Any such error should result iN p_leave being called, passing the error value. Any call to p_leave from within the ao_run method is handled by system code to perform standard error handling, as described in the Error Handling and Error Recovery chapter. Part of this standard mechanism is to send the active object an ao_ABRUN message. The ao_abrun method supplied by the acrrve class provides fail-safe reporting of the error and so, by default, no active object needs to take any explicit action to report errors to the user. The ao_abrun method is intended to be replaced by subclassers to provide any application-specific error recovery (again, see the Error Handling and Error Recovery chapter). In most cases the replacement method will, in addition to any other action, supersend the ao_aBRuNn message to report the error. Depending on the specific circumstances, the replacement ao_abrun method may, as its final action, send the active object a pesTRoY message. This would normally be appropriate if the active object was created by a command manager method, called as a result of the user's selection of a command menu option. A simple timer The Timer demonstration application is installed into the \sibosdk\oopdemo directory. It may be built from that directory by typing: make timer It is a simple example that uses a timer to print an information message every two seconds, but clearly illustrates the way in which an active object is set up and used. The category file, timer.cat contains, in addition to classes that will be familiar from the "Hello World" example, the class definition of a timer: CLASS mytimer active { REPLACE ao_init REPLACE ao_queue REPLACE ao_run PROPERTY { UWORD count; } } Note that, in order to show clearly the active object mechanisms, the myT1mer class subclasses active. In a real case it would be more efficient to subclass the OLIB timer class, which supplies some of the functionality that is duplicated in myTIMER. During the initialisation of the application's window server active object, an instance of myTImER is created and initialised as illustrated below: METHOD VOID timerws_ws_dyn_init (PR_TIMERWS *self) { self-—>wserv.cli=f_new (CAT_TIMER_TIMER, C_TIMERBW) ; p_send2 (self—>wserv.cli,O_WN_INIT)j; f_newsend (CAT_TIMER_TIMER, C_MYTIMER, O_AO_INIT, "TIM:",-1); } OBJECT ORIENTED PROGRAMMING GUIDE The ao_init method first supersends the ao_1n1T message to be processed by the acTIvE ao_init method, which opens a channel to the r1m: device. It then sets itself to a reasonably low priority and adds itself to the application manager's active object queue. Its final action is to send an ao_QuEUE message. METHOD VOID mytimer_ao_init (PR_MYTIMER *self,TEXT *devname, INT mode) { p_supersendé4 (self,O_AO_INIT, devname, mode) ; self—>active.priority=PRIORITY_ACTIVE_REPEATER; p_send3 (w_am, O_AM_ADD_TASK, self) ; p_send2 (self, O_AO_QUEUE) ; } The ao_queue method, listed below, simply makes an asynchronous request on the timer, using active.stat as its status word. Also, as required, it sets active.isactive to TRUE. An event will be signalled after the requested two-second delay. METHOD VOID mytimer_ao_queue (PR_MYTIMER *self) { LONG delay; delay=20; p_ioc4 (self-—>active.pcb, P_FREAD, &self->active.stat, &édelay) ; self—>active.isactive=TRUE; } When the event has occurred myTimer will be sent an ao_RuUN message to signify that the requested event has completed. The ao_run method increments a counter in its property and uses this value to display an information message at the bottom right hand corner of the screen. It then sends an ao_QuEUE message to restart the timer before returning RUN_ACTIVE_USED. METHOD INT mytimer_ao_run(PR_MYTIMER *self) { self—>mytimer.count+=1; hInfoPrint (TIMER_INFO, self-—>mytimer.count) ; p_send2 (self, O_AO_QUEUE) ; return (RUN_ACTIVE_USED) ; } The timer continues to run for the lifetime of the application. In a real application an active object could be created elsewhere in the program - frequently from a command manager method, executed in response to the selection of a menu option. The ao_run method would include a test for the completion of processing and, when complete would terminate itself by not sending an ao_QUEUE message (it might send itself a ppstRoy message instead). CHAPTER 10 ERROR HANDLING AND ERROR RECOVERY Psion's Object Oriented programming system and the associated libraries provide considerable support for reporting and recovering from error conditions. In consequence, a typical HWIM application contains very little explicit error-handling code. The cornerstones of error reporting and recovery are: e the use of p_enter and p_leave to centralise the handling of errors e¢ using PROPERTY n Statements in category files, for the automatic destruction of component objects e =the OLIB cizanup class, to provide for the freeing of temporary resources e standard error reporting in the ao_abrun method of all active objects This chapter gives a brief summary of the range of techniques that are available. One point that needs to be emphasised at the outset is that the error handling built into Psion's Object libraries does depend on p_ieave being called whenever a run-time error occurs. This means, for example, that an application should not permanently disable calls to p_leave from the window server by use of the window server function wDisableLeaves. If, for some specific purpose, application code does need to disable window server calls to p_leave, it should do so only temporarily. Any application code that calls woisableLeaves (TRUE) should ensure that it subsequently calls woisableLeaves (FALSE), before execution returns to system code. Errors during initialisation One simple precaution makes the handling errors during the initialisation of an application very simple: The application should be written so that all resources that the application needs in order to draw its initial view are created from within the ws_dyn_init method of its subclass of wSERV. If this condition is met then, assuming there are no coding errors that cause p_panic to be called, the application can not fail between the return from the ws_dyn_init method and its appearance on the screen. All that the application has to do in the event of a failure to create one of its start-up resources from anywhere in the ws_dyn_init method, or any other functions or methods that this calls, is to call p_leave with a suitable (negative) error number. System code handles the recovery and the reporting that the application has failed to start before terminating the application. Note that the application can call p_leave (-1) to terminate without a system-generated error report. The value -1 is the error number E_GEN_FAIL, and is also defined in hwimman.g as RUN_ACTIVE_CLEANUP_NONOTIFY. The most common cause of failure during initialisation is that there is insufficient memory available. In many applications this is the only error that could occur during start-up initialisation. An error of this nature can normally be precipitated by setting the application's initial heap size requirement to be sufficiently large. This is done by setting the heapsize variable in the applications .pr file, as described in the An HWIM Application - Hello World chapter. Should insufficient memory be available, the application will then fail at a very early stage, before any application-specific code is executed, and this failure will be handled by system code. 10-1 OBJECT ORIENTED PROGRAMMING GUIDE To avoid excessive memory use by an application you should avoid allowing for the worst case. You should not, for example, set an initial heap size for a file-based application so that it is guaranteed to be able to load an exceptionally large file. This technique may not prevent application code from running if, for example, the out-of-memory failure occurs when the window server is creating server-side resources for the application, or if a file-based application fails while opening a large file. It will, however, cause the application to fail earlier rather than later in the majority of cases. General error recovery One of the most important aspects of the handling of errors is to understand that virtually all application- specific code is executed from within the ao_run method of some active object or other. For example, all code that is executed in response to the receipt of a window server event is executed from within the ao_run method of the application's subclass of the wsErv object. This includes all system-generated redrawing and all keypress processing, which itself includes both the receipt of a wn_kzy message by any window and, more indirectly, the processing of a command by means of a command manager method. There are two main areas of code that are exceptions to this general case: e the start-up initialisation of an application e code executed in response to an error, such as an active object's ao_abrun method. Errors occurring in the first of these two areas are handled as described earlier, while the code executed in response to an error should be written so that it can never, itself, generate errors. In consequence an error can always be reported simply by calling p_leave. When called within an active object's ao_run method, this will be caught by the application manager's event handler and will, cause the application manager to be sent an aM_cLEAN_up message. This provides standard resource clean-up and also makes a call-back to the active object's own ao_abrun method, to provide standard error reporting (which is, itself, designed so that it will not fail). For further details, see the description of appman's am_start method, and the further topics that it references, in the APPMAN Application Manager Class chapter of the OLIB Reference manual. An application that has already reported an error in application-specific code may conclude its error response by calling p_leave (RUN_ACTIVE_CLEANUP_NONOTIFY). All the error recovery will be perfomed as described above, but there will be no system-generated error report. Except when under the explicit protection of a call to p_enter in application-specific code, an application should avoid calling p_leave (0). The application manager's event handler can not distinguish this from a return value of zero (RUN_ACTIVE_UNUSED) from an ao_run method and will cause the application to fail with a 'stray signal’ panic. Note that an application that has one or more global actions to execute on the occurrence of all types of error can subclass the am_clean_up method to provide these actions as well as the method's standard functionality. The Record application, which is described in the Application Design chapter, and whose source code is supplied with the SDK, provides an example of the use of this technique. The roll-back principle All operations that can fail should be written so that failure causes roll-back to a previously safe state. Failure to ensure adequate roll-back is one of the most common defects in application software and usually evidenced by a monotonically increasing use of memory as some operation is repeated. Such an occurrence can generally be detected faily easily, for example, by the use of spy.app (described in the Series 3/3a Programming Guide). A simple example is the insertion of records into a variable array (see, for example, the va_insertm method of the variat class, described in the Variable Arrays chapter of the OLIB Reference manual). If an error occurs before the insertion is complete, any partially inserted data is removed before the error is propagated by calling p_leave. 10-2 10 ERROR HANDLING AND ERROR RECOVERY In general, any operation that consists of a sequence of stages, where any stage could fail, must be written to release any resources that have been created in earlier successful stages. The general model is illustrated in the following code: VOID multi_stage() { INT error; stage_one(); /* this creates a resource, calling p_leave on failure */ error=p_enter(stage_two); /* catch any error in the second stage */ if (error) { undo_stage_one(); /* release the resource created in stage one */ p_leave (error) ; /* propagate the error to system code */ } } Note that this code assumes that any non-zero value of error is a negative error number. If there is a possibility that a positive value could be returned then, depending on its meaning, the code may have to test the sign of the value. If only negative values need to be propagated to system code, this can most conveniently be accomplished by calling £_leave (error) since this does not call p_leave if error is zero or positive. If possible, such error-handling code should be written so that the roll-back is as simple as possible. For example, suppose a large amount of data has to be inserted into a buffer, and that the data has to be read in segments. Recovery may be complex if each segment is read and inserted separately, since a failure will require the deletion of all previously inserted segments. A simpler approach, which requires no explicit error recovery code, is to pre-allocate space for the entire insertion in a single operation. If this fails (presumably by calling p_1eave) no further action is necessary. If it succeeds, the data can be written into the allocated space, segment by segment, with no risk of subsequent failure. If there could be a failure in reading the data segments then this approach still simplifies matters since there is always only a fixed size of allocated memory to be released, regardless of how many segments have been copied into it. The following sections give a number of different means of ensuring that roll-back recovers resources that have been created before an error occurs. In a real case it is likely that a mixture of these techniques will be used. Roll-back for component objects The creation of an object that contains a number of components could fail during the creation of the object itself or during the creation or initialisation of any of its components. Any failure should result in the destruction of the object and any partially created components. This situation is particularly simple since it can make use of the built-in mechanisms for component destruction. Suppose, for example, that a MYAPP category contains the myciass class, with component classes COMPONENT1 and componEnt2. The class definition for mycuass could be as follows: CLASS myclass root { ADD init PROPERTY 2 { VOID *comp1; VOID *comp2; } } where its init method function might be: VOID myclass_init (PR_MYCLASS *self) { self—>myclass.compl=f_new (CAT_MYAPP_MYAPP,C_COMPONENT1) ; self—>myclass.comp2=f_new (CAT_MYAPP_MYAPP, C_COMPONENT2) ; } If a mycLass instance is created with: VOID *hand; hand=f£_newsend (CAT_MYAPP_MYAPP, C_MYCLASS, O_INIT) ; 10-3 OBJECT ORIENTED PROGRAMMING GUIDE then a failure (by means of a call to p_ieave) at any stage will cause the object and any created components to be sent a pDesTRoy message, and the call to p_leave is then propagated. Note the use of £_new and £_newsend, to guarantee a call to p_leave on failure. The principle may be applied to the creation of components of the components, and so on. Other resources in an object's property Resources that are not component objects, but whose handles are stored in an object's property must be explicitly released in the object's destroy method. This is illustrated in the following example, which extends the one given above. In this case, mycnass has a cell of allocated memory, with its handle stored in its property, according to the class definition: CLASS myclass root { REPLACE destroy ADD init CONSTANTS { ALLOC_SIZE 100 } PROPERTY 2 { VOID *comp1; VOID *comp2; BYTE *alloc; } } Its init method function could then be: VOID myclass_init (PR_MYCLASS *self) { self-—>myclass.comp1l=f_new (CAT_MYAPP_MYAPP,C_COMPONENT1) ; self-—>myclass.comp2=f_new (CAT_MYAPP_MYAPP, C_COMPONENT2) ; self—->myclass.alloc=f_alloc (ALLOC_SIZE) ; } Again, note the use of the £_xxx functions, to guarantee a call to p_leave on failure. The destroy method function would be: VOID myclass_destroy(PR_MYCLASS *self) { if (self->myclass.alloc) { p_free(self—>myclass.alloc) self->myclass.alloc=NULL; /* not strictly necessary in this case */ } p_supersend2 (self,O_DESTROY) ; } Again, creating an instance of mycuass with: VOID *hand; hand=f£_newsend (CAT_MYAPP_MYAPP, C_MYCLASS, O_INIT) ; will result in total roll-back (and an error report) in the event of any failure. Note that it is good practice to zero the property corresponding to the handle of a resource when that resource is released. Although not strictly necessary in the above example, in general it is useful as it prevents an attempt being made to release a resource that does not exist. 10-4 10 ERROR HANDLING AND ERROR RECOVERY Using the CLEANUP list An application may create temporary resources whose handles are stored on the stack, rather than in an object's property. Alternatively, even if the handles are stored in property, the resources may not be created or destroyed at the same time as the 'owning' object, and the roll-back on failure to create the resource may not need an object to be destroyed. In such cases, roll-back can be performed by use of the cLzanup object that is present in every HWIM application. This object is described in The CLEANUP Class, in the OLIB Reference manual. Suppose that, the file afile.txt needs to be opened temporarily, together with the temporary creation of two allocated memory cells. On failure to create all of these three resources, any of them that have been created must be released and the error reported. Suitable code would be as follows: VOID CreateResources (VOID) { VOID *fcb; UBYTE *p1,p2; INT cleanl,clean2; f_open(&fcb, "AFILE.TXT") ; /* just leave on error */ cleanl=cl_add_iochan(fcb) ; /* add file handle to cleanup list */ pl=f_alloc(100); /* create first cell */ clean2=cl_add_alloc(pl1); /* add first cell to cleanup list */ p2=f_alloc(100); /* if this succeeds, all resources are created */ cl_remove (cleanl1) ; /* so we can remove both items... */ cl_remove (clean2) ; /* ... from the cleanup list */ /* processing that can not fail */ p_close(fcb); p_free(pl); p_free(p2); } Remember that resources on the cleanup list will be removed by system code if p_leave is called in the ao_run method of any active object. Interactions with system code Care should be taken when errors arise in application-specific code when system code also needs to perform error recovery. A typical case is during the initialisation of a dialog, since it is system code that controls the roll-back from any partially complete creation of dialog objects. In general this is not a problem since the application code does not normally need to take any specific action on an error condition. The application code can just call p_leave and leave the system code to perform all necessary error recovery. This might not be the case if, exceptionally, the application-specific code needs to perform some specific action on detection of an error, such as reporting the error in a non-standard way, or sending some form of notification to another process. In such a situation the application code will normally trap errors by calling p_enter and perform local error handling when this call returns an error. The preferred solution in such a case is to propagate the error to system code by calling p_1eave (with the same error number as was returned from the call to p_enter) after the local error handling is complete. This will allow system code to perform its own error recovery, including reporting the error in a standard way. If the error has already been reported by application code, the error can be propagated by calling p_leave (RUN_ACTIVE_CLEANUP_NONOTIFY). This will enable any required error recovery in system code but will disable the standard error reporting. Note that RuN_ACTIVE_CLEANUP_NoNoTIFY is defined in hwimman.g to be the value -1 (which is the same as the error number &_GEN_FAIL). A difficulty arises in the (fortunately rare) case where it is essential, for some reason, that the application code does not call p_1eave. If, in such a case, system-owned resources may need to be released. then the application's error recovery code should at least send the application manager a cL_CLEAN_LEVEL message, which will normally be sufficient. There is, however, no guarantee that this will always be totally successful: such a situation should be avoided if at all possible. 10-5 CHAPTER 11 FILE-BASED APPLICATIONS The general aspects of file-based applications for the Series 3 range of machines are described in the Communicating with the System Screen chapter of the Series 3 Programming Guide. This chapter assumes a basic familiarity with that material and concentrates on those aspects that are of significance to an object oriented application. The three main topics that are discussed are: e — start-up initialisation e opening and creating files e saving files To maintain consistency with the built-in applications, all file-based applications should obey the general guidelines for such applications. They should, for example, store their files in a suitable subdirectory and applications that use record-based files should write their records in a flash-friendly manner (see, for example, the Database Files chapter of the PLIB Reference manual). It is a general rule that an application must keep its current file open, even if it is not actually reading from or writing to the file. Start-up initialisation As is described in the Series 3 Programming Guide, the command line that is passed to a file-based application when it is started contains the name of a file to be opened or created, the default file extension and any 'alias' information. System initialisation code analyses the command line and writes the information that it contains to a number of standard locations. Thus, by the time the application receives a WS_DYN_INIT message, to perform application-specific initialisation, the data is set up as follows: e the full path name of the file to be opened or created is pointed to by the magic static DatUsedPathNamePtr e whether the file is to be created or opened is determined by the uByTE accessed by w_am->hwimman.command, which will contain either H_COMMAND_CREATE_FILE or H_COMMAND_OPEN_FILE (defined in hwimman.g) e the default file name extension for the application's files is pointed to by an item of the application manager's property and is accessed by the TExT pointer w_am->hwimman.defext e the alias information, if required, is accessed via the TEXT pointer w_am->hwimman.aliasinfo A convenient way of opening or creating the required file from within the application-specific initialisation code is to send the command manager a coM_FILE_CHANGE message of the form: p_send4 (w_ws->wserv.com, O_COM_FILE_CHANGE, w_am->hwimman.command, DatUsedPathNamePtr) ; The required behaviour of this method is described in the following section. 11-1 OBJECT ORIENTED PROGRAMMING GUIDE Opening and creating files A file-based application will normally have New and Open command menu options, to create a new file and to open an existing file. The corresponding command manager method functions would present suitable dialogs to specify a file name and any other relevant parameters. On successful completion of the dialog, the opening of an existing file or the creation of a new file could conveniently be performed by calling a replacement of the com_file_change method of the application's subclass of the comman command manager. A typical replacement would have the form indicated by the following code: GLDEF_D TEXT filename [P_FNAMESIZE] ; METHOD INT mycman_com_file_change(PR_MYCMAN *self, INT command, TEXT *pname) { SaveCurrentFile(self); p_scpy (&filename[0],pname) ; switch (command) { case H_COMMAND_CREATE_FILE: hEnsurePath (&filename[0]); CreateNewFile (self, &filename[0]); break; case H_COMMAND_OPEN_FILE: OpenExistingFile(self, &filename[0]); break; } p_send3 (w_am, O_AM_NEW_FILENAME, &filename[0]); return(0); /* there has been no call to p_leave */ } where SaveCurrentFile, CreateNewFile and openExistingFile represent application-specific code to perform the corresponding actions. The following points must be noted regarding this code: the method is also called by system code, under the protection of p_enter, and must return zero on successful completion. There is thus an implicit assumption that any failure within the method should result in p_1eave being called. Application code may, if desired, take advantage of this, to trap and explicitly handle errors, by sending the message by means of p_entersend. An application will normally have to handle a failure to open or create a file by attempting to reopen the previously open file. it is standard practice to call the utility function hEnsurePath at any point where it is possible that the directory specified by a file specification might not exist. Although this is not guaranteed to succeed, and does not report an error on failure, it reduces the likelihood that the following operation (in this case, the creation of a file) could fail, merely because a directory has not yet been created. whenever an application switches to a new file it must, on successful completion of the operation, send the application manager an aM_NEW_FILENAME message, passing a pointer to a permanent buffer containing the full file specification of the new file. System code sets patUsedPathnamePtr to point to this name, as required for correct operation of the System Screen. Note that, in the example, this buffer is, for clarity, implemented as static data. In a real application it would normally be part of the property of some object that remained in existence for the whole time that this file is the application's current file. In a simple application this object could be the command manager itself, but would normally be an object that represents the current file. A common alternative scheme is illustrated by the Record application (whose code is supplied and is discussed in the Application Design chapter). In this case, the application's command manager has, for example, a com_open_file method that does not use the com_file_change method. Instead, both methods call common application-specific code. 11-2 11 FILE-BASED APPLICATIONS Switchfiles messages As discussed in the Series 3 Programming Guide, the System Screen can, at any time, send a Switchfiles message to a file-based application. System code within the application converts such a message to a COM_FILE_CHANGE message, sent to the application's command manager. The receipt of this message may be handled in exactly the same way as described above. An application that is temporarily unable to process a Switchfiles message may set the magic static DatLocked to a non-zero value, clearing it when it is again able to process such a message. If opening a file takes an extended time, it would be sensible for an application to set Dat Locked for the duration of this operation. In this case, the application must ensure that patLocked is cleared on termination, even if the operation terminates on an error (which will generally result in p_leave being called). Saving files Saving a file is subject to many of the considerations already discussed in the previous section. An application will generally support at least Save and Save as menu options, with only the second of these requiring a dialog to select a file name. If, after saving the file with a specific name, the current file takes the new file name, this must be reported by sending the application manager an aM_NEW_FILENAME message, as described above. Saving the file may be an extended operation and should similarly be protected against Switchfiles messages by setting Dat Locked. Application termination On termination of a file-based application by means of an Exit menu option, the command manager's com_exit method should save any outstanding changes to the current file automatically, without any notification to the user. Should an error occur during any such saving, the application should come to the foreground (see below). The user should then be notified of the error and offered the option of cancelling the Exit. Once the file is successfully saved (or, on failure, the user has elected to terminate the application) the com_exit method should either supersend the com_zx1T message or, equivalently, call p_exit (0). The following code illustrates a possible replacement com_exit method: METHOD VOID mycman_com_exit (PR_MYCMAN *self) { INT error; error=p_enter2 (SaveChanges, self); if (error) { wClientPosition(0,0); /* come to foreground */ hErrorDialog(error,0); if (h2LineConfirm(-SYS_LOSING_CHANGES, -SYS_CONFIRM_CONTINUE) ) return; } p_supersend2 (self,O_COM_EXIT); } The code assumes that the application-specific function savechanges returns zero if no changes have been made or if saving the changed file was successful. Any p_leave caused by an error while saving the file is trapped by calling savechanges under the protection of p_enter and is explicitly reported (by use of the hErrorDialog utility function). Shutdown messages The System screen may, at any time, send an application a Shutdown message. System code within the application converts such a message to a coM_EXIT message, sent to the application's command manager. The receipt of this message may be handled in the same way as described above. An application may receive a Shutdown message while it is a background process. To ensure that the user can see and respond to any error notification, the process must therefore come to foreground, as mentioned above. Note that setting DatLocked disables Shutdown messages as well as Switchfiles messages. 11-3 CHAPTER 12 EDIT WINDOWS This chapter explains how to use the HWIM epwrtn class to create edit windows that (to name but a few features) e handle all standard cursor movement, selection, typing, and deletion keys e can be either single-line or multi-line e¢ automatically scroll vertically and/or horizontally, whenever required e¢ automatically word-wrap, whenever required e provide common editing functionality such as Copy, Insert, Bring, Evaluate, Find, and Replace. All the features of Hwif edit boxes, available through the Hwif nesxxx functions, are also available to HWIM programmers using EDWIN directly. (In fact, as can be confirmed by consulting the module ehwif-c in the optional \sibosdk\hwifsrc directory, the hEBxxx functions are just thin layers over calls to various methods of zpw1n.) However, programming directly at the Epw1n level opens up many additional possibilities. Some of these additional possibilities are: e more efficient handling of larger amounts of text e edit windows (and edit-like windows) which support “labels” in the left-margin e edit windows with tabs and variable tabstops e edit windows with multiple fonts and font styles. In fact, the main editing window in the Word Processor application built into the Series 3 is a subclass of EDWIN - as are the main windows of the Database and Program Editor applications. In order to achieve effects like this, programmers need to become acquainted with some of the component objects utilised by Epw1n - for example the EPpoc document object, the scrimc screen image object, and the scrLay screen layout object. Later sections of this chapter provide an introduction to these additional objects (which are all instances of classes in FORM). However, many of the aspects of Epwin can be accessed without any knowledge of the internal structure of the class. These aspects are described in the earlier sections of this chapter. Introduction to EDWIN Dialogs and edit windows contrasted The first use a programmer normally makes of EpwIn is by having an edit box in a dialog. (See the chapter Dialog Controls for information on how to program edit boxes in dialogs.) In this case, the initialisation of the edit window is taken care of by system code. System code likewise ensures that e keypresses are passed to the edit box at the right time e the edit box is always displayed in the correct “emphasis” state (ie with its cursor flashing or not, as the case may be, and with any select region highlighted when appropriate). 12-1 OBJECT ORIENTED PROGRAMMING GUIDE The responsibility of the programmer in this case is merely to e choose which initialisation flags to define e set text into the edit box when needed e sense the contents of the edit box, after the user has edited them. In contrast, when an application has an edit window outside a dialog box, the programmer has to accept the following additional responsibilities ¢ creating the edit window to start with - by filling in fields in the 1n_Epwrn data structure (and, optionally, in the 1n_Epw1n_x auxiliary data structure) e deciding when the edit window should receive keys - and passing these keys onto the edit window e deciding when the edit window should be emphasised. The programmer also has to draw any border required for the edit window; the Epwtn class has, itself, no notion of a border. The NOTES example program The examples in the first half of this chapter are mainly based on the example application Notes.app. The source code of this application is placed in the directory \sibosdk\notes if the optional OOPDEMO component of the SIBO C SDK is installed. This application may be recognised as an HWIM version of one of the Hwif example programs. It creates three different edit windows, as can be seen in the following screen shot: This is the title (single line editor) This is the main body of the note. Itis a multi-line editor, supporting clipboard functionality, and >the lines of text wrap automatically when required| Find: 274 In this screen shot, the emphasis is currently with the middle editor - as can be seen from the flashing cursor at the end of the third line, and also by the “margin cursor” in the left margin. These visual indications as to which editor has the emphasis will of course disappear when the emphasis (sometimes also called the “keyboard focus”) is moved elsewhere. The three editors are each drawn within their own border - which is provided by creating the editor within an instance of the Bw1n bordered window class. As suggested above, the EpwIn class itself does not make any call to any variant of gBorder: its concern is purely with the text inside the border. The screen shot is of this application running on a Series 3a. The application also runs on a Series 3, and in this case, there are fewer lines in the middle editor. In fact, as well as illustrating use of the Epw1n class, the application provides an example of how to write a fully-resizeable application, that can run on either the Series 3 or the Series 3a (exactly the same image program runs on the two different models, and would also run intelligently on machines with intermediate screen sizes). However, this feature of this application is incidental to the main theme of this chapter and will not be mentioned again. The “Hello World” program for edit windows Because Notes is a fairly well developed example program, the basic architecture of programming an edit window may to some extent be hidden, in its source code, by the lots of other concerns that have to be taken care of by that program. For this reason, installing the optional OOPDEMO component of the SDK also places the source code for a much simpler example, ehello.img, into the \sibosdk\ehello directory. This program simply draws a one-line edit window in the middle of the screen, and diverts most incoming keypresses to that window. The only exception is the ENTER keypress, which throws up a simple dialog confirming the text currently in the editor. The following two screen shots demonstrate, respectively, the main state of the application, and the confirmation dialog: 12-2 12 EDIT WINDOWS Hello hiorld Hello World Ehello Edit window Current text Hello world [Hello Wt The EHELLO category file The category file for the ehello application, ehello.cat, defines only three classes: IMAGE ehello EXTERNAL olib EXTERNAL hwim INCLUDE hwimman.g INCLUDE edwin.g CLASS ehwserv wserv { REPLACE ws_dyn_init } CLASS ehbwin bwin { REPLACE wn_init REPLACE wn_emphasise REPLACE wn_key REPLACE wn_draw PROPERTY { PR_EDWIN *edwin; } } CLASS ehdlg dlgbox { REPLACE dl_dyn_init } The bulk of what little code there is in the application resides in the EHBwIn class. As can seen, EHBWIN is a custom-subclass of swin, and owns a component EpwIn object. In technical terms - elaborated below - EHBWIN Is the “landlord” for the edit window in this application. 12-3 OBJECT ORIENTED PROGRAMMING GUIDE Initialisation code in EHELLO The code in main has the standard form: GLDEF_C VOID main (VOID) { IN_HWIMMAN app; IN_WSERV wserv; p_linklib(0); app. flags=FLG_APPMAN_RSCFILE|FLG_APPMAN_SRSCFILE|FLG_APPMAN_CLEAN; app.wserv_cat=p_getlibh (CAT_EHELLO_EHELLO) ; app.wserv_class=C_EHWSERV; wserv.com_cat=p_getlibh (CAT_EHELLO_HWIM) ; wserv.com_class=C_COMMAN; p_send4 (p_new (CAT_EHELLO_HWIM, C_HWIMMAN) ,O_AM_INIT, &app, &wserv) ; } After main, application code is next called in the ws_dyn_init method of the Exwserv class: METHOD VOID ehwserv_ws_dyn_init (PR_EHWSERV *self) { wsEnable(); self->wserv.cli=f_newsend (CAT_EHELLO_EHELLO, C_EHBWIN, O_WN_INIT) ; } Evidently, this creates and initialises the client window for the application - an instance of rHBwIN. In turn, the wn_init method of expwrtn is as follows: METHOD VOID ehbwin_wn_init (PR_EHBWIN *self) { W_WINDATA wd; IN_EDWIN_X initx; struct { IN_EDWIN e; TEXT rest [21]; } init; wd.extent .width=240-50; /* extent calculation presupposes SERIES 3 screen */ wd.extent.height=3+10+5; /* matches flags set for bwin below */ wd.extent.tl.x=0; wd.extent.tl.y=31; /* centred vertically */ p_send5 (self, O_WN_CONNECT, NULL, W_WIN_EXTENT, &wd) ; hLoadResBuf (EHSTR_INIT, &init.e.contents[0]); init.e.vulen=240-50-3-1-5-1; /* one extra pixel clearance each end */ init.e.maxlen=50; init.e.flags=IN_EDWIN_VULEN_PIXELS | IN_EDWIN_POSITION_SUPPLIED; initx.pos.x=3+1; initx.pos.y=3; self—>ehbwin. edwin=f_newsend (CAT_EHELLO_HWIM, C_EDWIN, O_WN_INIT, &init,self, &initx) ; self—>win.flags=IN_BWIN_SHADOW 2|IN BWIN_CUSHION; p_send3 (self, O_WN_EMPHASISE, TRUE) ; hiInitVis (self); } Without going into details at the moment, the basic form of this method can still be pointed out: ¢ connect the window to the Window Server (by sending se1f a wn_connect message) ¢ create and initialise the Epwrn component object e make this window tree visible. 12-4 12 EDIT WINDOWS Other code in EHELLO The other three methods of zEHBw1n mainly just delegate responsibility appropriately, between the Epw1N component and the pwrn superclass (see later for a fuller explanation of what is going on here): METHOD VOID ehbwin_wn_emphasise(PR_EHBWIN *self,INT flag) { p_supersend3 (self,O_WN_EMPHASISE, flag) ; p_send3 (self—>ehbwin.edwin, O_WN_EMPHASISE, flag) ; } METHOD VOID ehbwin_wn_draw(PR_EHBWIN *self) { p_supersend2 (self,O_WN_DRAW) ; p_send2 (self—>ehbwin. edwin, O_WN_DRAW) ; } METHOD VOID ehbwin_wn_key(PR_EHBWIN *self, INT keycode, INT mods) { SE_EDWIN sense; if (keycode!=W_KEY_RETURN) p_send4 (self—>ehbwin. edwin, O_WN_KEY, keycode, mods) ; else { p_send3 (self—>ehbwin.edwin, O_WN_SENSE, &sense) ; LaunchDialog(C_EHDLG, EHDLG, sense.buf) ; } } The utility routine LaunchDialog has the standard form LOCAL_C VOID LaunchDialog(INT class, INT resid,VOID *rbuf) { DL_DATA dld; did.id=resid; dld.rbuf=rbuf; dld.pdlg=NULL; hLaunchDial (CAT_EHELLO_EHELLO, class, &dld) ; } and, in turn, the dl1_dyn_init method of the zxpuc dialog box class merely sets the text of the edit window into a text window in the dialog: METHOD VOID ehdlg_dl_dyn_init (PR_DLGBOX *self) { hDlgSetText (1, self-—>dlgbox.rbuf) ; } Simple use of EDWIN This section explains: how to initialise an edit window, how to pass keys to it, how to set text into it and sense text out of it, how to pass wn_draw and wn_emphasise messages onto it, and the basic format of the text stored in it. Initialising an instance of EDWIN Often, the hardest aspect of incorporating an edit window in an application is initialising it correctly. Once the edit window has been set up properly, it handles most features automatically. In order for an edit window to be drawn on the screen, the following steps are required: 1. 2 an instance of the zpwin class (or a subclass thereof) has to be created a wn_init message must be sent to the object, with suitable parameters (see below) the window tree of which the editor is part must be made visible, usually via a call to the utility function hInitVis. 12-5 OBJECT ORIENTED PROGRAMMING GUIDE These steps may take place in code such as the following: IN_EDWIN init; IN_EDWIN_X initx; edwin=f_newsend (CAT_NOTES_HWIM, C_EDWIN, O_WN_INIT, &init, landlord, &initx) ; hiInitVis (landlord); The landlord of the edit window In the above code fragment, the variable 1andiord is the handle of an instance of (a subclass of) the HWIM wrn class which has connected to the Window Server. In the language of the Windows chapter in this manual (to which the reader is referred for background information on such concepts as “lodger windows’), the landlord object contains a notional wswin component. In other words, a wn_connect message has been sent to the 1andlora object, and the field 1andlord->win.id has been filled in as a result. Note that the value of 1andlord->win.ia must be filled in before the wn_init message is sent to the EDWIN object. Frequently, the 1andlord object is an instance of a (subclass of) the HWIM bwin class. Incidentally, whereas in the Notes example application, each of the three editors has its own unique landlord window, there is no general requirement for a given landlord window to contain only one editor. For example, if there are three editors in one dialog, that dialog window (an instance of digbox) is the common landlord of all three editors. As with all lodger windows, an zpw1n object requires to know e the top left offset within the landlord, to the rectangle occupied by the lodger e the width of the rectangle occupied e the height of this rectangle: \ _ top left offset LANDLORD In the case of EDWIN: e the height is worked out, inside the wn_init method, from a knowledge of the number of lines that are to be visible at one time (this defaults to one), the font in which the text in the editor is to be displayed, and the vertical leading to be applied with this font e the width is also worked out, by one of a variety of different means, depending on which flags are set on initialisation e the top-left offset always has to be supplied explicitly, in pixels - although as is explained below, it is possible to defer providing this value until later in the overal initialisation process. The IN_EDWIN and IN_EDWIN_X data structs The 1n_EDWIN and IN_EDWIN_x data structs are defined as follows in edwin.cl: typedef struct { UWORD vulen; viewing length or width UWORD flags; autoselect etc UWORD maxlen; maximum number of characters allowed TEXT contents[1]; rest of initial contents follows in line } IN_EDWIN; 12-6 12 EDIT WINDOWS typedef struct { WORD total; WORD top; } EDWIN_LEADING; typedef struct { UWORD vislines; number of lines visible in window P_POINT pos; top left offset relative to landlord WORD font; font id UWORD style; font style EDWIN_LEADING leading; total and top-only vertical leadings VOID *doc; document object to use VOID *clip; possible clipboard to use } IN_EDWIN_X; never used in edwins in dialogs Whilst the address of an IN_EDWIN struct must always be passed to the wn_init method of Epwtn, it is optional whether to pass the address of an IN_EDwIn_x struct. This is because, depending on various bit values that can be set in the flags field in the 1n_Epwin struct, default values are assumed for the fields that can be supplied in the 1n_Epw1n_x struct. More precisely: e unless IN_EDWIN_VISLINES_SUPPLIED is set in flags, the value of vislines is taken as 1 e the value of pos is ignored unless IN_EDWIN_POSITION_SUPPLIED Is Set (if this bit is clear, the value of pos must be supplied by a subsequent call to 1g_set_id_pos - see below) e unless IN_EDWIN_FONT_SUPPLIED is Set, the value of ws_rFonT_systTEM is assumed for font, and the value Gc_sTy_NoRMAL is assumed for style e unless IN_EDWIN_LEADING_SUPPLIED is Set, values of 2 and 1 are assumed for leading.total and leading.top e unless IN_EDWIN_DOC_SUPPLIED Is set, any supplied value of doc is ignored, and the edit window automatically creates a suitable document object (see later in this chapter for further discussion of document objects) e the value of clip is ignored unless IN_EDWIN_CLIPBOARD is Set (specifying that the editor is to support clipboard functionality). As can be appreciated, edit boxes in dialogs are never passed an address of an IN_EDwIn_x struct; all the corresponding flags are clear. This reflects the fact that the Epw1n resource struct, defined in hwim.rh, corresponds just to the 1N_EDwtIn struct, and does not have any fields matching the 1n_EDWIN_x struct. The Ig_set_id_pos method In case it is impossible (or particularly inconvenient) to give the value of pos, the top-left offset of the editor within its landlord, at the time when the wn_init method has to be called (this is the case when editors are created within dialogs, as the dimensions of a dialog cannot be determined until all the items in the dialog have been initialised), this can be given later, by sending the editor an 1g_set_id_pos message as follows: P_POINT pos; p_send5 (edwin, O_LG_SET_ID_POS, landlord->win.id, &pos, width) ; where width is the width of the region the editor is to display itself upon. Other edit window initialisation flags In addition to the six IN_EDwIN_xxx flags mentioned above, there are four other groups of possible values that the flags field in the 1n_EpwIn struct can contain: e values governing the interpretation of the vulen field, if set e values influencing the behaviour of the wn_key method of the editor (ie influencing the way the editor responds to various keypresses passed to it) e values governing the initial cursor position and initial highlighting (more precisely, these control the cursor position and the highlighting following initialisation and following any call to wn_set to set text into the editor) e miscellaneous other values governing attributes the edit window may or may not possess. 12-7 OBJECT ORIENTED PROGRAMMING GUIDE In detail, these ten additional values are as follows: IN_EDWIN_VULEN_CHARACTERS IN_EDWIN_VULEN_PIXELS IN_EDWIN_ACCEPT_TABS IN_EDWIN_ACCEPT_SOFT_HYPHENS IN_EDWIN_DIALLABLE IN_EDWIN_NO_AUTOSELECT IN_EDWIN_AUTO_CUR_END IN_EDWIN_LEFT_CURSOR IN_EDWIN_TEXT_SEGMENTED IN_EDWIN_PAGINATABLE if this is set in flags, the value of vulen is multiplied by the max_width value for the font to be used by the editor, and the result is used for the width of the editor if this is set in flags, the value of vulen is taken directly as the width to be used by the editor (if neither this flag or the previous one is set, then the width is set to the product of the max_width value of the font and the maxlen value from the In_EDwrIN struct) unless this is set, the wn_key method of the editor will reject the TAB key unless this is set, the wn_key method will reject the CONTROL-hyphen key (which would otherwise insert a so- called “soft” hyphen - which would be invisible in most cases) unless this is set, the wn_key method will reject the SHIFT- DIAL and the CONTROL-SHIFT-DIAL keys (if this is set, the wn_key method will, respectively, insert an 0x05 telephone symbol, or run the system ‘Append country markup’ dialog) if this is set, the initial cursor position is set to the start of the text, and there is no initial select region this has the same effect as the previous flag, except that the initial cursor position is set to the end of the text (if neither this flag or the preceding one is set, the cursor is placed at the end of the text and the entirety of the text is selected) if this is set, a triangular pointing cursor is displayed down a left-hand margin (as in the middle of the three editors in the Notes example application), using the same font as the main application but with all bits of the font style cleared apart from the c_sty_pousBLe bit (if set) if set, this means that the editor will create an EPSEG document object whenever needed (as opposed to the EPFLAT Object that is created by default - see later in this chapter for more discussion of document objects) (for advanced use only - see later). A note on the CONTENTS field in the IN EDWIN struct Evidently, the 1n_zpwtn struct only defines one element in a possible contents [] array. In order to specify initial contents different from just the null string ("" - specified when init.contents [0] 1S Zero), the application needs to make a declaration such as struct { IN_EDWIN e; TEXT rest [LENGTH-1+1]; } dna ts init.e.maxlen=...; p_scpy (&init.e.contents[0],pInitString) ; edwin=f_newsend(C_APP_HWIM, C_EDWIN, O_WN_INIT, &init,...); Note that the array contents[] is ignored altogether if the flag IN_EDWIN_DOC_SUPPLIED Is Set in flags. Apart from this, sinit..contents[0] is always interpreted as a pointer to a zero terminated string. 12-8 12 EDIT WINDOWS Alternative means of defining initial text for an editor (these methods can circumvent the limitation to setting text that is only one paragraph long) include: e using the wn_set or other methods of the Epw1n class, possibly repeatedly, before the editor is made visible e setting the text directly into the document object. Values of special characters in the text The following values are used to represent special characters within an editor: 7 (SCRLAY_SYM_HARD_HYPHEN) a minus sign (sometimes called a “nonbreaking hyphen’) that does not count as a word delimiter - normally entered at the keyboard using SHIFT-CONTROL-hyphen 8 (SCRLAY_SYM_SOFT_HYPHEN) a soft (sometimes called an “optional”) hyphen - normally invisible, but will transform into a visible hyphen to allow a word to wrap over two lines at the point, if required - normally entered at the keyboard using CONTROL-hyphen 15 (SCRLAY_SYM_HARD_SPACE) a space that does not count as a word delimiter - normally entered at the keyboard using SHIFT-CONTROL-SPACE 0 ("\0') a paragraph end - normally entered at the keyboard using ENTER 10 ("\n') a forced line break - normally entered at the keyboard using SHIFT-ENTER 9 ("\t') a tab character - normally entered at the keyboard using the TAB key 5 (WS_SYMBOL_PHONE) a telephone character - normally entered at the keyboard using SHIFT-DIAL. Apart from the above values, characters with values less than 32 should in general not be set into edit windows. Note in particular that characters with values less than 4 are all treated, by low-lying code (in the OLIB library) as paragraph delimiters - with potentially bizarre results, given that the formatting code in FORM only recognises the character value 0 as a paragraph delimiter. Note that no characters are written to the buffer to denote the location of line ends caused merely by word- wrap. These locations (sometimes called “soft carriage returns”) have no fundamental significance: e they change whenever the window size changes (eg when the status window is altered) or the display font is “zoomed” e they are calculated dynamically when needed, and are held in a different part of property of the edit window (actually in the scriay screen layout component). A note on the MAXLEN field in the IN_EDWIN struct For clarity, it should be emphasised that the maxien field in the 1n_zpwtn data struct specifies the maximum number of characters the editor can contain, not counting any final terminating character. For example, an editor with maxien set to 6 could contain the string "abcdef" (where the editor would in fact also store a terminating zero at the end of the string. However, if the editor was multi-line, it could not store the text "ab\ocde£" (representing a paragraph of two characters followed by one of four characters): that would require a maxlen of (at least) 7. The wn_sense method For many uses of edit windows, the following model is sufficient to explain the storage of text within the editor: the text is stored as a zero terminated string, and the wn_sense method of Epw1n provides access to this buffer, as follows: typedef struct { TEXT *buf; UWORD len; } SE_EDWIN; 12-9 OBJECT ORIENTED PROGRAMMING GUIDE SE_EDWIN sense; p_send3 (edwin, O_WN_SENSE, &sense) ; p_scpy (&store[0],sense.buf) ; Note however that the buffer whose address (sense .buf) 1s obtained in this way must always be regarded as read only. In order to change the contents of this buffer, the various methods of Epw1n (or methods of components of EpwrNn) have to be used. Second, note that the buffer used by the editor may move as more text is added. This is because the buffer is resized according to how much text it contains. Therefore, it would be a grave mistake to hold onto the address of this buffer, and assume that this will still be valid after more text could have been added into the editor. Third, as mentioned above, paragraph ends (in multi-line editors) are internally represented as zeros. However, code such as p_send3 (edwin, O_WN_SENSE, &sense) ; p_scpy (&store[0],sense.buf) ; will only succeed in copying out text as far as the first embedded zero. For this reason, the Notes example application essentially uses the following code instead: SE_EDWIN sense; p_send3 (edwin, O_WN_SENSE, &sense) ; p_bcpy (&store[0],sense.buf,sense.len) ; Finally, note that this still assumes that the text is stored in a flat buffer. This applies by default, but the initialisation flag IN_EDWIN_TEXT_SEGMENTED can be used to specify that the text is stored in a segmented buffer. (Roughly speaking, the larger the quantity of the text, the more pressing the need to store it in a segmented buffer - to cut down on the amount of data that needs to be shuffled along each time a single character is typed into the middle of the document.) If the text is stored segmented, the result of the wn_sense method is undefined, and other means are required to sense the contents of the editor. The wn_set method The wn_set method can be used to completely replace the contents of an EDwin object. It uses the same sE_EDWIN struct as does the wn_sense method: SE_EDWIN set; p_send3 (edwin, O_WN_SET, &set) ; After this method, the contents of the editor are the set .1en characters in the buffer pointed to by set .buf. (The operation of the method involves copying these characters into the internal storage buffers of the edit window.) All previous contents are discarded. The highlight and the cursor position are adjusted according to the IN_EDWIN_AUTO_CUR_END and IN_EDWIN_NO_AUTOSELECT flags specified on initialisation. The wn_key method The way to pass keypresses onto an edit window is to send a wn_key message as follows p_send4 (edwin, O_WN_KEY, keycode, modifiers) ; The method actually returns one of the following two values: @ WN_KEY_CHANGED - the contents of the edit window changed as a result of the keypress @ WN_KEY_NO_CHANGE (which is the same as FaLsE) - the contents of the edit window did not change as a result of the keypress. In most cases, however, this value will be ignored by application code that passes the key to the window. Something else that is automatically suitable in most uses of EpwIN is the behaviour of the wn_key method when the keypress passed to the editor ¢ causes an out-of-memory error, or e would cause the maximum capacity of the editor to be exceeded. See the discussion of the ew_leave method below for more information on how EpwrIn copes with these two cases. 12 - 10 12 EDIT WINDOWS The wn_emphasise method Although the need for the wn_key method is clear, the need for the wn_emphasise method may be less so. However, as mentioned several times in this manual, there is a definite need for applications to track the “emphasis” as it moves around an application: e into the menu bar and out again e into the Help subsystem and out again, or into dialogs and out again e around various parts of the main viewing screen of the application. The parameters to the wn_emphasise method of Epw1n are the same as those for any other window class within HWIM: p_send3 (edwin, O_WN_EMPHASISE, flag) ; where flag is either rausz, to indicate that emphasis is moving away from the edit window, or TRuz, to indicate that emphasis is moving to the edit window. The wn_draw method The wn_draw method shares with wn_key and wn_emphasise the feature that landlord windows for edit windows invariably have to pass these messages onto their EpwIN components. As far as EDWIN is concerned, there are no additional parameters to the wn_draw method (in particular, the entire visible portion of the editor has to be redrawn every time). Additional EDWIN methods This section explains some additional methods of Epw1n: e methods for inserting text, finding text, and replacing text e the copy and insert (“paste”) clipboard methods e =the ew_evaluate method e additional setting and sensing methods e how to detect if the contents of an edit box has been changed e edit boxes set to be “read only” e the ew_leave method for notifying run-time errors. The ew_insert method The following code will result in the bien characters at *buf being inserted into the editor with handle edwin: p_send4 (edwin, O_EW_INSERT, buf,blen) ; The characters are inserted at the cursor position (any selection being cancelled first), and the cursor is advanced to the end of the characters inserted. The ew_insert method calls ew_leave if any error occurs. The ew_find method It is possible to request an editor to search for given text within itself. The following code is used val=p_send4 (edwin, O_EW_FIND, pstr, flags) ; where pstr points to a zero-terminated string of the text to match, and possible bit values in f1ags are: @ EWF_BACKWaARDs to search backwards from the cursor position (the default is to search forwards from the cursor position) @ EWF_CASESENS to make the search case sensitive (the default is for the search to be case insensitive). 12-11 OBJECT ORIENTED PROGRAMMING GUIDE The ew_find method returns rause if no match was found, and otherwise TRUE - in which case the matched text is highlighted as the new select region. The ew_find method is intelligent enough to code with repeated calls to ew_find without finding the same text repeatedly. Note that matches across paragraph boundaries are not possible (this is consistent with the search string being zero terminated: it cannot contain an embedded zero). The ew_replace method The ew_replace method is in some ways similar to the ew_insert method, but it is designed primarily to implement a ‘Replace’ menu command (in conjunction with the ew_find method, which is designed to implement a ‘Find’ menu command). Whereas ew_insert cancels any selection before inserting the specified characters, ew_replace Starts by deleting any selection. Another difference is that ew_insert takes its insertion text in the (buf, 1en) form, whereas ew_replace expects a zero terminated string. Finally, whereas ew_insert always leaves cursor at the bottom end of the selection region consisting of the text just inserted, ew_replace allows the cursor to be positioned at either end of this selection - in order to support repeated forward or backward text replacement, without entering an infinite recursion: p_send4 (edwin, O_EW_REPLACE, replace, backwards) ; The replacement string is specified by the zero-terminated string *replace, and the flag backwards specifies whether the cursor should be placed at the top end of the selection (if backwards 1S TRUE) or at the bottom end. The method attempts to insert the replacement text first, before deleting the existing selection (if any), so that any out-of-memory error is handled automatically without the loss of any text. The ew_replace method calls ew_leave if any error occurs. The ew_replace_clip method The ew_replace_clip method is provided to implement a ‘Copy text’ menu command, in conjunction with the ew_paste_clip method (discussed next), which is provided to implement an ‘Insert text’ menu command (sometimes called a ‘Paste’ menu command). Both these methods presuppose that the flag 1N_EDWIN_CLIPBOARD was set on initialisation - otherwise the editor will panic (panic 55). Note however that the 1n_zpwIn_cLIpBoarp flag should not be set unnecessarily (ie if no calls to ew_replace_clip Of ew_paste_clip are to be made), since this entails an additional memory overhead. If IN_EDWIN_CLIPBOARD 1s Set on the initialisation of the editor, the value of the clip field in the IN_EDWIN_X initialisation struct becomes significant: e if this is non-nuLL, it is taken as the handle of a suitable clipboard object to be used by the editor e otherwise, the editor creates a clipboard object for its own use, which is in fact an instance of the OLIB eprtat class (unless the flag IN_EDWIN_TEXT_SEGMENTED was set on initialisation, in which case an instance of the OLIB epsze class is used). In most cases, setting clip to nuLL will be perfectly sufficient. The main exception is if the clipboard object has to persist beyond the lifetime of the editor (or if the clipboard is to be shared between two editors that both exist at the same time). Note here that the destroy method of zpwin sends a destroy message in turn to any clipboard object that the editor itself created - whereas clipboard objects specified via a non-NnuLL value of the clip field of the 1n_Epw1n_x struct passed at initialisation do not get destroyed in this way (that responsibility falls to the owner of the editor). An application that wishes to create a clipboard for external purposes can use code such as clip=f_newsend (CAT_APP_OLIB, C_LEPFLAT,O_EP_INIT,maxlentl) ; where the reason why 1 is added to maxien (the value used to initialise the editor itself) is that space has to be reserved, in the document object, for the final paragraph delimiter as well. In general, any realisable subclass of the OLIB class epRoot can be used as the clipboard. Note that there is no Epwin method corresponding directly to any ‘Cut’ menu command. There is of course no such menu command on the ROM-resident Series 3 applications; the way that text is “cut” into the clipboard is that the user highlights the text and simply presses DELETE. This keypress is received by the Epwin code in the wn_key method, and this is the location of code to copy the deleted text into the clipboard (if present). 12-12 12 EDIT WINDOWS Having said all that, in practice use of the ew_replace_clip method is simplicity itself. For example, the corresponding code in the Notes example application is just METHOD VOID nocomman_ncoe_copy (PR_NOCOMMAN *self) { CheckEditing(self) ; if (! (p_send2 (DatApp3, O_EW_REPLACE_CLIP) )) hInfoPrint (NOSTR_NO_TEXT_COPIED) ; else hinfoPrint (NOSTR_TEXT_COPIED) ; } In this example, patapp3 holds the handle of the editor. The ew_replace_clip method in fact returns the length of the current selection - which is therefore zero if there is nothing to copy. The ew_paste_clip method Use of the ew_paste_clip method is just as simple. The corresponding code in the Notes application is METHOD VOID nocomman_ncoe_insert (PR_NOCOMMAN *self) { CheckEditing(self) ; if (! (p_send2 (DatApp3,O_EW_PASTE_CLIP) ) ) hinfoPrint (NOSTR_NO_TEXT_INSERTED) ; } The ew_paste_clip method returns the length of the text in the clipboard - which is zero if there is nothing to insert. The ew_evaluate method The ew_evaluate method can usefully be discussed alongside ew_replace_clip and ew_paste_clip because e an ‘Evaluate’ menu command would normally be found alongside those for “Copy text’ and “Insert text’ e the usage of this method is, in practice, equally as straightforward (although, in all three cases, a great deal happens behind the scenes). The ew_evaluate method can, however, be used without the editor having been initialised with IN_EDWIN_CLIPBOARD. The code in the Notes application that implements the ‘Evaluate’ menu command there is METHOD VOID nocomman_ncoe_evaluate (PR_NOCOMMAN *self) { CheckEditing(self) ; p_send2 (DatApp3, O_EW_EVALUATE) ; } Note that whereas it is the responsibility of the application to detect “errors” (such as Nothing to insert) in the case of ew_replace_clip and ew_paste_clip, syntactical errors within the string to be evaluated are signalled by code within the ew_evaluate method - with the cursor being positioned to the error and an appropriate hInfoPrint being executed. The ew_set method For some purposes, the additional control provided by the ew_set method (as compared to the wn_set method) may be helpful: typedef struct { UWORD flags; SE_EDWIN txt; UWORD cursor; cursor (moving point of select) UWORD anchor; anchor point (fixed end of select) } SET_EDWIN; SET_EDWIN set; p_send3 (edwin, O_EW_SET, &set) ; 12 - 13 OBJECT ORIENTED PROGRAMMING GUIDE As well as containing an szE_EpwIN struct within itself, the seT_zpwin struct evidently also allows the cursor and select region to be defined more precisely. This is governed by the possible values in f1ags: e if sET_EDWIN_EMPTY is Set, this is merely a convenient way to empty the contents of the editor e if SET_EDWIN_TXxT Is set, a call to the wn_set method of the editor is effectively made e if SET_EDWIN_SEL_ALL is set, the entire contents of the editor are selected e if SET_EDWIN_CUR_END is Set, the cursor is set to the end of the document e if SET_EDWIN_ANCHoR is Set, the anchor point of the selection (the non-moving end) is set to document offset set .anchor e if SET_EDWIN_cuRSOR is set, the cursor (which is also the moving end of the selection, if one exists) is set to document offset set. cursor. The ew_sense method Whereas the ew_set method allows more control, compared to wn_set, over features of an edit window that can be set, the ew_sense allows additional editing details (namely, the two ends of the select region) to be sensed: typedef struct { UWORD cursor; cursor (moving point of select) UWORD anchor; anchor point (may equal cursor) } SENSE_EDWIN; SENSE_EDWIN sense; p_send3 (edwin, O_WN_SENSE, &sense) ; In fact, this method always returns the top end of the select region (if any) in sense. anchor, and the bottom end in sense.cursor. Ifthe length of the select region is presently zero, both the two fields in the SENSE_EDWIN return the cursor position. The concept of document offset Both the ew_set and ew_sense methods make use of the concept of document offset. In fact, this is a fundamental notion for the epwrn class as a whole. The idea is that although features like line number and line offset vary according to the zoom state and status window setting, the document offset of a given location with the editor remains constant, regardless of how the contents are viewed. For example, suppose an editor has its width decreased and its display font zoomed larger, causing the word-wrap to change. Then the possible cursor location just in front of, say, the ‘f’ of “four” i ae eeee Cae | One two three four | ,One two | | five six | three four | | | five six is on the first line in one case but on the second line in the second case. The line offset of this location also changes, but the document offset remains constant: the location has document offset 14 in both cases. Allowed values of document offset If there are n characters in a document - not counting the final terminating character zero - then there are precisely n+1 allowed character offsets, ranging in value from 0 to n. Note that the cursor cannot be positioned beyond the final terminator (ie at document offset n+1). Nor in fact can the cursor ever be positioned beyond the final character on any line - it always repositions automatically in these cases to the very beginning of the following line. 12-14 12 EDIT WINDOWS The EDWIN.CHANGE property Although many parts of the property of zpw1n should be regarded as private, the change field is open to direct read-write manipulation by application code. For example, the following routine in the Notes application checks whether any change has been made to the editor in a given window, and if so e senses the new text, and records it e resets the value of edwin. change tO FALSE: LOCAL_C VOID RecordChange(PR_NOWIN *win, LENBUF *1b) { PR_EDWIN *edwin; SE_EDWIN sense; edwin=win->nowin.edwin; if (! (edwin->edwin.change) ) return; p_send3 (edwin, O_WN_SENSE, &sense) ; lb->buf=f_realloc(lb->buf,sense.len) ; lb->len=sense.len; p_bcpy (1lb->buf, sense. buf, sense.len) ; edwin->edwin. change=FALSE; } In fact there are two different ew_cuancE_xxx bit flags defined in edwin.cl: EW_CHANGE_SINCE_SAVED and EW_CHANGE_SINCE_PAGINATE, with the values 0x01 and 0x02 respectively. Further, whenever any code inside a method of epw1n changes the contents of the editor, al/ the bits (oxf£££, symbolically rw_cHancE) are set iN edwin.change. This allows an application greater control over monitoring the extent to which changes may or may not have taken place. “Read-only” edit boxes and the ew_readonly method Before epw1n code ever allows a change to be made, by the user, to the contents of the edit box, the value of the pR_EDWIN_READONLY bit in the edwin. flags field in property is tested. If this is set, by default a beep is emitted and the thread of execution is terminated - as can be seen from the following utility routine called frequently internal to Epw1n code: LOCAL_C VOID CheckNotReadOnly(PR_EDWIN *self) { if (self->edwin.flags&PR_EDWIN_READONLY && p_send2(self,O_EW_READONLY) ) { hBeep (); p_leave (RUN_ACTIVE_USED) ; } } Note that there is no formal mechanism whereby the pR_EDWIN_READONLY bit in edwin. flags can be set or cleared, other than by the application directly manipulating this bit. (Care must be taken, however, to leave all other bits in this field well alone.) Note further that this bit is cleared at the end of edwin_wn_init, so that application code should only ever attempt to set it after the return of the wn_init message to the editor. The ew_readonly method is declared in edwin.cl to be equal to p_true - in other words, it always returns TRUE. An application can subclass this to make some additional tests. For example, code shared between the Program Editor and Word Processor applications supplies the following replacement: METHOD INT oplwin_ew_readonly(PR_OPLWIN *self) { if (IsOutlined() ) { KillOutline (self); return (FALSE) ; } return (TRUE) ; } where the effect is to cancel any outline state before allowing any change to take place in the document, whereas other reasons for the document being read-only continue to result in TRUE being returned. 12-15 OBJECT ORIENTED PROGRAMMING GUIDE The ew_leave method Whenever there is a run-time failure following user action that causes characters to be added to the document (eg inside the wn_key, ew_insert, OF ew_paste_clip methods), a call is made to ew_leave. For example, the code for edwin_ew_paste_clip is as follows: METHOD INT edwin_ew_paste_clip(PR_EDWIN *self) { UWORD len; INT err; CheckNotReadOnly (self); len=p_send2 (self->edwin.clip,O_EP_SENSE_LEN) ; if (len) { if ((err=p_entersend4 (self-—>edwin.doc,O_EP_PASTE, &self—>edwin.cpos, self->edwin.clip) ) !=0) p_send3 (self, O_EW_LEAVE, err) ; self—>edwin.clen+=len; EdwinFwdChange (self) ; SetEdwinSelect (self,self—>edwin.cpos-len, len) ; } return(len); } The significance of this is so that one particular error message can be trapped - namely the “Overflow” error, E_GEN_OVER, that document objects such as EPFLaT generate when an attempt is made to insert more than the allowed maximum number of characters. Code in the ew_leave method translates this particular error into something more meaningful to the user: METHOD VOID edwin_ew_leave(PR_EDWIN *self, INT err) { if (err==E_GEN_OVER) { hBeep () ; if (self->edwin.flags&PR_EDWIN_NOTIFY_OVERFLOW) hInfoPrint (-SYS_EDIT_NCHARS) ; err=RUN_ACTIVE_CLEANUP_NONOTIFY; } f_leave(err); } (If desired, an application could modify this behaviour by subclassing this method.) In English, the text for the system resource -sys_EDIT_NCcHaRs is “Maximum number of characters reached”. As can be seen, this message is displayed only if the pR_EDWIN_NOTIFY_OVERFLOw flag is set in edwin.flags. By default, this is set for all editors which set either of the 1n_EDwIN_vULEN_xxx flags on initialisation. Controlling the layout and formatting The mechanisms described in this section require various measures of knowledge of the scrimc and SCRLAY components of Epwin. In fact, on the whole, these mechanisms are not methods of Epwrn itself, but involve sending messages to the scrime and/or scriay objects. Amongst other things, these mechanisms allow: e changes in which kinds of hidden symbols are displayed e — setting the width of the cursor (eg to turn it off completely - if desired) e changes in the font used to display the text e changes in the size of window e changes in the margins applying to paragraphs (including the ability to disable word-wrapping) e setting tabstops. 12 - 16 12 EDIT WINDOWS An introduction to SCRLAY As mentioned already, the character content of an edit window is stored within a so-called “document object”, which is an instance of a subclass of the FORM eppoc class. The document object can, for the most part, be thought of as simply an extended buffer (possibly segmented), containing all the characters in the document, together with paragraph delimiters, tab characters, and forced line breaks stored in line. By contrast, the document object has no knowledge of e tab stop positions e whether various special characters (spaces, tabs, carriage returns, etc) are shown or hidden e left, right, and first-line margins applying to paragraphs e interline and interparagraph spacing e the “keep with next’, “keep together’, and “start new page” attributes of paragraphs e the fonts to be used to display (and/or print) characters. These attributes of an editor are supervised by a scruay (“screen layout”) sub-component. More precisely, from the point of view of scriay, screen layout consists of e a linked list of paragraphs, each of which consist of e a linked list of lines, each of which consists of e a linked list of so-called thoxes. There are three reasons why a line can be split into different tboxes: ¢ physical segmentation - the characters making up the line happen to be stored in two different physical buffers at that point (this can only ever apply, for editors, if the flag IN_EDWIN_TEXT_SEGMENTED is Set on initialisation) ¢ — stylistic segmentation - where there is a change of font, font-style, or character visibility ¢ enforced segmentation - where a limit of 236 characters per tbox is applied, to simplify visual display of the text of a tbox on the screen by means of the Window Server function gPrintBoxText (the value 236 is symbolically known as ws_PRINT_BOX_TEXT_MAX_LEN). SCRLAY structure definitions The screen layout is held in property of scriay using the following structures (defined in scrlay.cl); typedef struct que_tbox { struct scrlay_tbox *next; struct scrlay_tbox *prev; } QUE_TBOX; typedef struct scrlay_tbox { QUE_TBOX hd; WORD width; /* width of box in pixels */ UWORD tlen; /* number of doc positions, with mask info */ } SCRLAY_TBOX; typedef struct que_line { struct scrlay_line *next; struct scrlay_line *prev; } QUE_LINE; 12-17 OBJECT ORIENTED PROGRAMMING GUIDE typedef struct scrlay_line { QUE_LINE hd; QUE_TBOX tboxs; WORD indent; x pixel position of left of lst tbox UWORD len; number of addressible content positions UBYTE islast; TRUE if last line in paragraph UBYTE new_page; TRUE if start of page } SCRLAY_LINE; typedef struct que_para { struct scrlay_para *next; struct scrlay_para *prev; } QUE_PARA; typedef struct scrlay_para { QUE_PARA hd; QUE_LINE lines; } SCRLAY_PARA; An important efficiency measure is that scriay only contains the layout information for the visible portion of the data - ie the data currently visible on the screen (though it turns out simpler also to maintain the data for any portion of the first visible paragraph that is off the top of the screen). Thus scriay property contains the document offset of the top of the layout structure it currently possesses. From this, it is possible to calculate the document offset of the start of any line, or the start of any tbox, within the screen layout. As well as containing the screen layout data, scriay contains the logic for re-calculating screen layout, according to changes in, for example, document content or paragraph styling. Finally - and this is of particular concern to users of edit boxes - scrLay property contains a scRLAY_STYLE “global style definition” data structure: typedef struct { UWORD fid; UWORD style; UWORD height; } SCRLAY_FONT; typedef struct { font id for wserv or typeface for printer font style (eg bold) height of printer font in decipoints UWORD left; Left margin UWORD right; Right margin UWORD indent; Left margin of first line in para UWORD align; Alignment (left, right, centre or justified) } SCRLAY_MARGINS; typedef struct { UWORD line; UWORD above; UWORD below; UWORD flags; } SCRLAY_SPACING; typedef struct { UWORD x; UWORD type; } SCRLAY_TABSTOP typedef struct { UWORD ntab; SCRLAY_TABSTOP tab[SCRLAY_NTABS_MAX]; } SCRLAY_TABS; 12 - 18 Space between paragraph lines Space above paragraph Space below paragraph Keep together/next and start new page tab position tab type (left, right, centre or repeated) number of tabs 12 EDIT WINDOWS typedef struct { SCRLAY_MARGINS *margins; Paragraph margins SCRLAY_TABS *tabs; Paragraph tabs SCRLAY_SPACING *spacing; Paragraph spacing } SCRLAY_PDATA; typedef struct { UBYTE options; Layout options UBYTE printer; TRUE if printer layout SCRLAY_PDATA pd; Global margins, tabs, spacing SCRLAY_FONT *font; Global font id and style UBYTE *fwtab; Global font table or NULL UWORD scrpwidth; Width of screen in printer units SCRLAY_FONT *sfont; Global screen font id and style } SCRLAY_STYLE; Example: changing visibility of special characters The following code shows an example of interaction with the scrLay_styLe data structure inside scriay. The code either hides or shows specified so-called “special characters”: LOCAL_C VOID ShowSymbols(PR_EDWIN *edwin, INT symbols) { SCRLAY_STYLE style; p_send3 (edwin->edwin.scrlay,O_SL_SENSE, &style); style.options=symbols; p_send3 (edwin->edwin.scrlay,O_SL_SET, &éstyle) ; p_send3 (edwin->edwin.scrimg,O_SI_STYLE_CHANGED, SCRIMG_STCHNG_DOC) ; } The basic pattern here is: sense the global layout data, make the required changes, set the new values, and then notify scrime of the change (see later for further discussion of scr1IMc). The given routine is complete in its own right, but for it to be used, the allowed values of the options field in SCRLAY_STYLE property have to be known. These can be found out from scrlay.cl: SCRLAY_SHOW_TABS 0x01 SCRLAY_SHOW_SPACES 0x02 SCRLAY_SHOW_CRS 0x04 SCRLAY_SHOW_HYPHENS 0x08 Show optional hyphens SCRLAY_SHOW_LFS Ox10 SCRLAY_WIDOW_ORPHAN 0x20 Set to enable widow & orphan suppression Default values of SCRLAY_STYLE in edit windows Note that many of the fields in scrLay_styLz are stored by indirection. By default, the indirected data exists in suitable slots within EpwIn property - which contains a SCRLAY_MARGINS margins field anda SCRLAY_FONT font field. The following extract from edwin_wn_init shows how this works: SCRLAY_STYLE style; SCRLAY_DOC doc; style.options=SCRLAY_SHOW_TABS; style.printer=FALSE; style.pd.margins=(&self-—>edwin.margins) ; style.sfont=style.font=(&self->edwin.font) ; style.pd.tabs=(SCRLAY_TABS *) (&self—->edwin.font.height) ; /* ntab = 0 */ style. fwtab=NULL; style.scrpwidth=0; p_send3 (self,O_EW_INIT_STYLE, &style); /* chance for subclassers */ p_send4 (self->edwin.scrlay=h_fnew(C_SCRLAY) ,O_SL_INIT, &édoc, &style) ; (see later in this chapter for a discussion of the scrLay_poc structure). Note in particular that, by default, no tabstops are set up. (There is a minor piece of trickery here, relying on the fact that, for screen display purposes, the font .height field is always zero.) 12-19 OBJECT ORIENTED PROGRAMMING GUIDE Changing from the default layout style As can be seen above, one way to change from the default layout style is by a call to s1_sense followed by one to sl_set. Another approach is to subclass the ew_init_style method of pwn - since a call to this is made just prior to the scruay object is actually created and initialised (see the code fragment given earlier). By default, the ew_init_style method equals p_dummy, and does nothing. Finally, all the above concerns so-called global style for the editor - style applying by default to all portions. However, the formatting code within scruay is open to the possibility of local variations in style. This is discussed further in the section on document objects below. An introduction to SCRIMG As noted above, changing the layout style by means of a call to si_set is not, by itself, sufficient to cause an actual change in the formatted layout of the editor. In addition, the screen image scrime object has to be notified - hence the si_style_changed message in the example given. In general, scrime caters for the concepts of e cursor position and select region e emphasis on or off e knowledge of the (lodger) window to be drawn to e knowledge of how this window region may break down into a possible left gutter (“labels’’) region, and a possible “line cursor” margin, as well as the main drawing area e width of the text cursor, when displayed, as well as the style of any margin line cursor e parameters affecting the way horizontal scrolling takes place e the state of background reformatting (ie which parts of the layout are up-to-date, and which need to be re-evaluated as soon as time allows). Additionally, scrrme contains the logic for the actual displaying (drawing and redrawing) of the edit window, for recalculating layout information (ie for driving the scruay object), for freeing layout structures no longer required (since the visible portion of the document has altered), and for smoothly scrolling the display vertically whenever appropriate. In fact, it may well appear that scrimc contains the core logic for Epwt1n itself, and there is much to be said for this view. Several methods of Epw1n simply delegate responsibility to scrime by passing on an appropriate message. However, it may be worth pointing out a few of the general differences between the overall epw1n object and its scrImG component: ¢ scrime (and scruay and indeed any class in FORM) is completely independent of any of the concepts in HWIM, and can be utilised eg on the MC range of computers, where the front-line user interface library (WIMP) is significantly different from the HWIM library ¢ scrime can be utilised independently of zpwrn, to provide so-called “edit-like windows” ¢ pwn may be viewed as an organiser of the cooperation between a scRIMG, a SCRLAY, and an EPDoCc; the wn_init method of Epw1n involves a substantial amount of “form filling”, in which these sub-components are properly initialised in a suitable relationship to one another ¢ EDWIN contains an extensive wn_key method, which is actually one of the longest methods in the whole of the HWIM library e¢ pwn adds on significant clipboard functionality, evaluation functionality, link-paste functionality, and find and replace functionality e amazingly (as discussed in more detail later in this chapter), scrime has no direct knowledge whatsoever about the document object. 12 - 20 SCRIMG structure defin 12 EDIT WINDOWS itions The “window” or “drawing environment” aspects of a scrime object are stored in property in an SCRIMG_WIN data structure: typedef struct { UWORD wid; P_POINT tl; WORD nlines; UBYTE lheight; UBYTE lascent; WORD width; WORD margin; WORD lcfont; UBYTE cwidth; UBYTE lcstyle; UBYTE lccode; UBYTE hscrlx; UBYTE hscrlm; UBYTE drawplabs; } SCRIMG_WIN; window ID top left corner of area being drawn to number of text lines line height in pixels distance from top of line to text base line total width in pixels (margin, line cursor,text) width of label margin in pixels line cursor font (or zero for no line cursor) text cursor width line cursor style line cursor character code horizontal scroll x jump horizontal scroll margin draw trailing para labels after formatting if set Just as the scrLay_sTyLE data held by a scriay object can be sensed via an s1_sense method and set via an si_set method, so also is there an si_sense method to sense the scrrmc_win data held by a scrime object, and an si_set method to set this data (see later for examples of these calls). In fact, as the code for scrimg_si_set makes clear, the si_set method takes two parameters - the first being (if non-nut1) the poin ter to a SCRIMG_WIN Struct, and the second being (if non-nuLL) the handle of the associated scriay object: METHOD INT scrimg_si /* Optionally set the w and return the width Also waits for an ba ef: { (PR_SCRIMG *)Dat CompleteFormatti if (lay) self-—>scrimg if (win!=NULL) { self—>scrimg self—>scrimg self—>scrimg if (self->sc self->sc self self—>scrimg self-—>scrimg. self—>scrimg. } if (self->scrimg. self-—>scrimg. if (self->scrimg. p_send3 (self return (self->scr } During initialisation, EDwINn _set (PR_SCRIMG *self,SCRIMG_WIN *win,VOID *lay) indow and the layout object (win and lay may be NULL) (in pixels) of the text area. ckground formatting to die down. Scrimg=self; ng(); .lay=lay; -gc.font=WS_FONT_BASE; -gc.style=G_STY_NORMAL; -win=*win; rimg.win.lcfont) rimg.lcwidth=gTextWidth (self—>scrimg.win.lcfont, —>scrimg.win.lcstyle, &self-—>scrimg.win.lccode,1)+2; -mrwidth=self—>scrimg.win.margint+self—>scrimg.lcwidth; xo=self—>scrimg.mrwidtht+self—>scrimg.win.tl.x; txwidth=self—>scrimg.win.width-self-—>scrimg.mrwidth; crs.line>self-—>scrimg.win.nlines-1) crs.line=self-—>scrimg.win.nlines-1; lay) —>scrimg.lay,O_SL_SET_LINES, self->scrimg.win.nlines) ; img.txwidth) ; takes care of setting up appropriate values for the scrimc_win data structure - based (as can be imagined) on the data in the In_EDwIN and IN_EDWIN_x Structures. 12-21 OBJECT ORIENTED PROGRAMMING GUIDE Example: changing the width of the text cursor The following code shows an example of interaction with the scrimc_wtn data structure inside scrimc. The code adjusts the width that the flashing text cursor will have, when shown (eg, it could be used to set the width to zero - effectively to hide the cursor altogether): LOCAL_C VOID SetCursorWidth(PR_EDWIN *ebH, INT cwidth) { SCRIMG_WIN win; p_send3 (edwin->edwin.scrimg,O_SI_SENSE, &win) ; win. cwidth=cwidth; p_send4 (edwin->edwin.scrimg,O_SI_SET, &win, NULL) ; } Changing the font used by an editor The ew_set_font method of Epwrn can be used to change the font used for the display. The code follows: METHOD VOID edwin_ew_set_font (PR_EDWIN *self,INT font,UINT style, EDWIN_LEADING * leading) /* Expected to be accompanied by call to ew_set_size a { SCRIMG_WIN win; G_FONT_INFO finfo; self—>edwin.font.fid=font; self—>edwin.font.style=style; gFontInfo(self—>edwin.font.fid, self—>edwin.font.style, &finfo) ; p_send3 (self—->edwin.scrimg,O_SI_SENSE, &win) ; win.lheight=finfo.height+leading->total; win.lascent=finfo.ascent+leading->top; p_send4 (self—>edwin.scrimg,O_SI_SET, &éwin, NULL) ; } Note however that, as the comment in the code states, this call by itself will generally be insufficient to effect the font change. Additionally: e in many cases, the size of the window region may change (quite likely when the font change has been triggered by a ‘Zoom’ menu command) e a suitable request message will have to be passed in due course to scrime to request it to recalculate the screen image - which will involve invalidating some or all of the formatting information maintained by scruay. See later for more information about notification and request messages to scrimc. The main point here is that adjusting the scrimc_wt1n data does not, by itself, trigger a recalculation; rather, this is delayed until all necessary adjustments have been made, to avoid needless repeated re-calculations. Note incidentally that the new font details do not have to be passed on explicitly to scruay. Recall that the various scRLAY_FonT data structures required by scruay are referenced indirectly: the scRLAY_STYLE data structure actually contains, by default, pointers to the edwin. font structure inside EDWIN property. Note moreover that there is no compulsion to use the ew_set_font method, in order to change the font used by an editor. Rather, the code given above can be used as a template (in conjunction with more code to be listed shortly) for application-specific code to achieve a similar result. Note finally that editors with local variations in font - ie with some portions of text being displayed in one style, and with other portions being displayed in another style - require alternative document objects to be used - as is discussed later in this chapter. 12 - 22 12 EDIT WINDOWS The ew_sense_size and ew_set_size methods Code that can be used to resize an editor - for example in response to the status window changing, or in response to a ‘Zoom’ menu command - includes the ew_sense_size and ew_set_size methods. These are normally used as a pair, for obvious reasons: METHOD VOID edwin_ew_sense_size(PR_EDWIN *self,P_EXTENT *pext) { SCRIMG_WIN win; p_send3 (self—>edwin.scrimg,O_SI_SENSE, &win) ; pext—>tl=win.tl; pext—>width=win.width; pext—>height=win.nlines; } METHOD VOID edwin_ew_set_size(PR_EDWIN *self,P_EXTENT *pext,VOID *hand, INT method) { SCRIMG_WIN win; INT twid; p_send3 (self-—>edwin.scrimg,O_SI_SENSE, &win) ; self—>lodger.offset=win.tl=pext—>t1l; self—>lodger.width=win.width=pext-—>width; win.nlines=pext—>height; twid=p_send4 (self-—>edwin.scrimg, O_SI_SET, &éwin, NULL) ; if (hand) p_send3 (hand, method, twid) ; p_send3 (self—->edwin.scrimg, O_SI_STYLE_CHANGED, SCRIMG_STCHNG_DOC) ; } Note that the height field in the p_extent data accessed by both these methods refers to the number of lines in the screen window - in contrast to the values in each of the other three fields in the p_ExtEnt data, which all represent numbers of pixels. Evidently, scrimc always assumes that there are a whole number of lines visible. Next, note that the si_set method returns the number of pixels in the width of the text area of the editor, after the resize. This value may or may not be useful - see below for its use within the wn_init method of EDWIN itself. Finally, note that the ew_set_size method supports a possible “soft” call-back - if the parameter nana is non-nuLu - before making the si_style_changed request to scrime to trigger a layout recalculation. Changing the paragraph margins The following example could be used to set the “right” margin to the arbitrary large value of 4096 - and thereby to disable word-wrap, in effect (as in the Program Editor). Alternatively, the example could be extended to adjust the other paragraph margins used by paragraphs - ie the “left” and “first line” margins: LOCAL_C VOID SetRightMargin(PR_EDWIN *edwin,UINT right) { edwin->edwin.margins.right=right; p_send3 (edwin->edwin.scrimg,O_SI_STYLE_CHANGED, SCRIMG_STCHNG_DOC) ; } Notifying SCRIMG of a change in style The si_style_changed method causes scrime to recalculate some or all of the layout in the associated SCRLAY object, and to redraw the screen (intelligently - ie minimising the amount of redrawing that actually is done). The cursor position and any select region are maintained, and as far as possible, the cursor is drawn on the same line number of the screen (eg the second line down from the top of the visible portion) as before. Exactly how much work is carried out depends on the scrimG_sSTCHNG_xxx parameter passed (but note that this parameter is ignored on the Series 3, with notice of it being taken only on the Series 3a and on the MC): e if the parameter is scrIMG_sTCHNG_Doc, the entire layout is rebuilt and redrawn (subject, as always, to only building as much layout as is required to cover the visible portion of the document) 12 - 23 OBJECT ORIENTED PROGRAMMING GUIDE if the parameter is scRIMG_STCHNG_PaARA, layout above the top of the paragraph containing the cursor (or the top of any selected region) is not recalculated or redrawn, and any layout below the bottom of the paragraph containing the cursor (or the foot of any selected region) is merely scrolled vertically (if required) - this is the action appropriate when, for example, paragraph styling is applied in the word processor by a key sequence such as CONTROL-BT if the parameter is scRIMG_STCHNG_LINE, a similar optimisation is made, appropriate this time to the application of phrase style (sometimes called “emphasis’”) in the Word Processor - this can result in even less work being carried out, if for example the cursor is in the third or subsequent line in a paragraph. Initialising the SCRIMG_WIN data structure For general background interest, here is an extract from edwin_wn_init, containing the code that creates and initialises the scrrme subcomponent: METHOD VOID edwin_wn_init (PR_EDWIN *self,IN_EDWIN *init,PR_WIN *whand, IN_EDWIN_X *initx) 12 - 24 { SCRIMG_WIN win; SCRLAY_DOC doc; SCRLAY_STYLE style; G_FONT_INFO finfo; EDWIN_LEADING leading; self-—>lodger.landlord=whand; win.lcfont=0; /* no left cursor by default */ if (init->flags&IN_EDWIN_LEFT_CURSOR) win.lcfont=WS_FONT_BASE; win.lcstyle=self—>edwin.font.style&G_STY_DOUBLE; win. lccode=WS_SYMBOL_MARGIN_CURSOR; leading.total=2; /* by default, one pixel leading at top and at bottom */ leading.top=1; if (init->flags&IN_EDWIN_LEADING_SUPPLIED) leading=initx—>leading; win.lheight=finfo.height+leading.total; win.lascent=finfo.ascentt+tleading.top; win.wid=whand->win.id; win.tl=initx-—>pos; /* may be garbage but no harm done */ win.nlines=(init-—>flags&IN_EDWIN_VISLINES_SUPPLIED? initx->vislines: 1); win.width=self-—>lodger.width; win.margin=0; win.cwidth=2; if (win.nlines==1) { win. hscr1lx=0; win. hscrlm=win.width>>2; } else win. hscrlx=win.hscrlm=30; self—>edwin.scrimg=h_fnew(C_SCRIMG) ; self—>edwin.margins.right=p_send4 (self-—>edwin.scrimg,O_SI_SET, éwin, self-—>edwin.scrlay) -finfo.max_width; if (win.nlines==1) self—>edwin.margins.right=4096; /* any large value will do */ if (init->flags&IN_EDWIN_POSITION_SUPPLIED) { /* else defer until following lg_set_id_pos */ self—>lodger.offset=win.tl; self—>win.id=win.wid; p_send4 (self—>edwin.scrimg,O_SI_INIT,0,0); SelectAl1lOrCurEnd (self); } 12 EDIT WINDOWS Direct interaction with document objects A given EpwIn object interacts with up to two document objects: the document object where its own character content is stored, and (optionally) the clipboard document object. The clipboard document object is usually an instance of either EPFLAT or EPSEG. See the OLIB Reference manual for a description of these two classes - which are each a subclass of EPRooT. The main document object for an Epwin has to be a subclass of the FORM eppoc class. This is a specialised subclass of EPpRoot, and like EPRooT, it supports both “flat” and “segmented” concrete subclasses, namely eEpriat and Epsec. Methods of eproor all carry over to Eppoc - although the implementation may differ, in places. Although many of the methods of zpw1n manipulate the document objects on behalf of the application, there can be occasions where it is more appropriate for the application to interact directly with these objects. Afterwards, of course, the editor has to be informed that a change has taken place. Another reason for wishing to understand document objects more deeply is in order to support local variations in style data. Yet another is in order to supply “labels” for paragraphs. All these topics are discussed in the following subsections of this chapter. Setting text directly into the document object Suppose for example that an application wishes to set a large amount of text into an edit window - text that can be read in stages from a file. The following steps could be taken. First, the handle of the document object needs to be obtained. This can be read out of the edwin.doc property field of the Epw1n, or the document object may have been created separately - in which case the handle will already be known. Next, it may be appropriate (especially in the case of a flat document object) to set the capacity of the document object in advance - if the overall size is known. The ep_capacity method (refer to the OLIB Reference manual for details) can be used to this end. (The ep_capacity method is left as p_dummy for all segmented document objects.) Following that, repeated calls of the following sort can be made: p_send5 (doc, O_EP_INSERT,pos,buf,1len); having the effect, each time, of inserting the 1en characters at *bur into the document, at document offset pos. It would be usual to track the value of pos as this operation proceeds, with 1en being added to it each time 1en more characters are inserted. Finally, code of the following sort is required: LOCAL_C VOID NotifyDocChanged(PR_EDWIN *edwin) { UINT doclen; doclen=p_send2 (edwin->edwin.doc, O_EP_SENSE_LEN) ; edwin->edwin.clen=doclent+1; p_send3 (edwin->edwin.scrimg,O_SI_DOC_CHANGED, doclen) ; } It is this last routine that stands most in need of comment here (the earlier steps, after all, only require knowledge of the Eproot class). In general e scrime level data has to be adjusted - by means of, for example, the si_doc_changed method ¢ and, at the same time, Epwtn level data has to be adjusted - usually by direct manipulation of the relevant property fields. There are actually two key epw1n property fields in this context: ¢ edwin.clen, giving the total “character length” of the document (including the final paragraph delimiter) @ edwin.cpos, giving the cursor position, as a document offset. For simplicity, the above routine, Not ifyDocChanged, omits making any change in the cursor position - which may be appropriate in some cases, but it will not be appropriate in other cases, and will even result in program crashes in yet other cases (eg if there are fewer characters in the document after the change than before the change). 12 - 25 OBJECT ORIENTED PROGRAMMING GUIDE Dual variables at the EDWIN and SCRIMG levels As can be seen, copies of, effectively, the total character length of the document are held by both scrime and Epwin. Likewise, dual copies are also kept of the cursor position. There is also a select field within EDWIN property (which is TRuE if there is a non-nuLL select region, otherwise FaLsE), which must, once again, be kept in synchronisation with the status of the select region as known to scrime. The reason for this duplication of data storage is to increase the speed at which various critical manoeuvres within edit boxes can be executed. However, it should be pointed out that failure to keep these dual variables appropriately in harmony is a common cause of bugs in programming edit windows. Adjusting the cursor position One way that the cursor position of an edit window (and, with it, the select region) can be adjusted is via the ew_set method documented earlier in this chapter. For many purposes, however, the scrimc method si_move_cursor may prove more suitable. In fact, si_move_cursor is used frequently within Epw1n code (for example, within the ew_set method), often via the following utility routine: LOCAL_C VOID MoveCursor(PR_EDWIN *self,INT shift, INT type) { self—>edwin.select=p_send5 (self->edwin.scrimg, O_SI_MOVE_CURSOR, shift,type, &self-—>edwin.cpos) ; } Subclasses of zpw1n often contain a duplicate of this utility function - such is its use. The meaning of the shift parameter to si_move_cursor is as follows: e ifthe shift parameter is non-zero, it means to extend (or create) a select region, with the movement specified by type being applied to the moving end of the selection e if shift is zero, it means to cancel any existing select region, and to move the cursor as specified by type. The return value from si_move_cursor 1S TRUE if there is a non-NULL select region after the movement, and otherwise FALSE. The possible meanings of type are as follows: SCRIMG_LINEDN move the cursor down one line SCRIMG_LINEUP move the cursor up one line SCRIMG_PAGEDN move the cursor down one page SCRIMG_PAGEUP move the cursor up one page SCRIMG_LINBEG move the cursor to the beginning of the current line SCRIMG_LINEND move the cursor to the end of the current line SCRIMG_SETPOS move the cursor to the document offset specified by the final parameter. In all cases, the final position of the cursor, as a document offset, is written to the address specified by the final parameter to the si_move_cursor call. Logical cursor movement and physical cursor movement Most of the type values in the above table cater for so-called “physical” cursor movement - where the actual movement of the cursor is determined by reference to the current layout. (In order to work out where to position the cursor, scriMe interrogates the data structures maintained by the associated scrLay object.) In many other cases - for example, in response to the CONTROL-LEFT key, which moves the cursor back to the next beginning of a word - the movement of the cursor is instead determined by reference to document content, and results in a so-called “logical” cursor movement. Methods of EPRoot, such as ep_scan_word, may be of use in this case. Once the required document offset is known, a call to si_move_cursor iS required, specifying scrImMG_sETPos as the type. For this reason, subclasses of epw1n often contain a routine such as 12 - 26 12 EDIT WINDOWS LOCAL_C VOID SetCursor(PR_EDWIN *self) { MoveCursor (self,0,SCRIMG_SETPOS) ; } which, evidently, layers over the Movecursor utility routine described earlier. On this subject, yet another routine that may be worth duplicating is the following, whose effect is to set up a select region with given ends (this routine is called, in effect, from inside edwin_ew_set): LOCAL_C VOID SetEdwinSelect (PR_EDWIN *self,UINT ancpos,INT sellen) { self—>edwin.cpos=ancpos; MoveCursor (self,0,SCRIMG_SETPOS) ; self—>edwin.cpos+=sellen; MoveCursor (self, TRUE, SCRIMG_SETPOS) ; } Notifying SCRIMG of a change in document content The scrime class supports in all four different methods for reporting to it that there has been a change in the document. These methods differ primarily in how much reformatting is required - to avoid incurring unnecessary work re-evaluating layout data that cannot possibly have changed. (That is a very important consideration when the user is typing in the middle of a sizeable paragraph.) The si_doc_reset method can be called as follows: p_send5 (scrimg, O_SI_DOC_RESET, doclen,cpos, line) ; with the following effect: ¢ scrime is notified that the document has totally changed, and now has document length docien e any existing select region should be discarded e the existing layout data should be discarded e the layout should be rebuilt so that the curspor is put at document offset pos and is displayed at line number 1ine on the visible screen (subject to that line being reachable). Note that 1ine can be set to the value -1 in order to pick up the current line number (so that the cursor remains, if possible, on the same line of the screen as before). Note again that the docien value includes the final terminating paragraph delimiter in the document. A simple example of the use of si_doc_reset 1s in the following utility routine called inside edwin_wn_set: LOCAL_C VOID EdwinSet (PR_EDWIN *self,SE_EDWIN *set) { p_send4 (self—>edwin.doc, O_EP_SET_TEXT, set—->buf, set-—>len) ; self—>edwin.cpos=0; self—>edwin.clen=set-—>len+1; p_send5 (self—>edwin.scrimg, O_SI_DOC_RESET, self->edwin.clen,0,0); } (the code in edwin_wn_set goes on to set the cursor position and select region depending on the value of the initialisation 1N_EDWIN_xxx flags). Similar in effect to si_doc_reset, the si_doc_changed method differs on in that the cursor position is maintained the same as before the change was made. Accordingly, whilst a docien parameter is still needed, this method has no cpos or line parameters. The way to call si_doc_changed in general is p_send3 (scrimg, O_SI_DOC_CHANGED, doclen) ; For example, edwin_ew_replace calls si_doc_changed, indirectly, as follows (this code also illustrates use of the scrimc method si_sense_select): LOCAL_C VOID EdwinDocChanged(PR_EDWIN *self) { self—>edwin. change=EW_CHANGE; p_send3 (self->edwin.scrimg,O_SI_DOC_CHANGED, self->edwin.clen) ; } 12-27 OBJECT ORIENTED PROGRAMMING GUIDE METHOD INT edwin_ew_replace(PR_EDWIN *self, TEXT *replace, INT backwards) { UWORD replen; UWORD sellen; UWORD pos; INT err; CheckNotReadOnly (self); replen=p_slen (replace) ; sellen=p_send3 (self-—>edwin.scrimg,O_SI_GET_SELECT, &pos) ; if ((err=p_entersend5 (self—>edwin.doc, O_EP_INSERT, pos, replace, replen) ) !=0) p_send3 (self,O_EW_LEAVE, err) ; self—>edwin.cpos=pos+treplen; if (backwards) self—>edwin.cpos=pos; SetCursor (self); p_send4 (self—>edwin.doc, O_EP_DELETE, pos+replen, postreplentsellen) ; self—>edwin.clen+=(replen-sellen) ; EdwinDocChanged (self) ; return (0); /* confirm success */ } Notifying SCRIMG of a local change in document content The si_fwd_change method of scrime is called as follows: p_send3 (scrimg, O_SI_FWD_CHANGE, doclen) ; This takes exactly the same parameters as si_doc_changed, and as in that case, the method has the effect of e cancelling any select region e keeping the cursor at the same document offset as before. However, for si_fwd_change, ScRIMG makes the assumption that the layout cannot change in paragraphs earlier in the document than that containing the cursor; nor can it change in lines in the current paragraph more than one above that containing the cursor. Briefly (though, as can be appreciated, not completely accurately), only layout forward from the cursor can have changed. For example, edwin_ew_paste_clip Calls si_fwd_changed, indirectly, as follows: LOCAL_C VOID EdwinFwdChange(PR_EDWIN *self) { self—>edwin. change=EW_CHANGE; p_send3 (self—>edwin.scrimg,O_SI_FWD_CHANGE, self-—>edwin.clen) ; } METHOD INT edwin_ew_paste_clip(PR_EDWIN *self) { UWORD len; INT err; CheckNotReadOnly (self); len=p_send2 (self->edwin.clip,O_EP_SENSE_LEN) ; if (len) { if ((err=p_entersend4 (self—>edwin.doc,O_EP_PASTE, &self—>edwin.cpos, self->edwin.clip) ) !=0) p_send3 (self, O_EW_LEAVE, err); self—>edwin.clen+=len; EdwinFwdChange (self) ; SetEdwinSelect (self,self-—>edwin.cpos-len, len) ; } return(len); } The si_para_changed method takes stages one step further by restricting the extent of possible layout change to the current paragraph (whereas a change notified by si_fwd_change can effect regions on the screen arbitrarily far below the current paragraph). More precisely, si_para_changed assumes that the only possible change in layout for paragraphs below the current paragraph is vertical scrolling. 12 - 28 12 EDIT WINDOWS Another change between si_fwd_change and si_para_changed is in the form of the parameters passed. In general, a call to si_para_changed has the form SCRLAY_PLX old; p_send4 (scrimg, O_SI_PARA_CHANGED, keycode, &01d) ; The precise description of the method is that the paragraph containing the cursor has changed at the cursor position as a result of one of: e asingle character insertion of a content character, where keycode is either a character code (which is assumed to be printable) or zero (paragraph end) or '\t' (W_KEY_TAB) Or '\n' e a left delete, where the character code is '\b' (W_KEY_DELETE_LEFT) e aright delete, where the character is 127 (W_KEY_DELETE_RIGHT). The method immediately redraws the current line to reflect the input, and completes the production and the drawing of the layout as a background task. The final parameter, which is a pointer to a scRLAY_PLX struct, has significance only when keycode 18 W_KEY_DELETE_LEFT. This is required in order for scrimc code to be able to make a safe judgement about whether the deletion has effects that extend over more than one line (the problem being that scrime cannot in this calculate the old cursor position after being notified of the change in the document). An example of code that sets up the appropriate scrLay_pLx structure, prior to calling si_para_changed, is in the following extract from edwin_wn_key, for the case when a w_KEY_DELETE_LEFT key has been received: case W_KEY_DELETE_LEFT: CheckNotReadOnly (self); if (self->edwin.select) goto delsel; if (self->edwin.cpos==0) break; p_send3 (self—>edwin.scrimg,O_SI_DELPREP, &p1x) ; self—>edwin.cpos-=1; p_send4 (self-—>edwin.doc, O_EP_DELETE, self—>edwin.cpos, self->edwin.cpost1l) ; self—>edwin.clen-=1; p_send4 (self—>edwin.scrimg, O_SI_PARA_CHANGED, keycode, &p1x) ; As can be seen, there is no need to pass a doclen parameter to si_para_changed. When there is a change of content and a change in cursor position The above few sections have touched (and hinted) at one potential problem when orchestrating notification to scrime that the document has altered, whilst at the same time trying to take advantage of incremental updates in the layout information (for speed purposes). The problem is that scrimc cannot be left with a cursor position that no longer exists in the document. More precisely, recall that scriay ultimately views the document as consisting of a series of so-called tboxes. For example, a given tbox may refer to n characters starting at document offset dort. As mentioned earlier, these n characters will all be stored contiguously within a buffer inside the associated document object. But suppose, as a result of a change in the document, these characters are no longer stored contiguously. Then any subsequent attempt to access these characters - by reference - will fail. But this is precisely the kind of thing that scrimc and scruay will, between them, attempt to do - so long as they believe that part of the layout structure is still valid. Without going into any more details, the moral is clear: position the scrime cursor to the beginning of any region where change is about to occur, before making that actual change. Various aspects of the EDwIN code given above can be seen, upon inspection, to be obeying this principle. 12 - 29 OBJECT ORIENTED PROGRAMMING GUIDE The SCRLAY_DOC data structure The si_init method, described earlier, actually requires the address of a scrLay_poc data structure, as well as the address of a scRLAY_STYLE data structure. The scriay_poc structure informs scrLay about some very important aspects of the associated document object: typedef struct { UWORD len; Length of doc (one greater than max position) VOID *content; Object containing document content WORD sensechars; Method to sense character segments WORD sensepdata; Method to sense paragraph layout data WORD senseplabel; Method to sense paragraph label WORD toparst; Method to scan start of paragraph WORD enqpage; Method to enquire for a page break } SCRLAY_DOC; Here is how these values are filled in during edwin_wn_init: SCRLAY_DOC doc; doc.enqpage=0; if (init->flags&IN_EDWIN_TEXT_SEGMENTED) { doc.sensechars=O_EPDOC_SENSE_CHARS; doc.toparst=O_EPDOC_PARA_START; if (init->flags&IN_EDWIN_PAGINATABLE) doc.enqpage=O_EPDOC_ENQ_PAGE; } else { doc.sensechars=0O_EPFDOC_SENSE_CHARS; doc.toparst=O_EPFDOC_PARA_START; } if (init->flags&IN_EDWIN_DOC_SUPPLIED) self—>edwin.doc=initx->doc; else { self—->edwin.doc=NewEdwinDoc (CAT_HWIM_FORM, C_EPFDOC, init) ; p_send4 (self—>edwin.doc, O_EP_SET_TEXT, &init->contents[0],p_slen(é&init- >contents[0])); } doc.sensepdata=0; doc.senseplabel=0; doc.content=self->edwin.doc; doc. len=self-—>edwin.clen=p_send2 (self-—>edwin.doc, O_EP_SENSE_LEN) +1; Subclasses of zpw1n (or other window objects providing edit-like windows) may wish to alter some of the values of these fields, to achieve affects such as paragraphs with associated labels. The five soft method numbers in SCRLAY_DOC It is time to point out one key design decision embodied in the relationship between scrimc, scRLay, and EPpoc. In fact, scrnay only ever communicates with the document object via the five soft methods (also known as “call-backs’’) whose numbers are contained within the scruay_poc data structure. And scrimeG never communicates directly with the document object. (For example, when scrime needs to display text on the screen - eg in response to a redraw request - it reads the characters it has to draw, by sending the associated scrLay object an s1_read message.) This allows for diverse powerful objects to be built up, using scrLay and scrimc as components. In some cases, the associated document object will continue to be a subclass of Eppoc - as is always the case when EDWIN is involved. But there is no fundamental requirement for this to be the case. Instead, the only requirement is to provide methods for some of the slots in scRLAY_poc. The SENSECHARS call-back The protocol of the sensechars call-back can be seen from the following excerpt from scriay code: GLDEF_C VOID SenseChars (SCRLAY_SENSECHARS *ps,SCRLAY_FONT **pf,UBYTE **pfw) { p_send5 (DatScrlay-—>scrlay.doc.content, DatScrlay-—>scrlay.doc.sensechars,ps,pf,pfw); } 12 - 30 12 EDIT WINDOWS This uses the scRLAY_SENSECHARS Struct which is defined as follows: typedef struct { UWORD pos; document position to sense WORD printer; TRUE for printer data else screen data TEXT *buf; address of character block WORD blen; length of character block } SCRLAY_SENSECHARS; One concrete realisation of the sensechars call-back is provided by the epdoc_sense_chars method of the EPDoc class: METHOD VOID epdoc_epdoc_sense_chars (PR_EPDOC *self,SCRLAY_SENSECHARS *sense) { sense->blen=p_send5 (self, O_EP_SENSE_CHARS, &sense—>buf, sense-—>pos, WS_MAX_PRINT_BOX_TEXT_LEN) ; } It will be immediately apparent that this realisation of sensechars has ignored the final two parameters, pf and pfw. That is entirely deliberate. The reason for this is that, prior to scrLay sending the sensechars message, it pre-loads the pf and pfw variables to point to the corresponding global style fields, inside the scrLay_styLe data structure. Accordingly, for editors in which there is no local variation in style, there is no need for the sensechars call-back to write to the passed pf and pfw variables. For interest, here is an extract from the sensechars call-back provided by the Word Processor document class (known simply as doc): METHOD INT doc_epdoc_sense_chars(PR_DOC *self,SCRLAY_SENSECHARS *pss, SCRLAY_FONT **pf,UBYTE **pfw) { TAGLIST_ITEM *pi; LOG_POSITION *plog; UINT len; UBYTE *pw; pi=doc_dc_sense_position(self,pss-—>pos) ; plog=(&self—->doc.t->taglist.lpos) ; if (pf) /* if pss->printer is TRUE, get printer data */ { if (pss->printer) { SensePrnFont (pi->par,pi->phr, &self—>doc.pf); *pf=&self->doc.pf; } else { pw=SenseScrFont (self,pi->par,pi->phr, &self—>doc.sf); *pf=&self->doc.sf; } } if (pfw) /* if pss->printer is TRUE, get printer data */ { if (pss->printer) *pfw=SensePrnWidthTable(self,pi-—>par,pi->phr) ; else *pfw=pw; } len=pi->link-plog->offset; pss->blen=p_send5 (self, O_EP_SENSE_CHARS, &pss->buf,pss-—>pos, len); return (pss—>blen==len) ; } Without going into details, some points can be noted: e the significance of the printer field in the scrLAy_sENSECHARs Struct is that, if it is TRUE, printer font width tables and printer font details should be provided - otherwise data for screen display e atest should be made on pf and pfw, in case they are nut, which means that no data should be written to them in that case. 12 - 31 OBJECT ORIENTED PROGRAMMING GUIDE Structure of SCRLAY font width tables Reference has been made above to font width tables. As can be seen, the default value of the font width table pointer fwtab - as set up IN edwin_wn_init - is actually nunu. This clarifies that an explicit font width table is not always required. In this case, widths of pieces of text are calculated by calling the Window Server function gtextwidth. But for some purposes, having a font width table on the client side significantly increases the speed at which formatting can take place. Furthermore, for printing purposes, having font width tables loaded is essential. See the Printing chapter in this manual for more information about font width tables. The TOPARST call-back The protocol of the toparst call-back can be seen from the following excerpt from scruay code: LOCAL_C VOID ToParStart (UWORD *pos) { p_send3 (DatScrlay-—>scrlay.doc.content,DatScrlay->scrlay.doc.toparst,pos) ; } In contrast to the sensechars call-back, this is a completely straightforward routine, with only one parameter, which is a pointer to a document offset that needs to be converted to that for the start of the paragraph containing it. The implementation of toparst by Eppoc is as follows: METHOD VOID epdoc_epdoc_para_start (PR_EPDOC *self,UWORD *ppos) /* Convert *ppos to point to the beginning of the paragraph containing *ppos */ { p_send4 (self, O_EP_SCAN_PARA, ppos, EP_SCAN_BACKWARDS | EP_SCAN_STAY | EP_SCAN_TO_BEGIN) ; } The ENQPAGE call-back The enqpage call-back, if non-zero, is used by scriay to determine whether a page-break is to occur at a given line in a paragraph. This fact is indicated by setting the new_page field TRuz, in the corresponding SCRLAY_LINE data structure (see earlier in this chapter for the definition of this structure). By default, this call-back is left as zero, in edit windows, but Epwrn sets it to epdoc_eng_page if the initialisation flag IN_EDWIN_PAGINATABLE Is Set. A full discussion of the operation of epdoc_eng_page requires detailed knowledge of the Eppoc class. The SENSEPDATA call-back The sensepdata call-back plays a role analogous to sensechars call-back: it caters for local variations in paragraph styling (whereas sensechars Caters for local variations in phrase emphasis). The protocol for the sensepdata call-back can be seen from the following extract from scriay code: if (DatScrlay->scrlay.doc.sensepdata) p_send5 (DatScrlay-—>scrlay.doc.content, DatScrlay-—>scrlay.doc.sensepdata, posl,DatScrlay->scrlay.st.printer, &DatScrlay-—>scrlay.st.pd); In other words, if the call-back is non-zero, the document object has to provide the scrLay_ppata data for the paragraph containing the document offset posi (and paying due respect to whether the printer parameter is TRUE Or FALSE). 12 - 32 12 EDIT WINDOWS For interest, here is how the Word Processor doc class provides the sensepdata Call-back: METHOD VOID doc_dc_sense_pdata(PR_DOC *self,UINT pos,INT printer,SCRLAY_PDATA *pd) /* Sense the margin, tab and spacing data for the current paragraph. If printer is TRUE, return printer values. af { PAR_STYLE *par; par=doc_dc_sense_position(self,pos) —>par; if (!printer) { SenseScrTabs (self,par, &self-—>doc.tabs) ; pd->margins=&par->scr_mar; } else { SensePrntTabs (self,par, &Self—>doc.tabs) ; pd->margins=&par->prn_mar; pd->spacing=&par->prn_spc; } pd->tabs=&self->doc.tabs; } The SENSEPLABEL call-back If non-zero, the senseplabe1 call-back is assumed to provide information about labels to be drawn alongside the starts of paragraphs, in a left margin area. Two examples of paragraph labels are field names in the Database application style short-codes in the Word Processor application. The protocol of the call-back can be seen from the Word Processor implementation of the method: METHOD VOID doc_dc_sense_plabel(PR_DOC *self,UINT cpos,INT printer, SCRLAY_PLABEL k* ppl) { PAR_STYLE *par; XWP_LAY *pxlay; pxlay=SenseLay (self); *ppl=&pxlay-—>label; par=doc_dc_sense_position(self,cpos) ->par; pxlay->label.buf=&par->ph.sc[0]; pxlay->label.blen=2; } As with many of these call-backs, two of the parameters passed are the document offset, cpos, of the position of interest a flag, printer, specifying whether the information is required for screen-display purposes or for printing purposes. The final parameter involves the scRLAY_PLABEL struct, whose definition is as follows: typedef struct { SCRLAY_FONT font; font ID, height (printer only) and style UWORD align; alignment TEXT *buf; address of character block WORD blen; length of character block } SCRLAY_PLABEL; 12 - 33 OBJECT ORIENTED PROGRAMMING GUIDE Some examples of edit-/ike windows Admittedly, there is a formidable learning curve to gaining full familiarity with the scope and power of the scriay and scrime classes. In particular, despite its length, this chapter has only touched peripherally on such topics as page breaks and special support for printing (ie using approaches other than the LPRINTER and xPRINTER Classes). However, it turns out in practice that many displays can be programmed more quickly and more efficiently using scrimc and scruay, than using any alternative mechanisms. Furthermore, in many cases only a small amount of the rather detailed full interface to scrimc and scruay needs to be appreciated. One example is the “Synonyms” screen in the Spellchecker application. This consists of a list of words which can be navigated by standard keypresses. Thus if the word “Bang” is looked up, the screen can look like: ban cities: hit, bell, ringer, bull’s-eye, gong, slam, smash; crash, boom, clang, clap, resound, roar, rumble, shake, under; Bees blast, blare, boom, discharge, noise, pop, report, rOar; hit, knock, blow, box, bump, chop, clap, conk, crack, crash, cuff, impact, jar, jolt, lick, punch, rap, slap, slug, smack, smash, swat, swipe, tap, wallop, whack. crack, pop, snap, thump; dent, hollows, dimple, indent; In this example, ROM code handles: e automatically wrapping the lists of words at the edge of the window e¢ automatically smoothly scrolling the display vertically when needed e navigating by word - using the ep_scan_word method e displaying the relevant “labels” at the side of each group of words (in this example, “noun” and “verb’). ROM code even supports the notion of a “hard space”, eg between “bell” and “ringer” on the second line in the above screen, to prevent paired groups of words being split over a line. Another example worth mentioning is the main display window of the “Berlitz” five-language translator @ Berlitz application: Eng pitch-dark fadj? Fre knoir comme dans un four {adj} Ger stockdunkel fad Jj} Spa oscuro como bocs de lobo {adj} {74h (Engipitch sav] Thu i6 General comments on creating edit-like windows In all examples like this, a significant proportion of the work is in the initialisation. The various initialisation structs - which are quite long - have to be filled in appropriately, and in the correct order. But once the system of objects is in place, it largely runs itself. The main extra responsibility of the programmer is to pass messages onto the scrime object (and, less frequently, to the scriay object) at appropriate times. Many of these messages have already been discussed in the course of this chapter, but a few remain to be covered. 12 - 34 12 EDIT WINDOWS The si_redraw method The entire contents of the wn_draw method of Epwtn is to pass on a corresponding message to scRIMG: METHOD VOID edwin_wn_draw(PR_EDWIN *self) { p_send3 (self—>edwin.scrimg, O_SI_REDRAW, NULL) ; } The final nui parameter means to redraw the entirety of the region. On the SERIES 3a, the final parameter can meaningfully be other than nut (in fact, on the Series 3, the final parameter is ignored), in which case it should point to a p_ReEct structure indicating the portion of the region that needs to be redrawn. The si_emphasize method The entire contents of the wn_emphasise method of EpwIn is, again, to pass on a corresponding message to SCRIMG: METHOD VOID edwin_wn_emphasise(PR_EDWIN *self,INT flag) { p_send3 (self-—>edwin.scrimg,O_SI_EMPHASIZE, flag) ; } Any application that makes direct use of scrimc (ie without the benefit of an intermediate Epw1n object) would have to possess a corresponding message. The si_pan method The si_pan method provides a means for a window display to be scrolled horizontally, even though it is not displaying any visible cursor. An example of this behaviour is when the user presses RIGHT or LEFT when viewing the “Found” screen in the “Wrap off” state of the Database application. Code calling the si_pan method will look like p_send4 (scrimg, O_SI_PAN, func, par) ; The si_pan method actually combines three different functions, depending on the value of func: e if func is SCRIMG_PAN_SETNOPAN, the nopan property value of scrimc is set to par (which should be either TRUE Or FALSE) e if func 1S SCRIMG_PAN_DELTA, the effect is to scroll the display horizontally by par pixels (where par can be either positive or negative) e if func is ScRIMG_PAN_aBS, the effect is the scroll such that par is at the left of the view. Any scrolling is contrained to reasonable limits. For example, the display will not scroll past the right hand end of the longest visible line. The purpose of the nopan mode is to control whether, any time the view is scrolled vertically, it also tries to scroll horizontally (ie to “pan’’) in order to keep the cursor position visible. 12-35 CHAPTER 13 PRINTING This chapter describes the basics of access to the WDR print system from HWIM applications. See the chapter WDR Printing in the Additional System Information manual for background information about the scope of the SIBO WDR print system. Although partial access to the WDR print system is available to Hwif programmers, full access is only possible via object oriented techniques, such as are explained in this chapter. As far as HWIM applications that wish to print are concerned, three different levels of approach can be identified, in increasing order of sophistication (and difficulty): 1. applications create and use a subclass of the HWIM iprinter class, and REPLACE only the one method 1pr_sense_text 2. applications create and use a subclass of 1printer, and REPLACE other methods, such as lpr_read 3. applications avoid using lprinter, and instead interface more directly with classes such as pages in the FORM library (the interaction with pages is one of the things that 1printer handles automatically, for less demanding applications). For many applications, the first of these three levels is perfectly sufficient, and in this case, only a limited acquaintance with the material in this chapter is required. Print preview On the Series 3a, it is relatively straightforward for applications using the WDR print system to provide ‘Print preview’ menu commands, in addition to ‘Print’ commands. The basic step involved, in most cases, is to subclass the xprinter class, rather to subclass 1printer. (The xprinter class is in the XADD library and is not available on the Series 3.) The interface to xprinter Is very similar to that of lprinter (xprinter is actually a subclass of printer). The vast majority of application-level print preview code is exactly the same as the application code that implements print. The basic model of WDR printing Regardless of whether an application implements print preview as well as print, and regardless of the level of sophistication the application brings to printing, the first basic requirement, in use of the WDR print system, is an understanding of the woR_PRINT struct. This is defined as follows in prdrv.cl: typedef struct WORD flags; WDR_PRINT_XXX WORD typf; Typeface number for WDR_PRINT_FONT WORD fheight; Font height for WDR_PRINT_FONT (twips) WORD style; Font style for WDR_PRINT_FONT WORD down; Line down for WDR_PRINT_LINE WORD indent; Line indent for WDR_PRINT_LINE WORD height; Line height for WDR_PRINT_LINE WORD right; Right movement for WDR_PRINT_RIGHT TEXT *buf; Text to print for WDR_PRINT_TEXT UWORD blen; Length of data at buf for WDR_PRINT_TEXT } WDR_PRINT; 13-1 OBJECT ORIENTED PROGRAMMING GUIDE (Hwif programmers will recognise this as the same as the h_print struct.) Essentially, the basic model of WDR printing can be summarised as follows: e the application creates and initialises a suitable top-level printing object e in due course, system code repeatedly calls the application back, passing, each time, the address of a WDR_PRINT struct e the application fills in various fields in this struct, each time, to describe the next “print element’, and lets the flow of execution return into system code e eventually, printing comes to an end - either because it has finished normally, or because the user has cancelled it, or because an error condition has arisen e the top-level printing object gets destroyed. Thus printing can be viewed as a sequence of “print elements”, each described by a woR_pRINT struct. The possible types of print elements can be seen from the list of possible bits that the application can set, each time, in the £1ags field of the woR_PRINT struct: WDR_PRINT_PAGE this print element is to go on a new page (ie a page break will be forced, if not already on a new page) WDR_PRINT_LINE this print element is to go on a new line (ie a line break will be forced, if not already at the start of a line) WDR_PRINT_FONT this print element is to be in a specified font and font style WDR_PRINT_RIGHT this print element consists, in part, of moving the print position right by a specified amount WDR_PRINT_TEXT this print element consists, in part, of text to be printed (in cases where WDR_PRINT_RIGHT Is set as well, the movement right takes place before the printing of the text) WDR_PRINT_END this print element terminates the print process WDR_PRINT_KEEP the line containing this print element is to be kept, if possible, on the same page as the following print element WDR_PRINT_IDLE (for advanced use only - see later). Calculation of page breaks Note that applications in general have no need to calculate the positions of page breaks, as they print. System code, inside the pacrs class in FORM, automatically calculates when page break instructions should be emitted to the printer. This calculation takes all the following into account: e =©Any explicit instructions from the application, on account of print elements with the bits WDR_PRINT_PAGE and/or WOR_PRINT_KEEP set e The length of the page, as supplied by the user in the ‘Print setup’ dialog for the application e =The height of each line, and (in effect) the spacing between each line, as supplied in the height and down fields in print elements with woR_PRINT_LINE Set. System code (in pacEs) also takes care of printing relevant headers and footers at the top and bottom of each page - with the contents of the header and footer being supplied by the user in the ‘Print setup’ dialog for the application. Calculation of line breaks The situation as regards calculation of line breaks is rather different from that of the calculation of page breaks. Whereas the pacss class handles “vertical” formatting (such as pagination), it is up to other software (eg that in LPRINTER) to handle “horizontal” formatting (such as word-wrap). Thus the only time pacss repositions the print position back to the beginning of a line is when WDR_PRINT_LINE is explicitly set in the flags field of a print element. Moreover, Paces never prints any text, when moving horizontally, other than that supplied to it in a print element, which contrasts (again) with the case when moving vertically - since headers and footers are added in automatically, by paces, whenever they are required. 13-2 13 PRINTING Printer units Values set in the down, indent, height, and right fields in a print element must be supplied in printer units. These units vary from printer to printer (corresponding to the different degrees of resolution these printers support). Now it might at first seem that having to deal with printer units conflicts with the general philosophy of WDR printing, in which application code doesn't have to worry about which printer has been selected by the user. However e there are system services, such as the 1pr_sense_buf_width method of LpRINTER, to calculate the width, in current printer units, of a specified buffer of text e there are other system services, such as the wdr_twips_to_xy method of the wor class (see later for more details of this class), to assist with the transformation of printer independent units (ie “twips”, where there are 1440 twips to an inch) into printer dependent ones e simple uses of LPRINTER can avoid the need to specify units altogether, since they can accept the default values of the down, indent, height, and right fields in any print element. Note that the reason why word wrap and pagination calculations must take place in printer units is to avoid unnecessary rounding errors in the use of any other units. The difference between INDENT and RIGHT, and between DOWN and HEIGHT Superficially, the indent and right fields in the woR_PRINT struct may seem to serve the same purpose. However, the indent field is only relevant when the woR_PRINT_LINE bit is set in flags, and the right field is only relevant when the woR_PRINT_RIGHT bit is set in flags: e when woR_PRINT_LINE is Set, the current print position is moved back to the beginning of the line, then moved down by the height of one line, and then moved right by indent e when woR_PRINT_RIGHT is Set, the current print position is simply moved right by right (if a print element contains both a woR_PRINT_LINE and a WOR_PRINT_RIGHT, the former is executed before the latter). The indent field is intended for use, as its name implies, as the “left indent” (or “first line indent’’) parameter of a paragraph of formatted text, whereas the rignt field is intended for use as the spacing between two columns of data, or (more simply) as the representation of tab characters. Note that the action of any right movement adds on to the current horizontal offset of the print position - as established by earlier indents, rights, and text printing on that line. The difference between the down and height fields may also require some attention. Both fields are relevant only when woR_PRINT_LINE is set. Normally, the two values are simply added together, and the sum is taken as the amount, in printer units, to advance the print position vertically, before starting to print the given line of text. However, code in paces automatically zeros the supplied value of down if the given line of text would be the first on a page. For example, applications that wish to implement some measure of inter-paragraph spacing additional to inter-line spacing (or which wish, more generally, to separate related groups of printed lines by extra spacing in between these groups) should place this additional spacing in the down field. This will ensure that extra spacing is not printed, unnecessarily, at the tops of pages. (Blank spacing at page tops, of this form, would be seen, on occasion, if extra spacing between groups of lines were implemented, at the application level, simply as blank lines.) Margins and page size There is no need for application code to attempt to set the indent value in such a way as to include the margin at the left hand edge of the page (as set, by the user, inside the ‘Print setup’ dialog for the application). Likewise, nor is there any need to try to set the first height or down values to try to include the margin at the top of the page. Rather, these adjustments are automatically made by system code. In other words, zero is a suitable default value for both indent and down. As mentioned before, there is no need, either, for applications to determine the length of the printing area of the page (ie the total page length, minus the sum of the top and bottom margins), since pagination is handled by code in paczs. Nor, in general, does an application that uses LPRINTER need to investigate the width of the printing area of the page, since LPRINTER handles all (simple) word-wrap automatically. 13 -3 OBJECT ORIENTED PROGRAMMING GUIDE The PRINTER class and storage of the ‘Print setup’ dialog settings Whenever a standard ‘Print setup’ dialog is invoked in an application, the values displayed and edited in this dialog are stored within the property of an instance of the PRINTER class. The PRINTER class is in the FORM library and, in addition to allowing these data values to be stored, this class also contains general supervisory logic to do with printing. (The printer class also encapsulates knowledge of read/write access to the p$? printing environment variables, described in the WDR Printing chapter of the Additional System Information manual.) Some applications may choose to create a PRINTER instance as part of their standard initialisation. Other applications only create one such instance when they are about to: e print (or print preview) e run the print setup dialog. HWIM code relies on the handle of any instance of PRINTER being written to the wserv.printer field within the property of w_ws. On the Series 3a, this handle is also written to the appman. spare! field of w_am, where it can be accessed by FORM code (eg low level print preview code). However, HWIM applications have no need to write, themselves, the handle of the PRINTER object into either of these places. This is handled by system code, mainly inside the ws_ens_print_context method of wsErv. The contents of this method (which has no parameters) are, effectively, METHOD VOID wserv_ws_ens_print_context (PR_WSERV *self) { if (!self->wserv.printer) self—>wserv.printer=f_newsend (CAT_HWIM_FORM, C_PRINTER, O_PR_INIT) ; } Note that HWIM code automatically sends w_ws a ws_ens_print_context message in the following cases: e at the beginning of the ws_edit_print_context method of wserv - the method which applications invoke (see below) to run the standard ‘Print setup’ dialog suite e at the beginning of the 1pr_init method of LpRINTER - the method which applications invoke (see below) in order to print or to print preview. Consequently, most applications never need to call ws_ens_print_context directly. The exception is if an application wishes, for various reasons, to maintain an instance of PRINTER at other stages of its lifetime. For example, an application may store some of the print context (the property of the PRINTER class) to file, and restore that context when the file is opened again. In this case, typical action would be for the application to call ws_ens_print_context itself, during its file loading code, and then to set various parts of the in-memory print context, using methods of the printer class, passing data from file as parameters. (An example of code to achieve this is given near the end of this chapter.) Changing font or font style while printing One of the aspects of the “print context” is the so-called “default printing font”. Code in LPRINTER sets this font (and its associated style) by default into the typf£, fheight, and style fields in the woR_PRINT struct. Here, note that a font, for printing purposes, is identified by a combination of its “typeface number” (the typf value) and its “font height” (the fheight value). For background information about fonts and typefaces, see the chapter WDR Printing in the Additional System Information manual. Most applications will find it convenient not to adjust the typf or fheight values in any print element. However, applications may well wish to augment the style field, on occasion. For example, certain parts of the printed output might benefit from being emphasised in bold or in italics. In this case, note that the allowed values in the style field are bit combinations of woR_sTYLE_NORMAL, WDR_STYLE_UNDERLINE, WDR_STYLE_BOLD, WDR_STYLE_ITALIC, WOR_STYLE_SUPER, and wDR_STYLE_suB - where all the meanings are obvious from their names. Note also that variant style values should be in general be orred into the default ones supplied by system code, rather than completely overwriting these values. This is to preserve the freedom of the user, in the ‘Print setup’ dialog, to choose a “default printing font” with style other than normal. 13-4 13 PRINTING Applications that support multiple fonts (or multiple font heights) should be aware of the set of fonts (and font heights) supported by the current printer model (eg Epson, HP, Postscript), as selected by the user in the ‘Print setup’ dialog. Thus if the application provides the user with a “palette” of possible “character styles” (or whatever), the set of fonts from which the user is allowed to choose ought to match the set known to the loaded printer model. There are methods of the wor class that allow access to this information (see later in this chapter for more details). The text referenced in a print element There are a couple of potential errors that applications should watch out for, regarding the lifetime of the buffer referenced by the buf and bien fields of a print element. One is somewhat obvious; the other less so (but it only applies to applications that make explicit use of the woR_PRINT_KEEP flag). In the first place, this buffer must, obviously enough, continue in existence after return of the application callback routine (eg the 1pr_sense_text method) that sets up the print element. Thus it would be a grave error to assemble a print buffer on the stack of this routine. More subtly, consider a line of printed output with the wor_PRINT_KEEP flag set. Clearly, system code cannot print this line straightaway, on the current page of output, since it must first process at least one additional line, to see whether the first line should instead be held over for a new page (eg if there is no room to place this line and the following one on the current page). For this reason, any print buffer associated with a line with woR_PRINT_KEEP set must have a more permanent existence, and can only be re-used with due care. Limitations with the WODR_PRINT_KEEP flag Note incidentally that the wor_pRINT_kKEEP flag only has effect if it is set in the first print element in a line of output. Setting this flag for print elements other than those which also contain woR_PRINT_LINE has no effect. Applications which require more complicated pagination scenarios will need to perform a “pagination pass” prior to actually printing (this happens, in different ways, in both the Series 3 Word Processor and the Series 3 Spreadsheet) and will thereafter only set the woR_PRINT_pPaGE flag, and never the WDR_PRINT_KEEP flag. The need to specify font and style for each line One more potential problem should be pointed out (though this will be of concern only to programmers who interact more deeply with the paczs class). On the face of things, if the font never changes, throughout the course of printing, there oughtn't to be any need to specify the typf, fheight, and fstyle values anew for every print element. However, it must be borne in mind that two print elements which the application regards as being contiguous may, in the actual course of printing, end up on two different pages, with a footer and a header separating them. Given that the user is, in general, free to specify the print fonts of the header and the footer to differ from that used in the body of the page, it can now be appreciated why, as far as pacEs is concerned, it is important for application-generated print elements to have their print font specified explicitly each time. Programmers needn't worry about any particular inefficiency this might entail. Font change instructions are sent to the printer itself only when there is an actual change in between two adjacent print elements. Use of WDR_PRINT_IDLE A small proportion of applications may find themselves unable to generate print elements immediately (ie sufficiently quickly) in response to a system callback. For example, fetching and assembling the data to print may involve one or more application-specific active objects. This kind of application may wish to make use of print elements with woR_PRINT_IDLE Set. If paces finds that woR_PRINT_IDLE is set in any print element, the rest of that print element is disregarded and the print subsystem is effectively placed into a state of suspension. Thus no more application callbacks will take place until the print system is restarted by an explicit application call. The way that the application lets system code know to restart the print subsystem is to send the paces object an ao_queue message. Applications using LPRINTER can find the handle of the paces object in the lprinter.pages field in its property. 13-5 OBJECT ORIENTED PROGRAMMING GUIDE Using LPRINTER for standard printing purposes The printing requirements of most applications can be met by them defining an application-specific subclass of LPRINTER, in which they e ~=REPLACE the method ipr_sense_text (which is pererred at the LPRINTER level) e declare sufficient property to be able to keep track of the progress of printing. Then when the user invokes the ‘Print’? menu command in the application: e the application may choose to present a dialog (a so-called ‘Print details’ dialog), to collect parameters describing which portions of its data are to be printed, and (possibly) with what options e next, the application creates its subclass of LPRINTER and sends it an 1pr_init message e this message does not return until printing has completed (internally, a call to am_start is made); accordingly, the next lines of code can destroy the LPRINTER subclass object e however, in the meantime, the ipr_sense_text message will have been called repeatedly. Thus typical command manager code, in the print method, might look like RunPrintDetailsDialog(); hDestroy (f_newsend (CAT_APP_APP, C_APP_LPRINTER, O_LPR_INIT) ); Note: this code fragment assumes that the results of the “print details” dialog is available to the LPRINTER subclass via global data. An alternative approach would of course be to REPLACE the ipr_init method too, so that the command manager code would become something along the lines of PRINT_DETAILS_RBUF rbuf; RunPrintDetailsDialog(&rbuf) ; hDestroy (f_newsend (CAT_APP_APP, C_APP_LPRINTER, O_LPR_INIT, &rbuf) ); with the contents of the 1pr_init method looking like METHOD VOID app_lprinter_lpr_init (PR_APP_LPRINTER *self,PRINT_DETAILS_RBUF *prbuf) { self—>app_lprinter.prbuf=prbuf; p_supersend2 (self,O_LPR_INIT); /* calls am_start */ } The syntax of the LPR_SENSE_TEXT callback (Hwif programmers may note that this is the same as the syntax of the printLine callback function, where PrintLine is the function name passed as the parameter to nhprint.) This method passes the single parameter pr, which is a pointer to a woR_PRINT data structure. Application code adjusts one or more or the fields in *pr, as described above. The return value from 1pr_sense_text has the following significance: e a zero return value means that all print elements have already been defined, and that the print subsystem should now terminate e¢ anon-zero return value means that the application has not finished printing yet, and expects additional calls to ipr_sense_text to be made in due course. Note that LPRINTER fills in many of the parts of «pr before sending seit the 1pr_sense_text message: e the flags field is set to woR_PRINT_FONT | WDR_PRINT_LINE |WDR_PRINT_TEXT e the typf, fheight, style, and height fields are all set to values reflecting the default font and default font style (which the user can specify in the ‘Print setup’ dialog suite e =the down and indent fields are both set to zero. Thus all that needs to be filled in, in most cases, are the but and bien fields. On some occasions, the value of flags may need to be adjusted; likewise the down, indent, and right fields. Finally, a value will need to be provided for right in any case when wOR_PRINT_RIGHT is Set in flags. (See below for some examples.) 13 - 6 13 PRINTING LPRINTER and word-wrap As mentioned above, LPRINTER takes care of word-wrap automatically on behalf of applications: if the text at pr->buf is too wide to fit on the space remaining to the end of the line, LpRINTER splits it up into two or more sections, and thus creates two or more print elements (without any intervention being required to this end from the application). For the print elements formed in this way: e for new lines, the indent value is taken from the subsqind field within LpRINTER property (subclasses can write directly to this field within application code - see below for an example) e unless the user has disabled widows/orphans control in the ‘Print setup’ dialog suite, the flag WDR_PRINT_KEEP is orred into all but the last of these print elements. Note that the word-wrap calculation presupposes use of the default font and the default style throughout. If an application prints using multiple fonts or font-styles, it may need to subclass the 1pr_read method of LPRINTER, instead of the 1pr_sense_text method - see later for more details. Working out widths If, unusually (eg to support printing in columns), an application needs to know more about relative widths of various strings of text, the 1pr_sense_buf_width method may be used. This takes two parameters: ¢ aTExt* pointer to the buffer of text ¢ an InT giving the length of the buffer. This method returns the width of the buffer, in current printer units. Note that this method cannot be accessed until inside one of the 1pr_sense_text callbacks - since it relies on fields in LPRINTER property that are not set up until just before the first such callback. Note also that this calculation presupposes use of the default font and the default style. See later for how to calculate widths of text in other fonts and styles. Another width value that may be of interest to applications is stored in the width property field of LPRINTER. This is the width, in printer units, of the printing area of the page (ie the complete page width less its left and right margins). Again, this value is not available prior to the first callback to lpr_sense_text. Launching the print setup dialog suite If (as normal) an application that supports print also supports a ‘Print setup’ menu command, the code that is required in the command manager method for print setup is usually just the single line: p_send2 (w_ws, O_WS_EDIT_PRINT_CONTEXT) ; For interest's sake, the entire contents of the ws_edit_print_context method of wsErv is given here: METHOD VOID wserv_ws_edit_print_context (PR_WSERV *self) { p_send2 (self,O_WS_ENS_PRINT_CONTEXT) ; runHwimDialog (-SYS_PRINT_CONTROL_DL, C_PRNCTRL, NULL) ; } Examples of use of LPRINTER This section presents two related examples of the use of LpRINTER. Both are complete applications in their own right. Each example allows the user to specify a start date, and a number of days, and then prints the list of dates specified, in expanded form, eg as follows: Monday, 18th October 1993 Tuesday, 19th October 1993 Wednesday, 20th October 1993 The user is also able, in each case, to specify a “separation” between months, which can be either “No gap’, “Half a line”, or “Complete line”. Also in each case, the user is given an opportunity, before the printing actually takes place, to alter the values in the ‘Print setup’ dialog. 13-7 OBJECT ORIENTED PROGRAMMING GUIDE The second example builds on the first, adding additional formatting to produce printed output in two columns, with effect as in: Monday, 18th October 1993 Tuesday, 19th October 1993 Wednesday, 20th October 1993 On installation of the OOP component of the SDK, the source code for the second example is copied into a \sibosdk\tprint directory. Framework of the example applications Unusually, these applications have no menu bar or client window. This is possible because, throughout their lifetimes, a dialog is always presented: e first, the ‘Print details’ dialog of the application e = next, the ‘Print setup’ dialog e finally, the HWIM ‘Printing’ dialog, that keeps track of the progress of printing. In implementation terms, all this happens inside the ws_dyn_init initialisation callback to the wsERv subclass of the application. Code never returns from here, since, after the printing is complete, a call to p_exit 1s made. The entire contents of the category file, demo.cat, of the first example, are as follows: IMAGE demo EXTERNAL olib EXTERNAL hwim INCLUDE hwimman.g INCLUDE dlgbox.g INCLUDE lprinter.g INCLUDE time.g CLASS dewserv wserv { REPLACE ws_dyn_init } CLASS dedlg dlgbox { REPLACE dl_key TYPES { typedef struct { UWORD dayno; UWORD ndays; UWORD gap; } RBUF_LP; } CLASS delp lprinter { REPLACE lpr_init REPLACE lpr_sense_text PROPERTY 1 { VOID *time; RBUF_LP rbuf; TEXT buf [LN_TIME_DATE_STR]; } 13-8 13 PRINTING From this, it can be seen that there are four key objects in the application: e =the dewserv object, which provides the ws_dyn_init method the pepe object, which supervises the ‘Print details’ dialog of the application e the pELp object, which is a subclass of LPRINTER ¢ atime object, which is used to obtain the textual representations of the dates printed. The ‘Print details’ dialog The following resources define the ‘Print details’ dialog of the application (in demo.rss): RESOURCE MENU delp_gap_chlist { items = { CHOICE_ITEM { str="No gap";}, CHOICE_ITEM { str="Half a line"; }, CHOICE_ITEM { str="Complete line"; } he } RESOURCE DIALOG delp_dlg { title="Print list of days"; flags=DLGBOX_NOTIFY_ENTER | DLGBOX_RBUF_FILLED; controls= { CONTROL { class=C_DTEDIT; prompt="Start date"; info=DTEDIT { flags=IN_DTEDIT_DDMMYYYY; }; hy CONTROL { class=C_NCEDIT; prompt="Number of days to print"; info=NCEDIT { low=10; current=20; high=200; }; hy CONTROL { class=C_CHLIST; prompt="Space between months"; info=CHLIST { rid=delp_gap_chlist; }; } M; } When run, the dialog looks like Print list of days ‘Start date FE} 16-1993 "Humber of days to print 2A "Space between months Ho gap 13-9 OBJECT ORIENTED PROGRAMMING GUIDE Code in the di_key method of the piGBox subclass senses the values in the fields in this dialog into an RBUF_LP result buffer: #include #include #pragma METHOD_CALL METHOD INT dedlg_dl_key (PR_DLGBOX *self) { RBUF_LP *prbuf; prbuf=self-—>dlgbox.rbuf; prbuf->dayno=hDlgSenseDtedit (1); prbuf->ndays=hDlgSenseNcedit (2) ; prbuf->gap=hDlgSenseChlist (3) ; return (WN_KEY_CHANGED) ; } Startup code and WS_DYN_INIT code This dialog code is invoked (indirectly) from the code in the ws_dyn_init method of the application: #include #include #include LOCAL_C INT LaunchDialog(INT class, INT resid,VOID *rbuf) { DL_DATA dld; dld.id=resid; dld. rbuf=rbuf; dld.pdlg=NULL; return hLaunchDial (CAT_DEMO_DEMO, class, &dld) ; } #pragma METHOD_CALL METHOD VOID dewserv_ws_dyn_init (PR_DEWSERV *self) { RBUF_LP rbuf; if (LaunchDialog(C_DEDLG, DELP_DLG, érbuf) ) { p_send2 (self,O_WS_EDIT_PRINT_CONTEXT) ; hDestroy (f_newsend (CAT_DEMO_DEMO, C_DELP, O_LPR_INIT, &rbuf) ); } p_exit (0); } In turn, this code is invoked (again indirectly) from that in main: #include GLDEF_C VOID main(VOID) { IN_HWIMMAN app; IN_WSERV wserv; p_linklib(0); app. flags=FLG_APPMAN_RSCFILE|FLG_APPMAN_SRSCFILE|FLG_APPMAN_CLEAN; app.wserv_cat=p_getlibh (CAT_DEMO_DEMO) ; app.wserv_class=C_DEWSERV; wserv.com_cat=p_getlibh (CAT_DEMO_HWI™) ; wserv.com_class=C_COMMAN; p_send4 (p_new (CAT_DEMO_HWIM, C_HWIMMAN) ,O_AM_INIT, &app, &wserv) ; } 13 - 10 13 PRINTING Note incidentally that the resource file for this application only contains three resources in all: the two listed above, which define the ‘Print setup’ dialog, plus a “dummy” wseERv_1nro resource defined as follows: #include #include RESOURCE WSERV_INFO demo_accs { menbar_id=0; first_com=0; accel={'x'}; } (Although the application has no true menu bar - since it never returns to a “base state” without a dialog being present - it is a requirement of alJ HWIM applications that the first resource in their own resource file is a wsERv_InFo. This resource is loaded by system startup code, and must be present.) The LPRINTER initialisation code (first example) The LPRINTER subclass initialisation code has two parts: e that which takes place outside of any 1pr_sense_text callback e that which takes place during the first callback to 1pr_sense_text (by which time, all required system initialisation will be complete). These take place, respectively, in the routines delp_lpr_init and InitTimeobject, with the former being called from code in the ws_dyn_init method (see above), and the latter from code in 1pr_sense_text (see below): #include #define TIME_FMT_FLAGS (PR_TIME_MONTH NAME | PR TIME_SUFFIX_NAME | PR_TIME_DAY_NAME) LOCAL_C VOID InitTimeObject (PR_DELP *self) { P_DAYSEC ds; SE_TIME_ FORMAT fmt; ds.day=self->delp.rbuf.dayno; ds.sec=0; self—>delp.time=f_new (CAT_DEMO_OLIB,C_TIME) ; p_send4 (self-—>delp.time, O_TO_SET, SET_TIME_DAYSEC, &ds) ; fmt .flags=TIME_FMT_FLAGS; p_send4 (self->delp.time, O_TO_SET_FORMAT, &fmt, TIME_FMT_FLAGS) ; } #pragma METHOD_CALL METHOD VOID delp_lpr_init(PR_DELP *self,RBUF_LP *prbuf) { self—>delp.rbuf=(*prbuf) ; p_supersend2 (self,O_LPR_INIT); } 13-11 OBJECT ORIENTED PROGRAMMING GUIDE The LPR_SENSE_TEXT method (first example) METHOD INT delp_lpr_sense_text (PR_DELP *self,WDR_PRINT *pr) { P_DATE dt; if (!self->delp.time) InitTimeObject (self); else if (!self—->delp.rbuf.ndays) return (FALSE) ; else p_send4 (self—>delp.time,O_TO_ADD_DAYS,1,0); p_send4 (self-—>delp.time, O_TO_SENSE, SENSE_TIME_DATESTR, &self-—>delp.buf[0]); pr->buf=(&self—>delp.buf[0]); pr->blen=p_slen(pr->buf) ; if (self->delp.rbuf.gap) { p_send4 (self—>delp.time, O_TO_SENSE, SENSE_TIME_DATE, &dt) ; if (!dt.day) { pr->down=pr->height; if (self->delp.rbuf.gap==1) pr->down>>=1; } self—>delp.rbuf.ndays--; return (TRUE); } Second example: additional initialisation code To modify the example so that the printing takes place in columns, all that has to change is e the definition of pzLp in the category file e the pexp code (of course) ¢ one new sTRING resource is added to the resource file. The additional initialisation determines the values of some new property fields in pELp. The new class definition of pzLp becomes CLASS delp lprinter { REPLACE lpr_init REPLACE lpr_sense_text CONSTANTS { DELP_STATE_COL1 0 DELP_STATE_COL2 1 DELP_STATE_COL3 2 } PROPERTY 1 { VOID *time; UWORD colwid; UWORD right; UWORD state; RBUF_LP rbuf; TEXT dname [E_MAX_DAY_NAME] ; TEXT buf [LN_TIME_DATE_STR]; } 13 - 12 13 PRINTING The new initialisation routine FindWidthFirstColumn, called from within the first visit to delp_lpr_sense_text (Just after Init TimeObject is called) calculates the required width, in printer units, for the first column of printed output. This works as follows: LOCAL_C VOID FindWidthFirstColumn(PR_DELP *self) { INT i; TEXT buf [E_MAX_DAY_NAME]; UWORD wid; for (i=0; i<7; itt) { p_nmday (&ébuf[0],i); wid=SenseBufWidth (self, &buf[0]); if (wid>self-—>delp.colwid) self—>delp.colwid=wid; } self->delp.colwid+=SenseBufWidth(self," "); /* two spaces */ if (self->delp.colwid>(3*self->lprinter.width/4) ) { hinfoPrint (DELP_PAPER_NARROW) ; p_sleep (20); p_leave (RUN_ACTIVE_CLEANUP_NONOTIFY) ; } self—>lprinter.subsqind=self->delp.colwid; } The routine senseBufWidth, used within rindwidthFirstColumn, 1s just a convenience layer over the lpr_sense_buf_width method of LpRINTER: LOCAL_C UINT SenseBufWidth(PR_DELP *self,TEXT *pb) { return (p_send4 (self,O_LPR_SENSE_BUF_WIDTH, pb, p_slen (pb) )); } Note one more feature of the code in FindWidthFirstColumn - the check that the width required for the first column does not leave too little room left for the remaining column. A more professional application may wish to take more sophisticated action in this kind of situation. (For example, the Series 3 Spreadsheet application performs a pre-printing “pagination” run that determines how many pages horizontally are required, to print a given range of spreadsheet columns.) The three states in printing a two-column display In this example, where there are only two columns per date to be printed, the process of printing each date string is split into three separate visits to the lpr_sense_text method, resulting in three different print elements: e the first visit prints the text of the first column: since this starts a new line, the default PRINTER flags of woR_PRINT_LINE and woR_PRINT_TExT are left as they are; however, the width of the text actually printed (which will not exceed that of the column itself) is determined, by making another call to senseBufWidth, in order that the amount by which the print position should be moved right, in the next print element, can be known e the second visit consists just of moving the print position forward from the end of the text printed in the first column to where the text in the second column should start; the default flags WDR_PRINT_LINE and woR_PRINT_TEXT must be removed in this case, and the flag WDR_PRINT_RIGHT Set instead e finally, the third visit consists of printing the text for the second column; in this case, the flag WDR_PRINT_LINE has to be removed, but woR_PRINT_TEXT remains. (The way this mechanism would be extended to printing more than two columns should be clear enough.) The LPRINTER subclass keeps track of what it has to do next, in any particular callback, by using the state property field, which rotates around the three possible pELP_sTaTE_xxx values. 13 - 13 OBJECT ORIENTED PROGRAMMING GUIDE The code for the entire 1pr_sense_text method therefore becomes METHOD INT delp_lpr_sense_text (PR_DELP *self,WDR_PRINT *pr) { P_DATE dt; P_DAYSEC ds; if (!self->delp.time) { InitTimeObject (self); FindWidthFirstColumn (self) ; } else if (!self-—>delp.rbuf.ndays) return (FALSE) ; switch (self—>delp.state++) { case DELP_STATE_COL1: if (self->delp.rbuf.gap) { p_send4 (self-—>delp.time, O_TO_SENSE, SENSE_TIME_DATE, &dt) ; if (!dt.day) { pr->down=pr->height; if (self->delp.rbuf.gap==1) pr->down>>=1; } p_send4 (self—>delp.time, O_TO_SENSE, SENSE_TIME_DAYSEC, &ds) ; p_nmday (&self-—>delp.dname[0],P_WEEK(ds.day) ); self—>delp.right=self—>delp.colwid-SenseBufWidth (self, &self—>delp.dname[0]); pr->buf=(&self—>delp.dname[0]); break; case DELP_STATE_COL2: pr->flags&=(~ (WDR_PRINT_LINE|WDR_PRINT_TEXT) ); pr->flags |=WDR_PRINT_RIGHT; pr->right=self—>delp.right; return (TRUE) ; case DELP_STATE_COL3: p_send4 (self-—>delp.time, O_TO_SENSE, SENSE_TIME_DATESTR, &self—>delp.buf[0]); pr->buf=(&self—>delp.buf[0]); pr->flags&=(~WDR_PRINT_LINE) ; pr->indent=self-—>delp.colwid; self—->delp.rbuf.ndays--; p_send4 (self—>delp.time,O_TO_ADD_DAYS,1,0); self—>delp.state=DELP_STATE_COL1; } pr->blen=p_slen(pr->buf) ; return (TRUE) ; } More details about printing in columns with LPRINTER As mentioned above, LPRINTER checks all text passing through it, to ensure that it fits within the appropriate part of the page width. In the context of the above example of printing in columns, the text in the first column is guaranteed always to fit (otherwise the check on the relative width of the first column and the page width would have failed). However, it is certainly possible that the text in the second column might end up being wrapped. It was with an eye on this possibility that the following two lines of code were included in pELp code above: e self->lprinter.subsqind=self->delp.colwid; (iN FindWidthFirstColumn) @ = pr->indent=self->delp.colwid; (in the DELP_STATE_coL3 case in the 1pr_sense_text method). The first of these lines of code tells the superclass LpRINTER code that, if text passed to it ever does need to be word-wrapped, the second line should be positioned with a “subsequent indent” (subsqina value) as specified. (Were this left at its default value of zero, any new line required would start off in the space intended to be reserved for the left hand column.) 13-14 13 PRINTING The second of these lines of code is somewhat more obscure. The point is that LPRINTER code does not track the horizontal offset where the print position has reached. For this reason, in any word-wrap calculation, the only sensible width value for LPRINTER to wrap text within is equal to the expression self—>lprinter.width-pr->indent Now in principle LPRINTER could be used to create printed displays of the “hanging bullet” variety, in which text in the “left-hand column” always fits on just one line, whereas text in the “right-hand column” is typically longer, and can flow over several lines. However, a couple of words of caution are appropriate here: ¢ support for this kind of “hanging bullet” printing by LpRINTER Is only available, effectively, in the Series 3a version of the ROM code e in any case, no “widows and orphans” control takes place, since, as mentioned earlier, the WDR_PRINT_KEEP flag is effective only when present in the first print element of a line. In short, programmers requiring this kind of printed output would be best advised to dig deeper into the WDR print system possibilities - such as are featured in the remaining sections of this chapter. Advanced uses of LPRINTER - and beyond In this section, more features of the code in LPRINTER are gradually introduced - up to the point where it should be possible to see how to print without any use of LPRINTER - should that be desired. This section can be skipped altogether by programmers whose needs are met by the information in the preceding sections. The LPR_READ method of LPRINTER Consider the code in the ipr_read method of LPRINTER: METHOD VOID lprinter_lpr_read(PR_LPRINTER *self,INT x,WDR_PRINT *pr) { if (!self->lprinter.wdr) ReadWdrData (self); if (self—->lprinter.defer.blen) { *pr=self—>lprinter.defer; pr->indent=self-—>lprinter.subsqind; } else { pr->flags=WDR_PRINT_FONT |WDR_PRINT_LINE WDR_PRINT_TEXT; pr->typf=self->lprinter.f.fid; pr->fheight=self->lprinter.f.height; pr->style=self->lprinter.f.style; pr->height=self->lprinter.lheight; pr->down=0; pr->indent=0; if (!p_send3(self,O_LPR_SENSE_TEXT,pr) ) { pr->flags=WDR_PRINT_END; return; } } self—>lprinter.defer=(*pr) ; if (pr->indent>=self-—>lprinter.width) pr->indent=0; /* guard against incorrect use */ pr->blen=fit_line(self-—>lprinter.wtab, pr->buf,pr->blen, self—>lprinter.width-pr-—>indent, GetTextPrinterWidth) ; if (pr->blen==self—>lprinter.defer.blen) self—>lprinter.defer.blen=0; else { self—>lprinter.defer.blen-=pr-—>blen; self—>lprinter.defer.buft+=pr—>blen; self->lprinter.defer.flags|=WDR_PRINT_LINE; /* line added for Series 3a version */ if (!((PRINTER_PARAMS *) p_send2 (w_ws-—>wserv.printer,O_PR_GET_PARAMS) ) ->d.wo_control) pr->flags|=WDR_PRINT_KEEP; 13 - 15 OBJECT ORIENTED PROGRAMMING GUIDE As can be seen, this is the routine which contains the call to 1pr_sense_text - the deferred method that all LPRINTER subclasses have to provide. The role of 1pr_read is to provide a layer in between print elements defined by application code and print elements as required by the paczs class (see later for further discussion of paczs - for the moment it suffices to explain that the 1pr_read message Is sent to LPRINTER by paces). The 1pr_read method sets up defaults that are suitable for most LPRINTER subclasses, and also performs word-wrapping (via the call to the £it_1ine routine) that is, again, suitable for most subclasses. However, in some cases, this behaviour is no longer so helpful, and has to be changed - in which case the lpr_read method will, itself, have to be REPLAcEd. LPRINTER property introduced To make sense of the code in 1printer_lpr_read, reference will have to be made to various features of the full class definition of LPRINTER: CLASS lprinter root { REPLACE destroy ADD lpr_init ADD lpr_read ADD lpr_sense_buf_width DEFER lpr_sense_text PROPERTY { VOID *pages; Copy for reference only VOID *wdr; Copy for reference only SCRLAY_FONT f; The default font WDR_PRINT defer; In case previous data was too wide UBYTE *wtab; Width table UWORD lheight; Line height in printer units UWORD width; Width of region to print to UWORD subsqind; Indent for subsequent lines (if wrapped) } } The war field in property is used, amongst other ways, as a flag as to whether this is the first call to the method (in the current print session). If it is (in which case the war property field is nuuL), the initialisation routine ReadWdrData is called. See later for more details of the initialisation of the LPRINTER data. Apart from testing the value of war, the code in 1printer_lpr_read splits into two cases: e if defer.blen is non-zero, it means that the last text buffer specified by the subclass did not all fit on the space remaining on the previous printed line, and that some of this buffer remains to be processed; in this case, this call to ipr_read will not result in a call to lpr_sense_text e otherwise, there is no “deferred” text remaining, and the subclass has to be asked, by means of sending self an lpr_sense_text message, to supply another worR_pRINT “print element”. In either case, the next buffer of text is submitted to £it_line, to see whether this will all fit within the remaining page width. If an application subclasses 1pr_reaa, it may choose, however, to do one or more of the following: e skip the word-wrap calculation altogether e calculate “clipping”, whereby text that is too wide to fit within an allocated region of the page does not get word-wrapped, but rather is printed in a truncated form (with trailing or leading characters being omitted, as appropriate to the application) e make the word-wrap calculation (or a clipping calculation) more sophisticated, by allowing variable fonts or variable font styles. 13 - 16 13 PRINTING The default word-wrapping algorithm The routine fit_line is hardly sophisticated: (as well as being used within LPRINTER code, fit_line is also utilised within the ws_wrap_para method of WSERV) GLDEF_C INT fit_line(VOID *wrap,TEXT *buf, INT blen, INT wid, INT (*f) (VOID *, TEXT *, INT) ) /* Returns the number of characters, out of the buffer passed, that can form a line of up to the preset width. bd INT nchars; INT thischar; INT safe; INT seenbreak; nchars=0; seenbreak=FALSE; while (blen--—) { thischar=(*buf) ; wid-=(*f) (wrap, buf++,1); nchars++; if (thischar==' ') { safe=nchars; seenbreak=TRUE; } else if (wid<0) /* right margin burst */ return(seenbreak? safe: nchars-1l); } return (nchars) ; } As is evident, the only word delimiter recognised by £it_1ine is the space character. Now a fundamental limitation of £it_1ine is that all the characters in the buffer passed to it are assumed to belong to the same font (and to have the same style). Thus the callback function £ (which is Get TextPrinterWidth in the case when fit_line is called from 1iprinter_lpr_read) disregards the offset of characters within the buffer (for example, the width of the first ‘P in “ITALIC” would always be taken as the same as that of the second ‘T’ in that word). Subclasses that REPLACE 1pr_read may in fact consider any of the following three kinds of modifications to this method of reckoning widths: e text in one column may have a different font and/or style than that in another column e the text within one column (or, in the case when there is only one column across the whole page, the text within one line) may itself contain more than one font and/or style e special characters, such as tabs, may need additional consideration. The third of these possibilities is beyond the scope of this chapter. However, for each of the first two possibilities, it is clear that a generalisation of the Get Text Printerwidth routine will be required. Calculating widths of text with variable font The ipr_sense_buf_width method of LpRINTER simply consists of a call to this same routine, GetTextPrinterWidth: METHOD INT lprinter_lpr_sense_buf_width(PR_LPRINTER *self,TEXT *buf,INT len) { return (GetTextPrinterWidth (self—>lprinter.wtab,buf,len)); } 13-17 OBJECT ORIENTED PROGRAMMING GUIDE In turn, the code for Get Text Printerwidth 1s as follows (note: this code is also the same as the wdr_sense_width method of the wor class): LOCAL_C INT GetTextPrinterWidth(VOID *pwid, TEXT *buf,INT len) /* Return the printed width (in printer units) of buf, len */ { INT sum; if (* (UBYTE *) pwid==0) return (len* (* ((UBYTE *)pwid+t1l))); for (sum=0; len--;) sumt+=* ((UBYTE *)pwid+*buftt+) ; return (sum) ; } From this code, the structure of WDR printer font width tables can be seen. These fall into two types: e for monospaced fonts, the table is only two bytes long, with the first byte being o and the second being the common width, in printer units, of any of the characters in the font e for proportional fonts, the table is 256 bytes long, with the width of character 'a' (which has ASCII value 65), say, being the 65th byte in the table, and so on. Where printer font width tables come from The field wt ab In LPRINTER property is the address of the font width table of the default font for the currently selected printer model. The value of wt ab is filled in by the following line of code in the ReadWdrData routine called during the first visit to lprinter_lpr_read: self—>lprinter.wtab=(UBYTE *)p_send5(self->lprinter.wdr, O_WDR_GET_WIDTH_TABLE, self->lprinter.f.fid, self—>lprinter.f.height,self—>lprinter.f.style); As can be seen, printer font width tables are accessed by means of the wdr_get_width_table method of the wor class. Briefly, the wor class encapsulates the logic of reading printer driver .wdr files. (See the WDR Printing chapter in the Additional System Information manual for more information about .wdr files.) There are three parameters to the wdr_get_width_table method: e the typeface number of the required font e the height identifier of that font e the particular font style required. In the case when this method is called by tpRintTER, these three parameters are taken from details of the default font associated with the current printer, and therefore are bound to be valid. But even if values are passed that are not directly known to the current printer model - for example, there could be an enquiry concerning an unknown typeface - the method will follow the standard wor customs of “font mapping” and “font substitution”: details will be supplied of the “nearest” font, font height, and style actually known to the printer model. LPRINTER initialisation - phase one As has been mentioned, the initialisation of an LPRINTER instance takes place in two stages: e some takes place within the LpR_inrT method e another portion of the initialisation cannot, however, proceed until other parts of the WDR print system have been put into a full state of readiness, and has to wait until the first print element is requested, by paczs, before taking place. 13 - 18 13 PRINTING This section looks at the first of these two stages. The code in the LPR_1in1T method of LPRINTER is as follows: METHOD VOID lprinter_lpr_init (PR_LPRINTER *self) /* Returns only when printing is complete */- { INT preview; PRINTER_PARAMS *par; RBUF_PRINTING rbuf; preview=self->lprinter.subsqind; self—>lprinter.subsqind=FALSE; p_send2 (w_ws, O_WS_ENS_PRINT_CONTEXT) ; par=(PRINTER_PARAMS *)p_send2 (w_ws-—>wserv.printer,O_PR_GET_PARAMS) ; self—->lprinter.width=par->p.pg.body.width; /* in twips at the moment */ self—>lprinter.f=par-—>d.f; if (preview) return; rbuf.calls.hread=self; rbuf.calls.mread=O_LPR_READ; rbuf.ppages=(&self->lprinter.pages); /* to take pages handle when known */ runHwimDialog(-SYS_PRINTING_DIALOG, C_PRINTING, &rbuf) ; } In fact, this is the Series 3a version of the code; the Series 3 version omits the lines preview=self-—>lprinter.subsqind; self—>lprinter.subsqind=FALSE; and if (preview) return; These are connected with support for print preview (which is not available on the Series 3) and will be discussed in more detail later in this chapter. Apart from these lines, this code e sends w_ws a ws_ens_print_context message, for reasons discussed earlier in this chapter (essentially, to ensure that an instance of the prinTER class has been created and initialised) e senses the width of the printing portion of the page, from the PRINTER instance, and also the SCRLAY_FONT structure describing the default font for the current printer model e begins to set up an appropriate initialisation struct for the pacEs active object e launches an Hwim standard dialog (the printine dialog), whose di_dyn_init method will, in turn, set more code in motion that will create and initialise pacEs e since the printinc dialog calls am_start, the LpR_inrT method does not return until this dialog has completed. A brief description of the PAGES active object class Clearly there has to be some active object involved with printing - in order that the lengthy process of printing can be interleaved with calls to the ac_run method of w_ws (for example, to service redraws, or handle keypresses); pacss is this active object. pacers in fact lies at the heart of the WDR printing subsystem; whereas it is possible for an application (for example, the Series 3 Word Processor and Database application) to perform WDR printing without making any use of LPRINTER, it is not possible to avoid using PAGES. Whilst a full description of pacrs is beyond the scope of this manual, various points should be noted. For its initialisation, pacEs requires to be initialised with, among other items, two object handles and two method numbers that collectively make up the pacEs_caLts struct: typedef struct { VOID *hread; Callback handle for reading print elements WORD mread; Callback method for reading print elements VOID *hdone; Callback handle for %Sdone & completion WORD mdone; Callback method for %Sdone & completion } PAGES_CALLS; 13 - 19 OBJECT ORIENTED PROGRAMMING GUIDE Whenever paces requires another print element, for the body area of a page, it sends hread an mread message. In the case when printing is started by LPRINTER, hread Is set to the handle of the LPRINTER object itself, whereas mread is set equal to o_LPR_READ - aS can be seen in the above code from lprinter_lpr_init. Whenever pacss has an event to report to the user, it sends hdone an mdone message. These events include: the end of a page, the end of a printing session, and an error condition. In fact, when printing is started by LPRINTER, hdone is set, in due course, to the handle of the printine dialog, and mdone is set equal to 0_PRINTING_PRINTING. For interest, part of the code of the HWIM printine class follows: LOCAL_C UINT GetFirstPageToPrint (VOID) return(((PR_PRINTER *) (w_ws—>wserv.printer) )->printer.p.p.pgbeg) ; } LOCAL_C VOID SetPageNumDisplay (INT num, INT resid) { TEXT buf [40]; hAtos (&buf [0], resid, num) ; hDlgSetText (1, &buf[0]); } LOCAL_C VOID SetPageNumDisplayCheck(PR_PRINTING *self, INT num) { SetPageNumDisplay (num, (self-—>win.flags&PR_WIN_WILL_SKIP && GetFirstPageToPrint()>num)? -SYS_SKIPPING_PAGE: -SYS_PAGE_IS); } #ifdef JPIC #pragma METHOD_CALL #fendif METHOD VOID printing_printing_done(PR_PRINTING *self,PAGES_DONE *d) { switch (d->event) { case PAGES_DONE_PAGE: SetPageNumDisplayCheck (self,d->page) ; break; case PAGES_DONE_ERROR: case PAGES_DONE_END: self—>printing.pages=NULL; p_send2 (self,O_DESTROY) ; break; } } Note that, after sending hdone a PAGES_DONE_END Of PAGES_DONE_ERROR event, paces destroys itself. This is the reason why the above printiNc code nulls its copy of the handle of pacrs (otherwise, the destroy method of print1Nc would attempt to destroy pacEs a second time, in the standard procedure of automatic destruction of component objects). Finally note that a destroy message can also be sent to PRINTING as a result of the user pressing the ESC key - this happens automatically on account of standard picBox level code; in this case, it is appropriate for pacEs to be destroyed as a consequence of PRINTING being destroyed; this is how printing terminates in response to the user's “Abandon” request. More about the interface to and from PAGES Two cases where application code might send messages directly to pacrs are as follows: e if the application makes use of the flag woR_PRINT_IDLE when it prints - in which case, as explained earlier in this chapter, it needs to send the paces object an ao_queue when it has determined what the next print element is e if the application needs to destroy the pacrs object (but this will only arise if the application takes over the code that is, by default, handled by the destroy method of the printine class). In most cases, however, application code will never have any reason to send a message directly to the Paces object. Instead, the parts of the interface to and from paczs that are more likely to need to be understood are: e how to create and initialise the paczs object e the nature of the mreaa and the mdone callbacks from pacEs. 13 - 20 13 PRINTING A paces object is actually created by sending a message to the PRINTER object. There are in fact three very similar methods, all of which create and initialise paces in one way or another: e the pr_print method creates and initialises pacgs in printing mode e the pr_paginate method creates and initialises pacEs in paginating mode e the pr_preview method (Series 3a only) creates and initialises pacrs in print preview mode. In each case, there is one additional parameter - the address of a paczESs_caLus Struct, as described earlier. In each case, the method returns the handle of the pacgs object created. Thus code called from inside the ai_dyn_init method of the HWIM print1nc class (which is itself called from code inside the 1pr_init method of the LpRinTER class) contains the following: METHOD VOID *printing_printing_do_print (PR_PRINTING *self,PAGES_CALLS *pcalls) { return((VOID *)p_send3 (w_ws->wserv.printer,O_PR_PRINT,pcalls) ); } METHOD VOID printing_dl_dyn_init (PR_PRINTING *self) { RBUF_PRINTING *rbuf; rbuf=self-—>dlgbox.rbuf; rbuf->calls.hdone=self; rbuf-—>calls.mdone=O_PRINTING_DONE; self—>printing.pages=(VOID *)p_send3(self,O_PRINTING_DO_PRINT, &rbuf->calls) ; if (rbuf->ppages) *rbuf—>ppages=self—>printing.pages; } Note that the paginating mode of pacss is provided specially for use by window objects based on the FORM scruay and scrime classes - such as the main windows of the Word Processor and Database applications. The fact that the pagination logic is so similar to printing logic needn't particularly concern most users of paces. The only potentially significant point concerns the parameters passed back to each mread callback. As can be seen from the listing given above for the 1pr_read method of LpRINTER, a somewhat mysterious second parameter (called x in that listing) is passed. The actual significance of this is in whether or not paces is being run in paginating mode. Various optimisations can be made in this case within scruay callback code. (To be completely accurate, the callback in this case is to the pRNLAY subclass of scruay.) However, as is clear, this parameter is totally ignored when the callback is made, instead, to LPRINTER code. A brief description of the WDR class The wor class shares with pacss the feature of lying at the heart of the WDR printing subsystem. Whilst PaGEs Is the active object class that actually drives page formatting and printing, wor supports various query functions concerning the current printer model. For example, the wdr_get_width_table method has already been mentioned. Another useful service of the wor class is that of converting a null-terminated sequence of uworp values from twips into current printer units. The method involved here is war_twips_to_xy. An example of the use of this method is in the ReadWwdrData function already mentioned: LOCAL_C VOID ReadWdrData(PR_LPRINTER *self) { UWORD *1x[2]; UWORD *ly[2]; self—>lprinter.lheight=self—->lprinter.f.height; ly [0]=&self-—>lprinter.lheight; ly [1]=NULL; 1x[0]=&self—>lprinter.width; 1x [1]=NULL; self—>lprinter.wdr=((PR_PAGES *)self-—>lprinter.pages) —>pages.in.wdr; p_send4 (self-—>lprinter.wdr,O_WDR_TWIPS_TO_XY, &1x[0],&ly[0]); self—>lprinter.wtab=(UBYTE *)p_send5(self->lprinter.wdr, O_WDR_GET_WIDTH_TABLE, self->lprinter.f.fid, self—>lprinter.f.height,self—>lprinter.f.style); 13 - 21 OBJECT ORIENTED PROGRAMMING GUIDE In order to find out the number of typefaces supported by the current printer model, code such as the following can be used: nt=((WDR_MODEL *)p_send2 (wdr,O_WDR_SENSE_MODEL) ) ->num_typefaces; where the definition of the woR_mMopEL struct is (refer also to the the WDR Printing chapter in the Additional System Information manual) typedef struct { UWORD minx; minimum delta x (in twips, unless MINX_IS_DPI flag set) UWORD miny; minimum delta y in twips UWORD skipx; amount printer auto indents UWORD skipy; amount printer auto feeds UWORD flags; orientation UWORD num_typefaces; number of typefaces supported by model WDR_TYPEFACE *typeface[1]; list of typeface rids/pointers to typeface } WDR_MODEL; For any given typeface, referred to by index number (0, 1, ...), the corresponding name and typeface number, among other things, can be found out by sending the wor object a war_typeface message, which takes the index number as a parameter, and which returns a pointer to a wdr_typeface struct: typedef struct { TEXT name [WDR_FONT_NAME_LEN]; Typeface name UWORD typeface; RTF/Word compatible typeface UWORD type; WDR_TYPF_XXX WORD trans_rid; rid of translates record UWORD num_heights; Number of different typeface heights WDR_FONT font[1]; List of different heights } WDR_TYPEFACE; Another approach to finding a typeface with a given typeface number is to send the wor object a wdr_search_typeface message, which has the following definition: METHOD INT wdr_wdr_search_typeface(PR_WDR *self,INT typf,WORD *pix,WDR_TYPEFACE **ppt) /* Write the index and address of the typeface struct with RTF/Word typeface number typf to *pix and *ppt respectively and return TRUE if a matching typeface was found. Otherwise return FALSE and write a recommended typeface or -1 if no such typeface number exists in the current model. Either pix or pt may be NULL if that part of the return is not required. */ As will be appreciated, this method contains the “font mapping/ substitution” logic of the wor class. Finally, for a given typeface, the way to determine the range of heights available (and also the recommended way of matching a desired font height) can be seen from the following code - which is actually an extract from the standard Hwim “Font selector” dialog code: LOCAL_C VOID ResetSizes(PR_FONTSEL *self,INT typfix) { UWORD n,i; WORD height; TEXT buf[12]; p_send2 (self-—>fontsel.sizes,O_VA_RESET) ; n=((WDR_TYPEFACE *)p_send3(self-—>fontsel.wdr,O_WDR_TYPEFACE, typfix) )->num_heights; for (i=0;ifontsel.wdr,O_WDR_FONT_HEIGHT,typfix,i)/20) ]=0; p_send3 (self-—>fontsel.sizes,O_VA_APPEND, &buf[0]); } height=self->fontsel.pf-—>height; hDlgSetChlist (2,p_send4 (self—>fontsel.wdr,O_WDR_SEARCH_HEIGHT,typfix, &height) ); } (Note that the values returned by the wdr_font_height method are in twips: hence the multiplication by 20, to convert into points prior to presenting the values in a dialog for inspection by users.) 13 - 22 13 PRINTING Creating and destroying WDR objects Evidently, LpRINTER code - and any other WDR printing code - relies on a suitable wor object having been created and initialised. The paczs class in particular contains a property field given the handle of a wor object, and the ao_init method of paces requires to be passed this handle as part of its initialisation data. There is also a slot for the handle of a wor object within PRINTER property. For this reason, PRINTER code called inside the pr_print method (also called by the pr_paginate and pr_preview methods) contains the following lines: if (!self->printer.wdr) p_send2 (self,O_PR_OPEN_WDR) ; The pr_open_wdr method of PRINTER is as follows: METHOD PR_WDR *printer_pr_open_wdr(PR_PRINTER *self) { INT model; TEXT name [P_FNAMESIZE]; model=p_send3 (self,O_PR_SENSE_MODEL, &name[0]); printer_pr_close_wdr (self); self—>printer.wdr=f_newsend (CAT_FORM_FORM, C_WDR, O_WDR_INIT, &name[0],model) return (self—>printer.wdr); } In turn, the pr_sense_mode1 method obtains the appropriate printer model e by preference, from data set in PRINTER property by a prior call to pr_set_mode1 (see the end of this chapter for an example of code sending a pr_set_model message) ¢ otherwise, from the psm print model environment variable e failing that, from the hard-wired default of rom: :Bg. wor. Note that this mechanism leaves open the possibility of the application creating and initialising a wor object, for its own purposes, well before the user selects any print menu command. Evidently, the way to do this is to e ensure that the PRINTER object (handle at w_ws->wserv.printer) has been created and initialised e send this object a pr_open_wdr message. Incidentally, it is perfectly possible for there to be more than one wor object in existence, at the same time in the same application (although only one of them can have its handle written into PRINTER property). Thus when dialogs inside the HWIM “Print setup” dialog suite are operational, a “scratch” wor is used at various times, in order to enquire details of .wdr printer model files other than the one to which the application is currently “logged”. On the other hand, wor objects should be destroyed as soon as they are no longer required. (For example, a considerable amount of memory may be tied up by all the font width tables that may have been loaded.) To achieve this, simply send pRINTER a pr_close_wdr message. Thus the destroy method of LPRINTER 1s as follows: METHOD VOID lprinter_destroy(PR_LPRINTER *self) { if (w_ws->wserv.printer) p_send2 (w_ws-—>wserv.printer,O_PR_CLOSE_WDR) ; p_supersend2 (self,O_DESTROY) ; } (Note that the test of whether w_ws->wserv.printer is non-null is necessary because the 1pr_init method of LpRInTER could fail prior to the completion of the call to ws_ens_print_context.) 13 - 23 OBJECT ORIENTED PROGRAMMING GUIDE In turn, the code in the pr_close_wdr method of pRinTER is, naturally enough, GLDEF_C VOID DestroyRef(PR_ROOT **ref) { if (*ref) { p_send2 (*ref,O_DESTROY) ; *ref=NULL; } } METHOD VOID printer_pr_close_wdr(PR_PRINTER *self) { DestroyRef (&self—>printer.wdr) ; } Using XPRINTER for print preview Just as there are various levels at which the subject of printing can be approached, so also are there various levels at which the subject of print preview can be approached. However, e like printing, the requirements of most applications for print previewing can be met very simply, by means of creating and using a subclass of LPRINTER - except that this time a subclass of XPRINTER is required (xPRINTER itself being a subclass of LPRINTER) e in these cases, what makes application coding particularly easy is the fact that exactly the same subclass will suffice both for printing purposes and for print preview purposes e underlying this similarity is the fact that printing and print previewing are both driven by the PAGES active object, which requires in both cases to be fed by application code with a series of print elements (the same set of print elements in both cases). The difference between XPRINTER and LPRINTER First, note that xPRINTER is defined in the XADD library, which is not present in the ROM of the Series 3, so that print preview support does not exist on the Series 3 - only on the Series 3a. (Further to this, the versions of LPRINTER on the Series 3 and the Series 3a are also critically different, in a few small but key places - though the calling interface remains exactly the same.) Next, witness the entirety of the code of xPRINTER: #include #include #include GLREF_D PR_APPMAN *w_am; GLREF_D VOID *w_ws; #ifdef JPIC #pragma METHOD_CALL #endif METHOD VOID xprinter_destroy(PR_XPRINTER *self) { if (self->xprinter.locked) p_send3 (w_ws, O_WS_LOCK, FALSE) ; p_supersend2 (self,O_DESTROY) ; } METHOD VOID xprinter_lpr_init (PR_XPRINTER *self, INT commid) { if (!commid) { p_send3 (w_ws, O_WS_LOCK, TRUE) ; self—>xprinter.locked=TRUE; } self—>lprinter.subsqind=commid; /* communicate with subclass */ p_supersend2 (self,O_LPR_INIT); if (!commid) return; f_newsend (CAT_XADD_XADD, C_PRVVIEW, O_PVV_INIT, self, commid, &self—>lprinter.pages) ; } and the entirety of the corresponding class definition: 13 - 24 13 PRINTING CLASS xprinter lprinter { REPLACE destroy REPLACE Ipr_init PROPERTY { WORD locked; } } Evidently, xPRINTER adds two pieces of functionality to LPRINTER (one rather trivial, and the other more fundamental): XPRINTER locks the application whilst it is printing, to lessen the chance of “accidents” half-way through printing due to the user inadvertently switching files from the System screen (setting the application locked means the System screen will block any attempted file switch with a “XXX is busy” infoprint) XPRINTER expects an extra parameter to its LpR_inrT method; if the value of this is zero, LPRINTER code is invoked in printing mode, whereas if it is non-zero, print preview takes place instead. The actual meaning of this additional parameter - commia - is the method number of the command manager that system code will invoke if the user chooses the ‘Print’ menu command from within the menubar available inside print preview. Extended example of print and print preview using XPRINTER To illustrate use of xPRINTER for print and print preview purposes, consider the following variation upon the example applications presented earlier: once again, the user is given the choice of specifying a range of dates to be printed the dates will be printed in two columns (as in the second of the two earlier examples) the first column will be printed in bold (by way of illustrating some features described in the middle portion of this chapter) rather than a series of dialogs following each other irrevocably, the various possible choices in the application are available, in more standard style, as choices from the menu bar there are four menu commands available: Exit, Print setup, Print preview, and Print in order to illustrate another (quite separate) point, the application also attempts to load and save its current print setup to file, on startup and on exit (though discussion of this particular feature of the application is deferred until the end of the chapter). On installation of the OOP component of the SDK, the source code for this application is copied into a \sibosdk\wdrprint directory. The category file IMAGE demo EXTERNAL olib EXTERNAL hwim EXTERNAL xadd INCLUDE hwimman.g INCLUDE dlgbox.g INCLUDE xprinter.g INCLUDE time.g INCLUDE epoc.h CLASS dewserv wserv { REPLACE ws_dyn_init } 13 - 25 OBJECT ORIENTED PROGRAMMING GUIDE CLASS decomman comman { REPLACE com_init REPLACE com_exit ADD dec_psetup ADD dec_preview ADD dec_print=decomman_dec_preview ADD dec_print_direct TYPES { typedef struct { UWORD dayno; UWORD ndays; UWORD gap; } DE_PRINT_DETAILS; } PROPERTY { DE_PRINT_DETAILS dets; } CLASS dedlg dlgbox { REPLACE dl_dyn_init REPLACE dl_key } CLASS delp xprinter { REPLACE lpr_sense_text CONSTANTS { DELP_STATE_COL1 0 DELP_STATE_COL2 1 DELP_STATE_COL3 2 } PROPERTY 1 { VOID *time; VOID *wtab; UWORD colwid; UWORD right; UWORD state; UWORD ndays; TEXT dname [E_MAX_DAY_NAME]; TEXT buf [LN_TIME_DATE_STR]; } } Command manager The four commands in the menu bar - Exit, Print setup, Print preview, and Print - are handled by the command manager methods com_exit, dec_psetup, dec_preview, and dec_print. Note that the definition ADD dec_print=decomman_dec_preview means that the single routine decomman_dec_preview handles both the menu commands Print and Print preview. This is a common feature of applications supporting print preview as well as print. As in all cases of this sort, the code can distinguish which of the two menu commands has actually been chosen by testing the value of the additional commia parameter that is always passed, by system code, to command manager methods. Thus the code for decomman_dec_preview IS METHOD VOID decomman_dec_preview(PR_DECOMMAN *self,INT commid) { if (LaunchDialog(C_DEDLG, DELP_DLG, &commid) ) PrintOrPreview (commid) ; 13 - 26 13 PRINTING where LaunchDialog is the same as in previous examples (it is an entirely standard dialog-launching utility), and printorPreview is as follows: LOCAL_C VOID PrintOrPreview(INT commid) { commid= (commid==O_DEC_PREVIEW? O_DEC_PRINT_DIRECT: 0); hDestroy (f_newsend (CAT_DEMO_DEMO, C_DELP,O_LPR_INIT, commid) ) ; } As can be seen, the parameter passed to the 1pr_init method of Exp is either ¢ 0, in the case when DELP is to print, or @ 0_DEC_PRINT_DIRECT, in the case when DELP is to print preview. Note that the dec_print_direct method of the command manager is not directly associated with the Print menu command from the base state of the application. On the contrary, as already stated, this menu command has corresponding command manager method dec_print, which is handled by the same code as the dec_preview method. The role of the dec_print_direct method is to service the Print menu command from the special Print Preview submenu that is available inside the print preview subsystem: Wdrprint Show margins Pages to display Jump to page Exit preview \ Sun 24 The code for the dec_print_direct method, in this example, is just METHOD VOID decomman_dec_print_direct (VOID) { PrintOrPreview(O_DEC_PRINT) ; } Note: the reason for using the term “direct” is that printing is to proceed directly, without any additional ‘Print details’ dialog being presented first. The print details are the same as in the dialog that invoked the print preview. Apart from the methods of the command manager already covered above, pEcomman also has the following methods: e =the dec_psetup method just has one line, sending a ws_edit_print_context message tO w_ws e =the com_init method writes the handle of the command manager into patapp2 (for convenience elsewhere in the code) and also calls the routine rryLoadPrintContext e =the com_exit method is as follows: METHOD VOID decomman_com_exit (PR_DECOMMAN *self) { if (p_enterl (SavePrintContext) ) p_delete (SAVED_FILE_NAME) ; p_exit (0); } More details of the saveprintcontext and TryLoadPrintContext methods are given nearer the end of this chapter. Print details dialog The code here contains two enhancements compared to the earlier example: e if the dialog is visited more than once, it is seeded, in the di_dyn_init method, with the values last set by the user (as sensed in the preceding di_key method) e the dialog title has to vary, to reflect whether Print preview is to follow, or Print. 13 - 27 OBJECT ORIENTED PROGRAMMING GUIDE The entire code in the dialog module for the application is #include #include #include GLREF_D PR_DECOMMAN *DatApp2; #pragma METHOD_CALL METHOD VOID dedlg_dl_dyn_init (PR_DLGBOX *self) { INT commid; commid=(* (INT *) self—>dlgbox.rbuf) ; if (commid==0O_DEC_PREVIEW) hDlgSetTitleByRid (DELP_PREVIEW_TITLE) ; if (!DatApp2->decomman.dets.dayno) return; hDlgSetDtedit (1, DatApp2->decomman.dets.dayno) hDlgSetNcedit (2, DatApp2-—>decomman.dets.ndays) hDlgSetChlist (3, DatApp2-—>decomman.dets.gap) ; } ’ ’ METHOD INT dedlg_dl_key (PR_DLGBOX *self) { DatApp2->decomman.dets.dayno=hDlgSenseDtedit (1) DatApp2->decomman.dets.ndays=hDlgSenseNcedit (2) DatApp2->decomman.dets.gap=hDlgSenseChlist (3); return (WN_KEY_CHANGED) ; } Note that the actual pz_pRINT_pETAILs data structure edited in this dialog no longer exists purely on the stack (as it did in versions of this example discussed earlier in this chapter). Instead, to give this data greater persistence, it now exists within the property of the command manager - whose handle has been written to Datapp2 for convenience. (Otherwise, this handle could be obtained from w_ws->wserv.com.) 7 ’ The way the di_dyn_init routine determines whether the dialog has been invoked before - ie determines whether there is data in the pzk_PRINT_DETAILS Struct that ought to overwrite the defaults provided by the resource controls for the dialog - is by testing the value of patapp2->decomman.dets.dayno, to see whether it is non-zero. But note that, of course, the test for whether the dialog title should be changed is quite independent of this. Application initialisation Apart from the code in the com_init method, the other initialisation code for the application is in main itself, and in the ws_dyn_init method of the wserv subclass (as can be seen, there is nothing unusual in any of it): GLREF_D WSERV_SPEC *wserv_channel; LOCAL_C INT StatusWindowWidth (VOID) { P_EXTENT ext; wiInquireStatusWindow (-1, &ext) ; return (ext.width) ; } LOCAL_C VOID InitClientWindow(PR_DEWSERV *self) { W_WINDATA wd; PR_WIN *cliwin; wd.extent.tl.x=wd.extent.tl.y=0; wd.extent .height=wserv_channel-—>conn.info.pixels.y; wd.extent.width=wserv_channel->conn.info.pixels.x-StatusWindowWidth () ; cliwin=f_newsend (CAT_DEMO_HWIM, C_BWIN, O_WN_CONNECT, NULL, W_WIN_EXTENT, &wd) ; self—>wserv.cli=cliwin; cliwin->win.flags=PR_BWIN_CORNER 4|PR BWIN_SHADOW_1; p_send3 (cliwin, O_WN_EMPHASISE, TRUE) ; hiInitVis (cliwin) ; } 13 - 28 13 PRINTING #pragma METHOD_CALL METHOD VOID dewserv_ws_dyn_init (PR_DEWSERV *self) wsSet List (W_STATUS_WINDOW_ICON, NULL, 0) ; wStatusWindow (W_STATUS_WINDOW_BIG) ; InitClientWindow(self); } GLDEF_C VOID main(VOID) N_HWIMMAN app; N_WSERV wserv; p_linklib(0); app. flags=FLG_APPMAN_RSCFILE|FLG_APPMAN_SRSCFILE | FLG_APPMAN_CLEAN|FLG_APPMAN_FULLSCREEN; wserv.com_cat=app.wserv_cat=p_getlibh (CAT_DEMO_DEMO) ; app.wserv_class=C_DEWSERV; wserv.com_class=C_DECOMMAN; p_send4 (p_new (CAT_DEMO_HWIM, C_HWIMMAN) ,O_AM_INIT, &app, &wserv) ; } XPRINTER subclass initialisation All the initialisation code for the xPRINTER subclass of the applicaiton is called inside the first lpr_sense_text callback. This establishes: ¢ atime object, suitably prepared (as in previous examples) to render textual versions of given dates, and initialised with the start date from the p—_PRINT_DETAILS data structure e aprinter font width table (handle written to wt ab) for the bold version of the default print font e the value of the colwia property field, that gives the overall width for the first column: #include #include #include GLREF_D PR_DECOMMAN *DatApp2; #define TIME_FMT_FLAGS (PR_TIME_MONTH NAME | PR TIME_SUFFIX_NAME) LOCAL_C VOID InitTimeObject (PR_DELP *self) { P_DAYSEC ds; SE_TIME_ FORMAT fmt; ds .day=DatApp2->decomman.dets.dayno; ds.sec=0; self—>delp.time=f_new (CAT_DEMO_OLIB,C_TIME) ; p_send4 (self—>delp.time, O_TO_SET, SET_TIME_DAYSEC, &ds) ; fmt .flags=TIME_FMT_FLAGS; p_send4 (self->delp.time, O_TO_SET_FORMAT, &fmt, TIME_FMT_FLAGS) ; } LOCAL_C UINT SenseBufWidth(PR_DELP *self,TEXT *pb) { return (p_send5 (self—->lprinter.wdr,O_WDR_SENSE_WIDTH, self- >delp.wtab,pb,p_slen(pb))); } 13 - 29 OBJECT ORIENTED PROGRAMMING GUIDE LOCAL_C VOID FindWidthFirstColumn(PR_DELP *self) { INT i; TEXT buf [E_MAX_DAY_NAME]; UWORD wid; for (i=0; i<7; i++) { p_nmday (&ébuf[0],i); wid=SenseBufWidth (self, &buf[0]); if (wid>self-—>delp.colwid) self—>delp.colwid=wid; } self—>delp.colwid+=SenseBufWidth(self," "); /* two spaces */ if (self->delp.colwid>(3*self-—>lprinter.width/4) ) { hiInfoPrint (DELP_PAPER_NARROW) ; p_leave (RUN_ACTIVE_CLEANUP_NONOTIFY) ; self—>lprinter.subsqind=self->delp.colwid; LOCAL_C VOID InitWidthTable(PR_DELP *self) { self—>delp.wtab=(VOID *)p_send5(self->lprinter.wdr,O_WDR_GET_WIDTH_TABLE, self—>lprinter.f.fid,self-—>lprinter.f.height, self->lprinter.f.style|WDR_STYLE_BOLD) ; } For more information relevant to parts of this code, see the appropriate sections earlier in this chapter. The XPRINTER LPR_SENSE_TEXT callback What is particularly noteworthy about the peLp code in this application (referring in fact to the totality of the code in this class) is that although there are, admittedly, differences from the pep code given for earlier examples in this chapter, these differences have nothing to do with the extra support that this class is now providing for Print Preview as well as Print. These differences are purely to do with comparatively incidental points, such as the fact that, for example, the first column is now being printed in bold. In other words, the extra support for Print Preview is achieved without any change at the LPRINTER subclass level - except that the definition of the class now specifies xPRINTER as the superclass, instead of just LpRintER. The code itself could survive unchanged: 13 - 30 13 PRINTING METHOD INT delp_lpr_sense_text (PR_DELP *self,WDR_PRINT *pr) { P_DATE dt; P_DAYSEC ds; if (!self->delp.time) self—>delp.ndays=DatApp2-—>decomman.dets.ndays; InitTimeObject (self) ; InitWidthTable (self); FindWidthFirstColumn (self); else if (!self-—>delp.ndays) return (FALSE) ; switch (self—->delp.state++) case DELP_STATE_COL1: if (DatApp2-—>decomman.dets.gap) { p_send4 (self-—>delp.time, O_TO_SENSE, SENSE_TIME_DATE, &dt) ; if (!dt.day) pr->down=pr->height; if (DatApp2-—>decomman.dets.gap==1) pr->down>>=1; } p_send4 (self-—>delp.time, O_TO_SENSE, SENSE_TIME_DAYSEC, &ds) ; p_nmday (&self->delp.dname[0],P_WEEK (ds.day) ); self—>delp.right=self-—>delp.colwid-SenseBufWidth (self, &self—>delp.dname[0]); pr->buf=(&self—>delp.dname[0]); pr->style|=WDR_STYLE_BOLD; break; case DELP_STATE_COL2: pr->flags&=(~ (WDR_PRINT_LINE|WDR_PRINT_TEXT) ); pr->flags|=WDR_PRINT_RIGHT; pr->right=self—>delp.right; return (TRUE) ; case DELP_STATE_COL3: p_send4 (self->delp.time, O_TO_SENSE, SENSE_TIME_DATESTR, &self->delp.buf[0]); pr->buf=(&self—>delp.buf[0]); pr->flags&=(~WDR_PRINT_LINE) ; pr->indent=self-—>delp.colwid; self—>delp.ndays-——; p_send4 (self—>delp.time,O_TO_ADD_DAYS,1,0); self—>delp.state=DELP_STATE_COL1; } pr->blen=p_slen(pr->buf) ; return (TRUE) ; } Comments on the differences between XPRINTER and LPRINTER One minor change that would have to be made, however, between LPRINTER Subclass code and xpRINTER subclass code, would be in any replacement 1pr_init method. This simply due to the fact that the lpr_init method of xprinteER requires the additional commid parameter - to differentiate between the case of Print Preview and the case of Print. Note incidentally that, for compatibility reasons, this extra parameter could not be added in at the LPRINTER level. This is on account of existing applications (ie pre-Series 3a applications) that interact with LPRINTER code without passing any parameter explicitly to the ipr_init method. The way around this constraint, in the development of the Series 3a ROM code, can be seen from the code given earlier for the 1pr_init methods of both LpRINTER and xPRINTER: e the subsqina property field is re-used as a temporary “postbox’”’ between the two classes e after its use, in this way, at the initialisation stage, its value is set back to zero e the rationale behind this is that the subsqina field cannot be written to, by application subclass code, until inside the first callback to ipr_sense_text (OF lpr_read). 13 - 31 OBJECT ORIENTED PROGRAMMING GUIDE WDR printing miscellany WDR printing classes pictorial overview The following class diagram shows a possible application state before printing actually starts: P$? variables,” / PRINTER > / WDR ye LWDR file | fo * ee EEO. re —_ / ¥ ph Se z Z 7 a Bile , SA aS ., \ Sh . } 4 \ J \ | pete \ aan. \ \ \ ee ee SS — C Ad genes. Git ra - Se r ie Seas a aed / oN. / S / / W_ws (user? a styles C application “ interface) O—___ dialogs Cees data See By ate 7. / printsetup } / Print details a application : C dialogs | \ dialogs O ———_ data Sa . ites i 2 \ i \ ser Nee (A typical application need not, however, contain all the application-specific components shown here. For example, applications that print will not necessarily contain “styles dialogs”) The next diagram shows a possible situation once printing is underway (for clarity, many aspects of the previous diagram are omitted from this one): physical / PDR > a ACTIVE ‘printer a : So f / WDR “ PRINTING) Ke (done) i PRINTER) LPRINTER >} ra ee ; « ——C..__ (read) oC & data ‘ } ote / os * print details > application (dialogs O——<_ data a (Nothing too significant should be read into the type or the direction of the connections shown between different classes in these diagrams. As the foregoing chapter has made clear, the connections between the actual classes are, at times, rather more complex than could be done justice in any one diagram.) The PDR class The role of the ppr object is possibly worth mentioning. This receives all “printing output” from pacEs, and directs into towards the relevant IO channel - be it the parallel port, the serial port, or just an opened file (in the case of printing to file). The ppr class accesses data held by the wor class, to let it know exactly which escape sequences (or whatever) are required to effect various results - such as changing from one font to another, indenting by a given amount, and so on. 13 - 32 13 PRINTING For some kinds of printers, the ppr object has to be an instance of an appropriate subclass of the ppr class in FORM - rather than just being an instance of ppr itself. This happens when an appropriate flag is set in the .wdr file for that printer. In this case, system code looks for a suitably named DYL in the same directory as the .wdr file. See the WDR Printing chapter in the Additional System Information manual for some more details, or contact Psion directly for more information about writing ppr subclasses. The role of the ppr object in the above diagrams is of interest for one additional reason: when the WDR print system is driven in preview mode, as opposed to printing mode, this is one of only two parts of the diagram to change. Rather than create any instance of ppr (or a printer-specific subclass thereof), system code in this case creates an instance of the FORM class prvppr. Rather than direct printing output towards any IO channel, this directs it towards a suitable window - the print preview window. The other part of the diagram that changes is the PRINTING part: the PAGES mdone callback is directed to a different object, namely an instance of prvvizw. This is discussed briefly in the following section. Print preview without XPRINTER The code given earlier in this chapter for xPRINTER makes it clear what would be required should an application, for whatever reason, wish to access the print preview subsystem without using xPRINTER. Essentially, something equivalent to the following line of code is required: f_newsend (CAT_XADD_XADD, C_PRVVIEW, O_PVV_INIT, self, commid, &self-—>lprinter.pages) ; This creates and initialises an instance of the XADD class prvview. The code in the pyv_init method of prvview is as follows: METHOD VOID prvview_pvv_init (PR_PRVVIEW *self,VOID *xp,INT commid, VOID **ppages) { IN_PRVVIEW init; init.Calls.hread=xp; /* xprinter */ init.Calls.mread=O_LPR_READ; init.Calls.hdone=self; init.Calls.mdone=O_PVV_FALSE; init.PrintMethod=commid; init.Sparel = init.Spare2 = 0; p_send4 (self, O_WN_INIT, &init, FALSE) ; *ppages=self-—>prvview.pPages; self—>prvview. Started=TRUE; p_send2 (w_am,O_AM_START) ; } Applications may wish to avoid calling this method, but they cannot practically avoid sending the prvviEw object the wn_init message. Note in this context the definition of the 1n_PRvvIEw struct: typedef struct { PAGES_CALLS Calls; WORD PrintMethod; WORD Sparel; WORD Spare2; } IN_PRVVIEW; where Spare1 and spare2 should be set to zero. The default mdone callback method, pvv_false, always just returns raLse, and will be suitable for almost every client of pRvvizw. (Without going into too many details, there are in fact two layers of done callbacks when print preview applies: a first level callback from pacEs to pRvviEw, always using the method pvv_pages_done, and a possible second level callback from prvview to any specified recipient object.) Finally, the significance of the final TRuE/raLsr parameter to the wn_init method of prvview can be seen in the following code at the very end of this wn_init method: if (DoAmStart) { self—>prvview.Started = TRUE; p_send2 (w_am,O_AM_START) ; } 13 - 33 OBJECT ORIENTED PROGRAMMING GUIDE The only reason that the code in pvv_init cannot pass the DoAmStart parameter as TRUE is in order to write back the handle of pacss (effectively in LPRINTER property), prior to the call to am_start being made. This allows code in, for example, 1pr_read callbacks (which take place, of course, before the am_start call returns) to access pacEs as required. Saving and restoring print context from file As mentioned earlier, the pRINTER class has methods allowing the print context (the subject matter of the ‘Print setup’ dialog suite) to be set and sensed. These methods can be utilised to allow the print context to be saved to file, if desired, and then restored the next time the file is opened. Any application that wishes to save the print context to file has to answer a number of design decisions: ¢ what actual format should the data be stored in? e what kind of integrity check might be performed on file data, before setting it into PRINTER property on application startup? e ~=what kind of error recovery procedure should be adopted, if there is any run-time error, either on saving the data, or on loading it? This is not the place to discuss these matters at any length. Accordingly, many aspects of the example code, in the WDRPRINT subdirectory, will just be taken for granted in this discussion (though this is not to imply that there is anything special about the design decisions embodied therein). What can be briefly covered here, however, are various methods of PRINTER, which fall into two categories: those sensing the print context, and those setting it. The following code senses the print context and writes it out to file: LOCAL_C INT SavePrintContext (VOID) { VOID *fcb; VOID *printer; UBYTE *p; struct { UBYTE model; UBYTE name [P_FNAMESIZE+1]; } om; printer=w_ws-—>wserv.printer; if (!printer) return (0); f_open (&fcb, SAVED_FILE_NAME, P_FREPLACE|P_FSTREAM|P_FUPDATE) ; p=(UBYTE *)p_send2 (printer,O_PR_GET_PARAMS) ; f_write(fcb,p,sizeof (PRINTER_PARAMS) ) ; m.model=p_send3 (printer, O_PR_SENSE_MODEL, &ém.name[0]) ; f_write(fcb, &m.model,1+p_slen(&m.name[0])+1); p=(UBYTE *)p_send3 (printer, O_PR_GET_HD, PRINTER_HDR_TOP) ; f_write(fcb,p,p_slen(p)+1); p=(UBYTE *)p_send3 (printer, O_PR_GET_HD, PRINTER_HDR_BOT) ; f_write(fcb,p,p_slen(p) +1); p_close(fcb); return (0); } This code uses the following PRINTER methods: @ pr_get_params: returns the address of the pRINTER_PARams data structure inside PRINTER property @ pr_sense_model: writes a ZTS specification of the current .wdr file, and returns the index number of the current printer model within this file @ pr_get_hd: returns the address of a ZTS giving either the header text or the footer text, depending on the final parameter passed. Note that aspects of for example the alignment and the printer font of the header and footer are stored as parts of the fixed-length pRINTER_PaRaMs struct: it is only the (variable length) text of the header and footer that requires a separate method to sense it. 13 - 34 13 PRINTING The code to read the print context from file, and to set it into PRINTER property, is rather longer - but that is only because of the integrity tests that it makes: LOCAL_C VOID TryLoadPrintContext (VOID) { P_INFO junk; VOID *fcb; VOID *printer; UBYTE buf[512]; INT blen; INT ind; UBYTE *pl1,*p2,*p3,*p4; if (p_finfo (SAVED_FILE_NAME, &junk) ) return; /* eg file does not exist */ f_open (&fcb, SAVED_FILE_NAME, P FOPEN |P FSTREAM|P_FSHARE) ; blen=f_read(fcb, &buf[0],512); p_close(fcb); if (blen<=sizeof (PRINTER_PARAMS) +2) goto file_corrupt; pl=(é&buf[0]); blen-=sizeof (PRINTER_PARAMS) p2=pl+sizeof (PRINTER_PARAMS) ind=p_bloc(p2+1,blen-1,0); if (ind<0) goto file_corrupt; p3=p2+1t+ind+l; blen-=1+ind+1; if (blen<=0) goto file_corrupt; ind=p_bloc(p3,blen,0); if (ind<0) goto file_corrupt; p4=p3t+indt+1; blen-=ind+1; if (blen<=0) goto file_corrupt ind=p_bloc(p4,blen, 0) if (ind!=blen-1) { file_corrupt: hinfoPrint (DELETING_CORRUPT_FILE) ; p_delete (SAVED_FILE_NAME) ; return; } p_send2 (w_ws, O_WS_ENS_PRINT_CONTEXT) ; printer=w_ws-—>wserv.printer; ’ ’ ’ ’ p_bcpy((VOID *)p_send2 (printer, O_PR_GET_PARAMS) ,p1, sizeof (PRINTER_PARAMS) ); p_send5 (printer,O_PR_SET_MODEL, FALSE, p2+1,*p2); p_send4 (printer, O_PR_SET_HD, PRINTER_HDR_TOP,p3) p_send4 (printer,O_PR_SET_HD, PRINTER_HDR_BOT, p4) } The two new PRINTER methods used here are: ’ ’ @ pr_set_model: the first parameter is a pointer to a ZTS giving the .wdr filename, and the second is the printer model index number ¢ pr_set_ha: the first parameter specifies whether the header text or the footer text is being set, and the second gives a ZTS containing this text. 13 - 35 CHAPTER 14 LINK PASTE This chapter contains a practical introduction to programming “Link Paste” (also called “Bring”): e how to service link paste requests from other applications (the server side of link paste) e how to obtain link paste data from other applications (the client side of link paste) e = the role of the OLIB classes LINKsv, LINKCL and sYSTEM e the definitions of various link data “types” e — specific HWIM assistance for link paste involving edit windows. For the sake of concreteness, the discussions in this chapter are mainly based around various modifications and extensions of the Ehello example application that features in the opening sections of the Edit Windows chapter, and which is optionally installed into the \sibosdk\ehello directory. It should be stressed, however, that it is possible to grasp the concepts involved in programming link paste independently of any appreciation of programming edit windows. The modifications required to the original Ehello code, to add link paste functionality, are all given below. The server side of link paste When the user selects the ‘Bring’ menu command in application X, say, and sees new data added to application X, this data has come from another application - Y, say - that was in background at the time the menu command was issued. In this example, application X is the “link client” and application Y is the “link server’. It is important to realise that the data is fetched directly from application Y at the time the ‘Bring’ request is issued. That is, the data is not fetched into any independent “clipboard application” at any earlier stage (eg when application Y was in foreground). There is no “clipboard application” in the Epoc architecture (neither at the OS level nor at the HWIM level - nor at any intermediate level). Thus applications which wish to function as link servers have to be prepared to receive requests for data whilst they are in background. These requests are in fact interprocess communication (IPC) messages of a particular type - but this is largely hidden from HWIM applications, with the details of the IPC being handled, on the server side, by the OLIB t1nxsv class. Creating a LINKSV subclass instance The tinxsv class contains two deferred methods, 1s_set_format and 1s_get_data, that have to be supplied by any link-serving application. For this reason, applications never create an instance of LINKsv itself, but rather an instance of an application-specific subclass of linksv. For example, the following additional class definition could be added into the file ehello.cat (see later for the significance of the property fields) 14-1 OBJECT ORIENTED PROGRAMMING GUIDE INCLUDE ipc.g CLASS ehlinksv linksv { REPLACE ls_set_format REPLACE ls_get_data PROPERTY { WORD sellen; TEXT *pbuf; } } and the following line of code should be added to application start-up code, eg in the ws_dyn_init method of the wszrv subclass (in other applications, the code could be placed instead in the com_init method of the comman subclass - depending on what was most convenient): £_newsend (CAT_EHELLO_EHELLO, C_EHLINKSV, O_SV_INIT) ; Note that there is rarely any need to record the handle of the created L1nxsv subclass instance anywhere in application code: e the object will continue in existence throughout the lifetime of the application, and so there is no need to hold onto its handle just in order to send it a dest roy message at some later stage e the handle of the object is in fact recorded in the linked-list of so-called “server handles” held by the system recs object, and whenever a suitable IPC message is received by the application, the Ipcs object automatically redirects it to the L1nxsv object. Note also that any sv_init call will fail - with panic 55 - unless the flag rLg_appMaN_IPcs is set in main (in fact, this flag is effectively always set for HWIM applications on the Series 3a - but it is good practice to set it explicitly, in main, whenever an application creates a sERVER object of its own). Therefore the line in ehmain.c that defines the appman flags for the application becomes app. flags=FLG_APPMAN_RSCFILE|FLG_APPMAN_SRSCFILE|FLG_APPMAN_CLEAN | FLG_APPMAN_IPCS|FLG_APPMAN_LINKING; so that w_am is initialised with an 1pcs component (see later for the significance of the FLG_APPMAN_LINKING flag). Note finally that it is impossible to delay creating the L1nxsv object until an actual link paste data request is received - which might at first seem a good idea (in order to cut down on the memory overhead of an object that might never actually be needed). The point is that the Linxsv object is needed in order to receive the request in the first place (on pain of a panic 158). However, it is common practice to delay the allocation of additional data buffers (such as the pbug field of EHL1InKsv will point to) until actually required. Declaring link paste server status In order for an application to receive a link paste data request, two pre-conditions have to be satisfied: e the application has created and initialised a L1nxsv object - as described above e the application has sent the system component of w_am an sy_link_server message, declaring the presence (and type) of linkable data. Declaring current link paste server status is done every time the application passes into background. Therefore the application has to subclass the ws_background method of wserv. For example, the declaration of EHwsERv in ehello.cat becomes CLASS ehwserv wserv { REPLACE ws_dyn_init REPLACE ws_background } 14-2 14 LINK PASTE with the actual contents of the ws_background method being: GLREF_D PR_APPMAN *w_am; GLREF_D PR_EDWIN *DatApp3; METHOD VOID ehwserv_ws_background(PR_EHWSERV *self) { INT which; if (!(self->wserv.flags&PR_WSERV_RECEIVED_KEY) ) return; which=0; if (DatApp3->edwin.select) which=1<appman.system,O_SY_LINK_SERVER, which, 0) ; } The significance of the test on pR_WSERV_RECEIVED_FLAG in this code is to prevent the application “stealing” the link paste server status just because the user happens to task through it. Suppose that the user has, long ago, selected some text in application A, but now wants to link paste some data from application B to application C. The user therefore highlights the data in B, and then tasks to C. But suppose that A is tasked to foreground first - as may well happen if, in particular, A and C are instances of the same application (eg two different Spreadsheet files). So long as the user pressed no keys while transiently tasking through A (apart from the task keys themselves), the above test ensures that the eventual ‘Bring’ menu command in C fetches data from B and not from A. The bulk of the link-paste specific code in an application's ws_background method usually consists of determining the set of link-paste “types” that the current state of the application can support. See later for a discussion of various different standard link paste types. In the example above, the decision is a straightforward choice between two possibilities: either there is some selected text in the editor - in which case linkable data of type pr_LINK_TExT is available - or else there is not - in which case no linkable data is available. In general, an application will often be able to provide more than one type of data at any given time. For example, an application will often be able to offer both “plain text” and “native format data” - depending on who the recipient of the data is. Thus if the recipient of the data is another instance of the same application (eg one spreadsheet link-pasting data from another), a “native format” data transfer will generally transfer more information that the kind of “plain text” data transfer that would happen, instead, if the recipient application is not the same. For this reason, the sy_1ink_server method takes two parameters, which between them form a uLoNG “mask” made up of 32 bits. The more different bits that are set, the more different types of formats which the link server is prepared to “render” at that moment. To signal that the link server is prepared to render format type DF_LINK_TEXT, the bit (1<ehbwin.edwin; The test on DatApp3->edwin.select therefore just detects whether there is any select region in the editor: if there is, the application is prepared to render plain text format link paste data; otherwise, it has no data to render. 14-3 OBJECT ORIENTED PROGRAMMING GUIDE Initialising the SYSTEM component of w_am Before the application can send a sy_link_server message to w_am->appman. system, it is necessary to arrange for the creation and initialisation of the system component of w_am. This is arranged very simply: by setting the FLc_APPMAN_LINKING flag in main. Note carefully that setting the rLc_appmMan_system flag will not have the desired effect. That would succeed in creating and initialising an instance of the system object, but it would be the wrong type of SYSTEM object - being oriented towards the process sys$shll.img instead of towards the process sys$wsrv.img (see the OLIB Reference manual for more details). The anatomy of a link paste transaction (server-side viewpoint) In general, a link-paste transaction is seen, at the server end, as e one call to 1s_set_format e followed by a number of calls (one or more) to 1s_get_data. The transaction takes place in a number of stages, in general, since there is a limit to the amount of data that can reasonably be transferred in any one stage, and in order that the foreground application can remain responsive to redraw requests in the meanwhile. The link server will usually possess some “state variables” - generally in the property of its Ltnxsv subclass - in order to keep track of the progress of the current transaction. The purpose of the 1s_set_format call is e to specify which of the profferred data formats is actually being requested e to allow the link server to reset its state variables, reflecting the fact that such-and-such a type of link paste data transfer is about to start. The purpose of each subsequent 1s_get_data call is e to assemble data into a suitable buffer (if necessary) and to set a suitable variable (namely the linksv.buf property field) to point to this buffer e to specify the length of the data to be transferred in this stage of the transaction (this should be written to the 1inksv.1en property field) e to indicate, by means of the return value (TRUE or FALSE) whether the transaction has completed. Notice that a link paste transaction can end either because: e the link server has no more data to transmit e the link client does not wish to receive any more data. In the latter case, system code informs the L1nxsv subclass by means of specifying a negative len parameter - see the example code below. (Ordinarily, this parameter gives the size of the buffer in the data space of the recipient where the data is to be copied to.) Note that the buffer whose address is written to 1inksv.buf must not be on the stack (for obvious reasons). Example LINKSV code The code for the 1s_set_format and 1s_get_data methods of the zxL1nxsv methods is as follows: #include #include GLREF_D PR_EDWIN *DatApp3; LOCAL_C VOID LinkServiceOver (PR_EHLINKSV *self) { p_free(self—>ehlinksv.pbuf) ; self—>ehlinksv.pbuf=NULL; self—>ehlinksv.sellen=0; } #pragma METHOD_CALL 14-4 14 LINK PASTE METHOD VOID ehlinksv_ls_set_format (PR_EHLINKSV *self, INT type) { UWORD top; SENSE_EDWIN sense; SE_EDWIN se; LinkServiceOver (self); if (type!=DF_LINK_TEXT) p_leave (E_GEN_NSUP) ; p_send3 (DatApp3, O_EW_SENSE, &sense) ; if (sense.cursorehlinksv.sellen=sense.anchor-top; } else if (sense.cursor>sense.anchor) { top=sense.anchor; self—>ehlinksv.sellen=sense.cursor-top; } else return; self—>ehlinksv.pbuf=f_alloc(self-—>ehlinksv.sellen) ; p_send3 (DatApp3, O_WN_SENSE, &se) ; p_bcpy (self—->ehlinksv.pbuf,se.buf+top, self—>ehlinksv.sellen) ; } METHOD INT ehlinksv_ls_get_data(PR_EHLINKSV *self,INT len) { if (lenehlinksv.sellen) { LinkServiceOver (self); return (FALSE) ; } if (len>self->ehlinksv.sellen) len=self->ehlinksv.sellen; self—>linksv.len=len; self—>linksv.buf=self-—>ehlinksv.pbuf; self—>ehlinksv.sellen=0; return (TRUE) ; } In the 1s_set_format method, the code somewhat kindly just calls p_1eave (Z_GEN_NSUP) in any case that the client requests data not available for rendering. Arguably, it might be more appropriate to panic the client in this case: p_ppanic(self->server.cid, xxx); with some well-chosen panic number xxx (since the application has at no time ever declared that it could supply any other format of data - so there must be a bug in the client program for requesting such data). General remarks about link servers Note that a request for data to be rendered for link paste can be received even if e the application has one or more dialogs showing e the application has a menu showing e the application has a help screen showing. The HWIM architecture handles this completely smoothly: the application receives ws_background, 1ls_set_format, and 1s_get_data messages completely independently of whether there are dialogs or menus (etc) current. This is in contrast with the case of, for example, most Hwif or OPL/w applications, when any link paste request IPC messages would go completely unacknowledged in such a case. (And since there is an assumption in L1InKcL code that link paste request IPC messages do not remain unacknowledged - on pain of the link client application hanging - this is a strong reason why non-HWIM applications should, in general, avoid declaring themselves as link paste servers.) 14-5 OBJECT ORIENTED PROGRAMMING GUIDE Some standard link paste data formats In most cases, applications need only consider three standard link paste data formats: @ = DF_LINK_TEXT, in which data is transmitted as a series of buffers of purely printable characters @ DF_LINK_TABTEXT, in which the buffers of data can contain, in addition, tab characters @ DF_LINK_PaRas, in which the buffers of data can also contain embedded paragraph delimiters. (In addition to these standard formats, applications may also consider any number of application-specific so-called native formats. Native formats are discussed later in this chapter.) Now the code discussed so far may have given the impression that a pr_LINk_TExT data transfer only consists of one buffer of text. But that would be a mistaken impression. To see this, consider the following simple changes in the Ehello code: e declare an extra worp property field, ntimes, for EHLINKSV e change the ehlinksv_1s_get_data method so that the se1ien property field only gets reset to zero after the available data has been link pasted out three times in all. Thus the 1s_get_data method becomes METHOD INT ehlinksv_ls_get_data(PR_EHLINKSV *self,INT len) { if (lenehlinksv.sellen) { LinkServiceOver (self); return (FALSE) ; } if (len>self->ehlinksv.sellen) len=self->ehlinksv.sellen; self—>linksv.len=len; self—>linksv.buf=self-—>ehlinksv.pbuf; if (!--(self->ehlinksv.ntimes) ) self—>ehlinksv.sellen=0; return (TRUE) ; } and a new line self—>ehlinksv.ntimes=3; appears at the end of the 1s_set_format method. Highlighting some text in the editor and then choosing ‘Bring’ in an application such as the Word Processor or the Database now results in the highlighted text being transferred three times in all - going into three different paragraphs in the process. Note however that some link clients - such as the Series 3a Agenda - will terminate the transaction after only absorbing the first buffer of data. This is perfectly within their right (see below for the details of how to achieve this result). DF_LINK_TEXT and DF_LINK_PARAS contrasted Next, consider another change in Ehello code, in which the line in the ws_background method now declares the availability of pp_LINK_paRas as well as DF_LINK_TEXT: if (DatApp3->edwin.select) which= (1<appman.system,O_SY_LINK_PASTE, &fmt) ; if (!lkpid || ! (fmt & (1<appman. system, It is necessary to arrange for the creation and initialisation of the system component of w_am. This is arranged very simply: by setting the FLc_APPMAN_LINKING flag in main. Note incidentally that it is perfectly possible for an application to act as a link client but not as a link server. Such an application would have to set FLG_APPMAN_LINKING, but would not need to set FLG_APPMAN_1Ipcs (unless, of course, it created other kinds of sERvVER objects, ie apart from LINKSv). In the above code fragment, a test is made on fmt as well as on 1kpia. A test should always be done on fmt, though the nature of the test made will of course depend on which kinds of link paste data the application is prepared to accept. The text of the system message sys_NOTHING_TO_BRING is, in English, Nothing to bring. Applications are free to substitute more specific messages if they wish. The anatomy of a link paste transaction (client-side viewpoint) Whereas tinxsv is designed to be subclassed - so that applications never create a direct instance of LINKSV - LINKCL is useable as it stands. For this reasons, applications have no need to declare any subclass of L1nxc1 in their .CAT file. Thus application link paste client code will usually contain a line such as link=f_newsend (CAT_EHELLO_OLIB, C_LINKCL, O_LC_START, lkpid, DF_LINK_TEXT) ; directly creating and initialising an instance of L1nkcu. Here, the PID of the link server is specified as one parameter, and the required format type is specified in another. (The format specified in this 1c_start message is passed through to the 1s_set_format method processed by the link server.) Note another contrast with the case of Ltnxsv: the instance of L1nxc1 1s only created when explicitly needed - in response to a ‘Bring’ menu command. The handle of the instance needs to be stored e so that subsequent Lc_GET_DATA messages can be sent to it, for each buffer of data to be collected e so that an tc_stop message can be sent to it, if required e so that the object can be destroyed at the end of the transaction. In practice, once created, the n1nkc1 object usually has its handle added to the cleanup list, and the way the object is destroyed is by a subsequent call to cl_clean_item. 14-8 14 LINK PASTE Simple example of use of LINKCL Consider the following modification of Ehello: when the key combination PSION-ENTER is received, it is regarded as a ‘Bring’ menu instruction (recall that, for simplicity, Ehello has only the barest bones of a real menu bar). Code gets added to ehbwin.c as follows: #include #include GLREF_D PR_APPMAN *w_am; GLREF_D PR_WSERV *w_ws; GLREF_D PR_EDWIN *DatApp3; LOCAL_C VOID DoLinkPaste (VOID) { WORD lkpid; ULONG fmt; VOID *link; INT cl_link; TEXT buf[52]; WORD len; lkpid=p_send3 (w_am—->appman.system,O_SY_LINK_PASTE, &fmt) ; if (!lkpid || ! (fmt & (1<0) { buf [len] =0; p_send4 (DatApp3, O_EW_REPLACE, &buf[0],0); } w_ws-—>wserv.flags&=(~PR_WSERV_RECEIVED_KEY) ; cl_clean_item(cl_link); } #pragma METHOD_CALL METHOD VOID ehbwin_wn_key(PR_EHBWIN *self, INT keycode, INT mods) { SE_EDWIN sense; if (keycode!=W_KEY_RETURN) p_send4 (self—>ehbwin. edwin, O_WN_KEY, keycode, mods) ; else if (mods&W_PSION_MODIFIER) DoLinkPaste(); else } In a more general setting, one call to 1c_start will normally be followed by a sequence of calls to lc_get_data, continuing until the 1en return value from one of them is negative (it will actually be the value &_FILE_EOF, but it is not necessary to test for this explicitly). The client also has the option of terminating the transaction by calling 1c_stop at any stage. This has not been done in the above example, simply because the dest roy method of L1nxct (which is triggered by the above call to cl_clean_item) automatically sends self an 1c_stop message. Note that the client has to specify the amount of data it is prepared to accept, at each stage of the transaction, by means of the final parameter to the 1c_get_data message. This value is communicated to the link server as the 1en parameter in the 1s_get_data message. The significance of the line of code w_ws-—>wserv.flags&=(~PR_WSERV_RECEIVED_KEY) ; is to avoid the link client “stealing” the link paste server status, when it next passes into background. 14-9 OBJECT ORIENTED PROGRAMMING GUIDE Special help with link pasting to and from edit windows The ew_bring_in method of EDWIN In practice, any application that wishes to link paste text into an instance of Epw1n would actually use the ew_bring_in method of that class - which encapsulates the stages of e creating the L1nxci object e sending the relevant 1c_start and 1c_get_data messages e sending various messages to itself. The ew_bring_in method also encapsulates knowledge of the various standard types of textual link paste formats. For interest, the complete code of edwin_ew_bring_in follows (though it will be necessary to read the Edit Windows chapter carefully to appreciate some parts of it - eg some of the utility routines used): METHOD INT edwin_ew_bring_in(PR_EDWIN *self,INT lkpid, INT format) PR_ROOT *link; INT cl_link; INT SingleShot; UINT pos; UINT totlen; INT len; INT err; INT offset; TEXT buf[258]; CheckNotReadOnly (self); SingleShot=format &EW_BRING_SINGLE_SHOT; if (formaté (1<edwin.cpos; totlen=0; buf [0]=0; offset=1; while ((len=p_send4 (link, O_LC_GET_DATA, &buf[1],256) ) >=0) { if (!offset) lent+; if ((err=p_entersend5 (self,O_EW_EP_INSERT,pos, &buf [offset],len) ) !=0) { p_send4 (self-—>edwin.doc, O_EP_DELETE, self—>edwin.cpos, self- >edwin.cpos+totlen) ; cl_clean_item(cl_link); p_send3 (self, O_EW_LEAVE, err); } totlent+=len; post=len; if (SingleShot) { p_send2 (link, O_LC_STOP) ; break; } if (format !=DF_LINK_PARAS) offset=0; } self—>edwin.clen+=totlen; EdwinFwdChange (self) ; SetEdwinSelect (self, self->edwin.cpos,totlen) ; w_ws—>wserv.flags&=(~PR_WSERV_RECEIVED_KEY) ; cl_clean_item(cl_link); return (0); /* confirm no leave */ } 14-10 14 LINK PASTE Simple example of calling EW_BRING_IN The code in the ncoe_bring method of the command manager of the example Notes application (optionally installed into \sibosdk\notes) shows how simple it can be to call ew_bring_in: METHOD VOID nocomman_ncoe_bring(PR_NOCOMMAN *self) { INT lkpid; ULONG 1kfmt; CheckEditing(self) ; lkpid=p_send3 (w_am—->appman.system,O_SY_LINK_PASTE, &lkfmt) ; if (!lkpid || !(1kfmt & (1< #include GLREF_D PR_EDWIN *DatApp3; LOCAL_C VOID DestroyLinker (PR_NOLINKSV *self) { hDestroy (self->nolinksv.ewls) ; self—>nolinksv.ewls=NULL; } #pragma METHOD_CALL METHOD VOID nolinksv_ls_set_format (PR_NOLINKSV *self, INT type) { DestroyLinker (self) ; self—>nolinksv.ewls=f_newsend (CAT_NOTES_HWIM, C_EWLINKSV, O_EWLS_INIT,DatApp3,type) ; } 14-11 OBJECT ORIENTED PROGRAMMING GUIDE METHOD INT nolinksv_ls_get_data(PR_NOLINKSV *self,INT len) { if (len>0) { if (((INT) (self->linksv.len=p_send4 (self->nolinksv.ewls, O_EWLS_EXTRACT, &Sself->linksv.buf, len) ) ) >=0) return (TRUE) ; } DestroyLinker (self) ; return (FALSE) ; } Evidently, the ew1s_init method needs to be passed the handle of the associated Epw1n instance (in this case, this is stored at patapp3) and the specified format type. Thereafter the ewis_extract method returns the value that should be written into 1inksv.1len (or a negative number, if the transaction has terminated), and also writes back, to one of the passed parameters, the value for 1inksv.buf. The three text formats revisited The code for zEwLinxsv, reproduced below, contains (in conjunction with the code for the ew_bring_in method of epwin) what is in effect the implicit definition of the three standard link paste text formats: CLASS ewlinksv root Component of linksv class, extracts data from edwin { ADD ewls_init ADD ewls_extract PROPERTY { VOID *doc; WORD state; UWORD pos, parend, posend; TEXT buf[256]; } } METHOD INT ewlinksv_ewls_extract (PR_EWLINKSV *self,TEXT **ppb,UINT blen) 14-12 { UINT len; INT striptabs; TEXT *pb; TEXT *pbend; len=self->ewlinksv.posend-self-—>ewlinksv.pos; if (!len) return(-1); /* finished */ if (len>=blen) len=blen; p_send5 (self—>ewlinksv.doc, O_EP_EXTRACT, self->ewlinksv.pos, &self—->ewlinksv.buf[0],len); if (self->ewlinksv.state!=DF_LINK_PARAS) { striptabs=(self->ewlinksv.state==DF_LINK_TEXT) ; pb=(&self->ewlinksv.buf[0]); for (pbend=pb+len; pbewlinksv.post++; /* skip the zero too */ break; } (striptabs && *pb=='\t') *pb=" '; len=pb- (&self->ewlinksv.buf[0]); /* excludes any trailing zero */ } self—>ewlinksv.post=len; *ppb= (&self—>ewlinksv.buf[0]); return (len); } 14 LINK PASTE METHOD VOID ewlinksv_ewls_init (PR_EWLINKSV *self,PR_EDWIN *edwin, INT state) { UINT len; self—>ewlinksv.doc=edwin->edwin.doc; len=p_send3 (edwin->edwin.scrimg, O_SI_GET_SELECT, &self—>ewlinksv.pos) ; self—>ewlinksv.posend=self—>ewlinksv.pos+len; self->ewlinksv.state=state; } Native formats In many cases, applications will wish to support “native” formats of link paste data. For example, the Word Processor link pastes styles and emphasis data along with the text selected. Another example is that the Series 3a Agenda link pastes the details of an appointment - including the alarm setting and memo setting, if any. In cases like this, the mask specified in the sy_link_server message should include the bit for DF_LINK_NATIVE. For example, the ws_background method of the Word Processor is as follows: METHOD VOID wpwserv_ws_background(PR_WPWSERV *self) { INT type; if (self->wserv.flags&PR_WSERV_RECEIVED_KEY) { type= (1<edwin.select) type=0; p_send4 (w_am—->appman. system, O_SY_LINK_SERVER, type, 0) ; } } Note that the Window Server process - which is the central store, on the Series 3 and the Series 3a, for link paste data information - never reports the pF_LINK_NATIVE bit to an application that differs from that which registered the data. (This test is based on the result of calling p_pname.) For this reason, when the DF_LINK_NATIVE bit is set in the mask of available formats, the application can rest assured that the link paste server is indeed the same application as itself (though, possibly, running under a different alias). By convention, the top eight bits in the 32-bit wide mask of formats are reserved for applications passing more information to each other about which types of their own native format are presently available. Final comments Note that the link paste server must run unattended. There is no point in presenting the user with a query dialog, asking how the link paste service is to proceed. This is because the link paste server is in background: the user will not see the dialog, and will just notice that the link paste operation in the foreground window has completely stalled. If really desired, the link paste server can obtain additional information from its client, by having the client present the query dialog on its behalf. The information required to effect this would have to be defined and included as part of the native format (bear in mind that each partner in the process has access to the PID of the other so that the link paste IPC can be supplemented, if desired, by other forms of IPC at that moment). One other point that may be worth mentioning is the fact that the link server can happily call p_ieave (directly or indirectly) at any stage of its operation. System code (in L1nksv and LINnkcL) ensures that the error notification takes place in the client process, and not in the server process. 14 - 13 CHAPTER 15 HWIM RESOURCE FILES The basic information about resource files that applies to all SIBO applications is covered in the Resource Files chapter of the Additional System Information manual. That chapter includes: e the resource file format e the different possible locations for resource files e advice about multi-lingual applications e use of the resource compiler tool rcomp.exe e the allowed content of a .rss source file, including struct and constant declarations This chapter contains additional information that is specific to HWIM applications. The application resource file As was mentioned in the Introduction chapter, all HWIM applications must have an application resource file that contains at least the resources required to construct the application's menu bar and pull-down menus. Simple examples of such resources appear in the Hello World and Commands and Command Menus chapters. A further, more realistic, example can be found in the source files for the Record application. In addition to the resources for the application's command menus, the file record.rss contains a number of resources of various types. In particular, it #includes the file record.hlp that contains Help resources. Resource file location By default, the application resource file is expected to be built into the application's image file, with the aid of an add-file list (.aff) file, in the second of the four possible add-file slots. A resource file in this location will automatically be opened by system code during the initialisation of the application. An application that wishes to load its resource file from a different location may subclass the pwrmman application manager, replacing the am_rscname method. This method is passed a pointer to a buffer and must write the full file specification of the resource file to this buffer. For example, an application that has an application resource file with the same name as the application's image file, and resident in the same directory as the image file, could use the following replacement am_rscname method: METHOD VOID myappman_am_rscname (PR_MYAPPMAN *self,TEXT *pname) { p_fparse(".RSC", DatCommandPtr, pname, NULL) ; } 15-1 OBJECT ORIENTED PROGRAMMING GUIDE A multi-lingual application with a resource file for each of a number of languages could use code similar to that suggested in the Resource Files chapter of the Additional System Information manual: METHOD VOID myappman_am_rscname (PR_MYAPPMAN *self,TEXT *pname) { P_INFO £; p_atos (pname, "\\app\\archive\\archiv%02d.rsc",p_getlanguage()); p_fparse (pname, DatCommandPtr, pname, NULL) ; if (p_finfo(pname, &f) <0) p_supersend3 (self,O_AM_RSCNAME, pname) ; } As explained in that chapter, the resource files are assumed to reside in an application-specific subdirectory of the \app directory; in this example the directory is \app\archive. The resource file for the default language should be built into the image file so that it is available for use on a machine that is set to a language not supported by the application. If the call to p_finfo indicates that a resource file for a particular language is not available, supersending the am_rscname message ensures that the built-in resource file will be used. See also the description of this method in the OLIB reference manual chapter 10 page 11. Loading an application resource Once the resource file is open, any of its resources may be read by sending the application manager either an AM_LOAD_RESOURCE Or an AM_LOAD_RES_BUF message (or by use of the equivalent hLoadResource or hLoadResBuf utility functions) passing the resource ID of the required resource. Note that the resource IDs are published in the .rg file that is generated during compilation of the source file by rcomp.exe. Resource Structures The basic resource structures used for strings, dialogs and the standard dialog components are defined in the include file hwim.rh. This file should always be #included in the application-specific resource file. Application-specific resource structures, if required, will need additional structure definitions. These may appear in-line in the .rss source file or may be written in a separate header file, to be #included in the source file, together with hwim.rh. It is customary to give such a file a .rh extension. The system resource file The system resource file resides in the ROM, and the (English) source for each of the relevant machines is copied into a \sibosdk\resource directory when the OOP option of the SDK is installed. These files are supplied for reference only, and should not be used as source files in the building of any application. The files are: S3_.1SS The system resource file for the Series 3 SA_.1SS The system resource file for the Series 3a SC_.TSS The system resource file for the Series 3c SS_.TSS The system resource file for the Siena SW_.TSS The system resource file for the Workabout These files are broadly similar, and much of the content is common between all three. The files for the Series 3a, Series 3c, Siena and for the Workabout contain additional resources compared with the Series 3 system resource file. All differences from s3_.rss are commented. The system resource file contains text strings, dialogs and other resources that are used by system code. In addition to supplying ready-made resources that can also be loaded by application code, the source files provide a wide range of example templates for constructing application-specific resources. The source files contain comments that are primarily supplied to aid translators to produce non-English versions. These comments may, however, prove of use to developers by indicating the purpose of, and/or the constraints associated with, a particular resource. Subject to the caution given below, all system resources are, in principle, available for use by applications. 15 -2 15 HWIM RESOURCE FILES Loading a system resource A resource is identified as a system resource by specifying a negative resource ID. Otherwise, the process of loading a system resource (using, for example, hLoadResource OF hLoadResBuf) 1s identical to loading an application resource. For example, the following code can be used to allocate memory and load the choice list system resource with resource ID sys_No_yEs: TEXT *p; hLoadResource (-SYS_NO_YES, &p) ; Note the explicit minus sign and that, by convention, all system resource IDs start with sys_. The resource IDs of system resources are published (in upper case) in the include file s_.rsg, which must be #included in any file that makes an explicit reference to one or more system resource IDs. Note that s_.rsg contains the resource IDs of all the resources that are contained in all five of the resource files s3_.rss, sa_.rss, SC_.rss, sS_.rss and sw_.rss. In consequence, not all of the listed resource IDs are valid on all machines. Using system resources Many system resources are used implicitly by application code. For example, calling hBusyPrint implicitly loads the sys_Busy resource string, and an application that runs one of the system dialogs will cause the associated dialog resource to be loaded from the system resource file. A number of system resources are suitable for explicit use within an application. Perhaps the most commonly used group is the various string resources that are used as information messages. After a successful copy operation, for example, an application could present confirmation to the user with the code: hInfoPrint (-SYS_COPIED_PROMPT) ; Referencing system resources from an application resource file Other useful system resources include dialog box components such as the various choice lists, particularly SYS_OFF_oN and sys_No_yes, and the general-purpose action lists, for example, sys_ac_No_yEs and SYS_AC_CONTINUE. These are suitable for inclusion in application-specific dialog resources. Suppose, for example, that an application dialog resource needed to include a No/Yes choice list. A straightforward way of accomlishing this would be to define a suitable MENU resource, such as: RESOURCE MENU no_yes { items = { CHOICE_ITEM { str= "No";}, CHOICE_ITEM { str="Yes"; } hi and reference it in the control as: CONTROL { class=C_CHLIST; prompt="Ignore parity"; info=CHLIST{rid=no_yes; }; } Since a No/Yes menu resource exists in the system resource file, it is more efficient for the application's CONTROL resource to refer to the system resource instead of an application-specific replica: CONTROL { class=C_CHLIST; prompt="Ignore parity"; info=CHLIST{rid=-SYS_NO_YES; }; } 15 -3 OBJECT ORIENTED PROGRAMMING GUIDE Caution In an application it is possible that a system resource could be used in a context that is different from the one for which it was designed. This could cause problems in a multi-lingual application, since translations of system resources may expose differences in context that are not apparent in a single language. System resources should therefore be used cautiously in applications that are intended to run in more than one language. If an application writer has any doubt about the intended use of a system resource, he or she should use an application-specific resource. Help resources An application can supply application-specific Help information by means of one or more top-level HELP_ARRay resources. An example of such a resource is as follows: RESOURCE HELP_ARRAY myapp_help { topic="Myapp"; topic_id=myapp_help_index; } It contains a topic item, used to construct the title for a Help screen, and a topic_id which contains the resource ID of a Toprc_array resource. Such a resource is illustrated below; it contains an id_ist array of the resource IDs of one or more secondary HELP_ARRAY resources: RESOURCE TOPIC_ARRAY myapp_help_index { id_lst= { basics, -SYS_HELP_EDIT, -SYS_HELP_PRINT, -SYS_HELP_FILES, -SYS_HELP_NO_SYS_MEM ‘i } Note that this array may contain references to any combination of system and application-specific resources, in any order. A secondary HELP_ARRAY resource contains a topic item, again used in a title, followed by a striist array of strings, each of which will be displayed on a single line of a Help screen. RESOURCE HELP_ARRAY basics { topic="Basics"; strist= STRING {str="Enter to confirm selection"; }, STRING {str="";}, STRING {str="To move around:";}, STRING {str=ARROWS" to move cursor"; }, STRING {str="Psion-"" go to start/end of line"; }, STRING {str="Psion-"" to PageUp/Down"; }, STRING {str="Control-Psion-"" go to top/bottom"; } ‘i } Each line of text, after compilation, must not include more than 39 characters. Using Help resources The Help information that is provided when a user presses the Help key may be set within an application by writing the resource ID of a top-level HzLP_arrRay resource to the window server object's help_index_id property. For example, to use the Help data shown above: w_ws-—>wserv.help_index_id=MYAPP_HELP; 15-4 15 HWIM RESOURCE FILES Application code may set the Help resource ID at any time. If the value of the window server object's help_index_id is zero (the default value) system Help will be supplied. Most applications that supply their own Help will normally set the Help resource ID during initialisation, in the ws_dyn_init method. An application may provide context-sensitive Help by changing the Help resource ID as the application context changes. This may be done either by writing a new value to w_ws->wserv.help_index_id, or by subclassing the client window to replace its wn_sense_help method. The Help supplied for a dialog box may be set independently by creating additional sets of Help resources. The Help for a dialog box may be set either by writing to its helprid property, or by supplying a replacement wn_sense_help method in a piGBox subclass. Again, default system Help is supplied for dialog boxes. 15-5 CHAPTER 16 APPLICATION DESIGN It is clearly beyond the scope of this manual to discuss in any detail the principles and practice of object oriented design. There are a number of books available on this topic - two that have proved useful are: Object Oriented Modeling and Design, by James Rumbaugh et al. (Prentice-Hall International, 1991) Object Oriented Analysis and Design with Applications, second edition, by Grady Booch (The Benjamin/Cummings Publishing Company Inc, 1994) This chapter offers some specific guidance on object oriented application design for the Series 3 and Series 3a, using the Record application that is built into the Series 3a as an example. The full source code of this application is supplied and may optionally be installed into a \sibosdk\record directory. Once installed it may be built by making \sibosdk\record the current directory ant typing: make record The Record application illustrates a range of design techniques and solutions that may usefully be transferred to other applications. You should not, of course, take this to mean that Record is presented as a perfect example of application design. As with any design, the end result is a compromise between the ideal and the realistic. However, it is also true that, as a result of being designed, the application is much more robust and comprehensible than it would otherwise have been. Basic design The design of an application should, in general, separate into two layers: e the user interface contains all aspects of the application that are dependent on a particular machine e the engine (otherwise known as the system model) contains the data and the associated mechanisms that are particular to the application For Series 3 and Series 3a applications, the user interface contains objects based Pern cecn on the HWIM library whereas engines do not. An engine may, however, : rea ip optionally use any of the classes in the OLIB library. ee of ate) As is illustrated in the accompanying class diagram, the user interface has access to the engine, but there is no unsolicited communication in the opposite direction. The engine has no detailed knowledge of the classes making the calls. In practice pile this means that the user interface source files may include engine header files, but / engine / that the engine should not include user interface header files. Base ) The division between user interface and engine gives rise to many advantages, aug some of the more significant ones being: e separating the two aspects of the application reduces the complexity that has to be dealt with at any one time by both the designer and the implementer e such separation helps the designer to reduce the number of interactions between the various component objects, resulting in a 'cleaner' and more maintainable application 16-1 OBJECT ORIENTED PROGRAMMING GUIDE e the engine does not need to be changed (or, at least, provides a good starting point) if the application is ported to another machine with a different user interface e the engine may be tested independently - a test harness may be constructed to test all aspects of the engine, without the complications of testing via the user interface A consequence is that it is in the programmer's interest to put as much of the application code as possible into the engine, to improve portability and to protect the investment of effort that the code represents. A typical application The following diagram shows the basic components of a typical application. Most of these components will be familiar from the discussion of the basic mechanisms of an object oriented application in the /ntroduction chapter. —— — application / manager fine Oe Sa: Nas paca [ee ¢ resources / y peices , y menu bar / _ ) > ) ~ ) Lae Ci“ ome aes aon comman ialog box client / ( Smanager?——~“ 60 window ) hao Ny _ a la engine / me ) wee The diagram concentrates on those aspects of the design that are common to all applications and thus omits some relationships that may be present in a particular application. It is, for example, likely that a number of classes may make use of utility functions by sending messages to the application manager and/or the window server object. The handles of the application's instances of the application manager (Hwrmman) and the window server object (wsERv), are globally accessible (via the magic statics w_am and w_ws respectively) and many application classes may use them - for example, to use the system services that they provide. For clarity, such relationships are not shown. One of the more significant aspects of this diagram from a design viewpoint is the presence of the engine and its relationships with other application classes. As indicated, the engine is commonly accessed by the client window, the command manager and the application's dialogs, but not, for example, by HWIMMAN or WSERV. The user interface The HWIM library provides an effective design solution for many aspects of the user interface. As described in this manual, the library provides the necessary base classes and mechanisms to process the vast majority of messages that may be received from the window server process. For example, HWIM provides a ready-made solution for the processing of command options, selected either by an accelerator keypress or from pull-down menus. Thus, much user interface design reduces to considerations such as what commands options are required and what dialogs are needed to support them. 16-2 16 APPLICATION DESIGN An exception to this is the client window itself, which is used to provide the top-level view of the data within the engine. Apart from the positioning and draw/redraw mechanisms, the supplied HWIM window classes provide very little in terms of general client window design. There is support, from the Epwin class, for windows that display editable text and this is extended to cover printing and formatted text by the FORM library. Apart from the material in the Edit Windows and Printing chapters, these topics are beyond the scope of this manual. The engine An application's engine is specific to that application and, since it should be independent of the user interface, HWIM provides no significant support. In most cases, engine design will therefore be a major component of the design of the whole application. Many engine classes will be specific to a particular application, and will directly subclass root. It may, however, prove useful to examine the classes supplied by the OLIB library, to see if any of them could be used or subclassed. These classes are described in the OLIB Reference manual. The Record application Record is a reasonably simple, but non-trivial, application and, as such, is ideally suited to being used as an example of application design and implementation. The application arose from a need to demonstrate the digital sound capability of the Series 3a in an intuitive and easy to use manner. The application was required to provide for the recording of custom sounds to be attached to alarms and for the recording of voice "notes" for future recall, by playing back the sound. For all uses, recording has to use the minimum of key presses. Playing the sound back has also to be as easy as possible. Constraints on human and machine resources meant that the application had to be capable of being produced quickly and be as small as possible. It may be of interest to note that the entire development, including requirements, specification, design, implementation and testing took around 90 man-hours and that the final application size is about 9 kbytes (against an initial target of 4 kbytes). Specification Record is a file-based application for the Series 3a. It manipulates one file at a time and keeps its current file open. The application operates on sound files that by default are stored in \wve directories and have .wve extensions. Such files are suitable for attaching to alarms. The descriptions of Series 3a sound files and the services that operate on them are given in the General System Services chapter of the PLIB Reference manual. One significant feature of the sound services is that they make direct read and write access to sound files. The Record application thus has no need to maintain an in- memory copy of its current file. The Record application obeys Switchfiles and Shutdown messages from the system screen. It is never ‘busy’, even when recording or playing a file, so these messages can be received at any time. The application has two persistent parameters, stored in environment variables: e the sound volume at which to play back files e the default file duration. Top-level view The large status window is on permanently. The client window consists of a display of the attributes of the file being edited with buttons to perform actions on the file. The permanently displayed attributes are: e the name component only of the open file e the duration of the sound in seconds e the length of the file in bytes (so that the user is aware of the memory consumed by this file) 16 -3 OBJECT ORIENTED PROGRAMMING GUIDE If appropriate, the following attributes are also shown: e the number of repeats e the duration of the trailing silence in seconds e the total playing time The buttons are: e Record new (Tab) to record to a new file. A dialog is presented for the file name, the disk and the maximum duration. A generated file name, of the form recordnn where nn is 01, 02, 03 etc., is presented by default. The initially suggested disk is the default drive or, if that drive contains other than a RAM SSD, Internal. e Record over (Space) to re-record to the file, overwriting its existing content. A dialog is presented allowing the maximum duration to be set (the suggested duration is that of the current file, rounded up to the next 2K bytes). The dialog contains a warning that the current data will be replaced. e Play (Enter) to play back the file, including any repeats and trailing silences. Playing When playing the file, the three buttons are replaced by a single Stop (Esc) button. A bar graph is also presented, showing the elapsed playing time against the predicted total playing time. Recording After accepting the dialog presented by Record new or Record over, the three buttons are replaced by a single Start recording (Space) button. Pressing Space starts the recording and the button is replaced by a single Stop (Esc) button. A bar graph is also presented, showing the elapsed record time against the maximum duration. Pressing Esc terminates (but does not abort) the recording, resulting in a shorter sound file than would otherwise be produced. If Esc is not pressed, the recording terminates when the specified maximum duration has been reached. Running for the first time The first time the application is run, when no sound files exist under the icon, the application immediately presents (over a blank client window) the dialog that is presented when you subsequently press the Record new button. In this case the suggested file name is record (not record01) and pressing Esc in this dialog causes the application to exit. The same behaviour results, but with the specified file name, if you start the application by use of the System Screen's New (Psion+N) option. Menu The menu commands are: New file Essentially has the same effect as pressing the Record new button. Open file Presents a dialog with standard controls to select a new current file. Set repeat Presents a dialog to set the repeat count (1 to 999) and the trailing silence (0 to 10.00) in the file header. This is intended to be of use for constructing custom alarms. Adjust for alarm § Automatically sets the repeat and the trailing silence for a short recording (less than seven seconds or so) so that it is compatible with being clipped at 15 seconds by the alarm server. Set preferences Presents a dialog to set the volume of playback, the default duration and the default disk for new files (the last two being subsequently used by the Record to new file dialog). Exit There is no need to save any changes - all changes are made to the file directly. 16-4 16 APPLICATION DESIGN Design The overall design of the Record application is shown in the following class diagram. Although the diagram is fairly complex, it is interesting to see that the essential design can be captured in a single diagram. The Record application represents about the highest level of complexity for which this is possible. Despite its complexity, the diagram is still a simplification and shows less than the whole truth. It does not mention, for example, access to resource files and it omits a number of relationships of lesser significance and whose presence would hinder rather than help with an understanding of the design. The dialogs, for example, are generally run from within methods of the command manager, but including all such relationships would render the diagram totally unreadable. The diagram does, however, show all the really significant aspects of the design. As an example, it accurately illustrates the relationships between user interface classes and the engine, although the relationship between the rEcnew dialog class and the engine is conceptual rather than actual (this is explained later in more detail). It is an important part of the design that the file services, digital sound services and the environment variables are accessed via the engine and are not referenced directly by any user interface component. The relationships that are shown are drawn to represent the truth as closely as possible. The using relationship between the window server object and the client window is drawn to show that it is methods at the WSERV level that send messages to the client window. This reflects the fact the only relationships involved are those provided by system code. In contrast, as would be expected, the engine is used only by application-specific subclasses. SANS ie os recbut ; ( bwin Hee | 5 recnewdef ( ey 5 } 235 4 ae {active} — ie ok | a a recnew Nae Fs ~™ ee . 2 9! aoe : ss, (aan a , faetil ( iat eee! ay pee . recexist ( ( S S recopen 5 setrep ( recman : gers hn Fe aS Xw RS r hwimman we o a mes / setpref > receng 5 ss | oe fare Coe reccom > site ak ( eee waveao : on =z / Fie \ factivel L 4 DP 4 < woo Ne 2h BS Soa ~ . Er ‘) File services (i ; / Digital sound TSE EoN variables services ~ (impl.) imp Hinge Booch class diagram for record.app The following sections describe a number of aspects of the application's design 16-5 OBJECT ORIENTED PROGRAMMING GUIDE The client window The client window forms the application's top-level view of the current file and initially presents a view similar to that shown below. Bugle 1.1sec 9 Kbyte Record new Record over Play While recording or playing a file the buttons are replaced by a single button and a bar graph is presented to indicate the elapsed time, as illustrated in the next diagram. This diagram also shows the additional line of information that is shown for a file that is set to repeat and/or has trailing silence. Bugle 1.1sec 9 Kbyte Repeats:9 Trailing silence: @.2sec Total: 15.2 sec The following diagram shows the application when it is paused before making a recording, with another single button being visible. Record#2 Start recording The client window thus has, at various times, to display one or two lines of text, one or more of a set of five buttons, in various positions, and possibly a bar graph. The client window maintains two text strings (the second of which may be a null string) for centred display at fixed vertical offsets whenever a wn_draw message is received. In contrast, the bar graph and the five possible buttons are component objects of the client window and each of them contains the knowledge of its size and its position within the (fixed size) client window. Furthermore, each of these components contains a record of whether it is visible or not. Thus the client window does not need to maintain a record of which set of components is visible at any one time. An operation, such as pressing the Play button only needs to set the visibility of the appropriate components. On receipt of a wn_draw message, the client window always sends all six components an appropriate drawing message. Each component either acts upon or ignores this message, according to its own internal state. The following object diagram illustrates the drawing mechanism, in response to a WN_REDRAW message from the window server object. The same sequence is used for drawing that is initiated by the client window itself. 16 - 6 16 APPLICATION DESIGN wn_draw wn_redraw —_ O33 raw The client window thus has no direct knowledge of which buttons are visible. It must, however, respond to a set of keypresses that correspond to the buttons that are on display. Record accomplishes this by taking advantage of the keyboard filtering mechanism (see ws_process_key in the WSERV Class chapter of the HWIM Reference manual) to divert the processing of keypresses. In its normal, three-button, state, keys are processed by the client window's wn_key method, in the normal way. When it switches to either of the other two states, not only is a different set of buttons made visible, but a filter is set, redirecting key processing to one of two alternate key processing methods - either cl_sound_filter Of cl_pause_filter. The change of state actions are performed by the client window's cl_update, cl_begin_sound and cl_pause methods. Note that these two methods return wN_KEY_CHANGED, thus ensuring that interaction with the menu bar is disabled in the filtered states. Record uses negative values for wserv.filmethod So that Help is still available while keys are being filtered. It is worth noting that the behaviour in each of the two filtered states is very similar to what could alternatively have been achieved by presenting a dialog. The decision to simulate dialog behaviour rather than to use dialogs for these states was largely based on cosmetic considerations. The bar graph While recording or playing a file, Record displays an animated bar graph to show the progress against the predicted total time for the operation. Since the sound services provide no information regarding their progress, the bar graph animation has to be performed independently of the sound recording or playback. The Trmepar class therefore takes only a single item of external data - the total predicted time for the operation. It uses the free-running counter (Frc:) device in its repeating mode to ensure accurate synchronisation of the animation with the sound services. Since the Frc: device is a scarce resource, it is essential that Record releases it as soon as possible. The device is therefore opened every time it is used, and closed on completion of every record or playback operation. It is particularly important to ensure that an error condition does not leave the FRC: device open, so its closing is made an essential part of the application's error-handling (see The application manager, later in this chapter). The engine Record's engine is a static instance of the REcENe class, created on initialisation of the application and remaining in existence for the application's lifetime. This need not be the case in all applications - in many cases it may be appropriate to represent, say, a change of the application's file by destruction and recreation of the engine. RECENG centralises access to and manipulation of the data associated with the application's current file. Its handle is globally available (via the global static variable receng) so that it may be referenced by any object in the user interface. The classes that actually use the engine are as indicated in the earlier application class diagram. 16-7 OBJECT ORIENTED PROGRAMMING GUIDE The engine owns instances of the wvEFILE and waveao classes that respectively represent the current file and the record/playback process. All access to these classes is via REcENG methods. This reduces the engine's 'surface area’, by avoiding the need for any other object to be aware of the existence of these two instances. Note that some engine methods add little value (see, for example, the eng_sense_file method that senses the file data held by wer Le) and exist simply in order to delegate the action to a component. Although this results in a small increase in the size of the application, this is outweighed by the design advantages that it brings. The essentials of the sound-playing mechanism is illustrated in the following object diagram. Playing is initialised by pressing the Play button in the client window, which causes an ENG_SOUND_PLAY message to be sent to the engine. reccli sound_play ——— begin_sound update Ney ae ge done_play sense_info sense_fname waveao The name and total sound duration of the current file are sensed by means of wvE_SENSE_FNAME and WVE_SENSE_INFO Messages tO WVEFILE and the name is passed to wavEao in a WV_PLAY message to initiate the sound. The engine sends a cL_BEGIN_SOUND message to the client window (see later) to cause it to change the button display and to initialise and make visible the bar graph. WAVEAO 1s an active object which breaks up the playing of the file into a series of sections, allowing the application to continue to respond to keypresses (to abort the playing of the sound) and to update the growing bar graph (another active object). When wavzao completes the playing of the file it sends an ENG_DONE_PLAY message to the engine which, in turn, sends a cL_uPDATE message to the client window, causing it to revert to its three-button display. An engine should have no knowledge of user interface objects and certainly should not send them unsolicited messages. The design of the Record engine, however, requires the engine to send messages to the client window in response, for example, to the receipt of an ENG_SOUND_PLAY message. This apparent conflict is resolved by passing the client window's handle, and the message numbers of the required messages, to the engine as parameters. The parameters are normally sent to the engine (as in this case) with its initialisation message and are stored in the engine's property. The message can then be sent at a later time, with no knowledge of either the target object or the meaning of the message that is being sent. The engine thus preserves its ignorance of, and independence from, user interface classes. The special nature of this type of messages is indicated by italicising the corresponding message names in the above object diagram. Recording to a new file or re-recording an existing file broadly follow the same pattern. The major apparent difference in the mechanism arises from the fact that these two operations are also available as command menu options. The initiation of these operations from the client window therefore shares code with the corresponding command manager methods, but the principles remain the same. Before recording to a file starts, the engine deletes any existing file of the same name. Dialogs As with the majority of applications, there is very little design associated with Record's dialogs since they are largely constrained by the system-supplied mechanisms. 16-8 16 APPLICATION DESIGN om — a —~ ¢ digbox / ~ ) fo C y ¢ «setrep / setpref / oN — is receng / -: ) Nee The strep and setpreF classes, respectively used by the Set repeats and Set preferences menu options are typical subclasses of pLGBox, replacing the dl_dyn_init and di_key methods. As indicated in the above class diagram, both classes send messages to the engine to sense and set the relevant data. recopen The file-related dialog classes, RECOPEN, RECNEW, RECNEWDEF and REcExISsT, are related as shown in the above class diagram. The recopen dialog class reports a selected file name by means of a result buffer, pointed to by an item of dialog box property, digbox.rbuf. It thus has no direct using relationship with any other class in the application. The other three dialogs are associated with recording to a file. There is a conceptual relationship between these dialogs and the engine, since the current file name is found by means of a message sent (by the command manager) to the engine. This initial file name is passed to the dialogs by use of a result buffer, so the link to the engine is not represented in the above class diagram (although it is shown in the more conceptually orientated overall class diagram shown at the start of the Design section). All these dialogs initiate recording to the file by sending a cL_pausE message to the client window. The application manager On error, Record is designed to return to its base state, as on first entry to the application. Record's application manager subclasses pwrmman to replace the am_clean_up method. This method is called by system code as part of the standard recovery from an error condition (that has caused a call to p_leave). Its purpose is to assist roll-back to a safe state by freeing any resources that have been allocated since the application was last in a secure state. For further details see The CLEANUP Class and the description of the am_clean_up method in The APPMAN Application Manager Class, both of which chapters appear in the OLIB Reference manual. 16-9 OBJECT ORIENTED PROGRAMMING GUIDE The replacement method adds value by also performing all central generalised error recovery needed for any error condition. Its actions include, for example, clearing the value of wserv->filter and sending a TB_sTop message to the client window's bar graph (to ensure the release of the rrc: device). All these actions are guaranteed harmless if they are performed in a case when they are not needed. This use of the am_clean_up method may not be so convenient in a more complex application where a variety of more specific error recovery procedures may be needed. 16 - 10 CHAPTER 17 SERIES 3A ATTACHED APPLICATIONS The Series 3a version of the object libraries introduced the concept of attached applications, that is, the ability of one application to make use of the functionality of a second, cooperating, application. This feature is used by the Series 3a Agenda application, which uses the Word application to edit memo documents that can be attached to Agenda entries. This section provides an overview of the basic mechanisms of attaching one application to another. Only general guidelines can be given, since the details will be highly dependent on the two applications involved and on the particular circumstances under which the attachment is made. The following description assumes that process A wishes to attach to and make use of process B. It further assumes that the only communication that occurs between the two processes is on start-up and on termination of the attached process. Applications are free to implement more sophisticated inter-process communications with attached applications. Starting the attached process The first step is for process A to use a call to p_execc to create process B, passing a suitable command line: VOID CreateProcess(UWORD *ppid, TEXT *pfspec, UBYTE *pcomline, INT comlen) { *ppid=f_leave (p_execc (pfspec, pcomline, comlen) ) ; } The command line should contain data that can be used by process B to determine that is being started as an attached application. One simple way of doing this is to use a non-standard value for the command byte, provided it is not needed for its normal purpose of determining how a file-based application obtains its initial file. A particularly suitable command byte value to use is H_COMMAND_Bypass ('Z’) since this value prevents the HWIM application manager from parsing any following command line data. For further details of command line parsing, see the description of the am_init method in the HWIMMAN Application Manager chapter of the HWIM Reference manual. Following any standard elements of the command line that are needed by the attached application, further specific information may be appended. This information must minimally include the process ID of process A, but may also contain one or more pointers to data in process A to which process B will require access. One such item will normally be a pointer to a status word (possibly of some active object in process A) so that process B can notify process A of a successful termination. Following the creation of Process B, process A must call p_logona in order to be notified if process B terminates unexpectedly, either because of some error condition, or because process B is killed from the System Screen. A convenient way is to use an instance of the XADD tocona active object class: VOID LogTermination(VOID *hlogona, UWORD pid, VOID *hand, INT method) { *hlogona=f_newsend (CAT_MYAPP_XADD, C_LOGONA, O_AO_INIT, pid, hand, method) ; } When process B terminates, the object with handle hang in process A will receive a message with message number method. 17-1 OBJECT ORIENTED PROGRAMMING GUIDE If any aspect of communication between the two processes is handled by means of an active object in process A, this object should be sent an ao_quzuE message at this point, before process B starts to execute. This active object is expected to receive an ao_RUN message only when process A is signalled by process B. Process A should then set the priority of process B higher than its own and start process B running with a call to p_presume: VOID RunProcess(UWORD pid) { p_setpri (pid, 0x84) ; p_presume (pid) ; } The call to p_presume will not return until process B reduces its priority to be not greater than that of process A (or until process B either terminates, or has no other pending activity). The mechanism thus relies on the cooperation of process B, which is expected to reduce its priority as soon as possible. On return from the call to p_presume, process A uses WSERV'S ws_hide_app method to hide itself from the System Screen. The whole process may be summarised by the following code: #include GLREF_D PR_WSERV *w_ws; LOCAL_D WS_HIDE_APP_DATA hidden; VOID AttachProc(TEXT *pame, UWORD *ppid, UBYTE *pcomline, INT comlen, VOID *hlogona, VOID *hand, INT method) { CreateProcess (ppid, pfspec, pcomline, comlen) ; LogTermination (hlogona, *ppid, hand, method) ; /* queue any active object here */ RunProcess (*ppid) ; p_send4 (w_ws,O_WS_HIDE_APP, *ppid, &hidden) ; } Initialisation of the attached process The initialisation code of process B (normally in its ws_dyn_init method) must detect, from the command line data, that it is running as an attached application. It must also, minimally, read the process ID of process A (and, if supplied, the offset of a status word) from its command line. Process B should send its instance of a subclass of wsERV a WS_ATTACH_APP message, passing the process ID of process A. Process B may read one or more items of data from process A, using the process ID and other data passed to it in the command line. It will usually also make some change to its appearance to indicate that it is running as an attached application. As soon as possible, after its essential initialisation, process B should call wSetPriorityControl (TRUE) ; to return control of its priority to the Window Server, thereby allowing process A to resume execution. At this point, process A will return from its call to p_presume. Termination of the attached process This description assumes that, following its successful initialisation, the attached process runs (until its normal termination) independently of process A. It may be desirable for process B to log on to process A and thus be informed in the event of its abnormal termination. If the attached application terminates abnormally, process A will be notified by its instance of the Locona class. This notification should cause process A to take any necessary recovery action, including restoring itself from the hidden state by sending the message: p_send4 (w_ws,O_WS_HIDE_APP, FALSE, &hidden) ; 17-2 17 SERIES 3A ATTACHED APPLICATIONS The attached process will normally terminate on execution of the com_exit method of its command manager. At this point it will generally need to transfer information back to process A. This can most simply be accomplished by an inter-process copy (with a call to p_pcpyto) using the process ID of process A and one or more offsets into process A's data segment that were communicated to process B via its command line. Once any data has been successfully transferred, process B must notify process A that it is terminating successfully. One way of doing this is to copy a suitable value into a process A status word (the offset of which was passed to process B in its command line) and signal process A by calling p_iosignalbypid. Following this, process B may terminate. If using the method described in the previous paragraph, process A will be notified of the successful termination of the attached process by execution of the ao_run method of the appropriate active object. Process A should immediately destroy its instance of the Locona class, to prevent erroneous notification of an abnormal termination. It should then restore itself from the hidden state, as described earlier. This should be followed by any processing of the data sent back by the attached application. Termination of process B may need to be postponed until process A has successfully received and processed the data, One possible means of doing this is to use the same mechanism that was described above for notifying process A of the imminent termination of process B. To use this method, process B would create and queue an active object whose ao_run method contains the application's termination code, and send the offset of this object's status word to process A. Process A can then cause process B to terminate by copying a suitable value into the status word and then signalling process B by calling p_iosignalbypid. 17-3 CHAPTER 18 THE SERIES 3A AUTOMATIC TEST SYSTEM As its name suggests, the automatic test system (ATS) was conceived as a means of providing automated testing of Series 3a HWIM applications. As such it can be used to exercise an application, following a predetermined and repeatable sequence of operations. One example of such use is to display, in sequence, all an application's dialogs, to check that they do not contain text that is too wide to be displayed. This can be particularly valuable when validating the translation of the application's resource file into another language. The ATS provides a very general set of services. As a result, it can be used for purposes other than testing an application. Other uses include: e generating rolling demonstrations e asimple form of macro recording and playback e providing a measure of control for attached applications. A process that controls an ATS sequence does not have to be an HWIM application. It can be written in either C or OPL and does not need to have a user interface. The process that is running under the control of ATS does, however, have to be either a standard Series 3a HWIM application or one that mimics such an application's support for ATS. The ATS mechanism can not be used with applications that run on the Series 3. The ATS mechanism The ATS mechanism is implemented by means of standard inter-process messaging, using message types in the range TY_ATS_START_RANGE (0x30) tO TY_ATS_END_RANGE (0x3¢£) inclusive. These values, together with the message types that are explicitly supported and their corresponding message structures, are defined in the header file ats.h. A Series 3a HWIM application always supports the receipt of inter-process messages, regardless of whether or not the rLc_appMaN_1pcs flag is specified in the application's main (). Part of the Series 3a HWIMMAN initialisation is to create and initialise an instance of the XADD arssv class. This is a subclass of the OLIB server class and is used to process the receipt of ATS inter-process messages. In addition, the HWIMMAN initialisation code sets the gate.atson property of its instance of the caTE class to TRUE (and stores the handle of this instance in the DatGate magic static) so that another process can detect that the application supports ATS. In principle, an ATS controller program simply needs to use p_msendreceivea Or p_msendreceivew to send inter-process messages of the appropriate type (or types) to the application that is being controlled. If the application is known to support ATS, such as when using ATS to test a specific Series 3a HWIM application, this is all that is required. In other cases, the controller may have to check that the target application supports ATS before sending ATS messages. This is most conveniently done by creating an instance of the HWIM cate class (if it does not already exist) and sending it a GT_CHECK_ATS_ON message, passing the process ID of the target application. This method returns zero if ATS is supported by the target application and E_cEN_nsup if it is not (other errors may be returned if, for example, the specified process does not exist). In order to respond positively to this query, the target application must itself have created an instance of the cate class, storing its handle in the patGate magic static, and have set its gate.atson property to TRUE. 18-1 OBJECT ORIENTED PROGRAMMING GUIDE The data for an ATS inter-process message is conrained in an ats_mess struct, consisting of a standard E_MESSAGE inter-process message header (defined in epoc.h) and an ats_mEss_Bopy message body. The ATS_MEss and aTs_MEss_Bopy Structs are defined in ats.h as follows: typedef union { UWORD position; ATS_KEY_DEF k; ATS_DIAL_DEF d; UWORD delay; VOID *offs; WORD par; UWORD wid; } ATS_MESS_BODY; typedef struct { E_MESSAGE mess; ATS_MESS_BODY u; } ATS_MESS; The ats_KEY_DEF and atTs_DIAL_DEF structs are also defined in ats.h: typedef struct { UWORD key; UWORD mod; } ATS_KEY_DEF; typedef struct { UWORD main; UWORD mainlen; UWORD buts; WORD butslen; } ATS_DIAL_DEF; The ATS message types When an ATS inter-process message is received by an HWIM application, it is processed by the application's instance of the arssv class. This processing, for each type of message, is described in the Automatic Test System Classes chapter of the XADD Reference manual. The message types are also listed here, but with an emphasis on the way the messages are sent from the controlling process. For simplicity, all the examples given in this section send inter-process messages synchronously, using calls to p_msendreceivew. If a controlling application wishes to respond to other events, such as keypresses, it may use p_msendreceivea to send any of these inter-process messages asynchronously. An application is free to use any convenient combination of synchronous and asynchronous calls. The completion status, returned by p_msendreceivew or written to the status word used with p_msendreceivea, Will be zero (or, in some cases, a positive number) to indicate success, or a negative error to indicate failure. TY_ATS CLIENT POS Set client position Set the position in the task order of the specified process. The position is specified by the position member of an ars_mEss_Bopy Struct. Setting a position of zero brings the process to the front, as the foreground process. A position of ws_LAST_CLIENT_POSITION (defined in wlib.h) positions the process at the back. The following example brings the specified process to the front. #include VOID BringToFront (INT pid) { ATS_MESS_BODY u; u.position=0; p_msendreceivew (pid, TY_ATS_CLIENT_POS, &u) ; } 18 -2 18 THE SERIES 3A AUTOMATIC TEST SYSTEM TY_ATS_ KEY Send a keypress Send a keypress to the specified process. The keycode and any modifiers are specified by the k.key and k.moda members of an aTS_MESS_BODY struct. A receiving HWIM application will process the keypress as if it were received from the keyboard. The following example sends a Control-Psion-Enter keypress: #include #include VOID SendKey (INT pid) { ATS_MESS_BODY u; u.k.key=W_KEY_RETURN; u.k.mod=W_CTRL_MODIFIER|W_PSION_MODIFIER; . msendreceivew (pid, TY_ATS_KEY, &u) ; } TY_ATS_ PAUSE Pause an application Pause the specified process. The pause, in tenths of a second, is specified by the delay member of an ats_mEss_Bopy struct.. If a pause of zero is specified, the application's application manager will be sent an am_yIELD message, pausing the application until it has had an opportunity to service any outstanding events. The following example pauses an application for half a second: #include VOID Pause(INT pid) { ATS_MESS_BODY u; u.delay=5; p_msendreceivew (pid, TY_ATS_PAUSE, &u) ; } TY_ATS_ MESSAGE Display a message Display an information message in the top left corner of the screen.. The message text is pointed to by the offs (offset) member of an ats_mzss_Bopy struct. It must be supplied as a zero terminated string not exceeding 128 bytes in length, including the terminating zero. For example: #include VOID SendMessage(INT pid) { ATS_MESS_BODY u; u.offs="A remote message"; p_msendreceivew (pid, TY_ATS_MESSAGE, &u) ; } If the inter-process message is sent asynchronously, the buffer containing the string must remain in existence until the message completes. 18 -3 OBJECT ORIENTED PROGRAMMING GUIDE TY_ATS DIALOG Run a dialog Display and run a dialog in the specified process. The dialog is specified by the contents of the a member of an ats_mEss_Bopy struct. This is an ATS_DIAL_DEF Struct, defined in ats.h as: typedef struct { UWORD main; UWORD mainlen; UWORD buts; WORD butslen; } ATS_DIAL_DEF; The dialog must be represented by in-memory data, pointed to by d.main and of length d.mainlen. This data will usually have been loaded from a resource file, as illustrated below for the error dialog resource: RESOURCE DIALOG error_dialog { flags=DLGBOX_RBUF_FILLED | DLGBOX_NO_DDP; controls= { CONTROL { class=C_TEXTWIN; flags=DLGBOX_ITEM_CENTRE | DLGBOX_ITEM_DEAD | DLGBOX_ITEM_UNDERLINED; info=TXTMESS { flags=IN_TEXTWIN_AL_CENTRE; i ‘yy CONTROL { class=C_TEXTWIN; flags=DLGBOX_ITEM_CENTRE |DLGBOX_ITEM_DEAD; info=TXTMESS { flags=IN_TEXTWIN_AL_CENTRE; i a CONTROL { class=C_ACLIST; info=ACLIST { rid=ac_continue; ‘i ‘i } An HWIM controlling application could load this resource as shown in the following code fragment: ATS_MESS_BODY u; u.d.mainlen=p_send4 (w_am, O_AM_LOAD_RESOURCE, ERROR_DIALOG, &u.d.main) ; A non-HWIM controlling application might have to create a temporary instance of the OLIB rscriLe class to load the resource, as indicated below: HANDLE olib; VOID *hand ATS_MESS_BODY u; p_findlib("OLIB.DYL", &0lib) ; hand=f_newlibhsend(olib, C_RSCFILE,O_RS_INIT,DatCommandPtr) ; u.d.mainlen=p_send4 (hand, O_RS_READ, ERROR_DIALOG, &u.d.main) ; p_send2 (hand, O_DESTROY) ; Note that this code assumes that the resource file is built into the application file and is thus identified by the file specification string pointed to by the magic static DatcommandPtr. 18-4 18 THE SERIES 3A AUTOMATIC TEST SYSTEM The dialog resource may contain one item that requires an additional resource. The example resource is such a case, containing an action list that needs an additional acLisT_array resource, for example: RESOURCE ACLIST_ARRAY ac_continue { button = { PUSH_BUT { keycode=W_KEY_ESCAPE; str="Continue"; } ad } Any such additional resource should be pointed to by the d.buts element and its length should be given by d.butslen. The resource can be loaded by the same techique given above for the main dialog resource. For an HWIM controlling application, this could be: ATS_MESS_BODY u; u.d.butslen=p_send4 (w_am, O_AM_LOAD_RESOURCE, AC_CONTINUE, &u.d.buts) ; The dialog may not have more than one such item, which may be either an action list (as in the above example) a choice list or an edit box.. The edit box case is special, in that d.buts and d.butslen are used differently. The value of a.buts should be a pointer to a zero terminated string that does not exceed ars_MAX_EDITOR_LEN (256) bytes in length, including the zero terminator, and d.butslen should be set to a negative value whose magnitude is the index of the edit box item in the dialog box, plus one. The text string will be used to initialise the specified edit box. If the dialog does not contain an action list, a choice list or an edit box, d.buts and d.butslen should both be set to zero. Interpretation of the completion status depends on the dialog contents. In all cases, however, a negative completion status signifies an error. The error value z_GEN_FaIL (-1) is reserved to indicate that the user exited from the dialog box by pressing Escape. In general, this error should be explicitly detected since it will usually nor require an error message to be displayed. Terminating the dialog by pressing enter gives the following possible results: e if the dialog has no action list, choice list or edit box, the result is zero e if the dialog contains a choice list, the result is the index of the current item in the choice list (selecting the first item in the choice list gives a result of one) e if the dialog contains an edit box, the result is zero and the initialising text for the edit box is overwritten by the edited text, as a zero terminated string The following example runs the dialog defined by the resources given earlier in this section, returning the dialog result: #include INT RunDialog(INT pid) { ATS_MESS_BODY u; u.d.mainlen=p_send4 (w_am, O_AM_LOAD_RESOURCE, ERROR_DIALOG, &u.d.main) ; u.d.butslen=p_send4 (w_am, O_AM_LOAD_RESOURCE, AC_CONTINUE, &u.d.buts) ;u.position=0; return (p_msendreceivew (pid, TY_ATS_DIALOG, &u) ; } 18-5 OBJECT ORIENTED PROGRAMMING GUIDE TY_ATS SELF CHECK Run a consistency check Instruct the specified process to perform a self-consistency check on its data. Sends a ws_SELF_CHECK message to the application's instance of a subclass of wsERv, passing with this message the value of the par member of the ats_mess_Bopy struct. It is the application's responsibility to provide a meaningful ws_sel£_check method. For example: #include VOID CheckProcess(INT pid,WORD par) { ATS_MESS_BODY u; u.par=par; p_msendreceivew (pid, TY_ATS_SELF_CHECK, &u) ; } TY_ATS WINDOW_XSUM Perform a window checksum Perform a checksum on a specific window, or on the whole screen. To checksum a specific window, set the wid member of the ars_mzss_Bopy struct to contain the window ID. Use a value of zero to checksum the whole screen. A successful result gives a status word containing a checksum in the least significant byte and zero in the most significant byte. The following example returns either a negative error or a one-byte checksum value for the whole screen: #include INT ScreenXsum(INT pid) { ATS_MESS_BODY u; u.wid=0; return (p_msendreceivew (pid, TY_ATS_WINDOW_XSUM, &u) ) ; } TY_ATS_ RECORD Start or stop keypress recording Start or stop keypress recording from the specified process, depending on the value of the par member of the aTs_MESS_Bopy Struct. Starts keypress recording if par is non-zero, otherwise stops keypress recording. When keypress recording is on, the controlling application should repeatedly send ty_ats_GET_KkEy inter-process messages. Each of these messages will complete when the specified application receives a keypress. Attempting to start recording when keypress recording is already on will result in the error z_GEN_INUSE. Turning keypress recording off when there is an outstanding Ty_aTs_GET_kEy inter-process message will cancel the Ty_ATS_GET_KEY message. #include VOID SetRecordState(INT pid,INT flag) { ATS_MESS_BODY u; u.par=flag; p_msendreceivew (pid, TY_ATS_RECORD, &u) ; } 18 - 6 18 THE SERIES 3A AUTOMATIC TEST SYSTEM TY_ATS GET KEY Receive a keypress Receive notification of the next keypress received by the specified process. The details of the keypress will be written into an ars_xey struct, defined in ats.h as: typedef struct { UWORD time; UWORD keycode; UBYTE modifiers; UBYTE count; } ATS_KEY; Before sending this inter-process message, the address of this struct must be written to the of fs member of the ars_mEss_sopy Struct, as in the following example: #include LOCAL_D ATS_KEY nextkey; VOID GetNextKey (INT pid) { ATS_MESS_BODY u; u.offs=&nextkey; p_msendreceivew (pid, TY_ATS_GET_KEY, &u) ; } This inter-process message will normally be sent asynchronously, to allow the controlling process to be responsive to other events while waiting for notification of keypresses from the specified process. It is a programming error to send this inter-process message if keypress recording has not been turned on by means of an earlier Ty_aTS_RECoRD inter-process message. TY_ATS ALLCOUNT Check allocated memory Walk the heap of the specified process and display an information message showing the number of allocated cells and the total number of bytes of allocated memory. This inter-process message does not require any data to be written into the ars_mEss_sBopy struct. #include VOID BringToFront (INT pid) { ATS_MESS_BODY u; p_msendreceivew (pid, TY_ATS_CLIENT_POS, &u) ; } An example macro recorder This example provides a simple macro recording and playback facility for the Series 3a. Buildable code for this application can be installed in a \sibosdk\ats directory from the Optional Disk. The resulting kats.img file should be installed by transferring it to a \img directory on a Series 3a. When installed, the application will appear under the RunImg icon and can be run by selecting the Kats item and pressing Enter in the normal way. It runs in background and provides macro recording and playback for any foreground HWIM application. The macro recorder allows a number of named macros to be created by recording keystrokes received by the foreground application. It then allows these macros to be selected by name and played back to the foreground application. 18 -7 OBJECT ORIENTED PROGRAMMING GUIDE The program is intended to be an illustration of the use of the ATS mechanism rather than a full-featured application and has, in consequence, a number of limitations. For example, it does not distinguish between different applications. This means that a macro recorded from, say, the Word application can be played back to a different application, say, the Agenda - where the macro may have a quite different effect. It is not a file-based application and, although it supports a 'Save' keypress combination, the code to actually save the macros in a file is not implemented. The set of macros are stored in memory for the lifetime of the application and will be lost when the application is terminated. The program captures the following keypress combinations: Cont rol-Psion-Space Start/Stop recording a macro for the current foreground application Control-Psion-Enter, Start/Stop playback of a macro to the current foreground application Control-Psion-Tab Control-Psion-Diamond Save the current set of macros (the save code is not implemented) The application itself is written in straightforward C, with a minimum of object oriented code, illustrating that an ATS controller does not have to be an HWIM application. The following discussion describes the main features of the code, but does not list the entire contents of kats.c. It is intended to be read in conjunction with the code itself. The application's main() is simply: GLDEF_C INT main(VOID) { INT ret; ret=p_enterl (Initialise); if (!ret) MainLoop(); return (ret); } The initialisation is carried out under the protection of p_enter, in order to catch any failure. #pragma save, ENTER_CALL LOCAL_C INT Initialise (VOID) { FindOlib(); /* get the category handle of the OLIB library */ CreateGate(); /* create an instance of the HWIM GATE class */ LoadDialogData(); /* load the dialog resources into allocated memory */ CreateMacroStorage(); /* create/initialise an instance of the OLIB VAXVAR class */ ConnectToWindowServer(); /* uses the window server wConnect function */ CaptureKeys (); /* uses the window server wCaptureKey function */ Message ("Keyboard macro support loaded"); /* inform user of successful start-up */ return (0); } #pragma restore CreateGate Stores the category handle of the HWIM DYL in a static variable before creating an instance of the cate class. The gt_check_ats_on method of this instance is used to as a convenient means of checking the validity of an application to receive ATS inter-process messages. LOCAL_C VOID CreateGate (VOID) { HANDLE hwim; p_findlib("HWIM.DYL", &hwim) ; DatGate=f_newlibh (hwim, C_GATE) ; } FindOlib uses the same technique, storing the category handle in a static variable for later use to create instances of OLIB classes. Note that communication between many of the routines is by means of static variables. 18-8 18 THE SERIES 3A AUTOMATIC TEST SYSTEM The main event loop of the application is as follows. This is fairly straightforward, at least in terms of handling the receipt of ww_kry events: LOCAL_C VOID MainLoop (VOID) { FOREVER { wGetEvent (&event) ; wait: p_iowait (); switch (event.type) { case E_FILE_PENDING: if (rec_active && rec_stat!=E_FILE_PENDING) { rec_active=FALSE; if (record) /* else recording has previously been cancelled */ Next StageInRecord(); } else if { play_active=FALSE; if (playback) /* else playback has previously been cancelled */ Next StageInPlayback (); (play_active && play_stat!=E_FILE_PENDING) } goto wait; case WM_FOREGROUND: wClientPosition(WS_LAST_CLIENT_POSITION,0); /* never come to foreground */ break; case WM_KEY: switch (event.p.key.keycode) { case KATS_SAVE: if (record) Message("Can't save macros while recording"); else if (playback) Message("Can't save macros while playing back"); else if (!changed) Message("No macro changes to save"); else SavelfConfirmed() ; break; case KATS_RECORD: if (record) StopRecording(); else if (playback) Message("Can't record while playing back"); else StartRecording(); break; case KATS_PLAYBACK: case KATS_PLAYBACK2: if (playback) { Message ("Playback terminated") ; StopPlayback (); } else if (record) Message("Can't play back while recording") ; else if (!nmacros) Message ("Nothing to play back"); else StartPlayback (); } This application is designed to run in background. It may, however, become the foreground application as a result of another application either terminating or going into the background. In such a case it will receive a WM_FOREGROUND event and simply responds to this event by sending itself into the background. 18-9 OBJECT ORIENTED PROGRAMMING GUIDE Some of the ATS inter-process messaging is performed asynchronously. The completion of such a message signals the application with an event that is separate from the type of event that is requested by the call to wcetEvent. Events of this type are recognised by the fact that the weetEvent event type has not changed from the value &_F1LE_pENDING. As can be deduced from the above code, the only asynchronous inter-process messages are the ones that receive a keypress (ty_aTs_GET_kEy) while recording a macro and send a keypress (Ty_aTs_xey) while playing back a macro. The application identifies the current foreground client and checks that it is capable of receiving ATS messages as follows: LOCAL_C INT GetForegroundClient (VOID) { UWORD pids[WS_MAX_CLIENTS+1]; wGetProcessList (&pids[0]); return (pids[0]); } LOCAL_C INT GetTestForegroundClient (VOID) { INT pid; pid=GetForegroundClient (); if (!p_send3 (DatGate,O_GT_CHECK_ATS_ON, pid) ) return (pid) ; /* okay */ p_sound (5,320); p_sound (5,280); p_sound (5,240); return (NULL) ; } This is used before sending most ATS inter-process messages, as illustrated below. In this case the inter- process messaging is performed synchronously. LOCAL_C VOID ForegroundAtsWait (INT type,ATS_MESS_BODY *pu) { INT pid; pid=GetTestForegroundClient (); if (pid) p_msendreceivew (pid, type, pu) ; } This form of test and wait is used, for example, to send information messages to be displayed by the foreground application: LOCAL_C VOID Message(TEXT *msg) { ATS_MESS_BODY u; u.offs=msg; ForegroundAt sWait (TY_ATS_MESSAGE, &u) ; } Information messages of this form are used extensively in the application, as can be seen in the code given earlier. It is also used to display error messages, as in the following case: LOCAL_C VOID TellErr(INT err) { TEXT buf[64]; if (err==E_GEN_FAIL) return; p_errs (&buf[0],err); Message (&buf[0]); } LOCAL_C VOID TellOom(VOID) { /* report failure due to out-of-memory error */ TellErr (E_GEN_NOMEMORY) ; } 18 - 10 18 THE SERIES 3A AUTOMATIC TEST SYSTEM Note that Te11Err is also used to report an error in the completion of an ATS dialog. It therefore does not report an error for the error number &_GEN_Fatt (the value resulting from pressing Esc when an ATS dialog is being presented). The application enters the recording state by calling startRecording: LOCAL_C VOID StartRecording (VOID) { pid=GetTestForegroundClient (); if (!pid) return; if (GetMacroName () ) { record=TRUE; TransmitRecordState(); Message ("Recording..."); mac_rec.num_keys=0; RequestNextKey (); } } The routine GetMacroName uses an ATS dialog to obtain a name for the macro: LOCAL_C INT GetMacroName (VOID) { ATS_MESS_BODY u; INT ret; u.d=dl_edit; ret=p_msendreceivew (pid, TY_ATS_DIALOG, &u) ; if (ret<0) { TellErr (ret); return (0); } return (CheckNotMatching()); } where CheckNotMat ching scans any existing macros to ensure that the name is not a duplicate of an existing name (and offers the option to overwrite the existing macro if the name is a duplicate). The notification of keypresses from the foreground application is enabled by setting the static variable recording to TRUE and calling TransmitRecordState, which sends a Ty_ATS_RECORD inter-process message: LOCAL_C VOID TransmitRecordState (VOID) { ATS_MESS_BODY u; u.par=record; p_msendreceivew (pid, TY_ATS_RECORD, &u) ; } The receipt of notification of the first keypress is handled by RequestNextKey, which writes the keypress data into the static struct full_key: LOCAL_C VOID RequestNextKey (VOID) { ATS_MESS_BODY u; u.offs=(&full_key); rec_active=TRUE; p_msendreceivea (pid, TY_ATS_GET_KEY, &u, &rec_stat) ; } 18 - 11 OBJECT ORIENTED PROGRAMMING GUIDE Subsequent keypresses are received by repeated calls to Next StageInRecord. This copies the keycode and modifiers for the previously received key into the keys array of the mac_rec static structure and increments the count of received keys before calling RequestNextKey. In addition, it terminates recording when the maximum allowed number - max_num_KEys (128) - of keypresses for any one macro has been received: LOCAL_C VOID NextStageInRecord (VOID) { ATS_KEY_DEF *short_key; short_key=(&mac_rec.keys [mac_rec.num_keys++]); short_key->key=full_key.keycode; short_key-—>mod=full_key.modifiers; changed=TRUE; if (mac_rec.num_keys==MAX_NUM_KEYS) StopRecording(); else RequestNextKey (); } The recording of a macro is terminated by a call to stopRecording: LOCAL_C VOID StopRecording (VOID) { record=FALSE; TransmitRecordState(); if (!'mac_rec.num_keys) { Message ("Recording cancelled") ; return; } if (p_enter1l (AppendMacro) <0) TellOom() ; else if (mac_rec.num_keys==MAX_NUM_KEYS) Message ("Maximum number of keystrokes reached") ; else Message("Finished recording") ; } The data of the macro, including the macro name and the array of keypresses, is stored by appending it as a single record to the application's instance of vaxvar. Note that the macro is stored only if one or more keypresses have been received. Playback is implemented by a similar technique, using the routines startPlayback, PlayNextKey and StopPlayback. 18 - 12 APPENDIX A CATEGORY FILES A category file contains the class definitions for all the subclasses provided by an image or dynamic library file. It is expected to have a .cat file name extension. The category file defines the group of classes that form an object oriented category. Object oriented classes and categories are explained in the /ntroduction chapter of this manual and in the Object Oriented Programming chapter of the PLIB Reference manual. A category file is the input to the category translator tool, ctran.exe, which generates a number of files, as described later in in this chapter. Category file content The content of a category file is best explained in conjunction with the following example: A demonstration cat file IMAGE demo ! External reference to OLIB library EXTERNAL olib INCLUDE p_std.h INCLUDE p_object.h INCLUDE varray.g CLASS dummy root Dummy class definition, as an illustration only { REPLACE destroy ADD dm_init DEFER dm_sub CONSTANTS { ! for the buffer required for knowledge of VAFLAT the class name and its superclass Methods follow... free buffer and supersend create VAFLAT component and allocate buffer defined by a subclass... auxiliary symbolic constants DUMMY_BUF_SIZE 128 allocated buffer size ! for the VAFLAT component DUMMY_GRAN 16 } TYPES { typedef struct { TEXT *buf; UWORD len; } DUMMY_BUF; } PROPERTY 1 { PR_VAFLAT *array; DUMMY_BUF buffer; } contains auxiliary structs /* comments here are exceptional */ pointer to allocated buffer the component VAFLAT instance OBJECT ORIENTED PROGRAMMING GUIDE CLASS sub dummy Subclass of dummy { REPLACE dm_sub ..So that's what it does } This example is copiously commented, to illustrate where comments are allowed. The general rules are: e any number of lines of comment text may be placed at the start of the file, or in the lines immediately following a cass declaration e any line starting with an exclamation mark, optionally preceded by whitespace, is ignored e trailing comment text may be placed on any line (except for a typedef struct line in the tyPEs section) provided it is separated by whitespace from significant content. The whole of a typedef struct line is output to the .g file. Any comment in this line must, therefore, be suitable for inclusion in a C source file. Not counting comment lines, the structure of a category file is: ¢ an IMAGE OF LIBRARY Statement e zero or More EXTERNAL Statements @ one or more INCLUDE statements ¢ zero or more cLass statements e zero Or More REQUIRE Statements The first non-comment line of the file declares the category type and name. The keyword must be one of: IMAGE the executable will be an image (.img or .app) file LIBRARY the executable will be a dynamic library (.dy/) file The category name, in this case, "demo", must be the same as the file name. This category file must, therefore have the name demo.cat. An EXTERNAL Statement declares an external reference to a dynamic library (DYL). The file may contain any number of such external references. There must be an external reference to a DYL before a category file class definition may refer to, or subclass - directly or indirectly - a class from that DYL. The above example subclasses root, which is in the OLIB library and so must declare an ExTERNAL reference to OLIB. The effect is to include an external reference (.ext) file - in this case olib.ext - from the designated include directory. External reference files are generated by the ctran.exe category translator, and are described later. Since all subclasses are ultimately derived from the Root class,! all category files (except that for OLIB itself) must contain an EXTERNAL reference to OLIB. An INCLUDE statement includes a C language header file from the designated include directory. It is used in a similar way to #include in aC source file. All category files must IncLupE, either directly or indirectly, p_std.h and p_object.h. The above example also mncLupes the header file varray.g (generated by the category translation of the OLIB dynamic library category file and copied to the \sibosdk\include directory during installation). This file contains C #defines and typedets relating to the OLIB variable array Classes, including the definition of the pR_var.at struct. Class definition The ciass keyword introduces a class definition. It is followed by the name of the class and then the name of the parent superclass. A class name may be up to 15 characters long. The above example defines the class pummy which is a direct subclass of the root class. The layout of a class definition is significant; apart from leading whitespace, which is ignored, it must follow the pattern illustrated above - and in the class definitions given elsewhere. Exceptionally, a category may define its own root class and hence require no external reference to OLIB. A-2 APPENDIX A - CATEGORY FILES The class definition of each subclass lists its additional methods and any additional property. It may also, as in the above example, include the definitions of auxiliary structures and constants used by that class. There are many examples of class definitions in the manuals describing object oriented libraries (the OLIB Reference manual, for example). The class definition may include any number of method declarations,” introduced by the app, REPLACE or DEFER keywords. Each of these is followed by a method name, which may be up to 21 characters long. The method declarations may be followed by one of each of the constants, Types and PROPERTY keywords. The method declaration keywords have the following meanings: ADD declare a method in addition to the methods provided by the superclass. The name must be unique in relation to all other methods in this category, or any externally referenced categories. Although not compulsory, the name conventionally starts with a short prefix related to the name of the class in which it is introduced. REPLACE declare a method whose functionality is to replace that of a method supplied by a superclass. The name must be that of an existing method in the superclass inheritance tree. DEFER declare an additional method as for app, except that the functionality of the method is not defined by the current class and is expected to be provided by a subclass (using REPLACE). A class containing perErred methods is known as an abstract class and, in general, no instances of such a class will ever be created.3 It is recommended that each method name be followed by a concise descriptive comment. The constants keyword introduces a list of symbolic constant definitions, each consisting of the symbol name (conventionally in upper case) followed by the numeric value. The value may be an expression involving symbolic constants defined earlier, either in the category file itself, or in any included file. The expression must not contain any whitespace. The types keyword introduces a list of C language typedef struct definitions, whose layout should follow that given in the example category file. (Many further examples may be found in the class definitions shown for each class in, say, the OLIB Reference manual.) The property keyword introduces a list of data element declarations to be included in the struct that defines the class property. This keyword may optionally be followed by a literal number (expressions may not be used) that specifies how many component items listed in the property are to be sent an automatic pesTRoy message when an instance of the class is destroyed. This assumes that, for a value ncomp, the first ncomp items in the additional property for the class are either nun or handles (pointers to instances) of component objects. The automatic destruction mechanism is described in the Object Oriented Programming chapter of the PLIB Reference manual. It is implemented in the destroy method of the root class, described in the OLIB Reference manual. (See the Building a Dynamic Library chapter for a description of how a category may supply its own root class.) In the above example, pummy's component var.at instance will be automatically destroyed when pummy recelves a DESTROY Message. Sub-category files The contents of a category file may be be divided between a number of sub-category files, each of which must have a .cl file name extension. A sub-category file normally groups together a number of related classes.4 The OLIB category, for example, is constructed from a number of sub-category files, including: 2Subject to a maximum of 255 methods, including those inherited from the superclass tree. 3There is no formal requirement for all p—ErERred methods to be REPLACcEd and it is acceptable to create an instance of such a class provided that it is known that no pEFrERred method will ever be called. See, for example, the HWIM picsox class. 4The relationship may be by function, by inheritance, or by any other means appropriate to a particular application. OBJECT ORIENTED PROGRAMMING GUIDE varray.cl containing the class definitions of the segmented buffer and variable array classes described in the SGBUF Segmented Buffer Class and Variable Arrays chapters of the OLIB Reference manual edit.cl containing the class definitions of the classes described in the Editable Documents chapter of the OLIB Reference manual time.cl containing the single T1me class definition, described in the TIME Class chapter of the OLIB Reference manual A sub-category file is included in the category file by means of the REqurRE keyword. For example, olib.cat contains the line: REQUIRE varray to include the sub-category file varray.cl. Not counting comment lines, the structure of a sub-category file is: @ aNAME Statement @ one or more INCLUDE statements @ one or more cLass statements A sub-category file must start with a Name statement, specifying the sub-category name. This name must be the same as the file name. Thus the timer.cl sub-category file must start with the declaration: NAME timer A sub-category file must not contain EXTERNAL statements and normally will not contain REQUIRE keywords. During translation of the category file (described in the Category Translation chapter) a separate generated header (.g) file is created for each sub-category file. Using sub-category files A category file may contain in-line class definitions as well as those contained in REqurREd sub-category files. For example, the OLIB category file (olib.cat) is: LIBRARY olib INCLUDE p_std.h INCLUDE p_object.h CLASS root The ultimate superclass - all other classes have root as their ancestor. { ADD destroy PROPERTY { P_OBJECT pc; class link } } REQUIRE varray REQUIRE appman REQUIRE ipc REQUIRE factive REQUIRE time REQUIRE timer REQUIRE tlvfile REQUIRE edit The OLIB category file is unusual in that it contains no references to external categories. It therefore contains no EXTERNAL statements and no 1ncLupEs of any .g files. The category translation of olib.cat generates olib.g and a further .g file (such as varray.g) for each of the sub-category files. APPENDIX A - CATEGORY FILES The information in a .g file may include: e external category numbers e includes of .A and .g files e class numbers e¢ method numbers e = property structures e = auxiliary class constants e = auxiliary class structures This information is mainly° required by: e¢ C source files that provide the method functions for the classes in the category ¢ C source files that provide the method functions for classes in another category, if they refer to these classes (either by using or by subclassing) The definitions of category numbers only appear in the .g file generated from the main category file (such as olib.g). If this file contains no in-line class definitions, this is effectively all that the .g file contains. The includes of ./ and .g files result from 1NcLUDE statements in either the main category file or a sub- category file. The 1ncLupE statements may be tailored to the known dependencies between sub-categories. For example, the OLIB appman.cl sub-category file starts as follows: NAME appman INCLUDE varray.g INCLUDE p_que.h INCLUDE p_gen.h INCLUDE p_file.h Note that appman.cl requires a reference to varray.g since the appman class has a cLEANUP component, and CLEANUP is a subclass of an array class. Although the classes in appman.cl depend on olib.g and also require the inclusion of p_std.h and p_object.h, these do not need to be included explicitly. The classes in varray.cl also depend on these files, so varray.g can be relied on to perform the necessary includes. There is automatic protection in .g files against including the same file more than once, so explicit inclusion of these files, although not necessary, is harmless. One of the major advantages of using sub-categories stems from the observation that a particular C source file almost never needs to include all the category information. Judicious division of a category file into sub-categories to reduce the amount of category data included in each of the source files can significantly reduce the time and, more importantly, the memory usage involved in building an application. In general, classes should be grouped according to their relationships (which imply dependencies) either from subclassing or from usage as a component. For example, the classes in the OLIB varray.cl sub- category file (with one exception) are general-purpose array classes, all of which have varoot in their inheritance tree. In contrast, the classes in the appman.cl sub-category file are related either by being used as components of the application manager, or by the intimate relationship between the application manager and active objects. It is perfectly acceptable for a sub-category file to contain a single class definition, if that class is isolated from other classes in the category. For example, the OLIB trme class has its own sub-category file, time.cl. In this case it is particularly worthwhile since the Time class defines a large number of constants and structures. 5 Limited information may also be required by other files. For example, a resource file containing command menu items will require the corresponding command manager method numbers. OBJECT ORIENTED PROGRAMMING GUIDE Category translation The category translator tool, ctran.exe, is the principal tool used in the creation of a SIBO object oriented program. It generates a number of output files from a single input category file (which may include a number of external category references, header files and sub-category files). Which output files are generated, and where they are put, is determined by flags passed to ctran. exe, whose syntax is: ctran [-e -x[] -c[] -g[] -a[] -i[] -l[] -s - k -v] where: specifies the input category file name, assumed to have a file name extension of .cat -e specifies the directory from which files are included by the exTERNaAL and INCLUDE category file keywords -x [ specifies that an external reference .ext file should be generated in the current directory or, if given, the specified directory -c[ specifies that a .c C language category source file should be generated in the current directory or, if given, the specified directory -g [ specifies that one or more .g C language include files should be generated in the current directory or, if given, the specified directory -a[ specifies that a .asm assembly language category source file should be generated in the current directory or, if given, the specified directory. This flag is not normally set in the SDK development environment -il] specifies that one or more .ing assembly language include files should be generated in the current directory or, if given, the specified directory. This flag is not normally set in the SDK development environment -l[] specifies that a .Jis human readable class report file should be generated in the current directory or, if given, the specified directory -s specifies that output is being generated for the SDK. This flag is normally set in the SDK development environment. Omitting this flag causes additional information, irrelevant to the SDK development environment, to be included in the output .c and .g files -k specifies that a set of skeleton method function C source files are to be generated. A separate source file is generated for each class in the category file. The name of each file is the same as the class name, and has a.c extension. Each file is generated only if a file of that name does not already exist, so there is no danger of accidentally overwriting an existing source file. A warning is given if the file can not be created -v specifies verbose on-screen progress reports The various output files are best described with reference to the demonstration category file, listed at the start of this chapter. APPENDIX A - CATEGORY FILES The .ext external reference file The .ext file publishes the category name and information on the classes it contains. For each class in the category the file publishes: e the class name e the method names at their first appearance (when declared with app or DEFER) e flags indicating if the subclass has additional property and if it supplies method functions This file should be included (by means of the ExTERNAL keyword) in other category files that make external references to the category it describes. There is no need to generate this file if no other category makes such references. The demo.ext file generated from demo.cat is as follows: Generated by Ctran from demo.cat IMAGE demo CLASS dummy root { DECLARE dm_init DECLARE dm_sub HAS_METHOD HAS_PROPERTY } CLASS sub dummy { HAS_METHOD } The .c C language category source file This file contains the C language data definitions and initialisations for each class in the category. It is the source of the class descriptors which reside in the same code segment as the method functions (see also the Object Oriented Programming chapter of the PLIB Reference manual). This file must always be generated, and must be compiled and linked into the application. Before linking, the object file must be converted, by means of the ecobj.exe tool, to move the class descriptor data into the code segment. This is performed automatically by the ct.bat batch file described in the Building an Application chapter. The demo.c file generated from demo.cat is effectively as follows: /* Generated by Ctran from demo.cat */ #include /* External Superclass References */ #define ERC_ROOT C_ROOT /* Class dummy */ GLREF_C VOID dummy_destroy(); GLREF_C VOID dummy_dm_init(); GLDEF_D struct { P_CLASS c; VOID (*v[2]) (0; } c_dummy= { {1, (P_CLASS *)ERC_ROOT, sizeof (PR_DUMMY) ,0,0x6b,2,1}, { dummy_destroy, dummy_dm_init } OBJECT ORIENTED PROGRAMMING GUIDE /* Class sub */ GLREF_C VOID sub_dm_sub(); GLDEF_D struct { P_CLASS c; VOID (*v[1]) QO; } c_sub= { {0, (P_CLASS *) &c_dummy, sizeof (PR_SUB) ,2,0x6b,1,0}, { sub_dm_sub } di /* Class Lookup Table */ GLDEF_D P_CLASS *ClassTable[]= { (P_CLASS *) &c_dummy, (P_CLASS *) &c_sub i /* External Category Name Table */ GLDEF_D struct { UWORD number; UBYTE names[1] [14]; } ExtCatTable = {1, { {NOt Pa Bae By eo Ds T!,040;°09:07:0,'0} } hi Note the expansion of the method function names to include the class name, as well as the declared method name. This ensures that, even if a method replaces one supplied by a superclass, the method function names are unique. The .g C language include file A..g include file contains the generated enumerated constants for the categories, classes and methods declared in the category file, together with the generated structures that define the property of each class. In addition it reproduces the auxiliary constant and structure definitions from the various classes. The category numbers section lists the numbers used to refer to categories from within this category (for example, when creating an instance with p_new). This section will always contain at least one entry, with value zero, for the local category. There will be one additional entry for each externally referenced category. Unless building an assembly language program, a .g file must always be generated, and must be #included in any C source file that refers to any of the items mentioned above. Note that the name of a symbolic constant representing a class number is derived from the class name with a preceding "c_", and that of a method number is "o_" followed by the declared method name. The symbolic constant representing a class number is always of the form cat_xxxx_yyyy, where xxxx is the name of the local category and vvvy is the name of the category to which the number refers. Thus the DEMO category refers to itself via the constant caT_pEMo_pEMo and refers to the OLIB category by CAT_DEMO_OLIB. If the category file includes one or more sub-category (.c/) files, an additional .g file is generated for each sub-category. The content of each . g file corresponds to the content of the relevant category or sub- category file. Note that the .g file corresponding to the main category (.cat) file does not contain information relating to the content of any of the sub-category files. APPENDIX A - CATEGORY FILES The demo.g file generated from demo.cat is effectively as follows: /* Generated by Ctran from demo.cat */ define DEMO_G ifndef P_STD_H include endif ifndef P_OBJECT_H include endif ifndef VARRAY_G include endif /* Category Numbers */ define CAT_DEMO_DEMO 0 define CAT_DEMO_OLIB 1 /* Class Numbers */ define C_DUMMY 0 define C_SUB 1 /* Method Numbers */ define O_DM_SUB 2 define O_DM_INIT 1 /* Constants for dummy */ define DUMMY_BUFFER_SIZE 128 define DUMMY_GRAN 16 /* Types for dummy */ typedef struct { TEXT *buf; UWORD len; } DUMMY_BUF; /* Property of dummy */ typedef struct { PR_VAFLAT *array; DUMMY_BUF buffer; } PRS_DUMMY; typedef struct pr_dummy { PRS_ROOT root; PRS_DUMMY dummy; } PR_DUMMY; /* Property of sub */ typedef struct pr_sub { PRS_ROOT root; PRS_DUMMY dummy; } PR_SUB; The .asm assembly language category source file This is an assembly language version of the .c category source file, described above. It contains the assembly language data definitions and initialisations for each class in the category. It is the source of the class descriptors which reside in the same code segment as the method functions (see also the Object Oriented Programming chapter of the PLIB Reference manual). This file should only be generated, instead of the corresponding .c file, if the application is written in assembly language, in which case it must be assembled and linked into the application. OBJECT ORIENTED PROGRAMMING GUIDE The .ing assembly language include file A .ing file is an assembly language version of the .g file, described above. It contains the generated enumerated constants for the categories, classes and methods declared in the category file, together with the generated structures that define the property of each class. In addition it reproduces the auxiliary constant and structure definitions from the various classes. It should only be generated if the application is written in assembly language, in which case it must be INCLUDEd in any assembly language source file that refers to any of the items mentioned above. If the category file includes one or more sub-category (.c/) files, an additional .ing file is generated for each sub-category. The content of each .ing file corresponds to the content of the relevant category or sub- category file. Note that the .ing file corresponding to the main category (.cat) file does not contain information relating to the content of any of the sub-category files. The .lis category listing file A .lis file is a plain text listing, by sub-category, of all the classes in a category, together with their inheritance trees and use of component classes. It may be useful to aid a review of how classes are grouped into sub-categories, but is not otherwise required. The demo.lis file generated from demo.cat is as follows: Generated by Ctran from demo.cat IMAGE demo KKKKKKKKKK demo KKK KKK KK KK dummy Derived from root References vaflat Subclassed by sub sub Derived from dummy, root The .c skeleton method function source file A separate .c method function source file is generated for each class defined in either the category file or any included sub-category files. The file name is the same as the class name of the class from which it is generated (but truncated to the first eight characters in the case of long class names). Generating these files avoids the repetitive typing involved in creating method function files by hand. It is expected that a set of method function source files will be generated once only for each application. A suitable batch file for creating the skeleton method function source files is ctskel. bat: @echo off ctran %1 -e..\include\ -s -k which is used as, for example: ctskel demo This batch file is copied into the \sibosdk\oopdemo directory on installation of the optional OOP component of the SDK. It assumes that the SDK is installed on C: and should, of course, be modified as necessary for an SDK installed on a different drive. Each source file contains the required include files and a minimal skeleton for each method function that must be supplied by the corresponding class. The file generated from demo.cat for class pummy is as follows: APPENDIX A - CATEGORY FILES /* dummy .c Generated by Ctran from demo.cat */ #include #pragma METHOD_CALL METHOD VOID dummy_destroy(PR_SUB *self) { } METHOD VOID dummy_dm_init (PR_SUB *self) { } and that for class sus is: /* sub.c Generated by Ctran from demo.cat */f #include #pragma METHOD_CALL METHOD VOID sub_dm_sub(PR_SUB *self) { } The general form of these files is described in appendix B of this manual. In the generated skeleton files, all method functions are declared as being vorp and are supplied with only the first, mandatory, parameter (being the handle of the class instance). It is the responsibility of the programmer to make such modifications as are necessary to match the requirements of the method functions of a particular application. APPENDIX B METHOD FUNCTION SOURCE FILES For each method included in the category file by means of either app or REPLACE, there must be a corresponding method function declared in one of the source files that is compiled and linked into the application. A set of skeleton source files is generated automatically from the category file if a -k flag is passed to ctran.exe, as described in the Category Translation chapter. There is no formal requirement as to how method functions should be divided between a number of source files. The most common scheme is to place all the method functions for a particular class in a single source file, using a separate file for each class (the generated skeleton files follow this scheme). Depending on circumstances, however, the method functions of a single class may be divided between two or more source files: alternatively, a single file may contain the method functions of more than one class. A viable alternative to the above scheme is to group all the method functions of the classes of a single sub- category file. The overriding aim should be to enhance the clarity and maintainability of the code. The name of each method function is constructed by concatenating the class name with an underscore and the method name. Thus, the va_test method of the prru1stT class has a method function with the name dirlist_va_test. Method function names may not be more than 31 characters long - which is less than the sum of the maximum lengths of the class name (15 characters) and the method name (21 characters). Method function parameters A method function may have from one to four 16-bit parameters. These correspond, with the omission of the message number, to the parameters passed to a message sending function (such as p_sena). Note that the message sending mechanism removes the method number from the list of parameters before calling the appropriate method function. The first parameter of each method function must be the object handle, that is, the handle of the class instance. This handle, which is a pointer to the heap cell containing the instance property struct, is conventionally given the name seit. The name of the struct itself is derived by adding a leading pR_ to the class name. This struct is defined in the .g file corresponding to the .cat or .cl file that contains the corresponding class definition. This .g file must therefore be #included in the appropriate source file(s). Calling conventions and function order As described in the Introduction chapter, a method function should normally be declared with the METHOD_CALL calling convention (that is, preceded by a #pragma METHOD_CALL statement). The only exception is when a method function is the target of both a message sending function (such as p_send or p_entersend) and p_enter. In this case the method function must be declared with the cpEct calling convention. A method function source file may include any number of local and/or global auxiliary functions. To avoid the need to repeatedly switch between different calling conventions, it is recommended that functions should appear in the file in a standard order, for example: e local and global auxiliary functions, declared with LocAL_c or GLDEF_c e local and global auxiliary functions that are the target of p_enter, preceded by a #pragma ENTER_CALL statement e method functions that are also the target of p_enter, preceded by a #pragma CDECL statement e the remaining method functions, preceded by a #pragma METHOD_CALL statement OBJECT ORIENTED PROGRAMMING GUIDE Where possible, auxiliary functions should be written in ‘topological’ order, that is, a function should appear before any reference to that function. Method functions, however, may appear in any order. Sometimes, conflicting requirements mean that it is necessary to intermix functions of different calling conventions. If, for example, you need to position an auxiliary function that is the target of p_enter between other 'normal' auxiliary functions, you can surround the function in question with the pair of statements: #pragma save, ENTER_CALL #pragma restore APPENDIX C MECHANISMS This appendix provides more detail than was presented in the Basic concepts section of the Introduction chapter. In addition to giving further insight into the mechanisms involved in Psion's Object Oriented system, it may prove useful while debugging errant applications. Classes A class defines the data (property) and behaviour (methods) of a particular type of object. A class is implemented as: e a set of method functions e aclass descriptor The method functions are those functions that implement the class methods. The source code format of these functions is described in Appendix B. Class descriptor A class descriptor is a data structure that resides in the same code segment as the method functions of that class. It contains information relevant to the class and consists of a header, followed by an array of 16-bit code segment offsets to the method functions. The C structure for the class descriptor header is defined as: typedef { UWORD cat; struct p_class *super; /* superclass class */ UWORD len; /* length of instance */ UWORD base; /* base function number */ UBYTE sig_6b; /* signature - should be Ox6b */ UBYTE num; /* number of entries in vector table */ UBYTE ncomp; /* number of component objects */ } P_CLASS; The contents of a loaded and dynamically linked class descriptor is as follows: cat the handle of the code segment that contains the superclass class descriptor super the offset of the superclass class descriptor within segment cat, or zero if there is no superclass (i.e. this is the class descriptor of a root class) len the length of an instance of the class, including the lengths of inherited property (used by, for example, p_new and p_newlibh to create an instance) base the base method number corresponding to the first entry in the method table that follows the class descriptor sig_6b a signature (which should be 0x6) to guard against a bad class reference num the number of entries in the method table ncomp the number of component objects to be automatically destroyed OBJECT ORIENTED PROGRAMMING GUIDE The following method table may contain "holes", represented by zeroes, corresponding to those method functions that are supplied by a superclass. The following diagram illustrates a typical situation: subclass superclass header class descriptor where +a, +b, +c and +d represent code segment offsets to method functions. In this example, the superclass provides two methods, whose method functions are at code segment offsets +a and +b. The subclass replaces the second method of its superclass, with a method function at an offset +c in its own code segment. It also adds one new method, with method function code at an offset +d. The first method of the subclass is supplied by its superclass, as indicated by the zero in the table of offsets. Sending the corresponding message to the subclass will therefore cause the method function at offset +a in the superclass code segment to be executed. Note that the two classes may be in the same or different code segments. The resolution of the links between classes in different code segments is part of the dynamic linkage mechanism, described later. Object creation An instance of a class is implemented as a cell in the heap and is created by calling p_new, £_new, f_newlibh, p_newlibh, f_newsend Or f_newlibhsend. The returned handle is a pointer to the heap cell. The first two words of the cell contain the location of the class descriptor of the class of which the object is an instance (in exactly the same way as for the superclass reference in a linked class descriptor). The remainder of the cell contains the property (if any) of that class, including any property inherited from its superclasses. The property contribution of a class always follows the property contribution inherited from its superclass, as illustrated in the following diagram. handle class descriptor property of C1 property of C2 property of C3 In this example, class c1 is subclasses the Root class, c2 subclasses c1 and c3 subclasses c2. The property of c3 is made up of the contribution from c3 itself and the contributions inherited from C2, c1 and the root class. The various contributions are ordered within the cell as shown. The pointer to the class descriptor that heads the cell is set up when an instance of that class is created. It is, in fact, the property of the Root class, which is the ultimate superclass of all classes. APPENDIX C - MECHANISMS The property contribution of a class always follows the property contribution inherited from its superclass and this ordering stays the same even if the class itself is susequently subclassed. Suppose, for example, that class cLassi subclasses the class root. The property of an instance of cLassi is made up of the contribution from ciassi1 itself and the contribution inherited from the Root class, as shown in the following diagram: handle points to: property of Root property of cLass1 If cLass2 in turn subclasses ciass1, the property of an instance of cLass2 is composed of the contributions from three classes, ordered within the cell as shown below: handle points to: property of Root property of cLass1 property of cLass2 All the functions (p_new etc) that create an object initialise the property with zeros. Categories A category is formally defined as being a group of one or more classes. The classes are packaged into a load module which, when loaded, occupies a single code segment. A code segment may not contain more than one category. There is, therefore, a one-to-one correspondence between a category and the executable code in a single code segment.! Note that this implies that an application that occupies a single code segment may not contain more than one category. Category code segments are shared - there is only one copy of a particular category in memory, however many processes are executing it. There are two main groups of categories: e Image categories contain an entry point at offset zero and are used to implement programs. The name of a code segment that contains an image category has the extension .$sc. An image category code segment is created by loading an executable using p_execc (as described in the chapter Processes and Inter-Process Messaging in the Plib Reference manual). e = Dynamic library categories (DYLs) have no entry point, but contain classes that are referenced from image categories and other DYLs. The name of a code segment containing a DYL has the extension .dyl. A DYL code segment is created by loading a DYL load module (which may be a separate file or be embedded in an executable) using p_loadlib OF p_loadfilelib (these functions are described in the Object Oriented Programming chapter of the PLIB Reference manual). Category handles and category numbers A category handle identifies a category code segment, which may be in RAM or the ROM, as follows: e if the category handle is positive, it is the handle of a moveable RAM-based code segment e if the category handle is negative, it is the paragraph address of a ROM category code segment A category code segment may also be identified by a category number. This number is known at compile time, whereas category handles are only known at run time. A category number is mainly used to create an instance of an object class using p_new, f_new Or £_newsend although it is also used by the more obscure functions p_exactsend, p_reclass and p_cpycat. A local category is defined as the category containing the code that makes a category reference; an external category is a category other than the local category. Given these definitions, the local category always has the category number zero. An external category number is the index (from 1) into an array which, at run time will contain handles to the external categories. The array exists in the local category code segment and is generated from the list of =xTERNAL category references in a category file. ! This is true for all SIBO executables, even if they do not use Object Oriented techniques and thus do not explicitly define a category. OBJECT ORIENTED PROGRAMMING GUIDE The value of an external category number thus depends on the composition and order of the external category array in the local category. Different categories will, in general, use different category numbers to refer to the same external category. Because of this fact, a category number should not be passed as a parameter to an external method (for example, to create a component of variable class). When there is a requirement to pass a category as a parameter, the category handle rather than the category number should be used. The category handle may always be obtained from the category number by calling p_getlibh. For example, consider a situation where an image category cat/.$sc makes external references to dynamic libraries cat2.dyl and cat3.dyl, and cat2.dyl contains an external reference to cat3.dyl. CAT3.DYL The above diagram illustrates that in this case the external category number of cat3.dyl from cat1.$sc is 2, but from cat2.dyl it is 1. Dynamic linkage A reference to an external category by category number occurs when: e aclass from an external category is subclassed by the local category e the local category contains code that references an external category by a category number (most likely for the purposes of creating an instance of an external class using p_new, f_new or f£_newsend). At run time, each reference by category number (such as in a call to p_new) will generate a reference by category handle. Before this can be done, the calling category must be dynamically? linked with all the external categories to which it refers. External categories are referenced by their memory segment names and it follows that, when a category is dynamically linked, all the referenced categories must be loaded. Part of the linking process is the resolution of the reference to a (possibly external) superclass in each class descriptor in the category. A main image category is linked by calling p_1ink1lib (0). For the predominant case where an application is implemented as a single image category referencing only ROM-based DYLs (which do not need to be explicitly loaded), the image may be linked by calling p_1ink1ib at any time before the execution of code involving an external category reference. The call to p_link1lib is normally early in main. A DYL referencing only categories that are already loaded may be linked immediately after it is loaded. This may be done by passing a suitable parameter value to the function that loads the DYL (either p_loadlib OF p_loadfilelib). For example, this technique is suitable when an application category loads a DYL that references only the ROM-based DYLs and the application category. 2 The term dynamic linkage is used because the link is made at run time. This is as opposed to normal (static) linkage between code modules, which occurs at compile time (and is used to produce a category load module, such as an executable or, indeed, a DYL). C-4 APPENDIX C - MECHANISMS In any case where a category (whether an image category or a DYL) contains references to categories that are not yet loaded, it must not be linked immediately after loading. First, all other referenced categories must be loaded. Only then can the category be linked by calling p_1inklib. Referencing by category handle It is possible for a category to reference an external category by category handle - most commonly to create an instance of an external class using p_newlibh, f_newlibh Of f_newlibhsend or to call the more obscure p_reclassbyhandle. In this case, the handle is normally obtained independently of dynamic linkage by one of the following means: e the category handle is passed as a parameter to a method e from the segment name, by calling p_findlib (emulating dynamic linkage) e by the local category loading a DYL using p_loadlib Or p_loadfilelib Message passing In OOP terminology, sending a message to an object means calling a method function of the class or superclass of which that object is an instance. Method functions are identified by their method number, which must be between zero and 255. The method number zero is normally reserved for the method that destroys the object (and its components, if any). The most common way of sending a message is to use p_sena (or, more efficiently, one of the p_sendn variants). This function must be supplied with the handle of the object instance (the address of a cell in the heap, as returned by, say, p_new or p_newlibh) and the method number as its first two parameters. Up to three additional parameters may be supplied. The p_sena function locates the appropriate method function as follows: e it locates the class descriptor of which that object is an instance (using the category handle and class segment offset at the beginning of the instance) e if the method number is in range of the method table that follows the class descriptor, and the corresponding entry has a non-zero value, it calls the corresponding method function e otherwise it locates the superclass class descriptor and repeats the above If the process of trying to find a corresponding method in successive superclass class descriptors (sometimes called superclass chaining) fails, the sending function panics with panic number 48. The send will also panic (with panic number 55) if the category handle and class segment offset at the beginning of the instance points to a class descriptor that does not have the correct signature. This catches, amongst other things, the sending of a message to an object that has already been destroyed. If successfully located, the method function is passed the object handle and the optional parameters (the method number passed to p_send is suppressed). Within method function code (including any auxiliary functions) it is possible to 'send a message’ by making a normal function call to another method function, rather than using one of the above message- sending functions. This is only possible when: e the target method function is in the same category as the sending method e the target method is monomorphic, that is, the functionality does not depend on the class of the instance to which the message is sent e there are no calls to p_supersend in the target method This technique may be used freely in application-specific classes, since such classes are totally within the control of the application writer. When writing general-purpose library DYLs, one has to be more careful about calling a local method (rather than using a message sending function such as p_send). Making a direct call removes any opportunity for subclassers to divert the send to a subclass method. However, in some cases it may be positively desirable to restrict subclassers in this way. OBJECT ORIENTED PROGRAMMING GUIDE The message sending functions (p_send etc) represent the only mechanism for calling methods when: e the method is polymorphic (where a particular send may call different method functions depending on the class of the instance to which the method is being sent) e the method function is in an external category (for example, a ROM-based DYL) e the method function contains a call to p_supersend Calling conventions for method functions A method function that is the target of any of the message sending functions (eg p_send, p_supersend or p_entersend) must use one of the following two calling conventions: CDECL where the generated code will take the parameters off the stack METHOD_CALL where the generated code will take the parameters from the registers (which is more efficient) Note that if you call a method function directly, the prototype must be visible to the caller and must, of course, indicate the correct calling convention. Recall, from the Error Handling chapter of the PLIB Reference manual, that the target of a p_enter must use one of: CDECL where the generated code will take the parameters off the stack ENTER_CALL where the generated code will take the parameters from the registers Since the ENTER_CALL convention is different from the MzTHoD_CALL convention, a method function that is a target of both p_send (or any other message sending function, including p_entersend) and p_enter must be declared as cbEcL. Method parameters In the calling convention of message sending functions, such as p_send, the function parameters are passed in registers. In TopSpeed C this means that no more than five parameters may be passed (including the object handle and the method number) with each parameter being limited to a 16-bit value. Thus the parameters to a method sending function may not include the types Lonc, FLOAT Or DOUBLE, and structures may not be passed by value. The preferred technique is to pass the (16-bit) address of any of these types of data. In exceptional cases a Lonc may be passed as two worp parameters, where the first contains the least significant word and the second contains the most significant word. A very small number of methods in the OLIB dynamic library, for example, use this technique. INDEX .afl files oop application, 1-17 .asm files category source file HWIM, A-9 c files category generated source file HWIM, A-7 skeleton method source file HWIM, A-10 cat files HWIM, A-1 cl files sub category HWIM, A-3 .dfl files DYL add file lists, 3-5 .ext files category file include file HWIM, A-2 external reference file HWIM, A-7 .g files include file HWIM, A-4, A-8 .img files building illustrated in oop, 2-1 .ing files asm include file HWIM, A-10 lis files category listing file HWIM, A-10 -pic files oop application, 1-17 re files oop applications, 1-16 resource externals file example, 4-3 rg files oop applications, 1-16 th files in oop, 15-2 include file in oop, 15-2 resource header file HWIM, 4-4 sc files oop application, 1-17 shd files oop application, 1-17 .wve files Record application HWIM, 16-3 abstract class in oop, 1-14 ACLIST dialog resource HWIM, 8-9 ACLIST_ARRAY dialog resource HWIM, 8-9 action list dialog control HWIM, 8-7 action list small dialog control HWIM, 8-8 active objects AO_INIT message HWIM, 9-4 AO_QUEUE message HWIM, 9-4 AO_RUN message HWIM, 9-4 application responsiveness HWIM, 9-2 background processing HWIM, 9-2 compute intensive tasks HWIM, 9-2 errors HWIM, 9-3 introduction HWIM, 9-1 priority HWIM, 9-2 Record example app HWIM, 16-8 RUN_ACTIVE_USED HWIM, 9-2 timer example HWIM, 9-3 ADD category file statement HWIM, A-3 oop keyword, 1-14 add files DYLs and, 3-5 oop application, 1-17 AIDLE class compute intensive tasks HWIM, 9-2 AM_CLEAN_UP errors HWIM, 10-2 AM_INIT message in oop, 1-16 AM_LOAD_RES_BUF message HWIM, 15-2 AM_LOAD_RESOURCE message HWIM, 15-2 AM_NEW_FILENAME switching to a new file, 11-2 AM_RSCNAME replacing resource file load, 15-1 AO_INIT active objects HWIM, 9-4 AO_QUEUE active objects HWIM, 9-4 AO_RUN active objects HWIM, 9-4 app file from img file in oop, 2-2 app from img in oop, 2-2 application add files in oop, 1-17 attached Series 3a HWIM, 17-1 automatic test system Series 3a HWIM, 18-1 building in oop, 2-1 building oop example, 4-6 category file in oop, 1-12 category file oop example, 4-1, 4-3 client window oop example, 4-6 design basics HWIM, 16-1 design class diagrams HWIM, 16-1 design engine HWIM, 16-1, 16-3 design HWIM, 16-1 design Record app HWIM, 16-1 design Record example HWIM, 16-3, 16-5 design typical HWIM, 16-2 design user interface HWIM, 16-1, 16-2 DYL accessing built in, 3-5 DYL building in, 3-5 example project file in oop, 4-6 file based oop, 11-1 HWIM basic class structure, 1-12 icon file in oop, 1-17 initialisation specific in HWIM, 5-12 OBJECT ORIENTED PROGRAMMING GUIDE Kats macro recorder example, 18-7 main function oop example, 4-5 main in oop, 1-15 method functions in oop, 1-14 miscellaneous files in oop, 1-17 oop and PLIB, 2-6 oop example - hello world, 4-1 oop required files, 1-12 resource externals file in oop, 1-16 resource externals file oop example, 4-3 resource file in oop, 1-16 resource file oop example, 4-1, 4-4 responsiveness active objects HWIM, 9-2 shell data file in oop, 1-17 source code oop example, 4-4 source files in oop, 1-14 start up in oop, 1-16 system resource file in oop, 1-17 window server object oop example, 4-5 application components in HWIM, 1-9 application engine handle via magic static, 1-11 application manager handle via w_am, 1-9 HWIM, 1-9 HWIMMAN class, 1-9 APPMAN OLIB class, 1-9 array object example application prndir in oop, 2-2 asynchronous requests active objects HWIM, 9-1 ATS attached application control HWIM, 18-1 demonstrations HWIM, 18-1 macro recorder example HWIM, 18-7 macros HWIM, 18-1 mechanism HWIM, 18-1 message types HWIM, 18-2 Series 3a HWIM, 18-1 services general HWIM, 18-1 structures HWIM, 18-2 ats.h header file HWIM, 18-1 ATSSV class HWIM, 18-2 attached applications Agenda Series 3a HWIM, 17-1 applications Series 3a HWIM, 17-1 applications Word Series 3a HWIM, 17-1 process Series 3a HWIM, 17-2 automatic test system see ATS, 18-1 Series 3a HWIM, 18-1 background processing active objects HWIM, 9-2 bar graph Record example app HWIM, 16-7 Berlitz edit like windows example HWIM, 12-34 bring See link paste, 14-1 building applications in oop, 2-5 building application oop example, 4-6 C++ contrasted to Psion oop, 1-3 categories external in oop, 1-5 local in oop, 1-5 oop, 1-4 category DYL in oop, 1-4 file translation HWIM, A-6 handles in oop, 1-5 image in oop, 1-4 numbers in oop, 1-5, 1-6 sub files HWIM, A-3 sub files using HWIM, A-4 category file application oop example, 4-1, 4-3 contents HWIM, A-1 convertion in oop via ct.bat, 2-2 DYL example, 3-2 HWIM, A-1 oop, 1-3 oop application, 1-12 structure of HWIM, A-2 translation DYL example, 3-2 category listing file lis file HWIM, A-10 category source file .asm file HWIM, A-9 .c file HWIM, A-7 CHLIST dialog resource HWIM, 8-5 choice list dialog control HWIM, 8-5 CHOICE_ITEM dialog resource HWIM, 8-6 class abstract in oop, 1-14 basic structure HWIM application, 1-12 constant declaration in oop, 1-14 diagrams in oop, 1-7 instance of in oop, 1-3 method declaration keywords, 1-13 names in oop, 1-6 numbers in oop, 1-6 property declaration in oop, 1-14 property number declaration in oop, 1-14 relationships in oop, 1-7 types declaration in oop, 1-14 CLASS category file statement HWIM, A-2 class diagrams HWIM, 16-1 classes application specific - oop option, 1-8 introduction to, 1-3 client window HWIM application, 1-10, 6-2 COM_ACCL_CHECK example of use in oop, 5-9, 5-10 com_cat IN_WSERYV field in oop, 1-16 com_class IN_WSERYV field in oop, 1-16 COM_EXIT application termination, 11-3 Shut down message, 11-3 COM_FILE_CHANGE example of use, 11-1, 11-2, 11-3 COM_INIT example of use in oop, 5-12 COM_MENU example in oop, 5-7 example of use in oop, 5-10 COMMAN class header file, 5-1 HWIM library, 1-10, 5-1 subclassing in HWIM, 5-2 comman.g definition, 5-1 command byte H_COMMAND_BYPASS HWIM, 17-1 command line H_COMMAND_BYPASS byte HWIM, 17-1 command manager code sharing in HWIM applications, 5-6 handle, 1-10 HWIM class, 1-10 in HWIM, 1-10 in oop, 5-1 initialisation in oop, 5-1 commands handling in oop, 5-1 menus in HWIM applications, 5-2 component objects in oop, 1-3 compute intensive tasks active objects HWIM, 9-2 AIDLE class HWIM, 9-2 PRIORITY_ACTIVE_COMPUTE HWIM, 9-2 CONSTANTS category file statement HWIM, A-3 ct.bat batch file HWIM, A-7 ct.bat file category file convertion in oop, 2-2 ctran.exe category file translation HWIM, A-6 DYL example, 3-2 utility in oop, 1-12 utility program HWIM, A-6 DatApp1 to 7 magic statics, 1-11 DatDialogPtr magic static, 1-12, 8-1 date/time editor dialog control HWIM, 8-16 DatGate magic static, 18-1 DatLocked file based apps HWIM, 11-3 DatUsedPath NamePtr magic static, 11-1 DEFER category file statement HWIM, A-3 oop keyword, 1-14 INDEX designing applications HWIM, 16-1 DESTROY message HWIM, A-3 destruction of objects in oop, 1-4 dfl files DYL add file lists, 3-5 dialog adding items HWIM, 7-7 application specific HWIM, 1-11, 7-1 behaviour default HWIM, 7-2 boxes using HWIM, 7-2 bullet symbol, 8-3 button text changing HWIM, 7-8 contrasted with edit windows HWIM, 12-1 controls HWIM, 7-1, 8-1 dim/undim items HWIM, 7-7 dynamic items, 7-5, 7-7 handle via DatDialogPtr, 1-12 HWIM, 7-1 item focus HWIM, 7-2 items changing, 7-5, 7-7 items HWIM, 7-1, 8-1 launching example HWIM, 7-4 lock/unlock items HWIM, 7-7 maximum items HWIM, 7-2 modal HWIM, 7-1 prompt, 8-3 removing items HWIM, 7-7 replacing items HWIM, 7-7 resource example HWIM, 7-3 resource files HWIM, 7-3 resource flags HWIM, 7-4 resource structures HWIM, 7-4 resource TEXTWIN HWIM, 8-2 results retrieval HWIM, 7-9 simple HWIM, 7-5 subdialog HWIM, 7-11 system HWIM, 7-1 title, 8-2 title - replacing, 8-4 wait - with or without HWIM, 7-9 width controlling HWIM, 7-9 dialog controls action list, 8-7 action list small, 8-8 choice list, 8-5 date/time editor, 8-16 edit box, 8-9 file name choice list, 8-21 file name editor, 8-19 floating point editor, 8-15 integer numeric editor, 8-11 latitude/logitude editor, 8-18 LODGER class HWIM, 8-1 long numeric editor, 8-10 numeric editors, 8-10 pack selector, 8-19, 8-21 push button, 8-7 range numeric editor, 8-13 text window HWIM, 8-2 word numeric editor, 8-12 iii OBJECT ORIENTED PROGRAMMING GUIDE dialog resource ACLIST HWIM, 8-9 ACLIST_ARRAY HWIM, 8-9 CHLIST HWIM, 8-5 CHOICE_ITEM HWIM, 8-6 DTEDIT HWIM, 8-16 EDWIN HWIM, 8-9 FLTEDIT HWIM, 8-15 FNEDIT HWIM, 8-20 FNSELWN HWIM, 8-21 LLEDIT HWIM, 8-18 LNCEDIT HWIM, 8-11 MENU and choice list HWIM, 8-6 NCEDIT HWIM, 8-12 PUSH_BUT HWIM, 8-8 RGEDIT HWIM, 8-14 TXTMESS HWIM, 8-3 WNCEDIT HWIM, 8-13 DL_DYN_INIT example usage, 8-1 DL_KEY dialog method, 8-1 document objects EDWIN class direct interaction HWIM, 12-25 draw/redraw window mechanism in HWIM, 6-2 drawing window borders in HWIM, 6-3 windows in HWIM, 6-3 DTEDIT dialog resource HWIM, 8-16 DYL add file lists, 3-5 advantages of, 1-4 building, 3-1 building example, 3-2 building into an application, 3-5 built in accessing, 3-5 built in example, 3-5 built in example project file, 4-7 categories in oop, 1-4 category file example, 3-2 category file translation, 3-2 differences from applications, 3-1 example, 3-1 example - using, 3-3 example runsort.c, 3-3 example source code, 3-1 floating point and, 3-1 loading and linking example, 3-3 oop option, 1-8 PLIB, 3-1 project file example, 3-2 ROM based, 1-4 root class suppling example, 3-4 dynamic libraries see DYL, 1-4 ecobj.exe utility program HWIM, A-7 utility program in oop, 2-1 edit box dialog control HWIM, 8-9 edit like windows Berlitz example HWIM, 12-34 HWIM, 12-34 Spellchecker example HWIM, 12-34 edit windows contrasted with dialogs HWIM, 12-1 document object interactions HWIM, 12-25 document offset concept HWIM, 12-14 EDWIN class additional methods HWIM, 12-11 features additional HWIM, 12-1 features of HWIM, 12-1 HWIM, 12-1 landlord example HWIM, 12-6 layout and formatting HWIM, 12-16 multiple notes example app HWIM, 12-2 notes example application HWIM, 12-2 read only HWIM, 12-15 SCRIMG screen image HWIM, 12-20 SCRLAY screen layout HWIM, 12-17 simple example app HWIM, 12-2 special characters HWIM, 12-9 edump.exe example usage, 3-6 utility program, 3-6 EDWIN dialog resource HWIM, 8-9 EDWIN class document objects direct interaction HWIM, 12-25 document offset concept HWIM, 12-14 edit like windows HWIM, 12-34 edit window additional methods HWIM, 12-11 edit windows HWIM, 12-1 edit windows simple use HWIM, 12-5 layout and formatting HWIM, 12-16 read only HWIM, 12-15 SCRIMG screen image HWIM, 12-20 SCRLAY layout HWIM, 12-17 ehello edit windows simple example app HWIM, 12-2 emphasis windows in HWIM, 6-5 emulator unsuitable applications, 2-5 engine application design HWIM, 16-3 handle via magic static, 1-11 HWIM application, 1-11 environment variable Record application HWIM, 16-3 EPOC emulator unsuitable applications, 2-5 error handling HWIM, 10-1 handling mechanisms HWIM, 10-1 recovery HWIM, 10-1 errors active objects HWIM, 9-3 AM_CLEAN_UP message HWIM, 10-2 cleanup list HWIM, 10-5 detection - spy application, 10-2 general HWIM, 10-2 handling Record application HWIM, 10-2 initialisation HWIM, 10-1 memory HWIM, 10-1 numbers HWIM, 10-1 object component rollback HWIM, 10-3 resource releasing HWIM, 10-4 roll back principle HWIM, 10-2 system code interactions HWIM, 10-5 event main scheduling loop HWIM, 1-9 events active objects HWIM, 9-1 asynchronous requests active objects HWIM, 9-1 example application prndir in oop, 2-2 prndir in oop described, 2-2 project file in oop, 4-6 variable array object prndir in oop, 2-2 example DYL built into an application, 3-5 dynamic libary, 3-1 using, 3-3 EXTERNAL category file statement HWIM, A-2 declaration in oop, 1-15 external reference file .ext HWIM, A-7 file based apps alias information HWIM, 11-1 AM_NEW_FILENAME message HWIM, 11-2 COM_EXIT message HWIM, 11-3 COM_EXIT method HWIM, 11-3 COM_FILE_CHANGE message HWIM, 11-1 COM_FILE_CHANGE method HWIM, 11-2, 11-3 DatLocked magic static HWIM, 11-3 file default extension HWIM, 11-1 foreground switching to HWIM, 11-3 hEnsurePath function HWIM, 11-2 HWIM, 11-1 initialisation HWIM, 11-1 open/create command HWIM, 11-1 opening/creating HWIM, 11-2 path full HWIM, 11-1 saving HWIM, 11-3 shut down message HWIM, 11-3 switch files HWIM, 11-3 termination HWIM, 11-3 file name choice list dialog control HWIM, 8-21 file name editor dialog control HWIM, 8-19 files miscellaneous oop application, 1-17 floating point DYL restrictions, 3-1 floating point editor dialog control HWIM, 8-15 FLTEDIT dialog resource HWIM, 8-15 FNEDIT dialog resource HWIM, 8-20 FNSELWN dialog resource HWIM, 8-21 INDEX foreground file based apps switching to HWIM, 11-3 FORM library, 1-2 FRC Record example app HWIM, 16-7 free running counter Record example app HWIM, 16-7 function calls in oop, 1-5 function prototypes method functions in oop, 1-7 GATE class Series 3a HWIM, 18-1 H_COMMAND_BYPASS command byte HWIM, 17-1 handle application engine via magic static, 1-11 application manager via w_am, 1-9 command manager, 1-10 dialog via DatDialogPtr, 1-12 window server via w_ws, 1-10 handles application objects HWIM, 16-2 as property of an object, 1-3 category in oop, 1-5 of objects in oop, 1-3 help resource HELP_ARRAY, 15-4 identifier help_index_id HWIM, 15-5 identifiers context HWIM, 15-5 identifiers dialog HWIM, 15-5 identifiers HWIM, 15-4 TOPIC_ARRAY, 15-4 using HWIM, 15-4 HELP_ARRAY help resource, 15-4 help_index_id resource identifiers help HWIM, 15-5 hEnsurepath file function HWIM, 11-2 hLoadResource resource loading function HWIM, 15-3 hwim resource header .rh files, 4-4 HWIM .asm category source file, A-9 .c category C source file, A-7 .c Skeleton method source file, A-10 .cl sub category files, A-3 .ext external reference file, A-7 .g C include file, A-8 -g include files, A-4 .ing asm include file, A-10 lis category listing file, A-10 active object event sources, 9-1 active objects introduction, 9-1 active objects Record example, 16-8 ADD category file statement, A-3 agenda attached application, 17-1 AM_LOAD_RES_BUF message, 15-2 AM_LOAD_RESOURCE message, 15-2 application basic class structure, 1-12 application client window, 1-10 application components in oop, 1-9 OBJECT ORIENTED PROGRAMMING GUIDE application design, 16-1 application design basics, 16-1 application design class diagrams, 16-1 application design engine, 16-1, 16-3 application design Record app, 16-1 application design typical, 16-2 application design user interface, 16-1, 16-2 application engine, 1-11 application manager, 1-9 asynchronous requests active objects, 9-1 ATS inter-process messages, 18-1 ATS macro recorder example, 18-7 ATS mechanism, 18-1 ATS message types, 18-2 ATS Series 3a, 18-1 ATS structures, 18-2 ats.h header file, 18-1 ATSSV class, 18-2 attached applications Series 3a, 17-1 attached process Series 3a, 17-2 automatic test system Series 3a, 18-1 bar graph Record example, 16-7 category .ext file, A-2 category EXTERNAL statement, A-2 category file contents, A-1 category file structure of, A-2 category file translation, A-6 category files, A-1 category INCLUDE statement, A-2 category sub files, A-3 category sub files using, A-4 CLASS category file statement, A-2 client window, 6-2, 16-3 COM_ACCL_CHECK example of use, 5-9, 5-10 COM_INIT example of use, 5-12 COM_MENU example of use, 5-10 COM_MENU oop example, 5-7 COMMAN class, 5-1 COMMAN class subclassing, 5-2 command byte HHCOMMAND_BYPASS, 17-1 command manager, 1-10, 5-1 CONSTANTS category file statement, A-3 ct.bat batch file, A-7 DEFER category file statement, A-3 DESTROY message, A-3 dialog application specific, 1-11, 7-1 dialog behaviour default, 7-2 dialog boxes using, 7-2 dialog button text changing, 7-8 dialog control action list, 8-7 dialog control action list small, 8-8 dialog control choice list, 8-5 dialog control date/time editor, 8-16 dialog control edit box, 8-9 dialog control file name choice list, 8-21 dialog control file name editor, 8-19 dialog control floating point editor, 8-15 dialog control integer numeric editor, 8-11 dialog control latitude/logitude editor, 8-18 dialog control LODGER class, 8-1 dialog control long numeric editor, 8-10 dialog control numeric editors, 8-10 dialog control pack selector, 8-19, 8-21 dialog control push button, 8-7 dialog control range numeric editor, 8-13 dialog control text window, 8-2 dialog control word numeric editor, 8-12 dialog controls, 7-1, 8-1 dialog dynamic items, 7-5, 7-7 dialog item adding, 7-7 dialog item dim/undim, 7-7 dialog item focus, 7-2 dialog item lock/unlock, 7-7 dialog item replacing, 7-7 dialog items, 7-1, 8-1 dialog items changing, 7-5, 7-7 dialog launching example, 7-4 dialog maximum items, 7-2 dialog modal, 7-1 dialog resource example, 7-3 dialog resource flags, 7-4 dialog resource structures, 7-4 dialog resource TEXTWIN HWIM, 8-2 dialog results retrieval, 7-9 dialog simple, 7-5 dialog subdialog, 7-11 dialog system, 7-1 dialog wait - with or without, 7-9 dialog width controlling, 7-9 dialogs, 7-1 dialogs and resource files, 7-3 ecobj.exe utility program, A-7 edit like windows, 12-34 edit like windows Berlitz, 12-34 edit like windows Spellchecker, 12-34 edit window features additional, 12-1 edit window features of, 12-1 edit window landlord example, 12-6 edit window special characters, 12-9 edit windows, 12-1 edit windows document object interaction, 12-25 edit windows document offset concept, 12-14 edit windows layout and formatting, 12-16 edit windows notes example app, 12-2 edit windows read only, 12-15 EDWIN class, 12-1 EDWIN class additional methods, 12-11 EDWIN class simple use, 12-5 environment variable Record application, 16-3 error detection - spy application, 10-2 error handling, 10-1 error handling mechanisms, 10-1 error handling Record application, 10-2 error numbers, 10-1 error recovery, 10-1 errors AM_CLEAN_UP message, 10-2 errors cleanup list, 10-5 errors general, 10-2 errors initialisation, 10-1 errors memory, 10-1 errors object component rollback, 10-3 errors resource releasing, 10-4 errors roll back principle, 10-2 errors system code interactions, 10-5 event scheduling loop, 1-9 EWLINKSYV link paste edit windows, 14-11 example - hello world, 4-1 file based applications, 11-1 file based apps initialisation, 11-1 file based apps opening/creating, 11-2 file based apps saving, 11-3 file based apps shut down message, 11-3 file based apps switch files, 11-3 file based apps termination, 11-3 FRC Record example, 16-7 GATE class Series 3a, 18-1 handles of objects, 16-2 hLoadResBuf resource function, 15-2 hLoadResource resource function, 15-2 initialisation application specific, 5-12 inter-process messages ATS data, 18-2 inter-process messages Series 3a, 18-1 keyboard filtering example, 16-7 library, 1-1 library vs Hwif, 1-2 library vs OPL, 1-2 line page break calculation, 13-2 link paste, 14-1 link paste client side, 14-8 link paste client transaction, 14-8 link paste data availability, 14-8 link paste data formats, 14-6 link paste edit windows, 14-10 link paste edit windows example, 14-11 link paste example modified ehello, 14-1 link paste initialising w_am, 14-4 link paste IPC mechanisms, 14-1 link paste LINKSV class, 14-1 link paste LINKSV example, 14-4 link paste native formats, 14-13 link paste server comments, 14-5 link paste server operation, 14-13 link paste server side, 14-1 link paste server status declaring, 14-2 link paste server transaction, 14-4 link paste text formats revisisted, 14-12 link paste text types, 14-7 link paste word wrap, 14-7 LINKCL example, 14-9 loading a resource, 15-2 LPRINTER class, 13-1 LPRINTER examples of use, 13-7 LPRINTER examples of use advanced, 13-15 LPRINTER font width tables, 13-18 LPRINTER for standard printing, 13-6 LPRINTER PAGES active object, 13-19 LPRINTER print setup, 13-7 LPRINTER property, 13-16 LPRINTER vs XPRINTER, 13-24 LPRINTER WDR class description, 13-21 LPRINTER WDR objects, 13-23 LPRINTER widths, 13-7 LPRINTER widths variable fonts, 13-17 LPRINTER word-wrap, 13-7 LPRINTER word-wrapping default, 13-17 mechanisms categories, C-3 mechanisms category handle referencing, C-5 mechanisms category handles, C-3 INDEX mechanisms category numbers, C-3 mechanisms class descriptor, C-1 mechanisms classes, C-1 mechanisms dynamic linkage, C-4 mechanisms message passing, C-5 mechanisms method calling conventions, C-6 mechanisms method parameters, C-6 mechanisms object creation, C-2 mechanisms psion OOP, C-1 menu accelerator separators, 5-6 menu accelerators - shifted, 5-5 menu accelerators adding, 5-2 menu accelerators dynamic, 5-13 menu accelerators rules, 5-13 menu bar, 1-11 menu bar dynamic, 5-12 menu bar replacing, 5-12 menu command handling, 5-1 menu options adding - example, 5-2 menu options changing number of, 5-10 menu options code sharing, 5-6 menu options disabling, 5-9 menu options dynamic, 5-7 menu options multi-lingual, 5-8 menu options validity checking, 5-9 menu submenus, 5-14 message shut down, 5-14 method function calling, B-1 method function parameters, B-1 method function source files, B-1 method source file generation, B-1 multi-lingual applications, 1-9 OLIB dyl category file, A-2 OLIB library, 16-3 oop option, 1-8 PDR class, 13-32 print buffer lifetime, 13-5 print context file save/restore, 13-34 print preview, 13-1 print preview without XPRINTER, 13-33 printing - page size and margins, 13-3 printing - print setup storage, 13-4 printing font and style by line, 13-5 printing font style changing, 13-4 printing options WDR system, 13-1 printing page break calculation, 13-2 printing printer units, 13-3 printing WDR basic model, 13-1 printing WDR system, 13-1 printing WDR_PRINT_IDLE flag, 13-5 printing WDR_PRINT_KEEP flag, 13-5 PROPERTY category file statement, A-3 PROPERTY number, A-3 Record application design, 16-3, 16-5 Record application specification, 16-3 REPLACE category file statement, A-3 REQUIRE sub-category file statement, A-4 resource files, 15-1 resource files - application, 1-10 resource files - system, 1-10 resource files application, 15-1 resource files location, 15-1 resource files system, 15-2 resource files system source code, 15-2 OBJECT ORIENTED PROGRAMMING GUIDE resource help context identifiers, 15-5 resource help dialog identifiers, 15-5 resource help identifiers, 15-4 resource help using, 15-4 resource loading - example, 5-4 resource structures, 15-2 resource system loading, 15-3 resource system referencing, 15-3 resource system using, 15-3 SCRIMG edit window image, 12-20 SCRLAY edit window layout, 12-17 shut down message Record application, 16-3 shut down messages, 5-14 sound digital example, 16-3 status window displaying, 5-11 status window size of, 5-11 sub-category files, A-3 sub-category files using, A-4 subdialog, 7-11, 8-3 submenus, 5-14 switch files message Record application, 16-3 twips printer units, 13-3 TYPES category file statement, A-3 WDR classes diagram, 13-32 WDR miscellany, 13-32 window classes, 6-1 window draw/redraw mechanism, 6-2 window drawing, 6-3 window drawing a border, 6-3 window emphasis, 6-5 window lodger, 6-4 window main, 6-2 window resizing, 6-4 window usage, 6-2 windows, 6-1 Word attached application, 17-1 WS_DO_SUBMENU example of use, 5-14 WS_SET_MENUBAR example of use, 5-12 wve files Record application, 16-3 XADD library, 13-1 XPRINTER class, 13-1 XPRINTER print preview, 13-24 XPRINTER print/preview example, 13-25 XPRINTER vs LPRINTER, 13-24 XPRINTER vs LPRINTER comments, 13-31 HWIM class command manager, 1-10 hwim.rh include file in oop, 15-2 HWIMMAN class application manager, 1-9 icon oop application, 1-17 icon editor Iconed example application, 1-17 Iconeda example 3a application, 1-17 icon file oop application, 1-17 Iconed icon editor example application, 1-17 Iconeda icon editor example 3a application, 1-17 viii image categories in oop, 1-4 IN_WSERV struture in oop, 1-16 INCLUDE category file statement HWIM, A-2 include file .g file HWIM, A-8 .ing asm include file HWIM, A-10 ats.h HWIM, 18-1 inheritance in oop, 1-3 initialisation application specific in HWIM, 5-12 instance of class in oop, 1-3 integer numeric editor dialog control HWIM, 8-11 inter-process messages ATS data HWIM, 18-2 ATS HWIM, 18-1 Series 3a HWIM, 18-1 Kats ATS macro recorder example, 18-7 keyboard filtering example HWIM, 16-7 latitude/logitude editor dialog control HWIM, 8-18 If.bat linking batch file with example DYL, 3-3 lfc. bat linking batch file with example DYL, 3-3 libraries object oriented, 1-1 oop - advantages, 1-2 library FORM, 1-2 HWIM, 1-1 HWIM vs Hwif, 1-2 HWIM vs OPL, 1-2 OLIB, 1-1 XADD, 1-2 line break calculation printing in HWIM, 13-2 link errors spurious in oop, 2-6 link paste client side HWIM, 14-8 client transaction HWIM, 14-8 data availability HWIM, 14-8 data formats HWIM, 14-6 edit windows example HWIM, 14-11 edit windows HWIM, 14-10 EWLINKSYV edit windows HWIM, 14-11 example modified ehello HWIM, 14-1 HWIM, 14-1 initialising w_am HWIM, 14-4 IPC mechanisms HWIM, 14-1 link server comments HWIM, 14-5 LINKCL example HWIM, 14-9 LINKSV class HWIM, 14-1 LINKSV example code HWIM, 14-4 native formats HWIM, 14-13 server operation HWIM, 14-13 server side HWIM, 14-1 server status declaring HWIM, 14-2 server transaction HWIM, 14-4 text formats revisited HWIM, 14-12 text types HWIM, 14-7 word wrap HWIM, 14-7 linking If.bat file with example DYL, 3-3 lfc.bat file with example DYL, 3-3 LINKSV class link paste HWIM, 14-1 LLEDIT dialog resource HWIM, 8-18 LNCEDIT dialog resource HWIM, 8-11 loading and linking DYL example, 3-3 lodger windows in HWIM, 6-4 LODGER class dialog control class HWIM, 8-1 long numeric editor dialog control HWIM, 8-10 LPRINTER class examples of use advanced HWIM, 13-15 examples of use HWIM, 13-7 font width tables HWIM, 13-18 HWIM, 13-1 PAGES active object HWIM, 13-19 print setup HWIM, 13-7 property HWIM, 13-16 standard printing with HWIM, 13-6 WDR class description HWIM, 13-21 WDR objects HWIM, 13-23 widths HWIM, 13-7 widths variable fonts HWIM, 13-17 word-wrap HWIM, 13-7 word-wrapping default HWIM, 13-17 XPRINTER contrasted HWIM, 13-24 macro recorder example ATS HWIM, 18-7 macros ATS HWIM, 18-1 magic static DatApp1 to 7, 1-11 DatDialogPtr, 1-12, 8-1 DatGate, 18-1 DatLocked file based apps HWIM, 11-3 DatUsedPathNamePtr, 11-1 w_am, 1-9 w_ws, 1-10, 5-1 main function in oop application, 1-15 make file oop example, 4-8 make.bat oop example batch file, 4-8 menu submenus in HWIM applications, 5-14 MENU dialog resource and choice list HWIM, 8-6 menu accelerators 3a in HWIM applications, 5-5 adding in HWIM applications, 5-2 dynamic in HWIM applications, 5-13 grouping in HWIM applications, 5-6 INDEX rules in HWIM applications, 5-13 separators in HWIM applications, 5-6 shifted in HWIM applications, 5-5 menu bar dynamic in HWIM applications, 5-12 HWIM application, 1-11, 5-2 replacing in HWIM applications, 5-12 menu commands handling in oop, 5-1 menu options adding - example HWIM application, 5-2 adding in HWIM applications, 5-2 changing in HWIM applications, 5-7 changing number of - in HWIM applications, 5-10 code sharing in HWIM applications, 5-6 disabling in HWIM applications, 5-9 dynamic in HWIM applications, 5-7 enabling in HWIM applications, 5-9 language variants in HWIM applications, 5-8 multi-lingual in HWIM applications, 5-8 number of - changing in HWIM applications, 5-10 validity checking in oop, 5-9 message AM_INIT in oop, 1-16 ATS types HWIM, 18-2 numbers in oop, 1-6 shut down in HWIM applications, 5-14 message sending in oop, 1-5 method function names in oop, 1-6 functions in oop, 1-14 names in oop, 1-6 method function calling conventions HWIM, B-1 calling in oop, 1-5 parameters HWIM, B-1 prototypes in oop, 1-7 source files HWIM, B-1 method number in oop, 1-5 method sending p_send functions, 1-5 method source file generation HWIM, B-1 method WSERV ws_dyn_init, 1-10 methods of objects in oop, 1-3 multi-lingual applications HWIM, 1-9 menus in HWIM applications, 5-8 naming source files in oop, 2-2 NCEDIT dialog resource HWIM, 8-12 notes edit windows multiple example app HWIM, 12-2 numeric editors dialog control HWIM, 8-10 OBJECT ORIENTED PROGRAMMING GUIDE object component objects, 1-3 creation in oop, 1-3 destruction of in oop, 1-4 handles in oop, 1-3, 1-6 introduction to, 1-3 libraries oop option, 1-8 window server in oop, 1-10 object oriented programming introduction, 1-1 libraries, 1-1 see oop, 1-1 OLIB dyl category file HWIM, A-2 library, 1-1 library HWIM, 16-3 OLIB class APPMAN, 1-9 oop add files, 1-17 app file from img file, 2-2 application category file, 1-12 application required files, 1-12 application resource file, 1-16 application source files, 1-14 application specific classes - oop option, 1-8 application start up, 1-16 applications and PLIB, 2-6 basic concepts - Psion's system, 1-3 building an application, 2-1 building an image illustrated, 2-1 building application oop example, 4-6 building applications, 2-5 C++ contrasted with Psion oop, 1-3 categories, 1-4 category DYL, 1-4 category file convertion via ct.bat, 2-2 category file oop example, 4-3 category file oop example, 4-1 category files, A-1 category files in oop, 1-3 category image, 1-4 category numbers, 1-6 class constant declaration, 1-14 class diagrams, 1-7 class instances, 1-3 class names, 1-6 class numbers, 1-6 class property declaration, 1-14 class property number declaration, 1-14 class relationships, 1-7 class types declaration, 1-14 classes introduction, 1-3 client window oop example, 4-6 command handling, 5-1 command manager, 5-1 console based application, 2-5 ct.bat batch file, A-7 ctran.exe utility, 1-12 destruction of objects, 1-4 DYL building, 3-1 DYL differences from applications, 3-1 DYLs - oop option, 1-8 ecobj.exe utility program HWIM, A-7 edit window features additional, 12-1 edit window features of, 12-1 edit windows, 12-1 error detection - spy application, 10-2 error handling, 10-1 error handling mechanisms, 10-1 error handling Record application, 10-2 error numbers, 10-1 error recovery, 10-1 errors AM_CLEAN_UP message, 10-2 errors cleanup list, 10-5 errors general, 10-2 errors initialisation, 10-1 errors memory, 10-1 errors object component rollback, 10-3 errors resource releasing, 10-4 errors roll back principle, 10-2 errors system code interactions, 10-5 example application - hello world, 4-1 file based applications, 11-1 file based apps initialisation, 11-1 file based apps opening/creating, 11-2 file based apps saving, 11-3 file based apps shut down message, 11-3 file based apps switch files, 11-3 file based apps termination, 11-3 function calls, 1-5 HWIM - oop option, 1-8 HWIM application components, 1-9 icon file, 1-17 inheritance in, 1-3 introduction, 1-1 libraries advantages, 1-2 link errors spurious, 2-6 main function, 1-15 main function oop example, 4-5 mechanisms categories, C-3 mechanisms category handle referencing, C-5 mechanisms category handles, C-3 mechanisms category numbers, C-3 mechanisms class descriptor, C-1 mechanisms classes, C-1 mechanisms dynamic linkage, C-4 mechanisms message passing, C-5 mechanisms method calling conventions, C-6 mechanisms method parameters, C-6 mechanisms object creation, C-2 mechanisms psion, C-1 menu command handling, 5-1 message numbers, 1-6 method declaration keywords, 1-13 method function calling, B-1 method function names, 1-6 method function parameters, B-1 method functions, 1-14 method names, 1-6 method numbers, 1-5 methods of objects, 1-3 miscellaneous files, 1-17 notation and convensions, 1-6 object creation, 1-3 object handles, 1-6 object libraries - oop option, 1-8 object oriented programming introduction, 1-1 options - techniques, 1-8 property of objects, 1-3 property of oop library objects, 1-3 resource externals file, 1-16 resource externals file example, 4-3 resource file example, 4-4 resource file oop example, 4-1 shell data file, 1-17 source code oop example, 4-4 source file naming in oop, 2-2 subclasses, 1-3 superclasses, 1-3 system resource file, 1-17 window server object oop example, 4-5 p_send method sending functions, 1-5 pack selector dialog control HWIM, 8-19, 8-21 page break calculation printing in HWIM, 13-2 PDR class HWIM, 13-32 PLIB DYLs and, 3-1 oop applications and, 2-6 printer units printing in HWIM, 13-3 printing font and style by line HWIM, 13-5 font style changing HWIM, 13-4 line break calculation HWIM, 13-2 LPRINTER class HWIM, 13-1 LPRINTER examples of use advanced HWIM, 13-15 LPRINTER examples of use HWIM, 13-7 LPRINTER font width tables HWIM, 13-18 LPRINTER for standard printing HWIM, 13-6 LPRINTER PAGES active object HWIM, 13-19 LPRINTER print setup HWIM, 13-7 LPRINTER property HWIM, 13-16 LPRINTER vs XPRINTER HWIM, 13-24 LPRINTER WDR class description HWIM, 13-21 LPRINTER WDR objects HWIM, 13-23 LPRINTER widths HWIM, 13-7 LPRINTER widths variable fonts HWIM, 13-17 LPRINTER word-wrap HWIM, 13-7 LPRINTER word-wrapping default HWIM, 13-17 options WDR system HWIM, 13-1 page break calculation HWIM, 13-2 page size and margins HWIM, 13-3 PDR class HWIM, 13-32 print buffer lifetime HWIM, 13-5 print context file save/restore HWIM, 13-34 print preview HWIM, 13-1 print preview without XPRINTER HWIM, 13-33 print setup storage HWIM, 13-4 printer units HWIM, 13-3 INDEX twips printer units HWIM, 13-3 WDR basic model HWIM, 13-1 WDR classes diagram HWIM, 13-32 WDR miscellany HWIM, 13-32 WDR system HWIM, 13-1 WDR_PRINT flags, 13-2 WDR_PRINT structure, 13-1 WDR_PRINT_IDLE flag HWIM, 13-5 WDR_PRINT_KEEP flag HWIM, 13-5 XADD library, 13-1 XPRINTER class HWIM, 13-1 XPRINTER print preview HWIM, 13-24 XPRINTER print/preview example HWIM, 13-25 XPRINTER vs LPRINTER comments HWIM, 13-31 XPRINTER vs LPRINTER HWIM, 13-24 priority active objects HWIM, 9-2 PRIORITY_ACTIVE_COMPUTE compute intensive tasks HWIM, 9-2 prndir example application in oop, 2-2 programming options in oop, 1-8 project file DYL built in example, 4-7 DYL example, 3-2 oop example application, 4-6 property handles of component objects, 1-3 of objects, 1-3 of objects in oop, 1-3 of objects in oop libraries, 1-3 use of - example HWIM application, 5-4 PROPERTY category file statement HWIM, A-3 number category file HWIM, A-3 psion oop mechanisms, C-1 push button dialog control HWIM, 8-7 PUSH_BUT dialog resource HWIM, 8-8 range numeric editor dialog control HWIM, 8-13 rcomp.exe resource file compiler HWIM, 15-2 re.bat file oop applications, 1-16 Record application classes HWIM, 16-5 application design HWIM, 16-1, 16-3, 16-5 application error handling HWIM, 10-2 application specification HWIM, 16-3 REPLACE category file statement HWIM, A-3 oop keyword, 1-14 REQUIRE sub-category file statement HWIM, A-4 reserved static see magic static, 1-9 resizeable windows notes example application HWIM, 12-2 OBJECT ORIENTED PROGRAMMING GUIDE resizing windows in HWIM, 6-4 resource application loading HWIM, 15-2 help using HWIM, 15-4 HELP_ARRAY help, 15-4 hLoadResBuf loading function HWIM, 15-2 hLoadResource loading function HWIM, 15-2 identifiers help context HWIM, 15-5 identifiers help dialog HWIM, 15-5 identifiers help HWIM, 15-4 identifiers HWIM, 15-2 loading - example HWIM application, 5-4 structures HWIM, 15-2 system loading HWIM, 15-3 system referencing HWIM, 15-3 system using HWIM, 15-3 TOPIC_ARRAY help, 15-4 resource externals file example in oop, 4-3 oop application, 1-16 resource files AM_RSCNAME loading method, 15-1 application HWIM, 15-1 application oop example, 4-1 compiler rcomp.exe HWIM, 15-2 HWIM, 15-1 HWIM - system, 1-10 HWIM application, 1-10 loading a resource HWIM, 15-2 location HWIM, 15-1 oop application, 1-16 oop example, 4-4 ROM based, 1-10 system HWIM, 15-2 system source code HWIM, 15-2 resource header file HWIM application hwim.rh, 4-4 resources releasing on errors HWIM, 10-4 RGEDIT dialog resource HWIM, 8-14 ROM DYLs, 1-4 ROM based resource file, 1-10 system resource file, 1-17 root class suppling DYL example, 3-4 RUN_ACTIVE_USED active objects HWIM, 9-2 runsort DYL example, 3-3 SCRIMG edit windows screen image HWIM, 12-20 SCRLAY edit windows screen layout HWIM, 12-17 SE_DTEDIT structure HWIM, 8-17, 8-18 SE_EDWIN structure HWIM, 8-10 SE_FLEDIT structure HWIM, 8-15 SE_LLEDIT structure HWIM, 8-19 SE_LNCEDIT structure HWIM, 8-11 SE_NCEDIT structure HWIM, 8-12 SE_RGEDIT structure HWIM, 8-14 SE_TEXTWIN structure HWIM, 8-4 SE_WNCEDIT structure HWIM, 8-13 self handle of objects in oop, 1-3 sending messages in oop, 1-5 Series 3a attached applications HWIM, 17-1 shell data file oop application, 1-17 shut down message file based apps oop, 11-3 message Record application HWIM, 16-3 messages in HWIM applications, 5-14 skeleton method .c source file HWIM, A-10 sort example DYL based example, 3-3 sound digital example HWIM, 16-3 digital Record application HWIM, 16-3 source code application oop example, 4-4 source files method functions HWIM, B-1 naming in oop, 2-2 oop application, 1-14 Spellchecker edit like windows example HWIM, 12-34 start up oop application, 1-16 status window displaying in HWIM applications, 5-11 size in HWIM applications, 5-11 structures ATS HWIM, 18-2 IN_WSERYV in oop, 1-16 resources HWIM, 15-2 SE_DTEDIT HWIM, 8-17, 8-18 SE_EDWIN HWIM, 8-10 SE_FLEDIT HWIM, 8-15 SE_LLEDIT HWIM, 8-19 SE_LNCEDIT HWIM, 8-11 SE_NCEDIT HWIM, 8-12 SE_RGEDIT HWIM, 8-14 SE_TEXTWIN HWIM, 8-4 SE_WNCEDIT HWIM, 8-13 WDR_PRINT, 13-1 sub-category files HWIM, A-3 files using HWIM, A-4 subclass in oop, 1-3 subdialog HWIM, 7-11, 8-3 submenus in HWIM applications, 5-14 superclass in oop, 1-3 switch files file based apps HWIM, 11-2 message file based apps oop, 11-3 message Record application HWIM, 16-3 system resource file oop application, 1-17 ROM, 1-17 termination file based apps oop, 11-3 text window dialog control HWIM, 8-2 timer example active objects HWIM, 9-3 TOPIC_ARRAY help resource, 15-4 tsc example use in oop, 4-8 tscx example use in oop, 4-8 twips printer units HWIM, 13-3 TXTMESS dialog resource HWIM, 8-3 TYPES category file statement HWIM, A-3 user interface application design HWIM, 16-2 utility program rcomp.exe resource compiler, 15-2 utility program ctran.exe HWIM, A-6 ecobj.exe in oop, 2-1 edump.exe, 3-6 wspcx.exe bitmap processing, 1-17 w_am magic static, 1-9, 5-13 W_Ws magic static, 1-10, 5-1 WDR print preview HWIM, 13-1 printing basic model HWIM, 13-1 printing system HWIM, 13-1 printing system options HWIM, 13-1 WDR_PRINT flags, 13-2 structure, 13-1 WDR_PRINT_IDLE printing flag HWIM, 13-5 WDR_PRINT_KEEP printing flag HWIM, 13-5 window draw/redraw mechanism in HWIM, 6-2 drawing borders in HWIM, 6-3 drawing in HWIM, 6-3 emphasis in HWIM, 6-5 HWIM application and, 6-1 lodgers in HWIM, 6-4 main client HWIM, 16-3 main client in oop example, 4-6 resizeable notes example app HWIM, 12-2 resizing in HWIM, 6-4 INDEX usage in HWIM application, 6-2 window classes HWIM application and, 6-1 window main HWIM application, 1-10 window server active object WSERV, 6-2 object in oop, 1-10 object in oop example, 4-5 object via w_ws, 1-10 WNCEDIT dialog resource HWIM, 8-13 word numeric editor dialog control HWIM, 8-12 WS_DO_DIAL example of use, 7-2 WS_DO_SUBMENU example of use in oop, 5-14 ws_dyn_init WSERV method, 1-10 WS_SET_MENUBAR example of use in oop, 5-12 WSERV window server active object, 6-2 Wspcx.exe bitmap utility program, 1-17 wve files Record application HWIM, 16-3 XADD library, 1-2 library HWIM, 13-1 XPRINTER class HWIM, 13-1 XPRINTER subclass LPRINTER contrasted comments HWIM, 13-31 LPRINTER contrasted HWIM, 13-24 print preview HWIM, 13-24 print/preview example HWIM, 13-25 xiii