Typica is a free program for professional coffee roasters. https://typica.us
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

typica.w 728KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737
  1. % Instructions for generating source code and documentation
  2. % Step 1. Convert metapost diagrams into PDF documents
  3. % $ mptopdf pipes.mp ; epstopdf pipes.ps
  4. % $ mptopdf roast.mp ; epstopdf roast.ps
  5. % $ mptopdf search.mp ; epstopdf search.ps
  6. % Step 2. Weave and typeset
  7. % $ cweave typica
  8. % $ pdftex typica
  9. % Step 3. Tangle and moc
  10. % $ ctangle typica ; mv typica.c typica.cpp
  11. % $ moc typica.cpp > moc_typica.cpp
  12. %
  13. % If you have trouble compiling, check to make sure the required headers are in
  14. % your header search path and check to make sure the required libraries are
  15. % linked. If using qmake to generate a project file, remember to add the
  16. % following lines to your .pro file:
  17. % QT += xml
  18. % QT += script
  19. % Document style instructions
  20. \input graphicx.tex
  21. \mark{\noexpand\nullsec0{A Note on Notation}}
  22. \def\pn{Typica}
  23. \def\filebase{typica}
  24. \def\version{1.9.1 \number\year-\number\month-\number\day}
  25. \def\years{2007--2018}
  26. \def\title{\pn{} (Version \version)}
  27. \newskip\dangerskipb
  28. \newskip\dangerskip
  29. \dangerskip=20pt
  30. \dangerskipb=42pt
  31. \def\hang{\hangindent\dangerskip}
  32. \def\hangb{\hangindent\dangerskipb}
  33. \font\manual=manfnt at 12pt
  34. \def\danbend{{\manual\char127}}
  35. \def\datanger{\medbreak\begingroup\clubpenalty=10000
  36. \def\par{\endgraf\endgroup\medbreak} \noindent\hang\hangafter=-2
  37. \hbox to0pt{\hskip-3.5pc\danbend\hfill}}
  38. \outer\def\danger{\datanger}%
  39. %
  40. \def\datangerb{\medbreak\begingroup\clubpenalty=10000
  41. \def\par{\endgraf\endgroup\medbreak} \noindent\hang\hangafter=-2
  42. \hbox to0pt{\hskip-3.5pc\danbend\hfill}}
  43. \outer\def\dangerb{\datangerb}
  44. \def\endanger{\medskip}
  45. \def\nullsec{\S1}
  46. \def\lheader{\mainfont\the\pageno\kern1pc(\topsecno)\eightrm
  47. \qquad\grouptitle\hfill\title}
  48. \def\rheader{\eightrm\title\hfill\grouptitle\qquad\mainfont
  49. (\topsecno)\kern1pc\the\pageno}
  50. \def\botofcontents{\vfill
  51. \noindent Copyright \copyright\ \years~Neal Evan Wilson
  52. \bigskip\noindent Permission is hereby granted, free of charge, to any
  53. person obtaining a copy of this software and associated documentation files
  54. (the ``Software''), to deal in the Software without restriction, including
  55. without limitation the rights to use, copy, modify, merge, publish,
  56. distribute, sublicense, and/or sell copies of the Software, and to permit
  57. persons to whom the Software is furnished to do so, subject to the following
  58. conditions:\medskip
  59. The above copyright notice and this permission notice shall be included in
  60. all copies or substantial portions of the Software.\medskip
  61. THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  62. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  63. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  64. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  65. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  66. FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  67. IN THE SOFTWARE.
  68. \bigskip\noindent Parts of \pn{} are from QextSerialPort which is used under the
  69. MIT license as follows:
  70. \bigskip\noindent Copyright \copyright\ 2000--2003 Wayne Roth
  71. \noindent Copyright \copyright\ 2004--2007 Stefan Sander
  72. \noindent Copyright \copyright\ 2007 Michal Policht
  73. \noindent Copyright \copyright\ 2008 Brandon Fosdick
  74. \noindent Copyright \copyright\ 2009--2010 Liam Staskawicz
  75. \noindent Copyright \copyright\ 2011 Debao Zhang
  76. \bigskip\noindent Web: http://code.google.com/p/qextserialport/
  77. \bigskip\noindent Permission is hereby granted, free of charge, to any person obtaining
  78. a copy of this software and associated documentation files (the
  79. ``Software''), to deal in the Software without restriction, including
  80. without limitation the rights to use, copy, modify, merge, publish,
  81. distribute, sublicense, and/or sell copies of the Software, and to
  82. permit persons to whom the Software is furnished to do so, subject to
  83. the following conditions:
  84. The above copyright notice and this permission notice shall be
  85. included in all copies or substantial portions of the Software.
  86. THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
  87. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  88. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  89. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  90. LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  91. OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  92. WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  93. }
  94. \let\K=\leftarrow
  95. \def\CPLUSPLUS/{{%
  96. \mc C{\hbox{\kern.5pt\raise1pt\hbox{\sevenrm+\kern-1pt+}\kern.5pt}}
  97. \spacefactor1000}}
  98. \def\PP{\uparrow}
  99. \def\MM{\downarrow}
  100. \newbox\DCBox
  101. \setbox\DCBox=\hbox{$\in$}
  102. \def\DC{\copy\DCBox}
  103. \newbox\MODbox \setbox\MODbox=\hbox{\eightrm MOD}
  104. \def\MOD{\mathbin{\copy\MODbox}}
  105. % Title page
  106. \font\authorfont=cmr12
  107. \null\vfill
  108. \centerline{\titlefont \pn}
  109. \vskip 18pt\centerline{(Version \version)}
  110. \vskip 24pt\centerline{\authorfont Neal Evan Wilson}
  111. \vfill
  112. \titletrue\eject\hbox to 0pt{}
  113. \pageno=0 \titletrue\eject
  114. \secpagedepth=1
  115. % Convenience macros
  116. \def\newline{\vskip\baselineskip}
  117. \def\cweb{\.{CWEB}}
  118. \def\web{\.{WEB}}
  119. \newcount\footnotenumber
  120. \def\nfnote{\global\advance\footnotenumber by 1
  121. \footnote{$^{\the\footnotenumber}$}}
  122. % Listing macro from The TeXBook. See page 381 for an explaination.
  123. \def\uncatcodespecials{\def\do##1{\catcode`##1=12 }\dospecials}
  124. \newcount\lineno
  125. \def\setupverbatim{\tt \lineno=0
  126. \def\par{\leavevmode\endgraf} \catcode`\`=\active
  127. \obeylines \uncatcodespecials \obeyspaces
  128. \everypar{\advance\lineno by1 \llap{\sevenrm\the\lineno\ \ }}}
  129. {\obeyspaces\global\let =\ }
  130. \def\listing#1{\par\begingroup\setupverbatim\input#1 \endgroup}
  131. % Javascript chunk handling
  132. \def\jsfile#1#2{\Y\B\4\X\secno:\.{#1}\X${}\E{}\6$\par
  133. \listing{#2}}
  134. % Type formatting
  135. @s QTime int
  136. @s QMetaType int
  137. @s DAQ int
  138. @s Channel int
  139. @s QString int
  140. @s QObject int
  141. @s QThread int
  142. @s DAQImplementation int
  143. @s QVector int
  144. @s TaskHandle int
  145. @s qint32 int
  146. @s int32 int
  147. @s QMessageBox int
  148. @s QLCDNumber int
  149. @s QWidget int
  150. @s AnnotationButton int
  151. @s AnnotationSpinBox int
  152. @s QPushButton int
  153. @s QTimer int
  154. @s QAction int
  155. @s QApplication int
  156. @s PackLayout int
  157. @s QLayout int
  158. @s QLayoutItem int
  159. @s QRect int
  160. @s QList int
  161. @s QSize int
  162. @s QGraphicsScene int
  163. @s SceneButton int
  164. @s QGraphicsSceneMouseEvent int
  165. @s QPoint int
  166. @s true const
  167. @s false const
  168. @s QGraphicsView int
  169. @s QGraphicsTextItem int
  170. @s QFrame int
  171. @s QPaintDevice int
  172. @s QColor int
  173. @s QBrush int
  174. @s QHash int
  175. @s QPointF int
  176. @s QGraphicsLineItem int
  177. @s MeasurementModel int
  178. @s QTableView int
  179. @s QVariant int
  180. @s QAbstractItemView int
  181. @s QAbstractItemModel int
  182. @s QStringList int
  183. @s QModelIndex int
  184. @s MeasurementList int
  185. @s QVariantList int
  186. @s QSplitter int
  187. @s QHBoxLayout int
  188. @s QMainWindow int
  189. @s QCoreApplication int
  190. @s QSettings int
  191. @s QMenu int
  192. @s QCloseEvent int
  193. @s LogEditWindow int
  194. @s QFile int
  195. @s QFileInfo int
  196. @s QDir int
  197. @s QXmlStreamWriter int
  198. @s QXmlStreamReader int
  199. @s QIODevice int
  200. @s QLabel int
  201. @s QTimeEdit int
  202. @s QSpinBox int
  203. @s QDoubleSpinBox int
  204. @s ThermocoupleType int
  205. @s TemperatureUnits int
  206. @s Qt int
  207. @s emit throw
  208. @s TemperatureDisplay int
  209. @s ZeroEmitter int
  210. @s MeasurementAdapter int
  211. @s GraphView int
  212. @s ZoomLog int
  213. @s TimerDisplay int
  214. @s QBoxLayout int
  215. @s WidgetDecorator int
  216. @s XMLInput int
  217. @s XMLOutput int
  218. @s CSVOutput int
  219. @s QTextStream int
  220. @s QTranslator int
  221. @s QLocale int
  222. @s Application int
  223. @s QScriptContext int
  224. @s QScriptEngine int
  225. @s QScriptEngineDebugger int
  226. @s QScriptValue int
  227. @s FakeDAQ int
  228. @s QMenuBar int
  229. @s QKeySequence int
  230. @s QFileDialog int
  231. @s Measurement int
  232. @s Date int
  233. @s QLibrary int
  234. @s daqfp int
  235. @s QResizeEvent int
  236. @s QVBoxLayout int
  237. @s QByteArray int
  238. @s QSqlDatabase int
  239. @s QComboBox int
  240. @s QXmlStreamAttribute int
  241. @s QSqlQuery int
  242. @s QLineEdit int
  243. @s QDoubleValidator int
  244. @s QIntValidator int
  245. @s QTextEdit int
  246. @s QStandardItemModel int
  247. @s QValidator int
  248. @s QMap int
  249. @s QDomElement int
  250. @s QDomNodeList int
  251. @s QDomNode int
  252. @s QStack int
  253. @s QDomDocument int
  254. @s QDomNamedNodeMap int
  255. @s QFormLayout int
  256. @s QAbstractButton int
  257. @s QAbstractScrollArea int
  258. @s SqlComboBox int
  259. @s QUuid int
  260. @s SqlComboBoxDelegate int
  261. @s QItemDelegate int
  262. @s SqlConnectionSetup int
  263. @s QDialog int
  264. @s QCheckBox int
  265. @s SaltModel int
  266. @s QStyleOptionViewItem int
  267. @s QBuffer int
  268. @s QDateEdit int
  269. @s QCalendarWidget int
  270. @s QDate int
  271. @s QFocusEvent int
  272. @s QGridLayout int
  273. @s QScrollArea int
  274. @s QSqlQueryModel int
  275. @s QSqlRecord int
  276. @s QSqlResult int
  277. @s SqlQueryConnection int
  278. @s QFont int
  279. @s SqlQueryView int
  280. @s QTextDocument int
  281. @s QTextCursor int
  282. @s QTextFrame int
  283. @s ReportTable int
  284. @s QTextTable int
  285. @s QTextTableFormat int
  286. @s QTextFrameFormat int
  287. @s QTextTableCell int
  288. @s QPrinter int
  289. @s QPrintDialog int
  290. @s QSqlError int
  291. @s FormArray int
  292. @s QRegExp int
  293. @s QRegExpValidator int
  294. @s QDomDocumentFragment int
  295. @s QStackedLayout int
  296. @s QMouseEvent int
  297. @s QGraphicsPolygonItem int
  298. @s QPolygonF int
  299. @s QGraphicsPathItem int
  300. @s QPainterPath int
  301. @s QXmlQuery int
  302. @s QGraphicsItem int
  303. @s QWebView int
  304. @s QUrl int
  305. @s QShowEvent int
  306. @s QDateTimeEdit int
  307. @s ThresholdDetector int
  308. @s EdgeDirection int
  309. @s DeviceTreeModelNode int
  310. @s QMetaObject int
  311. @s QTreeView int
  312. @s QToolButton int
  313. @s QextPortInfo int
  314. @s QextSerialEnumerator int
  315. @s QMetaEnum int
  316. @s quint16 int
  317. @s QextSerialPort int
  318. @s QGroupBox int
  319. @s QVariantMap int
  320. @s QIcon int
  321. @s QFileInfoList int
  322. @s QMetaMethod int
  323. @f error normal
  324. @f line normal
  325. @f signals public
  326. @f slots int
  327. @f qRegisterMetaType make_pair
  328. @f READ TeX
  329. @f WRITE TeX
  330. @f tr TeX
  331. @f this TeX
  332. @f foreach while
  333. @f qobject_cast make_pair
  334. @f t1 TeX
  335. @f t2 TeX
  336. @f AppInstance TeX
  337. @f getself make_pair
  338. @f TYPE TeX
  339. @f argument make_pair
  340. @f toScriptValue make_pair
  341. @f arg1 TeX
  342. @f arg2 TeX
  343. @f arg3 TeX
  344. @f arg4 TeX
  345. @f findChild make_pair
  346. @f qscriptvalue_cast make_pair
  347. \def\READ{\kern4pt{\tt READ}\kern4pt}
  348. \def\WRITE{\kern4pt{\tt WRITE}\kern4pt}
  349. \def\tr{\delta}
  350. \def\this{\forall}
  351. \def\t#1{t_{#1}}
  352. \def\AppInstance{\.{AppInstance}}
  353. \def\TYPE{\cal T\kern1pt}
  354. \def\arg#1{arg_{#1}}
  355. % Document
  356. @** A Note on Notation.
  357. \noindent As noted by Falkoff and Iverson\nfnote{A.~D.~Falkoff and
  358. K.~E.~Iverson, ``The Design of APL'' (1973)}~there is little need to limit the
  359. typography used to represent a computer program in print. The printed code of
  360. \pn{} uses a number of notations that I have found useful in making clear the
  361. intent of the code. For example, a common mistake in \CPLUSPLUS/ \kern-0.5em
  362. code is the confusion of assignment ({\tt =}) with a test for equality
  363. ({\tt ==}). The \web{} convention of using |=| for assignment and |==| to test
  364. for equality makes such errors obvious at a glance. A list of special symbols
  365. and the equivalent \CPLUSPLUS/text is provided in Table \secno. Most of these
  366. symbols should be familiar\nfnote{The {\tt NULL} symbol is a break with the
  367. conventions of most Qt applications. According the the \CPLUSPLUS/standard, |0|
  368. is both an integer constant and a null pointer constant. Most programs using Qt
  369. use |0| in place of any name for the null pointer, however conceptually these
  370. are two very different things. The notation chosen here was used by Knuth for
  371. similar purposes and seems to have worked well there.}.
  372. \medskip
  373. \settabs 9 \columns
  374. \+&&&{\tt =}&|=|&Assignment\cr
  375. \+&&&{\tt --}&|--|&Decrement\cr
  376. \+&&&{\tt ==}&|==|&Equality Test\cr
  377. \+&&&{\tt >=}&|>=|&Greater or Equal Test\cr
  378. \+&&&{\tt ++}&|++|&Increment\cr
  379. \+&&&{\tt !=}&|!=|&Inequality Test\cr
  380. \+&&&{\tt <=}&|<=|&Less or Equal Test\cr
  381. \+&&&{\tt \char'046\char'046}&$\land$&Logical AND\cr
  382. \+&&&{\tt \char'174\char'174}&$\lor$&Logical OR\cr
  383. \+&&&{\tt ::}&|::|&Member of\cr
  384. \+&&&{\tt !}&|!|&Negation\cr
  385. \+&&&{\tt NULL}&|NULL|&Null Pointer\cr
  386. \+&&&{\tt this}&|this|&Object\cr
  387. \+&&&{\tt \%}&|%|&Remainder\cr
  388. \+&&&{\tt tr()}&|tr()|&Translate\cr
  389. \smallskip
  390. \centerline{Table \secno: Special Characters In \pn}
  391. \medskip
  392. Reserved words are set in bold face. As some of these reserved words are also
  393. the names of types, type names that are not specified in \CPLUSPLUS/are also
  394. set in bold face. Type placeholders in template definitions, however, are set in
  395. caligraphic capitals to emphasize that it will be replaced with a real type at
  396. compile time. Variables and class members are set in italics, character strings
  397. are set in a typewriter style with visible spaces. Macro names are also set in
  398. typewriter style. Numeric constants and plain text comments are set in an
  399. upright roman style. Comments containing \CEE/ or mathematics are styled as
  400. such. Code that will be interpreted by the ECMA-262 host environment has no
  401. pretty printing.
  402. \danger With apologies to prof.~Knuth\nfnote{This symbol was introduced in
  403. {\underbar{Computers~\char'046~Typesetting}}@q'@> (Knuth, 1984) to point out material
  404. that is for ``wizards only.'' It seems to be as appropriate a symbol as any to
  405. point out the darker corners of this program.}, code that is known to be
  406. potentially buggy is flagged with a dangerous bend sign. Some of this code is
  407. buggy due to issues with the code \pn{} depends on, others are things that
  408. should be fixed in \pn{}. Of course, there may also be bugs that have not yet
  409. been noticed or have not been attached to a particular block of code.\endanger
  410. A basic familiarity with literate programming techniques (particularly the
  411. conventions of \cweb{}), Qt, and \CPLUSPLUS/is recommended before altering the
  412. program, but an effort has been made to keep the program understandable for
  413. those who are only interested in studying it.
  414. @** Introduction.
  415. \noindent A common tool in the craft of coffee roasting is the data logger.
  416. Perhaps the most commonly used of these fall into the category of manual data
  417. loggers which require the roaster to use paper and a writing utensil,
  418. periodically recording measurements and noting control changes and observations
  419. of interest as needed.
  420. While there are many benefits to recording roast data\nfnote{Torrey Lee, Stephan
  421. Diedrich, Carl Staub, and Jack Newall, ``How to Obtain Excellence with Drum
  422. Roasters'' (2002) {\it Specialty Coffee Association of America 14$^{th}$ Annual
  423. Conference and Exhibition}}, there are a number of limitations to the manual
  424. approach; maintaining the records in a useful order is time consuming and error
  425. prone, it is difficult to work with aggregates of such records, and the
  426. attention required to log the data by hand can distract from the roasting. Using
  427. a computer with automatic data logging software designed with coffee roasting in
  428. mind can reduce or eliminate these deficiencies. \pn{} is one such program.
  429. The file {\tt \filebase.w} contains both \CPLUSPLUS/source code and the
  430. documentation for that code. This file is intended to be processed by
  431. \cweb\nfnote{Donald E. Knuth and Silvio Levy, ``The \cweb{} System of Structured
  432. Documentation'' (1994)}~to produce source code for your compiler and plain
  433. \TeX{}\nfnote{\TeX{} (pronounced $\tau\epsilon\chi$) is a trademark of the
  434. American Mathematical Society.} documentation that can be used to generate a PDF
  435. document for gorgeous printable documentation. These generated files may have
  436. been distributed with your copy of {\tt \filebase.w} for convenience.
  437. Changes to the program can be made in three ways. \cweb{} provides a patching
  438. mechanism which can be used to experiment with the code without risk of
  439. clobbering it. Instructions for the construction of such a change file can be
  440. found in the \cweb{} documentation. Adding the name of the change file to the
  441. invocation of {\tt ctangle} and {\tt cweave} will incorporate that change
  442. seamlessly in both source and documentation files. A section is provided at the
  443. end of this program for use with this mechanism in the case that new sections
  444. must be added. Another way to create persistent modifications is to alter
  445. {\tt \filebase.w} however this may make it more difficult to use changes with
  446. future versions of the software. Changes should not be made to
  447. {\tt \filebase.cpp} if these changes are expected to persist. Finally, it is
  448. possible to make many changes to how the program looks and behaves by creating a
  449. new configuration document for the program to load. Modifications made in this
  450. way do not even require recompiling the software. Examples that can serve as a
  451. starting point for such customizations are provided with \pn{}.
  452. \pn{} is a work in progress. There are several portions of the documentation
  453. that contain suggestions for future improvement. These notes provide clues for
  454. my future development plans. Naturally, if you have needs which are not quite
  455. addressed by this program, you should feel free to modify the code to suit your
  456. needs. Hopefully this will be easy to do.
  457. In the spirit of Benjamin Franklin\nfnote{``\dots as we enjoy great advantages
  458. from the inventions of others, we should be glad of an opportunity to serve
  459. others by any invention of ours; and this we should do freely and
  460. generously.''
  461. --- Benjamin Franklin, \underbar{The Private Life of the Late
  462. Benjamin Franklin, LL.D.~Originally
  463. Written By Himself, And Now}\par\noindent
  464. \underbar{Translated From The French} (1793)}, \pn{} is shared
  465. with minimal restriction (see the license after the table of contents for legal
  466. requirements). Libraries used by \pn{} may have other restrictions. Before
  467. undertaking to distribute a binary created from this code, you may want to
  468. either determine your rights with regard to these libraries or modify the
  469. program to remove them.
  470. As CWEB generates files with the wrong extension, we leave the default
  471. generated file empty.
  472. @c
  473. /* Nothing to see here. */
  474. @ The following is an overview of the structure of \pn:
  475. @(typica.cpp@>=
  476. #define PROGRAM_NAME "Typica"
  477. @<Header files to include@>@/
  478. @<Additional type definitions@>@/
  479. @<Additional function prototypes@>@/
  480. @<Class declarations@>@/
  481. @<Additional functions@>@/
  482. @<Function prototypes for scripting@>@/
  483. @<Logging function prototype@>@/
  484. @<Class implementations@>@/
  485. @<Functions for scripting@>@/
  486. @<Logging function implementation@>@/
  487. @<The main program@>
  488. #include "moc_typica.cpp"
  489. @ \pn{} is made of a number of distinct classes.
  490. @<Class implementations@>=
  491. @<NodeInserter implementation@>@/
  492. @<Measurement implementation@>@/
  493. @<DAQ Implementation@>@/
  494. @<DataqSdkDevice implementation@>@/
  495. @<FakeDAQ Implementation@>@/
  496. @<Channel Implementation@>@/
  497. @<TemperatureDisplay Implementation@>@/
  498. @<MeasurementTimeOffset Implementation@>@/
  499. @<ZeroEmitter Implementation@>@/
  500. @<MeasurementAdapter Implementation@>@/
  501. @<GraphView Implementation@>@/
  502. @<ZoomLog Implementation@>@/
  503. @<MeasurementModel Implementation@>@/
  504. @<AnnotationButton Implementation@>@/
  505. @<AnnotationSpinBox Implementation@>@/
  506. @<TimerDisplay Implementation@>@/
  507. @<PackLayout Implementation@>@/
  508. @<SceneButton Implementation@>@/
  509. @<WidgetDecorator Implementation@>@/
  510. @<LogEditWindow Implementation@>@/
  511. @<XMLOutput Implementation@>@/
  512. @<XMLInput Implementation@>@/
  513. @<CSVOutput Implementation@>@/
  514. @<SaltModel Implementation@>@/
  515. @<SqlComboBox Implementation@>@/
  516. @<SqlComboBoxDelegate Implementation@>@/
  517. @<Application Implementation@>@/
  518. @<SqlConnectionSetup implementation@>@/
  519. @<SqlQueryView implementation@>@/
  520. @<SqlQueryConnection implementation@>@/
  521. @<ReportTable implementation@>@/
  522. @<FormArray implementation@>@/
  523. @<ScaleControl implementation@>@/
  524. @<IntensityControl implementation@>@/
  525. @<ThresholdDetector Implementation@>@/
  526. @<PortSelector implementation@>@/
  527. @<BaudSelector implementation@>@/
  528. @<ParitySelector implementation@>@/
  529. @<FlowSelector implementation@>@/
  530. @<StopSelector implementation@>@/
  531. @<ModbusConfigurator implementation@>@/
  532. @<ShortHexSpinBox implementation@>@/
  533. @<ModbusRTUDevice implementation@>@/
  534. @<DeviceTreeModelNode implementation@>@/
  535. @<DeviceTreeModel implementation@>@/
  536. @<BasicDeviceConfigurationWidget implementation@>@/
  537. @<DeviceConfigurationWindow implementation@>@/
  538. @<Ni9211TcConfWidget implementation@>@/
  539. @<NiDaqMxBase9211ConfWidget implementation@>@/
  540. @<NiDaqMxBaseDriverConfWidget implementation@>@/
  541. @<ReportAction implementation@>@/
  542. @<NumericDelegate implementation@>@/
  543. @<NiDaqMxDriverConfWidget implementation@>@/
  544. @<NiDaqMx9211ConfWidget implementation@>@/
  545. @<NiDaqMxTc01ConfWidget implementation@>@/
  546. @<ModbusRtuPortConfWidget implementation@>@/
  547. @<ModbusRtuDeviceConfWidget implementation@>@/
  548. @<ModbusRtuDeviceTPvConfWidget implementation@>@/
  549. @<ModbusRtuDeviceTSvConfWidget implementation@>@/
  550. @<RoasterConfWidget implementation@>@/
  551. @<AnnotationButtonConfWidget implementation@>@/
  552. @<NoteSpinConfWidget implementation@>@/
  553. @<LinearCalibrator Implementation@>@/
  554. @<LinearSplineInterpolator Implementation@>@/
  555. @<LinearSplineInterpolationConfWidget implementation@>@/
  556. @<TranslationConfWidget implementation@>@/
  557. @<FreeAnnotationConfWidget implementation@>@/
  558. @<RateOfChange implementation@>@/
  559. @<SettingsWindow implementation@>@/
  560. @<GraphSettingsWidget implementation@>@/
  561. @<DataqSdkDeviceConfWidget implementation@>@/
  562. @<SerialScaleConfWidget implementation@>@/
  563. @<ValueAnnotation implementation@>@/
  564. @<ValueAnnotationConfWidget implementation@>@/
  565. @<ModbusNG implementation@>@/
  566. @<ThresholdAnnotationConfWidget implementation@>@/
  567. @<Annotator implementation@>@/
  568. @ A few headers are required for various parts of \pn{}. These allow the use of
  569. various Qt modules.
  570. @<Header files to include@>=
  571. #include <QtCore>
  572. #include <QtGui>
  573. #include <QtScript>
  574. #include <QtScriptTools>
  575. #include <QtXml>
  576. #include <QtSql>
  577. #include <QtDebug>
  578. #include <QtXmlPatterns>
  579. #include <QtWebKit>
  580. #include <QtSvg>
  581. #include <QtNetwork>
  582. @ New code is being written in separate files in a long term effort to improve
  583. organization of the code. The result of this is that some additional headers
  584. are required here.
  585. @<Header files to include@>=
  586. #include "helpmenu.h"
  587. @** The Scripting Engine.
  588. \noindent The main enhancement of \pn{} version 1.1 is the introduction of a
  589. scriptable environment. This change allows people to easily customize \pn{}
  590. without having to alter the program code. Instead, the user interface and
  591. program data flow is set up with a small script that runs in an ECMA-262 host
  592. environment\nfnote{Standard ECMA-262, 3$^{\rm{rd}}$ Edition\par\hbox{\indent%
  593. \pdfURL{%
  594. http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf}%
  595. {http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf}}}
  596. which requires
  597. significantly less expertise to modify than \pn{} itself. Such a scripting
  598. environment will be familiar to anybody with experience using JavaScript on web
  599. pages or ActionScript in Flash. \pn{}'@q'@>s configuration system was later updated
  600. to support running several script fragments found in an XML configuration
  601. document.
  602. Most of the application classes are available from the scripting environment.
  603. The functions that make this possible are presented along with the classes. A
  604. selection of classes provided by Qt are also available. These are presented
  605. here.
  606. This chunk provides two |QScriptValue| objects which are used in other sections
  607. appended to this chunk.
  608. @<Set up the scripting engine@>=
  609. QScriptEngine *engine = new QScriptEngine;
  610. QScriptValue constructor;
  611. QScriptValue value;
  612. @ A common task when working with objects created from a script is finding the
  613. object a method is called on from the current script context. The code for this
  614. is simple, but lengthy. This is shortened with the use of a template function
  615. that obtains the object in question and casts it to the appropriate type. If an
  616. incorrect type is specified, a null pointer or similarly invalid value will be
  617. returned.
  618. @<Functions for scripting@>=
  619. template<class TYPE> TYPE@, getself(QScriptContext *context)
  620. {
  621. TYPE@, self = qobject_cast<TYPE>(context->thisObject().toQObject());
  622. return self;
  623. }
  624. template<> QTime getself(QScriptContext *context)
  625. {
  626. QTime self = context->thisObject().toVariant().toTime();
  627. return self;
  628. }
  629. template<> QModelIndex getself(QScriptContext *context)
  630. {
  631. QModelIndex self = context->thisObject().toVariant().value<QModelIndex>();
  632. return self;
  633. }
  634. template<> QByteArray getself(QScriptContext *context)
  635. {
  636. QByteArray self = context->thisObject().toVariant().toByteArray();
  637. return self;
  638. }
  639. template<> SqlQueryConnection* getself(QScriptContext *context)
  640. {
  641. SqlQueryConnection *self =@|
  642. (SqlQueryConnection *)qscriptvalue_cast<void *>(context->thisObject());
  643. return self;
  644. }
  645. template<> QXmlQuery* getself(QScriptContext *context)
  646. {
  647. QXmlQuery *self =
  648. (QXmlQuery *)qscriptvalue_cast<void *>(context->thisObject());
  649. return self;
  650. }
  651. template<> QXmlStreamWriter* getself(QScriptContext *context)
  652. {
  653. QXmlStreamWriter *self = @|
  654. (QXmlStreamWriter *)qscriptvalue_cast<void *>(context->thisObject());
  655. return self;
  656. }
  657. template<> QXmlStreamReader* getself(QScriptContext *context)
  658. {
  659. QXmlStreamReader *self = @|
  660. (QXmlStreamReader *)qscriptvalue_cast<void *>(context->thisObject());
  661. return self;
  662. }
  663. @ Another common task is obtaining the arguments of a method call from the
  664. script context and casting these arguments to the proper type. This is once
  665. again done with templates.
  666. @<Functions for scripting@>=
  667. template<class TYPE> TYPE@, argument(int arg, QScriptContext *context)
  668. {
  669. TYPE@, argument = qobject_cast<TYPE>(context->argument(arg).toQObject());
  670. return argument;
  671. }
  672. template<> QString argument(int arg, QScriptContext *context)
  673. {
  674. return context->argument(arg).toString();
  675. }
  676. template<> QVariant argument(int arg, QScriptContext *context)
  677. {
  678. return context->argument(arg).toVariant();
  679. }
  680. template<> int argument(int arg, QScriptContext *context)
  681. {
  682. return context->argument(arg).toInt32();
  683. }
  684. template<> SqlQueryConnection* argument(int arg, QScriptContext *context)
  685. {
  686. return (SqlQueryConnection *)
  687. qscriptvalue_cast<void *>(context->argument(arg));
  688. }
  689. template<> QModelIndex argument(int arg, QScriptContext *context)
  690. {
  691. return qscriptvalue_cast<QModelIndex>(context->argument(arg));
  692. }
  693. template<> double argument(int arg, QScriptContext *context)
  694. {
  695. return (double)(context->argument(arg).toNumber());
  696. }
  697. template<> Units::Unit argument(int arg, QScriptContext *context)
  698. {
  699. return (Units::Unit)(context->argument(arg).toInt32());
  700. }
  701. template<> QByteArray argument(int arg, QScriptContext *context)
  702. {
  703. return qscriptvalue_cast<QByteArray>(context->argument(arg));
  704. }
  705. template<> bool argument(int arg, QScriptContext *context)
  706. {
  707. return context->argument(arg).toBool();
  708. }
  709. @ The scripting engine is informed of a number of classes defined elsewhere in
  710. the program. Code related to scripting these classes is grouped with the code
  711. implementing the classes. Additionally, there are several classes from Qt which
  712. are also made scriptable. These are detailed in the following sections.
  713. @* Exposing an Object Hierarchy to the Host Environment.
  714. \noindent While QtScript does a generally acceptable job of exposing
  715. information about objects that are available through the meta-object system,
  716. some methods require special handling in order to make them fully available to
  717. the host environment. Several functions are provided which provide a
  718. |QScriptValue| with these additional properties. The functions providing these
  719. properties also call other functions providing the properties of any base
  720. classes. In this way, any additional functionality provided to the host
  721. environment for a base class is also provided for any class that inherits that
  722. base class.
  723. For example, a |QBoxLayout| created in a script will have properties from
  724. |QLayout| which in turn brings in properties from |QObject| and |QLayoutItem|.
  725. A |QMainWindow| would bring in properties from |QWidget| which would bring in
  726. properties from |QObject|.
  727. Neither all methods nor all Qt classes are available from the host environment.
  728. When adding functionality to the host environment, there is a priority on
  729. classes and methods that are useful for \pn{}'@q'@>s intended purpose.
  730. @* Base Classes.
  731. \noindent There are a few classes that are base classes of classes exposed to
  732. the scripting engine. There is no need for the host environment to allow the
  733. creation of these base classes and there may not be methods that must be added
  734. as properties in derived classes, however stub functions are provided so that
  735. in the event that a method from one of these base classes is needed later, it
  736. can be added once for all derived classes.
  737. The first of these is |QObject|.
  738. @<Function prototypes for scripting@>=
  739. void setQObjectProperties(QScriptValue value, QScriptEngine *engine);
  740. QScriptValue QObject_setProperty(QScriptContext *context, QScriptEngine *engine);
  741. @ Attaching properties to a |QScriptValue| that wraps a |QObject| does not
  742. create a dynamic property on the underlying |QObject| by default. This can
  743. cause issues with certain interactions between script and native code. Rather
  744. than change every wrapper, we can instead expose a |setProperty()| method.
  745. @<Functions for scripting@>=
  746. void setQObjectProperties(QScriptValue value, QScriptEngine *engine)
  747. {
  748. value.setProperty("setProperty", engine->newFunction(QObject_setProperty));
  749. }
  750. QScriptValue QObject_setProperty(QScriptContext *context, QScriptEngine *)
  751. {
  752. QObject *self = getself<QObject *>(context);
  753. self->setProperty(argument<QString>(0, context).toUtf8().constData(),
  754. argument<QVariant>(1, context));
  755. return QScriptValue();
  756. }
  757. @ The same can be done for |QPaintDevice| and |QLayoutItem|.
  758. @<Function prototypes for scripting@>=
  759. void setQPaintDeviceProperties(QScriptValue value, QScriptEngine *engine);
  760. void setQLayoutItemProperties(QScriptValue value, QScriptEngine *engine);
  761. @ The implementations are similarly empty.
  762. @<Functions for scripting@>=
  763. void setQPaintDeviceProperties(QScriptValue, QScriptEngine *)
  764. {
  765. /* Nothing needs to be done here. */
  766. }
  767. void setQLayoutItemProperties(QScriptValue, QScriptEngine *)
  768. {
  769. /* Nothing needs to be done here. */
  770. }
  771. @* Timers.
  772. \noindent Some features in Typica require access to functionality similar to
  773. what |QTimer| provides from the host environment. This includes allowing
  774. script devices to periodically poll connected hardware and allowing a safety
  775. delay on profile translation.
  776. <@Function prototypes for scripting@>=
  777. void setQTimerProperties(QScriptValue value, QScriptEngine *engine);
  778. QScriptValue constructQTimer(QScriptContext *context, QScriptEngine *engine);
  779. @ The host environment is informed of the constructor.
  780. @<Set up the scripting engine@>=
  781. constructor = engine->newFunction(constructQTimer);
  782. value = engine->newQMetaObject(&QTimer::staticMetaObject, constructor);
  783. engine->globalObject().setProperty("Timer", value);
  784. @ Everything that we are interested in here is a signal, slot, or property so
  785. there is little else to do.
  786. @<Functions for scripting@>=
  787. void setQTimerProperties(QScriptValue value, QScriptEngine *engine)
  788. {
  789. setQObjectProperties(value, engine);
  790. }
  791. QScriptValue constructQTimer(QScriptContext *, QScriptEngine *engine)
  792. {
  793. QScriptValue object = engine->newQObject(new QTimer);
  794. setQTimerProperties(object, engine);
  795. return object;
  796. }
  797. @* Scripting QWidget.
  798. \noindent The first interesting class in this hierarchy is |QWidget|. This is
  799. mainly used as a base class for other widgets and in such a role it is not
  800. particularly interesting. It is, however, possible to apply a layout to a
  801. |QWidget| and use that to manage the size and position of one or more child
  802. widgets. A few functions are used to accomplish this.
  803. @<Function prototypes for scripting@>=
  804. void setQWidgetProperties(QScriptValue value, QScriptEngine *engine);
  805. QScriptValue constructQWidget(QScriptContext *context, QScriptEngine *engine);
  806. QScriptValue QWidget_setLayout(QScriptContext *context, QScriptEngine *engine);
  807. QScriptValue QWidget_activateWindow(QScriptContext *context,
  808. QScriptEngine *engine);
  809. @ The script constructor must be passed to the scripting engine.
  810. @<Set up the scripting engine@>=
  811. constructor = engine->newFunction(constructQWidget);
  812. value = engine->newQMetaObject(&QWidget::staticMetaObject, constructor);
  813. engine->globalObject().setProperty("QWidget", value);
  814. @ The constructor creates a script value, but uses another function to add
  815. properties that wrap methods we want to make available to subclasses. Note that
  816. properties of the base classes are added before properties of this class. This
  817. procedure ensures that properties added from base classes can be be replaced in
  818. subclasses.
  819. @<Functions for scripting@>=
  820. QScriptValue constructQWidget(QScriptContext *, QScriptEngine *engine)
  821. {
  822. QScriptValue object = engine->newQObject(new QWidget);
  823. setQWidgetProperties(object, engine);
  824. return object;
  825. }
  826. void setQWidgetProperties(QScriptValue value, QScriptEngine *engine)
  827. {
  828. setQObjectProperties(value, engine);
  829. setQPaintDeviceProperties(value, engine);
  830. value.setProperty("setLayout", engine->newFunction(QWidget_setLayout));
  831. value.setProperty("activateWindow",
  832. engine->newFunction(QWidget_activateWindow));
  833. }
  834. @ This just leaves the property implementations. |QWidget::setLayout()| takes
  835. one argument, a |QLayout| and returns |void|. This wrapper duplicates this
  836. interface. |QWidget::activateWindow()| takes no arguments and returns nothing
  837. meaningful.
  838. @<Functions for scripting@>=
  839. QScriptValue QWidget_setLayout(QScriptContext *context, QScriptEngine *)
  840. {
  841. if(context->argumentCount() == 1)
  842. {
  843. QWidget *self = getself<QWidget *>(context);
  844. QLayout *layout = argument<QLayout *>(0, context);
  845. if(layout)
  846. {
  847. self->setLayout(layout);
  848. }
  849. else
  850. {
  851. context->throwError("Incorrect argument type passed to "@|
  852. "QWidget::setLayout(). This method requires "@|
  853. "a QLayout.");
  854. }
  855. }
  856. else
  857. {
  858. context->throwError("Incorrect number of arguments passed to "@|
  859. "QWidget::setLayout(). This method takes one "@|
  860. "QLayout as an argument.");
  861. }
  862. return QScriptValue();
  863. }
  864. QScriptValue QWidget_activateWindow(QScriptContext *context,
  865. QScriptEngine *)
  866. {
  867. QWidget *self = getself<QWidget *>(context);
  868. self->activateWindow();
  869. return QScriptValue();
  870. }
  871. @* Scripting QMessageBox.
  872. \noindent Some features require that \pn{} pauses an operation until further
  873. information can be obtained. An example of this is discretionary validation
  874. where input is checked and if it seems unlikely but not impossible to be
  875. correct a dialog should come up asking if that input is correct. If it is not,
  876. the operation should be cancelled and the person using \pn{} should be allowed
  877. to correct the information and try again.
  878. For this use case, it is not necessary to fully expose the |QMessageBox| class.
  879. Instead, it is enough to provide a function that will raise an appropriate
  880. message and return the selected action.
  881. @<Function prototypes for scripting@>=
  882. QScriptValue displayWarning(QScriptContext *context, QScriptEngine *engine);
  883. QScriptValue displayError(QScriptContext *context, QScriptEngine *engine);
  884. QScriptValue displayInfo(QScriptContext *context, QScriptEngine *engine);
  885. @ This function is exposed to the host environment.
  886. @<Set up the scripting engine@>=
  887. constructor = engine->newFunction(displayWarning);
  888. engine->globalObject().setProperty("displayWarning", constructor);
  889. constructor = engine->newFunction(displayError);
  890. engine->globalObject().setProperty("displayError", constructor);
  891. constructor = engine->newFunction(displayInfo);
  892. engine->globalObject().setProperty("displayInfo", constructor);
  893. @ The function takes some arguments.
  894. @<Functions for scripting@>=
  895. QScriptValue displayWarning(QScriptContext *context, QScriptEngine *)
  896. {
  897. QMessageBox::StandardButton selection = QMessageBox::warning(NULL,
  898. argument<QString>(0, context),
  899. argument<QString>(1, context),
  900. QMessageBox::Ok | QMessageBox::Cancel);
  901. if(selection == QMessageBox::Ok) {
  902. return QScriptValue(true);
  903. }
  904. return QScriptValue(false);
  905. }
  906. QScriptValue displayError(QScriptContext *context, QScriptEngine *)
  907. {
  908. QMessageBox::critical(NULL, argument<QString>(0, context),
  909. argument<QString>(1, context));
  910. return QScriptValue();
  911. }
  912. QScriptValue displayInfo(QScriptContext *context, QScriptEngine *)
  913. {
  914. QMessageBox::information(NULL, argument<QString>(0, context),
  915. argument<QString>(1, context));
  916. return QScriptValue();
  917. }
  918. @* Scripting QMainWindow.
  919. \noindent Rather than directly exposing |QMainWindow| to the scripting engine,
  920. we expose a class derived from |QMainWindow| with a minor change allowing the
  921. script to be notified when the window is about to be closed.
  922. This allows us to save settings for objects populating the window. Close
  923. handlers can be established by connecting to the |aboutToClose()| signal which
  924. is emitted in the |closeEvent()| handler. The close event is always accepted
  925. after the script has had a chance to respond, so this cannot be used to present
  926. an, ``Are you sure?'' message without additional modification.
  927. Slots are also provided for saving the size and position of the window to
  928. settings and restoring the window geometry from these settings.
  929. As of version 1.4 window geometry management is provided for all windows. The
  930. |restoreSizeAndPosition()| and |saveSizeAndPosition()| methods should be
  931. considered depreciated.
  932. Version 1.6 adds a new property for handling the |windowModified| property
  933. such that an appropriate prompt is provided to confirm or cancel close events.
  934. Version 1.8 adds a new |setupFinished()| slot which is called after the
  935. initial |show()| at the end of window creation. This emits a |windowReady()|
  936. signal. Scripts can connect to this signal to perform tasks that must happen
  937. after the window has fully finished opening. The initial use for this is
  938. validating that all required configuration has been performed for a given
  939. window to be useful and, if not, immediately closing that. Without this, a
  940. call to |close()| in the script is reversed when the function creating the
  941. window calls |show()|.
  942. @<Class declarations@>=
  943. class ScriptQMainWindow : public QMainWindow@/
  944. {@t\1@>@/
  945. Q_OBJECT@;@/
  946. Q_PROPERTY(QString closePrompt READ closePrompt WRITE setClosePrompt)@;@/
  947. public:@/
  948. ScriptQMainWindow();
  949. QString closePrompt();@/
  950. @t\4@>public slots@t\kern-3pt@>:@/
  951. void show();
  952. void saveSizeAndPosition(const QString &key);
  953. void restoreSizeAndPosition(const QString &key);
  954. void displayStatus(const QString &message = QString());
  955. void setClosePrompt(QString prompt);
  956. void setupFinished();@/
  957. signals:@/
  958. void aboutToClose(void);
  959. void windowReady(void);@/
  960. protected:@/
  961. void closeEvent(QCloseEvent *event);
  962. void showEvent(QShowEvent *event);@/
  963. private:@/
  964. QString cprompt;@t\2@>@/
  965. }@t\kern-3pt@>;
  966. @ The implementation of these functions is simple.
  967. @<Functions for scripting@>=
  968. ScriptQMainWindow::ScriptQMainWindow()@+: QMainWindow(NULL),
  969. cprompt(tr("Closing this window may result in loss of data. Continue?"))@/
  970. {
  971. if(!AppInstance->databaseConnected())
  972. {
  973. statusBar()->addWidget(new QLabel(tr("Not connected to database")));
  974. }
  975. else
  976. {
  977. statusBar()->addWidget(new UserLabel);
  978. }
  979. }
  980. void ScriptQMainWindow::saveSizeAndPosition(const QString &key)
  981. {
  982. QSettings settings;
  983. settings.beginGroup(key);
  984. settings.setValue("pos", pos());
  985. settings.setValue("size", size());
  986. settings.endGroup();
  987. }
  988. void ScriptQMainWindow::restoreSizeAndPosition(const QString &key)
  989. {
  990. QSettings settings;
  991. settings.beginGroup(key);
  992. if(settings.contains("size"))
  993. {
  994. resize(settings.value("size").toSize());
  995. }
  996. if(settings.contains("pos"))
  997. {
  998. move(settings.value("pos").toPoint());
  999. }
  1000. settings.endGroup();
  1001. }
  1002. void ScriptQMainWindow::displayStatus(const QString &message)
  1003. {
  1004. statusBar()->showMessage(message);
  1005. }
  1006. void ScriptQMainWindow::showEvent(QShowEvent *event)
  1007. {
  1008. if(!event->spontaneous())
  1009. {
  1010. @<Restore window geometry@>@;
  1011. event->accept();
  1012. }
  1013. else
  1014. {
  1015. event->ignore();
  1016. }
  1017. }
  1018. void ScriptQMainWindow::show()
  1019. {
  1020. QMainWindow::show();
  1021. }
  1022. void ScriptQMainWindow::setupFinished()
  1023. {
  1024. emit windowReady();
  1025. }
  1026. @ When a close event occurs, we check the |windowModified| property to
  1027. determine if closing the window could result in loss of data. If this is
  1028. true, we allow the event to be cancelled. Otherwise, a signal is emitted which
  1029. allows scripts to perform any cleanup that may be required before closing the
  1030. window and the window geometry data is saved before allowing the window to
  1031. close.
  1032. @<Functions for scripting@>=
  1033. void ScriptQMainWindow::closeEvent(QCloseEvent *event)
  1034. {
  1035. if(isWindowModified()) {
  1036. @<Allow close event to be cancelled@>@;
  1037. }
  1038. emit aboutToClose();
  1039. @<Save window geometry@>@;
  1040. event->accept();
  1041. }
  1042. @ The prompt text for our confirmation window is provided through the
  1043. |closePrompt| property.
  1044. @<Allow close event to be cancelled@>=
  1045. QMessageBox::StandardButton result;
  1046. result = QMessageBox::warning(this, "Typica", closePrompt(),
  1047. QMessageBox::Ok | QMessageBox::Cancel);
  1048. if(result == QMessageBox::Cancel)
  1049. {
  1050. event->ignore();
  1051. return;
  1052. }
  1053. @ Implementation of the |closePrompt| property is trivial.
  1054. @<Functions for scripting@>=
  1055. QString ScriptQMainWindow::closePrompt()
  1056. {
  1057. return cprompt;
  1058. }
  1059. void ScriptQMainWindow::setClosePrompt(QString prompt)
  1060. {
  1061. cprompt = prompt;
  1062. }
  1063. @ Window geometry management from version 1.4 on makes use of the window ID to
  1064. produce an appropriate QSettings key. This decision relies on the ID being set
  1065. before any show or close events are received and it relies on every distinct
  1066. type of window having a unique ID. If this is not the case then other things
  1067. are likely very broken. Note that with this approach multiple instances of the
  1068. same type of window will use the same key. This may not be ideal in all cases,
  1069. but further refinements can be produced if necessary.
  1070. @<Save window geometry@>=
  1071. QSettings settings;
  1072. settings.setValue(QString("geometries/%1").arg(objectName()), saveGeometry());
  1073. @ Restoring saved geometry is performed similarly to saving it.
  1074. @<Restore window geometry@>=
  1075. QSettings settings;
  1076. restoreGeometry(settings.value(QString("geometries/%1").arg(objectName())).
  1077. toByteArray());
  1078. @ Three functions are required to obtain the required functionality from a
  1079. script. A fourth adds properties for the object hierarchy.
  1080. @<Function prototypes for scripting@>=
  1081. QScriptValue constructQMainWindow(QScriptContext *context,
  1082. QScriptEngine *engine);
  1083. QScriptValue QMainWindow_setCentralWidget(QScriptContext *context,@|
  1084. QScriptEngine *engine);
  1085. QScriptValue QMainWindow_menuBar(QScriptContext *context,
  1086. QScriptEngine *engine);
  1087. void setQMainWindowProperties(QScriptValue value, QScriptEngine *engine);
  1088. @ Of these, the engine only needs to be informed of the constructor initially.
  1089. @<Set up the scripting engine@>=
  1090. constructor = engine->newFunction(constructQMainWindow);
  1091. value = engine->newQMetaObject(&ScriptQMainWindow::staticMetaObject,
  1092. constructor);
  1093. engine->globalObject().setProperty("QMainWindow", value);
  1094. @ The constructor calls a function to add the additional properties to the
  1095. newly created value.
  1096. @<Functions for scripting@>=
  1097. QScriptValue constructQMainWindow(QScriptContext *, QScriptEngine *engine)
  1098. {
  1099. QScriptValue object = engine->newQObject(new ScriptQMainWindow);
  1100. setQMainWindowProperties(object, engine);
  1101. return object;
  1102. }
  1103. void setQMainWindowProperties(QScriptValue value, QScriptEngine *engine)
  1104. {
  1105. setQWidgetProperties(value, engine);
  1106. value.setProperty("setCentralWidget",
  1107. engine->newFunction(QMainWindow_setCentralWidget));
  1108. value.setProperty("menuBar", engine->newFunction(QMainWindow_menuBar));
  1109. }
  1110. @ The |"setCentralWidget"| property is used for setting a widget in the main
  1111. area of the window. In \pn{} this will usually be a |QSplitter| object, but it
  1112. could also be a bare |QWidget| with a layout managing multiple widgets or a
  1113. custom widget defined in a local change. This is a simple wrapper around
  1114. |QMainWindow::setCentralWidget()|.
  1115. @<Functions for scripting@>=
  1116. QScriptValue QMainWindow_setCentralWidget(QScriptContext *context,
  1117. QScriptEngine *)
  1118. {
  1119. if(context->argumentCount() == 1)
  1120. {
  1121. QMainWindow *self = getself<QMainWindow *>(context);
  1122. QWidget *widget = argument<QWidget *>(0, context);
  1123. if(widget)
  1124. {
  1125. self->setCentralWidget(widget);
  1126. }
  1127. else
  1128. {
  1129. context->throwError("Incorrect argument type passed to "@|
  1130. "QMainWindow::setCentralWidget(). This "@|
  1131. "method requires a QWidget.");
  1132. }
  1133. }
  1134. else
  1135. {
  1136. context->throwError("Incorrect number of arguments passed to "@|
  1137. "QMainWindow::setCentralWidget(). This method "@|
  1138. "takes one QWidget as an argument.");
  1139. }
  1140. return QScriptValue();
  1141. }
  1142. @ The |"menuBar"| property requires that we expose |QMenuBar| to the scripting
  1143. environment in a limited fashion. We don'@q'@>t need to allow scripts to create a
  1144. new menu bar as it can be obtained from the window, however to add the menus to
  1145. the menu bar, we need to add a property to the |QMenuBar| object before passing
  1146. it back.
  1147. @<Functions for scripting@>=
  1148. QScriptValue QMainWindow_menuBar(QScriptContext *context, QScriptEngine *engine)
  1149. {
  1150. QScriptValue object;
  1151. if(context->argumentCount() == 0)
  1152. {
  1153. QMainWindow *self = getself<@[QMainWindow *@]>(context);
  1154. QMenuBar *bar = self->menuBar();
  1155. object = engine->newQObject(bar);
  1156. setQMenuBarProperties(object, engine);
  1157. }
  1158. else
  1159. {
  1160. context->throwError("Incorrect number of arguments passed to "@|
  1161. "QMainWindow::menuBar(). This method takes no "@|
  1162. "arguments.");
  1163. }
  1164. return object;
  1165. }
  1166. @ The previous function is the only place a new |QMenuBar| is created through
  1167. the host environment. Two functions are used in handling this object creation.
  1168. @<Function prototypes for scripting@>=
  1169. void setQMenuBarProperties(QScriptValue value, QScriptEngine *engine);
  1170. QScriptValue QMenuBar_addMenu(QScriptContext *context, QScriptEngine *engine);
  1171. @ The first of these adds the appropriate properties to the newly created
  1172. object.
  1173. @<Functions for scripting@>=
  1174. void setQMenuBarProperties(QScriptValue value, QScriptEngine *engine)
  1175. {
  1176. setQWidgetProperties(value, engine);
  1177. value.setProperty("addMenu", engine->newFunction(QMenuBar_addMenu));
  1178. }
  1179. @ This function can be used to add new menus to a menu bar. In order to do
  1180. anything with the newly created menus, two properties are added to the |QMenu|
  1181. objects which allow actions to be added as menu items and allow separators to be
  1182. placed between groups of menu items.
  1183. At the time of this writing, there are three |QMenuBar::addMenu()| methods. This
  1184. function wraps |QMenu* QMenuBar::addMenu(const QString &title)|.
  1185. @<Functions for scripting@>=
  1186. QScriptValue QMenuBar_addMenu(QScriptContext *context, QScriptEngine *engine)
  1187. {
  1188. QScriptValue object;
  1189. if(context->argumentCount() == 1)
  1190. {
  1191. QMenuBar *self = getself<@[QMenuBar *@]>(context);
  1192. QString title = argument<QString>(0, context);
  1193. object = engine->newQObject(self->addMenu(title));
  1194. setQMenuProperties(object, engine);
  1195. }
  1196. else
  1197. {
  1198. context->throwError("Incorrect number of arguments passed to "@|
  1199. "QMenuBar::addMenu(). This method takes one "@|
  1200. "string as an argument.");
  1201. }
  1202. return object;
  1203. }
  1204. @ These three functions allow adding items to the menu and adding separators
  1205. between groups of items.
  1206. @<Function prototypes for scripting@>=
  1207. void setQMenuProperties(QScriptValue value, QScriptEngine *engine);
  1208. QScriptValue QMenu_addAction(QScriptContext *context, QScriptEngine *engine);
  1209. QScriptValue QMenu_addSeparator(QScriptContext *context, QScriptEngine *engine);
  1210. @ The first of these add properties to newly created |QMenu| objects.
  1211. @<Functions for scripting@>=
  1212. void setQMenuProperties(QScriptValue value, QScriptEngine *engine)
  1213. {
  1214. setQWidgetProperties(value, engine);
  1215. value.setProperty("addAction", engine->newFunction(QMenu_addAction));
  1216. value.setProperty("addSeparator", engine->newFunction(QMenu_addSeparator));
  1217. }
  1218. @ These functions are simple wrappers around |QMenu| methods.
  1219. @<Functions for scripting@>=
  1220. QScriptValue QMenu_addAction(QScriptContext *context, QScriptEngine *)
  1221. {
  1222. if(context->argumentCount() == 1)
  1223. {
  1224. QMenu *self = getself<@[QMenu *@]>(context);
  1225. QAction *action = argument<QAction *>(0, context);
  1226. if(action)
  1227. {
  1228. self->addAction(action);
  1229. }
  1230. else
  1231. {
  1232. context->throwError("Incorrect argument type passed to "@|
  1233. "QMenu::addAction(). This method requires a "@|
  1234. "QAction.");
  1235. }
  1236. }
  1237. else
  1238. {
  1239. context->throwError("Incorrect number of arguments passed to "@|
  1240. "QMenu::addAction(). This method takes one "@|
  1241. "QAction as an argument.");
  1242. }
  1243. return QScriptValue();
  1244. }
  1245. QScriptValue QMenu_addSeparator(QScriptContext *context, QScriptEngine *)
  1246. {
  1247. if(context->argumentCount() == 0)
  1248. {
  1249. QMenu *self = getself<@[QMenu *@]>(context);
  1250. self->addSeparator();
  1251. }
  1252. else
  1253. {
  1254. context->throwError("Incorrect number of arguments passed to "@|
  1255. "QMenu::addSeparator(). This method takes no "@|
  1256. "arguments.");
  1257. }
  1258. return QScriptValue();
  1259. }
  1260. @* Scripting QFrame.
  1261. \noindent |QFrame| is another class for which little needs to be done. It exists
  1262. as a subclass of |QWidget| and a superclass for |QSplitter|, |QLCDNumber|, and
  1263. |QAbstractScrollArea| among other classes.
  1264. @<Function prototypes for scripting@>=
  1265. void setQFrameProperties(QScriptValue value, QScriptEngine *engine);
  1266. QScriptValue constructQFrame(QScriptContext *context, QScriptEngine *engine);
  1267. @ The constructor must be passed to the scripting engine.
  1268. @<Set up the scripting engine@>=
  1269. constructor = engine->newFunction(constructQFrame);
  1270. value = engine->newQMetaObject(&QFrame::staticMetaObject, constructor);
  1271. engine->globalObject().setProperty("QFrame", value);
  1272. @ The implementation of these functions should seem familiar.
  1273. @<Functions for scripting@>=
  1274. QScriptValue constructQFrame(QScriptContext *, QScriptEngine *engine)
  1275. {
  1276. QScriptValue object = engine->newQObject(new QFrame);
  1277. setQFrameProperties(object, engine);
  1278. return object;
  1279. }
  1280. void setQFrameProperties(QScriptValue value, QScriptEngine *engine)
  1281. {
  1282. setQWidgetProperties(value, engine);
  1283. }
  1284. @* Scripting QLabel.
  1285. \noindent When constructing an interface wholly or partially through dynamic
  1286. means rather than entirely through the XML configuration document it can
  1287. sometimes be desirable to construct |QLabel| instances. This is usually used
  1288. to provide a very small amount of text.
  1289. @<Function prototypes for scripting@>=
  1290. void setQLabelProperties(QScriptValue value, QScriptEngine *engine);
  1291. QScriptValue constructQLabel(QScriptContext *context, QScriptEngine *engine);
  1292. @ The constructor must be passed to the scripting engine.
  1293. @<Set up the scripting engine@>=
  1294. constructor = engine->newFunction(constructQLabel);
  1295. value = engine->newQMetaObject(&QLabel::staticMetaObject, constructor);
  1296. engine->globalObject().setProperty("QLabel", value);
  1297. @ In the constructor we allow an optional argument to specify the text of the
  1298. label.
  1299. @<Functions for scripting@>=
  1300. QScriptValue constructQLabel(QScriptContext *context, QScriptEngine *engine)
  1301. {
  1302. QString text;
  1303. if(context->argumentCount() == 1)
  1304. {
  1305. text = argument<QString>(0, context);
  1306. }
  1307. QScriptValue object = engine->newQObject(new QLabel(text));
  1308. setQLabelProperties(object, engine);
  1309. return object;
  1310. }
  1311. void setQLabelProperties(QScriptValue value, QScriptEngine *engine)
  1312. {
  1313. setQFrameProperties(value, engine);
  1314. }
  1315. @* Scripting QSvgWidget.
  1316. \noindent Sometimes it is useful to provide a space for simple drawings without
  1317. the need for all of the other capabilities of a web view. This was introduced
  1318. as a way to draw box plots to help guide the creation of roast specifications.
  1319. @<Function prototypes for scripting@>=
  1320. void setQSvgWidgetProperties(QScriptValue value, QScriptEngine *engine);
  1321. QScriptValue constructQSvgWidget(QScriptContext *context,
  1322. QScriptEngine *engine);
  1323. QScriptValue QSvgWidget_loadDevice(QScriptContext *context,
  1324. QScriptEngine *engine);
  1325. void addSvgWidgetToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  1326. QStack<QLayout *> *layoutStack);
  1327. @ The constructor must be passed to the scripting engine.
  1328. @<Set up the scripting engine@>=
  1329. constructor = engine->newFunction(constructQSvgWidget);
  1330. value = engine->newQMetaObject(&QSvgWidget::staticMetaObject, constructor);
  1331. engine->globalObject().setProperty("QSvgWidget", value);
  1332. @ The constructor is trivial.
  1333. @<Functions for scripting@>=
  1334. QScriptValue constructQSvgWidget(QScriptContext *,
  1335. QScriptEngine *engine)
  1336. {
  1337. QScriptValue object = engine->newQObject(new QSvgWidget);
  1338. setQSvgWidgetProperties(object, engine);
  1339. return object;
  1340. }
  1341. @ A property is added that allows loading data from a |QIODevice|.
  1342. @<Functions for scripting@>=
  1343. void setQSvgWidgetProperties(QScriptValue value, QScriptEngine *engine)
  1344. {
  1345. setQWidgetProperties(value, engine);
  1346. value.setProperty("loadDevice",
  1347. engine->newFunction(QSvgWidget_loadDevice));
  1348. }
  1349. QScriptValue QSvgWidget_loadDevice(QScriptContext *context, QScriptEngine *)
  1350. {
  1351. if(context->argumentCount() == 1)
  1352. {
  1353. QSvgWidget *self = getself<@[QSvgWidget *@]>(context);
  1354. QIODevice *device = argument<QIODevice *>(0, context);
  1355. device->reset();
  1356. QByteArray data = device->readAll();
  1357. self->load(data);
  1358. }
  1359. else
  1360. {
  1361. context->throwError("Incorrect number of arguments passed to "@|
  1362. "QSvgWidget::loadData(). This method takes one "@|
  1363. "QIODevice as an argument.");
  1364. }
  1365. return QScriptValue();
  1366. }
  1367. @ Additional work is needed to allow including this from the XML description of
  1368. a window.
  1369. @<Additional box layout elements@>=
  1370. else if(currentElement.tagName() == "svgwidget")
  1371. {
  1372. addSvgWidgetToLayout(currentElement, widgetStack, layoutStack);
  1373. }
  1374. @ The function used to create this follows the usual pattern.
  1375. @<Functions for scripting@>=
  1376. void addSvgWidgetToLayout(QDomElement element, QStack<QWidget *> *,
  1377. QStack<QLayout *> *layoutStack)
  1378. {
  1379. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  1380. QSvgWidget *widget = new QSvgWidget;
  1381. layout->addWidget(widget);
  1382. QString id = element.attribute("id");
  1383. if(!id.isEmpty())
  1384. {
  1385. widget->setObjectName(id);
  1386. }
  1387. }
  1388. @* Scripting QLineEdit.
  1389. \noindent Similarly, we may want to allow line edits in interfaces defined
  1390. through the host environment. For example, this is used for the free text
  1391. annotation control for roasters this has been configured on.
  1392. @<Function prototypes for scripting@>=
  1393. void setQLineEditProperties(QScriptValue value, QScriptEngine *engine);
  1394. QScriptValue constructQLineEdit(QScriptContext *context, QScriptEngine *engine);
  1395. @ The constructor must be passed to the host environment.
  1396. @<Set up the scripting engine@>=
  1397. constructor = engine->newFunction(constructQLineEdit);
  1398. value = engine->newQMetaObject(&QLineEdit::staticMetaObject, constructor);
  1399. engine->globalObject().setProperty("QLineEdit", value);
  1400. @ The constructor is trivial.
  1401. @<Functions for scripting@>=
  1402. QScriptValue constructQLineEdit(QScriptContext *, QScriptEngine *engine)
  1403. {
  1404. QScriptValue object = engine->newQObject(new QLineEdit());
  1405. setQLineEditProperties(object, engine);
  1406. return object;
  1407. }
  1408. @ At present all of the QLineEdit functionality exposed through this interface
  1409. is provided automatically through the meta-object system.
  1410. @<Functions for scripting@>=
  1411. void setQLineEditProperties(QScriptValue value, QScriptEngine *engine)
  1412. {
  1413. setQWidgetProperties(value, engine);
  1414. }
  1415. @* Scripting QSplitter.
  1416. \noindent The |QSplitter| class is one of the main classes used for user
  1417. interface object layout in \pn{}. To provide this class to the scripting engine,
  1418. we provide five functions: a constructor, a method for adding widgets to the
  1419. splitter, a method for saving the size of each widget in the splitter, a
  1420. method for restoring these saved sizes, and a function for adding these methods
  1421. as properties of newly created |QSplitter| objects.
  1422. @<Function prototypes for scripting@>=
  1423. QScriptValue constructQSplitter(QScriptContext *context, QScriptEngine *engine);
  1424. QScriptValue QSplitter_addWidget(QScriptContext *context,
  1425. QScriptEngine *engine);
  1426. QScriptValue QSplitter_saveState(QScriptContext *context,
  1427. QScriptEngine *engine);
  1428. QScriptValue QSplitter_restoreState(QScriptContext *context,
  1429. QScriptEngine *engine);
  1430. QScriptValue QSplitter_count(QScriptContext *context,
  1431. QScriptEngine *engine);
  1432. QScriptValue QSplitter_setCollapsible(QScriptContext *context,
  1433. QScriptEngine *engine);
  1434. void setQSplitterProperties(QScriptValue value, QScriptEngine *engine);
  1435. @ Of these, the scripting engine must be informed of the constructor.
  1436. @<Set up the scripting engine@>=
  1437. constructor = engine->newFunction(constructQSplitter);
  1438. value = engine->newQMetaObject(&QSplitter::staticMetaObject, constructor);
  1439. engine->globalObject().setProperty("QSplitter", value);
  1440. @ The constructor creates the object and adds the required properties to it.
  1441. @<Functions for scripting@>=
  1442. QScriptValue constructQSplitter(QScriptContext *, QScriptEngine *engine)
  1443. {
  1444. QScriptValue object = engine->newQObject(new QSplitter);
  1445. setQSplitterProperties(object, engine);
  1446. return object;
  1447. }
  1448. void setQSplitterProperties(QScriptValue value, QScriptEngine *engine)
  1449. {
  1450. setQFrameProperties(value, engine);
  1451. value.setProperty("addWidget", engine->newFunction(QSplitter_addWidget));
  1452. value.setProperty("saveState", engine->newFunction(QSplitter_saveState));
  1453. value.setProperty("restoreState",
  1454. engine->newFunction(QSplitter_restoreState));
  1455. value.setProperty("count", engine->newFunction(QSplitter_count));
  1456. value.setProperty("setCollapsible", engine->newFunction(QSplitter_setCollapsible));
  1457. }
  1458. @ The |"addWidget"| property is a simple wrapper around
  1459. |QSplitter::addWidget()|.
  1460. @<Functions for scripting@>=
  1461. QScriptValue QSplitter_addWidget(QScriptContext *context, QScriptEngine *)
  1462. {
  1463. if(context->argumentCount() == 1)
  1464. {
  1465. QSplitter *self = getself<QSplitter *>(context);
  1466. QWidget *widget = argument<QWidget *>(0, context);
  1467. if(widget)
  1468. {
  1469. self->addWidget(widget);
  1470. }
  1471. else
  1472. {
  1473. context->throwError("Incorrect argument type passed to "@|
  1474. "QSplitter::addWidget(). This method "@|
  1475. "requires a QWidget.");
  1476. }
  1477. }
  1478. else
  1479. {
  1480. context->throwError("Incorrect number of arguments passed to "@|
  1481. "QSplitter::addWidget(). This method takes one "@|
  1482. "QWidget as an argument.");
  1483. }
  1484. return QScriptValue();
  1485. }
  1486. @ The methods for saving and restoring the state of a splitter do not behave
  1487. well when the number of widgets contained in the splitter increase. This is a
  1488. problem in the logging view where we may want to allow zero width indicators
  1489. but reconfiguration can cause the number of indicators to increase. This would
  1490. result in the right most indicators such as the batch timer disappearing. Most
  1491. people do not notice the splitter handle or think to drag that to the left to
  1492. correct this issue so it would be better to save the number of indicators when
  1493. saving the state and if the number of indicators does not match, we should not
  1494. restore the obsolete saved state.
  1495. @<Functions for scripting@>=
  1496. QScriptValue QSplitter_count(QScriptContext *context, QScriptEngine *)
  1497. {
  1498. QSplitter *self = getself<QSplitter *>(context);
  1499. return QScriptValue(self->count());
  1500. }
  1501. @ When saving and restoring the state of a splitter, we always want to do this
  1502. through a |QSettings| object. For this, we take an extra argument specifying the
  1503. settings key to read from or write to. Unlike the equivalent functions called
  1504. from native code, neither of these functions called from a script will return
  1505. the data being saved.
  1506. @<Functions for scripting@>=
  1507. QScriptValue QSplitter_saveState(QScriptContext *context, QScriptEngine *)
  1508. {
  1509. if(context->argumentCount() == 1)
  1510. {
  1511. QSplitter *self = getself<QSplitter *>(context);
  1512. QString key = argument<QString>(0, context);
  1513. QSettings settings;
  1514. settings.setValue(key, self->saveState());
  1515. }
  1516. else
  1517. {
  1518. context->throwError("Incorrect number of arguments passed to "@|
  1519. "QSplitter::saveState(). This method takes one "@|
  1520. "string as an argument.");
  1521. }
  1522. return QScriptValue();
  1523. }
  1524. QScriptValue QSplitter_restoreState(QScriptContext *context, QScriptEngine *)
  1525. {
  1526. if(context->argumentCount() == 1)
  1527. {
  1528. QSplitter *self = getself<QSplitter *>(context);
  1529. QString key = argument<QString>(0, context);
  1530. QSettings settings;
  1531. self->restoreState(settings.value(key).toByteArray());
  1532. }
  1533. else
  1534. {
  1535. context->throwError("Incorrect number of arguments passed to "@|
  1536. "QSplitter::restoreState(). This method takes "@|
  1537. "one string as an argument.");
  1538. }
  1539. return QScriptValue();
  1540. }
  1541. @ Sometimes a |QSplitter| is used to make it easy to hide optional elements. In
  1542. this use case it can be useful to indicate that required elements cannot be
  1543. collapsed.
  1544. @<Functions for scripting@>=
  1545. QScriptValue QSplitter_setCollapsible(QScriptContext *context, QScriptEngine *)
  1546. {
  1547. if(context->argumentCount() == 2)
  1548. {
  1549. QSplitter *self = getself<QSplitter *>(context);
  1550. self->setCollapsible(argument<int>(0, context), argument<bool>(1, context));
  1551. }
  1552. else
  1553. {
  1554. context->throwError("Incorrect number of arguments");
  1555. }
  1556. return QScriptValue();
  1557. }
  1558. @* Scripting Layout classes.
  1559. \noindent Layout classes simplify managing the size and position of widgets in a
  1560. user interface. Qt provides several such classes, including |QBoxLayout| which
  1561. can be used to construct a variety of different interfaces. As widgets
  1562. containing a layout should not really need to care which layout is being used,
  1563. the |QLayout| class acts as an abstract base for all layout classes. A bare
  1564. |QLayout| will never be constructed, however subclasses can make use of the
  1565. |addWidget()| property.
  1566. @<Function prototypes for scripting@>=
  1567. void setQLayoutProperties(QScriptValue value, QScriptEngine *engine);
  1568. QScriptValue QLayout_addWidget(QScriptContext *context, QScriptEngine *engine);
  1569. @ The implementation is trivial.
  1570. @<Functions for scripting@>=
  1571. void setQLayoutProperties(QScriptValue value, QScriptEngine *engine)
  1572. {
  1573. setQLayoutItemProperties(value, engine);
  1574. value.setProperty("addWidget", engine->newFunction(QLayout_addWidget));
  1575. }
  1576. QScriptValue QLayout_addWidget(QScriptContext *context, QScriptEngine *)
  1577. {
  1578. if(context->argumentCount() == 1)
  1579. {
  1580. QLayout *self = getself<QLayout *>(context);
  1581. QWidget *widget = argument<QWidget *>(0, context);
  1582. if(widget)
  1583. {
  1584. self->addWidget(widget);
  1585. }
  1586. else
  1587. {
  1588. context->throwError("Incorrect argument type passed to "@|
  1589. "QLayout::addWidget(). This method requires "@|
  1590. "a QWidget.");
  1591. }
  1592. }
  1593. else
  1594. {
  1595. context->throwError("Incorrect number of arguments passed to "@|
  1596. "QLayout::addWidget(). This method takes one "@|
  1597. "QWidget as an argument.");
  1598. }
  1599. return QScriptValue();
  1600. }
  1601. @ |QBoxLayout| is a more interesting layout class. This allows widgets to be
  1602. arranged in a single row or column and can be used, for example, to arrange a
  1603. row of buttons as in figure \secno.
  1604. \medskip
  1605. \resizebox*{6.3in}{!}{\includegraphics{boxlayoutexample}}
  1606. \smallskip
  1607. \centerline{Figure \secno: Buttons in a |QBoxLayout|.}
  1608. \medskip
  1609. This class makes use of the |addWidget()| method from |QLayout|.
  1610. @<Function prototypes for scripting@>=
  1611. QScriptValue constructQBoxLayout(QScriptContext *context,
  1612. QScriptEngine *engine);
  1613. void setQBoxLayoutProperties(QScriptValue value, QScriptEngine *engine);
  1614. QScriptValue QBoxLayout_addLayout(QScriptContext *context, QScriptEngine *engine);
  1615. QScriptValue QBoxLayout_addWidget(QScriptContext *context, QScriptEngine *engine);
  1616. @ The script constructor must be passed to the scripting engine.
  1617. @<Set up the scripting engine@>=
  1618. constructor = engine->newFunction(constructQBoxLayout);
  1619. value = engine->newQMetaObject(&QBoxLayout::staticMetaObject, constructor);
  1620. engine->globalObject().setProperty("QBoxLayout", value);
  1621. @ The implementation of these functions should seem familiar by now. Note that
  1622. while a horizontal layout is provided by default, this can be changed from the
  1623. script once the layout is created.
  1624. @<Functions for scripting@>=
  1625. QScriptValue constructQBoxLayout(QScriptContext *, QScriptEngine *engine)
  1626. {
  1627. QScriptValue object =
  1628. engine->newQObject(new QBoxLayout(QBoxLayout::LeftToRight));
  1629. setQBoxLayoutProperties(object, engine);
  1630. return object;
  1631. }
  1632. void setQBoxLayoutProperties(QScriptValue value, QScriptEngine *engine)
  1633. {
  1634. setQLayoutProperties(value, engine);
  1635. value.setProperty("addLayout", engine->newFunction(QBoxLayout_addLayout));
  1636. value.setProperty("addWidget", engine->newFunction(QBoxLayout_addWidget));
  1637. }
  1638. QScriptValue QBoxLayout_addLayout(QScriptContext *context, QScriptEngine *)
  1639. {
  1640. if(context->argumentCount() > 0 && context->argumentCount() < 3)
  1641. {
  1642. QBoxLayout *self = getself<QBoxLayout *>(context);
  1643. QLayout *layout = argument<QLayout *>(0, context);
  1644. int stretch = 0;
  1645. if(context->argumentCount() == 2)
  1646. {
  1647. stretch = argument<int>(1, context);
  1648. }
  1649. if(layout)
  1650. {
  1651. self->addLayout(layout, stretch);
  1652. }
  1653. else
  1654. {
  1655. context->throwError("Incorrect argument type passed to "@|
  1656. "QLayout::addLayout(). This method requires "@|
  1657. "a QLayout.");
  1658. }
  1659. }
  1660. else
  1661. {
  1662. context->throwError("Incorrect number of arguments passed to "@|
  1663. "QLayout::addLayout(). This method takes one "@|
  1664. "QLayout as an argument and optionally one integer.");
  1665. }
  1666. return QScriptValue();
  1667. }
  1668. @ We override the base class wrapper for |addWidget| to add two optional
  1669. arguments: one specifies the stretch factor of the widget and the other
  1670. specifies the alignment of the widget within the layout.
  1671. @<Functions for scripting@>=
  1672. QScriptValue QBoxLayout_addWidget(QScriptContext *context, QScriptEngine *)
  1673. {
  1674. if(context->argumentCount() > 0 && context->argumentCount() < 4)
  1675. {
  1676. QBoxLayout *self = getself<QBoxLayout *>(context);
  1677. QWidget *widget = argument<QWidget *>(0, context);
  1678. int stretch = 0;
  1679. Qt::Alignment alignment = 0;
  1680. if(context->argumentCount() > 1)
  1681. {
  1682. stretch = argument<int>(1, context);
  1683. }
  1684. if(context->argumentCount() > 2)
  1685. {
  1686. alignment = (Qt::Alignment)(argument<int>(2, context));
  1687. }
  1688. if(widget)
  1689. {
  1690. self->addWidget(widget, stretch, alignment);
  1691. }
  1692. else
  1693. {
  1694. context->throwError("Incorrect argument type passed to "@|
  1695. "QBoxLayout::addWidget(). This method requires "@|
  1696. "a QWidget.");
  1697. }
  1698. }
  1699. else
  1700. {
  1701. context->throwError("Incorrect number of arguments passed to "@|
  1702. "QBoxLayout::addWidget(). This method takes one "@|
  1703. "QWidget and optionally up to two integers as "@|
  1704. "arguments.");
  1705. }
  1706. return QScriptValue();
  1707. }
  1708. @* Scripting QAction.
  1709. \noindent The |QAction| class is used in \pn{} to create menu items and respond
  1710. to the selection of these items. Three functions are required for our scripting
  1711. needs with regard to this class.
  1712. @<Function prototypes for scripting@>=
  1713. QScriptValue constructQAction(QScriptContext *context, QScriptEngine *engine);
  1714. QScriptValue QAction_setShortcut(QScriptContext *context,
  1715. QScriptEngine *engine);
  1716. void setQActionProperties(QScriptValue value, QScriptEngine *engine);
  1717. @ The scripting engine must be informed of the constructor.
  1718. @<Set up the scripting engine@>=
  1719. constructor = engine->newFunction(constructQAction);
  1720. value = engine->newQMetaObject(&QAction::staticMetaObject, constructor);
  1721. engine->globalObject().setProperty("QAction", value);
  1722. @ The constructor is simple, however some might sensibly question why the
  1723. |"setShortcut"| property is needed at all. Why not have scripts simply set the
  1724. |shortcut| property of the |QAction| directly? The answer to this is that the
  1725. property expects data of the |QKeySequence| type. While this can be created from
  1726. a |QString|, passing a string to the property through the scripting engine did
  1727. not work at the time this was written.
  1728. @<Functions for scripting@>=
  1729. QScriptValue constructQAction(QScriptContext *, QScriptEngine *engine)
  1730. {
  1731. QScriptValue object = engine->newQObject(new QAction(NULL));
  1732. setQActionProperties(object, engine);
  1733. return object;
  1734. }
  1735. void setQActionProperties(QScriptValue value, QScriptEngine *engine)
  1736. {
  1737. setQObjectProperties(value, engine);
  1738. value.setProperty("setShortcut", engine->newFunction(QAction_setShortcut));
  1739. }
  1740. QScriptValue QAction_setShortcut(QScriptContext *context, QScriptEngine *)
  1741. {
  1742. if(context->argumentCount() == 1)
  1743. {
  1744. QAction *self = getself<@[QAction *@]>(context);
  1745. self->setShortcut(argument<QString>(0, context));
  1746. }
  1747. else
  1748. {
  1749. context->throwError("Incorrect number of arguments passed to "@|
  1750. "QAction::setShortcut(). This method takes one "@|
  1751. "string as an argument.");
  1752. }
  1753. return QScriptValue();
  1754. }
  1755. @* Scripting QFileDialog.
  1756. \noindent |QFileDialog| provides two static member functions which is all that
  1757. we need. The objects returned from these methods are built on the |QDialog|
  1758. abstract base class.
  1759. @<Function prototypes for scripting@>=
  1760. QScriptValue QFileDialog_getOpenFileName(QScriptContext *context,
  1761. QScriptEngine *engine);
  1762. QScriptValue QFileDialog_getSaveFileName(QScriptContext *context,
  1763. QScriptEngine *engine);
  1764. void setQFileDialogProperties(QScriptValue value, QScriptEngine *engine);
  1765. void setQDialogProperties(QScriptValue value, QScriptEngine *engine);
  1766. @ The scripting engine must be informed of the wrapper functions for the static
  1767. methods.
  1768. @<Set up the scripting engine@>=
  1769. value = engine->newQMetaObject(&QFileDialog::staticMetaObject);
  1770. value.setProperty("getOpenFileName",
  1771. engine->newFunction(QFileDialog_getOpenFileName));
  1772. value.setProperty("getSaveFileName",
  1773. engine->newFunction(QFileDialog_getSaveFileName));
  1774. engine->globalObject().setProperty("QFileDialog", value);
  1775. @ This function is just a simple wrapper around the |QFileDialog| method, but
  1776. the object returned has any properties added to the base class available.
  1777. @<Functions for scripting@>=
  1778. QScriptValue QFileDialog_getOpenFileName(QScriptContext *context,
  1779. QScriptEngine *engine)
  1780. {
  1781. QScriptValue retval;
  1782. if(context->argumentCount() == 3)
  1783. {
  1784. QWidget *widget = argument<QWidget *>(0, context);
  1785. if(widget)
  1786. {
  1787. QString caption = argument<QString>(1, context);
  1788. QString dir = argument<QString>(2, context);
  1789. retval = QScriptValue(engine,
  1790. QFileDialog::getOpenFileName(widget, caption,
  1791. dir, "", 0, 0));
  1792. setQFileDialogProperties(retval, engine);
  1793. }
  1794. else
  1795. {
  1796. context->throwError("Incorrect argument type passed to "@|
  1797. "QFileDialog::getOpenFileName(). The first "@|
  1798. "argument to this method must be a QWidget.");
  1799. }
  1800. }
  1801. else
  1802. {
  1803. context->throwError("Incorrect number of arguments passed to "@|
  1804. "QFileDialog::getOpenFileName(). This method "@|
  1805. "takes one QWidget followed by two strings for a "@|
  1806. "total of three arguments.");
  1807. }
  1808. return retval;
  1809. }
  1810. @ Similarly, this just wraps |QFileDialog::getSaveFileName()|.
  1811. @<Functions for scripting@>=
  1812. QScriptValue QFileDialog_getSaveFileName(QScriptContext *context,
  1813. QScriptEngine *engine)
  1814. {
  1815. QScriptValue retval;
  1816. if(context->argumentCount() == 3)
  1817. {
  1818. QWidget *widget = argument<QWidget *>(0, context);
  1819. if(widget)
  1820. {
  1821. QString caption = argument<QString>(1, context);
  1822. QString dir = argument<QString>(2, context);
  1823. retval = QScriptValue(engine,
  1824. QFileDialog::getSaveFileName(widget, caption,
  1825. dir, "", 0, 0));
  1826. setQFileDialogProperties(retval, engine);
  1827. }
  1828. else
  1829. {
  1830. context->throwError("Incorrect argument type passed to "@|
  1831. "QFileDialog::getSaveFileName(). The first "@|
  1832. "argument to this method must be a QWidget.");
  1833. }
  1834. }
  1835. else
  1836. {
  1837. context->throwError("Incorrect number of arguments passed to "@|
  1838. "QFileDialog::getSaveFileName(). This method "@|
  1839. "takes one QWidget followed by two strings for a "@|
  1840. "total of three arguments.");
  1841. }
  1842. return retval;
  1843. }
  1844. @ Adding object hierarchy properties to the objects created above is simple.
  1845. @<Functions for scripting@>=
  1846. void setQFileDialogProperties(QScriptValue value, QScriptEngine *engine)
  1847. {
  1848. setQDialogProperties(value, engine);
  1849. }
  1850. void setQDialogProperties(QScriptValue value, QScriptEngine *engine)
  1851. {
  1852. setQWidgetProperties(value, engine);
  1853. }
  1854. @* Scripting QFile.
  1855. \noindent When using a |QFile| in a script, we only need the constructor and two
  1856. functions for hooking it in with the rest of the object hierarchy.
  1857. @<Function prototypes for scripting@>=
  1858. QScriptValue constructQFile(QScriptContext *context, QScriptEngine *engine);
  1859. void setQFileProperties(QScriptValue value, QScriptEngine *engine);
  1860. QScriptValue QFile_remove(QScriptContext *context, QScriptEngine *engine);
  1861. void setQIODeviceProperties(QScriptValue value, QScriptEngine *engine);
  1862. QScriptValue QIODevice_open(QScriptContext *context, QScriptEngine *engine);
  1863. QScriptValue QIODevice_close(QScriptContext *context, QScriptEngine *engine);
  1864. QScriptValue QIODevice_readToString(QScriptContext *context,
  1865. QScriptEngine *engine);
  1866. QScriptValue QIODevice_putChar(QScriptContext *context, QScriptEngine *engine);
  1867. QScriptValue QIODevice_writeString(QScriptContext *context, QScriptEngine *engine);
  1868. QScriptValue QIODevice_writeBytes(QScriptContext *context, QScriptEngine *engine);
  1869. QScriptValue QIODevice_readBytes(QScriptContext *context, QScriptEngine *engine);
  1870. QScriptValue QIODevice_peek(QScriptContext *context, QScriptEngine *engine);
  1871. QScriptValue QIODevice_read(QScriptContext *context, QScriptEngine *engine);
  1872. @ This function is passed to the scripting engine.
  1873. @<Set up the scripting engine@>=
  1874. constructor = engine->newFunction(constructQFile);
  1875. value = engine->newQMetaObject(&QFile::staticMetaObject, constructor);
  1876. engine->globalObject().setProperty("QFile", value);
  1877. @ The implementation is trivial.
  1878. @<Functions for scripting@>=
  1879. QScriptValue constructQFile(QScriptContext *context, QScriptEngine *engine)
  1880. {
  1881. QScriptValue object =
  1882. engine->newQObject(new QFile(argument<QString>(0, context)));@/
  1883. setQFileProperties(object, engine);
  1884. return object;
  1885. }
  1886. @ |QFile| gets a wrapper around |remove()| to support deleting temporary files.
  1887. @<Functions for scripting@>=
  1888. void setQFileProperties(QScriptValue value, QScriptEngine *engine)
  1889. {
  1890. setQIODeviceProperties(value, engine);
  1891. value.setProperty("remove", engine->newFunction(QFile_remove));
  1892. }
  1893. QScriptValue QFile_remove(QScriptContext *context, QScriptEngine *engine)
  1894. {
  1895. QFile *self = getself<QFile *>(context);
  1896. bool retval = self->remove();
  1897. return QScriptValue(engine, retval);
  1898. }
  1899. @ Although we aren'@q'@>t going to create any instances of |QIODevice| directly,
  1900. subclasses such as |QFile| and |QBuffer| get two additional properties for
  1901. opening and closing the device.
  1902. In order to solve some class interoperability issues, a convenience method is
  1903. also added which is equivalent to creating a |QString| from the |QByteArray|
  1904. returned from the |readAll()| method.
  1905. @<Functions for scripting@>=
  1906. void setQIODeviceProperties(QScriptValue value, QScriptEngine *engine)
  1907. {
  1908. setQObjectProperties(value, engine);
  1909. value.setProperty("open", engine->newFunction(QIODevice_open));
  1910. value.setProperty("close", engine->newFunction(QIODevice_close));
  1911. value.setProperty("readToString",
  1912. engine->newFunction(QIODevice_readToString));
  1913. value.setProperty("putChar", engine->newFunction(QIODevice_putChar));
  1914. value.setProperty("writeString", engine->newFunction(QIODevice_writeString));
  1915. value.setProperty("writeBytes", engine->newFunction(QIODevice_writeBytes));
  1916. value.setProperty("readBytes", engine->newFunction(QIODevice_readBytes));
  1917. value.setProperty("peek", engine->newFunction(QIODevice_peek));
  1918. value.setProperty("read", engine->newFunction(QIODevice_read));
  1919. }
  1920. @ These are simple wrappers. In the case of the |open()| property, one argument
  1921. may be passed specifying the mode used for opening. The supported values for
  1922. this are 1 (Read Only), 2 (Write Only), and 3 (Read Write). If this argument is
  1923. not passed, it is assumed that the user wants read and write access.
  1924. @<Functions for scripting@>=
  1925. QScriptValue QIODevice_open(QScriptContext *context, QScriptEngine *)
  1926. {
  1927. QIODevice *self = getself<QIODevice *>(context);
  1928. bool retval = false;
  1929. if(context->argumentCount() == 1)
  1930. {
  1931. switch(argument<int>(0, context))
  1932. {
  1933. case 1:
  1934. retval = self->open(QIODevice::ReadOnly);
  1935. break;
  1936. case 2:
  1937. retval = self->open(QIODevice::WriteOnly);
  1938. break;
  1939. case 3:
  1940. retval = self->open(QIODevice::ReadWrite);
  1941. break;
  1942. default:
  1943. break;
  1944. }
  1945. }
  1946. else
  1947. {
  1948. retval = self->open(QIODevice::ReadWrite);
  1949. }
  1950. return QScriptValue(retval);
  1951. }
  1952. QScriptValue QIODevice_close(QScriptContext *context, QScriptEngine *)
  1953. {
  1954. QIODevice *self = getself<QIODevice *>(context);
  1955. self->close();
  1956. return QScriptValue();
  1957. }
  1958. @ The |readToString()| method is a simple extension of |QIODevice::readAll()| to
  1959. interface with classes that expect document data in the form of a string. Most
  1960. notably, this includes |QWebView|.
  1961. @<Functions for scripting@>=
  1962. QScriptValue QIODevice_readToString(QScriptContext *context, QScriptEngine *)
  1963. {
  1964. QIODevice *self = getself<QIODevice *>(context);
  1965. self->reset();
  1966. return QScriptValue(QString(self->readAll()));
  1967. }
  1968. @ In support of serial port communications, wrappers around two methods for
  1969. writing data have been added. As these are valid for other classes derived from
  1970. |QIODevice|, they are added here so the functionality is available more
  1971. broadly.
  1972. As we are unable to pass a type that guarantees only a single character, we
  1973. instead accept a string and only pass along the first character.
  1974. @<Functions for scripting@>=
  1975. QScriptValue QIODevice_putChar(QScriptContext *context, QScriptEngine *)
  1976. {
  1977. QIODevice *self = getself<QIODevice *>(context);
  1978. if(context->argumentCount() == 1)
  1979. {
  1980. return QScriptValue(self->putChar(argument<QString>(0, context).toUtf8().at(0)));
  1981. }
  1982. context->throwError("Incorrect number of arguments passed to "@|
  1983. "QIODevice::putChar()");
  1984. return QScriptValue();
  1985. }
  1986. @ Two wrappers are provided around |QIODevice::write()| for outputting
  1987. multi-byte data. If we are writing strings that are valid UTF-8, we can use the
  1988. |writeString| wrapper, but if we require full control over exactly which bytes
  1989. are output, the |writeBytes| wrapper is more appropriate.
  1990. @<Functions for scripting@>=
  1991. QScriptValue QIODevice_writeString(QScriptContext *context, QScriptEngine *)
  1992. {
  1993. QIODevice *self = getself<QIODevice *>(context);
  1994. if(context->argumentCount() == 1)
  1995. {
  1996. self->write(argument<QString>(0, context).toUtf8());
  1997. }
  1998. else
  1999. {
  2000. context->throwError("Incorrect number of arguments passed to "@|
  2001. "QIODevice::writeString()");
  2002. }
  2003. return QScriptValue();
  2004. }
  2005. QScriptValue QIODevice_writeBytes(QScriptContext *context, QScriptEngine *)
  2006. {
  2007. QIODevice *self = getself<QIODevice *>(context);
  2008. if(context->argumentCount() == 1)
  2009. {
  2010. self->write(argument<QByteArray>(0, context));
  2011. }
  2012. else
  2013. {
  2014. context->throwError("Incorrect number of arguments passed to "@|
  2015. "QIODevice::writeBytes()");
  2016. }
  2017. return QScriptValue();
  2018. }
  2019. @ The readBytes method is an alternate wrapper around |QByteArray::readAll()|
  2020. which returns the |QByteArray| instead of converting this to a |QString|.
  2021. @<Functions for scripting@>=
  2022. QScriptValue QIODevice_readBytes(QScriptContext *context, QScriptEngine *engine)
  2023. {
  2024. QIODevice *self = getself<QIODevice *>(context);
  2025. QScriptValue value = engine->toScriptValue<QByteArray>(self->readAll());
  2026. setQByteArrayProperties(value, engine);
  2027. return value;
  2028. }
  2029. @ Wrappers around |peek()| and |read()| are also provided.
  2030. @<Functions for scripting@>=
  2031. QScriptValue QIODevice_peek(QScriptContext *context, QScriptEngine *engine)
  2032. {
  2033. QIODevice *self = getself<QIODevice *>(context);
  2034. QScriptValue value = engine->toScriptValue<QByteArray>(
  2035. self->peek(argument<int>(0, context)));
  2036. setQByteArrayProperties(value, engine);
  2037. return value;
  2038. }
  2039. QScriptValue QIODevice_read(QScriptContext *context, QScriptEngine *engine)
  2040. {
  2041. QIODevice *self = getself<QIODevice *>(context);
  2042. QScriptValue value = engine->toScriptValue<QByteArray>(
  2043. self->read(argument<int>(0, context)));
  2044. setQByteArrayProperties(value, engine);
  2045. return value;
  2046. }
  2047. @* Scripting QProcess.
  2048. \noindent Sometimes it is useful to have \pn work with an external program.
  2049. The initial use case was document generation by typesetting instructions to a
  2050. file and then running \TeX to generate a shelf sign or a sheet of labels.
  2051. Other likely use cases include interfacing with external programs that output
  2052. measurement streams. There are several methods which we may want to expose,
  2053. however this is being done only as needed.
  2054. @<Function prototypes for scripting@>=
  2055. QScriptValue constructQProcess(QScriptContext *context, QScriptEngine *engine);
  2056. void setQProcessProperties(QScriptValue value, QScriptEngine *engine);
  2057. QScriptValue QProcess_execute(QScriptContext *context, QScriptEngine *engine);
  2058. QScriptValue QProcess_startDetached(QScriptContext *context, QScriptEngine *engine);
  2059. QScriptValue QProcess_setWorkingDirectory(QScriptContext *context, QScriptEngine *engine);
  2060. QScriptValue QProcess_start(QScriptContext *context, QScriptEngine *engine);
  2061. @ We follow the same pattern with this as with many other types.
  2062. @<Set up the scripting engine@>=
  2063. constructor = engine->newFunction(constructQProcess);
  2064. value = engine->newQMetaObject(&QProcess::staticMetaObject, constructor);
  2065. engine->globalObject().setProperty("QProcess", value);
  2066. @ The constructor is trivial.
  2067. @<Functions for scripting@>=
  2068. QScriptValue constructQProcess(QScriptContext *, QScriptEngine *engine)
  2069. {
  2070. QScriptValue object = engine->newQObject(new QProcess);
  2071. setQProcessProperties(object, engine);
  2072. return object;
  2073. }
  2074. @ As |QProcess| is a |QIODevice| we inherit some properties from that. We also
  2075. expose some details that are specific to |QProcess|.
  2076. @<Functions for scripting@>=
  2077. void setQProcessProperties(QScriptValue value, QScriptEngine *engine)
  2078. {
  2079. setQIODeviceProperties(value, engine);
  2080. value.setProperty("execute", engine->newFunction(QProcess_execute));
  2081. value.setProperty("startDetached", engine->newFunction(QProcess_startDetached));
  2082. value.setProperty("setWorkingDirectory", engine->newFunction(QProcess_setWorkingDirectory));
  2083. value.setProperty("start", engine->newFunction(QProcess_start));
  2084. }
  2085. @ The |execute()| method comes in two flavors: one with arguments and one without.
  2086. We always call the one with arguments and simply pass in an empty list if no
  2087. arguments are specified.
  2088. @<Functions for scripting@>=
  2089. QScriptValue QProcess_execute(QScriptContext *context, QScriptEngine *)
  2090. {
  2091. QProcess *self = getself<QProcess *>(context);
  2092. QString program = argument<QString>(0, context);
  2093. QStringList arguments = QStringList();
  2094. if(context->argumentCount() > 1) {
  2095. arguments = argument<QVariant>(1, context).toStringList();
  2096. }
  2097. int retval = self->execute(program, arguments);
  2098. return QScriptValue(retval);
  2099. }
  2100. @ Similarly |startDetached()| can be called in a few different ways.
  2101. @<Functions for scripting@>=
  2102. QScriptValue QProcess_startDetached(QScriptContext *context, QScriptEngine *)
  2103. {
  2104. QProcess *self = getself<QProcess *>(context);
  2105. QString program = argument<QString>(0, context);
  2106. QStringList arguments = QStringList();
  2107. if(context->argumentCount() > 1) {
  2108. arguments = argument<QVariant>(1, context).toStringList();
  2109. }
  2110. QString workingDirectory = "";
  2111. if(context->argumentCount() > 2) {
  2112. workingDirectory = argument<QString>(2, context);
  2113. }
  2114. bool retval;
  2115. switch(context->argumentCount())
  2116. {
  2117. case 1:
  2118. retval = self->startDetached(program);
  2119. break;
  2120. case 2:
  2121. retval = self->startDetached(program, arguments);
  2122. break;
  2123. case 3:
  2124. retval = self->startDetached(program, arguments, workingDirectory);
  2125. break;
  2126. default:
  2127. retval = false;
  2128. }
  2129. return QScriptValue(retval);
  2130. }
  2131. @ Sometimes we care about the working directory for our program.
  2132. @<Functions for scripting@>=
  2133. QScriptValue QProcess_setWorkingDirectory(QScriptContext *context, QScriptEngine *)
  2134. {
  2135. QProcess *self = getself<QProcess *>(context);
  2136. QString directory = argument<QString>(0, context);
  2137. self->setWorkingDirectory(directory);
  2138. return QScriptValue();
  2139. }
  2140. @ When using the |start()| method we always assume that we want read and write
  2141. access.
  2142. @<Functions for scripting@>=
  2143. QScriptValue QProcess_start(QScriptContext *context, QScriptEngine *)
  2144. {
  2145. QProcess *self = getself<QProcess *>(context);
  2146. QString program = argument<QString>(0, context);
  2147. QStringList arguments = QStringList();
  2148. if(context->argumentCount() > 1) {
  2149. arguments = argument<QVariant>(1, context).toStringList();
  2150. }
  2151. self->start(program, arguments);
  2152. return QScriptValue();
  2153. }
  2154. @ In order to work with |QByteArray| this should also be exposed to the host
  2155. environment.
  2156. @<Function prototypes for scripting@>=
  2157. QScriptValue QByteArray_toScriptValue(QScriptEngine *engine, const QByteArray &bytes);
  2158. void QByteArray_fromScriptValue(const QScriptValue &value, QByteArray &bytes);
  2159. QScriptValue constructQByteArray(QScriptContext *context, QScriptEngine *engine);
  2160. void setQByteArrayProperties(QScriptValue value, QScriptEngine *engine);
  2161. QScriptValue QByteArray_fromHex(QScriptContext *context, QScriptEngine *engine);
  2162. QScriptValue QByteArray_getAt(QScriptContext *context, QScriptEngine *engine);
  2163. QScriptValue QByteArray_setAt(QScriptContext *context, QScriptEngine *engine);
  2164. QScriptValue QByteArray_appendBytes(QScriptContext *context, QScriptEngine *engine);
  2165. QScriptValue QByteArray_appendString(QScriptContext *context, QScriptEngine *engine);
  2166. QScriptValue QByteArray_size(QScriptContext *context, QScriptEngine *engine);
  2167. QScriptValue QByteArray_left(QScriptContext *context, QScriptEngine *engine);
  2168. QScriptValue QByteArray_right(QScriptContext *context, QScriptEngine *engine);
  2169. QScriptValue QByteArray_mid(QScriptContext *context, QScriptEngine *engine);
  2170. QScriptValue QByteArray_chop(QScriptContext *context, QScriptEngine *engine);
  2171. QScriptValue QByteArray_remove(QScriptContext *context, QScriptEngine *engine);
  2172. QScriptValue QByteArray_toInt8(QScriptContext *context, QScriptEngine *engine);
  2173. QScriptValue QByteArray_toInt16(QScriptContext *context, QScriptEngine *engine);
  2174. QScriptValue QByteArray_toInt32(QScriptContext *context, QScriptEngine *engine);
  2175. QScriptValue QByteArray_toFloat(QScriptContext *context, QScriptEngine *engine);
  2176. QScriptValue QByteArray_toDouble(QScriptContext *context, QScriptEngine *engine);
  2177. @ First, we provide some functionns for moving array data across the
  2178. language barrier.
  2179. @<Functions for scripting@>=
  2180. QScriptValue QByteArray_toScriptValue(QScriptEngine *engine, const QByteArray &bytes)
  2181. {
  2182. QScriptValue object = engine->newVariant(QVariant(bytes));
  2183. setQByteArrayProperties(object, engine);
  2184. return object;
  2185. }
  2186. void QByteArray_fromScriptValue(const QScriptValue &value, QByteArray &bytes)
  2187. {
  2188. bytes = value.toVariant().toByteArray();
  2189. }
  2190. @ We register this our conversion functions and allow creation of new arrays
  2191. next.
  2192. @<Set up the scripting engine@>=
  2193. qScriptRegisterMetaType(engine, QByteArray_toScriptValue, QByteArray_fromScriptValue);
  2194. constructor = engine->newFunction(constructQByteArray);
  2195. engine->globalObject().setProperty("QByteArray", constructor);
  2196. @ The constructor is straightforward.
  2197. @<Functions for scripting@>=
  2198. QScriptValue constructQByteArray(QScriptContext *, QScriptEngine *engine)
  2199. {
  2200. QScriptValue object = engine->toScriptValue<QByteArray>(QByteArray());
  2201. setQByteArrayProperties(object, engine);
  2202. return object;
  2203. }
  2204. @ There are many methods which are not automatically available which we may
  2205. want to have wrappers around. These should be added as required.
  2206. @<Functions for scripting@>=
  2207. void setQByteArrayProperties(QScriptValue value, QScriptEngine *engine)
  2208. {
  2209. value.setProperty("fromHex", engine->newFunction(QByteArray_fromHex));
  2210. value.setProperty("getAt", engine->newFunction(QByteArray_getAt));
  2211. value.setProperty("setAt", engine->newFunction(QByteArray_setAt));
  2212. value.setProperty("appendBytes", engine->newFunction(QByteArray_appendBytes));
  2213. value.setProperty("appendString", engine->newFunction(QByteArray_appendString));
  2214. value.setProperty("size", engine->newFunction(QByteArray_size));
  2215. value.setProperty("left", engine->newFunction(QByteArray_left));
  2216. value.setProperty("right", engine->newFunction(QByteArray_right));
  2217. value.setProperty("mid", engine->newFunction(QByteArray_mid));
  2218. value.setProperty("chop", engine->newFunction(QByteArray_chop));
  2219. value.setProperty("remove", engine->newFunction(QByteArray_remove));
  2220. value.setProperty("toInt8", engine->newFunction(QByteArray_toInt8));
  2221. value.setProperty("toInt16", engine->newFunction(QByteArray_toInt16));
  2222. value.setProperty("toInt32", engine->newFunction(QByteArray_toInt32));
  2223. value.setProperty("toFloat", engine->newFunction(QByteArray_toFloat));
  2224. value.setProperty("toDouble", engine->newFunction(QByteArray_toDouble));
  2225. }
  2226. @ Perhaps the easiest way to deal with fixed byte strings for serial
  2227. communications across script boundaries is to use a hex encoded string.
  2228. @<Functions for scripting@>=
  2229. QScriptValue QByteArray_fromHex(QScriptContext *context, QScriptEngine *engine)
  2230. {
  2231. QByteArray self = getself<QByteArray>(context);
  2232. QByteArray retval;
  2233. retval = self.fromHex(argument<QString>(0, context).toUtf8());
  2234. QScriptValue value = engine->toScriptValue<QByteArray>(retval);
  2235. setQByteArrayProperties(value, engine);
  2236. return value;
  2237. }
  2238. @ A pair of methods is provided for getting and setting values at a particular
  2239. byte.
  2240. @<Functions for scripting@>=
  2241. QScriptValue QByteArray_getAt(QScriptContext *context, QScriptEngine *)
  2242. {
  2243. QByteArray self = getself<QByteArray>(context);
  2244. return QScriptValue((int)(self.at(argument<int>(0, context))));
  2245. }
  2246. QScriptValue QByteArray_setAt(QScriptContext *context, QScriptEngine *)
  2247. {
  2248. QByteArray self = getself<QByteArray>(context);
  2249. self[argument<int>(0, context)] = (char)(argument<int>(1, context));
  2250. return QScriptValue();
  2251. }
  2252. @ Methods are provided for appending either another |QByteArray| or a string
  2253. to a |QByteArray|. The only difference between these functions is the expected
  2254. argument type.
  2255. @<Functions for scripting@>=
  2256. QScriptValue QByteArray_appendBytes(QScriptContext *context, QScriptEngine *engine)
  2257. {
  2258. QByteArray self = getself<QByteArray>(context);
  2259. QScriptValue value =
  2260. engine->toScriptValue<QByteArray>(
  2261. self.append(argument<QByteArray>(0, context)));
  2262. setQByteArrayProperties(value, engine);
  2263. return value;
  2264. }
  2265. QScriptValue QByteArray_appendString(QScriptContext *context, QScriptEngine *engine)
  2266. {
  2267. QByteArray self = getself<QByteArray>(context);
  2268. QScriptValue value = engine->toScriptValue<QByteArray>(
  2269. self.append(argument<QString>(0, context)));
  2270. setQByteArrayProperties(value, engine);
  2271. return value;
  2272. }
  2273. @ Checking the size of our byte array frequently a requirement.
  2274. @<Functions for scripting@>=
  2275. QScriptValue QByteArray_size(QScriptContext *context, QScriptEngine *)
  2276. {
  2277. QByteArray self = getself<QByteArray>(context);
  2278. return QScriptValue(self.size());
  2279. }
  2280. @ It is also frequently useful to be able to work with specific parts of a byte
  2281. array, so a few methods are provided for carving these up.
  2282. @<Functions for scripting@>=
  2283. QScriptValue QByteArray_left(QScriptContext *context, QScriptEngine *engine)
  2284. {
  2285. QByteArray self = getself<QByteArray>(context);
  2286. QScriptValue value = engine->toScriptValue<QByteArray>(
  2287. self.left(argument<int>(0, context)));
  2288. setQByteArrayProperties(value, engine);
  2289. return value;
  2290. }
  2291. QScriptValue QByteArray_right(QScriptContext *context, QScriptEngine *engine)
  2292. {
  2293. QByteArray self = getself<QByteArray>(context);
  2294. QScriptValue value = engine->toScriptValue<QByteArray>(
  2295. self.right(argument<int>(0, context)));
  2296. setQByteArrayProperties(value, engine);
  2297. return value;
  2298. }
  2299. QScriptValue QByteArray_mid(QScriptContext *context, QScriptEngine *engine)
  2300. {
  2301. QByteArray self = getself<QByteArray>(context);
  2302. int length = -1;
  2303. if(context->argumentCount() > 1)
  2304. {
  2305. length = argument<int>(1, context);
  2306. }
  2307. QScriptValue value = engine->toScriptValue<QByteArray>(
  2308. self.mid(argument<int>(0, context), length));
  2309. setQByteArrayProperties(value, engine);
  2310. return value;
  2311. }
  2312. @ We may also want to remove bytes from an array.
  2313. @<Functions for scripting@>=
  2314. QScriptValue QByteArray_chop(QScriptContext *context, QScriptEngine *)
  2315. {
  2316. QByteArray self = getself<QByteArray>(context);
  2317. self.chop(argument<int>(0, context));
  2318. return QScriptValue();
  2319. }
  2320. QScriptValue QByteArray_remove(QScriptContext *context, QScriptEngine *engine)
  2321. {
  2322. QByteArray self = getself<QByteArray>(context);
  2323. QScriptValue value = engine->toScriptValue<QByteArray>(
  2324. self.remove(argument<int>(0, context), argument<int>(1, context)));
  2325. setQByteArrayProperties(value, engine);
  2326. return value;
  2327. }
  2328. @ When receiving data in a byte array, bytes are sometimes intended to
  2329. represent 8, 16, or 32 bit integers. In such cases we often want to perform
  2330. some computation on these values so having the ability to split off that
  2331. portion of the array (for example, with |mid()|) and convert to a Number is
  2332. useful.
  2333. @<Functions for scripting@>=
  2334. QScriptValue QByteArray_toInt8(QScriptContext *context, QScriptEngine *)
  2335. {
  2336. QByteArray self = getself<QByteArray>(context);
  2337. int value = 0;
  2338. char *bytes = (char *)&value;
  2339. bytes[0] = self[0];
  2340. return QScriptValue(value);
  2341. }
  2342. QScriptValue QByteArray_toInt16(QScriptContext *context, QScriptEngine *)
  2343. {
  2344. QByteArray self = getself<QByteArray>(context);
  2345. int value = 0;
  2346. char *bytes = (char *)&value;
  2347. bytes[0] = self[0];
  2348. bytes[1] = self[1];
  2349. return QScriptValue(value);
  2350. }
  2351. QScriptValue QByteArray_toInt32(QScriptContext *context, QScriptEngine *)
  2352. {
  2353. QByteArray self = getself<QByteArray>(context);
  2354. int value = 0;
  2355. char *bytes = (char *)&value;
  2356. bytes[0] = self[0];
  2357. bytes[1] = self[1];
  2358. bytes[2] = self[2];
  2359. bytes[3] = self[3];
  2360. return QScriptValue(value);
  2361. }
  2362. @ Similar methods are provided for converting bytes to a |float| or |double|.
  2363. Note that the return value from |toFloat| will, in the host environment, be
  2364. represented as a |double|.
  2365. @<Functions for scripting@>=
  2366. QScriptValue QByteArray_toFloat(QScriptContext *context, QScriptEngine *)
  2367. {
  2368. QByteArray self = getself<QByteArray>(context);
  2369. float value = 0.0;
  2370. char *bytes = (char *)&value;
  2371. bytes[0] = self[0];
  2372. bytes[1] = self[1];
  2373. bytes[2] = self[2];
  2374. bytes[3] = self[3];
  2375. return QScriptValue(value);
  2376. }
  2377. QScriptValue QByteArray_toDouble(QScriptContext *context, QScriptEngine *)
  2378. {
  2379. QByteArray self = getself<QByteArray>(context);
  2380. double value = 0.0;
  2381. char *bytes = (char *)&value;
  2382. bytes[0] = self[0];
  2383. bytes[1] = self[1];
  2384. bytes[2] = self[2];
  2385. bytes[3] = self[3];
  2386. bytes[4] = self[4];
  2387. bytes[5] = self[5];
  2388. bytes[6] = self[6];
  2389. bytes[7] = self[7];
  2390. return QScriptValue(value);
  2391. }
  2392. @ Some protocols require manipulating larger than 8 bit numbers as a sequence
  2393. of bytes. To facilitate this, methods are provided to construct a |QByteArray|
  2394. from different sized numbers. 8 bit numbers are provided for uniformity.
  2395. @<Function prototypes for scripting@>=
  2396. QScriptValue bytesFromInt8(QScriptContext *context, QScriptEngine *engine);
  2397. QScriptValue bytesFromInt16(QScriptContext *context, QScriptEngine *engine);
  2398. QScriptValue bytesFromInt32(QScriptContext *context, QScriptEngine *engine);
  2399. QScriptValue bytesFromFloat(QScriptContext *context, QScriptEngine *engine);
  2400. QScriptValue bytesFromDouble(QScriptContext *context, QScriptEngine *engine);
  2401. @ These are globally available.
  2402. @<Set up the scripting engine@>=
  2403. engine->globalObject().setProperty("bytesFromInt8", engine->newFunction(bytesFromInt8));
  2404. engine->globalObject().setProperty("bytesFromInt16", engine->newFunction(bytesFromInt16));
  2405. engine->globalObject().setProperty("bytesFromInt32", engine->newFunction(bytesFromInt32));
  2406. engine->globalObject().setProperty("bytesFromFloat", engine->newFunction(bytesFromFloat));
  2407. engine->globalObject().setProperty("bytesFromDouble", engine->newFunction(bytesFromDouble));
  2408. @ The methods all work by casting the appropriate numeric type to a |char *|
  2409. and copying the bytes to a new |QByteArray|. Note that the ECMA-262 standard
  2410. only has one type of number and this is an IEEE 754 binary64 double precision
  2411. floating point number. Functions other than |bytesFromDouble| will be cast
  2412. from |double|.
  2413. @<Functions for scripting@>=
  2414. QScriptValue bytesFromInt8(QScriptContext *context, QScriptEngine *engine)
  2415. {
  2416. qint8 value = (qint8)(argument<int>(0, context));
  2417. char *bytes = (char *)&value;
  2418. QByteArray retval;
  2419. retval.resize(1);
  2420. retval[0] = bytes[0];
  2421. QScriptValue v = engine->toScriptValue<QByteArray>(retval);
  2422. setQByteArrayProperties(v, engine);
  2423. return v;
  2424. }
  2425. QScriptValue bytesFromInt16(QScriptContext *context, QScriptEngine *engine)
  2426. {
  2427. qint16 value = (qint16)(argument<int>(0, context));
  2428. char *bytes = (char *)&value;
  2429. QByteArray retval;
  2430. retval.resize(2);
  2431. retval[0] = bytes[0];
  2432. retval[1] = bytes[1];
  2433. QScriptValue v = engine->toScriptValue<QByteArray>(retval);
  2434. setQByteArrayProperties(v, engine);
  2435. return v;
  2436. }
  2437. QScriptValue bytesFromInt32(QScriptContext *context, QScriptEngine *engine)
  2438. {
  2439. qint32 value = (qint32)(argument<int>(0, context));
  2440. char *bytes = (char *)&value;
  2441. QByteArray retval;
  2442. retval.resize(4);
  2443. retval[0] = bytes[0];
  2444. retval[1] = bytes[1];
  2445. retval[2] = bytes[2];
  2446. retval[3] = bytes[3];
  2447. QScriptValue v = engine->toScriptValue<QByteArray>(retval);
  2448. setQByteArrayProperties(v, engine);
  2449. return v;
  2450. }
  2451. QScriptValue bytesFromFloat(QScriptContext *context, QScriptEngine *engine)
  2452. {
  2453. float value = (float)(argument<double>(0, context));
  2454. char *bytes = (char *)&value;
  2455. QByteArray retval;
  2456. retval.resize(4);
  2457. retval[0] = bytes[0];
  2458. retval[1] = bytes[1];
  2459. retval[2] = bytes[2];
  2460. retval[3] = bytes[3];
  2461. QScriptValue v = engine->toScriptValue<QByteArray>(retval);
  2462. setQByteArrayProperties(v, engine);
  2463. return v;
  2464. }
  2465. QScriptValue bytesFromDouble(QScriptContext *context, QScriptEngine *engine)
  2466. {
  2467. double value = (double)(argument<double>(0, context));
  2468. char *bytes = (char *)&value;
  2469. QByteArray retval;
  2470. retval.resize(8);
  2471. retval[0] = bytes[0];
  2472. retval[1] = bytes[1];
  2473. retval[2] = bytes[2];
  2474. retval[3] = bytes[3];
  2475. retval[4] = bytes[4];
  2476. retval[5] = bytes[5];
  2477. retval[6] = bytes[6];
  2478. retval[7] = bytes[7];
  2479. QScriptValue v = engine->toScriptValue<QByteArray>(retval);
  2480. setQByteArrayProperties(v, engine);
  2481. return v;
  2482. }
  2483. @* Scripting QBuffer.
  2484. \noindent Sometimes it is desirable to load a roast profile from a file. At
  2485. other times, it is more useful to load that profile from a byte array stored in
  2486. a database. The |XMLInput| class takes data from a |QIODevice| object, which
  2487. means that we can choose from a |QFile| when we want the former or a |QBuffer|
  2488. when we want the latter.
  2489. @<Function prototypes for scripting@>=
  2490. QScriptValue constructQBuffer(QScriptContext *context, QScriptEngine *engine);
  2491. void setQBufferProperties(QScriptValue value, QScriptEngine *engine);
  2492. QScriptValue QBuffer_setData(QScriptContext *context, QScriptEngine *engine);
  2493. QScriptValue QBuffer_data(QScriptContext *context, QScriptEngine *engine);
  2494. @ The host environment needs to be aware of the constructor.
  2495. @<Set up the scripting engine@>=
  2496. constructor = engine->newFunction(constructQBuffer);
  2497. value = engine->newQMetaObject(&QBuffer::staticMetaObject, constructor);
  2498. engine->globalObject().setProperty("QBuffer", value);
  2499. @ The implementation is trivial.
  2500. @<Functions for scripting@>=
  2501. QScriptValue constructQBuffer(QScriptContext *context, QScriptEngine *engine)
  2502. {
  2503. QByteArray *array = new QByteArray(argument<QString>(0, context).toAscii());
  2504. QScriptValue object = engine->newQObject(new QBuffer(array));
  2505. setQBufferProperties(object, engine);
  2506. return object;
  2507. }
  2508. void setQBufferProperties(QScriptValue value, QScriptEngine *engine)
  2509. {
  2510. setQIODeviceProperties(value, engine);
  2511. value.setProperty("setData", engine->newFunction(QBuffer_setData));
  2512. value.setProperty("data", engine->newFunction(QBuffer_data));
  2513. }
  2514. QScriptValue QBuffer_setData(QScriptContext *context, QScriptEngine *)
  2515. {
  2516. QBuffer *self = getself<QBuffer *>(context);
  2517. self->setData(argument<QString>(0, context).toAscii());
  2518. return QScriptValue();
  2519. }
  2520. QScriptValue QBuffer_data(QScriptContext *context, QScriptEngine *)
  2521. {
  2522. QBuffer *self = getself<QBuffer *>(context);
  2523. return QScriptValue(QString(self->data()));
  2524. }
  2525. @* Scripting QXmlQuery.
  2526. \noindent Sometimes we have some XML data in a file or a buffer and we would
  2527. like to extract certain information from that data in the host environment.
  2528. Rather than write complicated string manipulation routines in an attempt to deal
  2529. with this sensibly, we can use the XQuery language to extract the information we
  2530. want. One common use case for this is extracting all measurements from a roast
  2531. profile that are associated with an annotation.
  2532. @<Function prototypes for scripting@>=
  2533. QScriptValue constructXQuery(QScriptContext *context, QScriptEngine *engine);
  2534. QScriptValue XQuery_bind(QScriptContext *context, QScriptEngine *engine);
  2535. QScriptValue XQuery_exec(QScriptContext *context, QScriptEngine *engine);
  2536. QScriptValue XQuery_setQuery(QScriptContext *context, QScriptEngine *engine);
  2537. QScriptValue XQuery_invalidate(QScriptContext *context, QScriptEngine *engine);
  2538. void setXQueryProperties(QScriptValue value, QScriptEngine *engine);
  2539. @ The constructor must be registered with the host environment. This is done a
  2540. bit differently from most classes as |QXmlQuery| is not a |QObject|.
  2541. @<Set up the scripting engine@>=
  2542. constructor = engine->newFunction(constructXQuery);
  2543. engine->globalObject().setProperty("XQuery", constructor);
  2544. @ The constructor just needs to make sure the functions we want to make
  2545. available are applied. A method is also provided to free the |QXmlQuery|.
  2546. @<Functions for scripting@>=
  2547. QScriptValue constructXQuery(QScriptContext *, QScriptEngine *engine)
  2548. {
  2549. QScriptValue object = engine->toScriptValue<void *>(new QXmlQuery);
  2550. setXQueryProperties(object, engine);
  2551. return object;
  2552. }
  2553. QScriptValue XQuery_invalidate(QScriptContext *context, QScriptEngine *)
  2554. {
  2555. QXmlQuery *self = getself<QXmlQuery *>(context);
  2556. delete self;
  2557. return QScriptValue();
  2558. }
  2559. void setXQueryProperties(QScriptValue value, QScriptEngine *engine)
  2560. {
  2561. value.setProperty("bind", engine->newFunction(XQuery_bind));
  2562. value.setProperty("exec", engine->newFunction(XQuery_exec));
  2563. value.setProperty("setQuery", engine->newFunction(XQuery_setQuery));
  2564. value.setProperty("invalidate", engine->newFunction(XQuery_invalidate));
  2565. }
  2566. @ The |bind()| property can be used to specify a |QIODevice| to be referenced by
  2567. a variable within a query.
  2568. @<Functions for scripting@>=
  2569. QScriptValue XQuery_bind(QScriptContext *context, QScriptEngine *)
  2570. {
  2571. QXmlQuery *self = getself<QXmlQuery *>(context);
  2572. QIODevice *buffer = argument<QIODevice *>(1, context);
  2573. self->bindVariable(argument<QString>(0, context), buffer);
  2574. return QScriptValue();
  2575. }
  2576. @ A method is also required for setting the query we wish to conduct.
  2577. @<Functions for scripting@>=
  2578. QScriptValue XQuery_setQuery(QScriptContext *context, QScriptEngine *)
  2579. {
  2580. QXmlQuery *self = getself<QXmlQuery *>(context);
  2581. self->setQuery(argument<QString>(0, context));
  2582. return QScriptValue();
  2583. }
  2584. @ This method runs the previously specified query.
  2585. @<Functions for scripting@>=
  2586. QScriptValue XQuery_exec(QScriptContext *context, QScriptEngine *)
  2587. {
  2588. QXmlQuery *self = getself<QXmlQuery *>(context);
  2589. QString result;
  2590. self->evaluateTo(&result);
  2591. return QScriptValue(result);
  2592. }
  2593. @* Scripting QXmlStreamWriter.
  2594. \noindent There are some cases where it may be desirable to produce XML from the
  2595. host environment. While there are several ways to accomplish this, the
  2596. |QXmlStreamWriter| class greatly simplifies generating complex XML documents.
  2597. This class is not related to |QObject|, so several functions are needed to
  2598. expose the functionality of this class to the host environment.
  2599. @<Function prototypes for scripting@>=
  2600. QScriptValue constructXmlWriter(QScriptContext *context, QScriptEngine *engine);
  2601. QScriptValue XmlWriter_setDevice(QScriptContext *context,
  2602. QScriptEngine *engine);
  2603. QScriptValue XmlWriter_writeAttribute(QScriptContext *context,
  2604. QScriptEngine *engine);
  2605. QScriptValue XmlWriter_writeCDATA(QScriptContext *context,
  2606. QScriptEngine *engine);
  2607. QScriptValue XmlWriter_writeCharacters(QScriptContext *context,
  2608. QScriptEngine *engine);
  2609. QScriptValue XmlWriter_writeDTD(QScriptContext *context, QScriptEngine *engine);
  2610. QScriptValue XmlWriter_writeEmptyElement(QScriptContext *context,
  2611. QScriptEngine *engine);
  2612. QScriptValue XmlWriter_writeEndDocument(QScriptContext *context,
  2613. QScriptEngine *engine);
  2614. QScriptValue XmlWriter_writeEndElement(QScriptContext *context,
  2615. QScriptEngine *engine);
  2616. QScriptValue XmlWriter_writeEntityReference(QScriptContext *context,
  2617. QScriptEngine *engine);
  2618. QScriptValue XmlWriter_writeProcessingInstruction(QScriptContext *context,
  2619. QScriptEngine *engine);
  2620. QScriptValue XmlWriter_writeStartDocument(QScriptContext *context,
  2621. QScriptEngine *engine);
  2622. QScriptValue XmlWriter_writeStartElement(QScriptContext *context,
  2623. QScriptEngine *engine);
  2624. QScriptValue XmlWriter_writeTextElement(QScriptContext *context,
  2625. QScriptEngine *engine);
  2626. void setXmlWriterProperties(QScriptValue value, QScriptEngine *engine);
  2627. @ The constructor must be registered with the host environment.
  2628. @<Set up the scripting engine@>=
  2629. constructor = engine->newFunction(constructXmlWriter);
  2630. engine->globalObject().setProperty("XmlWriter", constructor);
  2631. @ The constructor takes an optional argument allowing the output device to be
  2632. specified.
  2633. @<Functions for scripting@>=
  2634. QScriptValue constructXmlWriter(QScriptContext *context, QScriptEngine *engine)
  2635. {
  2636. QXmlStreamWriter *retval;
  2637. if(context->argumentCount() == 1)
  2638. {
  2639. retval = new QXmlStreamWriter(argument<QIODevice *>(0, context));
  2640. }
  2641. else
  2642. {
  2643. retval = new QXmlStreamWriter;
  2644. }
  2645. QScriptValue object = engine->toScriptValue<void *>(retval);
  2646. setXmlWriterProperties(object, engine);
  2647. return object;
  2648. }
  2649. void setXmlWriterProperties(QScriptValue value, QScriptEngine *engine)
  2650. {
  2651. value.setProperty("setDevice", engine->newFunction(XmlWriter_setDevice));
  2652. value.setProperty("writeAttribute",
  2653. engine->newFunction(XmlWriter_writeAttribute));
  2654. value.setProperty("writeCDATA", engine->newFunction(XmlWriter_writeCDATA));
  2655. value.setProperty("writeCharacters",
  2656. engine->newFunction(XmlWriter_writeCharacters));
  2657. value.setProperty("writeDTD", engine->newFunction(XmlWriter_writeDTD));
  2658. value.setProperty("writeEmptyElement",
  2659. engine->newFunction(XmlWriter_writeEmptyElement));
  2660. value.setProperty("writeEndDocument",
  2661. engine->newFunction(XmlWriter_writeEndDocument));
  2662. value.setProperty("writeEndElement",
  2663. engine->newFunction(XmlWriter_writeEndElement));
  2664. value.setProperty("writeEntityReference",
  2665. engine->newFunction(XmlWriter_writeEntityReference));
  2666. value.setProperty("writeProcessingInstruction",
  2667. engine->newFunction(XmlWriter_writeProcessingInstruction));
  2668. value.setProperty("writeStartDocument",
  2669. engine->newFunction(XmlWriter_writeStartDocument));
  2670. value.setProperty("writeStartElement",
  2671. engine->newFunction(XmlWriter_writeStartElement));
  2672. value.setProperty("writeTextElement",
  2673. engine->newFunction(XmlWriter_writeTextElement));
  2674. }
  2675. @ If the output device needs to be changed or if one is not passed to the
  2676. constructor, the |setDevice()| method can be used.
  2677. @<Functions for scripting@>=
  2678. QScriptValue XmlWriter_setDevice(QScriptContext *context, QScriptEngine *)
  2679. {
  2680. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2681. QIODevice *device = argument<QIODevice *>(0, context);
  2682. self->setDevice(device);
  2683. return QScriptValue();
  2684. }
  2685. @ The remaining functions are simple wrappers used for writing various types of
  2686. data. After creating a writer and setting the output device, the start of the
  2687. document should be written. One argument is required containing the XML version
  2688. number. Another function handles writing the end of the document.
  2689. @<Functions for scripting@>=
  2690. QScriptValue XmlWriter_writeStartDocument(QScriptContext *context,
  2691. QScriptEngine *)
  2692. {
  2693. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2694. self->writeStartDocument(argument<QString>(0, context));
  2695. return QScriptValue();
  2696. }
  2697. QScriptValue XmlWriter_writeEndDocument(QScriptContext *context,
  2698. QScriptEngine *)
  2699. {
  2700. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2701. self->writeEndDocument();
  2702. return QScriptValue();
  2703. }
  2704. @ After the start of the document, a DTD is commonly needed.
  2705. @<Functions for scripting@>=
  2706. QScriptValue XmlWriter_writeDTD(QScriptContext *context, QScriptEngine *)
  2707. {
  2708. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2709. self->writeDTD(argument<QString>(0, context));
  2710. return QScriptValue();
  2711. }
  2712. @ After this, elements need to be written. For this, we write the start
  2713. element, any attributes needed, character data, and the end element.
  2714. @<Functions for scripting@>=
  2715. QScriptValue XmlWriter_writeStartElement(QScriptContext *context,
  2716. QScriptEngine *)
  2717. {
  2718. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2719. self->writeStartElement(argument<QString>(0, context));
  2720. return QScriptValue();
  2721. }
  2722. QScriptValue XmlWriter_writeAttribute(QScriptContext *context, QScriptEngine *)
  2723. {
  2724. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2725. self->writeAttribute(argument<QString>(0, context),
  2726. argument<QString>(1, context));
  2727. return QScriptValue();
  2728. }
  2729. QScriptValue XmlWriter_writeCharacters(QScriptContext *context, QScriptEngine *)
  2730. {
  2731. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2732. self->writeCharacters(argument<QString>(0, context));
  2733. return QScriptValue();
  2734. }
  2735. QScriptValue XmlWriter_writeEndElement(QScriptContext *context, QScriptEngine *)
  2736. {
  2737. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2738. self->writeEndElement();
  2739. return QScriptValue();
  2740. }
  2741. @ For convenience, two other methods are provided for writing elements. Elements
  2742. which do not require anything between the start and end elements can be created
  2743. with |writeEmptyElement()|. Elements which do not require attributes, but do
  2744. contain text can be created with |writeTextElement()|.
  2745. @<Functions for scripting@>=
  2746. QScriptValue XmlWriter_writeEmptyElement(QScriptContext *context,
  2747. QScriptEngine *)
  2748. {
  2749. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2750. self->writeEmptyElement(argument<QString>(0, context));
  2751. return QScriptValue();
  2752. }
  2753. QScriptValue XmlWriter_writeTextElement(QScriptContext *context,
  2754. QScriptEngine *)
  2755. {
  2756. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2757. self->writeTextElement(argument<QString>(0, context),
  2758. argument<QString>(1, context));
  2759. return QScriptValue();
  2760. }
  2761. @ Less commonly needed are functions for writing CDATA sections, entity
  2762. references, and processing instructions.
  2763. @<Functions for scripting@>=
  2764. QScriptValue XmlWriter_writeCDATA(QScriptContext *context, QScriptEngine *)
  2765. {
  2766. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2767. self->writeCDATA(argument<QString>(0, context));
  2768. return QScriptValue();
  2769. }
  2770. QScriptValue XmlWriter_writeEntityReference(QScriptContext *context,
  2771. QScriptEngine *)
  2772. {
  2773. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2774. self->writeEntityReference(argument<QString>(0, context));
  2775. return QScriptValue();
  2776. }
  2777. QScriptValue XmlWriter_writeProcessingInstruction(QScriptContext *context,
  2778. QScriptEngine *)
  2779. {
  2780. QXmlStreamWriter *self = getself<QXmlStreamWriter *>(context);
  2781. self->writeProcessingInstruction(argument<QString>(0, context),
  2782. argument<QString>(1, context));
  2783. return QScriptValue();
  2784. }
  2785. @* Scripting QXmlStreamReader.
  2786. \noindent When a serializer is written using |QXmlStreamWriter|, a corresponding
  2787. deserializer should also be written. While there are several possible ways to do
  2788. this, using |QXmlStreamReader| is often the best choice. \pn{} provides a subset
  2789. of the functionality from this class which should be adequate for most purposes.
  2790. @<Function prototypes for scripting@>=
  2791. QScriptValue constructXmlReader(QScriptContext *context, QScriptEngine *engine);
  2792. QScriptValue XmlReader_atEnd(QScriptContext *context, QScriptEngine *engine);
  2793. QScriptValue XmlReader_attribute(QScriptContext *context,
  2794. QScriptEngine *engine);
  2795. QScriptValue XmlReader_hasAttribute(QScriptContext *context,
  2796. QScriptEngine *engine);
  2797. QScriptValue XmlReader_isDTD(QScriptContext *context, QScriptEngine *engine);
  2798. QScriptValue XmlReader_isStartElement(QScriptContext *context,
  2799. QScriptEngine *engine);
  2800. QScriptValue XmlReader_name(QScriptContext *context, QScriptEngine *engine);
  2801. QScriptValue XmlReader_readElementText(QScriptContext *context,
  2802. QScriptEngine *engine);
  2803. QScriptValue XmlReader_readNext(QScriptContext *context, QScriptEngine *engine);
  2804. QScriptValue XmlReader_text(QScriptContext *context, QScriptEngine *engine);
  2805. void setXmlReaderProperties(QScriptValue value, QScriptEngine *engine);
  2806. @ The constructor must be registered with the host environment.
  2807. @<Set up the scripting engine@>=
  2808. constructor = engine->newFunction(constructXmlReader);
  2809. engine->globalObject().setProperty("XmlReader", constructor);
  2810. @ The constructor requires an argument specifying the output device. This can be
  2811. any |QIODevice|. The |open()| method must be called on the device before passing
  2812. it as an argument to this function.
  2813. @<Functions for scripting@>=
  2814. QScriptValue constructXmlReader(QScriptContext *context, QScriptEngine *engine)
  2815. {
  2816. QXmlStreamReader *retval =
  2817. new QXmlStreamReader(argument<QIODevice *>(0, context));
  2818. QScriptValue object = engine->toScriptValue<void *>(retval);
  2819. setXmlReaderProperties(object, engine);
  2820. return object;
  2821. }
  2822. void setXmlReaderProperties(QScriptValue value, QScriptEngine *engine)
  2823. {
  2824. value.setProperty("atEnd", engine->newFunction(XmlReader_atEnd));
  2825. value.setProperty("attribute", engine->newFunction(XmlReader_attribute));
  2826. value.setProperty("hasAttribute",
  2827. engine->newFunction(XmlReader_hasAttribute));
  2828. value.setProperty("isDTD", engine->newFunction(XmlReader_isDTD));
  2829. value.setProperty("isStartElement",
  2830. engine->newFunction(XmlReader_isStartElement));
  2831. value.setProperty("name", engine->newFunction(XmlReader_name));
  2832. value.setProperty("readElementText",
  2833. engine->newFunction(XmlReader_readElementText));
  2834. value.setProperty("readNext",
  2835. engine->newFunction(XmlReader_readNext));
  2836. value.setProperty("text", engine->newFunction(XmlReader_text));
  2837. }
  2838. @ Most of the functions are simple member function wrappers. Two of these
  2839. properties are not. These are the |attribute()| and |hasAttribute()| properties.
  2840. @<Functions for scripting@>=
  2841. QScriptValue XmlReader_attribute(QScriptContext *context, QScriptEngine *)
  2842. {
  2843. QXmlStreamReader *self = getself<QXmlStreamReader *>(context);
  2844. QString retval =
  2845. self->attributes().value(argument<QString>(0, context)).toString();
  2846. return QScriptValue(retval);
  2847. }
  2848. QScriptValue XmlReader_hasAttribute(QScriptContext *context, QScriptEngine *)
  2849. {
  2850. QXmlStreamReader *self = getself<QXmlStreamReader *>(context);
  2851. bool retval =
  2852. self->attributes().hasAttribute(argument<QString>(0, context));
  2853. return QScriptValue(retval);
  2854. }
  2855. @ Other properties can be used for determining how to proceed with the
  2856. processing.
  2857. @<Functions for scripting@>=
  2858. QScriptValue XmlReader_atEnd(QScriptContext *context, QScriptEngine *)
  2859. {
  2860. QXmlStreamReader *self = getself<QXmlStreamReader *>(context);
  2861. return QScriptValue(self->atEnd());
  2862. }
  2863. QScriptValue XmlReader_isDTD(QScriptContext *context, QScriptEngine *)
  2864. {
  2865. QXmlStreamReader *self = getself<QXmlStreamReader *>(context);
  2866. return QScriptValue(self->isDTD());
  2867. }
  2868. QScriptValue XmlReader_isStartElement(QScriptContext *context, QScriptEngine *)
  2869. {
  2870. QXmlStreamReader *self = getself<QXmlStreamReader *>(context);
  2871. return QScriptValue(self->isStartElement());
  2872. }
  2873. @ We move from one element to the next with the |readNext()| property.
  2874. @<Functions for scripting@>=
  2875. QScriptValue XmlReader_readNext(QScriptContext *context, QScriptEngine *)
  2876. {
  2877. QXmlStreamReader *self = getself<QXmlStreamReader *>(context);
  2878. self->readNext();
  2879. return QScriptValue();
  2880. }
  2881. @ The remaining properties return the element name and text.
  2882. @<Functions for scripting@>=
  2883. QScriptValue XmlReader_name(QScriptContext *context, QScriptEngine *)
  2884. {
  2885. QXmlStreamReader *self = getself<QXmlStreamReader *>(context);
  2886. return QScriptValue(self->name().toString());
  2887. }
  2888. QScriptValue XmlReader_readElementText(QScriptContext *context, QScriptEngine *)
  2889. {
  2890. QXmlStreamReader *self = getself<QXmlStreamReader *>(context);
  2891. return QScriptValue(self->readElementText());
  2892. }
  2893. QScriptValue XmlReader_text(QScriptContext *context, QScriptEngine *)
  2894. {
  2895. QXmlStreamReader *self = getself<QXmlStreamReader *>(context);
  2896. return QScriptValue(self->text().toString());
  2897. }
  2898. @* Scripting QSettings.
  2899. \noindent Rather than have a script create a |QSettings| object when it needs to
  2900. save or load settings, the object is provided along with properties for getting
  2901. and setting values. Two functions are needed for this along with a third which
  2902. ensures any properties added to |QObject| are also available to |QSettings| from
  2903. the host environment.
  2904. @<Function prototypes for scripting@>=
  2905. QScriptValue QSettings_value(QScriptContext *context, QScriptEngine *engine);
  2906. QScriptValue QSettings_setValue(QScriptContext *context, QScriptEngine *engine);
  2907. void setQSettingsProperties(QScriptValue value, QScriptEngine *engine);
  2908. @ The object with properties for these functions is passed to the scripting
  2909. engine.
  2910. @<Set up the scripting engine@>=
  2911. value = engine->newQObject(&settings);
  2912. setQSettingsProperties(value, engine);
  2913. engine->globalObject().setProperty("QSettings", value);
  2914. @ Adding properties to the |QSettings| object should seem familiar.
  2915. @<Functions for scripting@>=
  2916. void setQSettingsProperties(QScriptValue value, QScriptEngine *engine)
  2917. {
  2918. setQObjectProperties(value, engine);
  2919. value.setProperty("value", engine->newFunction(QSettings_value));
  2920. value.setProperty("setValue", engine->newFunction(QSettings_setValue));
  2921. }
  2922. @ When getting a value from saved settings, there is the possibility that there
  2923. will not be a value saved for the requested key. An optional second argument can
  2924. be used to supply a default value.
  2925. @<Functions for scripting@>=
  2926. QScriptValue QSettings_value(QScriptContext *context, QScriptEngine *engine)
  2927. {
  2928. QScriptValue object;
  2929. if(context->argumentCount() == 1 || context->argumentCount() == 2)
  2930. {
  2931. QSettings settings;
  2932. QString key = argument<QString>(0, context);
  2933. QVariant value;
  2934. QVariant retval;
  2935. if(context->argumentCount() > 1)
  2936. {
  2937. value = argument<QVariant>(1, context);
  2938. retval = settings.value(key, value);
  2939. }
  2940. else
  2941. {
  2942. retval = settings.value(key);
  2943. }
  2944. object = engine->newVariant(retval);
  2945. }
  2946. else
  2947. {
  2948. context->throwError("Incorrect number of arguments passed to "@|
  2949. "QSettings::value(). This method takes one "@|
  2950. "string and one optional variant type.");
  2951. }
  2952. return object;
  2953. }
  2954. QScriptValue QSettings_setValue(QScriptContext *context, QScriptEngine *)
  2955. {
  2956. if(context->argumentCount() == 2)
  2957. {
  2958. QSettings settings;
  2959. QString key = argument<QString>(0, context);
  2960. QVariant value = argument<QVariant>(1, context);
  2961. settings.setValue(key, value);
  2962. }
  2963. else
  2964. {
  2965. context->throwError("Incorrect number of arguments passed to "@|
  2966. "QSettings::setValue(). This method takes one "@|
  2967. "string and one variant type for a total of two "@|
  2968. "arguments.");
  2969. }
  2970. return QScriptValue();
  2971. }
  2972. @* Scripting QLCDNumber.
  2973. \noindent |QLCDNumber| is used as a base class for \pn{}'@q'@>s |TemperatureDisplay|
  2974. and |TimerDisplay| classes, but it can also be used on its own for the display
  2975. of mainly numeric information.
  2976. @<Function prototypes for scripting@>=
  2977. QScriptValue constructQLCDNumber(QScriptContext *context,
  2978. QScriptEngine *engine);
  2979. void setQLCDNumberProperties(QScriptValue value, QScriptEngine *engine);
  2980. @ The constructor must be passed to the scripting engine.
  2981. @<Set up the scripting engine@>=
  2982. constructor = engine->newFunction(constructQLCDNumber);
  2983. value = engine->newQMetaObject(&QLCDNumber::staticMetaObject, constructor);
  2984. engine->globalObject().setProperty("QLCDNumber", value);
  2985. @ There is nothing special about the implementation.
  2986. @<Functions for scripting@>=
  2987. QScriptValue constructQLCDNumber(QScriptContext *, QScriptEngine *engine)
  2988. {
  2989. QScriptValue object = engine->newQObject(new QLCDNumber());
  2990. setQLCDNumberProperties(object, engine);
  2991. return object;
  2992. }
  2993. void setQLCDNumberProperties(QScriptValue value, QScriptEngine *engine)
  2994. {
  2995. setQFrameProperties(value, engine);
  2996. }
  2997. @* Scripting QTime.
  2998. \noindent |QTime| is a little different from the classes examined so far. This
  2999. class can be used for synchonizing time among various objects by creating a
  3000. common base reference time. This should not be needed as ECMA-262 already
  3001. specifies a |Date| class, however this has historically been troublesome to use.
  3002. One thing that makes this class different is that it is not related to
  3003. |QObject|. This makes usefully exposing it to the scripting engine a little more
  3004. difficult.
  3005. @<Function prototypes for scripting@>=
  3006. QScriptValue constructQTime(QScriptContext *context, QScriptEngine *engine);
  3007. QScriptValue QTime_addMSecs(QScriptContext *context, QScriptEngine *engine);
  3008. QScriptValue QTime_addSecs(QScriptContext *context, QScriptEngine *engine);
  3009. QScriptValue QTime_elapsed(QScriptContext *context, QScriptEngine *engine);
  3010. QScriptValue QTime_hour(QScriptContext *context, QScriptEngine *engine);
  3011. QScriptValue QTime_isNull(QScriptContext *context, QScriptEngine *engine);
  3012. QScriptValue QTime_isValid(QScriptContext *context, QScriptEngine *engine);
  3013. QScriptValue QTime_minute(QScriptContext *context, QScriptEngine *engine);
  3014. QScriptValue QTime_msec(QScriptContext *context, QScriptEngine *engine);
  3015. QScriptValue QTime_msecsTo(QScriptContext *context, QScriptEngine *engine);
  3016. QScriptValue QTime_restart(QScriptContext *context, QScriptEngine *engine);
  3017. QScriptValue QTime_second(QScriptContext *context, QScriptEngine *engine);
  3018. QScriptValue QTime_secsTo(QScriptContext *context, QScriptEngine *engine);
  3019. QScriptValue QTime_setHMS(QScriptContext *context, QScriptEngine *engine);
  3020. QScriptValue QTime_start(QScriptContext *context, QScriptEngine *engine);
  3021. QScriptValue QTime_toString(QScriptContext *context, QScriptEngine *engine);
  3022. QScriptValue QTime_currentTime(QScriptContext *context, QScriptEngine *engine);
  3023. QScriptValue QTime_fromString(QScriptContext *context, QScriptEngine *engine);
  3024. QScriptValue QTime_valueOf(QScriptContext *context, QScriptEngine *engine);
  3025. void setQTimeProperties(QScriptValue value, QScriptEngine *engine);
  3026. @ We must tell the script engine about the constructor. This is not done in
  3027. quite the same way as is done for |QObject| derived types.
  3028. @<Set up the scripting engine@>=
  3029. constructor = engine->newFunction(constructQTime);
  3030. engine->globalObject().setProperty("QTime", constructor);
  3031. @ The constructor has a couple interesting twists. The first is the ability to
  3032. accept a variable number of integer arguments. The other is that |QTime| is not
  3033. derived from |QObject|. The lack of |break| statements in the |switch| is
  3034. intended.
  3035. @<Functions for scripting@>=
  3036. QScriptValue constructQTime(QScriptContext *context,
  3037. QScriptEngine *engine)
  3038. {
  3039. QScriptValue object;
  3040. if(context->argumentCount() == 0 ||
  3041. (context->argumentCount() >= 2 && context->argumentCount() <= 4))@/
  3042. {
  3043. int arg1 = 0;
  3044. int arg2 = 0;
  3045. int arg3 = 0;
  3046. int arg4 = 0;
  3047. switch(context->argumentCount())
  3048. {@t\1@>@/
  3049. case 4:@/
  3050. arg4 = argument<int>(3, context);
  3051. arg3 = argument<int>(2, context);
  3052. arg2 = argument<int>(1, context);
  3053. arg1 = argument<int>(0, context);
  3054. break;
  3055. case 3:@/
  3056. arg3 = argument<int>(2, context);
  3057. arg2 = argument<int>(1, context);
  3058. arg1 = argument<int>(0, context);
  3059. break;
  3060. case 2:@/
  3061. arg2 = argument<int>(1, context);
  3062. arg1 = argument<int>(0, context);
  3063. break;
  3064. default:@/
  3065. break;@t\2@>@/
  3066. }
  3067. if(context->argumentCount())
  3068. {
  3069. object = engine->toScriptValue<QTime>(QTime(arg1, arg2, arg3,
  3070. arg4));
  3071. }
  3072. else
  3073. {
  3074. object = engine->toScriptValue<QTime>(QTime());
  3075. }
  3076. setQTimeProperties(object, engine);
  3077. }
  3078. else
  3079. {
  3080. context->throwError("Incorrect number of arguments passed to "@|
  3081. "QTime::QTime(). This method takes zero, two, "@|
  3082. "three, or four integer arguments.");
  3083. }
  3084. return object;
  3085. }
  3086. @ In order to use the various |QTime| methods, we must add wrapper functions as
  3087. properties of newly created script objects. The last two of these should really
  3088. be callable without starting from an existing |QTime|.
  3089. @<Functions for scripting@>=
  3090. void setQTimeProperties(QScriptValue value, QScriptEngine *engine)
  3091. {
  3092. value.setProperty("addMSecs", engine->newFunction(QTime_addMSecs));
  3093. value.setProperty("addSecs", engine->newFunction(QTime_addSecs));
  3094. value.setProperty("elapsed", engine->newFunction(QTime_elapsed));
  3095. value.setProperty("hour", engine->newFunction(QTime_hour));
  3096. value.setProperty("isNull", engine->newFunction(QTime_isNull));
  3097. value.setProperty("isValid", engine->newFunction(QTime_isValid));
  3098. value.setProperty("minute", engine->newFunction(QTime_minute));
  3099. value.setProperty("msec", engine->newFunction(QTime_msec));
  3100. value.setProperty("msecsTo", engine->newFunction(QTime_msecsTo));
  3101. value.setProperty("restart", engine->newFunction(QTime_restart));
  3102. value.setProperty("second", engine->newFunction(QTime_second));
  3103. value.setProperty("secsTo", engine->newFunction(QTime_secsTo));
  3104. value.setProperty("setHMS", engine->newFunction(QTime_setHMS));
  3105. value.setProperty("start", engine->newFunction(QTime_start));
  3106. value.setProperty("toString", engine->newFunction(QTime_toString));
  3107. value.setProperty("currentTime", engine->newFunction(QTime_currentTime));
  3108. value.setProperty("fromString", engine->newFunction(QTime_fromString));
  3109. value.setProperty("valueOf", engine->newFunction(QTime_valueOf));
  3110. }
  3111. @ The |valueOf()| method exposes a numeric representation of the time
  3112. suitable for use in comparing two time values. With this it is possible to
  3113. take two |QTime| values in script code {\tt t1} and {\tt t2} and get the
  3114. expected results from {\tt t1 == t2}, {\tt t1 < t2}, {\tt t1 > t2} and
  3115. similar comparative operations.
  3116. @<Functions for scripting@>=
  3117. QScriptValue QTime_valueOf(QScriptContext *context, QScriptEngine *)
  3118. {
  3119. QTime self = getself<QTime>(context);
  3120. int retval = (self.hour() * 60 * 60 * 1000) + (self.minute() * 60 * 1000) +
  3121. (self.second() * 1000) + self.msec();
  3122. return QScriptValue(retval);
  3123. }
  3124. @ These functions are effectively wrapper functions around existing |QTime|
  3125. functionality with some error checking for the scripting engine.
  3126. The |addMSecs()| and |addSecs()| methods return a new |QTime| object.
  3127. @<Functions for scripting@>=
  3128. QScriptValue QTime_addMSecs(QScriptContext *context, QScriptEngine *engine)
  3129. {
  3130. QTime time;
  3131. QScriptValue retval;
  3132. if(context->argumentCount() == 1)
  3133. {
  3134. QTime self = getself<QTime>(context);
  3135. time = self.addMSecs(argument<int>(0, context));
  3136. retval = engine->toScriptValue<QTime>(time);
  3137. setQTimeProperties(retval, engine);
  3138. }
  3139. else
  3140. {
  3141. context->throwError("Incorrect number of arguments passed to "@|
  3142. "QTime::addMSecs(). This method takes one "@|
  3143. "integer as an argument.");
  3144. }
  3145. return retval;
  3146. }
  3147. QScriptValue QTime_addSecs(QScriptContext *context, QScriptEngine *engine)
  3148. {
  3149. QTime time;
  3150. QScriptValue retval;
  3151. if(context->argumentCount() == 1)
  3152. {
  3153. QTime self = getself<QTime>(context);
  3154. time = self.addSecs(argument<int>(0, context));
  3155. retval = engine->toScriptValue<QTime>(time);
  3156. setQTimeProperties(retval, engine);
  3157. }
  3158. else
  3159. {
  3160. context->throwError("Incorrect number of arguments passed to "@|
  3161. "QTime::addSecs(). This method takes one "@|
  3162. "integer as an argument.");
  3163. }
  3164. return retval;
  3165. }
  3166. @ The |elapsed()| method returns an integer value.
  3167. @<Functions for scripting@>=
  3168. QScriptValue QTime_elapsed(QScriptContext *context, QScriptEngine *engine)
  3169. {
  3170. QScriptValue retval;
  3171. if(context->argumentCount() == 0)
  3172. {
  3173. QTime self = getself<QTime>(context);
  3174. retval = QScriptValue(engine, self.elapsed());
  3175. }
  3176. else
  3177. {
  3178. context->throwError("Incorrect number of arguments passed to "@|
  3179. "QTime::elapsed(). This method takes no "@|
  3180. "arguments.");
  3181. }
  3182. return retval;
  3183. }
  3184. @ The |hour()|, |minute()|, |second()| and |msec()| methods return an integer
  3185. with various parts of the time. The |hour()| method is typical of these methods.
  3186. @<Functions for scripting@>=
  3187. QScriptValue QTime_hour(QScriptContext *context, QScriptEngine *engine)
  3188. {
  3189. QScriptValue retval;
  3190. if(context->argumentCount() == 0)
  3191. {
  3192. QTime self = getself<QTime>(context);
  3193. retval = QScriptValue(engine, self.hour());
  3194. }
  3195. else
  3196. {
  3197. context->throwError("Incorrect number of arguments passed to "@|
  3198. "QTime::hour(). This method takes no "@|
  3199. "arguments.");
  3200. }
  3201. return retval;
  3202. }
  3203. @ The |minute()|, |second()|, and |msec()| methods are implemented similarly.
  3204. @<Functions for scripting@>=
  3205. QScriptValue QTime_minute(QScriptContext *context, QScriptEngine *engine)
  3206. {
  3207. QScriptValue retval;
  3208. if(context->argumentCount() == 0)
  3209. {
  3210. QTime self = getself<QTime>(context);
  3211. retval = QScriptValue(engine, self.minute());
  3212. }
  3213. else
  3214. {
  3215. context->throwError("Incorrect number of arguments passed to "@|
  3216. "QTime::minute(). This method takes no "@|
  3217. "arguments.");
  3218. }
  3219. return retval;
  3220. }
  3221. QScriptValue QTime_second(QScriptContext *context, QScriptEngine *engine)
  3222. {
  3223. QScriptValue retval;
  3224. if(context->argumentCount() == 0)
  3225. {
  3226. QTime self = getself<QTime>(context);
  3227. retval = QScriptValue(engine, self.second());
  3228. }
  3229. else
  3230. {
  3231. context->throwError("Incorrect number of arguments passed to "@|
  3232. "QTime::second(). This method takes no "@|
  3233. "arguments.");
  3234. }
  3235. return retval;
  3236. }
  3237. QScriptValue QTime_msec(QScriptContext *context, QScriptEngine *engine)
  3238. {
  3239. QScriptValue retval;
  3240. if(context->argumentCount() == 0)
  3241. {
  3242. QTime self = getself<QTime>(context);
  3243. retval = QScriptValue(engine, self.msec());
  3244. }
  3245. else
  3246. {
  3247. context->throwError("Incorrect number of arguments passed to "@|
  3248. "QTime::msec(). This method takes no "@|
  3249. "arguments.");
  3250. }
  3251. return retval;
  3252. }
  3253. @ The |isNull()| and |isValid()| methods return a boolean value. A |QTime| is
  3254. considered null if it was created with a constructor with no arguments. It is
  3255. considered invalid if it is null or if part of the time is out of range.
  3256. @<Functions for scripting@>=
  3257. QScriptValue QTime_isNull(QScriptContext *context, QScriptEngine *engine)
  3258. {
  3259. QScriptValue retval;
  3260. if(context->argumentCount() == 0)
  3261. {
  3262. QTime self = getself<QTime>(context);
  3263. retval = QScriptValue(engine, self.isNull());
  3264. }
  3265. else
  3266. {
  3267. context->throwError("Incorrect number of arguments passed to "@|
  3268. "QTime::isNull(). This method takes no "@|
  3269. "arguments.");
  3270. }
  3271. return retval;
  3272. }
  3273. QScriptValue QTime_isValid(QScriptContext *context, QScriptEngine *engine)
  3274. {
  3275. QScriptValue retval;
  3276. if(context->argumentCount() == 0)
  3277. {
  3278. QTime self = getself<QTime>(context);
  3279. retval = QScriptValue(engine, self.isValid());
  3280. }
  3281. else
  3282. {
  3283. context->throwError("Incorrect number of arguments passed to "@|
  3284. "QTime::isValid(). This method takes no "@|
  3285. "arguments.");
  3286. }
  3287. return retval;
  3288. }
  3289. @ The |secsTo()| and |msecsTo()| methods return an integer value indicating the
  3290. number of seconds or milliseconds until a |QTime| argument.
  3291. @<Functions for scripting@>=
  3292. QScriptValue QTime_msecsTo(QScriptContext *context, QScriptEngine *engine)
  3293. {
  3294. QScriptValue retval;
  3295. if(context->argumentCount() == 1)
  3296. {
  3297. QTime self = getself<QTime>(context);
  3298. QTime arg = argument<QVariant>(0, context).toTime();
  3299. retval = QScriptValue(engine, self.msecsTo(arg));
  3300. }
  3301. else
  3302. {
  3303. context->throwError("Incorrect number of arguments passed to "@|
  3304. "QTime::msecsTo(). This method takes one QTime.");
  3305. }
  3306. return retval;
  3307. }
  3308. QScriptValue QTime_secsTo(QScriptContext *context, QScriptEngine *engine)
  3309. {
  3310. QScriptValue retval;
  3311. if(context->argumentCount() == 1)
  3312. {
  3313. QTime self = getself<QTime>(context);
  3314. QTime arg = argument<QVariant>(0, context).toTime();
  3315. retval = QScriptValue(engine, self.secsTo(arg));
  3316. }
  3317. else
  3318. {
  3319. context->throwError("Incorrect number of arguments passed to "@|
  3320. "QTime::secsTo(). This method takes one QTime.");
  3321. }
  3322. return retval;
  3323. }
  3324. @ The |start()| and |restart()| methods each set the value of the |QTime()| to
  3325. the current time. The |restart()| method additionally returns the same value as
  3326. the |elapsed()| method.
  3327. @<Functions for scripting@>=
  3328. QScriptValue QTime_restart(QScriptContext *context, QScriptEngine *engine)
  3329. {
  3330. QScriptValue retval;
  3331. if(context->argumentCount() == 0)
  3332. {
  3333. QTime self = getself<QTime>(context);
  3334. retval = QScriptValue(engine, self.restart());
  3335. }
  3336. else
  3337. {
  3338. context->throwError("Incorrect number of arguments passed to "@|
  3339. "QTime::restart(). This method takes no "@|
  3340. "arguments.");
  3341. }
  3342. return retval;
  3343. }
  3344. QScriptValue QTime_start(QScriptContext *context, QScriptEngine *)
  3345. {
  3346. if(context->argumentCount() == 0)
  3347. {
  3348. QTime self = getself<QTime>(context);
  3349. self.start();
  3350. }
  3351. else
  3352. {
  3353. context->throwError("Incorrect number of arguments passed to "@|
  3354. "QTime::start(). This method takes no arguments.");
  3355. }
  3356. return QScriptValue();
  3357. }
  3358. @ The slightly inappropriately named |setHMS()| method changes the current value
  3359. of the time and returns a boolean to indicate if the new time value is valid.
  3360. @<Functions for scripting@>=
  3361. QScriptValue QTime_setHMS(QScriptContext *context, QScriptEngine *engine)
  3362. {
  3363. QScriptValue retval;
  3364. if(context->argumentCount() == 3 || context->argumentCount() == 4)
  3365. {
  3366. QTime self = getself<QTime>(context);
  3367. int arg1 = 0;
  3368. int arg2 = 0;
  3369. int arg3 = 0;
  3370. int arg4 = 0;
  3371. switch(context->argumentCount())@/
  3372. {@t\1@>@/
  3373. case 4:@/
  3374. arg4 = argument<int>(3, context);
  3375. arg3 = argument<int>(2, context);
  3376. arg2 = argument<int>(1, context);
  3377. arg1 = argument<int>(0, context);
  3378. break;
  3379. case 3:@/
  3380. arg3 = argument<int>(2, context);
  3381. arg2 = argument<int>(1, context);
  3382. arg1 = argument<int>(0, context);
  3383. break;
  3384. default:@/
  3385. break;@t\2@>@/
  3386. }
  3387. retval = QScriptValue(engine, self.setHMS(arg1, arg2, arg3, arg4));
  3388. }
  3389. else
  3390. {
  3391. context->throwError("Incorrect number of arguments passed to "@|
  3392. "QTime::setHMS(). This method takes three or "@|
  3393. "four integer arguments.");
  3394. }
  3395. return retval;
  3396. }
  3397. @ The |toString()| method returns a string representation of the time. See the
  3398. Qt documentation for instructions on creating a valid format string.
  3399. @<Functions for scripting@>=
  3400. QScriptValue QTime_toString(QScriptContext *context, QScriptEngine *engine)
  3401. {
  3402. QScriptValue retval;
  3403. if(context->argumentCount() == 1)
  3404. {
  3405. QTime self = getself<QTime>(context);
  3406. retval = QScriptValue(engine, self.toString(argument<QString>(0, context)));
  3407. }
  3408. else
  3409. {
  3410. context->throwError("Incorrect number of arguments passed to "@|
  3411. "QTime::toString(). This method takes one QString "@|
  3412. "as an argument.");
  3413. }
  3414. return retval;
  3415. }
  3416. @ The |currentTime()| and |fromString()| methods return a new |QTime| object.
  3417. These methods make no reference to the any other existing |QTime|.
  3418. @<Functions for scripting@>=
  3419. QScriptValue QTime_currentTime(QScriptContext *, QScriptEngine *engine)
  3420. {
  3421. QScriptValue object;
  3422. object = engine->toScriptValue<QTime>(QTime::currentTime());
  3423. setQTimeProperties(object, engine);
  3424. return object;
  3425. }
  3426. QScriptValue QTime_fromString(QScriptContext *context, QScriptEngine *engine)
  3427. {
  3428. QScriptValue object;
  3429. if(context->argumentCount() == 2)
  3430. {
  3431. QString time = argument<QString>(0, context);
  3432. QString format = argument<QString>(1, context);
  3433. object = engine->toScriptValue<QTime>(QTime::fromString(time, format));
  3434. setQTimeProperties(object, engine);
  3435. }
  3436. else
  3437. {
  3438. context->throwError("Incorrect number of arguments passed to "@|
  3439. "QTime::fromString(). This method takes two "@|
  3440. "string arguments.");
  3441. }
  3442. return object;
  3443. }
  3444. @ In order to pass |QTime| objects back from a script, we also need to overload
  3445. |argument()| for this type.
  3446. @<Functions for scripting@>=
  3447. template<> QTime argument(int arg, QScriptContext *context)
  3448. {
  3449. return qscriptvalue_cast<QTime>(context->argument(arg));
  3450. }
  3451. @* Scripting QColor.
  3452. \noindent |QColor| support is limited to creating colors from strings to pass
  3453. to objects expecting a color.
  3454. @<Function prototypes for scripting@>=
  3455. QScriptValue constructQColor(QScriptContext *context, QScriptEngine *engine);
  3456. @ We must tell the script engine about the constructor. This is not done in
  3457. quite the same way as is done for |QObject| derived types.
  3458. @<Set up the scripting engine@>=
  3459. constructor = engine->newFunction(constructQColor);
  3460. engine->globalObject().setProperty("QColor", constructor);
  3461. @ The constructor is trivial.
  3462. @<Functions for scripting@>=
  3463. QScriptValue constructQColor(QScriptContext *context, QScriptEngine *engine)
  3464. {
  3465. QScriptValue object = engine->toScriptValue<QColor>(QColor(argument<QString>(0, context)));
  3466. return object;
  3467. }
  3468. @* Scripting QBrush.
  3469. \noindent |QBrush| support is limited to creating brushes from color strings to
  3470. pass to objects expecting a brush.
  3471. @<Function prototypes for scripting@>=
  3472. QScriptValue constructQBrush(QScriptContext *context, QScriptEngine *engine);
  3473. @ The script is informed of the constructor.
  3474. @<Set up the scripting engine@>=
  3475. constructor = engine->newFunction(constructQBrush);
  3476. engine->globalObject().setProperty("QBrush", constructor);
  3477. @ The constructor is trivial.
  3478. @<Functions for scripting@>=
  3479. QScriptValue constructQBrush(QScriptContext *context, QScriptEngine *engine)
  3480. {
  3481. QBrush theBrush = QBrush(QColor(argument<QString>(0, context)));
  3482. QScriptValue object = engine->toScriptValue(theBrush);
  3483. return object;
  3484. }
  3485. @* Scripting Item View Classes.
  3486. \noindent |QAbstractScrollArea| is a |QFrame| that serves as the base class for
  3487. classes such as |QGraphicsView| and |QAbstractItemView|. Objects from this class
  3488. are not created directly.
  3489. @<Function prototypes for scripting@>=
  3490. void setQAbstractScrollAreaProperties(QScriptValue value,
  3491. QScriptEngine *engine);
  3492. @ The implementation of this is simple.
  3493. @<Functions for scripting@>=
  3494. void setQAbstractScrollAreaProperties(QScriptValue value, QScriptEngine *engine)
  3495. {
  3496. setQFrameProperties(value, engine);
  3497. }
  3498. @ This class is used by the |QAbstractItemView| class. This is another class
  3499. that we do not need a script constructor for.
  3500. @<Function prototypes for scripting@>=
  3501. void setQAbstractItemViewProperties(QScriptValue value, QScriptEngine *engine);
  3502. @ This function has another simple implementation.
  3503. @<Functions for scripting@>=
  3504. void setQAbstractItemViewProperties(QScriptValue value, QScriptEngine *engine)
  3505. {
  3506. setQAbstractScrollAreaProperties(value, engine);
  3507. }
  3508. @ The |QGraphicsView| and |QTableView| classes form the base of \pn{} classes.
  3509. @<Function prototypes for scripting@>=
  3510. void setQGraphicsViewProperties(QScriptValue value, QScriptEngine *engine);
  3511. void setQTableViewProperties(QScriptValue value, QScriptEngine *engine);
  3512. @ Again, the implementations are boring.
  3513. @<Functions for scripting@>=
  3514. void setQGraphicsViewProperties(QScriptValue value, QScriptEngine *engine)
  3515. {
  3516. setQAbstractScrollAreaProperties(value, engine);
  3517. }
  3518. void setQTableViewProperties(QScriptValue value, QScriptEngine *engine)
  3519. {
  3520. setQAbstractItemViewProperties(value, engine);
  3521. }
  3522. @* Scripting Button Classes.
  3523. \noindent \pn{} provides an |AnnotationButton| class which is a special kind of
  3524. |QPushButton| which in turn comes from |QAbstractButton|. While
  3525. |AnnotationButton| can be used in exactly the same way as a |QPushButton|, if
  3526. an annotation is not needed, there is little reason not to use the base class.
  3527. @<Function prototypes for scripting@>=
  3528. void setQAbstractButtonProperties(QScriptValue value, QScriptEngine *engine);
  3529. void setQPushButtonProperties(QScriptValue value, QScriptEngine *engine);
  3530. QScriptValue constructQPushButton(QScriptContext *context,
  3531. QScriptEngine *engine);
  3532. @ The constructor for |QPushButton| should be passed to the scripting engine.
  3533. @<Set up the scripting engine@>=
  3534. constructor = engine->newFunction(constructQPushButton);
  3535. value = engine->newQMetaObject(&QPushButton::staticMetaObject, constructor);
  3536. engine->globalObject().setProperty("QPushButton", value);
  3537. @ The implementation should seem familiar.
  3538. @<Functions for scripting@>=
  3539. QScriptValue constructQPushButton(QScriptContext *, QScriptEngine *engine)
  3540. {
  3541. QScriptValue object = engine->newQObject(new QPushButton());
  3542. setQPushButtonProperties(object, engine);
  3543. return object;
  3544. }
  3545. void setQPushButtonProperties(QScriptValue value, QScriptEngine *engine)
  3546. {
  3547. setQAbstractButtonProperties(value, engine);
  3548. }
  3549. void setQAbstractButtonProperties(QScriptValue value, QScriptEngine *engine)
  3550. {
  3551. setQWidgetProperties(value, engine);
  3552. }
  3553. @* Scripting QSqlQuery.
  3554. \noindent With this class exposed to the host environment, it becomes possible
  3555. for script code to execute SQL queries and evaluate the result.
  3556. Rather than use |QSqlQuery| directly, however, we use a proxy \nfnote{Erich
  3557. Gamma, Richard Helm, Raph Johnson, and John
  3558. Vlissides,\par\indent\underbar{Design Patterns: elements of reusable
  3559. object-oriented software} (1995) pp. 207--217} class. This class obtains its own
  3560. database connection and handles properly closing and removing these connections
  3561. when the query object is destroyed.
  3562. @<Class declarations@>=
  3563. class SqlQueryConnection : public QSqlQuery@/
  3564. {
  3565. public:@/
  3566. SqlQueryConnection(const QString &query = QString());
  3567. ~SqlQueryConnection();
  3568. QSqlQuery* operator->() const;
  3569. private:@/
  3570. QString connection;
  3571. QSqlQuery *q;
  3572. };
  3573. @ The constructor can be somewhat simplified from the four forms of |QSqlQuery|.
  3574. We are not interested in creating an object from a |QSqlResult| or from another
  3575. |QSqlQuery|. The database connection is managed by the class itself so the
  3576. constructor only needs an optional string containing a query. This is used to
  3577. initialize a real |QSqlQuery| object.
  3578. @<SqlQueryConnection implementation@>=
  3579. SqlQueryConnection::SqlQueryConnection(const QString &query)
  3580. {
  3581. QSqlDatabase database = AppInstance->database();
  3582. database.open();
  3583. q = new QSqlQuery(query, database);
  3584. connection = database.connectionName();
  3585. }
  3586. @ The destructor handles removing the |QSqlQuery| and the database connection
  3587. associated with it. The extra brackets introduce a new scope for the
  3588. |QSqlDatabase| so that it is out of scope when the connection is removed.
  3589. @<SqlQueryConnection implementation@>=
  3590. SqlQueryConnection::~SqlQueryConnection()
  3591. {
  3592. delete q;
  3593. {
  3594. QSqlDatabase database = QSqlDatabase::database(connection);
  3595. database.close();
  3596. }
  3597. QSqlDatabase::removeDatabase(connection);
  3598. }
  3599. @ For all other functionality, we simply forward the request to our |QSqlQuery|
  3600. object.
  3601. @<SqlQueryConnection implementation@>=
  3602. QSqlQuery* SqlQueryConnection::operator->() const
  3603. {
  3604. return q;
  3605. }
  3606. @ In order to use this new class in the host environment, a number of functions
  3607. are needed.
  3608. @<Function prototypes for scripting@>=
  3609. void setQSqlQueryProperties(QScriptValue value, QScriptEngine *engine);
  3610. QScriptValue constructQSqlQuery(QScriptContext *context, QScriptEngine *engine);
  3611. QScriptValue QSqlQuery_bind(QScriptContext *context, QScriptEngine *engine);
  3612. QScriptValue QSqlQuery_bindDeviceData(QScriptContext *context,
  3613. QScriptEngine *engine);
  3614. QScriptValue QSqlQuery_bindFileData(QScriptContext *context,
  3615. QScriptEngine *engine);
  3616. QScriptValue QSqlQuery_exec(QScriptContext *context,
  3617. QScriptEngine *engine);
  3618. QScriptValue QSqlQuery_executedQuery(QScriptContext *context,
  3619. QScriptEngine *engine);
  3620. QScriptValue QSqlQuery_invalidate(QScriptContext *context, QScriptEngine *engine);
  3621. QScriptValue QSqlQuery_next(QScriptContext *context, QScriptEngine *engine);
  3622. QScriptValue QSqlQuery_prepare(QScriptContext *context, QScriptEngine *engine);
  3623. QScriptValue QSqlQuery_value(QScriptContext *context, QScriptEngine *engine);
  3624. @ For conceptual convenience we simply pretend that we are working with a real
  3625. |QSqlQuery| object.
  3626. @<Set up the scripting engine@>=
  3627. constructor = engine->newFunction(constructQSqlQuery);
  3628. engine->globalObject().setProperty("QSqlQuery", constructor);
  3629. @ With connection creation no longer needed in the constructor, all that is
  3630. needed is object creation and applying the appropriate properties to the script
  3631. value.
  3632. @<Functions for scripting@>=
  3633. QScriptValue constructQSqlQuery(QScriptContext *, QScriptEngine *engine)
  3634. {
  3635. SqlQueryConnection *obj = new SqlQueryConnection();
  3636. QScriptValue object =
  3637. engine->toScriptValue<void *>(obj);
  3638. setQSqlQueryProperties(object, engine);
  3639. return object;
  3640. }
  3641. @ As this class does not derive from |QObject|, we must wrap all of the methods
  3642. we might want to use.
  3643. @<Functions for scripting@>=
  3644. void setQSqlQueryProperties(QScriptValue value, QScriptEngine *engine)
  3645. {
  3646. value.setProperty("bind", engine->newFunction(QSqlQuery_bind));
  3647. value.setProperty("bindFileData",
  3648. engine->newFunction(QSqlQuery_bindFileData));
  3649. value.setProperty("bindDeviceData",
  3650. engine->newFunction(QSqlQuery_bindDeviceData));
  3651. value.setProperty("exec", engine->newFunction(QSqlQuery_exec));
  3652. value.setProperty("executedQuery", engine->newFunction(QSqlQuery_executedQuery));
  3653. value.setProperty("invalidate", engine->newFunction(QSqlQuery_invalidate));
  3654. value.setProperty("next", engine->newFunction(QSqlQuery_next));
  3655. value.setProperty("prepare", engine->newFunction(QSqlQuery_prepare));
  3656. value.setProperty("value", engine->newFunction(QSqlQuery_value));
  3657. }
  3658. @ Most of these properties are wrappers around existing |QSqlQuery| functions.
  3659. @<Functions for scripting@>=
  3660. QScriptValue QSqlQuery_exec(QScriptContext *context, QScriptEngine *engine)
  3661. {
  3662. QSqlQuery *q = getself<SqlQueryConnection *>(context)->operator->();
  3663. QScriptValue retval;
  3664. if(context->argumentCount() == 1)
  3665. {
  3666. retval = QScriptValue(engine,
  3667. q->exec(argument<QString>(0, context)));
  3668. }
  3669. else
  3670. {
  3671. retval = QScriptValue(engine, q->exec());
  3672. }
  3673. if(q->lastError().isValid())
  3674. {
  3675. qDebug() << q->lastQuery();
  3676. qDebug() << q->lastError().text();
  3677. }
  3678. return retval;
  3679. }
  3680. QScriptValue QSqlQuery_executedQuery(QScriptContext *context, QScriptEngine *)
  3681. {
  3682. QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
  3683. return QScriptValue(query->lastQuery());
  3684. }
  3685. QScriptValue QSqlQuery_next(QScriptContext *context, QScriptEngine *engine)
  3686. {
  3687. QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
  3688. return QScriptValue(engine, query->next());
  3689. }
  3690. QScriptValue QSqlQuery_value(QScriptContext *context, QScriptEngine *engine)
  3691. {
  3692. QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
  3693. return QScriptValue(engine,
  3694. query->value(argument<int>(0, context)).toString());
  3695. }
  3696. @ For prepared queries, we support binding variables available to the script,
  3697. data available in a named file, or data from any open |QIODevice|.
  3698. @<Functions for scripting@>=
  3699. QScriptValue QSqlQuery_prepare(QScriptContext *context, QScriptEngine *engine)
  3700. {
  3701. QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
  3702. return QScriptValue(engine, query->prepare(argument<QString>(0, context)));
  3703. }
  3704. QScriptValue QSqlQuery_bind(QScriptContext *context, QScriptEngine *)
  3705. {
  3706. QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
  3707. query->bindValue(argument<QString>(0, context),
  3708. argument<QVariant>(1, context));
  3709. return QScriptValue();
  3710. }
  3711. QScriptValue QSqlQuery_bindFileData(QScriptContext *context,
  3712. QScriptEngine *)
  3713. {
  3714. QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
  3715. QString placeholder = argument<QString>(0, context);
  3716. QString filename = argument<QString>(1, context);
  3717. QFile file(filename);
  3718. QByteArray data;
  3719. if(file.open(QIODevice::ReadOnly))
  3720. {
  3721. data = file.readAll();
  3722. file.close();
  3723. }
  3724. query->bindValue(placeholder, data);
  3725. return QScriptValue();
  3726. }
  3727. QScriptValue QSqlQuery_bindDeviceData(QScriptContext *context,
  3728. QScriptEngine *)
  3729. {
  3730. QSqlQuery *query = getself<SqlQueryConnection *>(context)->operator->();
  3731. QString placeholder = argument<QString>(0, context);
  3732. QIODevice *device = argument<QIODevice *>(1, context);
  3733. device->reset();
  3734. QByteArray data;
  3735. data = device->readAll();
  3736. query->bindValue(placeholder, data);
  3737. return QScriptValue();
  3738. }
  3739. @ To avoid leaking database connections, we add the |invalidate()| property
  3740. which destroys our object. The object on which this method is called must not be
  3741. used after calling this method. In script code this will typically be used as in
  3742. the following example:
  3743. {\tt query = query.invalidate();}
  3744. @<Functions for scripting@>=
  3745. QScriptValue QSqlQuery_invalidate(QScriptContext *context, QScriptEngine *)
  3746. {
  3747. SqlQueryConnection *query = getself<SqlQueryConnection *>(context);
  3748. delete query;
  3749. return QScriptValue::UndefinedValue;
  3750. }
  3751. @* Other scripting functions.
  3752. \noindent There are a few functions that are exposed to the scripting engine
  3753. that are not associated with any class. Two functions are used for extracting
  3754. information from file names. Another is used to construct array values from SQL
  3755. array values. There is also a function for setting the default font for the
  3756. application or some part of the application.
  3757. @<Function prototypes for scripting@>=
  3758. QScriptValue baseName(QScriptContext *context, QScriptEngine *engine);
  3759. QScriptValue dir(QScriptContext *context, QScriptEngine *engine);
  3760. QScriptValue sqlToArray(QScriptContext *context, QScriptEngine *engine);
  3761. QScriptValue setFont(QScriptContext *context, QScriptEngine *engine);
  3762. QScriptValue annotationFromRecord(QScriptContext *context,
  3763. QScriptEngine *engine);
  3764. QScriptValue setTabOrder(QScriptContext *context, QScriptEngine *engine);
  3765. QScriptValue saveFileFromDatabase(QScriptContext *context, QScriptEngine *engine);
  3766. QScriptValue scriptTr(QScriptContext *context, QScriptEngine *engine);
  3767. @ These functions are passed to the scripting engine.
  3768. @<Set up the scripting engine@>=
  3769. engine->globalObject().setProperty("baseName", engine->newFunction(baseName));
  3770. engine->globalObject().setProperty("dir", engine->newFunction(dir));
  3771. engine->globalObject().setProperty("sqlToArray",
  3772. engine->newFunction(sqlToArray));
  3773. engine->globalObject().setProperty("setFont", engine->newFunction(setFont));
  3774. engine->globalObject().setProperty("annotationFromRecord",
  3775. engine->newFunction(annotationFromRecord));
  3776. engine->globalObject().setProperty("setTabOrder",
  3777. engine->newFunction(setTabOrder));
  3778. engine->globalObject().setProperty("saveFileFromDatabase",
  3779. engine->newFunction(saveFileFromDatabase));
  3780. engine->globalObject().setProperty("TTR", engine->newFunction(scriptTr));
  3781. @ These functions are not part of an object. They expect a string specifying
  3782. the path to a file and return a string with either the name of the file without
  3783. the path and extension or the path of the directory containing the file.
  3784. @<Functions for scripting@>=
  3785. QScriptValue baseName(QScriptContext *context, QScriptEngine *engine)
  3786. {
  3787. QFileInfo info(argument<QString>(0, context));
  3788. QScriptValue retval(engine, info.baseName());
  3789. return retval;
  3790. }
  3791. QScriptValue dir(QScriptContext *context, QScriptEngine *engine)
  3792. {
  3793. QFileInfo info(argument<QString>(0, context));
  3794. QDir dir = info.dir();
  3795. QScriptValue retval(engine, dir.path());
  3796. return retval;
  3797. }
  3798. @ This function takes a file ID and a file name and copies file data stored in
  3799. the database out to the file system.
  3800. @<Functions for scripting@>=
  3801. QScriptValue saveFileFromDatabase(QScriptContext *context, QScriptEngine *)
  3802. {
  3803. SqlQueryConnection h;
  3804. QSqlQuery *query = h.operator->();
  3805. QString q = "SELECT file FROM files WHERE id = :file";
  3806. query->prepare(q);
  3807. query->bindValue(":file", argument<int>(0, context));
  3808. query->exec();
  3809. query->next();
  3810. QByteArray array = query->value(0).toByteArray();
  3811. QFile file(argument<QString>(1, context));
  3812. file.open(QIODevice::WriteOnly);
  3813. file.write(array);
  3814. file.close();
  3815. return QScriptValue();
  3816. }
  3817. @ This function takes a string representing a SQL array and returns an array
  3818. value.
  3819. @<Functions for scripting@>=
  3820. QScriptValue sqlToArray(QScriptContext *context, QScriptEngine *engine)
  3821. {
  3822. QString source = argument<QString>(0, context);
  3823. source.remove(0, 1);
  3824. source.chop(1);
  3825. QStringList elements = source.split(",");
  3826. QString element;
  3827. QScriptValue dest = engine->newArray(elements.size());
  3828. int i = 0;
  3829. foreach(element, elements)
  3830. {
  3831. if(element.startsWith("\"") && element.endsWith("\""))
  3832. {
  3833. element.chop(1);
  3834. element = element.remove(0, 1);
  3835. }
  3836. dest.setProperty(i, QScriptValue(engine, element));
  3837. i++;
  3838. }
  3839. return dest;
  3840. }
  3841. @ This function can be used to set the default font for the application or on
  3842. a per-class hierarchy basis.
  3843. @<Functions for scripting@>=
  3844. QScriptValue setFont(QScriptContext *context, QScriptEngine *)
  3845. {
  3846. QString font = argument<QString>(0, context);
  3847. QString classname;
  3848. if(context->argumentCount() > 1)
  3849. {
  3850. classname = argument<QString>(1, context);
  3851. QApplication::setFont(QFont(font), classname.toLatin1().constData());
  3852. }
  3853. else
  3854. {
  3855. QApplication::setFont(QFont(font));
  3856. }
  3857. return QScriptValue();
  3858. }
  3859. @ This function was briefly used prior to adding support for |QXmlQuery| in the
  3860. host environment. The function is now depreciated and should not be used.
  3861. @<Functions for scripting@>=
  3862. QScriptValue annotationFromRecord(QScriptContext *context, QScriptEngine *)
  3863. {
  3864. SqlQueryConnection h;
  3865. QSqlQuery *query = h.operator->();
  3866. QString q = "SELECT file FROM files WHERE id = :file";
  3867. query->prepare(q);
  3868. query->bindValue(":file", argument<int>(0, context));
  3869. query->exec();
  3870. query->next();
  3871. QByteArray array = query->value(0).toByteArray();
  3872. QBuffer buffer(&array);
  3873. buffer.open(QIODevice::ReadOnly);
  3874. QXmlQuery xquery;
  3875. xquery.bindVariable("profile", &buffer);
  3876. QString xq;
  3877. xq = "for $b in doc($profile) //tuple where exists($b/annotation) return $b";
  3878. xquery.setQuery(xq);
  3879. QString result;
  3880. xquery.evaluateTo(&result);
  3881. return QScriptValue(result);
  3882. }
  3883. @ This function can be used to change the tab order for controls in Typica.
  3884. Changes to the example configuration in \pn{} 1.4 made the default handling
  3885. of tab controls in the logging window unacceptable.
  3886. @<Functions for scripting@>=
  3887. QScriptValue setTabOrder(QScriptContext *context, QScriptEngine *)
  3888. {
  3889. QWidget::setTabOrder(argument<QWidget*>(0, context),
  3890. argument<QWidget*>(1, context));
  3891. return QScriptValue();
  3892. }
  3893. @ This function is used to allow text that must be placed in scripts to be
  3894. translated into other languages.
  3895. @<Functions for scripting@>=
  3896. QScriptValue scriptTr(QScriptContext *context, QScriptEngine *)
  3897. {
  3898. return QScriptValue(QCoreApplication::translate(
  3899. "configuration",
  3900. argument<QString>(1, context).toUtf8().data()));
  3901. }
  3902. @** Application Configuration.
  3903. \noindent While \pn{} is intended as a data logging application, the diversity
  3904. of equipment and supporting technology precludes the option of providing a
  3905. single interface for common tasks. It is important that the application can be
  3906. configured to work with different roasting equipment, databases, and the like.
  3907. To accomplish this, \pn{} utilizes an XML description of the desired application
  3908. configuration and provides an ECMA-262 host environment which allows application
  3909. dataflow to be configured.
  3910. The scripting environment provides access to elements of the XML file and also
  3911. allows access to most of the application classes. A selection of classes
  3912. provided by Qt is also available. See the section on The Scripting Engine for
  3913. more details.
  3914. \danger While the code is the ultimate documentation of what is possible with
  3915. this interface, additional documentation should be provided to document the
  3916. meaning of supported elements and the objects available through the scripting
  3917. engine.\endanger
  3918. The application configuration is loaded when the program is started.
  3919. Starting with version 1.4, we check for a command line option with the path to
  3920. the configuration file and load that instead of prompting for the information
  3921. if possible.
  3922. Starting with version 1.8, if there is not a -c argument, Typica will first
  3923. search a small number of locations relative to the executable.
  3924. @<Load the application configuration@>=
  3925. QStringList arguments = QCoreApplication::arguments();
  3926. int position = arguments.indexOf("-c");
  3927. QString filename = QString();
  3928. if(position != -1)
  3929. {
  3930. if(arguments.size() >= position + 1)
  3931. {
  3932. filename = arguments.at(position + 1);
  3933. }
  3934. } else {
  3935. QDir checkPath(QCoreApplication::applicationDirPath() + "/../config/");
  3936. if(checkPath.exists("config.xml")) {
  3937. filename = checkPath.filePath("config.xml");
  3938. } else {
  3939. checkPath = QDir(QCoreApplication::applicationDirPath() + "/config/");
  3940. if(checkPath.exists("config.xml")) {
  3941. filename = checkPath.filePath("config.xml");
  3942. }
  3943. }
  3944. }
  3945. if(filename.isEmpty())
  3946. {
  3947. filename = QFileDialog::getOpenFileName(NULL, "Open Configuration File",
  3948. settings.value("config", "").toString());
  3949. }
  3950. QDir directory;
  3951. if(!filename.isEmpty())
  3952. {
  3953. QFile file(filename);
  3954. QFileInfo info(filename);
  3955. directory = info.dir();
  3956. QTextCodec::setCodecForTr(QTextCodec::codecForName("utf-8"));
  3957. QTranslator *configtr = new QTranslator;
  3958. if(configtr->load(QString("config.%1").arg(QLocale::system().name()),
  3959. QString("%1/Translations").arg(directory.canonicalPath())))
  3960. {
  3961. QCoreApplication::installTranslator(configtr);
  3962. }
  3963. settings.setValue("config", directory.path());
  3964. if(file.open(QIODevice::ReadOnly))
  3965. {
  3966. app.configuration()->setContent(&file, true);
  3967. }
  3968. } else {
  3969. return 1;
  3970. }
  3971. @<Substitute included fragments@>@;
  3972. @ The {\tt <application>} element can contain an arbitrary number of
  3973. {\tt <include>} elements. These elements should not appear in the DOM. Instead,
  3974. the element should be replaced by the content of the specified document.
  3975. @<Substitute included fragments@>=
  3976. QDomElement root = app.configuration()->documentElement();
  3977. QDomNodeList children = root.childNodes();
  3978. QString replacementDoc;
  3979. QDomDocument includedDoc;
  3980. QDomDocumentFragment fragment;
  3981. for(int i = 0; i < children.size(); i++)
  3982. {
  3983. QDomNode currentNode = children.at(i);
  3984. QDomElement currentElement;
  3985. if(currentNode.nodeName() == "include")
  3986. {
  3987. currentElement = currentNode.toElement();
  3988. if(currentElement.hasAttribute("src"))
  3989. {
  3990. replacementDoc = directory.path();
  3991. replacementDoc.append('/');
  3992. replacementDoc.append(currentElement.attribute("src"));
  3993. QFile doc(replacementDoc);
  3994. if(doc.open(QIODevice::ReadOnly))
  3995. {
  3996. includedDoc.setContent(&doc, true);
  3997. fragment = includedDoc.createDocumentFragment();
  3998. fragment.appendChild(includedDoc.documentElement());
  3999. root.replaceChild(fragment, currentNode);
  4000. doc.close();
  4001. }
  4002. }
  4003. }
  4004. }
  4005. @ Simply loading the configuration document does not display a user interface or
  4006. set up any objects that allow the program to do anything. To do this, a script
  4007. obtained from the configuration document is run. The root element of the
  4008. document should be {\tt <application>}. This element should have a number of
  4009. child elements including {\tt <window>} elements which describe the various
  4010. windows that can be opened in the application and {\tt <program>} elements
  4011. containing script code. These {\tt <program>} elements can occur in a number of
  4012. different contexts including within {\tt <window>} elements which would indicate
  4013. that such scripts should be evaluated when the window being described is
  4014. created. After the configuration document is loaded, all {\tt <program>}
  4015. elements that are direct children of the {\tt <application>} element are
  4016. concatenated and the script is run.
  4017. Before the script is run and user interface elements are drawn, we also check
  4018. for {\tt <style>} elements which can be used to set up a stylesheet for the
  4019. application.
  4020. @<Find and evaluate starting script@>=
  4021. QString styleText;
  4022. QString programText;
  4023. QDomElement currentElement;
  4024. for(int i = 0; i < children.size(); i++)
  4025. {
  4026. QDomNode currentNode = children.at(i);
  4027. if(currentNode.nodeName() == "style")
  4028. {
  4029. currentElement = currentNode.toElement();
  4030. styleText.append(currentElement.text());
  4031. }
  4032. else if(currentNode.nodeName() == "program")
  4033. {
  4034. currentElement = currentNode.toElement();
  4035. programText.append(currentElement.text());
  4036. }
  4037. }
  4038. app.setStyleSheet(styleText);
  4039. QScriptValue result = engine->evaluate(programText);
  4040. @<Report scripting errors@>
  4041. @ When a script is evaluated, there is a chance that there will be some error in
  4042. the execution of that script. If this occurs, we want to report that.
  4043. @<Report scripting errors@>=
  4044. if(engine->hasUncaughtException())
  4045. {
  4046. int line = engine->uncaughtExceptionLineNumber();
  4047. qDebug() << "Uncaught excpetion at line " << line << " : " <<
  4048. result.toString();
  4049. QString trace;
  4050. foreach(trace, engine->uncaughtExceptionBacktrace())
  4051. {
  4052. qDebug() << trace;
  4053. }
  4054. }
  4055. @* Creating a window.
  4056. \noindent When a configuration document is loaded, none of the {\tt <window>}
  4057. elements are interpreted or used to create a graphical user interface. Instead,
  4058. any {\tt <program>} elements that are immediate children of the
  4059. {\tt <application>} element are interpreted. In order to convert a
  4060. {\tt <window>} element into a window displayed on screen, the script in the
  4061. {\tt <program>} elements must call a function to display a specified window.
  4062. Report windows can be produced by scripts in a similar, but slightly different
  4063. manner.
  4064. \danger This design works, but it'@q'@>s not particularly good design. It was written
  4065. under severe time constraints and should be redesigned or at least cleaned up
  4066. and reorganized.\endanger
  4067. @<Function prototypes for scripting@>=
  4068. QScriptValue createWindow(QScriptContext *context, QScriptEngine *engine);
  4069. QScriptValue createReport(QScriptContext *context, QScriptEngine *engine);
  4070. void addLayoutToWidget(QDomElement element, QStack<QWidget*> *widgetStack,
  4071. QStack<QLayout*> *layoutStack);
  4072. void addLayoutToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4073. QStack<QLayout *> *layoutStack);
  4074. void addSplitterToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4075. QStack<QLayout *> *layoutStack);
  4076. void addSplitterToSplitter(QDomElement element, QStack<QWidget *> *widgetStack,
  4077. QStack<QLayout *> *layoutStack);
  4078. void populateGridLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4079. QStack<QLayout *> *layoutStack);
  4080. void populateBoxLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4081. QStack<QLayout *> *layoutStack);
  4082. void populateSplitter(QDomElement element, QStack<QWidget *> *widgetStack,@|
  4083. QStack<QLayout *> *layoutStack);
  4084. void populateWidget(QDomElement element, QStack<QWidget *> *widgetStack,@|
  4085. QStack<QLayout *> *layoutStack);
  4086. void populateStackedLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4087. QStack<QLayout *> *layoutStack);
  4088. void populateFormLayout(QDomElement element, QStack<QWidget *> *widgetStack,@|
  4089. QStack<QLayout *> *layoutStack);
  4090. void addTemperatureDisplayToSplitter(QDomElement element,@|
  4091. QStack<QWidget *> *widgetStack,
  4092. QStack<QLayout *> *layoutStack);
  4093. void addTemperatureDisplayToLayout(QDomElement element,@|
  4094. QStack<QWidget *> *widgetStack,
  4095. QStack<QLayout *> *layoutStack);
  4096. void addTimerDisplayToSplitter(QDomElement element,@|
  4097. QStack<QWidget *> *widgetStack,
  4098. QStack<QLayout *> *layoutStack);
  4099. void addTimerDisplayToLayout(QDomElement element,@|
  4100. QStack<QWidget *> *widgetStack,
  4101. QStack<QLayout *> *layoutStack);
  4102. void addDecorationToSplitter(QDomElement element,@|
  4103. QStack<QWidget *> *widgetStack,
  4104. QStack<QLayout *> *layoutStack);
  4105. void addDecorationToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4106. QStack<QLayout *> *layoutStack);
  4107. void addWidgetToSplitter(QDomElement element, QStack<QWidget *> *widgetStack,
  4108. QStack<QLayout *> *layoutStack);
  4109. void addButtonToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4110. QStack<QLayout *> *layoutStack);
  4111. void addZoomLogToSplitter(QDomElement element, QStack<QWidget *> *widgetStack,
  4112. QStack<QLayout *> *layoutStack);
  4113. void addGraphToSplitter(QDomElement element, QStack<QWidget *> *widgetStack,
  4114. QStack<QLayout *> *layoutStack);
  4115. void addSqlDropToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4116. QStack<QLayout *> *layoutStack);
  4117. void addSaltToLayout(QDomElement element, QStack<QWidget *> *widgetStack,@|
  4118. QStack<QLayout *> *layoutStack);
  4119. void addLineToLayout(QDomElement element, QStack<QWidget *> *widgetStack,@|
  4120. QStack<QLayout *> *layoutStack);
  4121. void addTextToLayout(QDomElement element, QStack<QWidget *> *widgetStack,@|
  4122. QStack<QLayout *> *layoutStack);
  4123. void addSqlQueryViewToLayout(QDomElement element,
  4124. QStack<QWidget *> *widgetStack,
  4125. QStack<QLayout *> *layoutStack);
  4126. void addCalendarToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4127. QStack<QLayout *> *layoutStack);
  4128. void addSpinBoxToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4129. QStack<QLayout *> *layoutStack);
  4130. void addTimeEditToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4131. QStack<QLayout *> *layoutStack);
  4132. @ The functions for creating windows must be made available to the scripting
  4133. engine.
  4134. @<Set up the scripting engine@>=
  4135. engine->globalObject().setProperty("createWindow",
  4136. engine->newFunction(createWindow));
  4137. engine->globalObject().setProperty("createReport",
  4138. engine->newFunction(createReport));
  4139. @ This function must examine the configuration document in search of the
  4140. appropriate window element, parse the contents of that element, and create a
  4141. multitude of objects, all of which must be passed to the scripting engine.
  4142. @<Functions for scripting@>=
  4143. QScriptValue createWindow(QScriptContext *context, QScriptEngine *engine)@/
  4144. {
  4145. QString targetID = argument<QString>(0, context);
  4146. QDomNode element;
  4147. QScriptValue object;
  4148. @<Find the window element@>@;
  4149. if(!element.isNull())
  4150. {
  4151. @<Display the window@>@;
  4152. }
  4153. return object;
  4154. }
  4155. @ Report files are not part of the configuration document and must be created
  4156. differently. While there is a special menu type that handles all of this
  4157. without involving the host environment, scripted generation and manipulation of
  4158. report windows requires another function. This function will only work after a
  4159. window with a reports menu has been created.
  4160. @<Functions for scripting@>=
  4161. QScriptValue createReport(QScriptContext *context, QScriptEngine *engine)
  4162. {
  4163. QString targetID = argument<QString>(0, context);
  4164. QFile file(QString("reports:%1").arg(targetID));
  4165. QScriptValue object;
  4166. if(file.open(QIODevice::ReadOnly))
  4167. {
  4168. QDomDocument document;
  4169. document.setContent(&file, true);
  4170. QDomElement element = document.documentElement();
  4171. if(!element.isNull())
  4172. {
  4173. @<Display the window@>@;
  4174. }
  4175. file.close();
  4176. }
  4177. return object;
  4178. }
  4179. @ First we must locate the {\tt <window>} element. The most sensible way to do
  4180. this would require that each {\tt <window>} element has an ID attribute and
  4181. search the DOM tree for that ID. Unfortunately, as of this writing,
  4182. |QDomDocument::elementByID()| always returns a null element, so that won'@q'@>t work.
  4183. Instead, we search the tree for all {\tt <window>} elements and then examine
  4184. the resulting list to find the element with the appropriate ID.
  4185. @<Find the window element@>=
  4186. QDomNodeList windows =
  4187. AppInstance->configuration()->documentElement().elementsByTagName("window");
  4188. QDomNode nullNode;
  4189. int i = 0;
  4190. element = nullNode;
  4191. while(i < windows.count())
  4192. {
  4193. element = windows.at(i);
  4194. QDomNamedNodeMap attributes = element.attributes();
  4195. if(attributes.contains("id"))
  4196. {
  4197. if(attributes.namedItem("id").toAttr().value() == targetID)
  4198. {
  4199. break;
  4200. }
  4201. }
  4202. element = nullNode;
  4203. i++;
  4204. }
  4205. @ In order to display a window, we start by creating a new |ScriptQMainWindow|
  4206. and set the central widget of that window to a new |QWidget|. After this, we see
  4207. if the window element has any children and proceed to populate the window.
  4208. When creating child elements, care must be taken that all objects are descended
  4209. from the window. If an object is descended from the window and has an object
  4210. name, it will be possible for script code to recover the created object.
  4211. As of version 1.4, the window itself is given the value of its {\tt id}
  4212. attribute as an object name to facilitate automatic window geometry management.
  4213. @<Display the window@>=
  4214. ScriptQMainWindow *window = new ScriptQMainWindow;
  4215. window->setObjectName(targetID);
  4216. object = engine->newQObject(window);
  4217. setQMainWindowProperties(object, engine);
  4218. QWidget *central = new(QWidget);
  4219. central->setParent(window);
  4220. central->setObjectName("centralWidget");
  4221. window->setCentralWidget(central);
  4222. if(element.hasChildNodes())
  4223. {
  4224. @<Process window children@>@;
  4225. }
  4226. @<Insert help menu@>@;
  4227. window->show();
  4228. window->setupFinished();
  4229. @ Three element types make sense as top level children of a {\tt <window>}
  4230. element. An element representing a layout element can be used to apply that
  4231. layout to the central widget. An element representing a menu can be used to add
  4232. a menu to the window. A {\tt <program>} element can be used to specify a script
  4233. to be run after the window has been assembled.
  4234. \danger As the window comes with a blank central widget, elements representing
  4235. a widget to be used as the central widget of the window cannot be used directly
  4236. here. If only one widget is needed in the window, there is a need to create a
  4237. layout element and place that widget in the layout. Also note that there is not
  4238. enough error checking in the following code. Provide invalid input at your
  4239. peril.\endanger
  4240. Program fragments pulled from the window description are executed with the
  4241. newly created window available as {\tt this}. When such a fragment is run, the
  4242. entire description of the window will have already been evaluated and any
  4243. necessary objects created. Obtaining a child object of the window can be done
  4244. by calling |findChildObject()|.
  4245. @<Process window children@>=
  4246. QStack<QWidget*> widgetStack;
  4247. QStack<QLayout*> layoutStack;
  4248. QString windowScript;
  4249. widgetStack.push(central);
  4250. QDomNodeList windowChildren = element.childNodes();
  4251. int i = 0;
  4252. while(i < windowChildren.count())
  4253. {
  4254. QDomNode current;
  4255. QDomElement element;
  4256. current = windowChildren.at(i);
  4257. if(current.isElement())
  4258. {
  4259. element = current.toElement();
  4260. if(element.tagName() == "program")
  4261. {
  4262. windowScript.append(element.text());
  4263. }
  4264. else if(element.tagName() == "layout")
  4265. {
  4266. element.setAttribute("trcontext", "configuration");
  4267. addLayoutToWidget(element, &widgetStack, &layoutStack);
  4268. }
  4269. else if(element.tagName() == "menu")
  4270. {
  4271. @<Process menus@>@;
  4272. }
  4273. }
  4274. i++;
  4275. }
  4276. QScriptValue oldThis = context->thisObject();
  4277. context->setThisObject(object);
  4278. QScriptValue result = engine->evaluate(windowScript);
  4279. @<Report scripting errors@>@;
  4280. context->setThisObject(oldThis);
  4281. @ Elements representing menus may provide a number of child elements
  4282. representing the items in that menu. The XML portion of the configuration will
  4283. not provide any information on what these menu items do. The contents of the
  4284. {\tt <program>} element for the window will need to request the |QAction|
  4285. objects and connect a signal from that object to the desired functionality.
  4286. One special consideration is the Reports menu. This menu will populate itself
  4287. according to its own logic and will have a {\tt type} property of
  4288. {\tt "reports"} and a {\tt src} property indicating the directory where reports
  4289. can be found.
  4290. @<Process menus@>=
  4291. QMenuBar *bar = window->menuBar();
  4292. bar->setParent(window);
  4293. bar->setObjectName("menuBar");
  4294. if(element.hasAttribute("name"))
  4295. {
  4296. QMenu *menu = bar->addMenu(QCoreApplication::translate("configuration",
  4297. element.attribute("name").toUtf8().data()));
  4298. menu->setParent(bar);
  4299. if(element.hasAttribute("type"))
  4300. {
  4301. if(element.attribute("type") == "reports")
  4302. {
  4303. if(element.hasAttribute("src"))
  4304. {
  4305. @<Populate reports menu@>@;
  4306. }
  4307. }
  4308. }
  4309. if(element.hasChildNodes())
  4310. {
  4311. @<Process menu items@>@;
  4312. }
  4313. }
  4314. @ To add items to a menu, we check for {\tt <item>} elements under the
  4315. {\tt <menu>} element and create a |QAction| for each item.
  4316. @<Process menu items@>=
  4317. QDomNodeList menuItems = element.childNodes();
  4318. int j = 0;
  4319. while(j < menuItems.count())
  4320. {
  4321. QDomNode item = menuItems.at(j);
  4322. if(item.isElement())
  4323. {
  4324. QDomElement itemElement = item.toElement();
  4325. if(itemElement.tagName() == "item")
  4326. {
  4327. QAction *itemAction = new QAction(QCoreApplication::translate("configuration",
  4328. itemElement.text().toUtf8().data()), menu);
  4329. if(itemElement.hasAttribute("id"))
  4330. {
  4331. itemAction->setObjectName(itemElement.attribute("id"));
  4332. }
  4333. if(itemElement.hasAttribute("shortcut"))
  4334. {
  4335. itemAction->setShortcut(itemElement.attribute("shortcut"));
  4336. }
  4337. menu->addAction(itemAction);
  4338. }
  4339. else if(itemElement.tagName() == "separator")
  4340. {
  4341. menu->addSeparator();
  4342. }
  4343. else if(itemElement.tagName() == "plugins")
  4344. {
  4345. @<Process plugin item@>@;
  4346. }
  4347. }
  4348. j++;
  4349. }
  4350. @i helpmenu.w
  4351. @i licensewindow.w
  4352. @ A layout can contain a number of different elements including a variety of
  4353. widget types and other layouts. This function is responsible for applying any
  4354. layout class to the widget currently being populated and processing children of
  4355. the {\tt <layout>} element to populate that layout. External stacks are used to
  4356. keep track of which widgets and layouts are currently being populated. The
  4357. {\tt type} attribute is used to determine what sort of layout should be created.
  4358. Currently, {\tt horizontal}, {\tt vertical}, {\tt grid}, and {\tt stack} types
  4359. are supported. The first two resolve to |QBoxLayout| layouts, {\tt grid}
  4360. resolves to a |QGridLayout|, and {\tt stack} resolves to a |QStackedLayout|.
  4361. @<Functions for scripting@>=
  4362. void addLayoutToWidget(QDomElement element, QStack<QWidget*> *widgetStack,
  4363. QStack<QLayout*> *layoutStack)
  4364. {
  4365. if(element.hasAttribute("type"))
  4366. {
  4367. @<Create and populate layout@>@;
  4368. QWidget *widget = widgetStack->top();
  4369. if(layout)
  4370. {
  4371. widget->setLayout(layout);
  4372. }
  4373. layoutStack->pop();
  4374. }
  4375. }
  4376. @ As there are multiple places where a {\tt <layout>} element is parsed with
  4377. slightly different semantics, the code for creating and populating the layout is
  4378. broken out so that code written to support additional layout types only needs to
  4379. be written once.
  4380. @<Create and populate layout@>=
  4381. QLayout *layout;
  4382. QString layoutType = element.attribute("type");
  4383. if(layoutType == "horizontal")
  4384. {
  4385. layout = new QHBoxLayout;
  4386. layoutStack->push(layout);
  4387. populateBoxLayout(element, widgetStack, layoutStack);
  4388. }
  4389. else if(layoutType == "vertical")
  4390. {
  4391. layout = new QVBoxLayout;
  4392. layoutStack->push(layout);
  4393. populateBoxLayout(element, widgetStack, layoutStack);
  4394. }
  4395. else if(layoutType == "grid")
  4396. {
  4397. layout = new QGridLayout;
  4398. layoutStack->push(layout);
  4399. populateGridLayout(element, widgetStack, layoutStack);
  4400. }
  4401. else if(layoutType == "stack")
  4402. {
  4403. layout = new QStackedLayout;
  4404. layoutStack->push(layout);
  4405. populateStackedLayout(element, widgetStack, layoutStack);
  4406. }
  4407. else if(layoutType == "form")
  4408. {
  4409. layout = new QFormLayout;
  4410. layoutStack->push(layout);
  4411. populateFormLayout(element, widgetStack, layoutStack);
  4412. }
  4413. if(element.hasAttribute("id"))
  4414. {
  4415. layout->setObjectName(element.attribute("id"));
  4416. }
  4417. if(element.hasAttribute("spacing"))
  4418. {
  4419. layout->setSpacing(element.attribute("spacing").toInt());
  4420. }
  4421. if(element.hasAttribute("margin"))
  4422. {
  4423. int m = element.attribute("margin").toInt();
  4424. layout->setContentsMargins(m, m, m, m);
  4425. }
  4426. @ Any direct child of a form layout must be a {\tt <row>} element to specify
  4427. the label for the given row. The field for the given row will always be a
  4428. |QVBoxLayout| containing whatever is specified by children of the {\tt <row>}.
  4429. @<Functions for scripting@>=
  4430. void populateFormLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4431. QStack<QLayout *> *layoutStack)
  4432. {
  4433. QDomNodeList children = element.childNodes();
  4434. QFormLayout *layout = qobject_cast<QFormLayout *>(layoutStack->top());
  4435. for(int i = 0; i < children.count(); i++)
  4436. {
  4437. QDomNode current;
  4438. QDomElement currentElement;
  4439. current = children.at(i);
  4440. if(current.isElement())
  4441. {
  4442. currentElement = current.toElement();
  4443. if(currentElement.tagName() == "row")
  4444. {
  4445. QString label = QString();
  4446. if(currentElement.hasAttribute("label"))
  4447. {
  4448. label = currentElement.attribute("label");
  4449. }
  4450. QVBoxLayout *childLayout = new QVBoxLayout;
  4451. layoutStack->push(childLayout);
  4452. populateBoxLayout(currentElement, widgetStack, layoutStack);
  4453. if(label.isEmpty())
  4454. {
  4455. layout->addRow(childLayout);
  4456. }
  4457. else
  4458. {
  4459. layout->addRow(label, childLayout);
  4460. }
  4461. }
  4462. }
  4463. }
  4464. }
  4465. @ Stacked layouts are a bit different from the other types. A stacked layout has
  4466. an arbitrary number of {\tt <page>} children which are just a |QWidget| which
  4467. can have the same child elements as {\tt <widget>} elements elsewhere. Only the
  4468. first page will be visible initially, however it is possible to use script code
  4469. to set the currently visible page provided that an ID is set for the layout.
  4470. @<Functions for scripting@>=
  4471. void populateStackedLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4472. QStack<QLayout *> *layoutStack)
  4473. {
  4474. QDomNodeList children = element.childNodes();
  4475. QStackedLayout *layout = qobject_cast<QStackedLayout *>(layoutStack->top());
  4476. for(int i = 0; i < children.count(); i++)
  4477. {
  4478. QDomNode current;
  4479. QDomElement currentElement;
  4480. current = children.at(i);
  4481. if(current.isElement())
  4482. {
  4483. currentElement = current.toElement();
  4484. if(currentElement.tagName() == "page")
  4485. {
  4486. QWidget *widget = new QWidget;
  4487. layout->addWidget(widget);
  4488. widgetStack->push(widget);
  4489. currentElement.setAttribute("trcontext", "configuration");
  4490. populateWidget(currentElement, widgetStack, layoutStack);
  4491. widgetStack->pop();
  4492. }
  4493. }
  4494. }
  4495. }
  4496. @ A common use of stacked layouts is in the creation of tabbed interfaces, but
  4497. there are also many uses in \pn{} where the tabs are not required. Therefore,
  4498. tab bar creation requires a separate XML element.
  4499. @<Additional box layout elements@>=
  4500. else if(currentElement.tagName() == "tabbar")
  4501. {
  4502. addTabBarToLayout(currentElement, widgetStack, layoutStack);
  4503. }
  4504. @ The function used to create this follows the usual pattern.
  4505. @<Functions for scripting@>=
  4506. void addTabBarToLayout(QDomElement element, QStack<QWidget*> *, QStack<QLayout*> *layoutStack)
  4507. {
  4508. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  4509. QTabBar *widget = new QTabBar;
  4510. layout->addWidget(widget);
  4511. if(!element.attribute("id").isEmpty())
  4512. {
  4513. widget->setObjectName(element.attribute("id"));
  4514. }
  4515. }
  4516. @ Rather than define the tab set in XML, this is left to the host environment.
  4517. This means that some additional scripting support is required.
  4518. @<Set up the scripting engine@>=
  4519. constructor = engine->newFunction(constructQTabBar);
  4520. value = engine->newQMetaObject(&QTabBar::staticMetaObject, constructor);
  4521. engine->globalObject().setProperty("QTabBar", value);
  4522. @ The constructor is trivial.
  4523. @<Functions for scripting@>=
  4524. QScriptValue constructQTabBar(QScriptContext *, QScriptEngine *engine)
  4525. {
  4526. QScriptValue object = engine->newQObject(new QTabBar);
  4527. setQTabBarProperties(object, engine);
  4528. return object;
  4529. }
  4530. @ There are many functions that I might want to some day add support for, but
  4531. the immediate need is just creating the tabs in the first place.
  4532. @<Functions for scripting@>=
  4533. void setQTabBarProperties(QScriptValue value, QScriptEngine *engine)
  4534. {
  4535. setQWidgetProperties(value, engine);
  4536. value.setProperty("addTab", engine->newFunction(QTabBar_addTab));
  4537. }
  4538. QScriptValue QTabBar_addTab(QScriptContext *context, QScriptEngine *)
  4539. {
  4540. QTabBar *self = getself<QTabBar *>(context);
  4541. if(context->argumentCount() > 0)
  4542. {
  4543. self->addTab(argument<QString>(0, context));
  4544. }
  4545. else
  4546. {
  4547. context->throwError("Incorrect number of arguments passed to "@|
  4548. "QTabBar::addTab().");
  4549. }
  4550. return QScriptValue();
  4551. }
  4552. @ Function prototypes are needed.
  4553. @<Function prototypes for scripting@>=
  4554. QScriptValue constructQTabBar(QScriptContext *context, QScriptEngine *engine);
  4555. void setQTabBarProperties(QScriptValue value, QScriptEngine *engine);
  4556. QScriptValue QTabBar_addTab(QScriptContext *context, QScriptEngine *engine);
  4557. @ Using a grid layout is a bit different from using a box layout. Child elements
  4558. with various attributes are required to take full advantage of this layout type.
  4559. All direct children of a grid layout element should be {\tt <row>} elements
  4560. which may have optional {\tt height} and {\tt stretch} attributes which apply to
  4561. that row.
  4562. @<Functions for scripting@>=
  4563. void populateGridLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4564. QStack<QLayout *> *layoutStack)
  4565. {
  4566. QDomNodeList children = element.childNodes();
  4567. int row = -1;
  4568. QGridLayout *layout = qobject_cast<QGridLayout *>(layoutStack->top());
  4569. for(int i = 0; i < children.count(); i++)
  4570. {
  4571. QDomNode current;
  4572. QDomElement currentElement;
  4573. current = children.at(i);
  4574. if(current.isElement())
  4575. {
  4576. currentElement = current.toElement();
  4577. if(currentElement.tagName() == "row")
  4578. {
  4579. row++;
  4580. if(currentElement.hasAttribute("height"))
  4581. {
  4582. layout->setRowMinimumHeight(row,
  4583. currentElement.attribute("height").toInt());
  4584. }
  4585. if(currentElement.hasAttribute("stretch"))
  4586. {
  4587. layout->setRowStretch(row,
  4588. currentElement.attribute("stretch").toInt());
  4589. }
  4590. @<Populate grid layout row@>@;
  4591. }
  4592. }
  4593. }
  4594. }
  4595. @ Each {\tt <row>} may have arbitrarily many {\tt <column>} children. A row with
  4596. nothing in it or that is entirely populated by spanning cells from previous rows
  4597. might have no children.
  4598. The {\tt <column>} element supports several optional attributes. The
  4599. {\tt column} attribute can be used to specify which column the element refers
  4600. to. Sibling {\tt <column>} elements will refer to columns farther right unless
  4601. a lower column number is specified. This does mean that it is possible to
  4602. specify the same column more than once, however actually doing so is not
  4603. recommended. The {\tt width} attribute specifies the minimum width of the
  4604. column. If multiple cells in a column specify this attribute, the last one takes
  4605. priority. Similarly, the {\tt stretch} attribute specifies the column stretch.
  4606. The {\tt rowspan} and {\tt colspan} attributes can be used for cells that span
  4607. more than one row or column. A value of |-1| can be used to have the cell span
  4608. to the last row or column in the layout.
  4609. Once the attributes of the cell are known, a |QHBoxLayout| is added to the
  4610. layout at the appropriate location in the grid and it is this layout which is
  4611. further populated by child elements. Anything that can be placed under a
  4612. {\tt <layout>} element with {\tt "horizontal"} or {\tt "vertical"} {\tt type}
  4613. attribute can be a child of a {\tt <column>} element in this context.
  4614. @<Populate grid layout row@>=
  4615. int column = -1;
  4616. QDomNodeList rowChildren = currentElement.childNodes();
  4617. for(int j = 0; j < rowChildren.count(); j++)
  4618. {
  4619. QDomNode columnNode;
  4620. QDomElement columnElement;
  4621. columnNode = rowChildren.at(j);
  4622. if(columnNode.isElement())
  4623. {
  4624. columnElement = columnNode.toElement();
  4625. if(columnElement.tagName() == "column")
  4626. {
  4627. column++;
  4628. if(columnElement.hasAttribute("column"))
  4629. {
  4630. column = columnElement.attribute("column").toInt();
  4631. }
  4632. if(columnElement.hasAttribute("width"))
  4633. {
  4634. layout->setColumnMinimumWidth(column,
  4635. columnElement.attribute("width").toInt());
  4636. }
  4637. if(columnElement.hasAttribute("stretch"))
  4638. {
  4639. layout->setColumnStretch(column,
  4640. columnElement.attribute("stretch").toInt());
  4641. }
  4642. int hspan = 1;
  4643. int vspan = 1;
  4644. if(columnElement.hasAttribute("rowspan"))
  4645. {
  4646. vspan = columnElement.attribute("rowspan").toInt();
  4647. }
  4648. if(columnElement.hasAttribute("colspan"))
  4649. {
  4650. hspan = columnElement.attribute("colspan").toInt();
  4651. }
  4652. QHBoxLayout *cell = new QHBoxLayout;
  4653. layout->addLayout(cell, row, column, vspan, hspan);
  4654. layoutStack->push(cell);
  4655. columnElement.setAttribute("trcontext", "configuration");
  4656. populateBoxLayout(columnElement, widgetStack, layoutStack);
  4657. layoutStack->pop();
  4658. }
  4659. }
  4660. }
  4661. @ Box layouts are populated by checking for child elements representing
  4662. supported widget types and layouts and adding these to the current layout.
  4663. @<Functions for scripting@>=
  4664. void populateBoxLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4665. QStack<QLayout *> *layoutStack)
  4666. {
  4667. QDomNodeList children = element.childNodes();
  4668. for(int i = 0; i < children.count(); i++)
  4669. {
  4670. QDomNode current;
  4671. QDomElement currentElement;
  4672. current = children.at(i);
  4673. if(current.isElement())
  4674. {
  4675. currentElement = current.toElement();
  4676. currentElement.setAttribute("trcontext", "configuration");
  4677. if(currentElement.tagName() == "button")
  4678. {
  4679. addButtonToLayout(currentElement, widgetStack, layoutStack);
  4680. }
  4681. else if(currentElement.tagName() == "calendar")
  4682. {
  4683. addCalendarToLayout(currentElement, widgetStack, layoutStack);
  4684. }
  4685. else if(currentElement.tagName() == "timeedit")
  4686. {
  4687. addTimeEditToLayout(currentElement, widgetStack, layoutStack);
  4688. }
  4689. else if(currentElement.tagName() == "decoration")
  4690. {
  4691. addDecorationToLayout(currentElement, widgetStack,
  4692. layoutStack);
  4693. }
  4694. else if(currentElement.tagName() == "layout")
  4695. {
  4696. addLayoutToLayout(currentElement, widgetStack, layoutStack);
  4697. }
  4698. else if(currentElement.tagName() == "splitter")
  4699. {
  4700. addSplitterToLayout(currentElement, widgetStack, layoutStack);
  4701. }
  4702. else if(currentElement.tagName() == "label")
  4703. {
  4704. QBoxLayout *layout =
  4705. qobject_cast<QBoxLayout *>(layoutStack->top());
  4706. QLabel *label = new QLabel(QCoreApplication::translate(
  4707. "configuration",
  4708. currentElement.text().toUtf8().data()));
  4709. if(currentElement.hasAttribute("id"))
  4710. {
  4711. label->setObjectName(currentElement.attribute("id"));
  4712. }
  4713. layout->addWidget(label);
  4714. }
  4715. else if(currentElement.tagName() == "lcdtemperature")
  4716. {
  4717. addTemperatureDisplayToLayout(currentElement, widgetStack,
  4718. layoutStack);
  4719. }
  4720. else if(currentElement.tagName() == "lcdtimer")
  4721. {
  4722. addTimerDisplayToLayout(currentElement, widgetStack,
  4723. layoutStack);
  4724. }
  4725. else if(currentElement.tagName() == "line")
  4726. {
  4727. addLineToLayout(currentElement, widgetStack, layoutStack);
  4728. }
  4729. else if(currentElement.tagName() == "report")
  4730. {
  4731. addReportToLayout(currentElement, widgetStack, layoutStack);
  4732. }
  4733. else if(currentElement.tagName() == "sqldrop")
  4734. {
  4735. addSqlDropToLayout(currentElement, widgetStack, layoutStack);
  4736. }
  4737. else if(currentElement.tagName() == "sqltablearray")
  4738. {
  4739. addSaltToLayout(currentElement, widgetStack, layoutStack);
  4740. }
  4741. else if(currentElement.tagName() == "sqlview")
  4742. {
  4743. addSqlQueryViewToLayout(currentElement, widgetStack,
  4744. layoutStack);
  4745. }
  4746. else if(currentElement.tagName() == "textarea")
  4747. {
  4748. addTextToLayout(currentElement, widgetStack, layoutStack);
  4749. }
  4750. else if(currentElement.tagName() == "spinbox")
  4751. {
  4752. addSpinBoxToLayout(currentElement, widgetStack, layoutStack);
  4753. }
  4754. else if(currentElement.tagName() == "formarray")
  4755. {
  4756. addFormArrayToLayout(currentElement, widgetStack, layoutStack);
  4757. }
  4758. else if(currentElement.tagName() =="hscale")
  4759. {
  4760. addScaleControlToLayout(currentElement, widgetStack,
  4761. layoutStack);
  4762. }
  4763. else if(currentElement.tagName() == "vscale")
  4764. {
  4765. addIntensityControlToLayout(currentElement, widgetStack,
  4766. layoutStack);
  4767. }
  4768. else if(currentElement.tagName() == "webview")
  4769. {
  4770. addWebViewToLayout(currentElement, widgetStack, layoutStack);
  4771. }
  4772. else if(currentElement.tagName() == "stretch")
  4773. {
  4774. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  4775. layout->addStretch();
  4776. }
  4777. @<Additional box layout elements@>@;
  4778. }
  4779. }
  4780. }
  4781. @ Box layouts support adding additional layouts to the layout. The form of the
  4782. function is very similar to |addLayoutToWidget()|.
  4783. @<Functions for scripting@>=
  4784. void addLayoutToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4785. QStack<QLayout *> *layoutStack)
  4786. {
  4787. QLayout *targetLayout = layoutStack->pop();
  4788. QBoxLayout *boxLayout = qobject_cast<QBoxLayout *>(targetLayout);
  4789. if(element.hasAttribute("type"))
  4790. {
  4791. @<Create and populate layout@>@;
  4792. boxLayout->addLayout(layout);
  4793. layoutStack->pop();
  4794. }
  4795. layoutStack->push(targetLayout);
  4796. }
  4797. @ A splitter is similar to a layout in that it manages the size and position of
  4798. one or more widgets, however it is not a layout and therefore needs to be
  4799. handled separately.
  4800. @<Functions for scripting@>=
  4801. void addSplitterToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  4802. QStack<QLayout *> *layoutStack)
  4803. {
  4804. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  4805. QSplitter *splitter = new(QSplitter);
  4806. layout->addWidget(splitter);
  4807. @<Set up splitter@>@;
  4808. }
  4809. @ As there are multiple places where a splitter element must be examined, the
  4810. common code is set aside.
  4811. @<Set up splitter@>=
  4812. QString orientation = element.attribute("type");
  4813. if(orientation == "horizontal")
  4814. {
  4815. splitter->setOrientation(Qt::Horizontal);
  4816. }
  4817. else if(orientation == "vertical")
  4818. {
  4819. splitter->setOrientation(Qt::Vertical);
  4820. }
  4821. QString id = element.attribute("id");
  4822. if(!id.isEmpty())
  4823. {
  4824. splitter->setObjectName(id);
  4825. }
  4826. if(element.hasChildNodes())
  4827. {
  4828. widgetStack->push(splitter);
  4829. populateSplitter(element, widgetStack, layoutStack);
  4830. widgetStack->pop();
  4831. }
  4832. @ When populating a splitter, it is important to note that only widgets can be
  4833. added. If a layout is needed, this can be handled by adding a |QWidget| and
  4834. applying the layout to that widget.
  4835. @<Functions for scripting@>=
  4836. void populateSplitter(QDomElement element, QStack<QWidget *> *widgetStack,@|
  4837. QStack<QLayout *> *layoutStack)
  4838. {
  4839. QDomNodeList children = element.childNodes();
  4840. for(int i = 0; i < children.count(); i++)
  4841. {
  4842. QDomNode current;
  4843. QDomElement currentElement;
  4844. current = children.at(i);
  4845. if(current.isElement())
  4846. {
  4847. currentElement = current.toElement();
  4848. currentElement.setAttribute("trcontext", "configuration");
  4849. if(currentElement.tagName() == "decoration")
  4850. {
  4851. addDecorationToSplitter(currentElement, widgetStack,
  4852. layoutStack);
  4853. }
  4854. else if(currentElement.tagName() == "graph")
  4855. {
  4856. addGraphToSplitter(currentElement, widgetStack, layoutStack);
  4857. }
  4858. else if(currentElement.tagName() == "splitter")
  4859. {
  4860. addSplitterToSplitter(currentElement, widgetStack, layoutStack);
  4861. }
  4862. else if(currentElement.tagName() == "lcdtemperature")
  4863. {
  4864. addTemperatureDisplayToSplitter(currentElement, widgetStack,
  4865. layoutStack);
  4866. }
  4867. else if(currentElement.tagName() == "lcdtimer")
  4868. {
  4869. addTimerDisplayToSplitter(currentElement, widgetStack,
  4870. layoutStack);
  4871. }
  4872. else if(currentElement.tagName() == "measurementtable")
  4873. {
  4874. addZoomLogToSplitter(currentElement, widgetStack, layoutStack);
  4875. }
  4876. else if(currentElement.tagName() == "widget")
  4877. {
  4878. addWidgetToSplitter(currentElement, widgetStack, layoutStack);
  4879. }
  4880. }
  4881. }
  4882. }
  4883. @ Adding a splitter to a splitter is similar to adding it to a layout.
  4884. @<Functions for scripting@>=
  4885. void addSplitterToSplitter(QDomElement element, QStack<QWidget *> *widgetStack,
  4886. QStack<QLayout *> *layoutStack)
  4887. {
  4888. QSplitter *parent = qobject_cast<QSplitter *>(widgetStack->top());
  4889. QSplitter *splitter = new(QSplitter);
  4890. splitter->setParent(parent);
  4891. parent->addWidget(splitter);
  4892. @<Set up splitter@>@;
  4893. }
  4894. @ Temperature displays are useful to have in an application such as this. At
  4895. present, this code only supports the {\tt id} attribute. It may be useful in the
  4896. future to allow other attributes for changing default attributes of the
  4897. indicator rather than needing to pull the object from script code and set
  4898. changes there.
  4899. @<Functions for scripting@>=
  4900. void addTemperatureDisplayToSplitter(QDomElement element,@|
  4901. QStack<QWidget *> *widgetStack,
  4902. QStack<QLayout *> *)
  4903. {
  4904. TemperatureDisplay *display = new(TemperatureDisplay);
  4905. if(element.hasAttribute("id"))
  4906. {
  4907. display->setObjectName(element.attribute("id"));
  4908. }
  4909. QSplitter *splitter = qobject_cast<QSplitter *>(widgetStack->top());
  4910. splitter->addWidget(display);
  4911. }
  4912. void addTemperatureDisplayToLayout(QDomElement element,@|
  4913. QStack<QWidget *> *,
  4914. QStack<QLayout *> *layoutStack)
  4915. {
  4916. TemperatureDisplay *display = new(TemperatureDisplay);
  4917. if(element.hasAttribute("id"))
  4918. {
  4919. display->setObjectName(element.attribute("id"));
  4920. }
  4921. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  4922. layout->addWidget(display);
  4923. }
  4924. @ Timer displays are similarly useful to have. The default format for a timer
  4925. display is {\tt hh:mm:ss}, but this can be changed through the {\tt format}
  4926. attribute of an {\tt <lcdtimer>} element.
  4927. @<Functions for scripting@>=
  4928. void addTimerDisplayToSplitter(QDomElement element,@|
  4929. QStack<QWidget *> *widgetStack,
  4930. QStack<QLayout *> *)
  4931. {
  4932. TimerDisplay *display = new(TimerDisplay);
  4933. if(element.hasAttribute("id"))
  4934. {
  4935. display->setObjectName(element.attribute("id"));
  4936. }
  4937. if(element.hasAttribute("format"))
  4938. {
  4939. display->setDisplayFormat(element.attribute("format"));
  4940. }
  4941. QSplitter *splitter = qobject_cast<QSplitter *>(widgetStack->top());
  4942. splitter->addWidget(display);
  4943. }
  4944. void addTimerDisplayToLayout(QDomElement element,@|
  4945. QStack<QWidget *> *,
  4946. QStack<QLayout *> *layoutStack)
  4947. {
  4948. TimerDisplay *display = new(TimerDisplay);
  4949. if(element.hasAttribute("id"))
  4950. {
  4951. display->setObjectName(element.attribute("id"));
  4952. }
  4953. if(element.hasAttribute("format"))
  4954. {
  4955. display->setDisplayFormat(element.attribute("format"));
  4956. }
  4957. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  4958. layout->addWidget(display);
  4959. }
  4960. @ When multiple timer or temperature displays are required, it can be useful to
  4961. provide a label to indicate just what is being measured.
  4962. @<Functions for scripting@>=
  4963. void addDecorationToLayout(QDomElement element, QStack<QWidget *> *,@|
  4964. QStack<QLayout *> *layoutStack)
  4965. {
  4966. @<Set up decoration@>@;
  4967. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  4968. layout->addWidget(decoration);
  4969. }
  4970. void addDecorationToSplitter(QDomElement element,
  4971. QStack<QWidget *> *widgetStack,
  4972. QStack<QLayout *> *)
  4973. {
  4974. @<Set up decoration@>@;
  4975. QSplitter *splitter = qobject_cast<QSplitter *>(widgetStack->top());
  4976. splitter->addWidget(decoration);
  4977. }
  4978. @ The decoration needs a label text, an orientation, and the widget to be
  4979. labeled.
  4980. @<Set up decoration@>=
  4981. QString labelText =
  4982. QCoreApplication::translate("configuration",
  4983. element.attribute("name").toUtf8().data());
  4984. Qt::Orientations@, orientation = Qt::Horizontal;
  4985. if(element.hasAttribute("type"))
  4986. {
  4987. if(element.attribute("type") == "horizontal")
  4988. {
  4989. orientation = Qt::Horizontal;
  4990. }
  4991. else if(element.attribute("type") == "vertical")
  4992. {
  4993. orientation = Qt::Vertical;
  4994. }
  4995. }
  4996. @<Find widget to decorate@>@;
  4997. WidgetDecorator *decoration = new WidgetDecorator(theWidget, labelText,
  4998. orientation);
  4999. if(element.hasAttribute("id"))
  5000. {
  5001. decoration->setObjectName(element.attribute("id"));
  5002. }
  5003. @ The widget to decorate should be found as a child of the {\tt <decoration>}
  5004. element.
  5005. @<Find widget to decorate@>=
  5006. QWidget *theWidget = NULL;
  5007. QDomNodeList children = element.childNodes();
  5008. for(int i = 0; i < children.count(); i++)
  5009. {
  5010. QDomNode item = children.at(i);
  5011. if(item.isElement())
  5012. {
  5013. QDomElement itemElement = item.toElement();
  5014. if(itemElement.tagName() == "lcdtemperature")
  5015. {
  5016. TemperatureDisplay *display = new TemperatureDisplay;
  5017. if(itemElement.hasAttribute("id"))
  5018. {
  5019. display->setObjectName(itemElement.attribute("id"));
  5020. }
  5021. theWidget = display;
  5022. }
  5023. else if(itemElement.tagName() == "lcdtimer")
  5024. {
  5025. TimerDisplay *display = new TimerDisplay;
  5026. if(itemElement.hasAttribute("id"))
  5027. {
  5028. display->setObjectName(itemElement.attribute("id"));
  5029. }
  5030. if(itemElement.hasAttribute("format"))
  5031. {
  5032. display->setDisplayFormat(itemElement.attribute("format"));
  5033. }
  5034. theWidget = display;
  5035. }
  5036. }
  5037. }
  5038. @ As splitters cannot contain layouts directly, there is a need to allow
  5039. otherwise empty widgets to be included in a splitter for cases where a splitter
  5040. should manage several widgets together as a group. A row of annotation buttons
  5041. is an example of such a layout.
  5042. When splitters are used as a way to hide optional features it sometimes has the
  5043. effect of forcing a window to stay larger than should be required. To fix this,
  5044. it is possible to set the \tt{ignoreSizePolicy} attribute to true. While this
  5045. does solve the window size issue, this technique is inconsistent with generally
  5046. expected behavior and its use should generally be discouraged.
  5047. @<Functions for scripting@>=
  5048. void addWidgetToSplitter(QDomElement element, QStack<QWidget *> *widgetStack,
  5049. QStack<QLayout *> *layoutStack)
  5050. {
  5051. QSplitter *splitter = qobject_cast<QSplitter *>(widgetStack->top());
  5052. QWidget *widget = new QWidget;
  5053. if(element.hasAttribute("id"))
  5054. {
  5055. widget->setObjectName(element.attribute("id"));
  5056. }
  5057. if(element.hasAttribute("ignoreSizePolicy"))
  5058. {
  5059. if(element.attribute("ignoreSizePolicy") == "true")
  5060. {
  5061. widget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
  5062. }
  5063. }
  5064. splitter->addWidget(widget);
  5065. if(element.hasChildNodes())
  5066. {
  5067. widgetStack->push(widget);
  5068. populateWidget(element, widgetStack, layoutStack);
  5069. widgetStack->pop();
  5070. }
  5071. }
  5072. void populateWidget(QDomElement element, QStack<QWidget *> *widgetStack,@|
  5073. QStack<QLayout *> *layoutStack)
  5074. {
  5075. QDomNodeList children = element.childNodes();
  5076. for(int i = 0; i < children.count(); i++)
  5077. {
  5078. QDomNode current;
  5079. QDomElement currentElement;
  5080. current = children.at(i);
  5081. if(current.isElement())
  5082. {
  5083. currentElement = current.toElement();
  5084. if(currentElement.tagName() == "layout")
  5085. {
  5086. currentElement.setAttribute("trcontext", "configuration");
  5087. addLayoutToWidget(currentElement, widgetStack, layoutStack);
  5088. }
  5089. }
  5090. }
  5091. }
  5092. @ There are two types of buttons that can be added to a layout. There are normal
  5093. push buttons and there are annotation buttons. Other button types may be added
  5094. in the future.
  5095. @<Functions for scripting@>=
  5096. void addButtonToLayout(QDomElement element, QStack<QWidget *> *,@|
  5097. QStack<QLayout *> *layoutStack)
  5098. {
  5099. QAbstractButton *button = NULL;
  5100. QString text =
  5101. QCoreApplication::translate("configuration",
  5102. element.attribute("name").toUtf8().data());
  5103. if(element.hasAttribute("type"))
  5104. {
  5105. QString type = element.attribute("type");
  5106. if(type == "annotation")
  5107. {
  5108. AnnotationButton *abutton = new AnnotationButton(text);
  5109. if(element.hasAttribute("annotation"))
  5110. {
  5111. abutton->setAnnotation(element.attribute("annotation"));
  5112. }
  5113. if(element.hasAttribute("series"))
  5114. {
  5115. abutton->setTemperatureColumn(element.attribute("series").
  5116. toInt());
  5117. }
  5118. if(element.hasAttribute("column"))
  5119. {
  5120. abutton->setAnnotationColumn(element.attribute("column").
  5121. toInt());
  5122. }
  5123. button = abutton;
  5124. }
  5125. else if(type == "check")
  5126. {
  5127. button = new QCheckBox(text);
  5128. }
  5129. else if(type == "push")
  5130. {
  5131. button = new QPushButton(text);
  5132. }
  5133. }
  5134. if(element.hasAttribute("id"))
  5135. {
  5136. button->setObjectName(element.attribute("id"));
  5137. }
  5138. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  5139. layout->addWidget(button);
  5140. }
  5141. @ While annotation buttons are useful for many batch notes, a spin box is
  5142. sometimes a better input choice. There are several attributes that can be set on
  5143. a spin box. These include text to be included in the annotation before and after
  5144. the value of the spin box, the temperature and annotation columns, the range of
  5145. values available in the spin box, the precision of allowed values, and the
  5146. amount by which increment and decrement operations change the value.
  5147. @<Functions for scripting@>=
  5148. void addSpinBoxToLayout(QDomElement element, QStack<QWidget *> *,@|
  5149. QStack<QLayout *> *layoutStack)
  5150. {
  5151. AnnotationSpinBox *box = new AnnotationSpinBox("", "", NULL);
  5152. if(element.hasAttribute("pretext"))
  5153. {
  5154. box->setPretext(QCoreApplication::translate(
  5155. "configuration",
  5156. element.attribute("pretext").toUtf8().data()));
  5157. }
  5158. if(element.hasAttribute("posttext"))
  5159. {
  5160. box->setPosttext(QCoreApplication::translate(
  5161. "configuration",
  5162. element.attribute("posttext").toUtf8().data()));
  5163. }
  5164. if(element.hasAttribute("series"))
  5165. {
  5166. box->setTemperatureColumn(element.attribute("series").toInt());
  5167. }
  5168. if(element.hasAttribute("column"))
  5169. {
  5170. box->setAnnotationColumn(element.attribute("column").toInt());
  5171. }
  5172. if(element.hasAttribute("min"))
  5173. {
  5174. box->setMinimum(element.attribute("min").toDouble());
  5175. }
  5176. if(element.hasAttribute("max"))
  5177. {
  5178. box->setMaximum(element.attribute("max").toDouble());
  5179. }
  5180. if(element.hasAttribute("decimals"))
  5181. {
  5182. box->setDecimals(element.attribute("decimals").toInt());
  5183. }
  5184. if(element.hasAttribute("step"))
  5185. {
  5186. box->setSingleStep(element.attribute("step").toDouble());
  5187. }
  5188. if(element.hasAttribute("id"))
  5189. {
  5190. box->setObjectName(element.attribute("id"));
  5191. }
  5192. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  5193. layout->addWidget(box);
  5194. }
  5195. @ Previously, in order to change a |ZoomLog| from the default set of columns,
  5196. script code would need to alter the column set. While this works fine on a Mac,
  5197. this did not work very well under Windows. For the current version, I would like
  5198. to remove the need to deal with table columns from the host environment. The
  5199. first step for this is allowing column descriptions in XML. After this, I'@q'@>d like
  5200. to remove the default column set from the widget code and provide some better
  5201. functionality for dealing with additional data sets.
  5202. When creating the |ZoomLog| here, we check for {\tt <column>} child elements
  5203. which specify the names of the columns.
  5204. @<Functions for scripting@>=
  5205. void addZoomLogToSplitter(QDomElement element, QStack<QWidget *> *widgetStack,
  5206. QStack<QLayout *> *)
  5207. {
  5208. ZoomLog *widget = new ZoomLog;
  5209. if(element.hasAttribute("id"))
  5210. {
  5211. widget->setObjectName(element.attribute("id"));
  5212. }
  5213. if(element.hasChildNodes())
  5214. {
  5215. QDomNodeList children = element.childNodes();
  5216. int column = 0;
  5217. for(int i = 0; i < children.count(); i++)
  5218. {
  5219. QDomNode current;
  5220. QDomElement currentElement;
  5221. current = children.at(i);
  5222. if(current.isElement())
  5223. {
  5224. currentElement = current.toElement();
  5225. if(currentElement.tagName() == "column")
  5226. {
  5227. QString text =
  5228. QCoreApplication::translate(
  5229. "configuration",
  5230. currentElement.text().toUtf8().data());
  5231. widget->setHeaderData(column, text);
  5232. column++;
  5233. }
  5234. }
  5235. }
  5236. }
  5237. QSplitter *splitter = qobject_cast<QSplitter *>(widgetStack->top());
  5238. if(splitter)
  5239. {
  5240. splitter->addWidget(widget);
  5241. }
  5242. else
  5243. {
  5244. qDebug() << "Splitter not found at top of widget stack!";
  5245. }
  5246. }
  5247. @ The last of the widgets needed to duplicate the window provided in previous
  5248. versions of \pn{} is the |GraphView|.
  5249. @<Functions for scripting@>=
  5250. void addGraphToSplitter(QDomElement element, QStack<QWidget *> *widgetStack,
  5251. QStack<QLayout *> *)
  5252. {
  5253. GraphView *view = new GraphView;
  5254. if(element.hasAttribute("id"))
  5255. {
  5256. view->setObjectName(element.attribute("id"));
  5257. }
  5258. QSplitter *splitter = qobject_cast<QSplitter *>(widgetStack->top());
  5259. splitter->addWidget(view);
  5260. }
  5261. @ When interacting with a database, it can be useful to provide a combo box
  5262. populated by the results of a database query. One way to do this is through a
  5263. |SqlComboBox| widget.
  5264. @<Functions for scripting@>=
  5265. void addSqlDropToLayout(QDomElement element, QStack<QWidget *> *,@|
  5266. QStack<QLayout *> *layoutStack)
  5267. {
  5268. SqlComboBox *box = new SqlComboBox();
  5269. if(element.hasAttribute("data"))
  5270. {
  5271. box->setDataColumn(element.attribute("data").toInt());
  5272. }
  5273. if(element.hasAttribute("display"))
  5274. {
  5275. box->setDisplayColumn(element.attribute("display").toInt());
  5276. }
  5277. if(element.hasAttribute("showdata"))
  5278. {
  5279. if(element.attribute("showdata") == "true")
  5280. {
  5281. box->showData(true);
  5282. }
  5283. }
  5284. if(element.hasAttribute("editable"))
  5285. {
  5286. if(element.attribute("editable") == "true")
  5287. {
  5288. box->setEditable(true);
  5289. }
  5290. }
  5291. if(element.hasChildNodes())
  5292. {
  5293. QDomNodeList children = element.childNodes();
  5294. for(int i = 0; i < children.count(); i++)
  5295. {
  5296. QDomNode current;
  5297. QDomElement currentElement;
  5298. current = children.at(i);
  5299. if(current.isElement())
  5300. {
  5301. currentElement = current.toElement();
  5302. if(currentElement.tagName() == "null")
  5303. {
  5304. box->addNullOption();
  5305. }
  5306. else if(currentElement.tagName() == "query")
  5307. {
  5308. box->addSqlOptions(currentElement.text());
  5309. }
  5310. }
  5311. }
  5312. }
  5313. if(element.hasAttribute("id"))
  5314. {
  5315. box->setObjectName(element.attribute("id"));
  5316. }
  5317. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  5318. layout->addWidget(box);
  5319. }
  5320. @ The next database aware widget that can be useful to have in \pn{} is a
  5321. {\bf S}QL {\bf A}rray {\bf L}iteral {\bf T}able. As might be apparent from the
  5322. name, this is a table view with an associated model and delegates appropriate
  5323. for creating ordered arrays to pass into a database. Each column represents an
  5324. array of values. The most common use of this is in cases where it is important
  5325. to produce multiple arrays of the same size in which each element of one array
  5326. is related to the element in the same position of another array. For example,
  5327. when roasting coffee there are times when some may want to add more than one
  5328. coffee to the roaster at a time. In order to correctly track the green coffee
  5329. inventory and so that the roasting log may have an accurate record of what is
  5330. really happening, insertions on the roasting log provide two arrays, one
  5331. representing all of the coffees being added to the roaster, the other the amount
  5332. of each of these coffees. The database can then use a trigger function to
  5333. examine these arrays and produce the necessary entries in the use table which in
  5334. turn update the record containing the amount of each green coffee currently in
  5335. stock.
  5336. While a generic |QTableView| is used here, there is a need to add functionality
  5337. specific to using this table with a |SaltModel| when obtaining this widget from
  5338. the host environment. In order to accomodate this, we add a dynamic property to
  5339. the view to identify the type of table in the absense of a unique class name.
  5340. @<Functions for scripting@>=
  5341. void addSaltToLayout(QDomElement element, QStack<QWidget *> *,@|
  5342. QStack<QLayout *> *layoutStack)
  5343. {
  5344. QTableView *view = new QTableView;
  5345. view->setProperty("tabletype", QVariant(QString("SaltTable")));
  5346. SaltModel *model = new SaltModel(element.childNodes().count());
  5347. if(element.hasAttribute("id"))
  5348. {
  5349. view->setObjectName(element.attribute("id"));
  5350. }
  5351. if(element.hasAttribute("editable"))
  5352. {
  5353. if(element.attribute("editable") == "false")
  5354. {
  5355. view->setEditTriggers(QAbstractItemView::NoEditTriggers);
  5356. }
  5357. }
  5358. if(element.hasAttribute("selectionBehavior"))
  5359. {
  5360. if(element.attribute("selectionBehavior") == "items")
  5361. {
  5362. view->setSelectionBehavior(QAbstractItemView::SelectItems);
  5363. }
  5364. else if(element.attribute("selectionBehavior") == "rows")
  5365. {
  5366. view->setSelectionBehavior(QAbstractItemView::SelectRows);
  5367. }
  5368. else if(element.attribute("selectionBehavior") == "columns")
  5369. {
  5370. view->setSelectionBehavior(QAbstractItemView::SelectColumns);
  5371. }
  5372. }
  5373. if(element.hasChildNodes())
  5374. {
  5375. QDomNodeList children = element.childNodes();
  5376. int currentColumn = 0;
  5377. for(int i = 0; i < children.count(); i++)
  5378. {
  5379. QDomNode current;
  5380. QDomElement currentElement;
  5381. current = children.at(i);
  5382. if(current.isElement())
  5383. {
  5384. currentElement = current.toElement();
  5385. if(currentElement.tagName() == "column")
  5386. {
  5387. if(currentElement.hasAttribute("name"))
  5388. {
  5389. model->setHeaderData(currentColumn,
  5390. Qt::Horizontal,
  5391. QCoreApplication::translate(
  5392. "configuration",
  5393. currentElement.attribute("name").toUtf8().data()));
  5394. }
  5395. if(currentElement.hasAttribute("delegate"))
  5396. {
  5397. @<Set column delegate from XML attribute@>@;
  5398. }
  5399. currentColumn++;
  5400. }
  5401. }
  5402. }
  5403. }
  5404. view->setModel(model);
  5405. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  5406. layout->addWidget(view);
  5407. }
  5408. @ It is often desirable to restrict the allowed values in an entry to either a
  5409. set of specific values or to a particular type of value. Delegates can be set
  5410. on a column to enforce such restrictions.
  5411. @<Set column delegate from XML attribute@>=
  5412. if(currentElement.attribute("delegate") == "sql")
  5413. {
  5414. @<Assign column delegate from SQL@>@;
  5415. }
  5416. else if(currentElement.attribute("delegate") == "numeric")
  5417. {
  5418. @<Assign numeric column delegate@>@;
  5419. }
  5420. else if(currentElement.attribute("delegate") == "positivenumeric")
  5421. {
  5422. @<Assign positive numeric column delegate@>@;
  5423. }
  5424. @ When using a |SaltModel|, there are times where the array values being
  5425. inserted are identification numbers representing some record that already exists
  5426. in the database. For example, the id number representing a green coffee in the
  5427. table of items. In such a case, it is beneficial to provide a delegate capable
  5428. of presenting a human readable list of choices.
  5429. @<Assign column delegate from SQL@>=
  5430. SqlComboBoxDelegate *delegate = new SqlComboBoxDelegate;
  5431. SqlComboBox *widget = new SqlComboBox();
  5432. if(currentElement.hasAttribute("nulltext"))
  5433. {
  5434. widget->setNullText(currentElement.attribute("nulltext"));
  5435. }
  5436. if(currentElement.hasAttribute("nulldata"))
  5437. {
  5438. widget->setNullData(QVariant(currentElement.attribute("nulldata")));
  5439. }
  5440. if(currentElement.hasAttribute("null"))
  5441. {
  5442. if(currentElement.attribute("null") == "true")
  5443. {
  5444. widget->addNullOption();
  5445. }
  5446. }
  5447. if(currentElement.hasAttribute("showdata"))
  5448. {
  5449. if(currentElement.attribute("showdata") == "true")
  5450. {
  5451. widget->showData(true);
  5452. }
  5453. }
  5454. if(currentElement.hasAttribute("data"))
  5455. {
  5456. widget->setDataColumn(currentElement.attribute("data").toInt());
  5457. }
  5458. if(currentElement.hasAttribute("display"))
  5459. {
  5460. widget->setDisplayColumn(currentElement.attribute("display").toInt());
  5461. }
  5462. if(currentElement.hasAttribute("editable"))
  5463. {
  5464. if(currentElement.attribute("editable") == "true")
  5465. {
  5466. widget->setEditable(true);
  5467. }
  5468. }
  5469. widget->addSqlOptions(currentElement.text());
  5470. delegate->setWidget(widget);
  5471. view->setItemDelegateForColumn(currentColumn, delegate);
  5472. @ Another common use is allowing numeric values.
  5473. @<Assign numeric column delegate@>=
  5474. NumericDelegate *delegate = new NumericDelegate;
  5475. view->setItemDelegateForColumn(currentColumn, delegate);
  5476. @ It is also possible to restrict allowed numeric values to non-negative
  5477. values.
  5478. @<Assign positive numeric column delegate@>=
  5479. NumericDelegate *delegate = new NumericDelegate(true);
  5480. view->setItemDelegateForColumn(currentColumn, delegate);
  5481. @ The |NumericDelegate| will only set the display value to a number, but it
  5482. will perform mathematical calculations that are entered into the editor as
  5483. well. This allows a person to type something like $13.26+5.06$ with the result
  5484. of the expression ($18.32$) appearing in the table.
  5485. @<Class declarations@>=
  5486. class NumericDelegate : public QItemDelegate@/
  5487. {
  5488. @[Q_OBJECT@]@;
  5489. public:@/
  5490. NumericDelegate(bool positiveOnly = false, QObject *parent = NULL);
  5491. QWidget *createEditor(QWidget *parent,
  5492. const QStyleOptionViewItem &option,@|
  5493. const QModelIndex &index) const;
  5494. void setEditorData(QWidget *editor, const QModelIndex &index) const;
  5495. void setModelData(QWidget *editor, QAbstractItemModel *model,@|
  5496. const QModelIndex &index) const;
  5497. void updateEditorGeometry(QWidget *editor,
  5498. const QStyleOptionViewItem &option,@|
  5499. const QModelIndex &index) const;
  5500. private:
  5501. bool m_positiveOnly;
  5502. };
  5503. @ There is nothing special about the constructor.
  5504. @<NumericDelegate implementation@>=
  5505. NumericDelegate::NumericDelegate(bool positiveOnly, QObject *parent) :
  5506. QItemDelegate(parent), m_positiveOnly(positiveOnly)
  5507. {
  5508. /* Nothing needs to be done here. */
  5509. }
  5510. @ Two roles are used by this delegate. The edit role should contain whatever
  5511. text has been entered in the editor while the display role contain the numeric
  5512. result of any expression that has been entered. Our editor only requires the
  5513. first of these.
  5514. @<NumericDelegate implementation@>=
  5515. void NumericDelegate::setEditorData(QWidget *editor,
  5516. const QModelIndex &index) const
  5517. {
  5518. QString value = index.model()->data(index, Qt::EditRole).toString();
  5519. QLineEdit *line = static_cast<QLineEdit*>(editor);
  5520. line->setText(value);
  5521. }
  5522. @ When editing is finished, the expression text must be saved back to the
  5523. model and the expression should be evaluated to set the display role. We make
  5524. use of the existing scripting engine to evaluate the expression, but only
  5525. preserve the result in the display role if the result of that expression is
  5526. numeric.
  5527. @<NumericDelegate implementation@>=
  5528. void NumericDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
  5529. const QModelIndex &index) const
  5530. {
  5531. QLineEdit *line = static_cast<QLineEdit*>(editor);
  5532. model->setData(index, line->text(), Qt::EditRole);
  5533. QScriptEngine *engine = AppInstance->engine;
  5534. engine->pushContext();
  5535. QString script = QString("Number(%1)").arg(line->text());
  5536. QScriptValue result = engine->evaluate(line->text());
  5537. if(result.isNumber())
  5538. {
  5539. if(m_positiveOnly)
  5540. {
  5541. if(result.toNumber() < 0)
  5542. {
  5543. model->setData(index, QVariant(), Qt::DisplayRole);
  5544. }
  5545. else
  5546. {
  5547. model->setData(index, result.toVariant(), Qt::DisplayRole);
  5548. }
  5549. }
  5550. else
  5551. {
  5552. model->setData(index, result.toVariant(), Qt::DisplayRole);
  5553. }
  5554. }
  5555. else
  5556. {
  5557. model->setData(index, QVariant(), Qt::DisplayRole);
  5558. }
  5559. engine->popContext();
  5560. }
  5561. @ There is nothing special about the line edit used for this.
  5562. @<NumericDelegate implementation@>=
  5563. QWidget* NumericDelegate::createEditor(QWidget *parent,
  5564. const QStyleOptionViewItem &,
  5565. const QModelIndex &) const
  5566. {
  5567. return (new QLineEdit(parent));
  5568. }
  5569. @ To ensure that the editor is displayed appropriately, we must pass the
  5570. geometry data to our editor.
  5571. @<NumericDelegate implementation@>=
  5572. void NumericDelegate::updateEditorGeometry(QWidget *editor,
  5573. const QStyleOptionViewItem &option,
  5574. const QModelIndex &) const
  5575. {
  5576. editor->setGeometry(option.rect);
  5577. }
  5578. @ The same general technique is useful for input to a |QLineEdit|, but there is
  5579. no model backing that widget to preserve multiple data roles. One way to get
  5580. this functionality without too much effort is to abuse |QValidator| to evaluate
  5581. expressions.
  5582. @<Class declarations@>=
  5583. class ScriptValidator : public QValidator
  5584. {
  5585. Q_OBJECT
  5586. public:
  5587. ScriptValidator(QValidator *validator, QObject *parent = NULL);
  5588. void fixup(QString &input) const;
  5589. QValidator::State validate(QString &input, int &pos) const;
  5590. private:
  5591. QValidator *v;
  5592. };
  5593. @ The key to this is in the |fixup()| method. Here we over-write input with the
  5594. result of the script evaluation.
  5595. @<ScriptValidator implementation@>=
  5596. void ScriptValidator::fixup(QString &input) const
  5597. {
  5598. QScriptEngine *engine = AppInstance->engine;
  5599. engine->pushContext();
  5600. input = engine->evaluate(input).toString();
  5601. engine->popContext();
  5602. }
  5603. @ This validator is intended to be used in conjunction with another one which
  5604. determines if the result of the expression is acceptable. In this case
  5605. |Invalid| is never returned.
  5606. @<ScriptValidator implementation@>=
  5607. QValidator::State ScriptValidator::validate(QString &input, int &pos) const
  5608. {
  5609. if(v)
  5610. {
  5611. if(v->validate(input, pos) == QValidator::Acceptable)
  5612. {
  5613. return QValidator::Acceptable;
  5614. }
  5615. }
  5616. return QValidator::Intermediate;
  5617. }
  5618. @ The constructor is trivial.
  5619. @<ScriptValidator implementation@>=
  5620. ScriptValidator::ScriptValidator(QValidator *validator, QObject *parent)
  5621. : QValidator(parent), v(validator)
  5622. {
  5623. /* Nothing needs to be done here. */
  5624. }
  5625. @ This is included in typica.cpp.
  5626. @<Class implementations@>=
  5627. @<ScriptValidator implementation@>
  5628. @ Line edits are useful when the user is expected to enter text without a
  5629. predetermined set of values.
  5630. Several attributes are supported on line edits. In addition to the usual
  5631. {\tt id} attribute, there is also a {\tt writable} attribute which, if
  5632. {\tt false}, can be used to create read only text areas which can only be edited
  5633. from script code. A {\tt validator} attribute allows entered text to be
  5634. restricted. This can take one of three values. If the value is {\tt "numeric"},
  5635. input is restricted to numeric values. If the value is {\tt "integer"}, input is
  5636. restricted to integer values. Finally, if the value is {\tt "expression"}, input
  5637. is restricted to text which matches a regular expression specified as the value
  5638. of the {\tt expression} attribute.
  5639. Note that when integer and numeric validators are specified, these are set for
  5640. a |ScriptValidator| to enable some basic expression evaluation.
  5641. @<Functions for scripting@>=
  5642. void addLineToLayout(QDomElement element, QStack<QWidget *> *,@|
  5643. QStack<QLayout *> *layoutStack)
  5644. {
  5645. QLineEdit *widget = new QLineEdit(element.text());
  5646. if(element.hasAttribute("id"))
  5647. {
  5648. widget->setObjectName(element.attribute("id"));
  5649. }
  5650. if(element.hasAttribute("writable"))
  5651. {
  5652. if(element.attribute("writable") == "false")
  5653. {
  5654. widget->setReadOnly(true);
  5655. }
  5656. }
  5657. if(element.hasAttribute("validator"))
  5658. {
  5659. if(element.attribute("validator") == "numeric")
  5660. {
  5661. widget->setValidator(new ScriptValidator(new QDoubleValidator));
  5662. }
  5663. else if(element.attribute("validator") == "integer")
  5664. {
  5665. widget->setValidator(new ScriptValidator(new QIntValidator));
  5666. }
  5667. else if(element.attribute("validator") == "expression" &&
  5668. element.hasAttribute("expression"))
  5669. {
  5670. widget->setValidator(new QRegExpValidator(
  5671. QRegExp(element.attribute("expression")),
  5672. NULL));
  5673. }
  5674. }
  5675. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  5676. layout->addWidget(widget);
  5677. }
  5678. @ It is natural for certain database fields to enter potentially large amounts
  5679. of free form text, for example, notes and annotations.
  5680. @<Functions for scripting@>=
  5681. void addTextToLayout(QDomElement element, QStack<QWidget *> *,@|
  5682. QStack<QLayout *> *layoutStack)
  5683. {
  5684. QTextEdit *widget = new QTextEdit;
  5685. if(element.hasAttribute("id"))
  5686. {
  5687. widget->setObjectName(element.attribute("id"));
  5688. }
  5689. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  5690. layout->addWidget(widget);
  5691. }
  5692. @ The common use of |SqlQueryView| calls for the possibility of changing the
  5693. query during use. As such, there is little reason to accept attributes other
  5694. than an id for obtaining the view in a script.
  5695. @<Functions for scripting@>=
  5696. void addSqlQueryViewToLayout(QDomElement element,
  5697. QStack<QWidget *> *,@|
  5698. QStack<QLayout *> *layoutStack)
  5699. {
  5700. SqlQueryView *view = new SqlQueryView;
  5701. if(element.hasAttribute("id"))
  5702. {
  5703. view->setObjectName(element.attribute("id"));
  5704. }
  5705. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  5706. layout->addWidget(view);
  5707. }
  5708. @ When the user is expected to enter a date, it is appropriate to use a date
  5709. editor. This one provides a calendar.
  5710. @<Functions for scripting@>=
  5711. void addCalendarToLayout(QDomElement element, QStack<QWidget *> *,@|
  5712. QStack<QLayout *> *layoutStack)
  5713. {
  5714. QWidget *widget;
  5715. if(element.hasAttribute("time"))
  5716. {
  5717. if(element.attribute("time") == "true")
  5718. {
  5719. QDateTimeEdit *edit = new QDateTimeEdit;
  5720. edit->setDateTime(QDateTime::currentDateTime());
  5721. edit->setCalendarPopup(true);
  5722. edit->setDisplayFormat("yyyy-MM-dd hh:mm:ss");
  5723. widget = qobject_cast<QWidget *>(edit);
  5724. }
  5725. else
  5726. {
  5727. QDateEdit *edit = new QDateEdit;
  5728. edit->setDate(QDate::currentDate());
  5729. edit->setCalendarPopup(true);
  5730. edit->setDisplayFormat("yyyy-MM-dd");
  5731. widget = qobject_cast<QWidget *>(edit);
  5732. }
  5733. }
  5734. else
  5735. {
  5736. QDateEdit *edit = new QDateEdit;
  5737. edit->setDate(QDate::currentDate());
  5738. edit->setCalendarPopup(true);
  5739. edit->setDisplayFormat("yyyy-MM-dd");
  5740. widget = qobject_cast<QWidget *>(edit);
  5741. }
  5742. if(element.hasAttribute("id"))
  5743. {
  5744. widget->setObjectName(element.attribute("id"));
  5745. }
  5746. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  5747. layout->addWidget(widget);
  5748. }
  5749. @ Some additional properties are added to this object when it is retrieved by
  5750. the host environment.
  5751. @<Functions for scripting@>=
  5752. void setQDateEditProperties(QScriptValue value, QScriptEngine *engine)
  5753. {
  5754. setQDateTimeEditProperties(value, engine);
  5755. }
  5756. void setQDateTimeEditProperties(QScriptValue value, QScriptEngine *engine)
  5757. {
  5758. setQAbstractSpinBoxProperties(value, engine);
  5759. value.setProperty("setDate", engine->newFunction(QDateTimeEdit_setDate));
  5760. value.setProperty("day", engine->newFunction(QDateTimeEdit_day));
  5761. value.setProperty("month", engine->newFunction(QDateTimeEdit_month));
  5762. value.setProperty("year", engine->newFunction(QDateTimeEdit_year));
  5763. value.setProperty("setToCurrentTime",
  5764. engine->newFunction(QDateTimeEdit_setToCurrentTime));
  5765. }
  5766. @ Certain operations on a |QDateEdit| are easier with a few convenience
  5767. properties that bypass the need to use the built in |date| property. For
  5768. example, an editor that should be set to 1 January of the current year can
  5769. obtain the year and set the date without directly using a |QDate| object.
  5770. @<Functions for scripting@>=
  5771. QScriptValue QDateTimeEdit_setDate(QScriptContext *context, QScriptEngine *)
  5772. {
  5773. QDateTimeEdit *self = getself<QDateTimeEdit *>(context);
  5774. if(context->argumentCount() == 3)
  5775. {
  5776. self->setDate(QDate(argument<int>(0, context),
  5777. argument<int>(1, context),
  5778. argument<int>(2, context)));
  5779. }
  5780. else
  5781. {
  5782. context->throwError("Incorrect number of arguments passed to "
  5783. "QDateTimeEdit::setDate(). This method takes three integer arguments "
  5784. "specifying the year, month, and day.");
  5785. }
  5786. return QScriptValue();
  5787. }
  5788. QScriptValue QDateTimeEdit_day(QScriptContext *context, QScriptEngine *)
  5789. {
  5790. QDateTimeEdit *self = getself<QDateTimeEdit *>(context);
  5791. return QScriptValue(self->date().day());
  5792. }
  5793. QScriptValue QDateTimeEdit_month(QScriptContext *context, QScriptEngine *)
  5794. {
  5795. QDateTimeEdit *self = getself<QDateTimeEdit *>(context);
  5796. return QScriptValue(self->date().month());
  5797. }
  5798. QScriptValue QDateTimeEdit_year(QScriptContext *context, QScriptEngine *)
  5799. {
  5800. QDateTimeEdit *self = getself<QDateTimeEdit *>(context);
  5801. return QScriptValue(self->date().year());
  5802. }
  5803. QScriptValue QDateTimeEdit_setToCurrentTime(QScriptContext *context, QScriptEngine *)
  5804. {
  5805. QDateTimeEdit *self = getself<QDateTimeEdit *>(context);
  5806. self->setDateTime(QDateTime::currentDateTime());
  5807. return QScriptValue();
  5808. }
  5809. @ A few function prototypes are needed for this.
  5810. @<Function prototypes for scripting@>=
  5811. void setQDateEditProperties(QScriptValue value, QScriptEngine *engine);
  5812. void setQDateTimeEditProperties(QScriptValue value, QScriptEngine *engine);
  5813. QScriptValue QDateTimeEdit_setDate(QScriptContext *context,
  5814. QScriptEngine *engine);
  5815. QScriptValue QDateTimeEdit_day(QScriptContext *context, QScriptEngine *engine);
  5816. QScriptValue QDateTimeEdit_month(QScriptContext *context,
  5817. QScriptEngine *engine);
  5818. QScriptValue QDateTimeEdit_year(QScriptContext *context, QScriptEngine *engine);
  5819. QScriptValue QDateTimeEdit_setToCurrentTime(QScriptContext *context, QScriptEngine *engine);
  5820. @ Sometimes it can be useful to allow editing a time or duration value without
  5821. a date field. For this, a |QTimeEdit| can be used.
  5822. @<Functions for scripting@>=
  5823. void addTimeEditToLayout(QDomElement element, QStack<QWidget *> *,@|
  5824. QStack<QLayout *> *layoutStack)
  5825. {
  5826. QTimeEdit *edit = new QTimeEdit;
  5827. if(element.hasAttribute("displayFormat"))
  5828. {
  5829. edit->setDisplayFormat(element.attribute("displayFormat"));
  5830. }
  5831. else
  5832. {
  5833. edit->setDisplayFormat("mm:ss.zzz");
  5834. }
  5835. if(element.hasAttribute("id"))
  5836. {
  5837. edit->setObjectName(element.attribute("id"));
  5838. }
  5839. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  5840. layout->addWidget(edit);
  5841. }
  5842. @ Additional properties are added as a |QTimeEdit| is a |QDateTimeEdit|.
  5843. @<Functions for scripting@>=
  5844. void setQTimeEditProperties(QScriptValue value, QScriptEngine *engine)
  5845. {
  5846. setQDateTimeEditProperties(value, engine);
  5847. }
  5848. @ A function prototype is needed.
  5849. @<Function prototypes for scripting@>=
  5850. void setQTimeEditProperties(QScriptValue value, QScriptEngine *engine);
  5851. @ In order to get to objects created from the XML description, it is necessary
  5852. to provide a function that can be called to retrieve children of a given widget.
  5853. When providing such an object to the script, it is necessary to determine the
  5854. type of that object and add the appropriate properties.
  5855. @<Function prototypes for scripting@>=
  5856. QScriptValue findChildObject(QScriptContext *context, QScriptEngine *engine);
  5857. @ This function must be made available to the scripting engine.
  5858. @<Set up the scripting engine@>=
  5859. engine->globalObject().setProperty("findChildObject",
  5860. engine->newFunction(findChildObject));
  5861. @ This function takes a script value representing some object which may have
  5862. been created from an XML description and a string containing the name of the
  5863. requested child element.
  5864. @<Functions for scripting@>=
  5865. QScriptValue findChildObject(QScriptContext *context, QScriptEngine *engine)
  5866. {
  5867. QObject *parent = argument<QObject *>(0, context);
  5868. QString name = argument<QString>(1, context);
  5869. QObject *object = parent->findChild<QObject *>(name);
  5870. QScriptValue value;
  5871. if(object)
  5872. {
  5873. value = engine->newQObject(object);
  5874. QString className = object->metaObject()->className();
  5875. @<Set object properties based on class name@>@;
  5876. }
  5877. return value;
  5878. }
  5879. @ Properties are added for a large number of class types.
  5880. @<Set object properties based on class name@>=
  5881. if(className == "TemperatureDisplay")
  5882. {
  5883. setTemperatureDisplayProperties(value, engine);
  5884. }
  5885. else if(className == "TimerDisplay")
  5886. {
  5887. setTimerDisplayProperties(value, engine);
  5888. }
  5889. else if(className == "QAction")
  5890. {
  5891. setQActionProperties(value, engine);
  5892. }
  5893. else if(className == "QBoxLayout")
  5894. {
  5895. setQBoxLayoutProperties(value, engine);
  5896. }
  5897. else if(className == "QDateEdit")
  5898. {
  5899. setQDateEditProperties(value, engine);
  5900. }
  5901. else if(className == "QDateTimeEdit")
  5902. {
  5903. setQDateTimeEditProperties(value, engine);
  5904. }
  5905. else if(className == "QFrame")
  5906. {
  5907. setQFrameProperties(value, engine);
  5908. }
  5909. else if(className == "QHBoxLayout")
  5910. {
  5911. setQBoxLayoutProperties(value, engine);
  5912. }
  5913. else if(className == "QLCDNumber")
  5914. {
  5915. setQLCDNumberProperties(value, engine);
  5916. }
  5917. else if(className == "QMenu")
  5918. {
  5919. setQMenuProperties(value, engine);
  5920. }
  5921. else if(className == "QMenuBar")
  5922. {
  5923. setQMenuBarProperties(value, engine);
  5924. }
  5925. else if(className == "QPushButton")
  5926. {
  5927. setQPushButtonProperties(value, engine);
  5928. }
  5929. else if(className == "QSplitter")
  5930. {
  5931. setQSplitterProperties(value, engine);
  5932. }
  5933. else if(className == "QTableView")
  5934. {
  5935. if(object->property("tabletype").isValid())
  5936. {
  5937. if(object->property("tabletype").toString() == "SaltTable")
  5938. {
  5939. setSaltTableProperties(value, engine);
  5940. }
  5941. }
  5942. }
  5943. else if(className == "QVBoxLayout")
  5944. {
  5945. setQBoxLayoutProperties(value, engine);
  5946. }
  5947. else if(className == "QWidget")
  5948. {
  5949. setQWidgetProperties(value, engine);
  5950. }
  5951. else if(className == "ScriptQMainWindow")
  5952. {
  5953. setQMainWindowProperties(value, engine);
  5954. }
  5955. else if(className == "SqlComboBox")
  5956. {
  5957. setSqlComboBoxProperties(value, engine);
  5958. }
  5959. else if(className == "SqlQueryView")
  5960. {
  5961. setSqlQueryViewProperties(value, engine);
  5962. }
  5963. else if(className == "ZoomLog")
  5964. {
  5965. setZoomLogProperties(value, engine);
  5966. }
  5967. else if(className == "QTextEdit")
  5968. {
  5969. setQTextEditProperties(value, engine);
  5970. }
  5971. else if(className == "QWebView")
  5972. {
  5973. setQWebViewProperties(value, engine);
  5974. }
  5975. else if(className == "QLineEdit")
  5976. {
  5977. setQLineEditProperties(value, engine);
  5978. }
  5979. else if(className == "QSvgWidget")
  5980. {
  5981. setQSvgWidgetProperties(value, engine);
  5982. }
  5983. else if(className == "QTabBar")
  5984. {
  5985. setQTabBarProperties(value, engine);
  5986. }
  5987. else if(className == "PrinterSelector")
  5988. {
  5989. setQComboBoxProperties(value, engine);
  5990. }
  5991. @ In the list of classes, the SaltTable entry is for a class which does not
  5992. strictly exist on its own. It is, however, useful to provide some custom
  5993. properties when passing such an object to the host environment.
  5994. @<Function prototypes for scripting@>=
  5995. void setSaltTableProperties(QScriptValue value, QScriptEngine *engine);
  5996. QScriptValue SaltTable_bindableColumnArray(QScriptContext *context,
  5997. QScriptEngine *engine);
  5998. QScriptValue SaltTable_bindableQuotedColumnArray(QScriptContext *context,
  5999. QScriptEngine *engine);
  6000. QScriptValue SaltTable_columnSum(QScriptContext *context,
  6001. QScriptEngine *engine);
  6002. QScriptValue SaltTable_columnArray(QScriptContext *context,
  6003. QScriptEngine *engine);
  6004. QScriptValue SaltTable_data(QScriptContext *context, QScriptEngine *engine);
  6005. QScriptValue SaltTable_model(QScriptContext *context, QScriptEngine *engine);
  6006. QScriptValue SaltTable_quotedColumnArray(QScriptContext *context,
  6007. QScriptEngine *engine);
  6008. QScriptValue SaltTable_setData(QScriptContext *context, QScriptEngine *engine);
  6009. QScriptValue SaltTable_clear(QScriptContext *context, QScriptEngine *engine);
  6010. QScriptValue SaltTable_removeRow(QScriptContext *context, QScriptEngine *engine);
  6011. QScriptValue SaltTable_findData(QScriptContext *context, QScriptEngine *engine);
  6012. @ There are times when it is useful to obtain the sum of values in a column of
  6013. a SaltTable object. For example, when a column represents the weight of the
  6014. green coffee used in a batch, a sum of that column provides the total weight of
  6015. the batch which, when presented to the user, can be used to catch errors in
  6016. measuring batches or entering data.
  6017. @<Functions for scripting@>=
  6018. QScriptValue SaltTable_columnSum(QScriptContext *context, QScriptEngine *engine)
  6019. {
  6020. QTableView *self = getself<QTableView *>(context);
  6021. SaltModel *model = qobject_cast<SaltModel *>(self->model());
  6022. QString datum;
  6023. double total = 0.0;
  6024. int column = argument<int>(0, context);
  6025. int role = argument<int>(1, context);
  6026. for(int i = 0; i < model->rowCount(); i++)
  6027. {
  6028. datum = model->data(model->index(i, column), role).toString();
  6029. if(!datum.isEmpty())
  6030. {
  6031. total += datum.toDouble();
  6032. }
  6033. }
  6034. return QScriptValue(engine, total);
  6035. }
  6036. @ Another common use of the SaltTable is producing the text for an array literal
  6037. to pass into a SQL query. The |SaltModel| used by this table makes this simple.
  6038. There are four functions for this functionality for different use cases.
  6039. @<Functions for scripting@>=
  6040. QScriptValue SaltTable_columnArray(QScriptContext *context,
  6041. QScriptEngine *engine)
  6042. {
  6043. QTableView *self = getself<QTableView *>(context);
  6044. SaltModel *model = qobject_cast<SaltModel *>(self->model());
  6045. int column = argument<int>(0, context);
  6046. int role = argument<int>(1, context);
  6047. QString literal = model->arrayLiteral(column, role);
  6048. return QScriptValue(engine, literal);
  6049. }
  6050. QScriptValue SaltTable_quotedColumnArray(QScriptContext *context,
  6051. QScriptEngine *engine)
  6052. {
  6053. QTableView *self = getself<QTableView *>(context);
  6054. SaltModel *model = qobject_cast<SaltModel *>(self->model());
  6055. int column = argument<int>(0, context);
  6056. int role = argument<int>(1, context);
  6057. QString literal = model->quotedArrayLiteral(column, role);
  6058. return QScriptValue(engine, literal);
  6059. }
  6060. QScriptValue SaltTable_bindableColumnArray(QScriptContext *context,
  6061. QScriptEngine *engine)
  6062. {
  6063. QTableView *self = getself<QTableView *>(context);
  6064. SaltModel *model = qobject_cast<SaltModel *>(self->model());
  6065. int column = argument<int>(0, context);
  6066. int role = argument<int>(1, context);
  6067. QString literal = model->arrayLiteral(column, role);
  6068. literal.chop(1);
  6069. literal = literal.remove(0, 1);
  6070. return QScriptValue(engine, literal);
  6071. }
  6072. QScriptValue SaltTable_bindableQuotedColumnArray(QScriptContext *context,
  6073. QScriptEngine *engine)
  6074. {
  6075. QTableView *self = getself<QTableView *>(context);
  6076. SaltModel *model = qobject_cast<SaltModel *>(self->model());
  6077. int column = argument<int>(0, context);
  6078. int role = argument<int>(1, context);
  6079. QString literal = model->quotedArrayLiteral(column, role);
  6080. literal.chop(1);
  6081. literal = literal.remove(0, 1);
  6082. return QScriptValue(engine, literal);
  6083. }
  6084. @ In order to obtain signals related to changes in the model, we need a way to
  6085. get to the model from the host environment.
  6086. @<Functions for scripting@>=
  6087. QScriptValue SaltTable_model(QScriptContext *context, QScriptEngine *engine)
  6088. {
  6089. QTableView *self = getself<QTableView *>(context);
  6090. QScriptValue value = engine->newQObject(self->model());
  6091. return value;
  6092. }
  6093. @ While this table was originally intended strictly for user input, there are a
  6094. few use cases in which it is useful to allow scripts to set the values in the
  6095. table. This can be done with |setData|. This method takes four arguments: the
  6096. row and column of the table being set, the value to set, and the role of the
  6097. data being set.
  6098. @<Functions for scripting@>=
  6099. QScriptValue SaltTable_setData(QScriptContext *context, QScriptEngine *)
  6100. {
  6101. QTableView *self = getself<QTableView *>(context);
  6102. int row = argument<int>(0, context);
  6103. int column = argument<int>(1, context);
  6104. QVariant value = argument<QVariant>(2, context);
  6105. int role = argument<int>(3, context);
  6106. SaltModel *model = qobject_cast<SaltModel *>(self->model());
  6107. QModelIndex cell = model->index(row, column);
  6108. model->setData(cell, value, role);
  6109. self->update(cell);
  6110. return QScriptValue();
  6111. }
  6112. @ It is sometimes useful to obtain the data from a single cell of the table.
  6113. This can be done with the |data()| property.
  6114. @<Functions for scripting@>=
  6115. QScriptValue SaltTable_data(QScriptContext *context, QScriptEngine *engine)
  6116. {
  6117. QTableView *self = getself<QTableView *>(context);
  6118. int row = argument<int>(0, context);
  6119. int column = argument<int>(1, context);
  6120. int role = argument<int>(2, context);
  6121. SaltModel *model = qobject_cast<SaltModel *>(self->model());
  6122. QModelIndex cell = model->index(row, column);
  6123. QVariant value = model->data(cell, role);
  6124. QScriptValue retval = engine->newVariant(value);
  6125. retval.setProperty("value", QScriptValue(value.toString()));
  6126. return retval;
  6127. }
  6128. @ There are times when it is useful to clear the content of a table. This is
  6129. used, for example, in the green coffees table after changing the roasted coffee
  6130. item to eliminate excess rows in the case where the previously selected item
  6131. was a pre-roast blend.
  6132. @<Functions for scripting@>=
  6133. QScriptValue SaltTable_clear(QScriptContext *context, QScriptEngine *)
  6134. {
  6135. QTableView *self = getself<QTableView *>(context);
  6136. SaltModel *model = qobject_cast<SaltModel *>(self->model());
  6137. model->clear();
  6138. return QScriptValue();
  6139. }
  6140. @ It is sometimes useful to remove a row from a table. This is done in the new
  6141. batch window when the coffee for a row is set to a NULL item.
  6142. @<Functions for scripting@>=
  6143. QScriptValue SaltTable_removeRow(QScriptContext *context, QScriptEngine *engine)
  6144. {
  6145. QTableView *self = getself<QTableView *>(context);
  6146. SaltModel *model = qobject_cast<SaltModel *>(self->model());
  6147. int row = argument<int>(0, context);
  6148. return engine->newVariant(model->removeRow(row));
  6149. }
  6150. @ To remove the correct row, it is sometimes useful to query the table for
  6151. special values. This is done with the |findData()| method on the underlying
  6152. model.
  6153. @<Functions for scripting@>=
  6154. QScriptValue SaltTable_findData(QScriptContext *context, QScriptEngine *engine)
  6155. {
  6156. QTableView *self = getself<QTableView *>(context);
  6157. SaltModel *model = qobject_cast<SaltModel *>(self->model());
  6158. QVariant value = argument<QVariant>(0, context);
  6159. int column = argument<int>(1, context);
  6160. return engine->newVariant(model->findData(value, column));
  6161. }
  6162. @ These functions need to be added as properties of the table when it is passed
  6163. to the host environment.
  6164. @<Functions for scripting@>=
  6165. void setSaltTableProperties(QScriptValue value, QScriptEngine *engine)
  6166. {
  6167. setQWidgetProperties(value, engine);
  6168. value.setProperty("columnArray",
  6169. engine->newFunction(SaltTable_columnArray));
  6170. value.setProperty("quotedColumnArray",
  6171. engine->newFunction(SaltTable_quotedColumnArray));
  6172. value.setProperty("bindableColumnArray",
  6173. engine->newFunction(SaltTable_bindableColumnArray));
  6174. value.setProperty("bindableQuotedColumnArray",
  6175. engine->newFunction(SaltTable_bindableQuotedColumnArray));
  6176. value.setProperty("columnSum", engine->newFunction(SaltTable_columnSum));
  6177. value.setProperty("data", engine->newFunction(SaltTable_data));
  6178. value.setProperty("model", engine->newFunction(SaltTable_model));
  6179. value.setProperty("setData", engine->newFunction(SaltTable_setData));
  6180. value.setProperty("clear", engine->newFunction(SaltTable_clear));
  6181. value.setProperty("removeRow", engine->newFunction(SaltTable_removeRow));
  6182. value.setProperty("findData", engine->newFunction(SaltTable_findData));
  6183. }
  6184. @ The |SqlComboBox| is another class that is not constructed from scripts but is
  6185. useful to access from them. A property is added to obtain the current user data
  6186. from the widget.
  6187. @<Function prototypes for scripting@>=
  6188. void setSqlComboBoxProperties(QScriptValue value, QScriptEngine *engine);
  6189. void setQComboBoxProperties(QScriptValue value, QScriptEngine *engine);
  6190. QScriptValue QComboBox_currentData(QScriptContext *context,
  6191. QScriptEngine *engine);
  6192. QScriptValue QComboBox_addItem(QScriptContext *context, QScriptEngine *engine);
  6193. QScriptValue QComboBox_setModel(QScriptContext *context, QScriptEngine *engine);
  6194. QScriptValue QComboBox_findText(QScriptContext *context, QScriptEngine *engine);
  6195. QScriptValue QComboBox_findData(QScriptContext *context, QScriptEngine *engine);
  6196. @ These functions should seem familiar by now.
  6197. @<Functions for scripting@>=
  6198. void setSqlComboBoxProperties(QScriptValue value, QScriptEngine *engine)
  6199. {
  6200. setQComboBoxProperties(value, engine);
  6201. }
  6202. void setQComboBoxProperties(QScriptValue value, QScriptEngine *engine)
  6203. {
  6204. setQWidgetProperties(value, engine);
  6205. value.setProperty("currentData",
  6206. engine->newFunction(QComboBox_currentData));
  6207. value.setProperty("addItem", engine->newFunction(QComboBox_addItem));
  6208. value.setProperty("setModel", engine->newFunction(QComboBox_setModel));
  6209. value.setProperty("findText", engine->newFunction(QComboBox_findText));
  6210. value.setProperty("findData", engine->newFunction(QComboBox_findData));
  6211. }
  6212. QScriptValue QComboBox_currentData(QScriptContext *context,
  6213. QScriptEngine *engine)
  6214. {
  6215. QComboBox *self = getself<QComboBox *>(context);
  6216. return QScriptValue(engine,
  6217. self->itemData(self->currentIndex()).toString());
  6218. }
  6219. QScriptValue QComboBox_addItem(QScriptContext *context, QScriptEngine *)
  6220. {
  6221. QComboBox *self = getself<QComboBox *>(context);
  6222. self->addItem(argument<QString>(0, context));
  6223. return QScriptValue();
  6224. }
  6225. QScriptValue QComboBox_setModel(QScriptContext *context, QScriptEngine *)
  6226. {
  6227. QComboBox *self = getself<QComboBox *>(context);
  6228. self->setModel(argument<QAbstractItemModel *>(0, context));
  6229. return QScriptValue();
  6230. }
  6231. QScriptValue QComboBox_findText(QScriptContext *context, QScriptEngine *engine)
  6232. {
  6233. QComboBox *self = getself<QComboBox *>(context);
  6234. return QScriptValue(engine, self->findText(argument<QString>(0, context)));
  6235. }
  6236. QScriptValue QComboBox_findData(QScriptContext *context, QScriptEngine *engine)
  6237. {
  6238. QComboBox *self = getself<QComboBox *>(context);
  6239. return QScriptValue(engine, self->findData(argument<QVariant>(0, context)));
  6240. }
  6241. @i abouttypica.w
  6242. @** A representation of temperature measurements.
  6243. \noindent Most of the information in a roast log will be temperature
  6244. measurements. These measurements are made of two components: the measured
  6245. temperature and the time at which that measurement was taken.
  6246. Measurement times are represented as instances of |QTime|.
  6247. @i units.w
  6248. @ We will require the |units.h| header.
  6249. @<Header files to include@>=
  6250. #include "units.h"
  6251. @i measurement.w
  6252. @** The Main Measurement Pipeline.
  6253. \noindent Measurements are sent through \pn{} in a way similar to liquid moving
  6254. through a series of connected pipes. \pn{} is not something that you just dump
  6255. measurements on. It'@q'@>s not a big truck\nfnote{Senator Ted Stevens (R-Alaska) on
  6256. network neutrality, June 28, 2006\par
  6257. \hbox{\indent\pdfURL{http://media.publicknowledge.org/stevens-on-nn.mp3}%
  6258. {http://media.publicknowledge.org/stevens-on-nn.mp3}}}. In most cases the
  6259. connections between classes (represented by arrows in Figure \secno) are made
  6260. with Qt'@q'@>s signals and slots mechanism\nfnote{Qt 4.4.3: Signals and Slots\par
  6261. \hbox{\indent\pdfURL{http://doc.trolltech.com/4.4/signalsandslots.html}%
  6262. {http://doc.trolltech.com/4.4/signalsandslots.html}}}, but
  6263. these
  6264. connections can
  6265. also be made through direct function calls as is the case with the connection
  6266. between |ZoomLog| and |MeasurementModel|.
  6267. \medskip
  6268. \includegraphics{pipes}
  6269. \smallskip
  6270. \centerline{Figure \secno: Example Flow of Measurement objects in \pn}
  6271. \medskip
  6272. Please note that Figure \secno~is representative of a typical configuration. Now
  6273. that the flow of measurements through \pn{} is determined by a script specified
  6274. by the user, whatever pipeline is needed can be specified at run time.
  6275. @* The DAQ class.
  6276. \noindent The |DAQ| class represents a piece of hardware that allows the
  6277. computer to read measurements from one or more thermocouples. Presently this
  6278. class is only handles continuous sampling on devices from National Instruments.
  6279. It should be simple to modify this class to handle similar devices from other
  6280. vendors.
  6281. Each device is represented by a single instance of this class. Multiple channels
  6282. can be used on a device if the device supports it.
  6283. Two enumerations are declared in this class to be used as arguments to
  6284. |newChannel()|. The first is used to set the measurement unit for the channel.
  6285. As the measurements themselves do not carry this information, it is important to
  6286. keep track of this information. The values come from {\tt nidaqmxbase.h} and can
  6287. be used to select among Fahrenheit, Celsius, Kelvin, and Rankine. The
  6288. second enumeration, |ThermocoupleType|, should be used to specify the type of
  6289. thermocouple connected to the device. If this does not match reality, the
  6290. measurements will not be correct. The meanings of the values should be obvious
  6291. from the names.
  6292. \danger When this class was originally written the method of thread handling
  6293. used was considered appropriate. New functionality in |QThread| has made this
  6294. no longer the case. This class is currently planned for depreciation once a
  6295. replacement class hierarchy more suited to multiple hardware types is available
  6296. however if this is not ready soon it may be beneficial to rewrite this class to
  6297. conform to current best practices.\endanger
  6298. @<Class declarations@>=
  6299. class Channel;
  6300. class DAQImplementation;@/
  6301. class DAQ : public QObject@;@/
  6302. {@t\1@>@/
  6303. Q_OBJECT@/
  6304. Q_ENUMS(ThermocoupleType)@;@/
  6305. DAQImplementation *imp;@/
  6306. @t\4@>private slots@t\kern-3pt@>:@/
  6307. void threadFinished();
  6308. public:@;
  6309. DAQ(QString device, const QString &driver = QString("nidaqmxbase"));
  6310. ~DAQ();@/
  6311. Channel* newChannel(int units, int thermocouple);@/
  6312. @[Q_INVOKABLE@,@, void@]@, setClockRate(double Hz);@t\2\2@>@/
  6313. @[Q_INVOKABLE@,@, void@]@, start();@t\2\2@>@/
  6314. @[Q_INVOKABLE@,@, void@]@, stop();@t\2\2@>@/
  6315. enum ThermocoupleType@/
  6316. {
  6317. @!TypeJ = 10072,
  6318. @!TypeK = 10073,
  6319. @!TypeN = 10077,
  6320. @!TypeR = 10082,
  6321. @!TypeS = 10085,
  6322. @!TypeT = 10086,
  6323. @!TypeB = 10047,
  6324. @!TypeE = 10055
  6325. };@t\2@>@/
  6326. }@t\kern-3pt@>;
  6327. @ The |DAQ| class has as a private member an instance of a class called
  6328. |DAQImplementation|. The two classes together create and run a new thread of
  6329. execution. This thread spends most of its time blocking while waiting for a new
  6330. measurement to become available. When a new measurement is available, that
  6331. measurement is passed to the appropriate channel which in turn passes it to any
  6332. interested object.
  6333. @<Class declarations@>=
  6334. class DAQImplementation : public QThread@;@/
  6335. {@;
  6336. Q_OBJECT@;@/
  6337. public:@;
  6338. DAQImplementation(const QString &driverinfo);
  6339. ~DAQImplementation();
  6340. void run();
  6341. void measure();
  6342. @<Library function pointers@>@;
  6343. @<DAQImplementation member data@>@;
  6344. }@+@t\kern-3pt@>;
  6345. @ In order to solve some minor problems, NI-DAQmxBase is no longer linked at
  6346. compile time. Rather, this is now linked at runtime through a |QLibrary| object.
  6347. In order to use functions from this library, these functions must be stored in
  6348. function pointers. Fortunately, all of these functions can be expressed with the
  6349. same pointer type. Unfortunately, this way of doing things offers very little
  6350. debugging information in the event that something is not quite right.
  6351. @<Library function pointers@>=
  6352. typedef int (*daqfp)(...);
  6353. daqfp read;
  6354. daqfp errorInfo;
  6355. daqfp startTask;
  6356. daqfp createTask;
  6357. daqfp createChannel;
  6358. daqfp setClock;
  6359. daqfp stopTask;
  6360. daqfp clearTask;
  6361. daqfp resetDevice;
  6362. daqfp waitForMeasurement;
  6363. @ |DAQImplementation| also maintains information about the device and the
  6364. channels the measurements are sent to.
  6365. @<DAQImplementation member data@>=
  6366. bool useBase;
  6367. QString device;
  6368. QVector<Channel*> channelMap;
  6369. unsigned int handle;@/
  6370. int error;
  6371. int channels;
  6372. bool ready;
  6373. QLibrary driver;
  6374. QVector<Units::Unit> unitMap;
  6375. @ Most of the interesting work associated with the |DAQ| class is handled in
  6376. the |measure()| method of |DAQImplementation|. This function will block until a
  6377. measurement is available. Once |buffer| is filled by |DAQmxBaseReadAnalogF64()|
  6378. that function returns and new |Measurement| objects are created based on the
  6379. information in the buffer. These measurements are sent to |Channel| objects
  6380. tracked by |channelMap|.
  6381. Up until version 1.0.7 there was a bug in this code that would prevent the code
  6382. from working when more than one channel is requested. This has been corrected.
  6383. With version 1.0.9, time measurement is moved out of the loop, reducing the
  6384. number of calls in cases of more than 1 measurement and ensuring that all
  6385. simultaneously obtained measurements have the same time stamp.
  6386. @<DAQ Implementation@>=
  6387. void DAQImplementation::measure()@t\2@>@/
  6388. @t\4@>{@/
  6389. int samplesRead = 0;
  6390. double buffer[channels];
  6391. error = read((unsigned int)(handle), (signed long)(1), (double)(10.0),@|
  6392. (unsigned long)(0), buffer, (unsigned long)(channels),@|
  6393. &samplesRead, (signed long)(0));@/
  6394. if(error)@/
  6395. @t\1@>{@/
  6396. ready = false;@t\2@>@/
  6397. }
  6398. else@/
  6399. {
  6400. if(samplesRead)@/
  6401. {
  6402. QTime time = QTime::currentTime();@/
  6403. for(int i = 0; i < samplesRead; i++)@/
  6404. {
  6405. for(int j = 0; j < channels; j++)@/
  6406. {
  6407. double measuredValue = buffer[j+(i*channels)];
  6408. Measurement measure(measuredValue, time,
  6409. unitMap[j]);
  6410. channelMap[@,j]->input(measure);
  6411. }
  6412. }
  6413. }
  6414. }
  6415. @t\4@>}
  6416. @ It was noted that |DAQmxBaseReadAnalogF64()| blocks until it is able to fill
  6417. the |buffer| passed to it. To prevent this behavior from having adverse effects
  6418. on the rest of the program, measure is called from a loop running in its own
  6419. thread of execution. When the thread is started, it begins its execution from
  6420. the |run()| method of |DAQImplementation| which overrides the |run()| method of
  6421. |QThread|. Here the priority of the thread is set in an attempt to cut down on
  6422. the variation in time between recorded measurements.
  6423. The while loop is controlled by |ready| which is set to |false| when there is an
  6424. error in collecting a measurement or when the user wants to exit the program. It
  6425. could also be set to |false| when the |DAQ| is reconfigured.
  6426. @<DAQ Implementation@>=
  6427. void DAQImplementation::run()
  6428. {
  6429. setPriority(QThread::TimeCriticalPriority);
  6430. while(ready)
  6431. {
  6432. measure();
  6433. }
  6434. }
  6435. @ When this loop exits, |DAQImplementation| emits a finished signal to indicate
  6436. that the thread is no longer running. This could be due to perfectly normal
  6437. conditions, but there could also be a problem that the user must be informed of.
  6438. That signal is connected to a function that checks for error conditions and
  6439. reports them if needed.
  6440. @<DAQ Implementation@>=
  6441. void DAQ::threadFinished()
  6442. {
  6443. if(imp->error)
  6444. {
  6445. @<Display DAQ Error@>@;
  6446. }
  6447. }
  6448. @ Errors are displayed with a |QMessageBox|. NIDAQmxBase provides the message
  6449. strings for these errors, but this should probably change as these error strings
  6450. are generally completely unrelated to what the problem really is. For example,
  6451. ``Error: -1'' usually means that the device is not plugged in.
  6452. ``Error: -200170'' usually means that \pn{} or another program using the device
  6453. did not exit cleanly. A table of replacement warning messages should be added to
  6454. this program.
  6455. \bigskip
  6456. \settabs 5 \columns
  6457. \+Error Code & NIDAQmxBase Text & & Likely Cause\cr
  6458. \+\hrulefill & \hrulefill & \hrulefill & \hrulefill & \hrulefill & \hrulefill\cr
  6459. \+ -1 & Not implemented for this device & & The device is not plugged in.\cr
  6460. \+ & type. & \cr
  6461. \+ -200170 & Physical channel specified & & The program did not exit cleanly\cr
  6462. \+ & does not exist on this device. & & or another program is using the\cr
  6463. \+ & Refer to the documentation for & & device.\cr
  6464. \+ & channels available on this device.\cr
  6465. \+ -1073807194 & {\it{(No text)}} & & The device has been unplugged.\cr
  6466. \medskip
  6467. \centerline{Table \secno: Error codes, text, and what they really mean.}
  6468. \bigskip
  6469. There are two calls to |DAQmxBaseGetExtendedErrorInfo()| to obtain the error
  6470. messages. The first is used just to obtain the length of the error string. That
  6471. length is then used to allocate space for the error message. The second call
  6472. fills that string. This isn'@q'@>t allowed by ISO \CPLUSPLUS/\nfnote{%
  6473. \CPLUSPLUS/Dynamic Arrays (Crowl and Austern, May 16, 2008)\par
  6474. \hbox{\indent\pdfURL{%
  6475. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2648.html}{%
  6476. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2648.html}}} but it
  6477. works with gcc. If this is a problem, the first call can be
  6478. removed and the buffer can be set large enough to fit the largest error message
  6479. that will be produced. Heap allocation could be used, but then we need to
  6480. remember to free the memory allocated to the string. Alternately, we can get rid
  6481. of \CEE/ style strings and write our own error messages. This would be the
  6482. preferred correction.
  6483. @<Display DAQ Error@>=
  6484. imp->ready = false;
  6485. QMessageBox warning;
  6486. warning.setStandardButtons(QMessageBox::Cancel);
  6487. warning.setIcon(QMessageBox::Warning);
  6488. warning.setText(QString(tr("Error: %1")).arg(imp->error));
  6489. unsigned long bytes = imp->errorInfo(NULL, 0);
  6490. char string[bytes];
  6491. imp->errorInfo(string, bytes);
  6492. warning.setInformativeText(QString(string));
  6493. warning.setWindowTitle(QString(PROGRAM_NAME));
  6494. warning.exec();
  6495. @ Starting the thread is almost as simple as ending it. The hardware is
  6496. instructed to begin taking measurements. If there is an error, this is handled.
  6497. Otherwise, the finished signal from |DAQImplementation| is connected to
  6498. |threadFinished()| and the new thread is started. The call to |imp->start()|
  6499. starts a new thread and passes control of that thread to |imp->run()|. The main
  6500. thread of execution returns without waiting for the new thread to do anything.
  6501. The call to |DAQmxBaseStartTask()| requires some time before the first
  6502. measurement is available. This is one of the reasons we start gathering
  6503. measurements before we really need them and continue to collect them until it is
  6504. time to exit the program.
  6505. @<DAQ Implementation@>=
  6506. void DAQ::start()
  6507. {
  6508. if(imp->ready)
  6509. {
  6510. imp->error = imp->startTask(imp->handle);
  6511. if(imp->error)
  6512. {
  6513. @<Display DAQ Error@>@;
  6514. }
  6515. else
  6516. {
  6517. connect(imp, SIGNAL(finished()), this, SLOT(threadFinished()));
  6518. imp->start();
  6519. }
  6520. }
  6521. }
  6522. void DAQ::stop()
  6523. {
  6524. if(imp->useBase)
  6525. {
  6526. imp->ready = @[false@];
  6527. imp->wait(ULONG_MAX);
  6528. imp->stopTask(imp->handle);
  6529. }
  6530. else
  6531. {
  6532. imp->ready = @[false@];
  6533. imp->error = imp->stopTask(imp->handle);
  6534. if(imp->error)
  6535. {
  6536. @<Display DAQ Error@>@;
  6537. }
  6538. imp->error = imp->clearTask(imp->handle);
  6539. if(imp->error)
  6540. {
  6541. @<Display DAQ Error@>@;
  6542. }
  6543. }
  6544. }
  6545. @ Setting up the DAQ begins by constructing a new |DAQ| object. The constructor
  6546. takes as its argument a string indicating the name of the device and will
  6547. typically be something like |"Dev1"|. This creates a new task which is required
  6548. for the setup that follows once a new |DAQ| is created.
  6549. @<DAQ Implementation@>=
  6550. DAQ::DAQ(QString device, const QString &driver) : imp(new DAQImplementation(driver))@/
  6551. @t\4\4@>{@/
  6552. imp->device = device;
  6553. imp->error = imp->createTask(device.toAscii().data(), &(imp->handle));
  6554. if(imp->error)@/
  6555. {
  6556. @<Display DAQ Error@>@;
  6557. }
  6558. else@/
  6559. @t\1@>{@/
  6560. imp->ready = true;@t\2@>@/
  6561. }@/
  6562. @t\4\4@>}
  6563. @ Once the |DAQ| is created, one or more channels can be added to that |DAQ|.
  6564. All |Channel| objects are created by the |DAQ| class and are managed by the
  6565. |DAQ| class. When a new channel is created, a pointer is passed back allowing
  6566. other classes to connect to the channel. Measurements cannot be read from the
  6567. |DAQ| directly. They must at some point pass through a channel.
  6568. @<DAQ Implementation@>=
  6569. Channel* DAQ::newChannel(int units, int thermocouple)
  6570. {
  6571. Channel *retval = new Channel();
  6572. imp->channelMap[imp->channels] = retval;
  6573. imp->unitMap[imp->channels] = (Units::Unit)units;
  6574. imp->channels++;
  6575. if(imp->ready)
  6576. {
  6577. if(imp->useBase)
  6578. {
  6579. imp->error = imp->createChannel(imp->handle,
  6580. QString("%1/ai%2").arg(imp->device).
  6581. arg(imp->channels - 1).
  6582. toAscii().data(),
  6583. "", (double)(-1.0), (double)(100.0),
  6584. (signed long)(units),
  6585. (signed long)(thermocouple),
  6586. (signed long)(10200), (double)(0),
  6587. "");
  6588. }
  6589. else
  6590. {
  6591. imp->error = imp->createChannel(imp->handle,
  6592. QString("%1/ai%2").arg(imp->device).
  6593. arg(imp->channels - 1).
  6594. toAscii().data(),
  6595. "", (double)(50.0), (double)(500.0),
  6596. (signed long)(units),
  6597. (signed long)(thermocouple),
  6598. (signed long)(10200), (double)(0),
  6599. "");
  6600. }
  6601. if(imp->error)
  6602. {
  6603. @<Display DAQ Error@>@;
  6604. }
  6605. }
  6606. return retval;
  6607. }
  6608. @ Once the channels are created, it is necessary to set the clock rate of the
  6609. DAQ. The clock rate chosen must be supported by the hardware. The clock rates
  6610. supported by the hardware may be altered by the number of channels in use.
  6611. The amount of time between measurements may vary slightly. A test configuration
  6612. at Wilson's Coffee \char'046~Tea used a 4Hz clock rate and provides measurements
  6613. every 251$\pm$1ms with 80\% of measurements spaced 251ms apart.
  6614. @<DAQ Implementation@>=
  6615. void DAQ::setClockRate(double Hz)
  6616. {
  6617. if(imp->ready)
  6618. {
  6619. imp->error = imp->setClock(imp->handle, "OnboardClock", Hz,
  6620. (signed long)(10280), (signed long)(10123),
  6621. (unsigned long long)(1));
  6622. if(imp->error)
  6623. {
  6624. @<Display DAQ Error@>@;
  6625. }
  6626. }
  6627. }
  6628. @ Before the program exits, the |DAQ| should be deleted. The destructor
  6629. instructs the measurement thread to stop, waits for it to finish, and resets the
  6630. device. If this is not done, an error would be issued the next time a program
  6631. attempted to use the device.
  6632. @<DAQ Implementation@>=
  6633. DAQ::~DAQ()@/
  6634. {
  6635. if(imp->useBase)
  6636. {
  6637. imp->resetDevice(imp->device.toAscii().data());
  6638. imp->clearTask(imp->handle);
  6639. }
  6640. else
  6641. {
  6642. if(imp->ready)
  6643. {
  6644. imp->ready = @[false@];
  6645. imp->wait(ULONG_MAX);
  6646. imp->stopTask(imp->handle);
  6647. imp->resetDevice(imp->device.toAscii().data());
  6648. imp->clearTask(imp->handle);
  6649. }
  6650. }
  6651. delete imp;
  6652. }
  6653. @ This just leaves the constructor and destructor for |DAQImplementation|. The
  6654. way the program is currently written, the number of channels available on the
  6655. |DAQ| is limited to 4. If a known larger number is required, the value here can
  6656. simply be set larger, however the best long term solution would be to modify
  6657. |newChannel()| to resize |channelMap| as more channels are added.
  6658. The constructor handles loading NI-DAQmxBase and preparing function pointers for
  6659. the symbols used in \pn{}.
  6660. @<DAQ Implementation@>=
  6661. DAQImplementation::DAQImplementation(const QString &driverinfo)
  6662. : QThread(NULL), channelMap(4), handle(0), error(0), channels(0), ready(false),
  6663. unitMap(4)@/
  6664. {
  6665. if(driverinfo == "nidaqmxbase")
  6666. {
  6667. useBase = true;
  6668. }
  6669. else
  6670. {
  6671. useBase = false;
  6672. }
  6673. if(useBase)
  6674. {
  6675. driver.setFileName("nidaqmxbase.framework/nidaqmxbase");
  6676. if(!driver.load())
  6677. {
  6678. driver.setFileName("nidaqmxbase");
  6679. if(!driver.load())
  6680. {
  6681. QMessageBox::critical(NULL, tr("Typica: Driver not found"),
  6682. tr("Failed to find nidaqmxbase. Please install it."));
  6683. QApplication::quit();
  6684. }
  6685. }
  6686. }
  6687. else
  6688. {
  6689. driver.setFileName("nicaiu");
  6690. if(!driver.load())
  6691. {
  6692. QMessageBox::critical(NULL, tr("Typica: Driver not found"),
  6693. tr("Failed to find nidaqmx. Please install it."));
  6694. QApplication::quit();
  6695. }
  6696. }
  6697. if(useBase)
  6698. {
  6699. if((createTask = (daqfp) driver.resolve("DAQmxBaseCreateTask")) == 0 || @|
  6700. (startTask = (daqfp) driver.resolve("DAQmxBaseStartTask")) == 0 || @|
  6701. (stopTask = (daqfp) driver.resolve("DAQmxBaseStopTask")) == 0 || @|
  6702. (clearTask = (daqfp) driver.resolve("DAQmxBaseClearTask")) == 0 || @|
  6703. (createChannel = (daqfp) driver.resolve("DAQmxBaseCreateAIThrmcplChan"))
  6704. == 0 || @|
  6705. (setClock = (daqfp) driver.resolve("DAQmxBaseCfgSampClkTiming")) ==
  6706. 0 || @|
  6707. (read = (daqfp) driver.resolve("DAQmxBaseReadAnalogF64")) == 0 || @|
  6708. (errorInfo = (daqfp) driver.resolve("DAQmxBaseGetExtendedErrorInfo")) ==
  6709. 0 || @|
  6710. (resetDevice = (daqfp) driver.resolve("DAQmxBaseResetDevice")) == 0)@/
  6711. {
  6712. waitForMeasurement = NULL;
  6713. QMessageBox::critical(NULL, tr("Typica: Link error"),
  6714. tr("Failed to link a required symbol in NI-DAQmxBase."));
  6715. QApplication::quit();
  6716. }
  6717. }
  6718. else
  6719. {
  6720. if((createTask = (daqfp)driver.resolve("DAQmxCreateTask")) == 0 || @|
  6721. (startTask = (daqfp)driver.resolve("DAQmxStartTask")) == 0 || @|
  6722. (stopTask = (daqfp)driver.resolve("DAQmxStopTask")) == 0 || @|
  6723. (clearTask = (daqfp)driver.resolve("DAQmxClearTask")) == 0 || @|
  6724. (createChannel = (daqfp)driver.resolve("DAQmxCreateAIThrmcplChan"))
  6725. == 0 || @|
  6726. (setClock = (daqfp)driver.resolve("DAQmxCfgSampClkTiming")) == 0 || @|
  6727. (read = (daqfp)driver.resolve("DAQmxReadAnalogF64")) == 0 || @|
  6728. (errorInfo = (daqfp)driver.resolve("DAQmxGetExtendedErrorInfo")) ==
  6729. 0 || @|
  6730. (resetDevice = (daqfp)driver.resolve("DAQmxResetDevice")) == 0 ||
  6731. (waitForMeasurement = (daqfp)driver.resolve("DAQmxWaitUntilTaskDone")) == 0)
  6732. {
  6733. QMessageBox::critical(NULL, tr("Typica: Link error"),
  6734. tr("Failed to link a required symbol in NI-DAQmx."));
  6735. QApplication::quit();
  6736. }
  6737. }
  6738. }
  6739. DAQImplementation::~DAQImplementation()
  6740. {
  6741. driver.unload();
  6742. }
  6743. @ When exposing the |DAQ| class to the scripting engine, we need to provide a
  6744. constructor that can be called from a script and we need a way to call
  6745. |DAQ::newChannel()|. Other methods that are useful when called from a script are
  6746. made available automatically with the |Q_INVOKABLE| macro, however this does not
  6747. work for methods such as |newChannel()| which return a pointer to a |Channel|
  6748. object.
  6749. @<Function prototypes for scripting@>=
  6750. QScriptValue constructDAQ(QScriptContext *context, QScriptEngine *engine);
  6751. QScriptValue DAQ_newChannel(QScriptContext *context, QScriptEngine *engine);
  6752. void setDAQProperties(QScriptValue value, QScriptEngine *engine);
  6753. @ These functions and the values from |Units::Unit| must be made available to
  6754. the host environment. The latter because this was widely used in configurations
  6755. before this enumeration was removed from the |DAQ| class. As these properties
  6756. must be available without an instance, the properties must be added here.
  6757. @<Set up the scripting engine@>=
  6758. constructor = engine->newFunction(constructDAQ);
  6759. value = engine->newQMetaObject(&DAQ::staticMetaObject, constructor);
  6760. value.setProperty("Fahrenheit", Units::Fahrenheit);
  6761. value.setProperty("Celsius", Units::Celsius);
  6762. value.setProperty("Kelvin", Units::Kelvin);
  6763. value.setProperty("Rankine", Units::Rankine);
  6764. engine->globalObject().setProperty("DAQ", value);
  6765. @ When creating a new |DAQ|, we make sure that it is owned by the script engine.
  6766. This is necessary to ensure that the destructor is called before \pn{} exits.
  6767. Just as the constructor requires an argument that specifies the device name, the
  6768. constructor available from a script also requires this argument.
  6769. @<Functions for scripting@>=
  6770. QScriptValue constructDAQ(QScriptContext *context, QScriptEngine *engine)
  6771. {
  6772. QScriptValue object;
  6773. if(context->argumentCount() == 1)
  6774. {
  6775. object = engine->newQObject(new DAQ(argument<QString>(0, context)),
  6776. QScriptEngine::ScriptOwnership);
  6777. setDAQProperties(object, engine);
  6778. }
  6779. else if(context->argumentCount() == 2)
  6780. {
  6781. object = engine->newQObject(new DAQ(argument<QString>(0, context),
  6782. argument<QString>(1, context)),
  6783. QScriptEngine::ScriptOwnership);
  6784. setDAQProperties(object, engine);
  6785. }
  6786. else
  6787. {
  6788. context->throwError("Incorrect number of arguments passed to DAQ"@|
  6789. "constructor. The DAQ constructor takes one"@|
  6790. "string as an argument specifying a device name."@|
  6791. "Example: Dev1");
  6792. }
  6793. return object;
  6794. }
  6795. @ As |DAQ| inherits |QObject|, we add the |newChannel()| property after adding
  6796. any |QObject| properties.
  6797. @<Functions for scripting@>=
  6798. void setDAQProperties(QScriptValue value, QScriptEngine *engine)
  6799. {
  6800. setQObjectProperties(value, engine);
  6801. value.setProperty("newChannel", engine->newFunction(DAQ_newChannel));
  6802. }
  6803. @ The |newChannel()| method method also requires that two arguments are provided
  6804. by the script.
  6805. @<Functions for scripting@>=
  6806. QScriptValue DAQ_newChannel(QScriptContext *context, QScriptEngine *engine)
  6807. {
  6808. DAQ *self = getself<@[DAQ *@]>(context);
  6809. QScriptValue object;
  6810. if(self)
  6811. {
  6812. object =
  6813. engine->newQObject(self->newChannel(argument<int>(0, context),@|
  6814. argument<int>(1, context)));
  6815. setChannelProperties(object, engine);
  6816. }
  6817. return object;
  6818. }
  6819. @ Sometimes it can be useful to test other parts of the program (for example,
  6820. when developing new scripts) when the DAQ hardware is not available. In these
  6821. cases, it is possible to temporarily use the |FakeDAQ| class. This class mimics
  6822. the |DAQ| class, but just makes up the measurements sent to the rest of the
  6823. program.
  6824. @<Class declarations@>=
  6825. class FakeDAQImplementation : public QThread@/
  6826. {@/
  6827. Q_OBJECT@;
  6828. public:@/
  6829. FakeDAQImplementation();
  6830. ~FakeDAQImplementation();
  6831. void run();
  6832. void measure();
  6833. QVector<Channel *> channelMap;
  6834. int channels;
  6835. bool ready;
  6836. double clockRate;
  6837. };@/
  6838. class FakeDAQ : public QObject@/
  6839. {@/
  6840. Q_OBJECT@;
  6841. FakeDAQImplementation *imp;
  6842. public:@/
  6843. FakeDAQ(QString device);
  6844. ~FakeDAQ();
  6845. Channel *newChannel(int units, int thermocouple);@/
  6846. @[Q_INVOKABLE@,@, void@]@, setClockRate(double Hz);@t\2\2@>@/
  6847. @[Q_INVOKABLE@,@, void@]@, start();@t\2\2@>@/
  6848. };
  6849. @ Just as in the |DAQ| class, most of the interesting stuff happens in
  6850. |measure()|. For each invokation of the method, we sleep for some amount of time
  6851. based on the clock rate then create a |Measurement| object at random for each
  6852. |Channel| that has been created.
  6853. @<FakeDAQ Implementation@>=
  6854. void FakeDAQImplementation::measure()
  6855. {
  6856. msleep((int)(1000/clockRate));
  6857. QTime time = QTime::currentTime();
  6858. for(int i = 0; i < channels; i++)
  6859. {
  6860. Measurement measure(qrand() % 500, time);
  6861. channelMap[i]->input(measure);
  6862. }
  6863. }
  6864. @ To call |measure|, we need to flesh out the rest of |FakeDAQImplementation|.
  6865. @<FakeDAQ Implementation@>=
  6866. void FakeDAQImplementation::run()
  6867. {
  6868. setPriority(QThread::TimeCriticalPriority);
  6869. while(ready)
  6870. {
  6871. measure();
  6872. }
  6873. }
  6874. FakeDAQImplementation::FakeDAQImplementation() : QThread(NULL), channelMap(4),
  6875. channels(0), ready(false), clockRate(1)@/
  6876. {
  6877. /* Nothing has to be done here. */
  6878. }
  6879. FakeDAQImplementation::~FakeDAQImplementation()
  6880. {
  6881. /* Nothing has to be done here. */
  6882. }
  6883. @ Next we need an implementation for the |FakeDAQ| class. This is simplified by
  6884. the fact that we are just using a random number generator to generate
  6885. measurements rather than special hardware for obtaining measurements.
  6886. @<FakeDAQ Implementation@>=
  6887. void FakeDAQ::start()
  6888. {
  6889. if(imp->ready)
  6890. {
  6891. imp->start();
  6892. }
  6893. }@#
  6894. FakeDAQ::FakeDAQ(QString) : imp(new FakeDAQImplementation())@t\2\2@>@/
  6895. {@t\1@>@/
  6896. imp->ready = true;@t\2@>@/
  6897. }@#
  6898. Channel* FakeDAQ::newChannel(int, int)
  6899. {
  6900. Channel *retval;
  6901. if(imp->ready)
  6902. {
  6903. retval = new Channel();
  6904. imp->channelMap[imp->channels] = retval;
  6905. imp->channels++;
  6906. }
  6907. else
  6908. {
  6909. return NULL;
  6910. }
  6911. return retval;
  6912. }
  6913. void FakeDAQ::setClockRate(double Hz)
  6914. {
  6915. if(imp->ready)
  6916. {
  6917. imp->clockRate = Hz;
  6918. }
  6919. }@#
  6920. FakeDAQ::~FakeDAQ()@t\2\2@>@/
  6921. {@t\1@>@/
  6922. imp->ready = false;
  6923. imp->wait(ULONG_MAX);
  6924. delete imp;@t\2@>@/
  6925. }
  6926. @ As the entire purpose of the |FakeDAQ| class is for testing purposes from
  6927. within the scripting engine, we need to make it available to the scripting
  6928. engine. This is done in a manner very similar to how the |DAQ| class is handled.
  6929. @<Function prototypes for scripting@>=
  6930. QScriptValue constructFakeDAQ(QScriptContext *context, QScriptEngine *engine);
  6931. QScriptValue FakeDAQ_newChannel(QScriptContext *context, QScriptEngine *engine);
  6932. void setFakeDAQProperties(QScriptValue value, QScriptEngine *engine);
  6933. @ The scripting engine is informed of the constructor.
  6934. @<Set up the scripting engine@>=
  6935. constructor = engine->newFunction(constructFakeDAQ);
  6936. value = engine->newQMetaObject(&FakeDAQ::staticMetaObject, constructor);
  6937. engine->globalObject().setProperty("FakeDAQ", value);
  6938. @ The constructor sets a property to allow calling |newChannel()| on a |FakeDAQ|
  6939. created from a script.
  6940. @<Functions for scripting@>=
  6941. QScriptValue constructFakeDAQ(QScriptContext *context,
  6942. QScriptEngine *engine)
  6943. {
  6944. QScriptValue object;
  6945. if(context->argumentCount() == 1)
  6946. {
  6947. object =
  6948. engine->newQObject(new FakeDAQ(argument<QString>(0, context)),
  6949. QScriptEngine::ScriptOwnership);
  6950. setFakeDAQProperties(object, engine);
  6951. }
  6952. else
  6953. {
  6954. context->throwError("Incorrect number of arguments passed to DAQ"@|
  6955. "constructor. The DAQ constructor takes one"@|
  6956. "string as an argument specifying a device name."@|
  6957. "Example: Dev1");
  6958. }
  6959. return object;
  6960. }
  6961. void setFakeDAQProperties(QScriptValue value, QScriptEngine *engine)
  6962. {
  6963. setQObjectProperties(value, engine);
  6964. value.setProperty("newChannel", engine->newFunction(FakeDAQ_newChannel));
  6965. }
  6966. QScriptValue FakeDAQ_newChannel(QScriptContext *context, QScriptEngine *engine)
  6967. {
  6968. FakeDAQ *self = getself<@[FakeDAQ *@]>(context);
  6969. QScriptValue object;
  6970. if(self)
  6971. {
  6972. object =
  6973. engine->newQObject(self->newChannel(argument<int>(0, context),@|
  6974. argument<int>(1, context)));
  6975. setChannelProperties(object, engine);
  6976. }
  6977. return object;
  6978. }
  6979. @* The Channel class.
  6980. \noindent |Channel| is a simple class. It is a subclass of |QObject| so it can
  6981. use Qt'@q'@>s signals and slots mechanism. Any object that is interested in
  6982. measurements from a channel can connect to the |newData| signal the channel
  6983. emits. Any number of objects can make this connection and each will receive a
  6984. copy of the measurement.
  6985. |Channel| objects should only be created by the |DAQ| class.
  6986. @<Class declarations@>=
  6987. class Channel : public QObject@;@/
  6988. {@t\1@>@/
  6989. Q_OBJECT@/
  6990. public:@;
  6991. Channel();
  6992. ~Channel();@/
  6993. @t\4@>public slots@t\kern-3pt@>:@;
  6994. void input(Measurement measurement);@/
  6995. signals:@;
  6996. void newData(Measurement);@t\2@>@/
  6997. };
  6998. @ The implementation of this class is trivial.
  6999. @<Channel Implementation@>=
  7000. Channel::Channel() : QObject(NULL)@/
  7001. {
  7002. /* Nothing has to be done here. */
  7003. }
  7004. Channel::~Channel()
  7005. {
  7006. /* Nothing has to be done here. */
  7007. }
  7008. void Channel::input(Measurement measurement)
  7009. {
  7010. emit newData(measurement);
  7011. }
  7012. @ A function is provided for use when a channel is created by a DAQ from a
  7013. script.
  7014. @<Function prototypes for scripting@>=
  7015. void setChannelProperties(QScriptValue value, QScriptEngine *engine);
  7016. @ The implementation is trivial.
  7017. @<Functions for scripting@>=
  7018. void setChannelProperties(QScriptValue value, QScriptEngine *engine)
  7019. {
  7020. setQObjectProperties(value, engine);
  7021. }
  7022. @* Calibration and Unit Conversion.
  7023. \noindent One of the planned features for \pn{} is support for hardware that
  7024. collects non-temperature measurements. This is frequently handled with analog
  7025. voltage signals which are proportional to some range in a meaningful unit. Some
  7026. hardware also requires calibration in software. In many cases both of these can
  7027. be handled at the same time with a single mapping in the form:
  7028. $$f(x) = L_1 + (x - L_2){U_1 - L_1\over{U_2 - L_2}}$$
  7029. \noindent where $L_1$ is the logical lower bound, $L_2$ is the measured lower
  7030. bound, $U_1$ is the logical upper bound, $U_2$ is the measured upper bound, and
  7031. $x$ is the value we wish to map from the range $\lbrack L_2, U_2 \rbrack$ to
  7032. the range $\lbrack L_1, U_1 \rbrack$.
  7033. Some use cases require a closed range but others require that this constraint
  7034. is loosened to allow extrapolation. Both are provided by this class.
  7035. Starting in \pn{} 1.6 this class has both the |measurement| and the
  7036. |newData| signals. This allows a |LinearCalibrator| to be treated like a
  7037. |Channel| when used with a |DataqSdkDevice|.
  7038. @<Class declarations@>=
  7039. class LinearCalibrator : public QObject@/
  7040. {@/
  7041. @[Q_OBJECT@]@;
  7042. @[Q_PROPERTY(double measuredLower READ measuredLower
  7043. WRITE setMeasuredLower)@]@;
  7044. @[Q_PROPERTY(double measuredUpper READ measuredUpper
  7045. WRITE setMeasuredUpper)@]@;
  7046. @[Q_PROPERTY(double mappedLower READ mappedLower WRITE setMappedLower)@]@;
  7047. @[Q_PROPERTY(double mappedUpper READ mappedUpper WRITE setMappedUpper)@]@;
  7048. @[Q_PROPERTY(bool closedRange READ isClosedRange WRITE setClosedRange)@]@;
  7049. @[Q_PROPERTY(double sensitivity READ sensitivity WRITE setSensitivity)@]@;
  7050. public:@/
  7051. LinearCalibrator(QObject *parent = NULL);
  7052. double measuredLower();
  7053. double measuredUpper();
  7054. double mappedLower();
  7055. double mappedUpper();
  7056. bool isClosedRange();
  7057. double sensitivity();
  7058. @t\4@>@[public slots@t\kern-3pt@>:@]@;
  7059. void setMeasuredLower(double lower);
  7060. void setMeasuredUpper(double upper);
  7061. void setMappedLower(double lower);
  7062. void setMappedUpper(double upper);
  7063. void setClosedRange(bool closed);
  7064. void setSensitivity(double sensitivity);
  7065. Measurement newMeasurement(Measurement measure);
  7066. @t\4@>@[signals:@]@;
  7067. void measurement(Measurement measure);
  7068. void newData(Measurement measure);
  7069. private:@/
  7070. double Lo1;
  7071. double Lo2;
  7072. double Up1;
  7073. double Up2;
  7074. double sensitivitySetting;
  7075. bool clamp;
  7076. };
  7077. @ When the measured range and the mapped range are identical and the range is
  7078. open, we have an identity mapping. This is the default state in a newly
  7079. constructed |LinearCalibrator| which should quickly be changed.
  7080. @<LinearCalibrator Implementation@>=
  7081. LinearCalibrator::LinearCalibrator(QObject *parent) :
  7082. QObject(parent), Lo1(0), Lo2(0), Up1(1), Up2(1), sensitivitySetting(0.0), clamp(false)@/
  7083. {
  7084. connect(this, SIGNAL(measurement(Measurement)), this, SIGNAL(newData(Measurement)));
  7085. }
  7086. @ The functional portion of the class is in the |newMeasurement()| slot. This
  7087. will receive measurements as they come in and emit a |measurement()| signal for
  7088. each with the calibration and unit adjustment performed.
  7089. This method also handles any rounding needed if there has been a call to
  7090. |setSensitivity()|.
  7091. @<LinearCalibrator Implementation@>=
  7092. Measurement LinearCalibrator::newMeasurement(Measurement measure)
  7093. {
  7094. double outval = Lo1 + (measure.temperature() - Lo2) * (Up1 - Lo1)/(Up2 - Lo2);
  7095. if(clamp)
  7096. {
  7097. if(outval < Lo1)
  7098. {
  7099. outval = Lo1;
  7100. }
  7101. else if(outval > Up1)
  7102. {
  7103. outval = Up1;
  7104. }
  7105. }
  7106. if(sensitivitySetting >= 0.05)
  7107. {
  7108. int temp = qRound(outval/sensitivitySetting);
  7109. outval = temp * sensitivitySetting;
  7110. }
  7111. Measurement adjusted(outval, measure.time(), measure.scale());
  7112. emit measurement(adjusted);
  7113. return adjusted;
  7114. }
  7115. @ The rest of the class consists of trivial accessor methods.
  7116. @<LinearCalibrator Implementation@>=
  7117. double LinearCalibrator::measuredLower()
  7118. {
  7119. return Lo2;
  7120. }
  7121. double LinearCalibrator::measuredUpper()
  7122. {
  7123. return Up2;
  7124. }
  7125. double LinearCalibrator::mappedLower()
  7126. {
  7127. return Lo1;
  7128. }
  7129. double LinearCalibrator::mappedUpper()
  7130. {
  7131. return Up1;
  7132. }
  7133. bool LinearCalibrator::isClosedRange()
  7134. {
  7135. return clamp;
  7136. }
  7137. void LinearCalibrator::setMeasuredLower(double lower)
  7138. {
  7139. Lo2 = lower;
  7140. }
  7141. void LinearCalibrator::setMeasuredUpper(double upper)
  7142. {
  7143. Up2 = upper;
  7144. }
  7145. void LinearCalibrator::setMappedLower(double lower)
  7146. {
  7147. Lo1 = lower;
  7148. }
  7149. void LinearCalibrator::setMappedUpper(double upper)
  7150. {
  7151. Up1 = upper;
  7152. }
  7153. void LinearCalibrator::setClosedRange(bool closed)
  7154. {
  7155. clamp = closed;
  7156. }
  7157. void LinearCalibrator::setSensitivity(double sensitivity)
  7158. {
  7159. sensitivitySetting = sensitivity;
  7160. }
  7161. double LinearCalibrator::sensitivity()
  7162. {
  7163. return sensitivitySetting;
  7164. }
  7165. @ Finally, we make this class available to the scripting engine. Two functions
  7166. are required for this.
  7167. @<Function prototypes for scripting@>=
  7168. QScriptValue constructLinearCalibrator(QScriptContext *context,
  7169. QScriptEngine *engine);
  7170. void setLinearCalibratorProperties(QScriptValue value, QScriptEngine *engine);
  7171. @ The scripting engine is informed of the constructor.
  7172. @<Set up the scripting engine@>=
  7173. constructor = engine->newFunction(constructLinearCalibrator);
  7174. value = engine->newQMetaObject(&LinearCalibrator::staticMetaObject,
  7175. constructor);
  7176. engine->globalObject().setProperty("LinearCalibrator", value);
  7177. @ The implementation of these functions is typical for this application.
  7178. @<Functions for scripting@>=
  7179. QScriptValue constructLinearCalibrator(QScriptContext *, QScriptEngine *engine)
  7180. {
  7181. QScriptValue object = engine->newQObject(new LinearCalibrator(NULL));
  7182. setLinearCalibratorProperties(object, engine);
  7183. return object;
  7184. }
  7185. void setLinearCalibratorProperties(QScriptValue value, QScriptEngine *engine)
  7186. {
  7187. setQObjectProperties(value, engine);
  7188. }
  7189. @* Linear Spline Interpolation.
  7190. \noindent While linear interpolation is adequate for many purposes, it fails
  7191. when a nonlinear mapping is required. The primary use case is to calibrate
  7192. multiple coffee roasters so that roast profiles can be shared among coffee
  7193. roasters with sufficiently similar heat transfer characteristics even if
  7194. differences in the measurement hardware result in different measured values.
  7195. By recording measured values at several points known to be equivalent due to
  7196. easily observable physical changes in the coffee it is possible to use linear
  7197. spline interpolation to produce a data series that approximates on one machine
  7198. the measurements that would have been produced at another. Acceptable results
  7199. may be available with surprisingly few data points.
  7200. It was originally suspected that cubic spline interpolation would produce a
  7201. more accurate mapping, but testing with linear spline interpolation produced
  7202. results good enough that more complex mappings were not needed. Cubic spline
  7203. interpolation may still be implemented in the future, but it is a low
  7204. priority.
  7205. @<Class declarations@>=
  7206. class LinearSplineInterpolator : public QObject@/
  7207. {@/
  7208. @[Q_OBJECT@]@;@/
  7209. public:@/
  7210. LinearSplineInterpolator(QObject *parent = NULL);
  7211. @[Q_INVOKABLE@,@, void@]@, add_pair(double source, double destination);@t\2\2@>@/
  7212. @[public slots@]:@/
  7213. Measurement newMeasurement(Measurement measure);
  7214. @[signals@]:@/
  7215. void newData(Measurement measure);
  7216. private:@/
  7217. void make_interpolators();
  7218. QMap<double, double> *pairs;
  7219. QList<LinearCalibrator *> *interpolators;@/
  7220. };
  7221. @ We take advantage of the fact that iterating over a QMap always returns
  7222. entries in key order. When new measurement pairs are specified, the
  7223. interpolators are regenerated. There is significant room for performance
  7224. improvement.
  7225. @<LinearSplineInterpolator Implementation@>=
  7226. void LinearSplineInterpolator::add_pair(double source, double destination)
  7227. {
  7228. pairs->insert(source, destination);
  7229. make_interpolators();
  7230. }
  7231. void LinearSplineInterpolator::make_interpolators()
  7232. {
  7233. if(pairs->size() > 1)
  7234. {
  7235. while(interpolators->size() > 0)
  7236. {
  7237. LinearCalibrator *removed = interpolators->takeFirst();
  7238. removed->deleteLater();
  7239. }
  7240. QMap<double, double>::const_iterator i = pairs->constBegin();
  7241. QMap<double, double>::const_iterator j = i + 1;
  7242. while(j != pairs->constEnd())
  7243. {
  7244. LinearCalibrator *segment = new LinearCalibrator();
  7245. segment->setMeasuredLower(i.key());
  7246. segment->setMappedLower(i.value());
  7247. segment->setMeasuredUpper(j.key());
  7248. segment->setMappedUpper(j.value());
  7249. segment->setClosedRange(false);
  7250. interpolators->append(segment);
  7251. connect(segment, SIGNAL(measurement(Measurement)), this, SIGNAL(newData(Measurement)));
  7252. i++;
  7253. j++;
  7254. }
  7255. }
  7256. }
  7257. LinearSplineInterpolator::LinearSplineInterpolator(QObject *parent) :
  7258. QObject(parent), pairs(new QMap<double, double>),
  7259. interpolators(new QList<LinearCalibrator *>)
  7260. {
  7261. /* Nothing needs to be done here. */
  7262. }
  7263. Measurement LinearSplineInterpolator::newMeasurement(Measurement measure)
  7264. {
  7265. QMap<double, double>::const_iterator i = pairs->constBegin();
  7266. int index = -1;
  7267. while(i != pairs->constEnd())
  7268. {
  7269. if(measure.temperature() <= i.key())
  7270. {
  7271. break;
  7272. }
  7273. i++;
  7274. index++;
  7275. }
  7276. if(index < 0)
  7277. {
  7278. index = 0;
  7279. }
  7280. if(index >= interpolators->size())
  7281. {
  7282. index = interpolators->size() - 1;
  7283. }
  7284. if(interpolators->at(index) != NULL)
  7285. {
  7286. return interpolators->at(index)->newMeasurement(measure);
  7287. }
  7288. return Measurement();
  7289. }
  7290. @ This is exposed to the scripting environment as usual.
  7291. @<Function prototypes for scripting@>=
  7292. QScriptValue constructLinearSplineInterpolator(QScriptContext *context, QScriptEngine *engine);
  7293. void setLinearSplineInterpolatorProperties(QScriptValue value, QScriptEngine *engine);
  7294. @ As usual.
  7295. @<Set up the scripting engine@>=
  7296. constructor = engine->newFunction(constructLinearSplineInterpolator);
  7297. value = engine->newQMetaObject(&LinearSplineInterpolator::staticMetaObject, constructor);
  7298. engine->globalObject().setProperty("LinearSplineInterpolator", value);
  7299. @ And again as usual.
  7300. @<Functions for scripting@>=
  7301. QScriptValue constructLinearSplineInterpolator(QScriptContext *, QScriptEngine *engine)
  7302. {
  7303. QScriptValue object = engine->newQObject(new LinearSplineInterpolator(NULL));
  7304. setLinearSplineInterpolatorProperties(object, engine);
  7305. return object;
  7306. }
  7307. void setLinearSplineInterpolatorProperties(QScriptValue value, QScriptEngine *engine)
  7308. {
  7309. setQObjectProperties(value, engine);
  7310. }
  7311. @* The TemperatureDisplay class.
  7312. Now that measurements have been generated by the |DAQ| and passed to a
  7313. |Channel|, any object that is interested in these measurements can connect to
  7314. the channel and use the measurements it sends out. So far, the time on each
  7315. measurement is the time at which it was collected. Unfortunately, this is often
  7316. not what we want. It is more useful to have the time relative to some other
  7317. point in time such as the start of the batch.
  7318. Until the measurement time is adjusted, the measurements are really only useful
  7319. to classes that do not care about the measurement time. The |TemperatureDisplay|
  7320. class is such a class. It receives measurements and displays the most recently
  7321. measured temperature.
  7322. This is a specialization of |QLCDNumber|.
  7323. @<Class declarations@>=
  7324. class TemperatureDisplay : public QLCDNumber@/
  7325. {@t\1@>@/
  7326. Q_OBJECT@;
  7327. int unit;
  7328. bool r;
  7329. public:@/
  7330. TemperatureDisplay(QWidget *parent = NULL);
  7331. ~TemperatureDisplay();@/
  7332. @t\4@>public slots@t\kern-3pt@>:@/
  7333. void setValue(Measurement temperature);
  7334. void invalidate();
  7335. void setDisplayUnits(Units::Unit scale);
  7336. void setRelativeMode(bool relative);@t\2@>@/
  7337. };
  7338. @ Starting in version 1.6 this widget is also used for displaying a relative
  7339. temperature value in the form of the rate of change indicator. To calculate
  7340. this correctly in Celsius or Kelvin we must have a way to bypass the
  7341. conversions for absolute measures.
  7342. @<TemperatureDisplay Implementation@>=
  7343. void TemperatureDisplay::setRelativeMode(bool relative)
  7344. {
  7345. r = relative;
  7346. }
  7347. @ Displaying a temperature is a simple matter of taking the temperature
  7348. component from the measurement and converting it to a string. Presently, this
  7349. code assumes that the measurements are in degrees Fahrenheit. If the code
  7350. becomes smarter about measurement units it might be a good idea to change this.
  7351. |QLCDNumber| is capable of displaying more than just numbers, so the call to
  7352. display takes a string which in this case consists of the temperature to the
  7353. $1\over100$th of a degree and might be followed by '@q'@> which will be
  7354. converted to $^\circ$ and the letter F, C, or r to indicate the unit. This
  7355. class should be mofified to allow a user specified precision.
  7356. @<TemperatureDisplay Implementation@>=
  7357. void TemperatureDisplay::setValue(Measurement temperature)
  7358. {
  7359. QString number;
  7360. switch(unit)
  7361. {
  7362. case Units::Fahrenheit:
  7363. display(QString("%1'F").
  7364. arg(number.setNum(temperature.toFahrenheit().temperature(), 'f',
  7365. 2)));
  7366. break;
  7367. case Units::Celsius:
  7368. if(!r) {
  7369. display(QString("%1'C").
  7370. arg(number.setNum(temperature.toCelsius().temperature(), 'f',
  7371. 2)));
  7372. } else {
  7373. number.setNum(temperature.temperature() * (5.0/9.0), 'f', 2);
  7374. display(QString("%1'C").arg(number));
  7375. }
  7376. break;
  7377. case Units::Kelvin:
  7378. if(!r) {
  7379. display(QString("%1").
  7380. arg(number.setNum(temperature.toKelvin().temperature(), 'f',
  7381. 2)));
  7382. } else {
  7383. number.setNum(temperature.temperature() * (5.0/9.0), 'f', 2);
  7384. display(QString("%1").arg(number));
  7385. }
  7386. break;
  7387. case Units::Rankine:
  7388. display(QString("%1'r").
  7389. arg(number.setNum(temperature.toRankine().temperature(), 'f',
  7390. 2)));
  7391. break;
  7392. case Units::Unitless:
  7393. display(QString("%1").arg(number.setNum(temperature.temperature(), 'f', 0)));
  7394. break;
  7395. default:
  7396. switch(temperature.scale())
  7397. {
  7398. case Units::Fahrenheit:
  7399. display(QString("%1'F").
  7400. arg(number.setNum(temperature.temperature(), 'f', 2)));
  7401. break;
  7402. case Units::Celsius:
  7403. display(QString("%1'C").
  7404. arg(number.setNum(temperature.temperature(), 'f', 2)));
  7405. break;
  7406. case Units::Kelvin:
  7407. display(QString("%1").
  7408. arg(number.setNum(temperature.temperature(), 'f', 2)));
  7409. break;
  7410. case Units::Rankine:
  7411. display(QString("%1'r").
  7412. arg(number.setNum(temperature.temperature(), 'f', 2)));
  7413. break;
  7414. case Units::Unitless:
  7415. display(QString("%1").arg(number.setNum(temperature.temperature(), 'f', 0)));
  7416. break;
  7417. default:
  7418. qDebug() << "Warning: Attempting to convert a non-temperature unit to a temperature unit";
  7419. break;
  7420. }
  7421. break;
  7422. }
  7423. }
  7424. @ Before measurements are displayed, we set a more sensible default display
  7425. style and an initial string. These defaults can all be overridden with calls to
  7426. the usual |QLCDNumber| methods.
  7427. \medskip
  7428. \resizebox*{6.3in}{!}{\includegraphics{QLCDNumber.png}}
  7429. \smallskip
  7430. \centerline{Figure \secno: Outline (Qt default) and Filled |QLCDNumber| Example}
  7431. \medskip
  7432. @<TemperatureDisplay Implementation@>=
  7433. TemperatureDisplay::TemperatureDisplay(QWidget *parent) :
  7434. QLCDNumber(8, parent), unit(Units::Fahrenheit), r(false)@/
  7435. {
  7436. setSegmentStyle(Filled);
  7437. display("---.--'F");
  7438. }
  7439. @ While it is not currently used, it would be good to allow an error state to
  7440. be easily discernible from a very stable temperature. Currently, if an error
  7441. occurs that results in the measurement thread exiting, no new measurements will
  7442. be generated and the temperature display will continue to read the most recent
  7443. measured value. If an error signal were emitted, it could be connected to the
  7444. following code to change the display to reflect the fact that the current
  7445. temperature is unknown.
  7446. @<TemperatureDisplay Implementation@>=
  7447. void TemperatureDisplay::invalidate()
  7448. {
  7449. display("---.--'F");
  7450. }
  7451. @ \pn{} supports the display of multiple types of unit. Typically, we use the
  7452. Auto scale which will cause |TemperatureDisplay| objects to display measurements
  7453. in whichever scale the measurement is associated with. Alternately, we can fix
  7454. the scale to a different supported scale and convert measurements to that scale
  7455. prior to display.
  7456. @<TemperatureDisplay Implementation@>=
  7457. void TemperatureDisplay::setDisplayUnits(Units::Unit scale)
  7458. {
  7459. unit = scale;
  7460. }
  7461. @ All that is left to deal with is the empty destructor.
  7462. @<TemperatureDisplay Implementation@>=
  7463. TemperatureDisplay::~TemperatureDisplay()
  7464. {
  7465. /* Nothing has to be done here. */
  7466. }
  7467. @ To use a |TemperatureDisplay| from a script, we need a function to pass a new
  7468. object to the scripting engine.
  7469. @<Function prototypes for scripting@>=
  7470. QScriptValue constructTemperatureDisplay(QScriptContext *context,
  7471. QScriptEngine *engine);
  7472. void setTemperatureDisplayProperties(QScriptValue value, QScriptEngine *engine);
  7473. QScriptValue TemperatureDisplay_setDisplayUnits(QScriptContext *context,
  7474. QScriptEngine *engine);
  7475. @ The scripting engine must be informed of this function.
  7476. @<Set up the scripting engine@>=
  7477. constructor = engine->newFunction(constructTemperatureDisplay);
  7478. value = engine->newQMetaObject(&TemperatureDisplay::staticMetaObject,
  7479. constructor);
  7480. engine->globalObject().setProperty("TemperatureDisplay", value);
  7481. @ The constructor is trivial.
  7482. @<Functions for scripting@>=
  7483. QScriptValue constructTemperatureDisplay(QScriptContext *,
  7484. QScriptEngine *engine)
  7485. {
  7486. QScriptValue object = engine->newQObject(new TemperatureDisplay);
  7487. setTemperatureDisplayProperties(object, engine);
  7488. return object;
  7489. }
  7490. void setTemperatureDisplayProperties(QScriptValue value, QScriptEngine *engine)
  7491. {
  7492. setQLCDNumberProperties(value, engine);
  7493. value.setProperty("setDisplayUnits",
  7494. engine->newFunction(TemperatureDisplay_setDisplayUnits));
  7495. }
  7496. @ There seems to be a bad interaction when enumerated value types as used as
  7497. the argument to slot methods called through QtScript. Script code that attempts
  7498. to make use of the enumeration appears to get the value without any type
  7499. information. When attempting to use that value as an argument the meta-object
  7500. system cannot find an appropriate match and the script just hangs silently.
  7501. The solution is to wrap such methods in the script bindings and explicitly cast
  7502. the argument value to the enumerated type. This looks stupid but it works.
  7503. @<Functions for scripting@>=
  7504. QScriptValue TemperatureDisplay_setDisplayUnits(QScriptContext *context, QScriptEngine *)
  7505. {
  7506. TemperatureDisplay *self = getself<@[TemperatureDisplay *@]>(context);
  7507. self->setDisplayUnits((Units::Unit)argument<int>(0, context));
  7508. return QScriptValue();
  7509. }
  7510. @* The MeasurementTimeOffset class.
  7511. When a |DAQ| object creates a |Measurement| object, the time component is a
  7512. system time. In most cases, the system time is not interesting and a more useful
  7513. time would be relative to the start of a process. This class should be used as a
  7514. filter, taking measurements with a system time stamp and producing measurements
  7515. with a relative time.
  7516. @<Class declarations@>=
  7517. class MeasurementTimeOffset : public QObject@/
  7518. {@t\1@>@/
  7519. Q_OBJECT@;
  7520. QTime epoch;
  7521. QTime previous;
  7522. bool hasPrevious;@/
  7523. public:@;
  7524. MeasurementTimeOffset(QTime zero);
  7525. QTime zeroTime();@/
  7526. @t\4@>public slots@t\kern-3pt@>:@;
  7527. void newMeasurement(Measurement measure);
  7528. void setZeroTime(QTime zero);
  7529. signals:@;
  7530. void measurement(Measurement measure);@t\2@>@/
  7531. }@t\kern-3pt@>;
  7532. @ The interesting part of this class is the function which takes a measurement
  7533. with a system time and produces a new measurement with a time relative to some
  7534. start time.
  7535. When using this class, it is possible that a measurement will arrive with a time
  7536. slightly before a newly chosen epoch. In such a case we do not want to simply
  7537. subtract the epoch from the measurement time as other classes will interpret
  7538. this incorrectly as a measurement time slightly less than 1 hour. This means
  7539. that we need to check if the measurement time is before the epoch. If it is, we
  7540. consider it to have been generated at the epoch rather than before. Aren't race
  7541. conditions fun?
  7542. Additionally, since we're only getting time of day information, some special
  7543. handling must be done for time series that cross the boundary between days. We
  7544. should never get measurements out of order, so keeping a record of the previous
  7545. measurement and verifying that the new measurement comes after it is sufficient.
  7546. @<MeasurementTimeOffset Implementation@>=
  7547. void MeasurementTimeOffset::newMeasurement(Measurement measure)@t\2\2@>@/
  7548. {@t\1@>@/
  7549. if(measure.time() < epoch)@/
  7550. {
  7551. if(hasPrevious)@/
  7552. {
  7553. QTime jitBase(epoch.hour() - 1, epoch.minute(), epoch.second(),
  7554. epoch.msec());
  7555. QTime jitComp(epoch.hour(), measure.time().minute(),
  7556. measure.time().second(), measure.time().msec());
  7557. int relTime = jitBase.msecsTo(jitComp);
  7558. @<Produce and emit relative time@>@;
  7559. }
  7560. else@/
  7561. {
  7562. Measurement rel = measure;
  7563. rel.setTime(QTime(0, 0, 0, 0));
  7564. emit measurement(rel);
  7565. }
  7566. }
  7567. else@/
  7568. {
  7569. int relTime = epoch.msecsTo(measure.time());
  7570. @<Produce and emit relative time@>@;
  7571. }
  7572. hasPrevious = true;
  7573. previous = measure.time();@t\2@>@/
  7574. }
  7575. @ The measurement emitted has a time with the number of minutes, seconds, and
  7576. milliseconds since the start of the batch. We never generate a time greater
  7577. than 1 hour.
  7578. @<Produce and emit relative time@>=
  7579. QTime newTime(0, 0, 0, 0);
  7580. newTime = newTime.addMSecs(relTime);
  7581. if(newTime.hour() > 0)
  7582. {
  7583. newTime.setHMS(0, newTime.minute(), newTime.second(), newTime.msec());
  7584. }
  7585. Measurement rel = measure;
  7586. rel.setTime(newTime);
  7587. emit measurement(rel);
  7588. @ The rest of the code handles updating and reporting the reference time.
  7589. @<MeasurementTimeOffset Implementation@>=
  7590. MeasurementTimeOffset::MeasurementTimeOffset(QTime zero) : epoch(zero),
  7591. previous(0, 0, 0, 0), hasPrevious(false)
  7592. {
  7593. /* Nothing has to be done here. */
  7594. }
  7595. QTime MeasurementTimeOffset::zeroTime()
  7596. {
  7597. return epoch;
  7598. }
  7599. void MeasurementTimeOffset::setZeroTime(QTime zero)
  7600. {
  7601. epoch = zero;
  7602. hasPrevious = false;
  7603. }
  7604. @ Two functions are required to make this class available to the scripting
  7605. engine.
  7606. @<Function prototypes for scripting@>=
  7607. QScriptValue constructMeasurementTimeOffset(QScriptContext *context,@|
  7608. QScriptEngine *engine);
  7609. void setMeasurementTimeOffsetProperties(QScriptValue value,
  7610. QScriptEngine *engine);
  7611. @ The scripting engine must be informed of the constructor.
  7612. @<Set up the scripting engine@>=
  7613. constructor = engine->newFunction(constructMeasurementTimeOffset);
  7614. value = engine->newQMetaObject(&MeasurementTimeOffset::staticMetaObject,
  7615. constructor);
  7616. engine->globalObject().setProperty("MeasurementTimeOffset", value);
  7617. @ Previously, another property was added to the newly created object. That
  7618. method is believed to be obsolete and has been removed. Careful testing will
  7619. need to be done to verify that this decision was sane. I was very hungry when
  7620. that change was made.
  7621. @<Functions for scripting@>=
  7622. QScriptValue constructMeasurementTimeOffset(QScriptContext *,
  7623. QScriptEngine *engine)
  7624. {
  7625. QScriptValue object =@|
  7626. engine->newQObject(new MeasurementTimeOffset(QTime::currentTime()));
  7627. setMeasurementTimeOffsetProperties(object, engine);
  7628. return object;
  7629. }
  7630. void setMeasurementTimeOffsetProperties(QScriptValue value,
  7631. QScriptEngine *engine)
  7632. {
  7633. setQObjectProperties(value, engine);
  7634. }
  7635. @* Measured value threshold detection.
  7636. \noindent There are times when one might want to detect when a measured value
  7637. from a data series has passed a given value, with the limitation that we are
  7638. only interested in the ascending or descending edge. This can be used, for
  7639. example, to translate roast profile data in a graph along the time axis such
  7640. that the bean temperature series are aligned at a given
  7641. temperature.\nfnote{More details on the reasoning behind why one might want
  7642. to do this are provided at:\par\indent\pdfURL{http://youtu.be/hS0SfzypyFQ}
  7643. {http://youtu.be/hS0SfzypyFQ}} For this we can use a |ThresholdDetector|.
  7644. If we would like to catch changes on both the ascending and descending edge, we
  7645. can use two objects, however it may be a good idea to use more than two to
  7646. allow for sane behavior in the face of hysteresis.
  7647. @<Class declarations@>=
  7648. class ThresholdDetector : public QObject@/
  7649. {
  7650. @[Q_OBJECT@]@;
  7651. @[Q_ENUMS(EdgeDirection)@]@;
  7652. public:@/
  7653. enum EdgeDirection {
  7654. Ascending, Descending
  7655. };
  7656. ThresholdDetector(double value);
  7657. @[public slots@]:@/
  7658. void newMeasurement(Measurement measure);
  7659. void setThreshold(double value);
  7660. void setEdgeDirection(EdgeDirection direction);
  7661. signals:@/
  7662. void timeForValue(double);
  7663. private:@/
  7664. bool previousValueValid;
  7665. double previousValue;
  7666. double threshold;
  7667. EdgeDirection currentDirection;
  7668. };
  7669. @ This class emits the time in seconds when a given measurement crosses the
  7670. threshold value in the appropriate direction.
  7671. This was previously written with |previousValue| initialized negative and a
  7672. check that |previousValue| was non-negative. When the |ThresholdDetector| is
  7673. connected to a data source representing temperature measurements this is a
  7674. reasonable choice, however it breaks when connected to a rate of change series.
  7675. To make this more generally correct, a boolean is checked to determine if a
  7676. previous value has been set.
  7677. @<ThresholdDetector Implementation@>=
  7678. void ThresholdDetector::newMeasurement(Measurement measure)
  7679. {
  7680. if((currentDirection == Ascending && previousValue < threshold &&
  7681. previousValueValid) || (currentDirection == Descending &&
  7682. previousValue > threshold && previousValueValid))
  7683. {
  7684. if((currentDirection == Ascending && measure.temperature() >= threshold) ||
  7685. (currentDirection == Descending && measure.temperature() <= threshold))
  7686. {
  7687. double offset = measure.time().hour() * 60 * 60;
  7688. offset += measure.time().minute() * 60;
  7689. offset += measure.time().second();
  7690. offset += measure.time().msec()/1000;
  7691. emit timeForValue(offset);
  7692. }
  7693. }
  7694. previousValue = measure.temperature();
  7695. previousValueValid = true;
  7696. }
  7697. ThresholdDetector::ThresholdDetector(double value) : QObject(NULL),
  7698. previousValueValid(false),
  7699. previousValue(-1), threshold(value), currentDirection(Ascending)
  7700. {
  7701. /* Nothing needs to be done here. */
  7702. }
  7703. void ThresholdDetector::setThreshold(double value)
  7704. {
  7705. threshold = value;
  7706. }
  7707. void ThresholdDetector::setEdgeDirection(EdgeDirection direction)
  7708. {
  7709. currentDirection = direction;
  7710. }
  7711. @ This is exposed to the host environment.
  7712. @<Function prototypes for scripting@>=
  7713. QScriptValue constructThresholdDetector(QScriptContext *context, QScriptEngine *engine);
  7714. void setThresholdDetectorProperties(QScriptValue value, QScriptEngine *engine);
  7715. @ Inform the engine of the constructor.
  7716. @<Set up the scripting engine@>=
  7717. constructor = engine->newFunction(constructThresholdDetector);
  7718. value = engine->newQMetaObject(&ThresholdDetector::staticMetaObject, constructor);
  7719. engine->globalObject().setProperty("ThresholdDetector", value);
  7720. @ Implementation. At present I'@q'@>m not bothering to implement constructor
  7721. arguments here and am aligning on a fixed point. Another slot method was added
  7722. to restore adjustability.
  7723. @<Functions for scripting@>=
  7724. QScriptValue constructThresholdDetector(QScriptContext *, QScriptEngine *engine)
  7725. {
  7726. QScriptValue object = engine->newQObject(new ThresholdDetector(300));
  7727. return object;
  7728. }
  7729. void setThresholdDetectorProperties(QScriptValue value, QScriptEngine *engine)
  7730. {
  7731. setQObjectProperties(value, engine);
  7732. }
  7733. @* The ZeroEmitter class.
  7734. \noindent Now that it is possible to record the time a measurement was taken
  7735. relative to an arbitrary start time, there is a minor problem left for logging.
  7736. It is extremely unlikely that a measurement will be passed through at the epoch.
  7737. For this, what we want to do is save the previous measurement and make it
  7738. available at time 0 whenever the start time is reset. This is the role of the
  7739. |ZeroEmitter| class.
  7740. Another problem is that most classes that are interested in a relative time are
  7741. also interested in logging multiple sets of temperature data. To facilitate this
  7742. an integer is emitted. Different temperature measurement sources should be set
  7743. to emit different numbers. A table view would place measurements in the
  7744. indicated column. A graph view would use different colors to plot different sets
  7745. of data.
  7746. @<Class declarations@>=
  7747. class ZeroEmitter : public QObject@/
  7748. {@t\1@>@/
  7749. @[Q_OBJECT@]@;
  7750. @[Q_PROPERTY(int column READ column WRITE setColumn)@]@;
  7751. Measurement cache;
  7752. int col;
  7753. public:@/
  7754. ZeroEmitter(int tempcolumn = 1);
  7755. int column();
  7756. double lastTemperature();@/
  7757. @t\4@>public slots@t\kern-3pt@>:@;
  7758. void newMeasurement(Measurement measure);
  7759. void setColumn(int column);
  7760. void emitZero();
  7761. signals:@;
  7762. void measurement(Measurement measure, int tempcolumn);@t\2@>@/
  7763. }@t\kern-3pt@>;
  7764. @ The implementation of the class is trivial.
  7765. @<ZeroEmitter Implementation@>=
  7766. ZeroEmitter::ZeroEmitter(int tempcolumn) : QObject(NULL), col(tempcolumn)@;
  7767. {
  7768. /* Nothing has to be done here. */
  7769. }
  7770. int ZeroEmitter::column()
  7771. {
  7772. return col;
  7773. }
  7774. double ZeroEmitter::lastTemperature()
  7775. {
  7776. return cache.temperature();
  7777. }
  7778. void ZeroEmitter::newMeasurement(Measurement measure)
  7779. {
  7780. cache = measure;
  7781. }
  7782. void ZeroEmitter::setColumn(int column)
  7783. {
  7784. col = column;
  7785. }
  7786. void ZeroEmitter::emitZero()
  7787. {
  7788. cache.setTime(QTime(0, 0, 0, 0));
  7789. emit measurement(cache, col);
  7790. }
  7791. @ Making this class available to scripts requires only two functions.
  7792. @<Function prototypes for scripting@>=
  7793. QScriptValue constructZeroEmitter(QScriptContext *context,
  7794. QScriptEngine *engine);
  7795. void setZeroEmitterProperties(QScriptValue value, QScriptEngine *engine);
  7796. @ To use it, we must inform the engine of the constructor.
  7797. @<Set up the scripting engine@>=
  7798. constructor = engine->newFunction(constructZeroEmitter);
  7799. value = engine->newQMetaObject(&ZeroEmitter::staticMetaObject, constructor);
  7800. engine->globalObject().setProperty("ZeroEmitter", value);
  7801. @ The implementation of the constructor is trivial.
  7802. @<Functions for scripting@>=
  7803. QScriptValue constructZeroEmitter(QScriptContext *context,
  7804. QScriptEngine *engine)
  7805. {
  7806. QScriptValue object =
  7807. engine->newQObject(new ZeroEmitter(argument<int>(0, context)));
  7808. setZeroEmitterProperties(object, engine);
  7809. return object;
  7810. }
  7811. void setZeroEmitterProperties(QScriptValue value, QScriptEngine *engine)
  7812. {
  7813. setQObjectProperties(value, engine);
  7814. }
  7815. @* The MeasurementAdapter class.
  7816. \noindent The last of the measurement filter classes is the |MeasurementAdapter|
  7817. class. This takes measurements, typically from a |MeasurementTimeOffset|, and
  7818. sends the measurement out with a number to indicate which data series the
  7819. measurement belongs to.
  7820. The measurement pipeline could probably be made simpler by introducing a series
  7821. identifier into the measurement class.
  7822. @<Class declarations@>=
  7823. class MeasurementAdapter : public QObject@/
  7824. {@t\1@>@/
  7825. Q_OBJECT@;
  7826. int col;
  7827. public:@/
  7828. MeasurementAdapter(int tempcolumn);
  7829. int column();@/
  7830. @t\4@>public slots@t\kern-3pt@>:@/
  7831. void newMeasurement(Measurement measure);
  7832. void setColumn(int column);
  7833. signals:@/
  7834. void measurement(Measurement measure, int tempcolumn);@t\2@>@/
  7835. }@t\kern-3pt@>;
  7836. @ The implementation of this filter class is trivial.
  7837. @<MeasurementAdapter Implementation@>=
  7838. MeasurementAdapter::MeasurementAdapter(int tempcolumn) : col(tempcolumn)@;
  7839. {
  7840. /* Nothing has to be done here. */
  7841. }
  7842. int MeasurementAdapter::column()
  7843. {
  7844. return col;
  7845. }
  7846. void MeasurementAdapter::newMeasurement(Measurement measure)
  7847. {
  7848. emit measurement(measure, col);
  7849. }
  7850. void MeasurementAdapter::setColumn(int column)
  7851. {
  7852. col = column;
  7853. }
  7854. @ This filter class is also available from the host environment.
  7855. @<Function prototypes for scripting@>=
  7856. QScriptValue constructMeasurementAdapter(QScriptContext *context,
  7857. QScriptEngine *engine);
  7858. void setMeasurementAdapterProperties(QScriptValue value, QScriptEngine *engine);
  7859. @ As usual, the engine must be informed of the constructor.
  7860. @<Set up the scripting engine@>=
  7861. constructor = engine->newFunction(constructMeasurementAdapter);
  7862. value = engine->newQMetaObject(&MeasurementAdapter::staticMetaObject,
  7863. constructor);
  7864. engine->globalObject().setProperty("MeasurementAdapter", value);
  7865. @ The implementation is trivial.
  7866. @<Functions for scripting@>=
  7867. QScriptValue constructMeasurementAdapter(QScriptContext *context,
  7868. QScriptEngine *engine)
  7869. {
  7870. QScriptValue object =
  7871. engine->newQObject(new MeasurementAdapter(argument<int>(0, context)));
  7872. setMeasurementAdapterProperties(object, engine);
  7873. return object;
  7874. }
  7875. void setMeasurementAdapterProperties(QScriptValue value, QScriptEngine *engine)
  7876. {
  7877. setQObjectProperties(value, engine);
  7878. }
  7879. @* A graph of temperature over time.
  7880. \noindent A useful tool when roasting is a visual depiction of the current batch
  7881. as it happens, possibly laid over a previously recorded target profile. The
  7882. |GraphView| class can take multiple sets of temperature data and produce such a
  7883. visualization.
  7884. \medskip
  7885. \centerline{\includegraphics{roast}}
  7886. \smallskip
  7887. \centerline{Figure \secno: A Typical Roast}
  7888. \medskip
  7889. This class assumes that temperature data will be passed in the correct order.
  7890. @<Class declarations@>=
  7891. class GraphView : public QGraphicsView@/
  7892. {@t\1@>@/
  7893. Q_OBJECT@;
  7894. QGraphicsScene *theScene;@/
  7895. QMap<int, QList<QGraphicsLineItem * > * > *graphLines;@/
  7896. QMap<int, QPointF> *prevPoints;
  7897. QMap<int, double> *translations;
  7898. QList<QGraphicsItem *> *gridLinesF;
  7899. QList<QGraphicsItem *> *gridLinesC;
  7900. QList<QGraphicsItem *> *relativeGridLines;
  7901. bool relativeEnabled;
  7902. bool timeIndicatorEnabled;
  7903. QGraphicsLineItem *timeLine;
  7904. LinearSplineInterpolator *relativeAdjuster;@/
  7905. public:@/
  7906. GraphView(QWidget *parent = NULL);
  7907. void removeSeries(int column);@/
  7908. protected:@/
  7909. void resizeEvent(QResizeEvent *event);@/
  7910. @t\4@>public slots@t\kern-3pt@>:@/
  7911. void newMeasurement(Measurement measure, int tempcolumn);
  7912. void setSeriesTranslation(int column, double offset);
  7913. void setTimeIndicatorEnabled(bool enabled);
  7914. void clear();
  7915. void showF();
  7916. void showC();@t\2@>@/
  7917. }@t\kern-3pt@>;
  7918. @ I decided that it would probably be best to keep the graph area the same even
  7919. with different roast lengths over different temperature ranges so that similar
  7920. portions between these graphs would continue to look similar rather than
  7921. constantly attempting to select the best way to display the data currently in
  7922. the view.
  7923. I have chosen to represent roasting times of up to 20 minutes and temperatures
  7924. between 0 and 500$^\circ$ Fahrenheit. This should certainly be configurable at
  7925. run time, but until that is implemented, roasters who routinely roast batches
  7926. for longer periods of time will want to change the constructor. This class
  7927. should probably be modified to allow the user to specify several characteristics
  7928. of the display.
  7929. This class must also deal with the fact that coordinates in a |QGraphicsScene|
  7930. are not quite the same as coordinates in a cartesian plane. The easiest way to
  7931. deal with this is to negate the y coordinate and translate the viewport to the
  7932. area we draw in.
  7933. A small margin area left around the edges of the graph. This should probably be
  7934. configurable for those with particularly small displays.
  7935. @<GraphView Implementation@>=
  7936. GraphView::GraphView(QWidget *parent) : QGraphicsView(parent),
  7937. theScene(new QGraphicsScene),@/
  7938. graphLines(new QMap<int, QList<QGraphicsLineItem *> *>),@/
  7939. prevPoints(new QMap<int, QPointF>),
  7940. translations(new QMap<int, double>),
  7941. gridLinesF(new QList<QGraphicsItem *>),
  7942. gridLinesC(new QList<QGraphicsItem *>),
  7943. relativeGridLines(new QList<QGraphicsItem *>),
  7944. relativeEnabled(false),
  7945. timeIndicatorEnabled(false),
  7946. timeLine(new QGraphicsLineItem),
  7947. relativeAdjuster(new LinearSplineInterpolator)@/
  7948. {
  7949. setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  7950. setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  7951. setScene(theScene);
  7952. setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
  7953. QPen timePen;
  7954. timePen.setColor(QColor(160, 160, 164, 127)); //gray, half opacity
  7955. timeLine->setPen(timePen);
  7956. timeLine->setLine(0, 0, 0, -500);
  7957. timeLine->hide();
  7958. theScene->addItem(timeLine);
  7959. @<Draw temperature axis and grid lines@>;
  7960. @<Draw secondary axes@>@;
  7961. @<Draw time axis and ticks@>;
  7962. fitInView(theScene->sceneRect().adjusted(-50,-50,50,50));
  7963. }
  7964. @ Grid lines are drawn every 100 degrees. These lines are labeled with
  7965. |setHtml()| for convenient access to the $^\circ$ symbol. If \pn{} is modified
  7966. to allow different units, this code should also be modified.
  7967. As of version 1.3.3 it is possible to switch the graph between degrees
  7968. Fahrenheit and degrees Celcius. Celcius grid lines are drawn but initially
  7969. hidden. Both the grid lines and the labels are added to a list depending on the
  7970. unit so that when changing from one view to another all that needs to happen is
  7971. hide one list of items and show another.
  7972. @<Draw temperature axis and grid lines@>=
  7973. QGraphicsLineItem *tempaxis = new QGraphicsLineItem;
  7974. tempaxis->setLine(-10, -500, -10, 0);
  7975. theScene->addItem(tempaxis);
  7976. QGraphicsLineItem *gridLine;
  7977. QGraphicsTextItem *label;
  7978. for(int y = -100; y > -600; y -= 100)@/
  7979. {@/
  7980. gridLine = new QGraphicsLineItem;
  7981. gridLine->setLine(0, y, 1200, y);
  7982. theScene->addItem(gridLine);
  7983. label = new QGraphicsTextItem;
  7984. label->setHtml(QString("%1&deg;F").arg(-y));
  7985. label->setPos(-55, y - (label->boundingRect().height() / 2));
  7986. theScene->addItem(label);
  7987. gridLinesF->append(gridLine);
  7988. gridLinesF->append(label);
  7989. }
  7990. for(int degC = 50; degC <= 250; degC += 50)
  7991. {
  7992. gridLine = new QGraphicsLineItem;
  7993. int y = -(degC * (9.0/5.0) + 32);
  7994. gridLine->setLine(0, y, 1200, y);
  7995. gridLine->hide();
  7996. theScene->addItem(gridLine);
  7997. gridLinesC->append(gridLine);
  7998. label = new QGraphicsTextItem;
  7999. label->setHtml(QString("%1&deg;C").arg(degC));
  8000. label->setPos(-55, y - (label->boundingRect().height() / 2));
  8001. label->hide();
  8002. theScene->addItem(label);
  8003. gridLinesC->append(label);
  8004. }
  8005. @ If we are going to plot relative temperature measurements, we must obtain
  8006. information on how we wish to do that from settings. We take advantage of the
  8007. fact that iterating over the keys in a |QMap| produces results in sorted order.
  8008. While drawing the grid lines we also set up the |relativeAdjuster| that will be
  8009. used to transform incoming measurements to our coordinate system.
  8010. @<Draw secondary axes@>=
  8011. QSettings settings;
  8012. if(settings.contains("settings/graph/relative/enable"))
  8013. {
  8014. if(settings.value("settings/graph/relative/enable").toBool())
  8015. {
  8016. relativeEnabled = @[true@];
  8017. QColor relativeColor = QColor(settings.value("settings/graph/relative/color").toString());
  8018. QString unit = QString(settings.value(@|"settings/graph/relative/unit").toInt() == 0 ? "F" : "C");
  8019. QMap<double, QString> relativeAxisPairs;
  8020. QStringList relativeAxisLabels = settings.value(@|"settings/graph/relative/grid").toString().split(QRegExp("[\\s,]+"), QString::SkipEmptyParts);
  8021. foreach(QString item, relativeAxisLabels)
  8022. {
  8023. relativeAxisPairs.insert(item.toDouble(), item);
  8024. }
  8025. if(relativeAxisPairs.size() > 1)
  8026. {
  8027. double skip = 500.0 / (relativeAxisPairs.size() - 1);
  8028. double y = 0;
  8029. foreach(double key, relativeAxisPairs.keys())
  8030. {
  8031. gridLine = new QGraphicsLineItem;
  8032. gridLine->setLine(0, y, 1205, y);
  8033. gridLine->setPen(QPen(relativeColor));
  8034. theScene->addItem(gridLine);
  8035. relativeGridLines->append(gridLine);
  8036. label = new QGraphicsTextItem;
  8037. label->setHtml(QString("%1&deg;%2").arg(relativeAxisPairs.value(key)).arg(unit));
  8038. label->setPos(1210, y - (label->boundingRect().height() / 2));
  8039. theScene->addItem(label);
  8040. relativeGridLines->append(label);
  8041. if(unit == "F")
  8042. {
  8043. relativeAdjuster->add_pair(key, -y);
  8044. }
  8045. else
  8046. {
  8047. relativeAdjuster->add_pair(key * (9.0/5.0), -y);
  8048. }
  8049. y -= skip;
  8050. }
  8051. }
  8052. }
  8053. }
  8054. @ Two slots are used to switch between the different sets of grid lines.
  8055. @<GraphView Implementation@>=
  8056. void GraphView::showF()
  8057. {
  8058. for(int i = 0; i < gridLinesF->size(); i++)
  8059. {
  8060. gridLinesF->at(i)->show();
  8061. }
  8062. for(int i = 0; i < gridLinesC->size(); i++)
  8063. {
  8064. gridLinesC->at(i)->hide();
  8065. }
  8066. }
  8067. void GraphView::showC()
  8068. {
  8069. for(int i = 0; i < gridLinesF->size(); i++)
  8070. {
  8071. gridLinesF->at(i)->hide();
  8072. }
  8073. for(int i = 0; i < gridLinesC->size(); i++)
  8074. {
  8075. gridLinesC->at(i)->show();
  8076. }
  8077. }
  8078. @ The time axis has a tick every two minutes. The use of the |?| tertiary
  8079. operator shifts the labels with two digits a little more than labels with only
  8080. one digit. If it worked, a more resilient approach would be to take the width of
  8081. the label and center it under the tick.
  8082. @<Draw time axis and ticks@>=
  8083. QGraphicsLineItem *timeaxis = new QGraphicsLineItem;
  8084. timeaxis->setLine(0, 10, 1200, 10);
  8085. theScene->addItem(timeaxis);
  8086. for(int x = 0; x < 1201; x += 120)@/
  8087. {@/
  8088. QGraphicsLineItem *tick = new QGraphicsLineItem;
  8089. tick->setLine(x, 0, x, 20);
  8090. theScene->addItem(tick);
  8091. QGraphicsTextItem *label = new QGraphicsTextItem;
  8092. label->setPlainText(QString("%1").arg(x/60));
  8093. label->setPos(x - (label->boundingRect().width() / 2), 20);
  8094. theScene->addItem(label);
  8095. }
  8096. @ Typically, the user will be able to resize the graph. When the widget is
  8097. resized, we should fit the graph to the new size of the widget. This is safe to
  8098. do as we have already turned off the scroll bars.
  8099. @<GraphView Implementation@>=
  8100. void GraphView::resizeEvent(QResizeEvent *)
  8101. {
  8102. fitInView(theScene->sceneRect().adjusted(-50,-50,50,50));
  8103. }
  8104. @ When adding a new measurement, there are three cases that should be
  8105. considered. In the case of the first measurement, no drawing occurs. A |QList|
  8106. of line items is initialized when the second measurement is taken. Subsequent
  8107. measurements are able to simply append new line segments to the list.
  8108. Relative measurements are first converted to the coordinate system of the
  8109. appropriate secondary axis.
  8110. @<GraphView Implementation@>=
  8111. #define FULLTIMETOINT(t) (t.msec() + (t.second() * 1000) + (t.minute() * 60 * 1000))
  8112. void GraphView::newMeasurement(Measurement measure, int tempcolumn)@/
  8113. {@/
  8114. double offset = 0;
  8115. if(measure.contains("relative"))
  8116. {
  8117. if(measure.value("relative").toBool())
  8118. {
  8119. if(relativeEnabled)
  8120. {
  8121. measure.setTemperature(relativeAdjuster->newMeasurement(measure).temperature());
  8122. }
  8123. else
  8124. {
  8125. return;
  8126. }
  8127. }
  8128. }
  8129. if(translations->contains(tempcolumn))
  8130. {
  8131. offset = translations->value(tempcolumn);
  8132. }
  8133. if(prevPoints->contains(tempcolumn))@/
  8134. @t\1@>{@/
  8135. @<At least one measurement exists@>@;
  8136. if(graphLines->contains(tempcolumn))@/
  8137. {@t\1@>
  8138. /* More than one measurement existed. */
  8139. graphLines->value(tempcolumn)->append(segment);@t\2@>@/
  8140. }@/
  8141. else@/
  8142. {@/
  8143. /* This is the second measurement. */
  8144. QList<QGraphicsLineItem *> *newLine =
  8145. new QList<QGraphicsLineItem *>;@/
  8146. newLine->append(segment);
  8147. graphLines->insert(tempcolumn, newLine);
  8148. }@t\2@>@/
  8149. }@/
  8150. else@/
  8151. {@/
  8152. @<Handle the first measurement@>@;
  8153. }
  8154. }
  8155. @ There are some parts of the code that are correct, but seem somewhat goofy.
  8156. This is especially true surrounding the graphics view architecture as this was
  8157. not working correctly when I wrote the code that uses it. The code as it is
  8158. written works for me, but when these workarounds are no longer needed it would
  8159. be better to remove them. Handling values on the time axis is one example of
  8160. this.
  8161. Some might question the use of an integer data type, particularly when storing
  8162. the result of a division operation. While this could be a concern if high
  8163. resolution wall sized displays become common, is is expected that in most cases
  8164. with sufficiently high sample rates, many data points will map to the same pixel
  8165. even with the minor data loss below.
  8166. In the case of the first measurement,
  8167. @<Handle the first measurement@>=
  8168. int x = FULLTIMETOINT(measure.time())/1000;
  8169. prevPoints->insert(tempcolumn, QPointF(x, measure.temperature()));
  8170. if(timeIndicatorEnabled)
  8171. {
  8172. timeLine->setLine(x, 0, x, -500);
  8173. }
  8174. @ When at least one measurement already exists, we need to handle drawing the
  8175. line between the new measurement and the previous measurement.
  8176. \danger At present, the color chosen for these lines is based on the temperature
  8177. column passed to the graph. It would be better if colors could be passed to the
  8178. view for a specified series rather than have this hard coded. \endanger
  8179. @<At least one measurement exists@>=
  8180. QGraphicsLineItem *segment = new QGraphicsLineItem;
  8181. QPointF nextPoint(FULLTIMETOINT(measure.time())/1000, measure.temperature());
  8182. segment->setLine(prevPoints->value(tempcolumn).x() + offset,
  8183. -(prevPoints->value(tempcolumn).y()),
  8184. nextPoint.x() + offset, -(nextPoint.y()));
  8185. static QColor p[12] = {Qt::yellow, Qt::blue, Qt::cyan, Qt::red, Qt::magenta,
  8186. Qt::green, Qt::darkGreen, Qt::darkMagenta,
  8187. Qt::darkRed, Qt::darkCyan, Qt::darkBlue,
  8188. Qt::darkYellow};
  8189. segment->setPen(p[tempcolumn % 12]);
  8190. theScene->addItem(segment);
  8191. prevPoints->insert(tempcolumn, nextPoint);
  8192. if(timeIndicatorEnabled)
  8193. {
  8194. timeLine->setLine(nextPoint.x() + offset, 0, nextPoint.x() + offset, -500);
  8195. }
  8196. @ In addition to adding data to the view, we also sometimes want to clear the
  8197. view of data.
  8198. @<GraphView Implementation@>=
  8199. void GraphView::clear()
  8200. {
  8201. int i;
  8202. foreach(i, prevPoints->keys())
  8203. {
  8204. removeSeries(i);
  8205. }
  8206. translations->clear();
  8207. }
  8208. @ Removing a set of data from the view involves removing the lines from the
  8209. scene and removing the column from a couple data structures.
  8210. @<GraphView Implementation@>=
  8211. void GraphView::removeSeries(int column)
  8212. {
  8213. if(graphLines->contains(column))
  8214. {
  8215. QList<QGraphicsLineItem *> *series = graphLines->value(column);
  8216. QGraphicsLineItem *segment;
  8217. foreach(segment, *series)
  8218. {
  8219. theScene->removeItem(segment);
  8220. }
  8221. qDeleteAll(*series);
  8222. }
  8223. graphLines->remove(column);
  8224. prevPoints->remove(column);
  8225. }
  8226. @ Second prototype for data series translation.
  8227. @<GraphView Implementation@>=
  8228. void GraphView::setSeriesTranslation(int column, double offset)
  8229. {
  8230. if(graphLines->contains(column))
  8231. {
  8232. QList<QGraphicsLineItem *> *series = graphLines->value(column);
  8233. QGraphicsLineItem *segment;
  8234. foreach(segment, *series)
  8235. {
  8236. segment->setPos(segment->pos().x()+offset, segment->pos().y());
  8237. }
  8238. }
  8239. if(translations->contains(column))
  8240. {
  8241. translations->insert(column, offset + translations->value(column));
  8242. }
  8243. else
  8244. {
  8245. translations->insert(column, offset);
  8246. }
  8247. }
  8248. @ Starting in \pn{} 1.6 it is possible to add a vertical line indicating the
  8249. time position of the most recent measurement. This should be hidden for loading
  8250. target roast profiles and shown depending on preference. A new method controls
  8251. this.
  8252. @<GraphView Implementation@>=
  8253. void GraphView::setTimeIndicatorEnabled(bool enabled)
  8254. {
  8255. timeIndicatorEnabled = enabled;
  8256. if(enabled)
  8257. {
  8258. timeLine->show();
  8259. }
  8260. else
  8261. {
  8262. timeLine->hide();
  8263. }
  8264. }
  8265. @ These functions are required to create a |GraphView| object from a script.
  8266. @<Function prototypes for scripting@>=
  8267. void setGraphViewProperties(QScriptValue value, QScriptEngine *engine);
  8268. QScriptValue constructGraphView(QScriptContext *context, QScriptEngine *engine);
  8269. @ The scripting engine must be informed of the constructor.
  8270. @<Set up the scripting engine@>=
  8271. constructor = engine->newFunction(constructGraphView);
  8272. value = engine->newQMetaObject(&GraphView::staticMetaObject, constructor);
  8273. engine->globalObject().setProperty("GraphView", value);
  8274. @ The function implementation is trivial.
  8275. @<Functions for scripting@>=
  8276. QScriptValue constructGraphView(QScriptContext *, QScriptEngine *engine)
  8277. {
  8278. QScriptValue object = engine->newQObject(new GraphView);
  8279. setGraphViewProperties(object, engine);
  8280. return object;
  8281. }
  8282. void setGraphViewProperties(QScriptValue value, QScriptEngine *engine)
  8283. {
  8284. setQGraphicsViewProperties(value, engine);
  8285. }
  8286. @* A table of roasting data.
  8287. \noindent A typical roast log is a table listing temperature measurements taken
  8288. at regular intervals. The introduction of a computer brings several advantages.
  8289. A human does not need to record the measurements. Every measurement taken can be
  8290. logged, but the measurements do not all need to be displayed. The |ZoomLog|
  8291. class presents a table with time, temperature, and annotation for one or more
  8292. sets of roasting data and allows the user to select from a few different levels
  8293. of detail.
  8294. Experience has shown that one measurement every 30 or 15 seconds is most useful,
  8295. but it is also possible to view one measurement every 1, 5, 10, or 60 seconds
  8296. and there is an option to view every measurement collected. This last is what is
  8297. saved to a file.
  8298. The zooming log is implemented by keeping a measurement model with every level
  8299. of detail of interest and making sure that new measurements get to the models
  8300. they belong in. Switching the level of detail of the view then becomes a matter
  8301. of changing which model the view is using. This is very inefficient in terms of
  8302. space, but it is very fast and simple to code.
  8303. Starting in version 1.4, column sizes are persisted automatically using the
  8304. same method as described in the section on |SqlQueryView|.
  8305. Starting in version 1.8, |rowCount()| is |Q_INVOKABLE|. This allows the manual
  8306. log entry interface to easily determine if any roasting data exists to save.
  8307. @<Class declarations@>=
  8308. class MeasurementModel;@/
  8309. class ZoomLog : public QTableView@/
  8310. {@/
  8311. @[Q_OBJECT@]@;
  8312. @<ZoomLog private member data@>@;
  8313. void switchLOD(MeasurementModel *m);@/
  8314. @[private slots@]:@/
  8315. void centerOn(int row);
  8316. void persistColumnResize(int column, int oldsize, int newsize);
  8317. void restoreColumnWidths();
  8318. public:@/
  8319. ZoomLog();
  8320. QVariant data(int row, int column) const;
  8321. @[Q_INVOKABLE@,@, int rowCount();
  8322. bool saveXML(QIODevice *device);
  8323. bool saveCSV(QIODevice *device);
  8324. QString lastTime(int series);
  8325. @[Q_INVOKABLE@,@, Units::Unit displayUnits()@];@t\2\2@>@/
  8326. @[public slots@]:@/
  8327. void setVisible(bool visibility);
  8328. void setHeaderData(int section, QString text);
  8329. void LOD_ms();
  8330. void LOD_1s();
  8331. void LOD_5s();
  8332. void LOD_10s();
  8333. void LOD_15s();
  8334. void LOD_30s();
  8335. void LOD_1m();
  8336. void newMeasurement(Measurement measure, int tempcolumn);
  8337. void newAnnotation(QString annotation, int tempcolumn,
  8338. int annotationcolumn);
  8339. void clear();
  8340. void addOutputTemperatureColumn(int column);
  8341. void addOutputControlColumn(int column);
  8342. void addOutputAnnotationColumn(int column);
  8343. void clearOutputColumns();
  8344. void setDisplayUnits(Units::Unit scale);
  8345. void addToCurrentColumnSet(int column);
  8346. void clearCurrentColumnSet();@/
  8347. protected:@/
  8348. virtual void showEvent(QShowEvent *event);
  8349. };
  8350. @ This class uses a different model for each level of detail and provides logic
  8351. for placing measurements and annotations in the appropriate models. A list of
  8352. each model is provided for conveniently performing operations that apply to
  8353. every model.
  8354. @<ZoomLog private member data@>=
  8355. MeasurementModel *model_ms;
  8356. MeasurementModel *model_1s;
  8357. MeasurementModel *model_5s;
  8358. MeasurementModel *model_10s;
  8359. MeasurementModel *model_15s;
  8360. MeasurementModel *model_30s;
  8361. MeasurementModel *model_1m;
  8362. QList<MeasurementModel *> modelSet;
  8363. QHash<int, Measurement> lastMeasurement;
  8364. MeasurementModel *currentModel;
  8365. QList<int> saveTempCols;
  8366. QList<int> saveControlCols;
  8367. QList<int> saveNoteCols;
  8368. QList<int> currentColumnSet;
  8369. @ Most of the functionality this class provides is in getting measurements to
  8370. the right models. Every measurement goes to the full detail model. We also keep
  8371. track of the most recent measurement to detect the first measurement in a new
  8372. second and pass all of these on to the 1 second level of detail model. Some of
  8373. these are also passed to other models. Additionally, the models that store
  8374. coarser data strip the millisecond portion of the time.
  8375. A decision was made to present data promptly. With a high sample rate, some
  8376. might prefer an average of a few measurements near the reported time, but such
  8377. a feature does not exist in \pn{} currently.
  8378. The first measurement is always added to each model.
  8379. @<ZoomLog Implementation@>=
  8380. void ZoomLog::newMeasurement(Measurement measure, int tempcolumn)
  8381. {
  8382. if(measure.time() != QTime(0, 0, 0, 0))
  8383. {
  8384. @<Synthesize measurements for slow hardware@>@;
  8385. }
  8386. model_ms->newMeasurement(measure, tempcolumn);
  8387. if(lastMeasurement.contains(tempcolumn))
  8388. {
  8389. if(measure.time().second() !=
  8390. lastMeasurement.value(tempcolumn).time().second())
  8391. {
  8392. Measurement adjusted = measure;
  8393. QTime adjtime(0, measure.time().minute(), measure.time().second(), 0);
  8394. adjusted.setTime(adjtime);
  8395. model_1s->newMeasurement(adjusted, tempcolumn);
  8396. if(adjusted.time().second() % 5 == 0)
  8397. {
  8398. model_5s->newMeasurement(adjusted, tempcolumn);
  8399. if(adjusted.time().second() % 10 == 0)
  8400. {
  8401. model_10s->newMeasurement(adjusted, tempcolumn);
  8402. }
  8403. if(adjusted.time().second() % 15 == 0)
  8404. {
  8405. model_15s->newMeasurement(adjusted, tempcolumn);
  8406. if(adjusted.time().second() % 30 == 0)
  8407. {
  8408. model_30s->newMeasurement(adjusted, tempcolumn);
  8409. if(adjusted.time().second() == 0)
  8410. {
  8411. model_1m->newMeasurement(adjusted, tempcolumn);
  8412. }
  8413. }
  8414. }
  8415. }
  8416. }
  8417. @<Synthesize measurements for columns in set@>@;
  8418. }
  8419. else
  8420. {
  8421. @<Add the first measurement to every model@>@;
  8422. }
  8423. lastMeasurement.insert(tempcolumn, measure);
  8424. }
  8425. @ The first measurement in a series should be the epoch measurement. This
  8426. should exist in every level of detail.
  8427. @<Add the first measurement to every model@>=
  8428. MeasurementModel *m;
  8429. foreach(m, modelSet)
  8430. {
  8431. m->newMeasurement(measure, tempcolumn);
  8432. }
  8433. @ Some measurement hardware in use cannot guarantee delivery of at least one
  8434. measurement per second. This causes problems for the current |ZoomLog|
  8435. implementation as, for example, if there is no measurement at a time where
  8436. the seconds are divisible by 5, there will be no entry in that view. This can
  8437. result in situations where the |ZoomLog| at its default view of one measurement
  8438. every 30 seconds might not display any data at all aside from the first
  8439. measurement, the last measurement, and any measurement that happens to have an
  8440. annotation associated with it. The solution in this case is to synthesize
  8441. measurements so that the |ZoomLog| thinks it gets at least one measurement
  8442. every second.
  8443. Prior to version 1.8 this simply replicated the last measurement every second
  8444. until the time for the most recent measurement was reached, however this yields
  8445. problematic results when loading saved data or attempting to use this view for
  8446. manual data entry. The current behavior performs a linear interpolation which
  8447. will match the graph.
  8448. @<Synthesize measurements for slow hardware@>=
  8449. if(lastMeasurement.contains(tempcolumn))
  8450. {
  8451. if(lastMeasurement[tempcolumn].time() < measure.time())
  8452. {
  8453. QList<QTime> timelist;
  8454. QList<double> templist;
  8455. QTime z = QTime(0, 0, 0, 0);
  8456. double ptime = (double)(z.secsTo(lastMeasurement[tempcolumn].time()));
  8457. double ptemp = lastMeasurement[tempcolumn].temperature();
  8458. double ctime = (double)(z.secsTo(measure.time()));
  8459. double ctemp = measure.temperature();
  8460. for(QTime i = lastMeasurement.value(tempcolumn).time().addSecs(1); i < measure.time(); i = i.addSecs(1))
  8461. {
  8462. timelist.append(i);
  8463. double v = ((ptemp * (ctime - z.secsTo(i))) + (ctemp * (z.secsTo(i) - ptime))) / (ctime - ptime);
  8464. templist.append(v);
  8465. }
  8466. for(int i = 0; i < timelist.size(); i++)
  8467. {
  8468. Measurement synthesized = measure;
  8469. synthesized.setTime(timelist[i]);
  8470. synthesized.setTemperature(templist[i]);
  8471. newMeasurement(synthesized, tempcolumn);
  8472. }
  8473. }
  8474. }
  8475. @ New to \pn{} 1.4 is the concept of a current column set. This was added to
  8476. improve support for devices where measurements on different data series may not
  8477. arrive at exactly the same time and for multi-device configurations where
  8478. measurements from different devices are unlikely to arrive at the same time.
  8479. This can cause issues with log annotations and serialization. The solution is
  8480. to group all columns that are logically part of the same data acquisition
  8481. process and as measurements come in, the most recent measurement from other
  8482. columns can be duplicated at the new time. Two methods are responsible for
  8483. managing this measurement set. One adds a column to the set and the other
  8484. removes all columns from the set.
  8485. @<ZoomLog Implementation@>=
  8486. void ZoomLog::addToCurrentColumnSet(int column)
  8487. {
  8488. currentColumnSet.append(column);
  8489. }
  8490. void ZoomLog::clearCurrentColumnSet()
  8491. {
  8492. currentColumnSet.clear();
  8493. }
  8494. @ Replicating the measurements occurs as measurements are delivered. Note
  8495. that this code will not be called for the first measurement in each column.
  8496. @<Synthesize measurements for columns in set@>=
  8497. if(currentColumnSet.contains(tempcolumn))
  8498. {
  8499. int replicationcolumn;
  8500. foreach(replicationcolumn, currentColumnSet)
  8501. {
  8502. if(replicationcolumn != tempcolumn)
  8503. {
  8504. if(lastMeasurement.contains(replicationcolumn))
  8505. {
  8506. if(measure.time() > lastMeasurement.value(replicationcolumn).time())
  8507. {
  8508. Measurement synthetic = lastMeasurement.value(replicationcolumn);
  8509. synthetic.setTime(measure.time());
  8510. model_ms->newMeasurement(synthetic, replicationcolumn);
  8511. if(synthetic.time().second() != lastMeasurement.value(replicationcolumn).time().second())@/
  8512. {
  8513. Measurement adjusted = synthetic;
  8514. adjusted.setTime(QTime(0, synthetic.time().minute(), synthetic.time().second(), 0));
  8515. model_1s->newMeasurement(adjusted, replicationcolumn);
  8516. if(adjusted.time().second() % 5 == 0)
  8517. {
  8518. model_5s->newMeasurement(adjusted, replicationcolumn);
  8519. if(adjusted.time().second() % 10 == 0)
  8520. {
  8521. model_10s->newMeasurement(adjusted, replicationcolumn);
  8522. }
  8523. if(adjusted.time().second() % 15 == 0)
  8524. {
  8525. model_15s->newMeasurement(adjusted, replicationcolumn);
  8526. if(adjusted.time().second() % 30 == 0)
  8527. {
  8528. model_30s->newMeasurement(adjusted, replicationcolumn);
  8529. if(adjusted.time().second() == 0)
  8530. {
  8531. model_1m->newMeasurement(adjusted, replicationcolumn);
  8532. }
  8533. }
  8534. }
  8535. }
  8536. }
  8537. lastMeasurement[replicationcolumn] = synthetic;
  8538. }
  8539. }
  8540. }
  8541. }
  8542. }
  8543. @ Just as the first measurement should exist at every level of detail, so should
  8544. any annotations. The measurement models will, when presented with an annotation,
  8545. apply it to the most recently entered measurement in the specified data series.
  8546. This presents a problem for the coarser views as the data point the annotation
  8547. belongs to most likely does not exist in that view. Furthermore, the model as it
  8548. is currently written will overwrite annotations that already exist on a
  8549. measurement if it is still the most recently entered. When collecting samples
  8550. during profile development, it is common to produce several annotations in a
  8551. short amount of time. The most useful thing to do in such a case is to add the
  8552. most recent measurement to each model and then apply the annotation. This, of
  8553. course, should only be done if there is a most recent measurement. An annotation
  8554. regarding the starting condition of the roaster should apply to the yet to be
  8555. recorded time zero measurement.
  8556. Note that only the value from the temperature column specified is displayed in
  8557. the row with the annotation. It would be better to check the full detail model
  8558. to determine if there are other measurements at the annotation time and present
  8559. these as well. Another possibility in the case of data not existing in other
  8560. temperature columns would be to interpolate a value from the existing data in
  8561. these columns, however this is potentially challenging as I would want to keep
  8562. true measurements distinct from estimations.
  8563. @<ZoomLog Implementation@>=
  8564. void ZoomLog::newAnnotation(QString annotation, int tempcolumn,
  8565. int annotationcolumn)
  8566. {
  8567. model_ms->newAnnotation(annotation, tempcolumn, annotationcolumn);
  8568. MeasurementModel *m;
  8569. if(lastMeasurement.contains(tempcolumn))
  8570. {
  8571. foreach(m, modelSet)
  8572. {
  8573. m->newMeasurement(lastMeasurement.value(tempcolumn), tempcolumn);
  8574. }
  8575. }
  8576. foreach(m, modelSet)
  8577. {
  8578. m->newAnnotation(annotation, tempcolumn, annotationcolumn);
  8579. }
  8580. }
  8581. @ As measurements are added to the model, the model will emit rowChanged
  8582. signals. These signals are connected to a function here that will attempt to
  8583. scroll the view to keep the most recently entered data in the center of the
  8584. view.
  8585. @<ZoomLog Implementation@>=
  8586. void ZoomLog::centerOn(int row)
  8587. {
  8588. scrollTo(currentModel->index(row, 0), QAbstractItemView::PositionAtCenter);
  8589. }
  8590. @ Once we are done with the data in the table, we want to clear it to prepare
  8591. for new data. This also clears the lists holding the output columns to use when
  8592. saving data.
  8593. @<ZoomLog Implementation@>=
  8594. void ZoomLog::clear()
  8595. {
  8596. MeasurementModel *m;
  8597. foreach(m, modelSet)
  8598. {
  8599. m->clear();
  8600. }
  8601. lastMeasurement.clear();
  8602. saveTempCols.clear();
  8603. saveControlCols.clear();
  8604. saveNoteCols.clear();
  8605. }
  8606. @ These are depreciated methods originally written to assist in serializing
  8607. model data prior to the introduction of the |XMLOutput| class. These methods are
  8608. likely to be removed in a future version of the program.
  8609. @<ZoomLog Implementation@>=
  8610. QVariant ZoomLog::data(int row, int column) const
  8611. {
  8612. return model_ms->data(model_ms->index(row, column, QModelIndex()),
  8613. Qt::DisplayRole);
  8614. }
  8615. int ZoomLog::rowCount()
  8616. {
  8617. return model_ms->rowCount();
  8618. }
  8619. @ This method initializes an |XMLOutput| instance, passes the columns that we
  8620. would like to save to that object, and uses it to write an XML file with the
  8621. desired data to the specified device.
  8622. Since the output format does not currently specify a unit, there is an
  8623. assumption that the XML output will always have measurements in Fahrenheit. If
  8624. the model is not currently displaying measurements in Fahrenheit, it is asked to
  8625. do so before writing the XML data. User preference is restored after the XML
  8626. data has been written. Since this change is only performed on |model_ms|, most
  8627. users will never notice this.
  8628. @<ZoomLog Implementation@>=
  8629. bool ZoomLog::saveXML(QIODevice *device)
  8630. {
  8631. Units::Unit prevUnits = model_ms->displayUnits();
  8632. if(prevUnits != Units::Fahrenheit)
  8633. {
  8634. model_ms->setDisplayUnits(Units::Fahrenheit);
  8635. }
  8636. XMLOutput writer(model_ms, device, 0);
  8637. int c;
  8638. foreach(c, saveTempCols)
  8639. {
  8640. writer.addTemperatureColumn(model_ms->headerData(c, Qt::Horizontal).
  8641. toString(), c);
  8642. }
  8643. foreach(c, saveControlCols)
  8644. {
  8645. writer.addControlColumn(model_ms->headerData(c, Qt::Horizontal).
  8646. toString(), c);
  8647. }
  8648. foreach(c, saveNoteCols)
  8649. {
  8650. writer.addAnnotationColumn(model_ms->headerData(c, Qt::Horizontal).
  8651. toString(), c);
  8652. }
  8653. bool retval = writer.output();
  8654. if(prevUnits != Units::Fahrenheit)
  8655. {
  8656. model_ms->setDisplayUnits(prevUnits);
  8657. }
  8658. return retval;
  8659. }
  8660. @ This method is similar to |saveXML()|. The main difference is that CSV data is
  8661. exported instead of XML.
  8662. @<ZoomLog Implementation@>=
  8663. bool ZoomLog::saveCSV(QIODevice *device)
  8664. {
  8665. CSVOutput writer(currentModel, device, 0);
  8666. int c;
  8667. foreach(c, saveTempCols)
  8668. {
  8669. writer.addTemperatureColumn(model_ms->headerData(c, Qt::Horizontal).
  8670. toString(), c);
  8671. }
  8672. foreach(c, saveControlCols)
  8673. {
  8674. writer.addControlColumn(model_ms->headerData(c, Qt::Horizontal).
  8675. toString(), c);
  8676. }
  8677. foreach(c, saveNoteCols)
  8678. {
  8679. writer.addAnnotationColumn(model_ms->headerData(c, Qt::Horizontal).
  8680. toString(), c);
  8681. }
  8682. return writer.output();
  8683. }
  8684. @ Several little functions, all alike\nfnote{If you get the reference, you may
  8685. enjoy reading another \cweb{} program:\par\indent\pdfURL{%
  8686. http://www-cs-staff.stanford.edu/$\sim$uno/programs/advent.w.gz}
  8687. {http://www-cs-staff.stanford.edu/~uno/programs/advent.w.gz}}, are used to
  8688. switch the view from one level of detail to another.
  8689. @<ZoomLog Implementation@>=
  8690. void ZoomLog::switchLOD(MeasurementModel *m)
  8691. {
  8692. disconnect(currentModel, SIGNAL(rowChanged(int)), this, 0);
  8693. setModel(m);
  8694. currentModel = m;
  8695. connect(currentModel, SIGNAL(rowChanged(int)), this, SLOT(centerOn(int)));
  8696. }
  8697. void ZoomLog::LOD_ms()
  8698. {
  8699. switchLOD(model_ms);
  8700. }
  8701. void ZoomLog::LOD_1s()
  8702. {
  8703. switchLOD(model_1s);
  8704. }
  8705. void ZoomLog::LOD_5s()
  8706. {
  8707. switchLOD(model_5s);
  8708. }
  8709. void ZoomLog::LOD_10s()
  8710. {
  8711. switchLOD(model_10s);
  8712. }
  8713. void ZoomLog::LOD_15s()
  8714. {
  8715. switchLOD(model_15s);
  8716. }
  8717. void ZoomLog::LOD_30s()
  8718. {
  8719. switchLOD(model_30s);
  8720. }
  8721. void ZoomLog::LOD_1m()
  8722. {
  8723. switchLOD(model_1m);
  8724. }
  8725. @ It can be useful to display temperature measurements in various units. To do
  8726. so, we simply tell all of the models which unit to provide data in. It is also
  8727. possible to obtain the currently selected unit.
  8728. @<ZoomLog Implementation@>=
  8729. void ZoomLog::setDisplayUnits(Units::Unit scale)
  8730. {
  8731. model_ms->setDisplayUnits(scale);
  8732. model_1s->setDisplayUnits(scale);
  8733. model_5s->setDisplayUnits(scale);
  8734. model_10s->setDisplayUnits(scale);
  8735. model_15s->setDisplayUnits(scale);
  8736. model_30s->setDisplayUnits(scale);
  8737. model_1m->setDisplayUnits(scale);
  8738. }
  8739. Units::Unit ZoomLog::displayUnits()
  8740. {
  8741. return model_ms->displayUnits();
  8742. }
  8743. @ For convenience, a method is provided for returning a string containing the
  8744. time of the last inserted measurement in a given data series.
  8745. @<ZoomLog Implementation@>=
  8746. QString ZoomLog::lastTime(int series)
  8747. {
  8748. Measurement measure = lastMeasurement.value(series);
  8749. QTime time = measure.time();
  8750. return time.toString("h:mm:ss.zzz");
  8751. }
  8752. @ This just leaves the initial table setup.
  8753. @<ZoomLog Implementation@>=
  8754. ZoomLog::ZoomLog() : QTableView(NULL), model_ms(new MeasurementModel(this)),
  8755. model_1s(new MeasurementModel(this)),@/ model_5s(new MeasurementModel(this)),
  8756. model_10s(new MeasurementModel(this)),@/ model_15s(new MeasurementModel(this)),
  8757. model_30s(new MeasurementModel(this)),@/ model_1m(new MeasurementModel(this))@/
  8758. {@/
  8759. setEditTriggers(QAbstractItemView::NoEditTriggers);
  8760. setSelectionMode(QAbstractItemView::NoSelection);
  8761. modelSet << model_ms << model_1s << model_5s << model_10s << model_15s <<
  8762. model_30s << model_1m;
  8763. currentModel = model_30s;
  8764. setModel(currentModel);
  8765. connect(currentModel, SIGNAL(rowChanged(int)), this, SLOT(centerOn(int)));
  8766. connect(horizontalHeader(), SIGNAL(sectionResized(int, int, int)),
  8767. this, SLOT(persistColumnResize(int, int, int)));
  8768. connect(horizontalHeader(), SIGNAL(sectionCountChanged(int, int)),
  8769. this, SLOT(restoreColumnWidths()));
  8770. }
  8771. @ A new method was added to this class for version 1.0.7. This allows header
  8772. data to be set on the log and have it propagate to the model set. The longer
  8773. term plan involves removing the hard coding of some of the header data.
  8774. @<ZoomLog Implementation@>=
  8775. void ZoomLog::setHeaderData(int section, QString text)
  8776. {
  8777. MeasurementModel *m;
  8778. foreach(m, modelSet)
  8779. {
  8780. m->setHeaderData(section, Qt::Horizontal, QVariant(text));
  8781. }
  8782. }
  8783. @ As of version 1.2.3, these methods replace similar methods added for version
  8784. 1.0.8. The main difference is that it is now possible to save multiple data
  8785. series to the same output document.
  8786. Starting in version 1.6 it is possible to save control columns. These should
  8787. contain unitless data which should remain unaffected by the current displayed
  8788. unit.
  8789. @<ZoomLog Implementation@>=
  8790. void ZoomLog::addOutputTemperatureColumn(int column)
  8791. {
  8792. saveTempCols.append(column);
  8793. }
  8794. void ZoomLog::addOutputControlColumn(int column)
  8795. {
  8796. saveControlCols.append(column);
  8797. }
  8798. void ZoomLog::addOutputAnnotationColumn(int column)
  8799. {
  8800. saveNoteCols.append(column);
  8801. }
  8802. void ZoomLog::clearOutputColumns()
  8803. {
  8804. saveTempCols.clear();
  8805. saveControlCols.clear();
  8806. saveNoteCols.clear();
  8807. }
  8808. @ Starting in version 1.4 two methods have been introduced which are used to
  8809. save and restore column widths.
  8810. @<ZoomLog Implementation@>=
  8811. void ZoomLog::persistColumnResize(int column, int, int newsize)
  8812. {
  8813. @<Save updated column size@>@;
  8814. }
  8815. void ZoomLog::restoreColumnWidths()
  8816. {
  8817. @<Restore table column widths@>@;
  8818. }
  8819. void ZoomLog::setVisible(bool visibility)
  8820. {
  8821. QTableView::setVisible(visibility);
  8822. }
  8823. void ZoomLog::showEvent(QShowEvent *)
  8824. {
  8825. @<Restore table column widths@>@;
  8826. }
  8827. @ The |ZoomLog| class is one of the more complicated classes to expose to the
  8828. scripting engine. In addition to a script constructor, we also need functions
  8829. for saving and restoring the state of the display and functions for saving data
  8830. from the log in the supported formats.
  8831. @<Function prototypes for scripting@>=
  8832. void setZoomLogProperties(QScriptValue value, QScriptEngine *engine);
  8833. QScriptValue constructZoomLog(QScriptContext *context, QScriptEngine *engine);
  8834. QScriptValue ZoomLog_saveXML(QScriptContext *context, QScriptEngine *engine);
  8835. QScriptValue ZoomLog_saveCSV(QScriptContext *context, QScriptEngine *engine);
  8836. QScriptValue ZoomLog_saveState(QScriptContext *context, QScriptEngine *engine);
  8837. QScriptValue ZoomLog_restoreState(QScriptContext *context,
  8838. QScriptEngine *engine);
  8839. QScriptValue ZoomLog_lastTime(QScriptContext *context, QScriptEngine *engine);
  8840. QScriptValue ZoomLog_saveTemporary(QScriptContext *context,
  8841. QScriptEngine *engnie);
  8842. QScriptValue ZoomLog_setDisplayUnits(QScriptContext *context,
  8843. QScriptEngine *engine);
  8844. @ Of these, the global object only needs to know about the constructor.
  8845. @<Set up the scripting engine@>=
  8846. constructor = engine->newFunction(constructZoomLog);
  8847. value = engine->newQMetaObject(&ZoomLog::staticMetaObject, constructor);
  8848. engine->globalObject().setProperty("ZoomLog", value);
  8849. @ The script constructor sets properties on the newly created object to allow
  8850. the other functions to be called.
  8851. @<Functions for scripting@>=
  8852. QScriptValue constructZoomLog(QScriptContext *, QScriptEngine *engine)@/
  8853. {@/
  8854. QScriptValue object = engine->newQObject(new ZoomLog);
  8855. setZoomLogProperties(object, engine);
  8856. return object;@/
  8857. }
  8858. void setZoomLogProperties(QScriptValue value, QScriptEngine *engine)
  8859. {
  8860. setQTableViewProperties(value, engine);
  8861. value.setProperty("saveXML", engine->newFunction(ZoomLog_saveXML));
  8862. value.setProperty("saveCSV", engine->newFunction(ZoomLog_saveCSV));
  8863. value.setProperty("saveState", engine->newFunction(ZoomLog_saveState));
  8864. value.setProperty("restoreState",
  8865. engine->newFunction(ZoomLog_restoreState));
  8866. value.setProperty("lastTime", engine->newFunction(ZoomLog_lastTime));
  8867. value.setProperty("saveTemporary",
  8868. engine->newFunction(ZoomLog_saveTemporary));
  8869. value.setProperty("setDisplayUnits", engine->newFunction(ZoomLog_setDisplayUnits));
  8870. }
  8871. @ The functions for saving data are simple wrappers around the corresponding
  8872. calls in |ZoomLog|, except for a function added for saving data to a temporary
  8873. file. The last provides the name of the file saved for use in copying that data
  8874. to a database entry.
  8875. @<Functions for scripting@>=
  8876. QScriptValue ZoomLog_saveXML(QScriptContext *context, QScriptEngine *engine)
  8877. {
  8878. ZoomLog *self = getself<ZoomLog *>(context);
  8879. bool retval = self->saveXML(argument<QIODevice *>(0, context));
  8880. return QScriptValue(engine, retval);
  8881. }
  8882. QScriptValue ZoomLog_saveCSV(QScriptContext *context, QScriptEngine *engine)
  8883. {
  8884. ZoomLog *self = getself<ZoomLog *>(context);
  8885. bool retval = self->saveCSV(argument<QIODevice *>(0, context));
  8886. return QScriptValue(engine, retval);
  8887. }
  8888. QScriptValue ZoomLog_saveTemporary(QScriptContext *context,
  8889. QScriptEngine *engine)
  8890. {
  8891. ZoomLog *self = getself<ZoomLog *>(context);
  8892. QString filename = QDir::tempPath();
  8893. filename.append("/");
  8894. filename.append(QUuid::createUuid().toString());
  8895. filename.append(".xml");
  8896. QFile *file = new QFile(filename);
  8897. self->saveXML(file);
  8898. file->close();
  8899. delete file;
  8900. return QScriptValue(engine, filename);
  8901. }
  8902. @ The remaining functions are convenience functions for use with the scripting
  8903. engine. One will save the column widths to a |QSettings| object. Another will
  8904. restore the column widths from settings. Finally, there is a function for
  8905. obtaining a string representation of the most recent measurement from a data
  8906. series.
  8907. \danger There are a couple of problems with these functions. First, the body of
  8908. these functions would probably be better off as methods in the |ZoomLog| class
  8909. proper, either as slots or |Q_INVOKABLE| so the special scripting functions
  8910. could be eliminated. Second, rather than polluting the settings with separate
  8911. entries for each column, it would probably be better to store all of these
  8912. values in an array.\endanger
  8913. |ZoomLog_saveState()| was changed in version 1.2.3 to not save a new value for
  8914. the column width if that width is |0|. This was done mainly to ease debugging.
  8915. Similarly, |ZoomLog_restoreState()| picks a new default value when |0| is
  8916. encountered.
  8917. @<Functions for scripting@>=
  8918. QScriptValue ZoomLog_saveState(QScriptContext *context, QScriptEngine *)
  8919. {
  8920. ZoomLog *self = getself<@[ZoomLog *@]>(context);
  8921. QString key = argument<QString>(0, context);
  8922. int columns = argument<int>(1, context);
  8923. QSettings settings;
  8924. for(int i = 0; i < columns; i++)
  8925. {
  8926. if(self->columnWidth(i))
  8927. {
  8928. settings.beginGroup(key);
  8929. settings.setValue(QString("%1").arg(i), self->columnWidth(i));
  8930. settings.endGroup();
  8931. }
  8932. }
  8933. return QScriptValue();
  8934. }
  8935. QScriptValue ZoomLog_restoreState(QScriptContext *context, QScriptEngine *)
  8936. {
  8937. ZoomLog *self = getself<@[ZoomLog *@]>(context);
  8938. QString key = argument<QString>(0, context);
  8939. int columns = argument<int>(1, context);
  8940. QSettings settings;
  8941. for(int i = 0; i < columns; i++)
  8942. {
  8943. settings.beginGroup(key);
  8944. self->setColumnWidth(i,
  8945. settings.value(QString("%1").arg(i), 80).toInt());
  8946. if(settings.value(QString("%1").arg(i), 80).toInt() == 0)
  8947. {
  8948. self->setColumnWidth(i, 80);
  8949. }
  8950. settings.endGroup();
  8951. }
  8952. return QScriptValue();
  8953. }
  8954. QScriptValue ZoomLog_lastTime(QScriptContext *context, QScriptEngine *engine)
  8955. {
  8956. ZoomLog *self = getself<@[ZoomLog *@]>(context);
  8957. return QScriptValue(engine, self->lastTime(argument<int>(0, context)));
  8958. }
  8959. @ There seems to be a bad interaction when enumerated value types as used as
  8960. the argument to slot methods called through QtScript. Script code that attempts
  8961. to make use of the enumeration appears to get the value without any type
  8962. information. When attempting to use that value as an argument the meta-object
  8963. system cannot find an appropriate match and the script just hangs silently.
  8964. The solution is to wrap such methods in the script bindings and explicitly cast
  8965. the argument value to the enumerated type. This looks stupid but it works.
  8966. @<Functions for scripting@>=
  8967. QScriptValue ZoomLog_setDisplayUnits(QScriptContext *context, QScriptEngine *)
  8968. {
  8969. ZoomLog *self = getself<@[ZoomLog *@]>(context);
  8970. self->setDisplayUnits((Units::Unit)argument<int>(0, context));
  8971. return QScriptValue();
  8972. }
  8973. @* A model for roasting data.
  8974. \noindent Qt provides a tool called the model view architecture. This provides a
  8975. uniform interface allowing different types of model classes to work with
  8976. different types of view classes without either needing to know implementation
  8977. details of the other. \pn{} provides the |MeasurementModel| as a specialization
  8978. of |QAbstractItemModel| for use in this architecture.
  8979. @<Class declarations@>=
  8980. class MeasurementList;@/
  8981. class MeasurementModel : public QAbstractItemModel@/
  8982. {@t\1@>@/
  8983. Q_OBJECT@;
  8984. Units::Unit unit;
  8985. QList<MeasurementList *> *entries;
  8986. QStringList *hData;
  8987. int colcount;
  8988. QHash<int, int> *lastTemperature;
  8989. QList<MeasurementList *>::iterator@, lastInsertion;
  8990. QHash<int, bool> *controlColumns;
  8991. public:@/
  8992. MeasurementModel(QObject *parent = NULL);
  8993. ~MeasurementModel();
  8994. int rowCount(const QModelIndex &parent = QModelIndex()) const;
  8995. int columnCount(const QModelIndex &parent = QModelIndex()) const;
  8996. bool setHeaderData(int section, Qt::Orientation orientation,
  8997. const QVariant &value,@|int role = Qt::DisplayRole);
  8998. QVariant data(const QModelIndex &index, int role) const;
  8999. bool setData(const QModelIndex &index, const QVariant &value,
  9000. int role = Qt::EditRole);
  9001. Qt::ItemFlags flags(const QModelIndex &index) const;
  9002. QVariant headerData(int section, Qt::Orientation orientation,
  9003. int role = Qt::DisplayRole) const;
  9004. QModelIndex index(int row, int column,
  9005. const QModelIndex &parent = QModelIndex()) const;
  9006. QModelIndex parent(const QModelIndex &index) const;
  9007. Units::Unit displayUnits();@/
  9008. @t\4@>public slots@t\kern-3pt@>:@/
  9009. void newMeasurement(Measurement measure, int tempcolumn);
  9010. void newAnnotation(QString annotation, int tempcolumn,
  9011. int annotationColumn);
  9012. void clear();
  9013. void setDisplayUnits(Units::Unit scale);
  9014. signals:@/
  9015. void rowChanged(int);@t\2@>@/
  9016. }@t\kern-3pt@>;
  9017. @ The measurement model stores its data in a list of measurement lists. This
  9018. allows the model to store as many sets of data as needed. In order to keep
  9019. measurements in the model sorted by time, the measurement list adds two
  9020. comparison functions.
  9021. @<Class declarations@>=
  9022. class MeasurementList : public QVariantList@/
  9023. {@t\1@>@/
  9024. @t\4@>public:@/
  9025. bool operator<(const MeasurementList &other) const;
  9026. bool operator==(const MeasurementList &other) const;@t\2@>@/
  9027. }@t\kern-3pt@>;
  9028. @ The overload of |<| checks if the time in one list (always stored in the first
  9029. column) is less than the time stored in the second. The overload of |==| is used
  9030. in an optimization that allows us to skip the search procedure on model
  9031. insertion.
  9032. @<MeasurementModel Implementation@>=
  9033. bool MeasurementList::operator<(const MeasurementList &other) const
  9034. {
  9035. return this->first().toTime() < other.first().toTime();
  9036. }
  9037. bool MeasurementList::operator==(const MeasurementList &other) const
  9038. {
  9039. return this->first().toTime() == other.first().toTime();
  9040. }
  9041. @ The |MeasurementModel| class extends the |QAbstractItemModel| class to work
  9042. better with measurements and annotations that are passed around in \pn{}. Many
  9043. of the class methods are required because of that choice. For example, the
  9044. parent function which is never used directly:
  9045. @<MeasurementModel Implementation@>=
  9046. QModelIndex MeasurementModel::parent(const QModelIndex&) const
  9047. {
  9048. return QModelIndex();
  9049. }
  9050. @ Perhaps the most complicated part of this class is the code for dealing with a
  9051. new measurement. This is complicated by the requirement to insert measurements
  9052. while keeping the model sorted by time.
  9053. @<MeasurementModel Implementation@>=
  9054. void MeasurementModel::newMeasurement(Measurement measure, int tempcolumn)
  9055. {
  9056. if(measure.scale() == Units::Unitless)
  9057. {
  9058. controlColumns->insert(tempcolumn, true);
  9059. }
  9060. else
  9061. {
  9062. controlColumns->insert(tempcolumn, false);
  9063. }
  9064. MeasurementList *temp;
  9065. temp = new MeasurementList;
  9066. temp->append(QVariant(measure.time()));
  9067. @<Find the insertion point@>@;
  9068. MeasurementList *newEntry;
  9069. int insertion;
  9070. if(i != entries->end())
  9071. {
  9072. insertion = entries->indexOf(*i);
  9073. if((*i)->first().toTime() == measure.time())
  9074. {
  9075. @<Insert a new measurement at an existing time@>@;
  9076. }
  9077. else
  9078. {
  9079. @<Insert a new measurement somewhere else@>@;
  9080. }
  9081. }
  9082. else
  9083. {
  9084. @<Append a measurement@>@;
  9085. }
  9086. if(tempcolumn >= colcount)
  9087. {
  9088. colcount = tempcolumn + 1;
  9089. }
  9090. emit rowChanged(insertion);
  9091. delete temp;
  9092. }
  9093. @ To find the insertion point for new measurements we use a binary search of the
  9094. existing data. The code below is a direct adaptation of Program B\nfnote{%
  9095. \underbar{The Art of Computer Programming} Volume 3 Sorting and Searching 2nd
  9096. ed. (Knuth, 1997) Section 6.2.1: Searching an Ordered Table} modified to use
  9097. list iterators and control structures more familiar to \CPLUSPLUS/programmers
  9098. rather than {\mc MIX} machine codes. When the loop exits |i| is the insertion
  9099. point.
  9100. \medskip
  9101. \centerline{\includegraphics{search}}
  9102. \smallskip
  9103. \centerline{Figure \secno: Binary Search}
  9104. \medskip
  9105. @<Find the insertion point@>=
  9106. @<Scan from most recent insertion@>@;
  9107. if(quickscan == false)
  9108. {
  9109. i = entries->begin();
  9110. QList<MeasurementList *>::iterator@, u = entries->end();
  9111. QList<MeasurementList *>::iterator@, midpoint;
  9112. int n = u - i;
  9113. int rA;
  9114. while(n > 0)@/
  9115. {
  9116. rA = n>>1; /* |rA = |~$\bigl\lfloor{n\over2}\bigr\rfloor$ */
  9117. midpoint = i + rA;
  9118. if(**midpoint < *temp)@/
  9119. {
  9120. i = midpoint + 1;
  9121. n -= rA + 1;
  9122. }
  9123. else@/
  9124. {
  9125. n = rA;
  9126. }
  9127. }
  9128. }
  9129. @ The binary search, while correct, is not a particularly optimal choice for
  9130. this application. While the average running time for this is on the order of
  9131. $\ln N$ when each insertion point is equally likely, the reality of this
  9132. application is that insertions will likely be at the beginning of the list, at
  9133. the point of the most recent insertion, or a short distance from the most recent
  9134. insertion. By first considering the possibility that the measurement should be
  9135. inserted at or near the most recent measurement, shorter, more constant running
  9136. times as $N$ increases can be obtained.
  9137. To do this, when the number of measurements in the list is above a small number
  9138. which must be greater than 1, we check first if the insertion point is at the
  9139. last insertion (the |<| comparison fails and we do an |==| comparison before
  9140. giving up), then we check a small number of rows for either the end of the list,
  9141. in which case the insertion point is at the end, or for a point at which the |<|
  9142. comparison fails. If neither condition holds for a small number of comparisons
  9143. we resort to the binary search.
  9144. Performance measurements with this modification compared with previous versions
  9145. shows that this provides a huge performance boost.
  9146. @<Scan from most recent insertion@>=
  9147. @[QList<MeasurementList *>::iterator@, i@] = lastInsertion;@/
  9148. bool quickscan = false;@/
  9149. if(entries->size() > 5)@/
  9150. {@t\1@>@/
  9151. if(**i < *temp)@/
  9152. {@t\1@>@/
  9153. i += 1;@/
  9154. for(int j = 10; j > 0; j--)@/
  9155. {@t\1@>@/
  9156. if(i != entries->end())@/
  9157. {@t\1@>@/
  9158. if(**i < *temp)@/
  9159. {
  9160. i += 1;
  9161. }@/
  9162. else@/
  9163. {@t\1@>@/
  9164. quickscan = true;
  9165. break;@t\2@>@/
  9166. }@t\2@>@/
  9167. }@/
  9168. else@/
  9169. {@t\1@>@/
  9170. quickscan = true;
  9171. break;@t\2@>@/
  9172. }@t\2@>@/
  9173. }@t\2@>@/
  9174. }@/
  9175. else@/
  9176. {@t\1@>@/
  9177. if(**i == *temp)@/
  9178. {@t\1@>@/
  9179. quickscan = true;@t\2@>@/
  9180. }@t\2@>@/
  9181. }@t\2@>@/
  9182. }
  9183. @ If the chosen insertion point is at an existing time, we don'@q'@>t need to
  9184. worry about inserting rows. There may be a need to increase the size of the
  9185. measurement list to accept an entry in a new data series.
  9186. @<Insert a new measurement at an existing time@>=
  9187. if((*i)->size() < tempcolumn + 1)
  9188. {
  9189. for(int j = (*i)->size() - 1; j < tempcolumn + 1; j++)
  9190. {
  9191. (*i)->append(QVariant());
  9192. }
  9193. }
  9194. (*i)->replace(tempcolumn, measure);
  9195. lastInsertion = i;
  9196. emit dataChanged(createIndex(insertion, tempcolumn),
  9197. createIndex(insertion, tempcolumn));
  9198. lastTemperature->insert(tempcolumn, insertion);
  9199. @ If the measurement is not past the end of the existing data and the insertion
  9200. point has a different time, we need to use |beginInsertRows()| and
  9201. |endInsertRows()| to notify any attached view that a new row will be added.
  9202. @<Insert a new measurement somewhere else@>=
  9203. beginInsertRows(QModelIndex(), insertion, insertion);
  9204. newEntry = new MeasurementList;
  9205. newEntry->append(QVariant(measure.time()));
  9206. for(int j = 0; j < tempcolumn + 1; j++)
  9207. {
  9208. newEntry->append(QVariant());
  9209. }
  9210. newEntry->replace(tempcolumn, measure);
  9211. lastInsertion = entries->insert(i, newEntry);
  9212. endInsertRows();
  9213. lastTemperature->insert(tempcolumn, insertion);
  9214. @ If the insertion point is past the end of the existing data, a new row should
  9215. be appended to the data. This only needs to be a separate case to prevent the
  9216. comparison with a nonexistent entry. This is very similar to the case of
  9217. inserting at a new time anywhere else.
  9218. @<Append a measurement@>=
  9219. insertion = entries->size();@/
  9220. @<Insert a new measurement somewhere else@>
  9221. @ The other bit of code that'@q'@>s a little bit more complicated than other
  9222. parts of the class handles adding annotations to the data. Two signals are
  9223. emitted in this method. The |dataChanged| signal is expected by view classes
  9224. that can use this model. The |rowChanged| signal is used by |ZoomLog| to scroll
  9225. the view to the row the annotation has been added to. This is mainly useful
  9226. when loading a target profile and entering the first annotation prior to
  9227. starting the batch.
  9228. @<MeasurementModel Implementation@>=
  9229. void MeasurementModel::newAnnotation(QString annotation, int tempcolumn,@|
  9230. int annotationColumn)
  9231. {
  9232. int r;
  9233. if(lastTemperature->contains(tempcolumn))
  9234. {
  9235. r = lastTemperature->value(tempcolumn);
  9236. }
  9237. else
  9238. {
  9239. r = 0;
  9240. }
  9241. if(r == 0 && entries->size() == 0)
  9242. {
  9243. @<Create the first row@>@;
  9244. }
  9245. MeasurementList *row = entries->at(r);
  9246. if(row->size() <= annotationColumn)
  9247. {
  9248. for(int i = row->size() - 1; i < annotationColumn + 1; i++)
  9249. {
  9250. row->append(QVariant());
  9251. }
  9252. }
  9253. row->replace(annotationColumn, annotation);
  9254. emit dataChanged(createIndex(r, annotationColumn),
  9255. createIndex(r, annotationColumn));
  9256. emit rowChanged(r);
  9257. if(annotationColumn > colcount - 1)
  9258. {
  9259. colcount = annotationColumn + 1;
  9260. }
  9261. }
  9262. @ There is no need to further complicate the function by adding the annotation
  9263. when the first row is created.
  9264. @<Create the first row@>=
  9265. beginInsertRows(QModelIndex(), 0, 0);
  9266. MeasurementList *newEntry = new MeasurementList;
  9267. newEntry->append(QVariant(QTime(0, 0, 0, 0)));
  9268. entries->append(newEntry);
  9269. endInsertRows();
  9270. @ Clearing the model data is a simple matter of deleting every row, remembering
  9271. to let any attached views know that we are doing this, and resetting the number
  9272. of columns.
  9273. @<MeasurementModel Implementation@>=
  9274. void MeasurementModel::clear()
  9275. {
  9276. beginRemoveRows(QModelIndex(), 0, entries->size());
  9277. while(entries->size() != 0)
  9278. {
  9279. MeasurementList *row = entries->takeFirst();
  9280. delete row;
  9281. }
  9282. endRemoveRows();
  9283. colcount = hData->size();
  9284. lastTemperature->clear();
  9285. reset();
  9286. }
  9287. @ While these methods for adding measurements and annotations are fine when
  9288. recording a stream of measurements, either from the |DAQ| or when loading saved
  9289. data, there are also cases where we'@q'@>d like to edit the data in the model
  9290. directly from the table view. For this, we need to reimplement |setData()|.
  9291. Note that editing from the |ZoomLog| has never been supported. While stream
  9292. inserted data currently preserves all properties of inserted measurements,
  9293. using |setData| it is possible to insert a numeric value as if it were a
  9294. measurement. Such an entry will not have any additional information associated
  9295. and cannot be expected to exhibit behavior implemented through the use of that
  9296. extra information.
  9297. Very little input checking is done here. Editable views may want to place
  9298. delegates\nfnote{Qt 4.4: Delegate Classes\par\indent\hbox{%
  9299. \pdfURL{http://doc.trolltech.com/4.4/model-view-delegate.html}{%
  9300. http://doc.trolltech.com/4.4/model-view-delegate.html}}} on the columns to make
  9301. editing the data easier and less error prone.
  9302. @<MeasurementModel Implementation@>=
  9303. bool MeasurementModel::setData(const QModelIndex &index,
  9304. const QVariant &value, int role)@t\2\2@>@/
  9305. {@t\1@>@/
  9306. if(role != Qt::EditRole && role != Qt::DisplayRole)@/
  9307. {@t\1@>@/
  9308. return false;@t\2@>@/
  9309. }@/
  9310. @<Check that the index is valid@>@;
  9311. if(!valid)@/
  9312. {@t\1@>@/
  9313. return false;@t\2@>@/
  9314. }@/
  9315. MeasurementList *row = entries->at(index.row());
  9316. if(index.column() >= row->size())
  9317. {
  9318. @<Expand the row to prepare for new data@>@;
  9319. }
  9320. if(index.column() == 0)
  9321. {
  9322. @<Edit data in the time column@>@;
  9323. }
  9324. else
  9325. {
  9326. @<Edit data in other columns@>@;
  9327. }
  9328. return true;@t\2@>@/
  9329. }
  9330. @ There is no sense in attempting to edit the data if there isn'@q'@>t any data
  9331. available to edit. This check is also used when retrieving data from the model.
  9332. @<Check that the index is valid@>=
  9333. bool valid = false;
  9334. if(index.isValid())@/
  9335. {@t\1@>@/
  9336. if(index.row() < entries->size())@/
  9337. {@t\1@>@/
  9338. if(index.column() < colcount)@/
  9339. {@t\1@>@/
  9340. valid = true;@t\2@>@/
  9341. }@t\2@>@/
  9342. }@t\2@>@/
  9343. }
  9344. @ When editing data, there might not be anything where we want to add the data.
  9345. For example, adding an annotation to an otherwise unannotated measurement. This
  9346. is fine, but we need to expand the row instead of inserting data out of bounds.
  9347. @<Expand the row to prepare for new data@>=
  9348. for(int i = row->size() - 1; i < index.column(); i++)
  9349. {
  9350. row->append(QVariant());
  9351. }
  9352. @ Changing time data must be considered separately from other data. As the model
  9353. keeps itself sorted based on the time field, allowing the user to get the model
  9354. data out of order would result in poorly defined behavior later. Our approach is
  9355. to remove the row from the model temporarily then reuse the code from
  9356. |newMeasurement()| to find the new insertion point. No attempt is made to merge
  9357. the contents from two rows with identical times, but an attempt is made to not
  9358. be too rigid in what we expect the user to enter. If an invalid time is entered,
  9359. we give up and leave the data as we found it.
  9360. @<Edit data in the time column@>=
  9361. QTime time;@/
  9362. if(!(time = QTime::fromString(value.toString(), "m:s.z")).isValid())@/
  9363. {@t\1@>@/
  9364. if(!(time = QTime::fromString(value.toString(), "m:s")).isValid())@/
  9365. {@t\1@>@/
  9366. return false;@t\2@>@/
  9367. }@t\2@>@/
  9368. }@/
  9369. row = entries->takeAt(index.row());
  9370. row->replace(index.column(), QVariant(time));
  9371. MeasurementList *temp = row;
  9372. @<Find the insertion point@>@;
  9373. entries->insert(i, row);
  9374. int newRow = entries->indexOf(*i);
  9375. if(newRow < index.row())@/
  9376. {
  9377. emit dataChanged(createIndex(newRow, index.column()), index);
  9378. }
  9379. else@/
  9380. {
  9381. emit dataChanged(index, createIndex(newRow, index.column()));
  9382. }
  9383. @ Data in other columns is a little easier to handle.
  9384. @<Edit data in other columns@>=
  9385. row->replace(index.column(), value);
  9386. emit dataChanged(index, index);
  9387. @ As it has already been established that the first column is always considered
  9388. the time of the measurement, this assumption can be built into the model
  9389. constructor.
  9390. @<MeasurementModel Implementation@>=
  9391. MeasurementModel::MeasurementModel(QObject *parent) : QAbstractItemModel(parent),
  9392. unit(Units::Fahrenheit), hData(new QStringList),
  9393. lastTemperature(new QHash<int, int>),
  9394. controlColumns(new QHash<int, bool>)@/
  9395. {
  9396. colcount = 1;
  9397. entries = new QList<MeasurementList *>;
  9398. lastInsertion = entries->begin();
  9399. hData->append(tr("Time"));
  9400. }
  9401. @ In the destructor we need to remember to clean up after ourselves.
  9402. @<MeasurementModel Implementation@>=
  9403. MeasurementModel::~MeasurementModel()
  9404. {
  9405. clear();
  9406. delete entries;
  9407. delete hData;
  9408. }
  9409. @ A pair of functions are used to determine the number of rows and columns the
  9410. model provides. No entries in the model have children, so the parent should
  9411. always be the invisible root object. If it isn'@q'@>t, we should return 0.
  9412. @<MeasurementModel Implementation@>=
  9413. int MeasurementModel::rowCount(const QModelIndex &parent) const
  9414. {
  9415. if(parent == QModelIndex())
  9416. {
  9417. return entries->size();
  9418. }
  9419. return 0;
  9420. }
  9421. int MeasurementModel::columnCount(const QModelIndex &parent) const
  9422. {
  9423. if(parent == QModelIndex())
  9424. {
  9425. return colcount;
  9426. }
  9427. return 0;
  9428. }
  9429. @ The model maintains a set of header data. At present, it only supports header
  9430. data at the top of the model due to the author'@q'@>s preference to not have row
  9431. numbers littering the left of the table (the time column is sufficient to
  9432. identify the row for the user).
  9433. The model view architecture supports the concept of different data roles in the
  9434. header data. At present, this model ignores the role when setting header data.
  9435. @<MeasurementModel Implementation@>=
  9436. bool MeasurementModel::setHeaderData(int section, Qt::Orientation orientation,@|
  9437. const QVariant &value, int)@t\2@>@/
  9438. @t\4@>{@/
  9439. if(orientation == Qt::Horizontal)@/
  9440. {@t\1@>@/
  9441. if(hData->size() < section + 1)@/
  9442. {@/
  9443. for(int i = hData->size(); i < section + 1; i++)@/
  9444. {@/
  9445. if(colcount < i)@/
  9446. {@/
  9447. beginInsertColumns(QModelIndex(), i, i);
  9448. }
  9449. hData->append(QString());
  9450. if(colcount < i)@/
  9451. {@/
  9452. endInsertColumns();
  9453. }
  9454. }
  9455. }
  9456. hData->replace(section, value.toString());
  9457. emit headerDataChanged(orientation, section, section);
  9458. if(colcount < section + 1)@/
  9459. {@/
  9460. colcount = section + 1;
  9461. }@/
  9462. return true;@t\2@>@/
  9463. }@/
  9464. return false;@/
  9465. @t\4@>}
  9466. @ While the current implementation always receives measurements in degrees
  9467. Fahrenheit, international users often want to see data presented in Celsius. To
  9468. do this, a slot is provided to allow selecting among different units. When this
  9469. method is called, the model indicates that all attached views must update all
  9470. displayed data and requests for temperature data will have any needed conversion
  9471. performed before sending that information to the view. Another method is
  9472. available to request a number identifyin the currently displayed units.
  9473. @<MeasurementModel Implementation@>=
  9474. void MeasurementModel::setDisplayUnits(Units::Unit scale)
  9475. {
  9476. beginResetModel();
  9477. unit = scale;
  9478. endResetModel();
  9479. }
  9480. Units::Unit MeasurementModel::displayUnits()
  9481. {
  9482. return unit;
  9483. }
  9484. @ A model is generally quite useless if the data the model contains cannot be
  9485. retrieved. To do this, we check that the index requested is a valid index that
  9486. is within the bounds of the model data and that a role we understand has been
  9487. requested. If none of these conditions are met, a default constructed |QVariant|
  9488. is returned.
  9489. At present, |Qt::DisplayRole| and |Qt::EditRole| are supported. These return the
  9490. same thing. Views will request the display role for presenting the information
  9491. to the user, but they will request the edit role if the user attempts to modify
  9492. the data through a view.
  9493. As of version 1.6, |Qt::UserRole| allows retrieval of raw measurement data.
  9494. @<MeasurementModel Implementation@>=
  9495. QVariant MeasurementModel::data(const QModelIndex &index, int role) const@/
  9496. {@/
  9497. @<Check that the index is valid@>@;
  9498. if(!valid)
  9499. {
  9500. return QVariant();
  9501. }
  9502. MeasurementList *row = entries->at(index.row());
  9503. if(role == Qt::UserRole)
  9504. {
  9505. return QVariant(row->at(index.column()));
  9506. }
  9507. if(role == Qt::DisplayRole || role == Qt::EditRole)
  9508. {
  9509. if(index.column() > row->size())
  9510. {
  9511. return QVariant();
  9512. }
  9513. else
  9514. {
  9515. if(index.column() == 0)
  9516. {
  9517. return QVariant(row->at(0).toTime().toString("mm:ss.zzz"));
  9518. }
  9519. else if(lastTemperature->contains(index.column()))
  9520. {
  9521. QVariantMap v = row->at(index.column()).toMap();
  9522. if(!v.contains("measurement"))
  9523. {
  9524. return QVariant();
  9525. }
  9526. if((Units::Unit)(v.value("unit").toInt()) == Units::Unitless)
  9527. {
  9528. return v.value("measurement");
  9529. }
  9530. else
  9531. {
  9532. if(v.contains("relative"))
  9533. {
  9534. if(v.value("relative").toBool())
  9535. {
  9536. return QVariant(QString("%1").@|arg(Units::convertRelativeTemperature(v.value("measurement").
  9537. toDouble(),@| (Units::Unit)(v.value("unit").toInt()), unit)));
  9538. }
  9539. }
  9540. return QVariant(QString("%1").@|
  9541. arg(Units::convertTemperature(v.value("measurement").toDouble(),@|
  9542. (Units::Unit)(v.value("unit").toInt()), unit)));
  9543. }
  9544. }
  9545. return QVariant(row->at(index.column()).toString());
  9546. }
  9547. }
  9548. return QVariant();@/
  9549. }
  9550. @ Views also must be able to retrieve the header data.
  9551. @<MeasurementModel Implementation@>=
  9552. QVariant MeasurementModel::headerData(int section, Qt::Orientation orientation,
  9553. int role ) const
  9554. {
  9555. if(orientation == Qt::Horizontal)
  9556. {
  9557. if(role == Qt::DisplayRole)
  9558. {
  9559. if(section < hData->size())
  9560. {
  9561. return QVariant(hData->at(section));
  9562. }
  9563. }
  9564. }
  9565. return QVariant();
  9566. }
  9567. @ Views will sometimes request information about the interactions available for
  9568. an index. In the case of this model, each index is treated in the same way.
  9569. It may be a good idea to extend the model class to allow models that can be
  9570. edited through the view such as the table view presented in the |LogEditWindow|
  9571. and models that probably shouldn'@q'@>t be edited in the view, such as the models
  9572. managed by |ZoomLog|. This could be done by subclassing and only reimplementing
  9573. this method. Otherwise, a new method to specify that the user should not edit
  9574. the model could be provided and a flag would be checked here.
  9575. @<MeasurementModel Implementation@>=
  9576. Qt::ItemFlags MeasurementModel::flags(const QModelIndex &index) const@/
  9577. {@/
  9578. @<Check that the index is valid@>@;
  9579. if(valid)
  9580. {
  9581. return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
  9582. }
  9583. return 0;
  9584. }
  9585. @ Much of the way models are interacted with in Qt'@q'@>s model view architecture is
  9586. through model indices. The model is responsible for creating these indices from
  9587. row column pairs.
  9588. @<MeasurementModel Implementation@>=
  9589. QModelIndex MeasurementModel::index(int row, int column,
  9590. const QModelIndex &parent) const@t\2\2@>@/
  9591. {@t\1@>@/
  9592. if(parent == QModelIndex())@/
  9593. {@t\1@>@/
  9594. if(row < entries->size() && entries->isEmpty() == false)@/
  9595. {@/
  9596. if(column < entries->at(row)->size())@/
  9597. {@/
  9598. return createIndex(row, column);@/
  9599. }@/
  9600. }@t\2@>@/
  9601. }@/
  9602. return QModelIndex();@/
  9603. @t\4@>}
  9604. @** Annotating roast data.
  9605. \noindent In addition to recording time temperature pairs, \pn{} allows the user
  9606. to annotate the roasting log to indicate control changes, the end of the batch,
  9607. or samples collected from the roast. It is important that these annotations can
  9608. be applied to the roasting log quickly. This is the purpose of the
  9609. |AnnotationButton| class.
  9610. @<Class declarations@>=
  9611. class AnnotationButton : public QPushButton@/
  9612. {@t\1@>@/
  9613. Q_OBJECT@;
  9614. QString noteTemplate;
  9615. QString note;
  9616. int tc;
  9617. int ac;
  9618. int count;
  9619. QString batch;
  9620. public:@/
  9621. AnnotationButton(const QString &text, QWidget *parent = NULL);@/
  9622. @t\4@>public slots@t\kern-3pt@>:@/
  9623. void setAnnotation(const QString &annotation);
  9624. void setTemperatureColumn(int tempcolumn);
  9625. void setAnnotationColumn(int annotationcolumn);
  9626. void annotate();
  9627. void resetCount();
  9628. void resetBatch();
  9629. void incrementBatch();
  9630. signals:@/
  9631. void annotation(QString annotation, int tempcolumn,
  9632. int notecolumn);@t\2@>@/
  9633. }@t\kern-3pt@>;
  9634. @ Setting up a new annotation button begins with the constructor. This takes a
  9635. string specifying the text that will appear on the button and optionally a
  9636. parent widget. This is also a sensible place to set up the desired behavior the
  9637. button should exhibit when clicked.
  9638. @<AnnotationButton Implementation@>=
  9639. AnnotationButton::AnnotationButton(const QString &text, QWidget *parent) :
  9640. QPushButton(text, parent), noteTemplate(""), note(""), tc(0), ac(0),
  9641. count(0), batch("A")@/
  9642. {
  9643. connect(this, SIGNAL(clicked()), this, SLOT(annotate()));
  9644. }
  9645. @ The slot that is called when the button is clicked needs to be able to handle
  9646. two types of annotations. Simple annotations send the same annotation every time
  9647. the button is clicked. Counting annotations are annotation strings that have a
  9648. |"%1"| somewhere in the string. That substring will be replaced with an integer
  9649. that is incremented before the annotation is sent. This integer is initialized
  9650. to 0. It will be incremented to 1 the first time the button is clicked and that
  9651. will be the replacement value.
  9652. @<AnnotationButton Implementation@>=
  9653. void AnnotationButton::annotate()
  9654. {
  9655. if(note.contains("%1"))
  9656. {
  9657. count++;
  9658. emit annotation(note.arg(count), tc, ac);
  9659. }
  9660. else
  9661. {
  9662. emit annotation(note, tc, ac);
  9663. }
  9664. }
  9665. @ A few methods are available to indicate which temperature series the
  9666. annotation should be applied to, which column in a table view the annotation
  9667. should be entered in, and what text should be in the annotation.
  9668. @<AnnotationButton Implementation@>=
  9669. void AnnotationButton::setTemperatureColumn(int tempcolumn)
  9670. {
  9671. tc = tempcolumn;
  9672. }
  9673. void AnnotationButton::setAnnotationColumn(int annotationcolumn)
  9674. {
  9675. ac = annotationcolumn;
  9676. }
  9677. void AnnotationButton::setAnnotation(const QString &annotation)
  9678. {
  9679. noteTemplate = annotation;
  9680. @<Replace batch holder in template@>@;
  9681. }
  9682. @ Note templates that contain "%A" should have that replaced with a
  9683. string that can be incremented between batches.
  9684. @<Replace batch holder in template@>=
  9685. int batchReplace = noteTemplate.indexOf("%A");
  9686. if(batchReplace >= 0)
  9687. {
  9688. QString temp = noteTemplate;
  9689. note = temp.replace(batchReplace, 2, batch);
  9690. }
  9691. else
  9692. {
  9693. note = noteTemplate;
  9694. }
  9695. @ In the case of counting annotations, there should be a way to reset
  9696. the number and batch used in the annotation.
  9697. @<AnnotationButton Implementation@>=
  9698. void AnnotationButton::resetCount()
  9699. {
  9700. count = 0;
  9701. }
  9702. void AnnotationButton::resetBatch()
  9703. {
  9704. batch = "A";
  9705. @<Replace batch holder in template@>@;
  9706. }
  9707. @ The batch sequence starts at A through Z, then proceeds to AA through AZ
  9708. and so on.
  9709. @<AnnotationButton Implementation@>=
  9710. void AnnotationButton::incrementBatch()
  9711. {
  9712. int position = batch.size() - 1;
  9713. increment:
  9714. if(batch[position] != 'Z')
  9715. {
  9716. batch[position] = batch[position].unicode() + 1;
  9717. }
  9718. else
  9719. {
  9720. batch[position] = 'A';
  9721. if(position > 0)
  9722. {
  9723. position--;
  9724. goto increment;
  9725. }
  9726. else
  9727. {
  9728. batch.append("A");
  9729. }
  9730. }
  9731. @<Replace batch holder in template@>@;
  9732. }
  9733. @ A script constructor is needed to allow an |AnnotationButton| to be created
  9734. from a script.
  9735. @<Function prototypes for scripting@>=
  9736. QScriptValue constructAnnotationButton(QScriptContext *context,
  9737. QScriptEngine *engine);
  9738. void setAnnotationButtonProperties(QScriptValue value, QScriptEngine *engine);
  9739. @ In order to use this, the engine needs to be informed of the function.
  9740. @<Set up the scripting engine@>=
  9741. constructor = engine->newFunction(constructAnnotationButton);
  9742. value = engine->newQMetaObject(&AnnotationButton::staticMetaObject,
  9743. constructor);
  9744. engine->globalObject().setProperty("AnnotationButton", value);
  9745. @ The implementation is trivial.
  9746. @<Functions for scripting@>=
  9747. QScriptValue constructAnnotationButton(QScriptContext *context,
  9748. QScriptEngine *engine)
  9749. {
  9750. QScriptValue object =
  9751. engine->newQObject(new AnnotationButton(argument<QString>(0, context)));
  9752. setAnnotationButtonProperties(object, engine);
  9753. return object;
  9754. }
  9755. void setAnnotationButtonProperties(QScriptValue value, QScriptEngine *engine)
  9756. {
  9757. setQPushButtonProperties(value, engine);
  9758. }
  9759. @* A spin box for annotations.
  9760. \noindent While the annotation button is adequate for most log annotation tasks,
  9761. there are some times where the log should contain a small number of numerical
  9762. observations where it is inconvenient or cost prohibitive to enable automated
  9763. logging. For these tasks, a spin box that produces an appropriate annotation may
  9764. be useful.
  9765. @<Class declarations@>=
  9766. class AnnotationSpinBox : public QDoubleSpinBox@/
  9767. {@t\1@>@/
  9768. Q_OBJECT@;
  9769. QString pretext;
  9770. QString posttext;
  9771. int tc;
  9772. int ac;
  9773. bool change;
  9774. public:
  9775. AnnotationSpinBox(const QString &pret, const QString &postt,
  9776. QWidget *parent = NULL);@/
  9777. @t\4@>public slots@t\kern-3pt@>:@/
  9778. void setPretext(const QString &pret);
  9779. void setPosttext(const QString &postt);
  9780. void setTemperatureColumn(int tempcolumn);
  9781. void setAnnotationColumn(int annotationcolumn);
  9782. void annotate();
  9783. void resetChange();
  9784. signals:@/
  9785. void annotation(QString annotation, int tempcolumn,
  9786. int notecolumn);@t\2@>@/
  9787. }@t\kern-3pt@>;
  9788. @ Setting up a new annotation spin box begins with the constructor. This takes
  9789. two strings specifying optional text that may appear before or after the
  9790. numerical value of the spin box in the annotation. No spaces are placed between
  9791. the text and the numerical values, so if such spacing is required, it must be
  9792. included in the relevant string.
  9793. This function also sets up the behavior for firing annotation events. An
  9794. annotation should be fired when the user presses enter while the spin box has
  9795. focus. This implementation will also attempt to fire an annotation when the
  9796. spin box loses focus. No annotation is fired if the value of the spin box has
  9797. not been changed since the previous annotation event.
  9798. @<AnnotationSpinBox Implementation@>=
  9799. AnnotationSpinBox::AnnotationSpinBox(const QString &pret,
  9800. const QString &postt,@|
  9801. QWidget *parent)
  9802. : QDoubleSpinBox(parent), pretext(pret), posttext(postt)@/
  9803. {
  9804. resetChange();
  9805. connect(this, SIGNAL(editingFinished()), this, SLOT(annotate()));
  9806. connect(this, SIGNAL(valueChanged(double)), this, SLOT(resetChange()));
  9807. }
  9808. @ The |resetChange()| signal just sets a boolean which is checked prior to
  9809. sending an annotation. This is called automatically when the value of the spin
  9810. box is changed, but it should also be called when a batch is finished in case
  9811. the first required annotation is the same as the last required annotation from
  9812. the previous batch.
  9813. @<AnnotationSpinBox Implementation@>=
  9814. void AnnotationSpinBox::resetChange()@t\2\2@>@/
  9815. {@t\1@>@/
  9816. change = true;@t\2@>@/
  9817. }
  9818. @ The annotation slot is responsible for determining if an annotation should be
  9819. sent. The current implementation is to only attempt to send such a signal when
  9820. the |editingFinished()| signal is emitted, however this could also be connected
  9821. to other signals.
  9822. @<AnnotationSpinBox Implementation@>=
  9823. void AnnotationSpinBox::annotate()@t\2\2@>@/
  9824. {@t\1@>@/
  9825. if(change)@/
  9826. {@t\1@>@/
  9827. change = false;@/
  9828. emit annotation(QString("%1%2%3").arg(pretext).
  9829. arg(value()).arg(posttext), tc, ac);@t\2@>@/
  9830. }@t\2@>@/
  9831. }
  9832. @ These methods set various properties of the annotation.
  9833. @<AnnotationSpinBox Implementation@>=
  9834. void AnnotationSpinBox::setTemperatureColumn(int tempcolumn)
  9835. {
  9836. tc = tempcolumn;
  9837. }
  9838. void AnnotationSpinBox::setAnnotationColumn(int annotationcolumn)
  9839. {
  9840. ac = annotationcolumn;
  9841. }
  9842. void AnnotationSpinBox::setPretext(const QString &pret)
  9843. {
  9844. pretext = pret;
  9845. }
  9846. void AnnotationSpinBox::setPosttext(const QString &postt)
  9847. {
  9848. posttext = postt;
  9849. }
  9850. @ Two functions are needed to interface |AnnotationSpinBox| with the host
  9851. environment. Additional functions are required for setting up inheritance
  9852. properly.
  9853. @<Function prototypes for scripting@>=
  9854. QScriptValue constructAnnotationSpinBox(QScriptContext *context,
  9855. QScriptEngine *engine);
  9856. void setAnnotationSpinBoxProperties(QScriptValue value, QScriptEngine *engine);
  9857. void setQDoubleSpinBoxProperties(QScriptValue value, QScriptEngine *engine);
  9858. void setQAbstractSpinBoxProperties(QScriptValue value, QScriptEngine *engine);
  9859. @ The first of these is passed into the host environment.
  9860. @<Set up the scripting engine@>=
  9861. constructor = engine->newFunction(constructAnnotationSpinBox);
  9862. value = engine->newQMetaObject(&AnnotationSpinBox::staticMetaObject,
  9863. constructor);
  9864. engine->globalObject().setProperty("AnnotationSpinBox", value);
  9865. @ The script constructor creates a new object and passes it to a function that
  9866. is responsible for setting up properties in the inheritance chain.
  9867. @<Functions for scripting@>=
  9868. QScriptValue constructAnnotationSpinBox(QScriptContext *context,
  9869. QScriptEngine *engine)
  9870. {
  9871. QScriptValue object = engine->newQObject(new AnnotationSpinBox(
  9872. argument<QString>(0, context), argument<QString>(1, context)));
  9873. setAnnotationSpinBoxProperties(object, engine);
  9874. return object;
  9875. }
  9876. void setAnnotationSpinBoxProperties(QScriptValue value, QScriptEngine *engine)
  9877. {
  9878. setQDoubleSpinBoxProperties(value, engine);
  9879. }
  9880. void setQDoubleSpinBoxProperties(QScriptValue value, QScriptEngine *engine)
  9881. {
  9882. setQAbstractSpinBoxProperties(value, engine);
  9883. }
  9884. void setQAbstractSpinBoxProperties(QScriptValue value, QScriptEngine *engine)
  9885. {
  9886. setQWidgetProperties(value, engine);
  9887. }
  9888. @** A digital timer.
  9889. \noindent Before \pn{} was a data logger, it was a simple digital timer written
  9890. because there were no shops in Racine that could sell a simple dual digital
  9891. count up timer at a time when my first timer was malfunctioning. After
  9892. attempting to purchase a replacement device at several stores that have sold
  9893. such devices in the past, I decided to spend a couple hours writing my own
  9894. timer.
  9895. For historical reasons, the |TimerDisplay| class is considerably more functional
  9896. than \pn{} requires. Those needing only a digital timer can extract the code for
  9897. this class and use it in a timer application. This should work on any platform
  9898. supported by Qt.
  9899. @<Class declarations@>=
  9900. class TimerDisplay : public QLCDNumber@/
  9901. {@t\1@>@/
  9902. Q_OBJECT@/
  9903. @<TimerDisplay Properties@>@;
  9904. @t\4@>private slots@t\kern-3pt@>:@/
  9905. void updateTime();
  9906. void setCountUpMode();
  9907. void setCountDownMode();
  9908. void setClockMode();
  9909. public:@/
  9910. TimerDisplay(QWidget *parent = NULL);
  9911. ~TimerDisplay();
  9912. enum TimerMode
  9913. {
  9914. CountUp,
  9915. CountDown,
  9916. Clock
  9917. };
  9918. QString value();
  9919. QTime seconds();
  9920. TimerMode mode();
  9921. bool isRunning();
  9922. QTime resetValue();
  9923. QString displayFormat();
  9924. bool autoReset();@/
  9925. @t\4@>public slots@t\kern-3pt@>:@/
  9926. void setTimer(QTime value = QTime(0, 0, 0));
  9927. void setMode(TimerMode mode);
  9928. void startTimer();
  9929. void stopTimer();
  9930. void copyTimer();
  9931. void setResetValue(QTime value = QTime(0, 0, 0));
  9932. void reset();
  9933. void setDisplayFormat(QString format);
  9934. void setAutoReset(bool reset);
  9935. void updateDisplay();
  9936. signals:@/
  9937. void valueChanged(QTime);
  9938. void runStateChanged(bool);@/
  9939. private:@/
  9940. @<TimerDisplay Private Variables@>@;@t\2@>@/
  9941. }@t\kern-3pt@>;
  9942. @ Qt provides a property system based on its meta-object system. This allows for
  9943. a number of advanced features which \pn{} does not use. The properties available
  9944. for the TimerDisplay class exist for historical reasons, but there are some
  9945. plans for future development which may make use of them. The properties may also
  9946. be useful for someone using this class in another program.
  9947. @<TimerDisplay Properties@>=
  9948. Q_PROPERTY(QTime seconds READ seconds WRITE setTimer)@/
  9949. Q_PROPERTY(TimerMode mode READ mode WRITE setMode)@/
  9950. Q_PROPERTY(bool running READ isRunning)@/
  9951. Q_PROPERTY(QTime resetValue READ resetValue WRITE setResetValue)@/
  9952. Q_PROPERTY(QString displayFormat READ displayFormat WRITE setDisplayFormat)@/
  9953. Q_PROPERTY(bool autoReset READ autoReset WRITE setAutoReset)@/
  9954. Q_PROPERTY(QString value READ value)@/
  9955. @ A number of private variables are used to implement this class.
  9956. @<TimerDisplay Private Variables@>=
  9957. QTime s;
  9958. QTime r;
  9959. QTimer clock;
  9960. TimerDisplay::TimerMode m;
  9961. bool running;
  9962. bool ar;
  9963. QAction *startAction;
  9964. QAction *stopAction;
  9965. QAction *resetAction;
  9966. QString f;
  9967. QTime relative;
  9968. QTime base;
  9969. @ |TimerDisplay| is a specialization of |QLCDNumber| designed for time keeping
  9970. purposes. It sets up a timer that fires roughly every half second to see if it
  9971. needs to update itself. The constructor sets this up, but does not start the
  9972. timer. The class provides three actions which can be used to start, stop, or
  9973. reset the timer. These actions are also set up in the constructor.
  9974. By default, the timer will display its time in hours, minutes, and seconds. This
  9975. can be changed as is done with the batch timer (it is expected that nobody will
  9976. want to spend an hour or more to roast a batch of coffee). The display style is
  9977. also changed to a sensible default, but this can be changed with the usual
  9978. |QLCDNumber| methods.
  9979. @<TimerDisplay Implementation@>=
  9980. TimerDisplay::TimerDisplay(QWidget *parent) : QLCDNumber(8, parent),
  9981. s(QTime(0, 0, 0)), r(QTime(0, 0, 0)), clock(NULL),@/ m(TimerDisplay::CountUp),
  9982. running(false), ar(false), startAction(new QAction(tr("Start"), NULL)),@/
  9983. stopAction(new QAction(tr("Stop"), NULL)),
  9984. resetAction(new QAction(tr("Reset"), NULL)),@/ f(QString("hh:mm:ss")),
  9985. relative(QTime::currentTime()), base(QTime(0, 0, 0))@/
  9986. {
  9987. connect(startAction, SIGNAL(triggered(bool)), this, SLOT(startTimer()));
  9988. connect(stopAction, SIGNAL(triggered(bool)), this, SLOT(stopTimer()));
  9989. connect(resetAction, SIGNAL(triggered(bool)), this, SLOT(reset()));
  9990. clock.setInterval(500);
  9991. clock.setSingleShot(false);
  9992. connect(&clock, SIGNAL(timeout()), this, SLOT(updateTime()));
  9993. setSegmentStyle(Filled);
  9994. updateDisplay();
  9995. }
  9996. @ The complicated bits are all in the |updateTime()| method. The behavior of
  9997. this function depends on the current |TimerMode| of the display.
  9998. @<TimerDisplay Implementation@>=
  9999. void TimerDisplay::updateTime()
  10000. {
  10001. QTime time;
  10002. int cseconds = 0;
  10003. int oseconds = 0;
  10004. int r = 0;
  10005. QTime nt = QTime(0, 0, 0);
  10006. int n = 0;
  10007. int bseconds = 0;
  10008. switch(m)@/
  10009. {@t\1@>@/
  10010. case TimerDisplay::CountUp:@/
  10011. @<Check for Timer Increment@>;
  10012. break;
  10013. case TimerDisplay::CountDown:@/
  10014. @<Check for Timer Decrement@>;
  10015. break;
  10016. case TimerDisplay::Clock:@/
  10017. @<Check for Clock Change@>;
  10018. break;
  10019. default:@/
  10020. Q_ASSERT_X(false, "updateTime", "invalid timer mode");
  10021. break;@t\2@>@/
  10022. }
  10023. updateDisplay();
  10024. }
  10025. @ To have the timer count up, we calculate the value that the timer should
  10026. indicate and compare it to the time indicated. If there is a difference, we
  10027. update the time to the new value and send emit a signal.
  10028. @<Check for Timer Increment@>=
  10029. @<Load seconds since base time into r@>@;
  10030. nt = nt.addSecs(r);
  10031. if(nt != s)
  10032. {
  10033. s = nt;
  10034. emit valueChanged(s);
  10035. }
  10036. @ Here we want to calculate the number of seconds in the current time, the
  10037. number of seconds in a base time, and the difference between the two. The
  10038. value loaded into oseconds could probably be cached.
  10039. @<Load seconds since base time into r@>=
  10040. #define TIMETOINT(t) ((t.hour() * 60 * 60) + (t.minute() * 60) + (t.second()))
  10041. time = QTime::currentTime();
  10042. cseconds = TIMETOINT(time);
  10043. oseconds = TIMETOINT(relative);
  10044. r = cseconds - oseconds;
  10045. @ The logic for a count down timer is very similar to the logic for a count up
  10046. timer. A key difference is that we don'@q'@>t want to continue counting down if the
  10047. timer has already reached 0.
  10048. @<Check for Timer Decrement@>=
  10049. if(s > QTime(0, 0, 0))@/
  10050. {@/
  10051. @<Load seconds since base time into r@>@;
  10052. bseconds = TIMETOINT(base);
  10053. n = bseconds - r;
  10054. nt = nt.addSecs(n);
  10055. if(nt != s)
  10056. {
  10057. s = nt;
  10058. emit valueChanged(s);
  10059. }
  10060. } else {
  10061. stopTimer();
  10062. }
  10063. @ The clock mode is the simplest case as it just needs to find out if the time
  10064. has changed.
  10065. @<Check for Clock Change@>=
  10066. time = QTime::currentTime();
  10067. if(time != s)
  10068. {
  10069. s = time;
  10070. emit valueChanged(s);
  10071. }
  10072. @ When counting up or down, it is important to record the time at which the
  10073. timer starts. The clock that triggers time updates must also be started. The
  10074. timer also needs to reset its value if that behavior is desired.
  10075. @<TimerDisplay Implementation@>=
  10076. #define TIMESUBTRACT(t1, t2) (t1.addSecs(-(TIMETOINT(t2))).addSecs(-t2.msec()))
  10077. void TimerDisplay::startTimer()@t\2\2@>@/
  10078. {@t\1@>@/
  10079. if(!running)@/
  10080. {@t\1@>@/
  10081. relative = QTime::currentTime();
  10082. if(ar)@/
  10083. {
  10084. reset();
  10085. }
  10086. else
  10087. {
  10088. relative = TIMESUBTRACT(relative, s);
  10089. }
  10090. if(m == Clock)@/
  10091. {
  10092. updateTime();
  10093. }
  10094. base = s;
  10095. clock.start();@/
  10096. running = true;
  10097. emit runStateChanged(true);@t\2@>@/
  10098. }@t\2@>@/
  10099. }
  10100. @ Stopping the timer is a little simpler. Remember to stop the clock so we
  10101. aren'@q'@>t updating senselessly.
  10102. @<TimerDisplay Implementation@>=
  10103. void TimerDisplay::stopTimer()@t\2\2@>@/
  10104. {@t\1@>@/
  10105. if(running)@/
  10106. {@t\1@>@/
  10107. clock.stop();@/
  10108. running = false;
  10109. emit runStateChanged(false);@t\2@>@/
  10110. }@t\2@>@/
  10111. }
  10112. @ The clock is also stopped in the destructor.
  10113. @<TimerDisplay Implementation@>=
  10114. TimerDisplay::~TimerDisplay()
  10115. {
  10116. clock.stop();
  10117. }
  10118. @ The rest of the functions are trivial. There are functions for changing the
  10119. timer mode:
  10120. @<TimerDisplay Implementation@>=
  10121. void TimerDisplay::setCountUpMode()
  10122. {
  10123. m = TimerDisplay::CountUp;
  10124. }
  10125. void TimerDisplay::setCountDownMode()
  10126. {
  10127. m = TimerDisplay::CountDown;
  10128. }
  10129. void TimerDisplay::setClockMode()
  10130. {
  10131. m = TimerDisplay::Clock;
  10132. }
  10133. @ There are a few functions to obtain information about the state of the timer.
  10134. @<TimerDisplay Implementation@>=
  10135. QString TimerDisplay::value()
  10136. {
  10137. return s.toString(f);
  10138. }
  10139. QTime TimerDisplay::seconds()
  10140. {
  10141. return s;
  10142. }
  10143. TimerDisplay::TimerMode TimerDisplay::mode()
  10144. {
  10145. return m;
  10146. }
  10147. bool TimerDisplay::isRunning()
  10148. {
  10149. return running;
  10150. }
  10151. QTime TimerDisplay::resetValue()
  10152. {
  10153. return r;
  10154. }
  10155. QString TimerDisplay::displayFormat()
  10156. {
  10157. return f;
  10158. }
  10159. bool TimerDisplay::autoReset()
  10160. {
  10161. return ar;
  10162. }
  10163. @ There are also some functions for setting aspects of the timer state.
  10164. @<TimerDisplay Implementation@>=
  10165. void TimerDisplay::setTimer(QTime value)
  10166. {
  10167. if(value.isValid())
  10168. {
  10169. s = value;
  10170. updateDisplay();
  10171. emit valueChanged(value);
  10172. }
  10173. }
  10174. void TimerDisplay::setMode(TimerDisplay::TimerMode mode)
  10175. {
  10176. m = mode;
  10177. }
  10178. void TimerDisplay::setResetValue(QTime value)
  10179. {
  10180. r = value;
  10181. }
  10182. void TimerDisplay::setDisplayFormat(QString format)
  10183. {
  10184. f = format;
  10185. setNumDigits(format.length());
  10186. }
  10187. void TimerDisplay::setAutoReset(bool reset)
  10188. {
  10189. ar = reset;
  10190. }
  10191. @ |TimerDisplay| supports using the system clipboard to copy the current timer
  10192. value.
  10193. @<TimerDisplay Implementation@>=
  10194. void TimerDisplay::copyTimer()
  10195. {
  10196. QApplication::clipboard()->setText(value());
  10197. }
  10198. @ Resetting the timer is simple. We don'@q'@>t reset the timer if it is still running
  10199. mainly to prevent accidents.
  10200. @<TimerDisplay Implementation@>=
  10201. void TimerDisplay::reset()
  10202. {
  10203. if(!running)
  10204. {
  10205. s = r;
  10206. updateDisplay();
  10207. }
  10208. }
  10209. @ Finally, there is the function for changing the text of the display to the
  10210. current time value.
  10211. @<TimerDisplay Implementation@>=
  10212. void TimerDisplay::updateDisplay()
  10213. {
  10214. display(value());
  10215. }
  10216. @ Exposing |TimerDisplay| to the host environment is simple.
  10217. @<Function prototypes for scripting@>=
  10218. QScriptValue constructTimerDisplay(QScriptContext *context,
  10219. QScriptEngine *engine);
  10220. void setTimerDisplayProperties(QScriptValue value, QScriptEngine *engine);
  10221. QScriptValue TimerDisplay_setTimerMode(QScriptContext *context, QScriptEngine *engine);
  10222. @ The engine must be informed of the script constructor.
  10223. @<Set up the scripting engine@>=
  10224. constructor = engine->newFunction(constructTimerDisplay);
  10225. value = engine->newQMetaObject(&TimerDisplay::staticMetaObject, constructor);
  10226. engine->globalObject().setProperty("TimerDisplay", value);
  10227. @ The implementation of these functions is trivial.
  10228. @<Functions for scripting@>=
  10229. QScriptValue constructTimerDisplay(QScriptContext *, QScriptEngine *engine)
  10230. {
  10231. QScriptValue object = engine->newQObject(new TimerDisplay);
  10232. setTimerDisplayProperties(object, engine);
  10233. return object;
  10234. }
  10235. void setTimerDisplayProperties(QScriptValue value, QScriptEngine *engine)
  10236. {
  10237. setQLCDNumberProperties(value, engine);
  10238. value.setProperty("setTimerMode", engine->newFunction(TimerDisplay_setTimerMode));
  10239. }
  10240. @ A new feature in \pn{} 1.6.4 benefits from having the ability to set the
  10241. timer mode from a script. Rather than exposing the |enum| responsible for this
  10242. to the host environment, a new function is provided to allow integer based
  10243. setting.
  10244. @<Functions for scripting@>=
  10245. QScriptValue TimerDisplay_setTimerMode(QScriptContext *context, QScriptEngine *)
  10246. {
  10247. TimerDisplay *self = getself<TimerDisplay *>(context);
  10248. if(self)
  10249. {
  10250. switch(argument<int>(0, context))
  10251. {
  10252. case 0:
  10253. self->setMode(TimerDisplay::CountUp);
  10254. break;
  10255. case 1:
  10256. self->setMode(TimerDisplay::CountDown);
  10257. break;
  10258. case 2:
  10259. self->setMode(TimerDisplay::Clock);
  10260. break;
  10261. default:
  10262. break;
  10263. }
  10264. }
  10265. return QScriptValue();
  10266. }
  10267. @** The Human Computer Interface.
  10268. \noindent A few classes are required for putting the rest of the program
  10269. together in a way that it can be used by a human. There is a layout class for
  10270. arranging widgets in a way that is not simple with the layouts provided by Qt.
  10271. There are classes for labeling the various indicators. There are also window
  10272. classes that put all of this together in a useful and usable way. One of these
  10273. classes is currently depreciated.
  10274. @* The PackLayout Class.
  10275. \noindent The |PackLayout| class provides functionality similar to the
  10276. |QBoxLayout| class in Qt. It allows the construction of a row or column of
  10277. widgets. Each item will take up space along the orientation of the layout equal
  10278. to its size hint except for the last widget which will take up all remaining
  10279. space. Widgets will be resized in the direction perpendicular to the orientation
  10280. of the layout to use all available space.
  10281. This class was originally written with the |WidgetDecorator| class which we will
  10282. get to later in mind, but it has found use in other places where the left or top
  10283. most widgets should not be resized.
  10284. By default, a new |PackLayout| will arrange widgets horizontally. This can be
  10285. changed with a call to |setOrientation()|.
  10286. @<Class declarations@>=
  10287. class PackLayout : public QLayout@/
  10288. {@/
  10289. int doLayout(const QRect &rect, bool testOnly) const;@/
  10290. QList<QLayoutItem *> itemList;@/
  10291. Qt::Orientations@, orientation;@/
  10292. public:@/
  10293. PackLayout(QWidget *parent, int margin = 0, int spacing = -1);
  10294. PackLayout(int spacing = -1);
  10295. ~PackLayout();
  10296. void addItem(QLayoutItem *item);
  10297. Qt::Orientations@, expandingDirections() const;
  10298. bool hasHeightForWidth() const;
  10299. int heightForWidth(int width) const;
  10300. int count() const;
  10301. QLayoutItem *itemAt(int index) const;
  10302. QSize minimumSize() const;
  10303. void setGeometry(const QRect &rect);
  10304. void setOrientation(Qt::Orientations direction);
  10305. QSize sizeHint() const;
  10306. QLayoutItem *takeAt(int index);
  10307. };
  10308. @ The interesting portion of this class is in |doLayout()|. This function goes
  10309. over the items in the layout and sets the geometry appropriately.
  10310. The seemingly odd choice of returning |y| at the end of this function (indeed of
  10311. having a return value at all) is to allow this function to provide the return
  10312. value needed in |heightForWidth()|.
  10313. If |testOnly| is set to |true|, |y| will be calculated, but the widget geometry
  10314. will not be changed.
  10315. @<PackLayout Implementation@>=
  10316. int PackLayout::doLayout(const QRect &rect, bool testOnly) const
  10317. {
  10318. int x = rect.x();
  10319. int y = rect.y();
  10320. QLayoutItem *item;
  10321. if(orientation == Qt::Horizontal)
  10322. {
  10323. @<Lay the widgets out horizontally@>@;
  10324. }
  10325. else
  10326. {
  10327. @<Lay the widgets out vertically@>@;
  10328. }
  10329. return y;
  10330. }
  10331. @ To lay the widgets out horizontally, we go over each item in the list taking
  10332. the width of the size hint and spacing into account unless the item is the last
  10333. item in the list, in which case the right of the widget needs to be at the end
  10334. of the available space. We use the foreach construction that Qt provides to
  10335. iterate over each item in the list in much the same way as foreach constructions
  10336. are used in languages that support them directly.
  10337. @<Lay the widgets out horizontally@>=
  10338. foreach(item, itemList)
  10339. {
  10340. int nextX = x + item->sizeHint().width() + spacing();
  10341. int right = x + item->sizeHint().width();
  10342. if(item == itemList.last())
  10343. {
  10344. right = rect.right();
  10345. }
  10346. int bottom = rect.bottom();
  10347. if(!testOnly)
  10348. {
  10349. item->setGeometry(QRect(QPoint(x, y), QPoint(right, bottom)));
  10350. }
  10351. x = nextX;
  10352. }
  10353. @ Laying out the widgets vertically is very similar.
  10354. @<Lay the widgets out vertically@>=
  10355. foreach(item, itemList)
  10356. {
  10357. int nextY = y + item->sizeHint().height() + spacing();
  10358. int bottom = y + item->sizeHint().height();
  10359. if(item == itemList.last())
  10360. {
  10361. bottom = rect.bottom();
  10362. }
  10363. int right = rect.right();
  10364. if(!testOnly)
  10365. {
  10366. item->setGeometry(QRect(QPoint(x, y), QPoint(right, bottom)));
  10367. }
  10368. y = nextY;
  10369. }
  10370. @ As a layout class, there are a number of things the class should be able to do
  10371. in order to play nicely with other classes. One of these is determining the
  10372. minimum size of the layout. The minimum size of the layout is equal to the space
  10373. required for each item in the layout plus the margin space. The margin space
  10374. will be equal to twice the specified margin in each direction to account for a
  10375. top, bottom, left, and right margin.
  10376. @<PackLayout Implementation@>=
  10377. QSize PackLayout::minimumSize() const
  10378. {
  10379. QSize size;
  10380. QLayoutItem *item;
  10381. foreach(item, itemList)
  10382. {
  10383. if(orientation == Qt::Horizontal)
  10384. {
  10385. size += QSize(item->minimumSize().width(), 0);
  10386. if(size.height() < item->minimumSize().height())
  10387. {
  10388. size.setHeight(item->minimumSize().height());
  10389. }
  10390. }
  10391. else
  10392. {
  10393. size += QSize(0, item->minimumSize().height());
  10394. if(size.width() < item->minimumSize().width())
  10395. {
  10396. size.setWidth(item->minimumSize().width());
  10397. }
  10398. }
  10399. }
  10400. size += QSize(2*margin(), 2*margin());
  10401. return size;
  10402. }
  10403. @ |PackLayout| features two constructors. One allows for setting the margin,
  10404. spacing, and a parent widget at the time of construction. The other creates a
  10405. parentless layout which will have to be added to another widget or layout.
  10406. @<PackLayout Implementation@>=
  10407. PackLayout::PackLayout(QWidget *parent, int margin, int spacing) :
  10408. QLayout(parent)@/
  10409. {
  10410. setMargin(margin);
  10411. setSpacing(spacing);
  10412. setOrientation(Qt::Horizontal);
  10413. }
  10414. PackLayout::PackLayout(int spacing)
  10415. {
  10416. setSpacing(spacing);
  10417. setOrientation(Qt::Horizontal);
  10418. }
  10419. @ In Qt, items in a layout are owned by that layout. When the layout is
  10420. destroyed, all of the items in that layout must also be deleted.
  10421. @<PackLayout Implementation@>=
  10422. PackLayout::~PackLayout()
  10423. {
  10424. QLayoutItem *item;
  10425. while((item = takeAt(0)))
  10426. {
  10427. delete item;
  10428. }
  10429. }
  10430. @ Deleting the items uses the |takeAt()| method to remove each widget from the
  10431. layout prior to deleting it. The item requested should exist, but if it doesn'@q'@>t,
  10432. |NULL| is returned.
  10433. @<PackLayout Implementation@>=
  10434. QLayoutItem* PackLayout::takeAt(int index)
  10435. {
  10436. if(index >= 0 && index < itemList.size())
  10437. {
  10438. return itemList.takeAt(index);
  10439. }
  10440. else
  10441. {
  10442. return NULL;
  10443. }
  10444. }
  10445. @ If we are interested in which item is in a particular position in the layout
  10446. but do not want to remove it from the layout, |itemAt()| provides that.
  10447. @<PackLayout Implementation@>=
  10448. QLayoutItem* PackLayout::itemAt(int index) const
  10449. {
  10450. if(index >= 0 && index < itemList.size())
  10451. {
  10452. return itemList.at(index);
  10453. }
  10454. else
  10455. {
  10456. return NULL;
  10457. }
  10458. }
  10459. @ A layout class is not very useful unless there is a way to get items into the
  10460. layout. The |QLayoutItem| class is designed in such a way that it is possible to
  10461. pass pointers to objects that inherit |QLayout| or |QWidget|.
  10462. The base |QLayout| class provides an |addWidget()| method that will use our
  10463. version of |addItem()|. That should be used when adding a widget to the layout.
  10464. The Qt documentation recommends also providing an |addLayout()| method so that
  10465. other code does not need to call this method, but that has not been provided
  10466. yet.
  10467. @<PackLayout Implementation@>=
  10468. void PackLayout::addItem(QLayoutItem *item)
  10469. {
  10470. itemList.append(item);
  10471. }
  10472. @ It is sometimes useful to know how many items are in a layout.
  10473. @<PackLayout Implementation@>=
  10474. int PackLayout::count() const@;@/
  10475. {@/
  10476. return itemList.size();@/
  10477. }
  10478. @ A few more functions are needed to make the layout class work well with other
  10479. classes. For more details, please consult the Qt Reference
  10480. Documentation\nfnote{Qt Reference Documentation\par\indent\hbox{%
  10481. \pdfURL{http://doc.trolltech.com/4.3/index.html}%
  10482. {http://doc.trolltech.com/4.3/index.html}}}
  10483. @<PackLayout Implementation@>=
  10484. Qt::Orientations PackLayout::expandingDirections() const
  10485. {
  10486. return Qt::Vertical | Qt::Horizontal;
  10487. }
  10488. bool PackLayout::hasHeightForWidth() const@t\2\2@>@/
  10489. {@t\1@>@/
  10490. return false;@t\2@>@/
  10491. }@/
  10492. int PackLayout::heightForWidth(int width) const
  10493. {
  10494. return doLayout(QRect(0, 0, width, 0), true);
  10495. }
  10496. void PackLayout::setGeometry(const QRect &rect)
  10497. {
  10498. QLayout::setGeometry(rect);
  10499. doLayout(rect, false);
  10500. }
  10501. QSize PackLayout::sizeHint() const
  10502. {
  10503. return minimumSize();
  10504. }
  10505. @ It was mentioned previously that this layout is capable of lining widgets up
  10506. in a row or presenting them in a column. This is done with the
  10507. |setOrientation()| method.
  10508. @<PackLayout Implementation@>=
  10509. void PackLayout::setOrientation(Qt::Orientations direction)
  10510. {
  10511. orientation = direction;
  10512. doLayout(geometry(), false);
  10513. }
  10514. @* The SceneButton Class.
  10515. \noindent Ordinarily, mouse down events that are passed from a |QGraphicsView|
  10516. to an interactive |QGraphicsScene| will continue to pass that click down to an
  10517. item in the scene. This class is used when we are interested in a click anywhere
  10518. in the view and it doesn'@q'@>t really matter where in the scene that click occurred
  10519. or even if there is a graphics item at that point. Any click passed to the
  10520. |SceneButton| will cause the scene to emit a signal containing the screen
  10521. coordinates of the click.
  10522. This was originally designed for use in the |WidgetDecorator| class. While the
  10523. functionality provided is not currently used, the original plan was to use this
  10524. to provide access to configuration options.
  10525. It is possible that this class is no longer necessary even if features it was
  10526. made for are implemented.
  10527. @<Class declarations@>=
  10528. class SceneButton : public QGraphicsScene@/
  10529. {@/
  10530. Q_OBJECT@;
  10531. public:@/
  10532. SceneButton();
  10533. ~SceneButton();
  10534. protected:@/
  10535. void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent);
  10536. signals:@/
  10537. void clicked(QPoint pos);
  10538. };
  10539. @ The implementation is trivial.
  10540. @<SceneButton Implementation@>=
  10541. SceneButton::SceneButton() : QGraphicsScene()@/
  10542. {
  10543. /* Nothing has to be done here. */
  10544. }
  10545. SceneButton::~SceneButton()
  10546. {
  10547. /* Nothing has to be done here. */
  10548. }
  10549. void SceneButton::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
  10550. {
  10551. emit clicked(mouseEvent->buttonDownScreenPos(mouseEvent->button()));
  10552. }
  10553. @* The WidgetDecorator Class.
  10554. \noindent The |WidgetDecorator| class provides a way to label various widgets
  10555. while also providing additional options for interacting with them. The
  10556. decoration can exist to the left or atop the widget being decorated. When the
  10557. label is to the left of the widget, the label text is rotated.
  10558. This class is likely to change considerably in the future as features are added
  10559. that allow actions to be added to the decoration to allow various configuration
  10560. options.
  10561. @<Class declarations@>=
  10562. class WidgetDecorator : public QWidget@/
  10563. {
  10564. Q_OBJECT@;
  10565. PackLayout *layout;
  10566. QGraphicsView *label;
  10567. QGraphicsTextItem *text;
  10568. SceneButton *scene;
  10569. public:@/
  10570. WidgetDecorator(QWidget *widget, const QString &labeltext,@|
  10571. Qt::Orientations@, orientation = Qt::Horizontal,@|
  10572. QWidget *parent = NULL, Qt::WindowFlags f = 0);
  10573. ~WidgetDecorator();
  10574. void setBackgroundBrush(QBrush background);
  10575. void setTextColor(QColor color);
  10576. };
  10577. @ Almost everything this class currently does is handled in the constructor.
  10578. @<WidgetDecorator Implementation@>=
  10579. WidgetDecorator::WidgetDecorator(QWidget *widget, const QString &labeltext,
  10580. Qt::Orientations orientation,
  10581. QWidget *parent, Qt::WindowFlags f)@/:
  10582. QWidget(parent, f), label(new QGraphicsView()),
  10583. scene(new SceneButton())@t\2@>@/
  10584. {
  10585. layout = new PackLayout(this);
  10586. layout->setOrientation(orientation);
  10587. @<Prepare the graphics view@>@;
  10588. @<Add the label to the scene@>@;
  10589. @<Adjust the decoration width@>@;
  10590. @<Pack widgets into the layout@>@;
  10591. }
  10592. @ The decoration is a |QGraphicsView|. To get this to look right, we need to
  10593. make sure there aren'@q'@>t any scroll bars and there shouldn'@q'@>t be a frame
  10594. surrounding it. While we'@q'@>re at it, we allow it to accept clicks, though this
  10595. functionality is not yet used.
  10596. @<Prepare the graphics view@>=
  10597. label->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  10598. label->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  10599. label->setFrameShape(QFrame::NoFrame);
  10600. label->setInteractive(true);
  10601. @ The |QGraphicsView| needs a scene to display anything. The scene consists of a
  10602. background which, by default is solid cyan. This can be changed later by setting
  10603. a different background brush. Text also needs to be added to the scene. If the
  10604. decoration is to the left of the widget, the text needs to be rotated.
  10605. @<Add the label to the scene@>=
  10606. scene->setBackgroundBrush(Qt::cyan);
  10607. text = scene->addText(labeltext);
  10608. if(orientation == Qt::Horizontal)
  10609. {
  10610. text->rotate(270.0);
  10611. }
  10612. label->setScene(scene);
  10613. @ The decoration should have the text centered in the view. The widget should
  10614. also be no wider (or taller for horizontal orientation) than necessary for the
  10615. text.
  10616. The case for horizontal orientation here may seem a little strange, however the
  10617. dimensions of the bounding rectangle are not affected by rotation. This means
  10618. that even though we want the width of the rotated text, this is the same as the
  10619. height of the text.
  10620. @<Adjust the decoration width@>=
  10621. if(orientation == Qt::Horizontal)
  10622. {
  10623. label->setMaximumWidth((int)(text->boundingRect().height() + 1));
  10624. }
  10625. else
  10626. {
  10627. label->setMaximumHeight((int)(text->boundingRect().height() + 1));
  10628. }
  10629. label->centerOn(text);
  10630. @ Once the decoration is ready, the decoration and the widget being decorated
  10631. can be added to the layout. A minimum size for the compound widget is also
  10632. calculated.
  10633. @<Pack widgets into the layout@>=
  10634. layout->addWidget(label);
  10635. layout->addWidget(widget);
  10636. if(orientation == Qt::Horizontal)
  10637. {
  10638. setMinimumSize(widget->sizeHint().width() + label->sizeHint().width(),
  10639. widget->sizeHint().height());
  10640. }
  10641. else
  10642. {
  10643. setMinimumSize(widget->sizeHint().width(),
  10644. widget->sizeHint().height() + label->sizeHint().height());
  10645. }
  10646. @ As mentioned previously, it is possible to change the background pattern for
  10647. the decoration. It is also possible to change the color of the text.
  10648. @<WidgetDecorator Implementation@>=
  10649. void WidgetDecorator::setBackgroundBrush(QBrush background)
  10650. {
  10651. scene->setBackgroundBrush(background);
  10652. }
  10653. void WidgetDecorator::setTextColor(QColor color)
  10654. {
  10655. text->setDefaultTextColor(color);
  10656. }
  10657. @ Finally, there is a destructor.
  10658. @<WidgetDecorator Implementation@>=
  10659. WidgetDecorator::~WidgetDecorator()
  10660. {
  10661. /* Nothing has to be done here. */
  10662. }
  10663. @ In order to create a decorated widget from a script, we need these functions.
  10664. @<Function prototypes for scripting@>=
  10665. void setWidgetDecoratorProperties(QScriptValue value, QScriptEngine *engine);
  10666. QScriptValue constructWidgetDecorator(QScriptContext *context,
  10667. QScriptEngine *engine);
  10668. @ The scripting engine must be informed of this function.
  10669. @<Set up the scripting engine@>=
  10670. constructor = engine->newFunction(constructWidgetDecorator);
  10671. value = engine->newQMetaObject(&WidgetDecorator::staticMetaObject, constructor);
  10672. engine->globalObject().setProperty("WidgetDecorator", value);
  10673. @ The constructor is slightly more complex than other script constructors, but
  10674. still simple.
  10675. @<Functions for scripting@>=
  10676. QScriptValue constructWidgetDecorator(QScriptContext *context,
  10677. QScriptEngine *engine)
  10678. {
  10679. QWidget *widget = argument<QWidget *>(0, context);
  10680. QString text = argument<QString>(1, context);
  10681. Qt::Orientations@, orientation;
  10682. switch(argument<int>(2, context))@/
  10683. {@t\1@>@/
  10684. case 2:@/
  10685. orientation = Qt::Vertical;
  10686. break;
  10687. default:@/
  10688. orientation = Qt::Horizontal;
  10689. break;@t\2@>@/
  10690. }
  10691. QScriptValue object =
  10692. engine->newQObject(new WidgetDecorator(widget, text, orientation));
  10693. setWidgetDecoratorProperties(object, engine);
  10694. return object;
  10695. }
  10696. void setWidgetDecoratorProperties(QScriptValue value, QScriptEngine *engine)
  10697. {
  10698. setQWidgetProperties(value, engine);
  10699. }
  10700. @* The LogEditWindow Class.
  10701. \noindent This class will be depreciated in a future release once I have
  10702. confirmed that the class can be replaced by the configuration system. It has not
  10703. been updated to support new functionality added in version 1.2.3 and use of this
  10704. class is highly discouraged.
  10705. While the logging window provided in the example configuration is fine for
  10706. recording an existing roast, there are some who would like to be able to use
  10707. \pn{} to work with data collected with a manual logger. Different controls are
  10708. useful in such a case. The |LogEditWindow| provides this.
  10709. @<Class declarations@>=
  10710. class LogEditWindow : public QMainWindow@/
  10711. {@t\1@>@/
  10712. Q_OBJECT@;
  10713. QWidget *centralWidget;
  10714. PackLayout *mainLayout;
  10715. QHBoxLayout *addRowsLayout;
  10716. QLabel *startTimeLabel;
  10717. QTimeEdit *startTime;
  10718. QLabel *endTimeLabel;
  10719. QTimeEdit *endTime;
  10720. QLabel *intervalLabel;
  10721. QSpinBox *interval;
  10722. QPushButton *addRows;
  10723. QAction *saveXml;
  10724. QAction *saveCsv;
  10725. QAction *openXml;
  10726. MeasurementModel *model;
  10727. QTableView *log;@/
  10728. @t\4@>private slots@t\kern-3pt@>:@/
  10729. void addTheRows();
  10730. void saveXML();
  10731. void saveCSV();
  10732. void openXML();@/
  10733. protected:@/
  10734. void closeEvent(QCloseEvent *event);@/
  10735. public:@/
  10736. LogEditWindow();@t\2@>@/
  10737. }@t\kern-3pt@>;
  10738. @ This window provides controls for adding rows to a measurement. Typically, the
  10739. data on a manual roast log will have measurements at regular intervals with the
  10740. possible exception of a few points where there are control changes or the end of
  10741. the batch. The routine for adding rows is capable of adding a single row, rows
  10742. in a range of times at regular intervals, or rows in a range of times at regular
  10743. intervals plus one time at the end.
  10744. @<LogEditWindow Implementation@>=
  10745. void LogEditWindow::addTheRows()
  10746. {
  10747. QTime s = startTime->time();
  10748. while(s < endTime->time())
  10749. {
  10750. model->newMeasurement(Measurement(0, s), 1);
  10751. s = s.addSecs(interval->value());
  10752. }
  10753. model->newMeasurement(Measurement(0, endTime->time()), 1);
  10754. }
  10755. @ The window is prepared in its constructor.
  10756. @<LogEditWindow Implementation@>=
  10757. LogEditWindow::LogEditWindow() : QMainWindow(NULL),
  10758. centralWidget(new QWidget(NULL)), mainLayout(new PackLayout(0)),@|
  10759. addRowsLayout(new QHBoxLayout(NULL)),
  10760. startTimeLabel(new QLabel("Start Time")),@|
  10761. startTime(new QTimeEdit(QTime(0, 0, 0, 0))),@|
  10762. endTimeLabel(new QLabel("End Time")),
  10763. endTime(new QTimeEdit(QTime(0, 20, 0, 0))),@|
  10764. intervalLabel(new QLabel("Interval (seconds)")),@|
  10765. interval(new QSpinBox()),
  10766. addRows(new QPushButton("Add Rows")),@|
  10767. saveXml(new QAction(tr("Save Profile As..."), NULL)),@|
  10768. saveCsv(new QAction(tr("Export CSV"), NULL)),@|
  10769. openXml(new QAction(tr("Load Target Profile..."), NULL)),@|
  10770. model(new MeasurementModel()),
  10771. log(new QTableView())@/
  10772. {
  10773. @<Restore editor window geometry from settings@>@;
  10774. @<Set up the editor control bar@>@;
  10775. @<Prepare the model@>@;
  10776. @<Prepare the log table@>@;
  10777. mainLayout->addItem(addRowsLayout);
  10778. mainLayout->addWidget(log);
  10779. centralWidget->setLayout(mainLayout);
  10780. setCentralWidget(centralWidget);
  10781. QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
  10782. fileMenu->addAction(openXml);
  10783. connect(openXml, SIGNAL(triggered()), this, SLOT(openXML()));
  10784. fileMenu->addAction(saveXml);
  10785. connect(saveXml, SIGNAL(triggered()), this, SLOT(saveXML()));
  10786. fileMenu->addAction(saveCsv);
  10787. connect(saveCsv, SIGNAL(triggered()), this, SLOT(saveCSV()));
  10788. }
  10789. @ The window keeps its previous size and location in settings. These need to be
  10790. restored when a new window is created.
  10791. @<Restore editor window geometry from settings@>=
  10792. QSettings settings;
  10793. resize(settings.value("logSize", QSize(620,400)).toSize());
  10794. move(settings.value("logPos", QPoint(200,60)).toPoint());
  10795. @ When a new window is opened, it starts with an empty profile. If this is used
  10796. to manually enter a profile rather than edit an existing profile, rows will need
  10797. to be added. For this, we provide a set of controls where a start time, an end
  10798. time, and an interval in seconds is specified along with a button that, when
  10799. pressed, will produce a row in the model for the starting time, the ending time,
  10800. and regularly spaced times between the two. If only a single row is needed, this
  10801. can be produced by setting the start and end times the same.
  10802. @<Set up the editor control bar@>=
  10803. mainLayout->setOrientation(Qt::Vertical);
  10804. addRowsLayout->addSpacing(10);
  10805. addRowsLayout->addWidget(startTimeLabel);
  10806. addRowsLayout->addWidget(startTime);
  10807. addRowsLayout->addSpacing(10);
  10808. startTime->setDisplayFormat("mm:ss");
  10809. addRowsLayout->addWidget(endTimeLabel);
  10810. addRowsLayout->addWidget(endTime);
  10811. addRowsLayout->addSpacing(10);
  10812. endTime->setDisplayFormat("mm:ss");
  10813. addRowsLayout->addWidget(intervalLabel);
  10814. addRowsLayout->addWidget(interval);
  10815. addRowsLayout->addSpacing(10);
  10816. interval->setRange(0, 60);
  10817. interval->setValue(30);
  10818. addRowsLayout->addWidget(addRows);
  10819. addRowsLayout->addSpacing(10);
  10820. connect(addRows, SIGNAL(clicked()), this, SLOT(addTheRows()));
  10821. @ The model will have three columns: Time, Temperature, and Annotation. This
  10822. probably should not be hard coded.
  10823. @<Prepare the model@>=
  10824. model->setHeaderData(0, Qt::Horizontal, "Time");
  10825. model->setHeaderData(1, Qt::Horizontal, "Temperature");
  10826. model->setHeaderData(2, Qt::Horizontal, "Annotation");
  10827. model->clear();
  10828. @ The profile is presented in a table view. The columns should be wide enough to
  10829. contain a label, the data contained in the column, and an editor delegate.
  10830. @<Prepare the log table@>=
  10831. log->setModel(model);
  10832. log->setColumnWidth(0, 100);
  10833. log->setColumnWidth(1, 100);
  10834. log->setColumnWidth(2, 100);
  10835. @ Most users will want to save a profile after they'@q'@>ve edited it. We also
  10836. provide CSV export here. Note that this class only supports logs with a single
  10837. temperature and a single annotation column. As the class is considered
  10838. depreciated, it will not be extended to support arbitrarily many columns.
  10839. @<LogEditWindow Implementation@>=
  10840. void LogEditWindow::saveXML()
  10841. {
  10842. QSettings settings;
  10843. QString lastDir = settings.value("lastDirectory").toString();
  10844. QString filename = QFileDialog::getSaveFileName(this, tr("Save Log As..."),
  10845. lastDir, "", 0);
  10846. QFile file(filename);
  10847. XMLOutput writer(model, &file, 0);
  10848. writer.addTemperatureColumn("Temperature", 1);
  10849. writer.addAnnotationColumn("Annotation", 2);
  10850. if(writer.output())
  10851. {
  10852. QFileInfo info(filename);
  10853. QDir directory = info.dir();
  10854. lastDir = directory.path();
  10855. settings.setValue("lastDirectory", lastDir);
  10856. }
  10857. }
  10858. void LogEditWindow::saveCSV()
  10859. {
  10860. QSettings settings;
  10861. QString lastDir = settings.value("lastDirectory").toString();
  10862. QString filename = QFileDialog::getSaveFileName(this, tr("Export As..."),
  10863. lastDir, "", 0);
  10864. QFile file(filename);
  10865. CSVOutput writer(model, &file, 0);
  10866. writer.addTemperatureColumn("Temperature", 1);
  10867. writer.addAnnotationColumn("Annotation", 2);
  10868. if(writer.output())
  10869. {
  10870. QFileInfo info(filename);
  10871. QDir directory = info.dir();
  10872. lastDir = directory.path();
  10873. settings.setValue("lastDirectory", lastDir);
  10874. }
  10875. }
  10876. @ Some may want to open a previously saved profile, for example, to adjust the
  10877. position of an annotation. Note that this class is not appropriate for editing
  10878. profiles with more than one temperature column.
  10879. @<LogEditWindow Implementation@>=
  10880. void LogEditWindow::openXML()
  10881. {
  10882. QSettings settings;
  10883. QString lastDir = settings.value("lastDirectory").toString();
  10884. QString filename = QFileDialog::getOpenFileName(this, tr("Open XML Log..."),
  10885. lastDir, "", 0);
  10886. if(filename.isNull())
  10887. {
  10888. return;
  10889. }
  10890. QFile file(filename);
  10891. XMLInput reader(&file, 1);
  10892. connect(&reader, SIGNAL(measure(Measurement, int)),
  10893. model, SLOT(newMeasurement(Measurement, int)));
  10894. connect(&reader, SIGNAL(annotation(QString, int, int)),
  10895. model, SLOT(newAnnotation(QString, int, int)));
  10896. if(reader.input())
  10897. {
  10898. QFileInfo info(filename);
  10899. setWindowTitle(QString(tr("%1 - %2")).@|
  10900. arg(QCoreApplication::applicationName()).arg(info.baseName()));
  10901. QDir directory = info.dir();
  10902. lastDir = directory.path();
  10903. settings.setValue("lastDirectory", lastDir);
  10904. }
  10905. }
  10906. @ The window should remember its last size and position, so we store this
  10907. information in settings when the window is closed.
  10908. @<LogEditWindow Implementation@>=
  10909. void LogEditWindow::closeEvent(QCloseEvent *event)
  10910. {
  10911. QSettings settings;
  10912. settings.setValue("logSize", size());
  10913. settings.setValue("logPos", pos());
  10914. event->accept();
  10915. }
  10916. @ One function is required to instantiate this class from a script.
  10917. @<Function prototypes for scripting@>=
  10918. QScriptValue constructLogEditWindow(QScriptContext *context,
  10919. QScriptEngine *engine);
  10920. @ The engine must be informed of this function.
  10921. @<Set up the scripting engine@>=
  10922. constructor = engine->newFunction(constructLogEditWindow);
  10923. value = engine->newQMetaObject(&LogEditWindow::staticMetaObject, constructor);
  10924. engine->globalObject().setProperty("LogEditWindow", value);
  10925. @ The constructor just creates the window and passes it back to the engine.
  10926. @<Functions for scripting@>=
  10927. QScriptValue constructLogEditWindow(QScriptContext *, QScriptEngine *engine)
  10928. {
  10929. QScriptValue object = engine->newQObject(new LogEditWindow);
  10930. return object;
  10931. }
  10932. @** File IO.
  10933. \noindent So far, the data is all stored in memory. It is often useful to save
  10934. data to a file or read back previously saved data. Presently, two formats are
  10935. supported: an XML format which can also be read back in and CSV which can easily
  10936. be used with many external tools.
  10937. File IO is handled by a few classes: one per input format and one per output
  10938. format. The classes in the following sections should be simple enough to follow
  10939. that it should be clear how to extend \pn{} to support other formats if needed.
  10940. Should additional output formats be required, it may be beneficial to
  10941. reimplement the serializers as subclasses of a new abstract serializer class in
  10942. order to share common code among them where reusing \cweb{} chunks is not an
  10943. appropriate technique.
  10944. @* XML Output.
  10945. \noindent An XML format has been chosen as the native format for \pn{} because
  10946. of Qt'@q'@>s excellent support for reading and writing such documents. Using this
  10947. capability is less error prone than developing a new, more compact format.
  10948. Another reason to choose XML is that it becomes quite easy to modify saved data
  10949. in a text editor and still end up with something \pn{} will understand.
  10950. The structure of the file we will produce is simple, however it has been
  10951. modified from a simpler structure that was used in versions of Typica prior to
  10952. 1.2.3. How to read these files can be determined by the document type found at
  10953. the start of the file. At the start of the file, there should be one or more
  10954. {\tt <tempseries>} elements and one or more {\tt <noteseries>} elements. These
  10955. are empty elements with a {\tt name} attribute which can be used to label the
  10956. column in a view. Once these column declarations have been written, a
  10957. {\tt <roast>} element is produced which contains a set of zero or more
  10958. {\tt <tuple>} elements. Each tuple contains one {\tt <time>} element containing
  10959. the time of the measurement relative to the start of the batch and optionally
  10960. one or more {\tt <temperature>} and {\tt <annotation>} elements containing
  10961. measurement and annotation data associated with that time. The
  10962. {\tt <temperature>} and {\tt <annotation>} elements have a {\tt series}
  10963. attribute where the value of the attribute matches the {\tt name} attribute of a
  10964. {\tt <tempseries>} or {\tt <noteseries>} element which allows each measurement
  10965. to be placed in the correct data series regardless of element ordering in the
  10966. document.
  10967. There are certain oddities about this format compared with other XML based
  10968. formats. The order of some elements in the current implementation affects the
  10969. behavior of the program and there is no longer a proper root element. This
  10970. format may be extended in future versions of \pn{} to support additional
  10971. functionality or to improve the robustness of the format. Should such
  10972. modifications occur, an effort should be made to ensure that \pn{} continues to
  10973. support the import of old data.
  10974. As of version 1.0.8, this class is derived from |QObject| for easier integration
  10975. with the scripting engine.
  10976. The |temperatureColumns| and |annotationColumns| member data structures are
  10977. currently a |QMap| rather than a |QHash| because the number of data series in a
  10978. single file is likely to be small enough that the difference in lookup time
  10979. should be negligeable and the ability to iterate over the keys in the |QMap| in
  10980. sorted order is useful.
  10981. @<Class declarations@>=
  10982. class XMLOutput : public QObject@/
  10983. {@/
  10984. Q_OBJECT@;@/
  10985. MeasurementModel *data;
  10986. QIODevice *out;
  10987. int time;
  10988. QMap<int, QString> temperatureColumns;
  10989. QMap<int, QString> controlColumns;
  10990. QMap<int, QString> annotationColumns;
  10991. public:@/
  10992. XMLOutput(MeasurementModel *model, QIODevice *device, int timec = 0);
  10993. void addTemperatureColumn(const QString &series, int column);
  10994. void addControlColumn(const QString &series, int column);
  10995. void addAnnotationColumn(const QString &series, int column);
  10996. void setModel(MeasurementModel *model);
  10997. void setTimeColumn(int column);
  10998. void setDevice(QIODevice *device);
  10999. bool output();
  11000. };
  11001. @ The interesting part of this class is the |output| routine. This goes over the
  11002. data in the model and constructs an appropriate XML document. If the operation
  11003. fails, the function returns |false|, otherwise it returns |true|.
  11004. @<XMLOutput Implementation@>=
  11005. bool XMLOutput::output()@t\2\2@>@/
  11006. {@t\1@>@/
  11007. if(!out->open(QIODevice::WriteOnly | QIODevice::Text))@/
  11008. {@t\1@>@/
  11009. return false;@t\2@>@/
  11010. }@/
  11011. QXmlStreamWriter xmlout(out);
  11012. xmlout.writeStartDocument("1.0");
  11013. xmlout.writeDTD("<!DOCTYPE roastlog3.0>");
  11014. xmlout.writeStartElement("roastlog");
  11015. @<Output the column declarations@>@;
  11016. xmlout.writeStartElement("roast");
  11017. bool oresult;
  11018. for(int i = 0; i < data->rowCount(); i++)@/
  11019. {
  11020. @<Check if row should be output@>@;
  11021. if(oresult)
  11022. {
  11023. @<Output tuple element@>@;
  11024. }
  11025. }
  11026. xmlout.writeEndElement();
  11027. xmlout.writeEndElement();
  11028. xmlout.writeEndDocument();
  11029. out->close();@/
  11030. return true;@t\2@>@/
  11031. }
  11032. @ Temperature column declarations are output before annotation column
  11033. declarations. Within each category, column declarations are output in order by
  11034. column number.
  11035. @<Output the column declarations@>=
  11036. foreach(int c, temperatureColumns.keys())
  11037. {
  11038. xmlout.writeStartElement("tempseries");
  11039. xmlout.writeAttribute("name", temperatureColumns.value(c));
  11040. xmlout.writeEndElement();
  11041. }
  11042. foreach(int c, controlColumns.keys())
  11043. {
  11044. xmlout.writeStartElement("controlseries");
  11045. xmlout.writeAttribute("name", controlColumns.value(c));
  11046. xmlout.writeEndElement();
  11047. }
  11048. foreach(int c, annotationColumns.keys())
  11049. {
  11050. xmlout.writeStartElement("noteseries");
  11051. xmlout.writeAttribute("name", annotationColumns.value(c));
  11052. xmlout.writeEndElement();
  11053. }
  11054. @ When checking a row in the model to determine if it contains values that need
  11055. to be written, we want to know if any of the temperature or annotation columns
  11056. contain a value. If at least one of these columns is not empty for this row, we
  11057. need to output a tuple for that row.
  11058. @<Check if row should be output@>=
  11059. oresult = false;@/
  11060. foreach(int c, temperatureColumns.keys())@/
  11061. {@t\1@>@/
  11062. if(data->data(data->index(i, c), Qt::DisplayRole).isValid() &&
  11063. !(data->data(data->index(i, c), Qt::DisplayRole).toString().isEmpty()))@/
  11064. {@t\1@>@/
  11065. oresult = true;
  11066. break;@t\2@>@/
  11067. }@t\2@>@/
  11068. }@/
  11069. foreach(int c, controlColumns.keys())
  11070. {
  11071. if(data->data(data->index(i, c), Qt::DisplayRole).isValid() &&
  11072. !(data->data(data->index(i, c), Qt::DisplayRole).toString().isEmpty()))
  11073. {
  11074. oresult = true;
  11075. break;
  11076. }
  11077. }
  11078. if(oresult == false)@/
  11079. {@t\1@>@/
  11080. foreach(int c, annotationColumns.keys())@/
  11081. {@t\1@>@/
  11082. if(data->data(data->index(i, c), Qt::DisplayRole).isValid() &&
  11083. !(data->data(data->index(i, c), Qt::DisplayRole).toString().
  11084. isEmpty()))@/
  11085. {@t\1@>@/
  11086. oresult = true;
  11087. break;@t\2@>@/
  11088. }@t\2@>@/
  11089. }@t\2@>@/
  11090. }
  11091. @ Now that we know that values from the current row should be output, we can
  11092. produce a {\tt <tuple>} element, a {\tt <time>} element for that tuple, and then
  11093. iterate over the set of columns we might want to output, producing an
  11094. appropriate element for each non-empty column for that row.
  11095. @<Output tuple element@>=
  11096. xmlout.writeStartElement("tuple");
  11097. xmlout.writeTextElement("time", data->data(data->index(i, time),
  11098. Qt::DisplayRole).toString());
  11099. foreach(int c, temperatureColumns.keys())@/
  11100. {
  11101. if(data->data(data->index(i, c), Qt::DisplayRole).isValid() &&
  11102. !(data->data(data->index(i, c), Qt::DisplayRole).toString().isEmpty()))@/
  11103. {
  11104. xmlout.writeStartElement("temperature");
  11105. xmlout.writeAttribute("series", temperatureColumns.value(c));
  11106. if(data->data(data->index(i, c), Qt::UserRole).toMap().contains("relative"))
  11107. {
  11108. if(data->data(data->index(i, c), Qt::UserRole).toMap().value("relative").toBool())
  11109. {
  11110. xmlout.writeAttribute("relative", "true");
  11111. }
  11112. }
  11113. xmlout.writeCharacters(data->data(data->index(i, c), Qt::DisplayRole).
  11114. toString());
  11115. xmlout.writeEndElement();
  11116. }
  11117. }
  11118. foreach(int c, controlColumns.keys())
  11119. {
  11120. if(data->data(data->index(i, c), Qt::DisplayRole).isValid() &&
  11121. !(data->data(data->index(i, c), Qt::DisplayRole).toString().isEmpty()))
  11122. {
  11123. xmlout.writeStartElement("control");
  11124. xmlout.writeAttribute("series", controlColumns.value(c));
  11125. xmlout.writeCharacters(data->data(data->index(i, c), Qt::DisplayRole).toString());
  11126. xmlout.writeEndElement();
  11127. }
  11128. }
  11129. foreach(int c, annotationColumns.keys())@/
  11130. {
  11131. if(data->data(data->index(i, c), Qt::DisplayRole).isValid() &&
  11132. !(data->data(data->index(i, c), Qt::DisplayRole).toString().isEmpty()))@/
  11133. {
  11134. xmlout.writeStartElement("annotation");
  11135. xmlout.writeAttribute("series", annotationColumns.value(c));
  11136. xmlout.writeCharacters(data->data(data->index(i, c), Qt::DisplayRole).
  11137. toString());
  11138. xmlout.writeEndElement();
  11139. }
  11140. }
  11141. xmlout.writeEndElement();
  11142. @ The rest of the class just initializes the private member data.
  11143. @<XMLOutput Implementation@>=
  11144. XMLOutput::XMLOutput(MeasurementModel *model, QIODevice *device, int timec)
  11145. : QObject(NULL), data(model), out(device), time(timec)@/
  11146. {
  11147. /* Nothing has to be done here. */
  11148. }@;
  11149. void XMLOutput::setModel(MeasurementModel *model)
  11150. {
  11151. data = model;
  11152. }
  11153. void XMLOutput::setTimeColumn(int column)
  11154. {
  11155. time = column;
  11156. }
  11157. void XMLOutput::setDevice(QIODevice *device)
  11158. {
  11159. out = device;
  11160. }
  11161. @ As of version 1.2.3, the old |setTemperatureColumn()| and
  11162. |setAnnotationColumn()| methods have been replaced with the
  11163. |addTemperatureColumn()| and |addAnnotationColumn()| methods respectively. The
  11164. main difference is that the new methods take a column name in addition to a
  11165. number and it is now possible to specify multiple columns of each category for
  11166. export.
  11167. @<XMLOutput Implementation@>=
  11168. void XMLOutput::addTemperatureColumn(const QString &series, int column)
  11169. {
  11170. temperatureColumns.insert(column, series);
  11171. }
  11172. void XMLOutput::addControlColumn(const QString &series, int column)
  11173. {
  11174. controlColumns.insert(column, series);
  11175. }
  11176. void XMLOutput::addAnnotationColumn(const QString &series, int column)
  11177. {
  11178. annotationColumns.insert(column, series);
  11179. }
  11180. @* XML Input.
  11181. \noindent Once model data can be saved to a file, it is useful to be able to
  11182. read that data back in. This is a little different from reading data out of a
  11183. model as more than one object is potentially interested in the data. Instead, we
  11184. emit signals for measurements and annotations. This class has been modified to
  11185. support both the current (as of version 1.2.3) output of the |XMLOutput| class
  11186. and the older version. If changes are made to |XMLOutput| this class may also
  11187. need to be modified.
  11188. The main differences in the current version of this class are that the first
  11189. column is specified rather than specifying temperature and annotation columns
  11190. separately and additional signals are emitted to allow views to prepare for an
  11191. arbitrary number of columns.
  11192. The |newTemperatureColumn| and |newAnnotationColumn| signals can be used to set
  11193. up column headers while the |lastColumn| signal can be used to shift live data
  11194. streams to unoccupied columns.
  11195. @<Class declarations@>=
  11196. class XMLInput : public QObject@/
  11197. {
  11198. Q_OBJECT@;
  11199. int firstc;
  11200. QIODevice *in;
  11201. public:@/
  11202. XMLInput(QIODevice *input, int c);
  11203. void setFirstColumn(int column);
  11204. void setDevice(QIODevice *device);
  11205. bool input();
  11206. signals:@/
  11207. void measure(Measurement, int);
  11208. void annotation(QString, int, int);
  11209. void newTemperatureColumn(int, QString);
  11210. void newAnnotationColumn(int, QString);
  11211. void lastColumn(int);
  11212. };
  11213. @ The main point of interest here is the |input()| method. If the file is read
  11214. successfully, |true| is returned. Otherwise, |false| is returned.
  11215. @<XMLInput Implementation@>=
  11216. bool XMLInput::input()@t\2\2@>@/
  11217. {@t\1@>@/
  11218. if(!in->open(QIODevice::ReadOnly | QIODevice::Text))@/
  11219. {@t\1@>@/
  11220. return false;@t\2@>@/
  11221. }@/
  11222. QXmlStreamReader xmlin(in);
  11223. QMap<QString, int> temperatureColumns;
  11224. QMap<QString, int> annotationColumns;
  11225. int nextColumn = firstc;
  11226. @<Read column declarations@>@;
  11227. QTime timeval = QTime();
  11228. double tempval = 0;
  11229. QString noteval = QString();
  11230. int column;
  11231. int counter = 0;@/
  11232. while(!xmlin.atEnd())@/
  11233. {@/
  11234. @<Read XML file@>@;
  11235. }@/
  11236. return true;@t\2@>@/
  11237. }
  11238. @ A data file may or may not contain elements that specify the name of a column.
  11239. In order to determine how to proceed, we should check the doctype of the input
  11240. file. This should be the first element of the input file encountered.
  11241. \danger There is not nearly enough error checking here.
  11242. \endanger
  11243. @<Read column declarations@>=
  11244. while(!xmlin.isDTD())
  11245. {
  11246. xmlin.readNext();
  11247. }
  11248. if(xmlin.isDTD())
  11249. {
  11250. if(xmlin.text() == "<!DOCTYPE roastlog>")
  11251. {
  11252. @<Emit old format column specification@>@;
  11253. }
  11254. else
  11255. {
  11256. xmlin.readNext();
  11257. @<Scan for column declarations and emit@>@;
  11258. }
  11259. }
  11260. @ Old format data will not have column declarations. This means that we must
  11261. produce a default set of signals rather than waiting to read elements describing
  11262. the columns.
  11263. @<Emit old format column specification@>=
  11264. emit newTemperatureColumn(firstc, "Bean");
  11265. emit newAnnotationColumn(firstc + 1, "Note");
  11266. emit lastColumn(firstc + 1);
  11267. @ The current format will have column declarations prior to the {\tt <roast>}
  11268. element. We can just read until we hit that element and emit the appropriate
  11269. signals as elements are encountered.
  11270. @<Scan for column declarations and emit@>=
  11271. while(xmlin.name() != "roast")
  11272. {
  11273. if(xmlin.isStartElement())
  11274. {
  11275. if((xmlin.name() == "tempseries") || (xmlin.name() == "controlseries"))
  11276. {
  11277. temperatureColumns.insert(xmlin.attributes().value("name").
  11278. toString(),
  11279. nextColumn);
  11280. emit newTemperatureColumn(nextColumn,
  11281. xmlin.attributes().value("name").
  11282. toString());
  11283. nextColumn++;
  11284. }
  11285. else if(xmlin.name() == "noteseries")
  11286. {
  11287. annotationColumns.insert(xmlin.attributes().value("name").
  11288. toString(), nextColumn);
  11289. emit newAnnotationColumn(nextColumn,
  11290. xmlin.attributes().value("name").
  11291. toString());
  11292. nextColumn++;
  11293. }
  11294. }
  11295. xmlin.readNext();
  11296. }
  11297. emit lastColumn(nextColumn - 1);
  11298. @ Now we are ready to read measurements from the file. When encountering a
  11299. {\tt <time>} element, we record the time and move on. For {\tt <temperature>}
  11300. and {\tt <annotation>} elements, we emit the appropriate signal. This is handled
  11301. slightly differently depending on which version of the file format is being
  11302. used. Note that there is not nearly enough error checking here and we are
  11303. basically ignoring {\tt <tuple>} elements.
  11304. Due to the typically large number of measurements taken over the course of a
  11305. roast and the amount of time often taken to process these measurements when they
  11306. are read from a file, there is a need to periodically pass control back to the
  11307. event loop to remain responsive to user input.
  11308. @<Read XML file@>=
  11309. xmlin.readNext();
  11310. if(xmlin.isStartElement())
  11311. {
  11312. @<Read measurement data@>@;
  11313. }
  11314. counter++;
  11315. if(counter % 100 == 0)
  11316. {
  11317. QCoreApplication::processEvents();
  11318. }
  11319. @ When reading start elements, it is safe to ignore {\tt <tuple>} and
  11320. {\tt <roast>}. Technically, this means that the program can read certain types
  11321. of invalid data. The Robustness Principle\nfnote{``Be liberal in what you
  11322. accept, and conservative in what you send,'' --- Robert Braden, {\it RFC 1122
  11323. \S 1.2.2}} is generally applicable to any type of data exchange. That said,
  11324. malformed data is not guaranteed readable in the future, even if it does work
  11325. now.
  11326. \danger One set of test input caused this code to emit an empty annotation for
  11327. every measurement. This is the reason for wrapping the annotation signal
  11328. emission to check for this. The detected annotation elements were not present in
  11329. the input stream and I have absolutely no idea where the program came up with
  11330. them. \endanger
  11331. @<Read measurement data@>=
  11332. if(xmlin.name() == "time")
  11333. {
  11334. timeval = QTime::fromString(xmlin.readElementText(), "mm:ss.zzz");
  11335. }
  11336. else if(xmlin.name() == "temperature")
  11337. {
  11338. column = xmlin.attributes().hasAttribute("series") ?
  11339. temperatureColumns.value(xmlin.attributes().value("series").toString()) : firstc;
  11340. bool relative = (xmlin.attributes().value("relative") == "true");
  11341. tempval = xmlin.readElementText().toDouble();
  11342. Measurement measurement(tempval, timeval);
  11343. if(relative)
  11344. {
  11345. measurement.insert("relative", true);
  11346. }
  11347. emit measure(measurement, column);
  11348. }
  11349. else if(xmlin.name() == "control")
  11350. {
  11351. column = xmlin.attributes().value("series").toString().isEmpty() ?
  11352. firstc : temperatureColumns.value(xmlin.attributes().
  11353. value("series").toString());
  11354. tempval = xmlin.readElementText().toDouble();
  11355. Measurement measurement(tempval, timeval, Units::Unitless);
  11356. emit measure(measurement, column);
  11357. }
  11358. else if(xmlin.name() == "annotation")
  11359. {
  11360. column = xmlin.attributes().value("series").toString().isEmpty() ?
  11361. firstc + 1 : annotationColumns.value(xmlin.attributes().
  11362. value("series").toString());
  11363. noteval = xmlin.readElementText();
  11364. if(!noteval.isEmpty())
  11365. {
  11366. emit annotation(noteval, firstc, column);
  11367. }
  11368. }
  11369. @ The other methods just set the private member data.
  11370. @<XMLInput Implementation@>=
  11371. XMLInput::XMLInput(QIODevice *input, int c) :
  11372. firstc(c), in(input)@/
  11373. {@/
  11374. /* Nothing has to be done here. */
  11375. }
  11376. void XMLInput::setFirstColumn(int column)
  11377. {
  11378. firstc = column;
  11379. }
  11380. void XMLInput::setDevice(QIODevice *device)
  11381. {
  11382. in = device;
  11383. }
  11384. @ In order to allow scripts to instantiate the |XMLInput| class, we need a
  11385. constructor and a wrapper around the |input()| method.
  11386. @<Function prototypes for scripting@>=
  11387. QScriptValue constructXMLInput(QScriptContext *context, QScriptEngine *engine);
  11388. QScriptValue XMLInput_input(QScriptContext *context, QScriptEngine *engine);
  11389. @ The script constructor is passed to the scripting engine.
  11390. @<Set up the scripting engine@>=
  11391. constructor = engine->newFunction(constructXMLInput);
  11392. value = engine->newQMetaObject(&XMLInput::staticMetaObject, constructor);
  11393. engine->globalObject().setProperty("XMLInput", value);
  11394. @ The implementation should seem familiar.
  11395. @<Functions for scripting@>=
  11396. QScriptValue constructXMLInput(QScriptContext *context, QScriptEngine *engine)
  11397. {
  11398. QIODevice *device = argument<QIODevice *>(0, context);
  11399. QScriptValue object = engine->newQObject(new XMLInput(&*device,
  11400. argument<int>(1, context)));
  11401. object.setProperty("input", engine->newFunction(XMLInput_input));
  11402. return object;
  11403. }
  11404. QScriptValue XMLInput_input(QScriptContext *context, QScriptEngine *)
  11405. {
  11406. XMLInput *self = getself<@[XMLInput *@]>(context);
  11407. self->input();
  11408. return QScriptValue();
  11409. }
  11410. @* CSV Output.
  11411. \noindent While XML is convenient for \pn{}, other programs may not handle this
  11412. format well. For this purpose, we use a text file with comma separated values.
  11413. Data in this format can easily be handled by shell scripts, simple programs, and
  11414. any spreadsheet (though some may handle the time column poorly).
  11415. We do not need to concern ourselves with reading data in this format back in,
  11416. but there is no reason a class could not be written to do this.
  11417. The structure of this class is very similar to the |XMLOutput| class.
  11418. @<Class declarations@>=
  11419. class CSVOutput@/
  11420. {@/
  11421. MeasurementModel *data;
  11422. QIODevice *out;
  11423. int time;
  11424. QMap<int, QString> temperatureColumns;
  11425. QMap<int, QString> controlColumns;
  11426. QMap<int, QString> annotationColumns;@/
  11427. public:@/
  11428. CSVOutput(MeasurementModel *model, QIODevice *device, int timec = 0);
  11429. void addTemperatureColumn(const QString &series, int column);
  11430. void addControlColumn(const QString &series, int column);
  11431. void addAnnotationColumn(const QString &series, int column);
  11432. void setModel(MeasurementModel *model);
  11433. void setTimeColumn(int column);
  11434. void setDevice(QIODevice *device);
  11435. bool output();@/
  11436. };
  11437. @ Very little needs to be done to output the data. We open the output stream
  11438. and, if the output stream was successfully opened, we look for measurements and
  11439. output the text, remembering to output a comma between items and a newline after
  11440. each record. If the data is successfully output, |true| is returned, otherwise
  11441. we return |false|.
  11442. The comparably simple structure of the CSV format allows us to just fling the
  11443. data onto a text stream.
  11444. @<CSVOutput Implementation@>=
  11445. bool CSVOutput::output()@t\2\2@>@/
  11446. {@t\1@>@/
  11447. if(!out->open(QIODevice::WriteOnly | QIODevice::Text))@/
  11448. {@t\1@>@/
  11449. return false;@t\2@>@/
  11450. }@/
  11451. QTextStream output(out);
  11452. @<Output CSV column headers@>@;
  11453. bool oresult;
  11454. for(int i = 0; i < data->rowCount(); i++)@/
  11455. {
  11456. @<Check if row should be output@>@;
  11457. if(oresult)
  11458. {
  11459. @<Output CSV row@>@;
  11460. }
  11461. }
  11462. out->close();@/
  11463. return true;@t\2@>@/
  11464. }
  11465. @ Before writing the data, we output a row containing the name of each column.
  11466. @<Output CSV column headers@>=
  11467. output << "Time";
  11468. foreach(int c, temperatureColumns.keys())
  11469. {
  11470. output << ',' << temperatureColumns.value(c);
  11471. }
  11472. foreach(int c, controlColumns.keys())
  11473. {
  11474. output << ',' << controlColumns.value(c);
  11475. }
  11476. foreach(int c, annotationColumns.keys())
  11477. {
  11478. output << ',' << annotationColumns.value(c);
  11479. }
  11480. output << '\n';
  11481. @ Once the header information has been written, we can proceed to output the
  11482. real data. The algorithm for doing this has been changed as of version 1.2.3
  11483. with the result that most uses will now produce more delimiters than the same
  11484. data in previous versions. This should have no impact on the ability of other
  11485. programs to interact with data produced by \pn{}. The code to handle output in
  11486. this way is much easier to read. A future version might once again suppress
  11487. superfluous commas, however the presence of these commas is not considered a
  11488. serious issue at this time.
  11489. @<Output CSV row@>=
  11490. output << data->data(data->index(i, time), Qt::DisplayRole).toString();
  11491. foreach(int c, temperatureColumns.keys())
  11492. {
  11493. output << ',' << data->data(data->index(i, c), Qt::DisplayRole).toString();
  11494. }
  11495. foreach(int c, controlColumns.keys())
  11496. {
  11497. output << ',' << data->data(data->index(i, c), Qt::DisplayRole).toString();
  11498. }
  11499. foreach(int c, annotationColumns.keys())
  11500. {
  11501. output << ',' << data->data(data->index(i, c), Qt::DisplayRole).toString();
  11502. }
  11503. output << '\n';
  11504. @ The rest of the class just initializes the private member data. See notes
  11505. on the implementation of |XMLOutput|.
  11506. @<CSVOutput Implementation@>=
  11507. CSVOutput::CSVOutput(MeasurementModel *model, QIODevice *device, int timec) :
  11508. data(model), out(device), time(timec)@/
  11509. {
  11510. /* Nothing has to be done here. */
  11511. }@;
  11512. void CSVOutput::setModel(MeasurementModel *model)
  11513. {
  11514. data = model;
  11515. }
  11516. void CSVOutput::setTimeColumn(int column)
  11517. {
  11518. time = column;
  11519. }
  11520. void CSVOutput::addTemperatureColumn(const QString &series, int column)
  11521. {
  11522. temperatureColumns.insert(column, series);
  11523. }
  11524. void CSVOutput::addControlColumn(const QString &series, int column)
  11525. {
  11526. controlColumns.insert(column, series);
  11527. }
  11528. void CSVOutput::addAnnotationColumn(const QString &series, int column)
  11529. {
  11530. annotationColumns.insert(column, series);
  11531. }
  11532. void CSVOutput::setDevice(QIODevice *device)
  11533. {
  11534. out = device;
  11535. }
  11536. @i webview.w
  11537. @i printerselector.w
  11538. @* The Application class.
  11539. The |Application| class represents the \pn{} program. It is responsible for
  11540. setting up the settings object and localization in addition to the normal
  11541. responsibilities of |QApplication|. In addition to declaring the class, we also
  11542. define a macro that returns the |Application| instance.
  11543. @<Class declarations@>=
  11544. #define AppInstance (qobject_cast<@[Application *@]>(qApp))
  11545. class NodeInserter;
  11546. class DeviceTreeModel;
  11547. class DatabaseNotification;
  11548. class Application : public QApplication@/
  11549. {@/
  11550. @[Q_OBJECT@]@;
  11551. public:@/
  11552. Application(int &argc, char **argv);
  11553. QDomDocument* configuration();
  11554. @<Device configuration members@>@;
  11555. QSqlDatabase database();
  11556. Q_INVOKABLE bool databaseConnected();
  11557. Q_INVOKABLE QString currentTypicaUser();
  11558. Q_INVOKABLE bool login(const QString &user, const QString &password);
  11559. Q_INVOKABLE bool autoLogin();
  11560. QScriptEngine *engine;
  11561. QNetworkAccessManager *network;
  11562. DatabaseNotification *subscribe(const QString &notification);@/
  11563. @[public slots@]:@/
  11564. void setDatabaseConnected(bool status);
  11565. void setCurrentTypicaUser(const QString &user);
  11566. void notify(const QString &notification);
  11567. @<Extended Application slots@>@;
  11568. @[signals@]:@/
  11569. void userChanged(const QString &user);
  11570. private:@/
  11571. @<Application private data members@>@;
  11572. QDomDocument conf;
  11573. bool connectionStatus;
  11574. QString currentUser;
  11575. QMap<QString, DatabaseNotification*> notifiers;
  11576. QSqlDriver *notificationDriver;
  11577. };
  11578. @ The constructor for this class handles a few things that had previously been
  11579. handled in |main()|.
  11580. @<Application Implementation@>=
  11581. Application::Application(int &argc, char **argv) : QApplication(argc, argv),
  11582. connectionStatus(false), currentUser(QString())@/
  11583. {
  11584. network = new QNetworkAccessManager(this);
  11585. @<Allow use of the default QSettings constructor@>@;
  11586. @<Load translation objects@>@;
  11587. @<Register meta-types@>@;
  11588. @<Register top level device configuration nodes@>@;
  11589. }
  11590. @ We use |QSettings| objects throughout \pn{} to remember details such as the
  11591. size and position of windows and the most recently used directory. To simplify
  11592. the creation of these objects, we specify some details up front. This allows us
  11593. to use the default constructor rather than specifying these things every time we
  11594. need an object.
  11595. @<Allow use of the default QSettings constructor@>=
  11596. setOrganizationName("Wilson's Coffee & Tea");
  11597. setOrganizationDomain("wilsonscoffee.com");
  11598. setApplicationName(PROGRAM_NAME);
  11599. @ Much of the user visible text in \pn{} is wrapped in a call to |tr()|. Such
  11600. text can be replaced with translated text based on the user'@q'@>s locale. For more
  11601. details, see the Qt Linguist manual.
  11602. @<Load translation objects@>=
  11603. QTranslator *base = new QTranslator;
  11604. if(base->load(QString("qt_%1").arg(QLocale::system().name()), QString("%1/Translations").arg(QCoreApplication::applicationDirPath())))
  11605. {
  11606. installTranslator(base);
  11607. }
  11608. QTranslator *app = new QTranslator;
  11609. if(app->load(QString("%1_%2").arg("Typica").arg(QLocale::system().name()), QString("%1/Translations").arg(QCoreApplication::applicationDirPath())))
  11610. {
  11611. installTranslator(app);
  11612. }
  11613. @ We also want to be able to access the application instance from within the
  11614. scripting engine. We don'@q'@>t need to be able to create new instances, just access the one that already exists.
  11615. @<Set up the scripting engine@>=
  11616. value = engine->newQObject(AppInstance);
  11617. value.setProperty("subscribe", engine->newFunction(Application_subscribe));
  11618. engine->globalObject().setProperty("Application", value);
  11619. @ The |configuration()| method provides access to an XML document containing the
  11620. current application configuration. The object is populated in |main()|.
  11621. @<Application Implementation@>=
  11622. QDomDocument* Application::configuration()
  11623. {
  11624. return &conf;
  11625. }
  11626. @ The |database()| method provides access to a database connection for use by
  11627. database aware widgets.
  11628. @<Application Implementation@>=
  11629. QSqlDatabase Application::database()
  11630. {
  11631. QString connectionName;
  11632. QSqlDatabase connection =
  11633. QSqlDatabase::database(QLatin1String(QSqlDatabase::defaultConnection), false);
  11634. do
  11635. {
  11636. connectionName = QUuid::createUuid().toString();
  11637. } while (QSqlDatabase::connectionNames().contains(connectionName));
  11638. return QSqlDatabase::cloneDatabase(connection, QString(connectionName));
  11639. }
  11640. @ Starting with version 1.8 there are methods for determining if a connection
  11641. to the database was successfully established when Typica was opened.
  11642. @<Application Implementation@>=
  11643. void Application::setDatabaseConnected(bool status)
  11644. {
  11645. connectionStatus = status;
  11646. }
  11647. bool Application::databaseConnected()
  11648. {
  11649. return connectionStatus;
  11650. }
  11651. @ Database notifications allow various parts of Typica to be aware when data in
  11652. the database may have been changed by another instance of Typica running on a
  11653. different computer or by another program that uses the same database. To pass
  11654. notifications to the various places that may be interested in these and not
  11655. bother notification aware controls that are uninterested in unrelated
  11656. notifications, a notification object is created for each notification.
  11657. Note that the notification channel is an identifier and unquoted identifiers
  11658. will be converted to lowercase while \pn{} will attempt to match subscribed to
  11659. notifications exactly. This means that it is generally a good idea to subscribe
  11660. with the channel specified in lowercase.
  11661. @<Application Implementation@>=
  11662. DatabaseNotification* Application::subscribe(const QString &notification)
  11663. {
  11664. DatabaseNotification *retval;
  11665. if(notifiers.contains(notification))
  11666. {
  11667. retval = notifiers.value(notification);
  11668. }
  11669. else
  11670. {
  11671. if(notifiers.size() == 0)
  11672. {
  11673. notificationDriver = QSqlDatabase::database().driver();
  11674. connect(notificationDriver, SIGNAL(notification(QString)), this, SLOT(notify(QString)));
  11675. }
  11676. retval = new DatabaseNotification;
  11677. connect(this, SIGNAL(aboutToQuit()), retval, SLOT(deleteLater()));
  11678. if(notifiers.size() !=
  11679. notificationDriver->subscribedToNotifications().size())
  11680. {
  11681. for(int i = 0; i < notifiers.size(); i++)
  11682. {
  11683. notificationDriver->subscribeToNotification(notifiers.keys().at(i));
  11684. }
  11685. }
  11686. notifiers.insert(notification, retval);
  11687. notificationDriver->subscribeToNotification(notification);
  11688. }
  11689. return retval;
  11690. }
  11691. @ As the various parts of \pn{} operate mostly indepent of each other and
  11692. notifications from the current instance may not be immediately returned, it is
  11693. generally a good idea for operations that would generate a notification to also
  11694. signal that notification within the application.
  11695. @<Application Implementation@>=
  11696. void Application::notify(const QString &notification)
  11697. {
  11698. if(notifiers.contains(notification))
  11699. {
  11700. DatabaseNotification *notifier = notifiers.value(notification);
  11701. notifier->forwardNotification(notification);
  11702. }
  11703. }
  11704. @ A function is provided to allow subscribing to notifications from scripts.
  11705. @<Function prototypes for scripting@>=
  11706. QScriptValue Application_subscribe(QScriptContext *context,
  11707. QScriptEngine *engine);
  11708. @ The implementation is trivial.
  11709. @<Functions for scripting@>=
  11710. QScriptValue Application_subscribe(QScriptContext *context,
  11711. QScriptEngine *engine)
  11712. {
  11713. return engine->newQObject(
  11714. AppInstance->subscribe(argument<QString>(0, context)));
  11715. }
  11716. @ The DatabaseNotification object just needs a signal that interested objects
  11717. can connect to.
  11718. @<Class declarations@>=
  11719. class DatabaseNotification : public QObject
  11720. {
  11721. Q_OBJECT
  11722. public:
  11723. DatabaseNotification();
  11724. public slots:
  11725. void forwardNotification(const QString &notification);
  11726. signals:
  11727. void notify(const QString &notification);
  11728. };
  11729. @ The implementation is trivial.
  11730. @<Class declarations@>=
  11731. DatabaseNotification::DatabaseNotification() : QObject(NULL)
  11732. {
  11733. /* Nothing needs to be done here. */
  11734. }
  11735. void DatabaseNotification::forwardNotification(const QString &notification)
  11736. {
  11737. emit notify(notification);
  11738. }
  11739. @** Table editor for ordered arrays with SQL relations.
  11740. \noindent A database in use at Wilson's Coffee \char'046~Tea stores information
  11741. for a roasting log and uses entered information to adjust inventory tracking
  11742. tables. This roasting log connects the use of unroasted coffee with the creation
  11743. of roasted coffee. In order to support roasting coffee from more than one lot at
  11744. the same time, the columns that specify the types of coffee used and the amount
  11745. of each coffee are entered as ordered arrays in which the first entry in the
  11746. array specifying an unroasted coffee is associated with the first entry in the
  11747. array specifying the amount of coffee used. While most batches will involve only
  11748. a single unroasted coffee, the database has no limitation on the number of
  11749. coffees that may be roasted in a single batch. An additional characteristic of
  11750. this table is that the database requires an identification number for unroasted
  11751. coffee items, but it would be better to provide a list of acceptable items with
  11752. human readable names.
  11753. A scrollable area containing a table view which can provide the necessary input
  11754. delegates (such as a combo box for SQL relations) and validators which ensures
  11755. that there is always at least one empty row available for input with convenience
  11756. functions for extracting the arrays needed for database insertion would be ideal
  11757. for this.
  11758. To get this, we need a simple table model based on |QStandardItemModel|
  11759. or |QAbstractItemModel|. The model should ensure that there is always at least
  11760. one empty row available for editing. It should also provide a function for
  11761. obtaining a string that presents all values from a specified column with a given
  11762. role as an array literal suitable for binding to an SQL query.
  11763. A class based on |QComboBox| providing options selected from an SQL query will
  11764. be needed. This can be used as a standalone widget elsewhere, but here it is
  11765. also needed as an editor class for a column delegate. Another delegate class
  11766. allows input in another column to be constrained by a |QValidator| (in this case
  11767. a |QDoubleValidator|).
  11768. A class based on |QTableView| brings all of these classes together and presents
  11769. them to the user.
  11770. @* A table model for producing SQL array literals.
  11771. \noindent This is a simple table model which provides two somewhat unusual
  11772. features. First, it always provides at least one empty row at the end of the
  11773. data. Second, it provides SQL array literals for columns in the model.
  11774. \danger At some point I would like to replace this model and |MeasurementModel|
  11775. with an improved table model suitable to replace both. Some preliminary design
  11776. work suggests that this improvement simplifies \pn{} considerably both
  11777. internally and in the data flow configuration. This has not yet been done due to
  11778. development time constraints.\endanger
  11779. @<Class declarations@>=
  11780. class SaltModel : public QAbstractItemModel@/
  11781. {
  11782. Q_OBJECT@t\2\2@>@/
  11783. QList<QList<QMap<int, QVariant> > > modelData;
  11784. QStringList hData;
  11785. int colcount;@t\1\1@>@/
  11786. public:@/
  11787. SaltModel(int columns);
  11788. ~SaltModel();
  11789. Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const;
  11790. int columnCount(const QModelIndex &parent = QModelIndex()) const;
  11791. bool setHeaderData(int section, Qt::Orientation@, orientation,
  11792. const QVariant &value, int role = Qt::DisplayRole);
  11793. QVariant data(const QModelIndex &index, int role) const;
  11794. bool setData(const QModelIndex &index, const QVariant &value,
  11795. int role = Qt::EditRole);
  11796. Qt::ItemFlags@, flags(const QModelIndex &index) const;
  11797. QVariant headerData(int section, Qt::Orientation@, orientation,
  11798. int role = Qt::DisplayRole) const;
  11799. QModelIndex index(int row, int column,
  11800. const QModelIndex &parent = QModelIndex()) const;
  11801. QModelIndex parent(const QModelIndex &index) const;
  11802. QString arrayLiteral(int column, int role) const;
  11803. QString quotedArrayLiteral(int column, int role) const;
  11804. void clear();
  11805. bool removeRows(int row, int count,
  11806. const QModelIndex &parent = QModelIndex());
  11807. int findData(const QVariant &value, int column, int role = Qt::UserRole);
  11808. };
  11809. @ The only unique methods in this class are the |arrayLiteral| and
  11810. |quotedArrayLiteral| methods. These take a column number and a data role and
  11811. produce a SQL array literal for every entry in that column with the specified
  11812. role. The string will take the form of
  11813. {\tt{'\LB row 1, row 2, }}$\dots$ {\tt{row N\RB '}}.
  11814. This is done simply by starting with a string identifying the start of an array
  11815. literal, looping over the rows in the model while appending any data found along
  11816. with the commas to separate values. If any data is found, the extra comma and
  11817. space are removed from the constructed string. Finally, text marking the end of
  11818. the array literal is added.
  11819. The |arrayLiteral| method is appropriate where the expected values are numeric.
  11820. The |quotedArrayLiteral| method is appropriate where the expected values are
  11821. text. The values in the array will have quotation marks around them for the
  11822. |quotedArrayLiteral|. Note that when binding these values to placeholders in a
  11823. SQL query the leading and trailing single quote characters should be removed.
  11824. \danger The way this method is currently used is quite harmless. Data from one
  11825. column is integer data obtained as a result of a previous database query and
  11826. data from the other column is restricted by the view to numeric data. Please
  11827. note, however, that it would be extremely stupid to use code such as this when
  11828. user input cannot be controlled so tightly. Were this model used with a view
  11829. that allows general text input, it would be trivial to construct an SQL
  11830. injection attack.
  11831. \medskip
  11832. \centerline{\includegraphics[width=6in]{exploits_of_a_mom}}
  11833. \smallskip
  11834. \centerline{Figure \secno: An Example of an SQL injection attack.\nfnote{%
  11835. Comic copyright Randall Munroe. Original can be found at:~%
  11836. \pdfURL{%
  11837. http://xkcd.com/327/}%
  11838. {http://xkcd.com/327/}}}
  11839. \medskip
  11840. \endanger
  11841. @<SaltModel Implementation@>=
  11842. QString SaltModel::arrayLiteral(int column, int role) const
  11843. {
  11844. QString literal = "'{";
  11845. for(int i = 0; i < rowCount(); i++)
  11846. {
  11847. QString datum = data(index(i, column), role).toString();
  11848. if(!datum.isEmpty())
  11849. {
  11850. literal.append(datum);
  11851. literal.append(", ");
  11852. }
  11853. }
  11854. if(literal.size() > 2)
  11855. {
  11856. literal.chop(2);
  11857. }
  11858. literal.append("}'");
  11859. return literal;
  11860. }
  11861. QString SaltModel::quotedArrayLiteral(int column, int role) const
  11862. {
  11863. QString literal = "'{";
  11864. for(int i = 0; i < rowCount(); i++)
  11865. {
  11866. QString datum = data(index(i, column), role).toString();
  11867. if(!datum.isEmpty())
  11868. {
  11869. literal.append("\"");
  11870. literal.append(datum);
  11871. literal.append("\", ");
  11872. }
  11873. }
  11874. if(literal.size() > 2)
  11875. {
  11876. literal.chop(2);
  11877. }
  11878. literal.append("}'");
  11879. return literal;
  11880. }
  11881. @ No entries in this model have children.
  11882. @<SaltModel Implementation@>=
  11883. QModelIndex SaltModel::parent(const QModelIndex &) const
  11884. {
  11885. return QModelIndex();
  11886. }
  11887. @ The |setData()| method is called by delegates on views when the user enters
  11888. new data. This method must check to determine if the data is being entered in
  11889. the last row to increase the size of the table.
  11890. The end of this function may seem a little strange. Why not simply look up the
  11891. map and insert information directly into the model data? Well, as of this
  11892. writing, that doesn'@q'@>t work. There are two ways around that problem. One is to
  11893. have the lists store references and dereference the real data. The other option
  11894. is to obtain a copy of the row, then a copy of the cell, update the cell, then
  11895. replace the old value of the cell in the copy of the row, then replace the old
  11896. values of the row in the real data. The other option would probably be more
  11897. efficient, but this does work.
  11898. @<SaltModel Implementation@>=
  11899. bool SaltModel::setData(const QModelIndex &index, const QVariant &value,
  11900. int role)@t\2\2@>@/
  11901. {@t\1@>@/
  11902. @<Check that the SaltModel index is valid@>@;
  11903. if(!valid)@/
  11904. {@t\1@>@/
  11905. return false;@t\2@>@/
  11906. }
  11907. if(index.row() == modelData.size() - 1)@/
  11908. {
  11909. beginInsertRows(QModelIndex(), modelData.size(), modelData.size());
  11910. @<Expand the SaltModel@>@;
  11911. endInsertRows();
  11912. }
  11913. QList<QMap<int, QVariant> > row = modelData.at(index.row());
  11914. QMap<int, QVariant> cell = row.at(index.column());
  11915. cell.insert(role, value);
  11916. if(role == Qt::EditRole)@/
  11917. {
  11918. cell.insert(Qt::DisplayRole, value);
  11919. }
  11920. row.replace(index.column(), cell);
  11921. modelData.replace(index.row(), row);
  11922. emit dataChanged(index, index);@/
  11923. return true;@t\2@>@/
  11924. }
  11925. @ Some model operations require checking that a model index is valid. This
  11926. chunk is used in these cases.
  11927. @<Check that the SaltModel index is valid@>=
  11928. bool valid = false;@/
  11929. if(index.isValid())@/
  11930. {@t\1@>@/
  11931. if(index.row() < modelData.size())@/
  11932. {@t\1@>@/
  11933. if(index.column() < colcount)@/
  11934. {@t\1@>@/
  11935. valid = true;@t\2@>@/
  11936. }@t\2@>@/
  11937. }@t\2@>@/
  11938. }
  11939. @ When data is modified in the last row of the table, the model must be expanded
  11940. to allow for additional data.
  11941. @<Expand the SaltModel@>=
  11942. QList<QMap<int, QVariant> > newRow;
  11943. QMap<int, QVariant> defaults;
  11944. for(int i = 0; i < colcount; i++)
  11945. {
  11946. newRow.append(defaults);
  11947. }
  11948. modelData.append(newRow);
  11949. @ The number of columns in the table is specified in the model constructor.
  11950. @<SaltModel Implementation@>=
  11951. SaltModel::SaltModel(int columns) : QAbstractItemModel(), colcount(columns)
  11952. {
  11953. for(int i = 0; i < columns; i++)
  11954. {
  11955. hData << "";
  11956. }
  11957. @<Expand the SaltModel@>@;
  11958. }
  11959. @ The destructor doesn'@q'@>t need to do anything.
  11960. @<SaltModel Implementation@>=
  11961. SaltModel::~SaltModel()
  11962. {
  11963. /* Nothing needs to be done here. */
  11964. }
  11965. @ A pair of methods provide the number of rows and columns in the model. No
  11966. entries in the model have children, so the parent should always be the invisible
  11967. root object.
  11968. @<SaltModel Implementation@>=
  11969. int SaltModel::rowCount(const QModelIndex &parent) const
  11970. {
  11971. return (parent == QModelIndex() ? modelData.size() : 0);
  11972. }
  11973. int SaltModel::columnCount(const QModelIndex &parent) const
  11974. {
  11975. return (parent == QModelIndex() ? colcount : 0);
  11976. }
  11977. @ The model maintains header data for labeling the model columns.
  11978. @<SaltModel Implementation@>=
  11979. bool SaltModel::setHeaderData(int section, Qt::Orientation@, orientation,@|
  11980. const QVariant &value, int)@t\2\2@>@/
  11981. {@t\1@>@/
  11982. if(orientation == Qt::Horizontal && section < colcount)@/
  11983. {@t\1@>@/
  11984. hData.replace(section, value.toString());@/
  11985. emit headerDataChanged(orientation, section, section);@/
  11986. return true;@t\2@>@/
  11987. }@/
  11988. return false;@t\2@>@/
  11989. }
  11990. @ Views need to be able to retrieve model and header data.
  11991. @<SaltModel Implementation@>=
  11992. QVariant SaltModel::data(const QModelIndex &index, int role) const
  11993. {
  11994. @<Check that the SaltModel index is valid@>@;
  11995. if(!valid)
  11996. {
  11997. return QVariant();
  11998. }
  11999. QList<QMap<int,QVariant> > row = modelData.at(index.row());
  12000. QMap<int,QVariant> cell = row.at(index.column());
  12001. return cell.value(role, QVariant());
  12002. }
  12003. QVariant SaltModel::headerData(int section, Qt::Orientation@, orientation,
  12004. int role) const
  12005. {
  12006. if(orientation == Qt::Horizontal && role == Qt::DisplayRole &&
  12007. section < colcount)
  12008. {
  12009. return QVariant(hData.at(section));
  12010. }
  12011. return QVariant();
  12012. }
  12013. @ Views need to know certain details such as if an item in the view can be
  12014. altered by the view. For this model, all valid indices can be edited.
  12015. @<SaltModel Implementation@>=
  12016. Qt::ItemFlags SaltModel::flags(const QModelIndex &index) const
  12017. {
  12018. @<Check that the SaltModel index is valid@>@;
  12019. if(valid)
  12020. {
  12021. return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
  12022. }
  12023. return 0;
  12024. }
  12025. @ So far, many of the methods use model indices. The model is responsible for
  12026. creating these.
  12027. @<SaltModel Implementation@>=
  12028. QModelIndex SaltModel::index(int row, int column,
  12029. const QModelIndex &parent) const
  12030. {
  12031. if(parent == QModelIndex())
  12032. {
  12033. if(row < modelData.size() && column < colcount)
  12034. {
  12035. return createIndex(row, column);
  12036. }
  12037. }
  12038. return QModelIndex();
  12039. }
  12040. @ There are some times when it is useful to clear the model data. Note that
  12041. column header data is retained and the table will contain a single empty row
  12042. after this method is called.
  12043. @<SaltModel Implementation@>=
  12044. void SaltModel::clear()
  12045. {
  12046. beginResetModel();
  12047. modelData.clear();
  12048. @<Expand the SaltModel@>@;
  12049. endResetModel();
  12050. }
  12051. @ Another commonly useful operation is the ability to remove rows from the
  12052. model. The new batch window uses this feature to eliminate rows in which the
  12053. coffee is set to NULL. Note that if all rows of the model are removed, a new
  12054. empty row will be created.
  12055. @<SaltModel Implementation@>=
  12056. bool SaltModel::removeRows(int row, int count,
  12057. const QModelIndex &parent)
  12058. {
  12059. if(parent == QModelIndex())
  12060. {
  12061. if(row >= 0 && count > 0 && (row + count - 1) < modelData.size())
  12062. {
  12063. beginRemoveRows(parent, row, row + count - 1);
  12064. for(int i = 0; i < count; i++)
  12065. {
  12066. modelData.removeAt(row);
  12067. }
  12068. endRemoveRows();
  12069. if(modelData.size() == 0)
  12070. {
  12071. beginInsertRows(parent, 0, 0);
  12072. @<Expand the SaltModel@>@;
  12073. endInsertRows();
  12074. }
  12075. return @[true@];
  12076. }
  12077. }
  12078. return @[false@];
  12079. }
  12080. @ To find the row number for removal operations it is useful to search for
  12081. special values on a given role. The |findData()| method returns the first row
  12082. in which the given value matches for a particular column and a particular role
  12083. or |-1| if no such match exists.
  12084. @<SaltModel Implementation@>=
  12085. int SaltModel::findData(const QVariant &value, int column, int role)
  12086. {
  12087. for(int i = 0; i < modelData.size(); i++)
  12088. {
  12089. if(modelData.at(i).size() > column)
  12090. {
  12091. if(modelData.at(i).at(column).contains(role))
  12092. {
  12093. if(modelData.at(i).at(column).value(role) == value)
  12094. {
  12095. return i;
  12096. }
  12097. }
  12098. }
  12099. }
  12100. return -1;
  12101. }
  12102. @* A Delegate for SQL Relations.
  12103. \noindent The first column of the table view being described is responsible for
  12104. providing item numbers to the database. Requiring that these numbers be entered
  12105. directly is prone to not particularly user friendly and almost encourages input
  12106. errors. These item numbers, however, refer to the items table in the database
  12107. which includes, among other details, a human readable text string naming the
  12108. item. This delegate provides the user with a drop down menu from which such a
  12109. string may be selected with this information provided by the database itself.
  12110. When the user selects an item, it informs the model not only of the text string
  12111. in the display role, but also of the id number in a user data role which can
  12112. later be queried in order to properly craft the appropriate query.
  12113. This is implemented with two classes. The first is a |QComboBox| which queries
  12114. the database and maintains a mapping of id to text. This is made its own widget
  12115. as it is useful without being turned into a delegate. The second class provides
  12116. this widget as a delegate and handles communications between it and the model.
  12117. @<Class declarations@>=
  12118. class SqlComboBox : public QComboBox@/
  12119. {@t\1@>@/
  12120. Q_OBJECT@;
  12121. int dataColumn;
  12122. int displayColumn;
  12123. bool dataColumnShown;
  12124. QString specialNullText;
  12125. QVariant specialNullData;
  12126. public:@/
  12127. SqlComboBox();
  12128. ~SqlComboBox();
  12129. SqlComboBox* clone(QWidget *parent);@/
  12130. @t\4@>public slots@t\kern-3pt@>:@/
  12131. void addNullOption();
  12132. void addSqlOptions(QString query);
  12133. void setDataColumn(int column);
  12134. void setDisplayColumn(int column);
  12135. void showData(bool show);
  12136. void setNullText(QString nullText);
  12137. void setNullData(QVariant nullData);@t\2@>@/
  12138. }@t\kern-3pt@>;
  12139. @ In order to make this class work a little more nicely as an item delegate,
  12140. the |clone()| method has been provided to create a new object with the same
  12141. options as a prototype.
  12142. @<SqlComboBox Implementation@>=
  12143. SqlComboBox* SqlComboBox::clone(QWidget *parent)
  12144. {
  12145. SqlComboBox *widget = new SqlComboBox();
  12146. widget->setParent(parent);
  12147. for(int i = 0; i < count(); i++)
  12148. {
  12149. widget->addItem(itemText(i), itemData(i));
  12150. }
  12151. return widget;
  12152. }
  12153. @ When using this class, we must first decide if the data column is shown. If
  12154. this is desired, the entries displayed will contain both the value from the
  12155. display column followed by the value from the data column. This can be useful in
  12156. cases where the same text is used for two different items.
  12157. @<SqlComboBox Implementation@>=
  12158. void SqlComboBox::showData(bool show)
  12159. {
  12160. dataColumnShown = show;
  12161. }
  12162. @ Next, there is a need to know if the NULL value may legally be selected. Where
  12163. this is the case, we generally want this to be inserted first. As the
  12164. |QComboBox| supports storing both display and user data, much of the code is a
  12165. thin wrapper around calls to the base class. The text and data for the NULL
  12166. value can be set arbitrarily, which can be useful in certain cases. Note that
  12167. any customization of the NULL text or data must be set before a call to
  12168. |addNullOption()|.
  12169. @<SqlComboBox Implementation@>=
  12170. void SqlComboBox::addNullOption()
  12171. {
  12172. addItem(specialNullText, specialNullData);
  12173. }
  12174. void SqlComboBox::setNullText(QString nullText)
  12175. {
  12176. specialNullText = nullText;
  12177. }
  12178. void SqlComboBox::setNullData(QVariant nullData)
  12179. {
  12180. specialNullData = nullData;
  12181. }
  12182. @ Typically, the SQL query used to populate this widget will request two columns
  12183. of data. One column is used as the display data, the other as user data. This is
  12184. done to present a human readable string where a database query needs an
  12185. identification number. By default, column |0| is used in both roles. If this
  12186. is not desired, the methods to change that must be called before specifying the
  12187. query.
  12188. @<SqlComboBox Implementation@>=
  12189. void SqlComboBox::setDataColumn(int column)
  12190. {
  12191. dataColumn = column;
  12192. }
  12193. void SqlComboBox::setDisplayColumn(int column)
  12194. {
  12195. displayColumn = column;
  12196. }
  12197. @ Once the widget is properly configured, we can run the SQL query and populate
  12198. the combo box with the results.
  12199. @<SqlComboBox Implementation@>=
  12200. void SqlComboBox::addSqlOptions(QString query)
  12201. {
  12202. SqlQueryConnection h;
  12203. QSqlQuery *dbquery = h.operator->();
  12204. if(!dbquery->exec(query))
  12205. {
  12206. QSqlError error = dbquery->lastError();
  12207. qDebug() << error.databaseText();
  12208. qDebug() << error.driverText();
  12209. qDebug() << error.text();
  12210. qDebug() << dbquery->lastQuery();
  12211. /* Throw an error here, please. */
  12212. }
  12213. while(dbquery->next())
  12214. {
  12215. QString displayValue(dbquery->value(displayColumn).toString());
  12216. QString dataValue(dbquery->value(dataColumn).toString());
  12217. if(dataColumnShown)
  12218. {
  12219. displayValue.append(QString(" (%1)").arg(dataValue));
  12220. }
  12221. addItem(displayValue, dataValue);
  12222. }
  12223. }
  12224. @ The constructor initializes some private member data. A size policy is also
  12225. set on the pop up. This allows the pop up to appear wider than the combo box to
  12226. allow more data to appear. On Linux this appears to also change the text elide
  12227. mode to something that is conveniently more appropriate for the use cases in
  12228. Typica. Note that this was not enough of a change to force the pop up to be
  12229. wide enough to contain all of the text for especially long items, but if the
  12230. combo box is wide enough the pop up will match that width.
  12231. The destructor is trivial.
  12232. @<SqlComboBox Implementation@>=
  12233. SqlComboBox::SqlComboBox() :
  12234. dataColumn(0), displayColumn(0), dataColumnShown(false),
  12235. specialNullText(tr("Unknown")), specialNullData(QVariant::String)
  12236. {
  12237. view()->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
  12238. setMinimumContentsLength(20);
  12239. }
  12240. SqlComboBox::~SqlComboBox()
  12241. {
  12242. /* Nothing needs to be done here. */
  12243. }
  12244. @ To use this class as an editor delegate in a model we wrap the class in a
  12245. |QItemDelegate|.
  12246. @<Class declarations@>=
  12247. class SqlComboBoxDelegate : public QItemDelegate@/
  12248. {
  12249. Q_OBJECT@;
  12250. SqlComboBox *delegate;
  12251. public:@/
  12252. SqlComboBoxDelegate(QObject *parent = NULL);
  12253. QWidget *createEditor(QWidget *parent,
  12254. const QStyleOptionViewItem &option,@|
  12255. const QModelIndex &index) const;
  12256. void setEditorData(QWidget *editor, const QModelIndex &index) const;
  12257. void setModelData(QWidget *editor, QAbstractItemModel *model,@|
  12258. const QModelIndex &index) const;
  12259. void setWidget(SqlComboBox *widget);
  12260. virtual QSize sizeHint() const;
  12261. void updateEditorGeometry(QWidget *editor,
  12262. const QStyleOptionViewItem &option,@|
  12263. const QModelIndex &index) const;
  12264. };
  12265. @ Rather than set the values for the combo box through the delegate class, we
  12266. create the editor and pass it in to the delegate.
  12267. @<SqlComboBoxDelegate Implementation@>=
  12268. void SqlComboBoxDelegate::setWidget(SqlComboBox *widget)
  12269. {
  12270. delegate = widget;
  12271. }
  12272. @ When a view requests this delegate, we simply return the widget that was
  12273. previously passed in.
  12274. @<SqlComboBoxDelegate Implementation@>=
  12275. QWidget* SqlComboBoxDelegate::createEditor(QWidget *parent,@|
  12276. const QStyleOptionViewItem &,
  12277. const QModelIndex &) const
  12278. {
  12279. SqlComboBox *retval = delegate->clone(parent);
  12280. if(delegate->isEditable())
  12281. {
  12282. retval->setEditable(true);
  12283. }
  12284. return retval;
  12285. }
  12286. @ To set the appropriate editor data, we check the value in the model and
  12287. attempt to set the value to match that.
  12288. @<SqlComboBoxDelegate Implementation@>=
  12289. void SqlComboBoxDelegate::setEditorData(QWidget *editor,
  12290. const QModelIndex &index) const
  12291. {
  12292. SqlComboBox *self = qobject_cast<SqlComboBox *>(editor);
  12293. self->setCurrentIndex(self->findData(
  12294. index.model()->data(index,
  12295. Qt::UserRole).toString()));
  12296. }
  12297. @ When setting the model data, we need to specify both the display role and the
  12298. user data role.
  12299. @<SqlComboBoxDelegate Implementation@>=
  12300. void SqlComboBoxDelegate::setModelData(QWidget *editor,@|
  12301. QAbstractItemModel *model,
  12302. const QModelIndex &index) const
  12303. {
  12304. SqlComboBox *self = qobject_cast<SqlComboBox *>(editor);
  12305. model->setData(index, self->currentText(), Qt::DisplayRole);
  12306. model->setData(index, self->itemData(self->currentIndex(), Qt::UserRole),
  12307. Qt::UserRole);
  12308. }
  12309. @ This is needed to play nicely with the model view architecture.
  12310. @<SqlComboBoxDelegate Implementation@>=
  12311. void SqlComboBoxDelegate::updateEditorGeometry(QWidget *editor,
  12312. const QStyleOptionViewItem &option,
  12313. const QModelIndex &) const
  12314. {
  12315. editor->setGeometry(option.rect);
  12316. }
  12317. @ When this delegate is used in a table view, we want to be able to provide a
  12318. size hint that can be used to resize the column in order to fit the delegate.
  12319. @<SqlComboBoxDelegate Implementation@>=
  12320. QSize SqlComboBoxDelegate::sizeHint() const
  12321. {
  12322. return delegate->sizeHint();
  12323. }
  12324. @ Finally, we need a constructor.
  12325. @<SqlComboBoxDelegate Implementation@>=
  12326. SqlComboBoxDelegate::SqlComboBoxDelegate(QObject *parent)
  12327. : QItemDelegate(parent)@/
  12328. {
  12329. /* Nothing needs to be done here. */
  12330. }
  12331. @** The main program.
  12332. The |main()| function is where program execution starts. Most of the work
  12333. required here is taken care of for us by the |Application| object.
  12334. The odd handling of argc is required to prevent segmentation faults in the Linux
  12335. build.
  12336. @<The main program@>=
  12337. int main(int argc, char **argv)@/
  12338. {@/
  12339. int *c = &argc;
  12340. Application app(*c, argv);
  12341. QSettings settings;
  12342. @<Set up logging@>@;
  12343. @<Set up icons@>@;
  12344. @<Set up fonts@>@;
  12345. @<Register device configuration widgets@>@;
  12346. @<Prepare the database connection@>@;
  12347. @<Load the application configuration@>@;
  12348. @<Set up the scripting engine@>@;
  12349. app.engine = engine;
  12350. @<Find and evaluate starting script@>@;
  12351. int retval = app.exec();
  12352. delete engine;
  12353. return retval;@/
  12354. }
  12355. @ \pn{} 1.6.3 introduces optional logging of diagnostic messages to a file. By
  12356. default this feature is not enabled. A sensible future refinement to this would
  12357. allow specification of where this file should be created.
  12358. @<Set up logging@>=
  12359. if(settings.value("settings/advanced/logging", false).toBool())
  12360. {
  12361. qInstallMsgHandler(messageFileOutput);
  12362. }
  12363. @ This requires that we have our messageFileOutput function.
  12364. @<Logging function prototype@>=
  12365. void messageFileOutput(QtMsgType type, const char *msg);
  12366. @ The current implementation is straightforward.
  12367. @<Logging function implementation@>=
  12368. void messageFileOutput(QtMsgType, const char *msg)
  12369. {
  12370. QFile output("Typica-"+QDate::currentDate().toString("yyyy-MM-dd")+".log");
  12371. output.open(QIODevice::WriteOnly | QIODevice::Append);
  12372. QTextStream outstream(&output);
  12373. outstream << msg << "\r\n";
  12374. }
  12375. @ \pn{} 1.4 introduces the ability to use icons in certain interface elements.
  12376. Some commonly desired public domain graphics are provided by the Tango Desktop
  12377. Project. We also set an application level default window icon.
  12378. @<Set up icons@>=
  12379. QStringList themeSearchPath = QIcon::themeSearchPaths();
  12380. themeSearchPath.append(":/resources/icons/tango");
  12381. QIcon::setThemeSearchPaths(themeSearchPath);
  12382. QIcon::setThemeName(":/resources/icons/tango");
  12383. app.setWindowIcon(QIcon(":/resources/icons/appicons/logo.svg"));
  12384. @ Similarly some elements make use of a special font which is loaded from
  12385. resource data.
  12386. There has been a report of a bug which I have not been able to reproduce and
  12387. which the original reporter has not yet gotten back to me with the results of
  12388. a test, so I have opted for an alternate approach which does not preclude the
  12389. use of the earlier plan but which may solve the matter. This brings in the
  12390. TeX Gyre Pagella font and sets this as the default standard font for all web
  12391. views.
  12392. @s QFontDatabase int
  12393. @s QWebSettings int
  12394. @<Set up fonts@>=
  12395. QFile entypo(":/resources/fonts/entypo.ttf");
  12396. entypo.open(QIODevice::ReadOnly);
  12397. QFontDatabase::addApplicationFontFromData(entypo.readAll());
  12398. entypo.close();
  12399. QFontDatabase::addApplicationFont(":/resources/fonts/texgyrepagella-regular.otf");
  12400. QFontDatabase::addApplicationFont(":/resources/fonts/texgyrepagella-bold.otf");
  12401. QFontDatabase::addApplicationFont(":/resources/fonts/texgyrepagella-bolditalic.otf");
  12402. QFontDatabase::addApplicationFont(":/resources/fonts/texgyrepagella-italic.otf");
  12403. QWebSettings::globalSettings()->setFontFamily(QWebSettings::StandardFont, "Tex Gyre Pagella");
  12404. @ Some widgets provided by \pn{} require access to a database in order to work.
  12405. To simplify using these widgets, the application will request information
  12406. needed to connect to a database. The use of two distinct |if| blocks rather than
  12407. an |if|$\dots$|else| construction is used because the data from settings can be
  12408. changed if an attempt to connect to the database fails.
  12409. @<Prepare the database connection@>=
  12410. if(settings.value("database/exists", "false").toString() == "true")
  12411. {
  12412. @<Try connecting to the database@>@;
  12413. }
  12414. if(settings.value("database/exists", "false").toString() == "false")
  12415. {
  12416. @<Prompt for database connection information@>@;
  12417. }
  12418. @ In order to connect to the database, we need five pieces of information: the
  12419. name of a database driver (PostgreSQL is recommended for now), the host name of
  12420. the computer running the database, the name of the database, the name of the
  12421. user connecting to the database, and that user'@q'@>s password. This information will
  12422. be stored in the user settings for the application so that the database
  12423. connection can be established without prompting the user next time. A class is
  12424. provided to gather this information.
  12425. @<Class declarations@>=
  12426. class SqlConnectionSetup : public QDialog@/
  12427. {@t\1@>@/
  12428. Q_OBJECT@;
  12429. public:@/
  12430. SqlConnectionSetup();
  12431. ~SqlConnectionSetup();@/
  12432. @t\4@>public slots@t\kern-3pt@>:@/
  12433. void testConnection();
  12434. private:@/
  12435. QFormLayout *formLayout;
  12436. QComboBox *driver;
  12437. QLineEdit *hostname;
  12438. QLineEdit *portnumber;
  12439. QLineEdit *dbname;
  12440. QLineEdit *user;
  12441. QLineEdit *password;
  12442. QVBoxLayout *layout;
  12443. QHBoxLayout *buttons;
  12444. QPushButton *cancelButton;
  12445. QPushButton *connectButton;@t\2@>@/
  12446. }@t\kern-3pt@>;
  12447. @ The constructor sets up this widget. The destructor does nothing.
  12448. @<SqlConnectionSetup implementation@>=
  12449. SqlConnectionSetup::SqlConnectionSetup() :
  12450. formLayout(new QFormLayout), driver(new QComboBox), hostname(new QLineEdit),
  12451. portnumber(new QLineEdit),
  12452. dbname(new QLineEdit), user(new QLineEdit), password(new QLineEdit),
  12453. layout(new QVBoxLayout), buttons(new QHBoxLayout),
  12454. cancelButton(new QPushButton(tr("Cancel"))),
  12455. connectButton(new QPushButton(tr("Connect")))@/
  12456. {
  12457. QSettings settings;
  12458. driver->addItem("PostgreSQL", "QPSQL");
  12459. formLayout->addRow(tr("Database driver:"), driver);
  12460. formLayout->addRow(tr("Host name:"), hostname);
  12461. hostname->setText(settings.value("database/hostname").toString());
  12462. formLayout->addRow(tr("Port number:"), portnumber);
  12463. portnumber->setText(settings.value("database/portnumber", "5432").toString());
  12464. formLayout->addRow(tr("Database name:"), dbname);
  12465. dbname->setText(settings.value("database/dbname").toString());
  12466. formLayout->addRow(tr("User name:"), user);
  12467. user->setText(settings.value("database/user").toString());
  12468. password->setEchoMode(QLineEdit::Password);
  12469. formLayout->addRow(tr("Password:"), password);
  12470. password->setText(settings.value("database/password").toString());
  12471. layout->addLayout(formLayout);
  12472. buttons->addStretch(1);
  12473. buttons->addWidget(cancelButton);
  12474. connect(cancelButton, SIGNAL(clicked(bool)), this, SLOT(reject()));
  12475. buttons->addWidget(connectButton);
  12476. layout->addLayout(buttons);
  12477. connect(connectButton, SIGNAL(clicked(bool)), this, SLOT(testConnection()));
  12478. connectButton->setDefault(true);
  12479. setLayout(layout);
  12480. setModal(true);
  12481. }
  12482. SqlConnectionSetup::~SqlConnectionSetup()
  12483. {
  12484. /* Nothing needs to be done here. */
  12485. }
  12486. @ The |testConnection()| method checks if the information provided can be used
  12487. to open a new database connection.
  12488. @<SqlConnectionSetup implementation@>=
  12489. void SqlConnectionSetup::testConnection()
  12490. {
  12491. QSqlDatabase database =
  12492. QSqlDatabase::addDatabase(driver->itemData(driver->currentIndex()).
  12493. toString());
  12494. database.setConnectOptions("application_name=Typica");
  12495. database.setHostName(hostname->text());
  12496. database.setPort(portnumber->text().toInt());
  12497. database.setDatabaseName(dbname->text());
  12498. database.setUserName(user->text());
  12499. database.setPassword(password->text());
  12500. if(database.open())
  12501. {
  12502. QSettings settings;
  12503. settings.setValue("database/exists", "true");
  12504. settings.setValue("database/driver",
  12505. driver->itemData(driver->currentIndex()).toString());
  12506. settings.setValue("database/hostname", hostname->text());
  12507. settings.setValue("database/portnumber", portnumber->text());
  12508. settings.setValue("database/dbname", dbname->text());
  12509. settings.setValue("database/user", user->text());
  12510. settings.setValue("database/password", password->text());
  12511. AppInstance->setDatabaseConnected(true);
  12512. accept();
  12513. }
  12514. else
  12515. {
  12516. QMessageBox::information(this, tr("Database connection failed"),
  12517. tr("Failed to connect to database."));
  12518. }
  12519. }
  12520. @ In order to prompt for connection information, we simply create a
  12521. |SqlConnectionSetup| object and call |exec()|. When control returns, the
  12522. settings will either contain appropriate connection information or we have to
  12523. give up on getting that information from the user for now.
  12524. @<Prompt for database connection information@>=
  12525. SqlConnectionSetup dialog;
  12526. dialog.exec();
  12527. @ If we have connected to a database in the previous running of the application,
  12528. we try to connect to the same database automatically rather than prompt the
  12529. user. If the connection attempt fails, we can fall back on asking the user for
  12530. help.
  12531. @<Try connecting to the database@>=
  12532. QSqlDatabase database =
  12533. QSqlDatabase::addDatabase(settings.value("database/driver").toString());
  12534. database.setConnectOptions("application_name=Typica");
  12535. database.setHostName(settings.value("database/hostname").toString());
  12536. database.setPort(settings.value("database/portnumber", 5432).toInt());
  12537. database.setDatabaseName(settings.value("database/dbname").toString());
  12538. database.setUserName(settings.value("database/user").toString());
  12539. database.setPassword(settings.value("database/password").toString());
  12540. if(!database.open())
  12541. {
  12542. settings.setValue("database/exists", "false");
  12543. }
  12544. else
  12545. {
  12546. AppInstance->setDatabaseConnected(true);
  12547. }
  12548. @** Viewing a record of batches.
  12549. \noindent It is frequently useful to present a table view with the results of a
  12550. SQL query and have a way of interacting with that view to obtain more details
  12551. related to a given record in that table. For this purpose, \pn{} provides a
  12552. widget based on |QTableView| which presents information from a
  12553. |QSqlQueryModel|. The table emits signals when an entry in the table is double
  12554. clicked. One of these contains the data from the first column of that row and
  12555. is suitable for use when a primary key is presented in that column and this is
  12556. sufficient for the desired drill down. The other signal provides the row number
  12557. which can be used along with a reference to the table to obtain the data in any
  12558. column.
  12559. This class also automatically persists column widths when these are changed.
  12560. @<Class declarations@>=
  12561. class SqlQueryView : public QTableView@/
  12562. {@/
  12563. @[Q_OBJECT@]@;
  12564. public:@/
  12565. SqlQueryView(QWidget *parent = NULL);
  12566. void setQuery(const QString &query);
  12567. bool setHeaderData(int section, Qt::Orientation@, orientation,
  12568. const QVariant &value, int role);
  12569. @[Q_INVOKABLE@,@, QVariant@]@, data(int row, int column,
  12570. int role = Qt::DisplayRole);@t\2\2@>@/
  12571. signals:@/
  12572. void openEntry(QString key);
  12573. void openEntryRow(int row);
  12574. void selectEntry(QString key);
  12575. void selectEntryRow(int row);@/
  12576. protected:@/
  12577. virtual void showEvent(QShowEvent *event);@/
  12578. @[private slots@]:@/
  12579. void openRow(const QModelIndex &index);
  12580. void persistColumnResize(int column, int oldsize, int newsize);
  12581. void selectRow(const QModelIndex &index);@/
  12582. };
  12583. @ The constructor sets up the communication between the model and the view and
  12584. also provides the connection needed to notice when columns change size to
  12585. persist that preference.
  12586. @<SqlQueryView implementation@>=
  12587. SqlQueryView::SqlQueryView(QWidget *parent) : QTableView(parent)
  12588. {
  12589. setModel(new QSqlQueryModel);
  12590. connect(this, SIGNAL(doubleClicked(QModelIndex)),
  12591. this, SLOT(openRow(QModelIndex)));
  12592. connect(horizontalHeader(), SIGNAL(sectionResized(int, int, int)),
  12593. this, SLOT(persistColumnResize(int, int, int)));
  12594. connect(this, SIGNAL(activated(QModelIndex)),
  12595. this, SLOT(selectRow(QModelIndex)));
  12596. }
  12597. @ Column width persistance requires two methods. First we have a slot
  12598. method which is called when a column width is changed. This is saved with
  12599. |QSettings| under a key utilizing the name of the window, the name of the
  12600. table, and the column number.
  12601. @<SqlQueryView implementation@>=
  12602. void SqlQueryView::persistColumnResize(int column, int, int newsize)
  12603. {
  12604. @<Save updated column size@>@;
  12605. }
  12606. @ The body of this function has been split out so that it can be shared with
  12607. other table views without the need to introduce a new common base class.
  12608. @<Save updated column size@>=
  12609. QSettings settings;
  12610. @<Obtain top level widget@>@;
  12611. settings.setValue(QString("columnWidths/%1/%2/%3").
  12612. arg(topLevelWidget->objectName()).
  12613. arg(objectName()).arg(column),
  12614. QVariant(newsize));
  12615. @ To determine which window a given table is in, we just follow
  12616. |parentWidget()| until there isn'@q'@>t one. It is possible that the table view
  12617. will also be the window, however this is not advised as it is easier for the
  12618. settings key to be non-unique in such a case.
  12619. @<Obtain top level widget@>=
  12620. QWidget *topLevelWidget = this;
  12621. while(topLevelWidget->parentWidget())
  12622. {
  12623. topLevelWidget = topLevelWidget->parentWidget();
  12624. }
  12625. @ We restore column widths in response to a show event. One of these should be
  12626. received just before the widget is shown so the widget should appear correctly.
  12627. @<SqlQueryView implementation@>=
  12628. void SqlQueryView::showEvent(QShowEvent *event)
  12629. {
  12630. @<Restore table column widths@>@;
  12631. event->accept();
  12632. }
  12633. @ Similarly, most of the body of this method has also been split into a chunk
  12634. so that it might be shared with other classes.
  12635. @<Restore table column widths@>=
  12636. QSettings settings;
  12637. @<Obtain top level widget@>
  12638. QString baseKey =
  12639. QString("columnWidths/%1/%2").arg(topLevelWidget->objectName()).
  12640. arg(objectName());
  12641. for(int i = 0; i < model()->columnCount(); i++)
  12642. {
  12643. QString key = QString("%1/%2").arg(baseKey).arg(i);
  12644. if(settings.contains(key))
  12645. {
  12646. setColumnWidth(i, settings.value(key).toInt());
  12647. }
  12648. }
  12649. @ A slot is required for obtaining the information to send out in our signals.
  12650. @<SqlQueryView implementation@>=
  12651. void SqlQueryView::openRow(const QModelIndex &index)
  12652. {
  12653. emit openEntry(((QSqlQueryModel *)model())->record(index.row()).value(0).toString());
  12654. emit openEntryRow(index.row());
  12655. }
  12656. void SqlQueryView::selectRow(const QModelIndex &index)
  12657. {
  12658. emit selectEntry(((QSqlQueryModel *)model())->record(index.row()).value(0).toString());
  12659. emit selectEntryRow(index.row());
  12660. }
  12661. @ The other functions are wrappers around model methods.
  12662. @<SqlQueryView implementation@>=
  12663. void SqlQueryView::setQuery(const QString &query)
  12664. {
  12665. QSqlDatabase database = AppInstance->database();
  12666. database.open();
  12667. QSqlQuery q(query, database);
  12668. ((QSqlQueryModel*)model())->setQuery(q);
  12669. database.close();
  12670. }
  12671. bool SqlQueryView::setHeaderData(int section, Qt::Orientation@, orientation,
  12672. const QVariant &value, int role)
  12673. {
  12674. return model()->setHeaderData(section, orientation, value, role);
  12675. }
  12676. @ A method is also provided to allow scripts to access the data.
  12677. @<SqlQueryView implementation@>=
  12678. QVariant SqlQueryView::data(int row, int column, int role)
  12679. {
  12680. return model()->data(model()->index(row, column), role);
  12681. }
  12682. @ To use this class, it is useful to expose it to the host environment.
  12683. @<Function prototypes for scripting@>=
  12684. void setSqlQueryViewProperties(QScriptValue value, QScriptEngine *engine);
  12685. QScriptValue constructSqlQueryView(QScriptContext *context,
  12686. QScriptEngine *engine);
  12687. QScriptValue SqlQueryView_setQuery(QScriptContext *context,
  12688. QScriptEngine *engine);
  12689. QScriptValue SqlQueryView_setHeaderData(QScriptContext *context,
  12690. QScriptEngine *engine);
  12691. QScriptValue SqlQueryView_rows(QScriptContext *context,
  12692. QScriptEngine *engine);
  12693. @ The script constructor is passed to the host environment.
  12694. @<Set up the scripting engine@>=
  12695. constructor = engine->newFunction(constructSqlQueryView);
  12696. value = engine->newQMetaObject(&SqlQueryView::staticMetaObject, constructor);
  12697. engine->globalObject().setProperty("SqlQueryView", value);
  12698. @ Next we construct the view, add properties to access its methods from the host
  12699. environment, and pass that back.
  12700. @<Functions for scripting@>=
  12701. QScriptValue constructSqlQueryView(QScriptContext *, QScriptEngine *engine)
  12702. {
  12703. QScriptValue object = engine->newQObject(new SqlQueryView);
  12704. setSqlQueryViewProperties(object, engine);
  12705. return object;
  12706. }
  12707. void setSqlQueryViewProperties(QScriptValue value, QScriptEngine *engine)
  12708. {
  12709. setQTableViewProperties(value, engine);
  12710. value.setProperty("setHeaderData",
  12711. engine->newFunction(SqlQueryView_setHeaderData));
  12712. value.setProperty("setQuery", engine->newFunction(SqlQueryView_setQuery));
  12713. value.setProperty("rows", engine->newFunction(SqlQueryView_rows));
  12714. }
  12715. @ The properties added are simplified wrappers around the class methods.
  12716. @<Functions for scripting@>=
  12717. QScriptValue SqlQueryView_setQuery(QScriptContext *context, QScriptEngine *)
  12718. {
  12719. SqlQueryView *self = getself<SqlQueryView *>(context);
  12720. QString query = argument<QString>(0, context);
  12721. self->setQuery(query);
  12722. self->reset();
  12723. return QScriptValue();
  12724. }
  12725. QScriptValue SqlQueryView_setHeaderData(QScriptContext *context,
  12726. QScriptEngine *)
  12727. {
  12728. SqlQueryView *self = getself<SqlQueryView *>(context);
  12729. int section = argument<int>(0, context);
  12730. QString data = argument<QString>(1, context);
  12731. self->setHeaderData(section, Qt::Horizontal, data, Qt::DisplayRole);
  12732. return QScriptValue();
  12733. }
  12734. QScriptValue SqlQueryView_rows(QScriptContext *context, QScriptEngine *)
  12735. {
  12736. SqlQueryView *self = getself<SqlQueryView *>(context);
  12737. return QScriptValue(self->model()->rowCount());
  12738. }
  12739. @** Reporting.
  12740. \noindent \pn{} version 1.4 added a new type of menu which is designed to
  12741. handle reports. This makes extensive use of the previously existing reporting
  12742. system at present which makes modifying existing reports to work with the new
  12743. system very simple. Further changes may be introduced in the future that
  12744. substantially depart from this in order to simplify report files.
  12745. Previously to add a new report to a configuration, you needed to create the
  12746. report, add an {\tt <include>} tag in the main configuration file to bring that
  12747. report into the application configuration, then in any window with a Reports
  12748. menu you would need to add the report to that menu in its configuration file
  12749. and write a small bit of JavaScript to obtain a reference to that new menu
  12750. item and create the report when that menu item is triggered. This is highly
  12751. repetetive, error prone, and with the new approach it is not needed at all.
  12752. To add a new report to a configuration using the new approach one need only
  12753. save the new report file in the appropriate directory and \pn{} will detect
  12754. this, add it to any Reports menus that may exist, and handle all of the details
  12755. of generating these reports on demand.
  12756. The Reports menu is created in a configuration as a {\tt <menu>} element with
  12757. three attributes. The {\tt name} attribute as usual is the name of the menu
  12758. item. The {\tt type} attribute will have a value of {\tt "reports"} and the
  12759. {\tt src} attribute will indicate the directory to search for reports to
  12760. populate that menu. This allows for multiple Reports menus with different
  12761. reports in each menu if desired.
  12762. Reports are added to the menu in the order of the file names in the reports
  12763. directory.
  12764. \danger While it should not be an issue with the limited number of reports
  12765. presently distributed with Typica, the approach taken to implementing this menu
  12766. type is highly inefficient. There are many optimizations available if this
  12767. becomes problematic.\endanger
  12768. When a report menu is generated, the directory used for this is added as a
  12769. search path for the |"reports"| prefix. This is used by the |createReport()|
  12770. script method and is intended to allow access to reports from outside of the
  12771. Report menu.
  12772. @<Populate reports menu@>=
  12773. QSettings settings;
  12774. QString reportDirectory = QString("%1/%2").arg(settings.value("config").
  12775. toString()).
  12776. arg(element.attribute("src"));
  12777. QDir::addSearchPath("reports", reportDirectory);
  12778. QDir directory(reportDirectory);
  12779. directory.setFilter(QDir::Files);
  12780. directory.setSorting(QDir::Name);
  12781. QStringList nameFilter;
  12782. nameFilter << "*.xml";
  12783. directory.setNameFilters(nameFilter);
  12784. QFileInfoList reportFiles = directory.entryInfoList();
  12785. for(int i = 0; i < reportFiles.size(); i++)
  12786. {
  12787. QFileInfo reportFile = reportFiles.at(i);
  12788. @<Add report to reports menu@>@;
  12789. }
  12790. @ The menu items themselves are a subclass of |QAction| which holds all of the
  12791. information needed to respond to its activation by generating the appropriate
  12792. report.
  12793. @<Class declarations@>=
  12794. class ReportAction : public QAction@/
  12795. {@/
  12796. @[Q_OBJECT@]@;
  12797. public:@/
  12798. ReportAction(const QString &fileName, const QString &reportName,
  12799. QObject *parent = NULL);@/
  12800. @[private slots@]:@/
  12801. void createReport();@/
  12802. private:@/
  12803. QString reportFile;@/
  12804. };
  12805. @ The constructor receives the name of the report file which is used to
  12806. generate the report when needed and the name of the report which is used as the
  12807. name presented in the menu.
  12808. @<ReportAction implementation@>=
  12809. ReportAction::ReportAction(const QString &fileName, const QString &reportName,
  12810. QObject *parent) :
  12811. QAction(reportName, parent), reportFile(fileName)
  12812. {
  12813. connect(this, SIGNAL(triggered()), this, SLOT(createReport()));
  12814. }
  12815. @ The slot method is responsible for creating the new report. This is very
  12816. similar to the old approach and reuses much of the same code. Of particular
  12817. note is the |targetID| variable. This is set to facilitate window geometry
  12818. management, though this should probably be set from the {\tt id} attribute
  12819. of the {\tt <window>} element in the file to preserve window geometry
  12820. settings if the configuration is moved to another location in the file
  12821. system.
  12822. @<ReportAction implementation@>=
  12823. void ReportAction::createReport()
  12824. {
  12825. QFile file(reportFile);
  12826. QDomDocument document;
  12827. if(file.open(QIODevice::ReadOnly))
  12828. {
  12829. document.setContent(&file, true);
  12830. QDomElement element = document.documentElement();
  12831. QScriptEngine *engine = AppInstance->engine;
  12832. QScriptContext *context = engine->pushContext();
  12833. QScriptValue object;
  12834. QString targetID = reportFile;
  12835. @<Display the window@>@;
  12836. file.close();
  12837. engine->popContext();
  12838. }
  12839. }
  12840. @ With the |ReportAction| available, we are now ready to add reports to the
  12841. Reports menu. To do this we check each file in the given directory to determine
  12842. if it is a report file, obtain the report title and location within the menu
  12843. hierarchy from the file data, create the actions, and add them to the menu.
  12844. @<Add report to reports menu@>=
  12845. QString path = reportFile.absoluteFilePath();
  12846. QFile file(path);
  12847. if(file.open(QIODevice::ReadOnly))
  12848. {
  12849. QDomDocument document;
  12850. document.setContent(&file, true);
  12851. QDomElement root = document.documentElement();
  12852. QString translationContext = root.attribute("id");
  12853. QDomNode titleNode = root.elementsByTagName("reporttitle").at(0);
  12854. if(!titleNode.isNull())
  12855. {
  12856. QDomElement titleElement = titleNode.toElement();
  12857. QString title = QCoreApplication::translate("configuration",
  12858. titleElement.text().toUtf8().data());
  12859. if(!title.isEmpty())
  12860. {
  12861. QStringList hierarchy = title.split(":->");
  12862. QMenu *insertionPoint = menu;
  12863. @<Traverse report menu hierarchy@>
  12864. ReportAction *action = new ReportAction(path, hierarchy.last());
  12865. insertionPoint->addAction(action);
  12866. }
  12867. }
  12868. }
  12869. @ \pn{} allows the Reports menu to contain arbitrarily deep menu hierarchies.
  12870. It is advised to keep these hierarchies shallow.
  12871. @<Traverse report menu hierarchy@>=
  12872. for(int j = 0; j < hierarchy.size() - 1; j++)
  12873. {
  12874. QObjectList menuList = insertionPoint->children();
  12875. bool menuFound = @[false@];
  12876. for(int k = 0; k < menuList.size(); k++)
  12877. {
  12878. QMenu *currentItem = qobject_cast<QMenu*>(menuList.at(k));
  12879. if(currentItem)
  12880. {
  12881. if(currentItem->title() == hierarchy.at(j))
  12882. {
  12883. menuFound = @[true@];
  12884. insertionPoint = currentItem;
  12885. break;
  12886. }
  12887. }
  12888. }
  12889. if(!menuFound)
  12890. {
  12891. insertionPoint = insertionPoint->addMenu(hierarchy.at(j));
  12892. }
  12893. }
  12894. @ \noindent The reporting functionality in \pn{} is based on the Scribe framework
  12895. in Qt. This brings several benefits, including making it easy to print reports
  12896. or save reports as plain text or HTML.
  12897. Reports are specified in the \pn{}'@q'@>s configuration document and can include both
  12898. static elements and elements that are populated by external data such as the
  12899. result of a SQL query.
  12900. @<Function prototypes for scripting@>=
  12901. void addReportToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  12902. QStack<QLayout *> *layoutStack);
  12903. @ When adding a report to a layout, we must not only add the widget to the
  12904. layout, but also construct the document.
  12905. @<Functions for scripting@>=
  12906. void addReportToLayout(QDomElement element, QStack<QWidget *> *,@|
  12907. QStack<QLayout *> *layoutStack)
  12908. {
  12909. QTextEdit *widget = new QTextEdit;
  12910. if(element.hasAttribute("id"))
  12911. {
  12912. widget->setObjectName(element.attribute("id"));
  12913. }
  12914. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  12915. layout->addWidget(widget);
  12916. QTextDocument *document = new QTextDocument;
  12917. QFont defaultFont;
  12918. defaultFont.setPointSize(11);
  12919. document->setDefaultFont(defaultFont);
  12920. QTextCursor cursor(document);
  12921. @<Construct report document@>@;
  12922. widget->setDocument(document);
  12923. }
  12924. @ Several child elements are allowed under the {\tt <report>} element. These
  12925. should be processed in order to produce the final report document.
  12926. @<Construct report document@>=
  12927. QDomNodeList children = element.childNodes();
  12928. for(int i = 0; i < children.count(); i++)
  12929. {
  12930. QDomNode current;
  12931. QDomElement currentElement;
  12932. current = children.at(i);
  12933. if(current.isElement())
  12934. {
  12935. currentElement = current.toElement();
  12936. @<Process report document elements@>@;
  12937. }
  12938. }
  12939. @ If any custom styling for HTML content is required, a {\tt <style>} element
  12940. should be placed in the report description before any such content.
  12941. @<Process report document elements@>=
  12942. if(currentElement.tagName() == "style")
  12943. {
  12944. document->setDefaultStyleSheet(currentElement.text());
  12945. }
  12946. @ One common need is the ability to insert static text, such as the title of the
  12947. report. In order to simplify formatting, the text can be interpreted as HTML.
  12948. Note that to avoid having HTML tags eaten by the parser, the text of this
  12949. element should be a CDATA section.
  12950. @<Process report document elements@>=
  12951. if(currentElement.tagName() == "html")
  12952. {
  12953. cursor.insertHtml(currentElement.text());
  12954. }
  12955. @ If no special formatting is needed, a plain text element can be used. This
  12956. might be extended in the future to allow attributes for specifying the character
  12957. formatting to be used with the text.
  12958. @<Process report document elements@>=
  12959. if(currentElement.tagName() == "text")
  12960. {
  12961. cursor.insertText(currentElement.text());
  12962. }
  12963. @ One of the more interesting elements of a report is the {\tt <table>} element.
  12964. This is an element which can change its contents in response to changes in user
  12965. controls.
  12966. @<Process report document elements@>=
  12967. if(currentElement.tagName() == "table")
  12968. {
  12969. QTextFrame *frame = cursor.insertFrame(QTextFrameFormat());
  12970. ReportTable *table = new ReportTable(frame, currentElement);
  12971. table->setParent(widget);
  12972. if(currentElement.hasAttribute("id"))
  12973. {
  12974. table->setObjectName(currentElement.attribute("id"));
  12975. }
  12976. }
  12977. @ The |ReportTable| class is responsible for parsing {\tt <table>} child
  12978. elements and inserting the table into the document at the correct location.
  12979. @<Class declarations@>=
  12980. class ReportTable : public QObject@/
  12981. {@t\1@>@/
  12982. Q_OBJECT@;
  12983. QTextFrame *area;
  12984. QDomElement configuration;
  12985. QMap<QString, QVariant> bindings;
  12986. public:@/
  12987. ReportTable(QTextFrame *frame, QDomElement description);
  12988. ~ReportTable();
  12989. @[Q_INVOKABLE@,@, void@]@, bind(QString placeholder, QVariant value);@t\2\2@>@/
  12990. @t\4@>public slots@t\kern-3pt@>:@/
  12991. void refresh();@t\2@>@/
  12992. }@t\kern-3pt@>;
  12993. @ The |ReportTable| class takes a |QTextFrame| and |QDomElement| pointer in
  12994. its constructor. The former is used to establish the bounds of the table within
  12995. a document and the latter is used for generating the table contents.
  12996. @<ReportTable implementation@>=
  12997. ReportTable::ReportTable(QTextFrame *frame, QDomElement description) :
  12998. area(frame), configuration(description)
  12999. {
  13000. refresh();
  13001. }
  13002. ReportTable::~ReportTable()
  13003. {
  13004. }
  13005. @ In order to change the table contents based on user controls, the |bind()|
  13006. method allows a placeholder to be replaced with a value when evaluating a SQL
  13007. query.
  13008. @<ReportTable implementation@>=
  13009. void ReportTable::bind(QString placeholder, QVariant value)
  13010. {
  13011. bindings.insert(placeholder, value);
  13012. }
  13013. @ All of the interesting work is done in the |refresh()| slot. This method
  13014. deletes the current content of the frame and creates a table based on the
  13015. description of the table in the configuration document.
  13016. @<ReportTable implementation@>=
  13017. void ReportTable::refresh()
  13018. {
  13019. @<Delete current report table content@>@;
  13020. int rows = 1;
  13021. int columns = 1;
  13022. int currentRow = 0;
  13023. QTextTable *table = cursor.insertTable(rows, columns);
  13024. @<Set table formatting@>@;
  13025. @<Reconstruct report table content@>@;
  13026. if(rows > 1)
  13027. {
  13028. table->removeRows(0, 1);
  13029. }
  13030. }
  13031. @ Deleting the current content of the table involves using a cursor to select
  13032. everything in the frame and then removing that selection. There are more optimal
  13033. ways to do this but if there are performance problems with this, you may want to
  13034. reconsider what you are trying to do.
  13035. @<Delete current report table content@>=
  13036. QTextCursor cursor = area->firstCursorPosition();
  13037. while(cursor < area->lastCursorPosition())
  13038. {
  13039. cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
  13040. }
  13041. cursor.removeSelectedText();
  13042. @ When creating a new table, we may need to alter the formatting of that table.
  13043. To do this, we get the current format, modify that based on attributes of the
  13044. {\tt <table>} element, and apply the modified copy to the newly constructed
  13045. table.
  13046. @<Set table formatting@>=
  13047. QTextTableFormat format = table->format();
  13048. format.setBorderStyle(QTextFrameFormat::BorderStyle_None);
  13049. if(configuration.hasAttribute("align"))
  13050. {
  13051. if(configuration.attribute("align") == "center")
  13052. {
  13053. format.setAlignment(Qt::AlignHCenter);
  13054. }
  13055. }
  13056. table->setFormat(format);
  13057. @ To reconstruct the table, we need to parse the description of the table.
  13058. @<Reconstruct report table content@>=
  13059. QDomNodeList children = configuration.childNodes();
  13060. for(int i = 0; i < children.count(); i++)
  13061. {
  13062. QDomNode current;
  13063. QDomElement currentElement;
  13064. current = children.at(i);
  13065. if(current.isElement())
  13066. {
  13067. currentElement = current.toElement();
  13068. if(currentElement.tagName() == "query")
  13069. {
  13070. @<Add SQL query results to report table@>@;
  13071. }
  13072. else if(currentElement.tagName() == "row")
  13073. {
  13074. @<Add new row to report table@>@;
  13075. }
  13076. }
  13077. }
  13078. @ The text of a {\tt <query>} element will be the query desired in the table.
  13079. This might include placeholders that must be bound to values before the query is
  13080. executed. If query execution results in an error (as it will if it contains
  13081. placeholders that have not yet had values bound to them), there will be no
  13082. change to the table and the next child element, if any, will be processed.
  13083. @<Add SQL query results to report table@>=
  13084. SqlQueryConnection h;
  13085. QSqlQuery *query = h.operator->();
  13086. query->prepare(currentElement.text());
  13087. foreach(QString key, bindings.uniqueKeys())
  13088. {
  13089. if(currentElement.text().contains(key))
  13090. {
  13091. query->bindValue(key, bindings.value(key));
  13092. }
  13093. }
  13094. query->exec();
  13095. if(!query->next())
  13096. {
  13097. continue;
  13098. }
  13099. if(query->record().count() > columns)
  13100. {
  13101. table->appendColumns(query->record().count() - columns);
  13102. }
  13103. do
  13104. {
  13105. table->appendRows(1);
  13106. rows++;
  13107. currentRow++;
  13108. for(int j = 0; j < query->record().count(); j++)
  13109. {
  13110. QTextTableCell cell = table->cellAt(currentRow, j);
  13111. cursor = cell.firstCursorPosition();
  13112. cursor.insertText(query->value(j).toString());
  13113. }
  13114. } while(query->next());
  13115. @ It is sometimes desirable to add fixed data such as column headers to a table.
  13116. This is done with the {\tt <row>} element.
  13117. Technically, this isn'@q'@>t needed. The same results can be produced by using a
  13118. {\tt <query>} element to select constant data, but this approach saves a trip to
  13119. the database.
  13120. @<Add new row to report table@>=
  13121. table->appendRows(1);
  13122. currentRow++;
  13123. rows++;
  13124. QDomNodeList rowChildren = currentElement.childNodes();
  13125. int currentColumn = 0;
  13126. for(int j = 0; j < rowChildren.count(); j++)
  13127. {
  13128. QDomNode node;
  13129. QDomElement nodeElement;
  13130. node = rowChildren.at(j);
  13131. if(node.isElement())
  13132. {
  13133. nodeElement = node.toElement();
  13134. if(nodeElement.tagName() == "cell")
  13135. {
  13136. if(currentColumn == columns)
  13137. {
  13138. table->appendColumns(1);
  13139. columns++;
  13140. }
  13141. QTextTableCell cell = table->cellAt(currentRow, currentColumn);
  13142. cursor = cell.firstCursorPosition();
  13143. cursor.insertText(nodeElement.text());
  13144. currentColumn++;
  13145. }
  13146. }
  13147. }
  13148. @ In order to expose report printing capabilities, we provide a property on
  13149. |QTextEdit| objects to handle this.
  13150. @<Function prototypes for scripting@>=
  13151. void setQTextEditProperties(QScriptValue value, QScriptEngine *engine);
  13152. QScriptValue QTextEdit_print(QScriptContext *context, QScriptEngine *engine);
  13153. @ This function is a trivial adaptation from the Qt documentation.
  13154. @<Functions for scripting@>=
  13155. QScriptValue QTextEdit_print(QScriptContext *context, QScriptEngine *)
  13156. {
  13157. QTextEdit *self = getself<QTextEdit *>(context);
  13158. QTextDocument *document = self->document();
  13159. QPrinter printer;
  13160. QPrintDialog printwindow(&printer, self);
  13161. if(printwindow.exec() != QDialog::Accepted)
  13162. {
  13163. return QScriptValue();
  13164. }
  13165. document->print(&printer);
  13166. return QScriptValue();
  13167. }
  13168. @ The host environment must be informed of this function.
  13169. @<Functions for scripting@>=
  13170. void setQTextEditProperties(QScriptValue value, QScriptEngine *engine)
  13171. {
  13172. setQAbstractScrollAreaProperties(value, engine);
  13173. value.setProperty("print", engine->newFunction(QTextEdit_print));
  13174. }
  13175. @i plugins.w
  13176. @i daterangeselector.w
  13177. @** An area for repeated user interface elements.
  13178. \noindent There are multiple use cases in which it is useful to specify a
  13179. complex aggregation of user interface elements to be repeated arbitrarily many
  13180. times. For example, placing multiple copies of a cupping form in a single area
  13181. for conveniently entering observations for all coffees in a particular session
  13182. or providing any number of copies of the form for entering coffee purchase
  13183. information. The |FormArray| widget provides this capability, allowing the
  13184. XML portion of the configuration document to specify the form once and allowing
  13185. the host environment to access the copies.
  13186. Slots and the |Q_INVOKABLE| macro are used to simplify the use of this class
  13187. from the host environment.
  13188. @<Class declarations@>=
  13189. class FormArray : public QScrollArea@/
  13190. {@t\1@>@/
  13191. Q_OBJECT@;
  13192. QDomElement configuration;
  13193. QWidget itemContainer;
  13194. QVBoxLayout itemLayout;
  13195. int maxwidth;
  13196. int maxheight;
  13197. public:@/
  13198. FormArray(QDomElement description);
  13199. @[Q_INVOKABLE@,@, QWidget*@] elementAt(int index);@t\2\2@>@/
  13200. @[Q_INVOKABLE@,@, int@] elements();@t\2\2@>@/
  13201. @t\4@>public slots@t\kern-3pt@>:@/
  13202. void addElements(int copies = 1);
  13203. void removeAllElements();
  13204. void setMaximumElementWidth(int width);
  13205. void setMaximumElementHeight(int height);@t\2@>@/
  13206. }@t\kern-3pt@>;
  13207. @ The |FormArray| is just a |QScrollArea| providing a view onto a |QWidget|
  13208. containing a layout which has arbitrarily many copies of a |QWidget| with
  13209. contents determined by the configuration document used to create the
  13210. |FormArray|.
  13211. @<FormArray implementation@>=
  13212. FormArray::FormArray(QDomElement description) : configuration(description),
  13213. maxwidth(-1), maxheight(-1)@/
  13214. {
  13215. setWidget(&itemContainer);
  13216. itemContainer.setLayout(&itemLayout);
  13217. }
  13218. @ The |FormArray| was initially created by an XML element. A copy of this is
  13219. stored in the private variable |configuration|. This can have the same child
  13220. elements as {\tt <widget>}, allowing us to reuse the function for creating
  13221. populating the widget. When adding a new element, we must resize the
  13222. |itemContainer|, otherwise Qt will attempt to cram all widgets in the layout
  13223. into the same vertical space as was previously required. The result is not
  13224. attractive. We also set a minimum width just in case the newly created widget is
  13225. the first one added to the area.
  13226. @<FormArray implementation@>=
  13227. void FormArray::addElements(int copies)
  13228. {
  13229. QStack<QWidget *> *widgetStack = new QStack<QWidget *>;
  13230. QStack<QLayout *> *layoutStack = new QStack<QLayout *>;
  13231. QWidget *widget;
  13232. for(int i = 0; i < copies; i++)
  13233. {
  13234. widget = new QWidget;
  13235. if(maxwidth > -1)
  13236. {
  13237. widget->setMaximumWidth(maxwidth);
  13238. }
  13239. if(maxheight > -1)
  13240. {
  13241. widget->setMaximumHeight(maxheight);
  13242. }
  13243. if(configuration.hasChildNodes())
  13244. {
  13245. widgetStack->push(widget);
  13246. populateWidget(configuration, widgetStack, layoutStack);
  13247. widgetStack->pop();
  13248. widget->setMinimumHeight(widget->sizeHint().height());
  13249. itemLayout.addWidget(widget);
  13250. if(widget->sizeHint().height() > maxheight && maxheight > -1)
  13251. {
  13252. itemContainer.setMinimumHeight(maxheight * elements() + 50);
  13253. }
  13254. else
  13255. {
  13256. itemContainer.setMinimumHeight(itemContainer.sizeHint().height()
  13257. + widget->sizeHint().height());
  13258. }
  13259. if(maxwidth > -1)
  13260. {
  13261. itemContainer.setMinimumWidth(maxwidth + 50);
  13262. }
  13263. else
  13264. {
  13265. itemContainer.setMinimumWidth(widget->sizeHint().width() + 50);
  13266. }
  13267. }
  13268. }
  13269. }
  13270. @ In order to retrieve a widget from the area, we use the |elementAt()| method.
  13271. The pointer returned by this function can be used as the first argument to
  13272. |findChildObject()| in the host environment in order to find any widget in the
  13273. form.
  13274. @<FormArray implementation@>=
  13275. QWidget* FormArray::elementAt(int index)
  13276. {
  13277. if(index < itemLayout.count())
  13278. {
  13279. QLayoutItem *item = itemLayout.itemAt(index);
  13280. return item->widget();
  13281. }
  13282. else
  13283. {
  13284. return NULL;
  13285. }
  13286. }
  13287. @ Removing all elements is trivial, however we must be sure to reset the size of
  13288. |itemContainer|.
  13289. @<FormArray implementation@>=
  13290. void FormArray::removeAllElements()
  13291. {
  13292. while(itemLayout.count() > 0)
  13293. {
  13294. QLayoutItem *item;
  13295. item = itemLayout.itemAt(0);
  13296. item->widget()->hide();
  13297. itemLayout.removeWidget(item->widget());
  13298. }
  13299. itemContainer.setMinimumHeight(0);
  13300. }
  13301. @ This just leaves a method for determining the number of elements already in
  13302. the view. This is equal to the number of items in the layout.
  13303. @<FormArray implementation@>=
  13304. int FormArray::elements()
  13305. {
  13306. return itemLayout.count();
  13307. }
  13308. @ Some widgets do not behave well in a |FormArray| setting and will try to use
  13309. an excess of screen space. In these cases, constraining the size of the elements
  13310. can be beneficial. These just set private member variables which are used when
  13311. adding new elements.
  13312. @<FormArray implementation@>=
  13313. void FormArray::setMaximumElementWidth(int width)
  13314. {
  13315. maxwidth = width;
  13316. }
  13317. void FormArray::setMaximumElementHeight(int height)
  13318. {
  13319. maxheight = height;
  13320. }
  13321. @ In order to create an instance of this class from the configuration document,
  13322. a {\tt <formarray>} element is used. This can be added to any layout.
  13323. @<Function prototypes for scripting@>=
  13324. void addFormArrayToLayout(QDomElement element, QStack<QWidget *> *widgetStack,@|
  13325. QStack<QLayout *> *layoutStack);
  13326. @ Processing child elements is deferred until a call to
  13327. |FormArray::addElements()| has been made.
  13328. @<Functions for scripting@>=
  13329. void addFormArrayToLayout(QDomElement element, QStack<QWidget *> *,@|
  13330. QStack<QLayout *> *layoutStack)
  13331. {
  13332. FormArray *widget = new FormArray(element);
  13333. if(element.hasAttribute("id"))
  13334. {
  13335. widget->setObjectName(element.attribute("id"));
  13336. }
  13337. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  13338. layout->addWidget(widget);
  13339. }
  13340. @** Scale widgets.
  13341. \noindent One of the most commonly used methods of documenting the properties of
  13342. a coffee is through a cupping form. Several such forms exist to meet different
  13343. needs, however most involve several scales which can be marked to determine some
  13344. aspect of a particular attribute. Some of these scales are scored values which,
  13345. when considered with other scored values produce a numerical representation of
  13346. the quality of a given coffee. Others are unscored values which serve to provide
  13347. additional documentation of a property. For example, when the SCAA cupping form
  13348. expanded to the current form of ten scored properties with defects subtracted
  13349. from the total, a second scale was added for the acidity property. The scored
  13350. scale is used for marking the quality of the acidity while another unscored
  13351. scale is used for marking the intensity of that acidity.
  13352. Previously, in order to enter cupping information in \pn{}, numeric entry fields
  13353. were used. While this was very efficient for transcribing paper cupping forms
  13354. (and this was, in fact, the principal use case for our prototype cupping form
  13355. database software), it is not a form that lends itself to convenient use at the
  13356. cupping table.
  13357. Two new widgets are therefore introduced which allows a cupper to simply click
  13358. at some point on the scale to record that impression. Unfortunately, this is
  13359. still something of a tradeoff. It is not quite so efficient as using a paper
  13360. form in my experience, however it is faster than transcribing a stack of cupping
  13361. forms, particularly when working with cuppers with ambiguous handwriting. By
  13362. moving data acquisition to the point of data generation, a more useful record
  13363. can be produced for use in aggregate analysis.
  13364. @* The Horizontal Scale.
  13365. \noindent Several cupping forms make use of 10 point scales for the quality of
  13366. various attributes. The |ScaleControl| widget provides such a scale. It
  13367. consists of a bar with major ticks at 0, 5, and 10 and minor ticks at integer
  13368. values within this range. The first click sets both the initial and final value
  13369. of the scale while subsequent clicks adjust only the final value. A pair of
  13370. controls at each end of the scale allows the user to adjust either of these
  13371. values to compensate for imprecision at the point of the click. The two values,
  13372. an initial unscored value and a final scored value, provide some limited
  13373. temporal documentation. That is, it documents how the perception of a coffee
  13374. changes as it cools.
  13375. The widget is implemented as a |QGraphicsView| subclass. Please note that the
  13376. scale widgets are not particularly robust. In order to support a broader range
  13377. of cupping forms, there are plans to extend this class to allow user defined
  13378. range and tick patterns and user defined colors for the indicators.
  13379. @<Class declarations@>=
  13380. class ScaleControl : public QGraphicsView@/
  13381. {@t\1@>@/
  13382. Q_OBJECT@/
  13383. Q_PROPERTY(double initialValue READ initialValue WRITE setInitialValue)@/
  13384. Q_PROPERTY(double finalValue READ finalValue WRITE setFinalValue)@/
  13385. @<ScaleControl private members@>@t\2\2@>@/
  13386. public:@/
  13387. ScaleControl();
  13388. double initialValue(void);
  13389. double finalValue(void);
  13390. virtual QSize sizeHint() const;@/
  13391. @[public slots@]:@/
  13392. void setInitialValue(double value);
  13393. void setFinalValue(double value);@/
  13394. signals:@/
  13395. void initialChanged(double);
  13396. void finalChanged(double);@/
  13397. protected:@/
  13398. virtual void mousePressEvent(QMouseEvent *event);
  13399. virtual void mouseReleaseEvent(QMouseEvent *event);@t\2@>@/
  13400. }@t\kern-4pt@>;
  13401. @ The private variables available to instances of this class are used for
  13402. managing various aspects of the widget.
  13403. @<ScaleControl private members@>=
  13404. QGraphicsScene scene;
  13405. QGraphicsPolygonItem initialDecrement;
  13406. QGraphicsPolygonItem initialIncrement;
  13407. QGraphicsPolygonItem finalDecrement;
  13408. QGraphicsPolygonItem finalIncrement;
  13409. QGraphicsPolygonItem initialIndicator;
  13410. QGraphicsPolygonItem finalIndicator;
  13411. QGraphicsPathItem scaleLine;
  13412. QPolygonF left;
  13413. QPolygonF right;
  13414. QPolygonF down;
  13415. QPolygonF up;
  13416. QPainterPath scalePath;
  13417. QBrush initialBrush;
  13418. QBrush finalBrush;
  13419. double nonScoredValue;
  13420. double scoredValue;
  13421. bool initialSet;
  13422. bool finalSet;
  13423. bool scaleDown;
  13424. @ The constructor sets up the scene displayed by this widget. There is
  13425. considerable room for improvement here.
  13426. @<ScaleControl implementation@>=
  13427. ScaleControl::ScaleControl() : QGraphicsView(NULL, NULL), nonScoredValue(-1),
  13428. scoredValue(-1), initialSet(false), finalSet(false), scaleDown(false)
  13429. {
  13430. left << QPointF(0, 5) << QPointF(10, 0) << QPointF(10, 10) <<
  13431. QPointF(0, 5);
  13432. right << QPointF(10, 5) << QPointF(0, 0) << QPointF(0, 10) <<
  13433. QPointF(10, 5);
  13434. down << QPointF(0, 0) << QPointF(-5, -10) << QPointF(5, -10) <<
  13435. QPointF(0, 0);
  13436. up << QPointF(0, 0) << QPointF(-5, 10) << QPointF(4, 10) << QPointF(0, 0);
  13437. initialBrush.setColor(QColor(170, 170, 255));
  13438. initialBrush.setStyle(Qt::SolidPattern);
  13439. finalBrush.setColor(Qt::blue);
  13440. finalBrush.setStyle(Qt::SolidPattern);
  13441. initialDecrement.setPolygon(left);
  13442. initialDecrement.setBrush(initialBrush);
  13443. initialDecrement.setPos(0, 0);
  13444. scene.addItem(&initialDecrement);
  13445. initialIncrement.setPolygon(right);
  13446. initialIncrement.setBrush(initialBrush);
  13447. initialIncrement.setPos(122, 0);
  13448. scene.addItem(&initialIncrement);
  13449. finalDecrement.setPolygon(left);
  13450. finalDecrement.setBrush(finalBrush);
  13451. finalDecrement.setPos(0, 12);
  13452. scene.addItem(&finalDecrement);
  13453. finalIncrement.setPolygon(right);
  13454. finalIncrement.setBrush(finalBrush);
  13455. finalIncrement.setPos(122, 12);
  13456. scene.addItem(&finalIncrement);
  13457. scalePath.moveTo(0, 10);
  13458. scalePath.lineTo(100, 10);
  13459. scalePath.moveTo(0, 0);
  13460. scalePath.lineTo(0, 20);
  13461. scalePath.moveTo(10, 5);
  13462. scalePath.lineTo(10, 15);
  13463. scalePath.moveTo(20, 5);
  13464. scalePath.lineTo(20, 15);
  13465. scalePath.moveTo(30, 5);
  13466. scalePath.lineTo(30, 15);
  13467. scalePath.moveTo(40, 5);
  13468. scalePath.lineTo(40, 15);
  13469. scalePath.moveTo(50, 0);
  13470. scalePath.lineTo(50, 20);
  13471. scalePath.moveTo(60, 5);
  13472. scalePath.lineTo(60, 15);
  13473. scalePath.moveTo(70, 5);
  13474. scalePath.lineTo(70, 15);
  13475. scalePath.moveTo(80, 5);
  13476. scalePath.lineTo(80, 15);
  13477. scalePath.moveTo(90, 5);
  13478. scalePath.lineTo(90, 15);
  13479. scalePath.moveTo(100, 0);
  13480. scalePath.lineTo(100, 20);
  13481. scaleLine.setPath(scalePath);
  13482. scaleLine.setPos(16, 1);
  13483. scene.addItem(&scaleLine);
  13484. setScene(&scene);
  13485. initialIndicator.setPolygon(down);
  13486. initialIndicator.setBrush(initialBrush);
  13487. finalIndicator.setPolygon(up);
  13488. finalIndicator.setBrush(finalBrush);
  13489. setMinimumSize(sizeHint());
  13490. setMaximumSize(sizeHint());
  13491. setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  13492. setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  13493. setMinimumSize(sizeHint());
  13494. }
  13495. @ The size hint forces a smaller representation of the widget, making it easier
  13496. to arrange with other widgets.
  13497. @<ScaleControl implementation@>=
  13498. QSize ScaleControl::sizeHint() const
  13499. {
  13500. return QSize(140, 30);
  13501. }
  13502. @ The methods for setting the values represented on the scale must ensure that
  13503. the appropriate indicator is drawn and position it appropriately.
  13504. @<ScaleControl implementation@>=
  13505. void ScaleControl::setInitialValue(double value)@t\2\2@>@/
  13506. {@t\1@>@/
  13507. if(value >= 0 && value <= 10)@/
  13508. {@t\1@>@/
  13509. nonScoredValue = value;
  13510. if(!initialSet)
  13511. {
  13512. scene.addItem(&initialIndicator);
  13513. }
  13514. initialSet = true;
  13515. initialIndicator.setPos(value * 10 + 16, 10);
  13516. emit initialChanged(value);
  13517. if(!finalSet)
  13518. {
  13519. setFinalValue(value);
  13520. }@t\2@>@/
  13521. }@t\2@>@/
  13522. }@/@/
  13523. void ScaleControl::setFinalValue(double value)@t\2\2@>@/
  13524. {@t\1@>@/
  13525. if(value >= 0 && value <= 10)@/
  13526. {@t\1@>@/
  13527. scoredValue = value;
  13528. if(!finalSet)@/
  13529. {
  13530. scene.addItem(&finalIndicator);
  13531. }
  13532. finalSet = true;
  13533. finalIndicator.setPos(value * 10 + 16, 11);
  13534. emit finalChanged(value);@t\2@>@/
  13535. }@t\2@>@/
  13536. }
  13537. @ These values can, of course, be retrieved programmatically.
  13538. @<ScaleControl implementation@>=
  13539. double ScaleControl::initialValue(void)
  13540. {
  13541. return nonScoredValue;
  13542. }
  13543. double ScaleControl::finalValue(void)
  13544. {
  13545. return scoredValue;
  13546. }
  13547. @ This only leaves the matter of handling interaction with the widget. A future
  13548. version of this class might split the various interface elements in the scene
  13549. into distinct classes capable of using the event propagation capabilities
  13550. provided by the graphics view framework, however with the current design, we
  13551. must do a little more work.
  13552. There are two events which must be accepted in order to register a click on a
  13553. given portion of the scale. One event is generated when the mouse button is
  13554. pressed.
  13555. @<ScaleControl implementation@>=
  13556. void ScaleControl::mousePressEvent(QMouseEvent *event)
  13557. {
  13558. @<Check that the left button was pressed@>@;
  13559. scaleDown = @[true@];
  13560. event->accept();
  13561. }
  13562. @ The primary action button on the mouse is the left button. While there might
  13563. be sensible interactions to provide in response to other buttons, these are not
  13564. presently supported.
  13565. @<Check that the left button was pressed@>=
  13566. if(event->button() != Qt::LeftButton)
  13567. {
  13568. event->ignore();
  13569. return;
  13570. }
  13571. @ Most of the click event handling is done in response to releasing the mouse
  13572. button. In this event handler we must determine if the click occurred in a
  13573. clickable portion of the scale and take the appropriate action in response.
  13574. @<ScaleControl implementation@>=
  13575. void ScaleControl::mouseReleaseEvent(QMouseEvent *event)@t\2\2@>@/
  13576. {@t\1@>@/
  13577. @<Check that the left button was pressed@>@;
  13578. if(!scaleDown)
  13579. {
  13580. event->ignore();
  13581. return;
  13582. }
  13583. scaleDown = false;
  13584. QPointF sceneCoordinate = mapToScene(event->x(), event->y());
  13585. @<Handle clicks in the decrement controls@>@;
  13586. @<Handle clicks in the increment controls@>@;
  13587. @<Handle clicks in the scale area@>@;
  13588. event->ignore();
  13589. return;@t\2@>@/
  13590. }
  13591. @ As currently implemented, each horizontal pixel position represents a value
  13592. evenly divisible by $0.1$. It is, however, quite common to see vaues with a
  13593. $.25$ or $.75$ after the whole number on cupping forms. In order to make it
  13594. possible to select such values without vastly increasing the length of the
  13595. scale, the increment and decrement controls adjust the represented value by
  13596. $0.05$.
  13597. @<Handle clicks in the decrement controls@>=
  13598. if(sceneCoordinate.x() >= 0 && sceneCoordinate.x() <= 10)
  13599. {
  13600. if(sceneCoordinate.y() >= 0 && sceneCoordinate.y() <= 10)
  13601. {
  13602. if(initialSet)
  13603. {
  13604. setInitialValue(nonScoredValue - 0.05);
  13605. }
  13606. event->accept();
  13607. return;
  13608. }
  13609. else if(sceneCoordinate.y() >= 12 && sceneCoordinate.y() <= 22)
  13610. {
  13611. if(finalSet)
  13612. {
  13613. setFinalValue(scoredValue - 0.05);
  13614. event->accept();
  13615. return;
  13616. }
  13617. }
  13618. }
  13619. @ Incrementing represented values is done in the same manner as decrementing
  13620. them.
  13621. @<Handle clicks in the increment controls@>=
  13622. else if(sceneCoordinate.x() >= 122 && sceneCoordinate.x() <= 132)
  13623. {
  13624. if(sceneCoordinate.y() >= 0 && sceneCoordinate.y() <= 10)
  13625. {
  13626. if(initialSet)
  13627. {
  13628. setInitialValue(nonScoredValue + 0.05);
  13629. event->accept();
  13630. return;
  13631. }
  13632. }
  13633. else if(sceneCoordinate.y() >= 12 && sceneCoordinate.y() <= 22)
  13634. {
  13635. if(finalSet)
  13636. {
  13637. setFinalValue(scoredValue + 0.05);
  13638. event->accept();
  13639. return;
  13640. }
  13641. }
  13642. }
  13643. @ When handling clicks in the scale area, there is a difference between the
  13644. first click and any subsequent click.
  13645. @<Handle clicks in the scale area@>=
  13646. double relativeX = sceneCoordinate.x() - 16;
  13647. if(initialSet)
  13648. {
  13649. if(relativeX >= 0 && relativeX <= 100)
  13650. {
  13651. setFinalValue(relativeX / 10.0);
  13652. event->accept();
  13653. return;
  13654. }
  13655. }
  13656. else
  13657. {
  13658. if(relativeX >= 0 && relativeX <= 100)
  13659. {
  13660. setInitialValue(relativeX / 10.0);
  13661. event->accept();
  13662. return;
  13663. }
  13664. }
  13665. @* The Vertical Scale.
  13666. \noindent In addition to the horizontal scale, there is also a vertical scale.
  13667. The implementation of this class is in some ways a bit simpler as only one value
  13668. must be retained. While there is no prohibition on using this scale for scored
  13669. values (and this might enable a rather compact representation which might be
  13670. useful in some applications), its intent is for unscored values which are less
  13671. likely to change over time. If the dry aroma of a coffee changes significantly
  13672. during a cupping session, you are most likely waiting far too long to pour the
  13673. water.
  13674. @<Class declarations@>=
  13675. class IntensityControl : public QGraphicsView@/
  13676. {@/
  13677. @[Q_OBJECT@]@;
  13678. @[Q_PROPERTY(double value READ value WRITE setValue)@]@;
  13679. QGraphicsScene scene;
  13680. QGraphicsPolygonItem decrement;
  13681. QGraphicsPolygonItem increment;
  13682. QGraphicsPolygonItem indicator;
  13683. QGraphicsPathItem scaleLine;
  13684. QPolygonF left;
  13685. QPolygonF up;
  13686. QPolygonF down;
  13687. QPainterPath scalePath;
  13688. QBrush theBrush;
  13689. double theValue;
  13690. bool valueSet;
  13691. bool scaleDown;
  13692. public:@/
  13693. IntensityControl();
  13694. double value();
  13695. virtual QSize sizeHint() const;@/
  13696. @[public slots@]:@/
  13697. void setValue(double val);@/
  13698. signals:@/
  13699. void valueChanged(double);@/
  13700. protected:@/
  13701. virtual void mousePressEvent(QMouseEvent *event);
  13702. virtual void mouseReleaseEvent(QMouseEvent *event);@/
  13703. };
  13704. @ Note the similarity between this constructor and the the |ScaleControl|
  13705. constructor.
  13706. @<IntensityControl implementation@>=
  13707. IntensityControl::IntensityControl() : QGraphicsView(NULL, NULL), theValue(-1),
  13708. valueSet(false), scaleDown(false)
  13709. {
  13710. setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  13711. setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  13712. left << QPointF(0, 0) << QPointF(10, -5) << QPointF(10, 5) << QPointF(0, 0);
  13713. down << QPointF(0, 0) << QPointF(10, 0) << QPointF(5, 10) << QPointF(0, 0);
  13714. up << QPointF(0, 10) << QPointF(10, 10) << QPointF(5, 0) << QPointF(0, 10);
  13715. theBrush.setColor(Qt::blue);
  13716. theBrush.setStyle(Qt::SolidPattern);
  13717. increment.setPolygon(up);
  13718. increment.setBrush(theBrush);
  13719. increment.setPos(0, 0);
  13720. scene.addItem(&increment);
  13721. decrement.setPolygon(down);
  13722. decrement.setBrush(theBrush);
  13723. decrement.setPos(0, 122);
  13724. scene.addItem(&decrement);
  13725. scalePath.moveTo(5, 0);
  13726. scalePath.lineTo(5, 100);
  13727. scalePath.moveTo(0, 0);
  13728. scalePath.lineTo(10, 0);
  13729. scalePath.moveTo(0, 10);
  13730. scalePath.lineTo(10, 10);
  13731. scalePath.moveTo(0, 20);
  13732. scalePath.lineTo(10, 20);
  13733. scalePath.moveTo(0, 30);
  13734. scalePath.lineTo(10, 30);
  13735. scalePath.moveTo(0, 40);
  13736. scalePath.lineTo(10, 40);
  13737. scalePath.moveTo(0, 50);
  13738. scalePath.lineTo(10, 50);
  13739. scalePath.moveTo(0, 60);
  13740. scalePath.lineTo(10, 60);
  13741. scalePath.moveTo(0, 70);
  13742. scalePath.lineTo(10, 70);
  13743. scalePath.moveTo(0, 80);
  13744. scalePath.lineTo(10, 80);
  13745. scalePath.moveTo(0, 90);
  13746. scalePath.lineTo(10, 90);
  13747. scalePath.moveTo(0, 100);
  13748. scalePath.lineTo(10, 100);
  13749. scaleLine.setPath(scalePath);
  13750. scaleLine.setPos(0, 16);
  13751. scene.addItem(&scaleLine);
  13752. setScene(&scene);
  13753. indicator.setPolygon(left);
  13754. indicator.setBrush(theBrush);
  13755. setMinimumSize(sizeHint());
  13756. setMaximumSize(sizeHint());
  13757. }
  13758. @ Once again, the size hint reduces the default size of the widget.
  13759. @<IntensityControl implementation@>=
  13760. QSize IntensityControl::sizeHint() const
  13761. {
  13762. return QSize(25, 160);
  13763. }
  13764. @ To support a more intuitive numerical representation, higher values should map
  13765. to higher positions on the scale. This is contrary to the coordinate system
  13766. provided by Qt, so the code setting the position of the indicator on the scale
  13767. must account for this.
  13768. During testing of this class, I found that it was impossible to select the
  13769. values 0 or 10 either through a click or with the increment or decrement
  13770. controls. Adding two additional execution branches corrects this issue.
  13771. @<IntensityControl implementation@>=
  13772. void IntensityControl::setValue(double val)
  13773. {
  13774. if(val >= 0 && val <= 10)
  13775. {
  13776. theValue = val;
  13777. if(!valueSet)
  13778. {
  13779. scene.addItem(&indicator);
  13780. }
  13781. valueSet = @[true@];
  13782. indicator.setPos(6, (100 - (val * 10)) + 16);
  13783. emit(valueChanged(val));
  13784. }
  13785. else if(val < 1)
  13786. {
  13787. setValue(0);
  13788. }
  13789. else
  13790. {
  13791. setValue(10);
  13792. }
  13793. }
  13794. double IntensityControl::value()
  13795. {
  13796. return theValue;
  13797. }
  13798. @ Mouse event handling is similar as well. The mouse press event simply notes
  13799. that the button has been pressed.
  13800. @<IntensityControl implementation@>=
  13801. void IntensityControl::mousePressEvent(QMouseEvent *event)
  13802. {
  13803. @<Check that the left button was pressed@>@;
  13804. scaleDown = @[true@];
  13805. event->accept();
  13806. }
  13807. @ Since there are fewer clickable areas there are fewer regions to check while
  13808. handling the mouse release event. Just as the |setValue()| method must
  13809. compensate for a mismatch between the scale and the underlying coordinate
  13810. system, so must click handling in the scale area take this into consideration
  13811. when determining which value the click intends.
  13812. @<IntensityControl implementation@>=
  13813. void IntensityControl::mouseReleaseEvent(QMouseEvent *event)
  13814. {
  13815. @<Check that the left button was pressed@>@;
  13816. if(!scaleDown)
  13817. {
  13818. event->ignore();
  13819. return;
  13820. }
  13821. scaleDown = @[false@];
  13822. QPointF sceneCoordinate = mapToScene(event->x(), event->y());
  13823. if(sceneCoordinate.x() >= 0 && sceneCoordinate.x() <= 16)
  13824. {
  13825. if(sceneCoordinate.y() >= 0 && sceneCoordinate.y() <= 10)
  13826. {
  13827. if(valueSet)
  13828. {
  13829. setValue(theValue + 0.05);
  13830. }
  13831. event->accept();
  13832. return;
  13833. }
  13834. else if(sceneCoordinate.y() >= 122 && sceneCoordinate.y() <= 132)
  13835. {
  13836. if(valueSet)
  13837. {
  13838. setValue(theValue - 0.05);
  13839. }
  13840. event->accept();
  13841. return;
  13842. }
  13843. else if(sceneCoordinate.y() >= 16 && sceneCoordinate.y() <= 116)
  13844. {
  13845. setValue(10 - ((sceneCoordinate.y() - 16) / 10.0));
  13846. event->accept();
  13847. return;
  13848. }
  13849. }
  13850. }
  13851. @* Scripting the Scale Widgets.
  13852. \noindent Scale widgets can be added through the configuration system with
  13853. {\tt <hscale>} and {\tt <vscale>} elements. These can be added to layouts.
  13854. @<Function prototypes for scripting@>=
  13855. void addScaleControlToLayout(QDomElement element,
  13856. QStack<QWidget *> *widgetStack,
  13857. QStack<QLayout *> *layoutStack);
  13858. void addIntensityControlToLayout(QDomElement element,
  13859. QStack<QWidget *> *widgetStack,
  13860. QStack<QLayout *> *layoutStack);
  13861. @ Adding these widgets is done in the same way as adding other widgets.
  13862. @<Functions for scripting@>=
  13863. void addScaleControlToLayout(QDomElement element, QStack<QWidget *> *,
  13864. QStack<QLayout *> *layoutStack)
  13865. {
  13866. ScaleControl *scale = new ScaleControl;
  13867. if(element.hasAttribute("id"))
  13868. {
  13869. scale->setObjectName(element.attribute("id"));
  13870. }
  13871. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  13872. layout->addWidget(scale);
  13873. }
  13874. void addIntensityControlToLayout(QDomElement element, QStack<QWidget *> *,
  13875. QStack<QLayout *> *layoutStack)
  13876. {
  13877. IntensityControl *scale = new IntensityControl;
  13878. if(element.hasAttribute("id"))
  13879. {
  13880. scale->setObjectName(element.attribute("id"));
  13881. }
  13882. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  13883. layout->addWidget(scale);
  13884. }
  13885. @** Device Configuration.
  13886. \noindent Starting in \pn{} 1.4, all supported measurement hardware is
  13887. available from the same build and it is possible to use multiple devices from
  13888. differernt vendors at the same time. In previous versions, there were very few
  13889. available hardware configurations and a small number of example configuration
  13890. documents covered the needs of most people using the software. As more hardware
  13891. is supported and more people with distinct needs start using \pn{}, that
  13892. approach becomes unsustainable and the need for in-program configuration
  13893. becomes increasingly pronounced.
  13894. Device configuration is coupled to configuration of the logging window and
  13895. it is possible to configure multiple different roasters with different hardware
  13896. and other customizations of the logging window specific to a particular
  13897. machine.
  13898. The core of this is the use of an XML document saved with |QSettings| under
  13899. the |"DeviceConfiguration"| key. Within the root {\tt <DeviceConfiguration>}
  13900. element there are two grouping elements: {\tt <devices>} and
  13901. {\tt <references>}. The terminology was developed at a time when it was
  13902. thought that hardware configuration and logging view configuration would be
  13903. decoupled from each other, however the benefits of combining these far
  13904. outweighed the minor loss of flexibility with this approach.
  13905. Within the {\tt <devices>} tag there are arbitrarily many {\tt <node>} tags
  13906. which themselves may contain {\tt <node>} tags nested to any depth. Each of
  13907. these has two attributes, a {\tt name} attribute which specifies the display
  13908. text used to represent that node and a {\tt reference} attribute with a
  13909. unique value. Typica will generate a UUID for each node to use as the unique
  13910. value but this is not strictly required. The {\tt name} attribute does not
  13911. need to be unique and will generally be supplied by the person using the
  13912. software. The top level {\tt node} elements represent a coffee roaster and
  13913. the sub-elements can represent hardware, annotation controls, and possibly
  13914. other configurable features.
  13915. Within the {\tt <references>} tag there are as many {\tt <reference>} tags as
  13916. there are {\tt <node>} tags. Each of these has an {\tt id} attribute which
  13917. matches the {\tt reference} attribute in a {\tt <node>} tag and a {\tt driver}
  13918. attribute which is used to determine which class should be used to interact
  13919. with these settings. In the case of device configuration, this allows a
  13920. determination of which class should be used to generate an editor for settings
  13921. related to that node. Within each {\tt <reference>} tag is an arbitrary number
  13922. of {\tt <attribute>} tags with {\tt name} and {\tt value} attributes. Code for
  13923. providing the settings widgets and device interfaces can use these for any
  13924. desired purpose, but it is common to have one {\tt <attribute>} tag per setting
  13925. appropriate for a given node and possibly more to identify the concept a
  13926. particular node represents.
  13927. The global |Application| object is extended to maintain a |QDomDocument|
  13928. representation of this XML document.
  13929. @<Application private data members@>=
  13930. QDomDocument deviceConfigurationDocument;
  13931. @ Two methods are also provided for interacting with this document. The
  13932. |deviceConfiguration()| method returns a pointer to the private data member,
  13933. loading the XML from |QSettings| if required and creating a new document with
  13934. no {\tt <node>} tags if the document does not exist in settings. The
  13935. |saveDeviceConfiguration()| method serializes the |QDomDocument| to settings.
  13936. @<Device configuration members@>=
  13937. QDomDocument deviceConfiguration();
  13938. @ The method for saving should be a slot so a model representing this data
  13939. can persist changes through the signals and slots mechanism rather than
  13940. requiring the calls to be explicitn.
  13941. @<Extended Application slots@>=
  13942. void saveDeviceConfiguration();
  13943. @ Serializing the current configuration is trivial.
  13944. @<Application Implementation@>=
  13945. void Application::saveDeviceConfiguration()
  13946. {
  13947. QSettings settings;
  13948. settings.setValue("DeviceConfiguration",
  13949. QVariant(deviceConfigurationDocument.toByteArray()));
  13950. }
  13951. @ Producing a pointer to a loaded configuration only slightly more complicated.
  13952. If the configuration has been previously loaded we just return the pointer.
  13953. Otherwise, we attempt to load the document.
  13954. @<Application Implementation@>=
  13955. QDomDocument Application::deviceConfiguration()
  13956. {
  13957. if(deviceConfigurationDocument.isNull())
  13958. {
  13959. @<Load device configuration document from settings@>@;
  13960. }
  13961. return deviceConfigurationDocument;
  13962. }
  13963. @ In most cases a document will already exist in settings, but we must verify
  13964. this and create a new document if it does not exist. We also clear device
  13965. configuration settings if the configuration document is invalid.
  13966. @<Load device configuration document from settings@>=
  13967. QSettings settings;
  13968. QByteArray document = settings.value("DeviceConfiguration").toByteArray();
  13969. QString etext;
  13970. int eline;
  13971. int ecol;
  13972. if(document.length() == 0)
  13973. {
  13974. qDebug() << "Loaded settings length is 0. Creating new configuration.";
  13975. @<Create device configuration document@>@;
  13976. }
  13977. else
  13978. {
  13979. if(!deviceConfigurationDocument.setContent(document, false,
  13980. &etext, &eline, &ecol))
  13981. {
  13982. @<Report configuration loading error@>@;
  13983. @<Create device configuration document@>@;
  13984. }
  13985. }
  13986. @ Rather than generate the empty device configuration programmatically, we keep
  13987. an empty device configuration document as a resource.
  13988. @<Create device configuration document@>=
  13989. QFile emptyDocument(":/resources/xml/EmptyDeviceConfiguration.xml");
  13990. emptyDocument.open(QIODevice::ReadOnly);
  13991. if(!deviceConfigurationDocument.setContent(&emptyDocument, false,
  13992. &etext, &eline, &ecol))
  13993. {
  13994. @<Report configuration loading error@>@;
  13995. }
  13996. else
  13997. {
  13998. saveDeviceConfiguration();
  13999. }
  14000. @ There isn'@q'@>t really anything that can be done if the device configuration data
  14001. is corrupt, but an error message can be produced if the program happens to have
  14002. access to a debugging console.
  14003. @<Report configuration loading error@>=
  14004. qDebug() << QString(tr("An error occurred loading device configuration."));
  14005. qDebug() << QString(tr("Line %1, Column %2")).arg(eline).arg(ecol);
  14006. qDebug() << etext;
  14007. @* Model and view for device configuration hierarchy.
  14008. \noindent When manipulating device configuration data, it can be useful to
  14009. present the device hierarchy in a tree view. To do this, we use two classes to
  14010. produce a tree model. This is slightly extended and modified from an example
  14011. in the Qt documentation.\nfnote{Simple DOM Model Example:\par\indent\pdfURL{%
  14012. http://doc.qt.nokia.com/4.7-snapshot/itemviews-simpledommodel.html}
  14013. {http://doc.qt.nokia.com/4.7-snapshot/itemviews-simpledommodel.html}}
  14014. Our model uses the |DeviceTreeModelNode| class to cache the |QDomNode|
  14015. instances with the data we want.
  14016. @<Class declarations@>=
  14017. class DeviceTreeModelNode
  14018. {
  14019. public:
  14020. DeviceTreeModelNode(QDomNode &node, int row,
  14021. DeviceTreeModelNode *parent = NULL);
  14022. ~DeviceTreeModelNode();
  14023. DeviceTreeModelNode *child(int index);
  14024. DeviceTreeModelNode *parent();
  14025. QDomNode node() const;
  14026. int row();
  14027. private:
  14028. QDomNode domNode;
  14029. QHash<int, DeviceTreeModelNode*> children;
  14030. int rowNumber;
  14031. DeviceTreeModelNode *parentItem;
  14032. };
  14033. @ Implementation of this helper class is trivial.
  14034. @<DeviceTreeModelNode implementation@>=
  14035. DeviceTreeModelNode::DeviceTreeModelNode(QDomNode &node, int row,
  14036. DeviceTreeModelNode *parent)
  14037. : domNode(node), rowNumber(row), parentItem(parent)
  14038. {
  14039. /* Nothing needs to be done here. */
  14040. }
  14041. DeviceTreeModelNode::~DeviceTreeModelNode()
  14042. {
  14043. QHash<int, DeviceTreeModelNode *>::iterator@, i;
  14044. for(i = children.begin(); i != children.end(); i++)
  14045. {
  14046. delete i.value();
  14047. }
  14048. }
  14049. DeviceTreeModelNode *DeviceTreeModelNode::parent()
  14050. {
  14051. return parentItem;
  14052. }
  14053. int DeviceTreeModelNode::row()
  14054. {
  14055. return rowNumber;
  14056. }
  14057. QDomNode DeviceTreeModelNode::node() const
  14058. {
  14059. return domNode;
  14060. }
  14061. DeviceTreeModelNode *DeviceTreeModelNode::child(int index)
  14062. {
  14063. if(children.contains(index))
  14064. {
  14065. return children[index];
  14066. }
  14067. if(index >= 0 && index < domNode.childNodes().count())
  14068. {
  14069. QDomNode childNode = domNode.childNodes().item(index);
  14070. DeviceTreeModelNode *childItem = new DeviceTreeModelNode(childNode,
  14071. index, this);
  14072. children[index] = childItem;
  14073. return childItem;
  14074. }
  14075. return NULL;
  14076. }
  14077. @ The model class provides a single column representation of the {\tt devices}
  14078. section of the device configuration document. It provides methods for editing
  14079. the name of any node, for adding new nodes to the model, for deleting any node
  14080. in the model, and for obtaining the {\tt reference} element corresponding to
  14081. a given node.
  14082. @<Class declarations@>=
  14083. class DeviceTreeModel : public QAbstractItemModel@/
  14084. {@/
  14085. @[Q_OBJECT@]@;
  14086. public:@/
  14087. DeviceTreeModel(QObject *parent = NULL);
  14088. ~DeviceTreeModel();
  14089. QVariant data(const QModelIndex &index, int role) const;
  14090. Qt::ItemFlags flags(const QModelIndex &index) const;
  14091. QVariant headerData(int section, Qt::Orientation orientation,
  14092. int role = Qt::DisplayRole) const;
  14093. QModelIndex index(int row, int column,
  14094. const QModelIndex &parent = QModelIndex()) const;
  14095. QModelIndex parent(const QModelIndex &child) const;
  14096. Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const;
  14097. int columnCount(const QModelIndex &parent = QModelIndex()) const;
  14098. bool setData(const QModelIndex &index, const QVariant &value,
  14099. int role);
  14100. bool removeRows(int row, int count, const QModelIndex &parent);
  14101. QDomElement referenceElement(const QString &id);
  14102. @[public slots@]:@/
  14103. void newNode(const QString &name, const QString &driver,
  14104. const QModelIndex &parent);
  14105. private:@/
  14106. QDomDocument document;
  14107. DeviceTreeModelNode *root;
  14108. QDomNode referenceSection;
  14109. QDomNode treeRoot;
  14110. };
  14111. @ In the constructor we locate the {\tt devices} and {\tt references} sections
  14112. of the passed in document. Our tree of cached |QDomNode| elements starts with
  14113. the former and is expanded as needed.
  14114. @<DeviceTreeModel implementation@>=
  14115. DeviceTreeModel::DeviceTreeModel(QObject *parent)
  14116. : QAbstractItemModel(parent)
  14117. {
  14118. document = AppInstance->deviceConfiguration();
  14119. QDomNodeList elements = document.elementsByTagName("devices");
  14120. if(elements.size() != 1)
  14121. {
  14122. qDebug() << "Unexpected result when loading device map.";
  14123. }
  14124. treeRoot = elements.at(0);
  14125. root = new DeviceTreeModelNode(treeRoot, 0);
  14126. elements = document.elementsByTagName("references");
  14127. if(elements.size() != 1)
  14128. {
  14129. qDebug() << "No references section. Creating.";
  14130. referenceSection = document.createElement("references");
  14131. document.appendChild(referenceSection);
  14132. }
  14133. else
  14134. {
  14135. referenceSection = elements.at(0);
  14136. }
  14137. connect(this, SIGNAL(dataChanged(QModelIndex, QModelIndex)),
  14138. AppInstance, SLOT(saveDeviceConfiguration()));
  14139. connect(this, SIGNAL(modelReset()),
  14140. AppInstance, SLOT(saveDeviceConfiguration()));
  14141. connect(this, SIGNAL(rowsInserted(QModelIndex, int, int)),
  14142. AppInstance, SLOT(saveDeviceConfiguration()));
  14143. }
  14144. @ We only provide a single column for our model, so |columnCount()| can simply
  14145. return a constant. The |rowCount()| method can return a variety of values
  14146. depending on the parent node.
  14147. @<DeviceTreeModel implementation@>=
  14148. int DeviceTreeModel::columnCount(const QModelIndex &) const
  14149. {
  14150. return 1;
  14151. }
  14152. int DeviceTreeModel::rowCount(const QModelIndex &parent) const
  14153. {
  14154. if(parent.column() > 0)
  14155. {
  14156. return 0;
  14157. }
  14158. @<Get parent item from index@>@;
  14159. return parentItem->node().childNodes().count();
  14160. }
  14161. @ If an invalid index is passed as the parent index, we assume the parent to
  14162. be the root element.
  14163. @<Get parent item from index@>=
  14164. DeviceTreeModelNode *parentItem;
  14165. if(!parent.isValid())
  14166. {
  14167. parentItem = root;
  14168. }
  14169. else
  14170. {
  14171. parentItem = static_cast<DeviceTreeModelNode *>(parent.internalPointer());
  14172. }
  14173. @ As seen in |rowCount()|, we keep a pointer to the cached node in our model
  14174. indices.
  14175. @<DeviceTreeModel implementation@>=
  14176. QModelIndex DeviceTreeModel::index(int row, int column,
  14177. const QModelIndex &parent) const
  14178. {
  14179. if(!hasIndex(row, column, parent))
  14180. {
  14181. return QModelIndex();
  14182. }
  14183. @<Get parent item from index@>@;
  14184. DeviceTreeModelNode *childItem = parentItem->child(row);
  14185. if(childItem)
  14186. {
  14187. return createIndex(row, column, childItem);
  14188. }
  14189. return QModelIndex();
  14190. }
  14191. @ We can also request an index for the parent of a given index.
  14192. @<DeviceTreeModel implementation@>=
  14193. QModelIndex DeviceTreeModel::parent(const QModelIndex &child) const
  14194. {
  14195. if(!child.isValid())
  14196. {
  14197. return QModelIndex();
  14198. }
  14199. DeviceTreeModelNode *childItem =
  14200. static_cast<DeviceTreeModelNode *>(child.internalPointer());
  14201. DeviceTreeModelNode *parentItem = childItem->parent();
  14202. if(!parentItem || parentItem == root)
  14203. {
  14204. return QModelIndex();
  14205. }
  14206. return createIndex(parentItem->row(), 0, parentItem);
  14207. }
  14208. @ All items should be enabled, selectable, and editable.
  14209. @<DeviceTreeModel implementation@>=
  14210. Qt::ItemFlags DeviceTreeModel::flags(const QModelIndex &index) const
  14211. {
  14212. if(!index.isValid())
  14213. {
  14214. return 0;
  14215. }
  14216. return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
  14217. }
  14218. @ Each node in the model maintains two pieces of information. One is the
  14219. display value for the node which is held in the {\tt name} attribute of the
  14220. corresponding XML element. The other is a reference ID held in the
  14221. {\tt reference} attribute.
  14222. @<DeviceTreeModel implementation@>=
  14223. QVariant DeviceTreeModel::data(const QModelIndex &index, int role) const
  14224. {
  14225. if(!index.isValid())
  14226. {
  14227. return QVariant();
  14228. }
  14229. if(role != Qt::DisplayRole && role != Qt::UserRole && role != Qt::EditRole)
  14230. {
  14231. return QVariant();
  14232. }
  14233. DeviceTreeModelNode *item =
  14234. static_cast<DeviceTreeModelNode *>(index.internalPointer());
  14235. QDomNode node = item->node();
  14236. QDomElement element = node.toElement();
  14237. switch(role)
  14238. {
  14239. case Qt::DisplayRole:@/
  14240. case Qt::EditRole:@/
  14241. return QVariant(element.attribute("name"));
  14242. case Qt::UserRole:@/
  14243. return QVariant(element.attribute("reference"));
  14244. default:@/
  14245. return QVariant();
  14246. }
  14247. return QVariant();
  14248. }
  14249. @ The reference value is managed by the model and should never be changed. The
  14250. display value for a node is, however, editable. These changes must propagate
  14251. back to the XML document underlying the model.
  14252. @<DeviceTreeModel implementation@>=
  14253. bool DeviceTreeModel::setData(const QModelIndex &index,
  14254. const QVariant &value, int)@;@/
  14255. {@t\1@>@/
  14256. if(!index.isValid())@/
  14257. {@t\1@>@/
  14258. return false;@t\2@>@/
  14259. }@/
  14260. DeviceTreeModelNode *item =
  14261. static_cast<DeviceTreeModelNode *>(index.internalPointer());
  14262. QDomNode node = item->node();
  14263. QDomElement element = node.toElement();
  14264. element.setAttribute("name", value.toString());
  14265. emit dataChanged(index, index);@;
  14266. return true;@t\2@>@/
  14267. }
  14268. @ A custom method is provided for adding new nodes to the model. This generates
  14269. the two XML elements needed for the node. The |name| parameter is the display
  14270. name of the new node, the |driver| parameter is used as the value for the
  14271. {\tt driver} attribute in the {\tt reference} element which will be used to
  14272. determine what classes are used to work with that data.
  14273. @<DeviceTreeModel implementation@>=
  14274. void DeviceTreeModel::newNode(const QString &name, const QString &driver,
  14275. const QModelIndex &parent)
  14276. {
  14277. QString referenceID = QUuid::createUuid().toString();
  14278. @<Get parent item from index@>@;
  14279. QDomNode parentNode = parentItem->node();
  14280. int newRowNumber = rowCount(parent);
  14281. beginInsertRows(parent, newRowNumber, newRowNumber);
  14282. QDomElement deviceElement = document.createElement("node");
  14283. deviceElement.setAttribute("name", name);
  14284. deviceElement.setAttribute("reference", referenceID);
  14285. parentNode.appendChild(deviceElement);
  14286. QDomElement referenceElement = document.createElement("reference");
  14287. referenceElement.setAttribute("id", referenceID);
  14288. referenceElement.setAttribute("driver", driver);
  14289. referenceSection.appendChild(referenceElement);
  14290. endInsertRows();
  14291. }
  14292. @ We can also delete nodes. When deleting a node, both XML elements are
  14293. removed and our node cache is invalidated.
  14294. @<DeviceTreeModel implementation@>=
  14295. bool DeviceTreeModel::removeRows(int row, int count, const QModelIndex &parent)@t\2\2@>@/
  14296. {@t\1@>@/
  14297. @<Get parent item from index@>@;
  14298. QDomNode parentNode = parentItem->node();
  14299. QDomNodeList childNodes = parentNode.childNodes();@;
  14300. if(childNodes.size() < row + count)@/
  14301. {@t\1@>@/
  14302. return false;@t\2@>@/
  14303. }@/
  14304. beginRemoveRows(parent, row, row + count - 1);
  14305. QList<QDomElement> removalList;
  14306. for(int i = row; i < row + count; i++)
  14307. {
  14308. removalList.append(childNodes.at(i).toElement());
  14309. }
  14310. QDomElement element;
  14311. QDomElement reference;
  14312. for(int i = 0; i < count; i++)
  14313. {
  14314. element = removalList.at(i);
  14315. if(element.hasAttribute("reference"))
  14316. {
  14317. reference = referenceElement(element.attribute("reference"));
  14318. if(!reference.isNull())
  14319. {
  14320. referenceSection.removeChild(reference);
  14321. }
  14322. }
  14323. parentNode.removeChild(element);
  14324. }
  14325. endRemoveRows();
  14326. beginResetModel();
  14327. delete root;
  14328. root = new DeviceTreeModelNode(treeRoot, 0);
  14329. endResetModel();@;
  14330. return true;@t\2@>@/
  14331. }
  14332. @ Another custom method obtains the {\tt reference} element for a given
  14333. reference ID.
  14334. @<DeviceTreeModel implementation@>=
  14335. QDomElement DeviceTreeModel::referenceElement(const QString &id)
  14336. {
  14337. QDomNodeList childNodes = referenceSection.childNodes();
  14338. QDomElement element;
  14339. for(int i = 0; i < childNodes.size(); i++)
  14340. {
  14341. element = childNodes.at(i).toElement();
  14342. if(element.hasAttribute("id"))
  14343. {
  14344. if(element.attribute("id") == id)
  14345. {
  14346. return element;
  14347. }
  14348. }
  14349. }
  14350. return QDomElement();
  14351. }
  14352. @ We don'@q'@>t want any headers, so |headerData()| is very simple.
  14353. @<DeviceTreeModel implementation@>=
  14354. QVariant DeviceTreeModel::headerData(int, Qt::Orientation, int) const
  14355. {
  14356. return QVariant();
  14357. }
  14358. @ The destructor destroys the node cache. The destructor for the top level node
  14359. will recursively destroy all child nodes.
  14360. @<DeviceTreeModel implementation@>=
  14361. DeviceTreeModel::~DeviceTreeModel()
  14362. {
  14363. delete root;
  14364. }
  14365. @ Exposing this class to the host environment allows a number of interesting
  14366. possibilities. Setting the model to a combo box, for example, allows the
  14367. selection of top level nodes representing a particular coffee roaster. It is
  14368. also useful to have the ability to traverse a specified sub-tree of the model
  14369. to set up a logging view that matches the configuration for such a selected
  14370. roaster.
  14371. @<Function prototypes for scripting@>=
  14372. QScriptValue constructDeviceTreeModel(QScriptContext *context,
  14373. QScriptEngine *engine);
  14374. void setDeviceTreeModelProperties(QScriptValue value, QScriptEngine *engine);
  14375. void setQAbstractItemModelProperties(QScriptValue value, QScriptEngine *engine);
  14376. QScriptValue DeviceTreeModel_referenceElement(QScriptContext *context,
  14377. QScriptEngine *engine);
  14378. QScriptValue QAbstractItemModel_data(QScriptContext *context, QScriptEngine *engine);
  14379. QScriptValue QAbstractItemModel_index(QScriptContext *context, QScriptEngine *engine);
  14380. QScriptValue QAbstractItemModel_rowCount(QScriptContext *context, QScriptEngine *engine);
  14381. QScriptValue QAbstractItemModel_hasChildren(QScriptContext *context, QScriptEngine *engine);
  14382. @ The constructor is trivial.
  14383. @<Functions for scripting@>=
  14384. QScriptValue constructDeviceTreeModel(QScriptContext *, QScriptEngine *engine)
  14385. {
  14386. QScriptValue object = engine->newQObject(new DeviceTreeModel);
  14387. setDeviceTreeModelProperties(object, engine);
  14388. return object;
  14389. }
  14390. @ As usual the host environment is informed of this constructor.
  14391. @<Set up the scripting engine@>=
  14392. constructor = engine->newFunction(constructDeviceTreeModel);
  14393. value = engine->newQMetaObject(&DeviceTreeModel::staticMetaObject,
  14394. constructor);
  14395. engine->globalObject().setProperty("DeviceTreeModel", value);
  14396. @ A number of properties are set to allow script code to traverse the model.
  14397. Most of these properties are properly members of |QAbstractItemModel| and
  14398. the code is written to allow any models that may be exposed to the host
  14399. environment in the future to make use of these as well. Note that this is not
  14400. a full set of functionality but only what I needed to implement a particular
  14401. feature set.
  14402. @<Functions for scripting@>=
  14403. void setDeviceTreeModelProperties(QScriptValue value, QScriptEngine *engine)
  14404. {
  14405. setQAbstractItemModelProperties(value, engine);
  14406. value.setProperty("referenceElement",
  14407. engine->newFunction(DeviceTreeModel_referenceElement));
  14408. }
  14409. void setQAbstractItemModelProperties(QScriptValue value, QScriptEngine *engine)
  14410. {
  14411. setQObjectProperties(value, engine);
  14412. value.setProperty("data", engine->newFunction(QAbstractItemModel_data));
  14413. value.setProperty("index", engine->newFunction(QAbstractItemModel_index));
  14414. value.setProperty("rowCount", engine->newFunction(QAbstractItemModel_rowCount));
  14415. value.setProperty("hasChildren", engine->newFunction(QAbstractItemModel_hasChildren));
  14416. }
  14417. @ The wrapped call to |referenceElement| does a little more than might be
  14418. expected. Rather than returning a |QDomElement| and leaving it up to script
  14419. code to traverse the sub-tree, we create a |QVariantMap| which in script code
  14420. is translated as an object with the keys as properties of the object containing
  14421. the values of those keys. This is populated by first specifying a {\tt driver}
  14422. key with its value from the {\tt driver} attribute of the {\tt reference} node.
  14423. We then examine the {\tt <attribute>} sub-elements and use the {\tt name}
  14424. attribute as keys and the {\tt value} attribute as values to fill out the rest
  14425. of the map.
  14426. @<Functions for scripting@>=
  14427. QScriptValue DeviceTreeModel_referenceElement(QScriptContext *context,
  14428. QScriptEngine *engine)
  14429. {
  14430. DeviceTreeModel *model = getself<DeviceTreeModel *>(context);
  14431. QDomElement referenceElement = model->referenceElement(argument<QString>(0, context));
  14432. QDomNodeList configData = referenceElement.elementsByTagName("attribute");
  14433. QDomElement node;
  14434. QVariantMap retval;
  14435. retval.insert("driver", referenceElement.attribute("driver"));
  14436. for(int i = 0; i < configData.size(); i++)
  14437. {
  14438. node = configData.at(i).toElement();
  14439. retval.insert(node.attribute("name"), node.attribute("value"));
  14440. }
  14441. return engine->toScriptValue(retval);
  14442. }
  14443. QScriptValue QAbstractItemModel_data(QScriptContext *context, QScriptEngine *engine)
  14444. {
  14445. QAbstractItemModel *model = getself<QAbstractItemModel *>(context);
  14446. QModelIndex index = argument<QModelIndex>(0, context);
  14447. int role = argument<int>(1, context);
  14448. return engine->toScriptValue(model->data(index, role));
  14449. }
  14450. QScriptValue QAbstractItemModel_index(QScriptContext *context, QScriptEngine *engine)
  14451. {
  14452. QAbstractItemModel *model = getself<QAbstractItemModel *>(context);
  14453. int row = 0;
  14454. int column = 0;
  14455. QModelIndex index;
  14456. if(context->argumentCount() > 1)
  14457. {
  14458. row = argument<int>(0, context);
  14459. column = argument<int>(1, context);
  14460. }
  14461. if(context->argumentCount() > 2)
  14462. {
  14463. index = argument<QModelIndex>(2, context);
  14464. }
  14465. QModelIndex retval = model->index(row, column, index);
  14466. return engine->toScriptValue(retval);
  14467. }
  14468. QScriptValue QAbstractItemModel_rowCount(QScriptContext *context,
  14469. QScriptEngine *)
  14470. {
  14471. QAbstractItemModel *model = getself<QAbstractItemModel *>(context);
  14472. QModelIndex index;
  14473. if(context->argumentCount() == 1)
  14474. {
  14475. index = argument<QModelIndex>(0, context);
  14476. }
  14477. return QScriptValue(model->rowCount(index));
  14478. }
  14479. QScriptValue QAbstractItemModel_hasChildren(QScriptContext *context,
  14480. QScriptEngine *engine)
  14481. {
  14482. QAbstractItemModel *model = getself<QAbstractItemModel *>(context);
  14483. QModelIndex index;
  14484. if(context->argumentCount() == 1)
  14485. {
  14486. index = argument<QModelIndex>(0, context);
  14487. }
  14488. return QScriptValue(engine, model->hasChildren(index));
  14489. }
  14490. @ Some additional work is needed to handle |QModelIndex| appropriately. First
  14491. we declare that as a metatype.
  14492. @<Class declarations@>=
  14493. Q_DECLARE_METATYPE(QModelIndex)
  14494. @ Next we need a pair of functions to convert |QModelIndex| to and from script
  14495. values. Some |QModelIndex| methods are also exposed to the host environment.
  14496. @<Function prototypes for scripting@>=
  14497. QScriptValue QModelIndex_toScriptValue(QScriptEngine *engine, const QModelIndex &index);
  14498. void QModelIndex_fromScriptValue(const QScriptValue &value, QModelIndex &index);
  14499. void setQModelIndexProperties(QScriptValue value, QScriptEngine *engine);
  14500. QScriptValue QModelIndex_row(QScriptContext *context, QScriptEngine *engine);
  14501. @ These are implemented thusly.
  14502. @<Functions for scripting@>=
  14503. QScriptValue QModelIndex_toScriptValue(QScriptEngine *engine, const QModelIndex &index)
  14504. {
  14505. QVariant var;
  14506. var.setValue(index);
  14507. QScriptValue object = engine->newVariant(var);
  14508. setQModelIndexProperties(object, engine);
  14509. return object;
  14510. }
  14511. void QModelIndex_fromScriptValue(const QScriptValue &value, QModelIndex &index)
  14512. {
  14513. index = value.toVariant().value<QModelIndex>();
  14514. }
  14515. void setQModelIndexProperties(QScriptValue value, QScriptEngine *engine)
  14516. {
  14517. value.setProperty("row", engine->newFunction(QModelIndex_row));
  14518. }
  14519. QScriptValue QModelIndex_row(QScriptContext *context, QScriptEngine *engine)
  14520. {
  14521. QModelIndex self = getself<QModelIndex>(context);
  14522. return QScriptValue(engine, self.row());
  14523. }
  14524. @ Finally we register this with the engine.
  14525. @<Set up the scripting engine@>=
  14526. qScriptRegisterMetaType(engine, QModelIndex_toScriptValue, QModelIndex_fromScriptValue);
  14527. @* Device Configuration Widgets.
  14528. \noindent Each node in the {\tt devices} section of the |DeviceTreeModel| is
  14529. associated with a {\tt reference} element that provides a driver string which
  14530. can be used to identify the classes used to interact with the device
  14531. configuration data. An example of this is selecting which widget to use when
  14532. selecting a node in a configuration window. These widgets must be registered
  14533. to the appropriate driver string in advance. This is currently handled through
  14534. the |Application| instance, though it would probably be better to split this
  14535. into its own class at some point in the future.
  14536. @<Application private data members@>=
  14537. QHash<QString, QMetaObject> deviceConfigurationWidgets;
  14538. @ Two methods register widgets and retrieve an instance of the appropriate
  14539. widget for a given node in the device configuration model.
  14540. @<Device configuration members@>=
  14541. void registerDeviceConfigurationWidget(QString driver, QMetaObject widget);
  14542. QWidget* deviceConfigurationWidget(DeviceTreeModel *model,
  14543. const QModelIndex &index);
  14544. @ Registration is a simple wrapper around the underlying |QHash|.
  14545. @<Application Implementation@>=
  14546. void Application::registerDeviceConfigurationWidget(QString driver,
  14547. QMetaObject widget)
  14548. {
  14549. deviceConfigurationWidgets.insert(driver, widget);
  14550. }
  14551. @ Obtaining the configuration widget for a given node involves looking up the
  14552. reference element, extracting the driver string, looking up the associated
  14553. meta-object, and returning a new instance of that object.
  14554. As there is no concept of an invalid |QMetaObject| we default to the static
  14555. meta-object for a |QWidget| if a widget for the specified driver string is not
  14556. registered and check for this prior to creating a new instance of the
  14557. configuration widget.
  14558. @<Application Implementation@>=
  14559. QWidget* Application::deviceConfigurationWidget(DeviceTreeModel *model,
  14560. const QModelIndex &index)
  14561. {
  14562. QVariant nodeReference = index.data(Qt::UserRole);
  14563. QDomElement referenceElement = model->referenceElement(
  14564. model->data(index, Qt::UserRole).toString());
  14565. QMetaObject metaObject =
  14566. deviceConfigurationWidgets.value(referenceElement.attribute("driver"),
  14567. QWidget::staticMetaObject);
  14568. QWidget *editor;
  14569. if(metaObject.className() == QWidget::staticMetaObject.className())
  14570. {
  14571. editor = NULL;
  14572. }
  14573. else
  14574. {
  14575. editor = qobject_cast<QWidget *>(
  14576. metaObject.newInstance(Q_ARG(DeviceTreeModel *, model),
  14577. Q_ARG(QModelIndex, index)));
  14578. }
  14579. return editor;
  14580. }
  14581. @ Every node type should have an associated editor and the editors for nodes
  14582. which can have child nodes should be able to handle creating these child nodes.
  14583. This leaves the problem of creating the top level nodes. For this we must have
  14584. a way to register three key pieces of information: the text which should appear
  14585. for selecting a new top level node to add to the configuration, the default
  14586. name for a node of that type, and the registered driver string associated with
  14587. that node type. The most likely use for this information is in constructing a
  14588. menu. |QAction| seems like a good fit, but this cannot pass all of the
  14589. required information. Part of the chosen solution is a |QAction| subclass
  14590. which takes all three pieces of information and provides a new signal to
  14591. supply the information needed to add a new top level node.
  14592. @<Class declarations@>=
  14593. class NodeInserter : public QAction@/
  14594. {@/
  14595. @[Q_OBJECT@]@;
  14596. public:@/
  14597. NodeInserter(const QString &title, const QString &name,
  14598. const QString &driver, QObject *parent = NULL);
  14599. signals:@/
  14600. void triggered(QString name, QString driver);
  14601. @[private slots@]:@/
  14602. void onTriggered();
  14603. private:@/
  14604. QString defaultNodeName;
  14605. QString driverString;
  14606. };
  14607. @ The constructor saves the information that will later be emitted and connects
  14608. the |triggered()| signal from |QAction| to a private slot which emits our new
  14609. |triggered()| signal.
  14610. @<NodeInserter implementation@>=
  14611. NodeInserter::NodeInserter(const QString &title, const QString &name,
  14612. const QString &driver, QObject *parent) :
  14613. QAction(title, parent), defaultNodeName(name), driverString(driver)
  14614. {
  14615. connect(this, SIGNAL(triggered()), this, SLOT(onTriggered()));
  14616. }
  14617. void NodeInserter::onTriggered()
  14618. {
  14619. emit triggered(defaultNodeName, driverString);
  14620. }
  14621. @ An interface for adding top level nodes to the device configuration needs to
  14622. be able to access a list of these actions so we make this available through the
  14623. |Application| instance. Once again, it would be better to split device
  14624. configuration registration data to a separate class and there should be
  14625. accessors around this.
  14626. Note that this terminology was introduced when it was assumed that device
  14627. configuration and logging view configuration would be separate. It is likely
  14628. that a future code cleanup will remove this in favor of handling the top level
  14629. of the device configuration hierarchy (under roaster specification) in the same
  14630. way that sub-nodes are handled.
  14631. @<Device configuration members@>=
  14632. QList<NodeInserter *> topLevelNodeInserters;
  14633. @ With this done, we can now produce a window which allows someone to easily
  14634. edit the device configuration.
  14635. As of version 1.6 this class is no longer a window but just a |QWidget| which
  14636. is inserted into another more general settings window. The name of the class
  14637. should be changed in a future version to reflect this change.
  14638. @<Class declarations@>=
  14639. class DeviceConfigurationWindow : public QWidget
  14640. {
  14641. @[Q_OBJECT@]@;
  14642. public:@/
  14643. DeviceConfigurationWindow();
  14644. @[public slots@]:@/
  14645. void addDevice();
  14646. void removeNode();
  14647. void newSelection(const QModelIndex &index);
  14648. @[private slots@]:@/
  14649. void resizeColumn();
  14650. private:@/
  14651. QDomDocument document;
  14652. DeviceTreeModel *model;
  14653. QTreeView *view;
  14654. QScrollArea *configArea;
  14655. };
  14656. @ This window consists of two main panels separated by a splitter. The left
  14657. panel presents a tree view of the current device configuration and a set of
  14658. controls that allows someone to either add a new top level node to the
  14659. configuration or delete any node in the configuration along with all of its
  14660. child nodes.
  14661. The right panel provides a |QScrollArea|. When a node is selected from the tree
  14662. view, the appropriate configuration widget will be inserted into that area.
  14663. When a configuration widget adds a new node to the device model, the parent
  14664. node (which should be the currently selected node but the code does not assume
  14665. this) is expanded to show the new child node if it has not already been
  14666. expanded.
  14667. @<DeviceConfigurationWindow implementation@>=
  14668. DeviceConfigurationWindow::DeviceConfigurationWindow() : QWidget(NULL),
  14669. view(new QTreeView), configArea(new QScrollArea)
  14670. {
  14671. QSplitter *splitter = new QSplitter;
  14672. QWidget *leftWidget = new QWidget;
  14673. leftWidget->setMinimumWidth(200);
  14674. QVBoxLayout *left = new QVBoxLayout;
  14675. view->setAnimated(true);
  14676. view->setSelectionMode(QAbstractItemView::SingleSelection);
  14677. document = AppInstance->deviceConfiguration();
  14678. model = new DeviceTreeModel;
  14679. view->setModel(model);
  14680. view->setTextElideMode(Qt::ElideNone);
  14681. view->expandAll();
  14682. view->resizeColumnToContents(0);
  14683. connect(model, SIGNAL(modelReset()), view, SLOT(expandAll()));
  14684. QHBoxLayout *treeButtons = new QHBoxLayout;
  14685. QToolButton *addDeviceButton = new QToolButton;
  14686. addDeviceButton->setIcon(QIcon::fromTheme("list-add"));
  14687. addDeviceButton->setToolTip(tr("New Roaster"));
  14688. connect(addDeviceButton, SIGNAL(clicked()),
  14689. this, SLOT(addDevice()));
  14690. QToolButton *removeNodeButton = new QToolButton;
  14691. removeNodeButton->setIcon(QIcon::fromTheme("list-remove"));
  14692. removeNodeButton->setToolTip(tr("Delete Selection"));
  14693. connect(removeNodeButton, SIGNAL(clicked()), this, SLOT(removeNode()));
  14694. treeButtons->addWidget(addDeviceButton);
  14695. treeButtons->addWidget(removeNodeButton);
  14696. left->addWidget(view);
  14697. left->addLayout(treeButtons);
  14698. leftWidget->setLayout(left);
  14699. splitter->addWidget(leftWidget);
  14700. configArea->setMinimumWidth(580);
  14701. configArea->setMinimumHeight(460);
  14702. configArea->setWidgetResizable(true);
  14703. splitter->addWidget(configArea);
  14704. QVBoxLayout *centralLayout = new QVBoxLayout;
  14705. centralLayout->addWidget(splitter);
  14706. setLayout(centralLayout);
  14707. connect(view, SIGNAL(activated(QModelIndex)),
  14708. this, SLOT(newSelection(QModelIndex)));
  14709. connect(view, SIGNAL(clicked(QModelIndex)),
  14710. this, SLOT(newSelection(QModelIndex)));
  14711. connect(model, SIGNAL(rowsInserted(QModelIndex, int, int)),
  14712. view, SLOT(expand(QModelIndex)));
  14713. connect(model, SIGNAL(rowsInserted(QModelIndex, int, int)),
  14714. this, SLOT(resizeColumn()));
  14715. connect(model, SIGNAL(rowsRemoved(QModelIndex, int, int)),
  14716. this, SLOT(resizeColumn()));
  14717. }
  14718. @ Adding a new top level node to the model is just a matter of extracting the
  14719. required information from the signal requesting that addition.
  14720. @<DeviceConfigurationWindow implementation@>=
  14721. void DeviceConfigurationWindow::addDevice()
  14722. {
  14723. model->newNode(tr("New Roaster"), "roaster", QModelIndex());
  14724. }
  14725. @ Removing the currently selected node is also simple.
  14726. @<DeviceConfigurationWindow implementation@>=
  14727. void DeviceConfigurationWindow::removeNode()
  14728. {
  14729. QModelIndex index = view->currentIndex();
  14730. if(index.isValid())
  14731. {
  14732. int row = index.row();
  14733. QModelIndex parent = index.parent();
  14734. model->removeRow(row, parent);
  14735. }
  14736. }
  14737. @ Due to most of the required logic being implemented in
  14738. |Application::deviceConfigurationWidget()|, inserting the proper editor in the
  14739. right area is also trivial.
  14740. @<DeviceConfigurationWindow implementation@>=
  14741. void DeviceConfigurationWindow::newSelection(const QModelIndex &index)
  14742. {
  14743. QWidget *editor = AppInstance->deviceConfigurationWidget(model, index);
  14744. if(editor)
  14745. {
  14746. configArea->setWidget(editor);
  14747. editor->show();
  14748. }
  14749. }
  14750. @ As nodes are added deeper in the device hierarchy or as nodes obtain longer
  14751. names, the nodes names may be elided by default rather than indicate that the
  14752. view can be scrolled horizontally. There has been feedback that this behavior
  14753. is not preferred so instead as the model data changes we expand the column
  14754. instead.
  14755. @<DeviceConfigurationWindow implementation@>=
  14756. void DeviceConfigurationWindow::resizeColumn()
  14757. {
  14758. view->resizeColumnToContents(0);
  14759. }
  14760. @ At least for the initial testing of this feature it will be useful if we can
  14761. instantiate this from the host environment. For this we at least require a
  14762. constructor.
  14763. Now that this widget is available through a more general settings window it may
  14764. be better to remove direct access to this class from the host environment.
  14765. @<Function prototypes for scripting@>=
  14766. QScriptValue constructDeviceConfigurationWindow(QScriptContext *context,
  14767. QScriptEngine *engine);
  14768. @ The constructor is trivial.
  14769. @<Functions for scripting@>=
  14770. QScriptValue constructDeviceConfigurationWindow(QScriptContext *,
  14771. QScriptEngine *engine)
  14772. {
  14773. QScriptValue object = engine->newQObject(new DeviceConfigurationWindow);
  14774. return object;
  14775. }
  14776. @ Finally we inform the host environment of this constructor.
  14777. @<Set up the scripting engine@>=
  14778. constructor = engine->newFunction(constructDeviceConfigurationWindow);
  14779. value = engine->newQMetaObject(&DeviceConfigurationWindow::staticMetaObject,
  14780. constructor);
  14781. engine->globalObject().setProperty("DeviceConfigurationWindow", value);
  14782. @* A Base Class for Device Configuration Widgets.
  14783. \noindent There are certain operations that are very commonly required
  14784. among device configuration widgets. These common elements have been implemented
  14785. in a base class.
  14786. @<Class declarations@>=
  14787. class BasicDeviceConfigurationWidget : public QWidget
  14788. {
  14789. @[Q_OBJECT@]@;
  14790. public:@/
  14791. BasicDeviceConfigurationWidget(DeviceTreeModel *model,
  14792. const QModelIndex &index);
  14793. @[public slots@]:@/
  14794. void insertChildNode(const QString &name, const QString &driver);
  14795. void updateAttribute(const QString &name, const QString &value);
  14796. protected:@/
  14797. DeviceTreeModel *deviceModel;
  14798. QModelIndex currentNode;
  14799. };
  14800. @ The constructor just passes its arguments to a pair of protected data
  14801. members. These are commonly required in subclasses but need not be exposed
  14802. outside of this branch of the object hierarchy.
  14803. @<BasicDeviceConfigurationWidget implementation@>=
  14804. BasicDeviceConfigurationWidget::BasicDeviceConfigurationWidget(
  14805. DeviceTreeModel *model, const QModelIndex &index)
  14806. : QWidget(NULL), deviceModel(model), currentNode(index)
  14807. {
  14808. /* Nothing needs to be done here. */
  14809. }
  14810. @ The |updateAttribute()| method sets the value property of an attribute
  14811. element of a given name that is a child of the current node, creating the
  14812. element if it does not exist.
  14813. @<BasicDeviceConfigurationWidget implementation@>=
  14814. void BasicDeviceConfigurationWidget::updateAttribute(const QString &name,
  14815. const QString &value)
  14816. {
  14817. QDomElement referenceElement = deviceModel->referenceElement(
  14818. deviceModel->data(currentNode, Qt::UserRole).toString());
  14819. QDomNodeList configData = referenceElement.elementsByTagName("attribute");
  14820. QDomElement node;
  14821. bool found = @[false@];
  14822. for(int i = 0; i < configData.size(); i++)
  14823. {
  14824. node = configData.at(i).toElement();
  14825. if(node.attribute("name") == name)
  14826. {
  14827. node.setAttribute("value", value);
  14828. found = @[true@];
  14829. break;
  14830. }
  14831. }
  14832. if(!found)
  14833. {
  14834. node = AppInstance->deviceConfiguration().createElement("attribute");
  14835. node.setAttribute("name", name);
  14836. node.setAttribute("value", value);
  14837. referenceElement.appendChild(node);
  14838. }
  14839. AppInstance->saveDeviceConfiguration();
  14840. }
  14841. @ The |insertChildNode()| method constructs a new node with the specified name
  14842. and driver as a child of the current node. Node insertion is a generic
  14843. operation that does not require any knowledge of the configuration options that
  14844. will be presented in that node.
  14845. @<BasicDeviceConfigurationWidget implementation@>=
  14846. void BasicDeviceConfigurationWidget::insertChildNode(const QString &name,
  14847. const QString &driver)
  14848. {
  14849. deviceModel->newNode(name, driver, currentNode);
  14850. }
  14851. @** Configuration of Top Level Roaster Nodes.
  14852. \noindent The first configuration widget required is one for defining a coffee
  14853. roaster. This stores the identification number that will be used for machine
  14854. references in the database and also provides controls for adding all of the
  14855. required child nodes for hardware and configurable elements of the logging
  14856. window that may vary from one machine to the next.
  14857. All of the configuration widgets follow a similar pattern. One important detail
  14858. to note is that these configuration widgets are instantiated through Qt'@q'@>s
  14859. meta-object system. All of these constructors take a |DeviceTreeModel *| and a
  14860. |QModelIndex &| as arguments and they are marked as |Q_INVOKABLE|.
  14861. @<Class declarations@>=
  14862. class RoasterConfWidget : public BasicDeviceConfigurationWidget
  14863. {
  14864. @[Q_OBJECT@]@;
  14865. public:@/
  14866. @[Q_INVOKABLE@]@, RoasterConfWidget(DeviceTreeModel *model,
  14867. const QModelIndex &index);
  14868. @[private slots@]:@/
  14869. void updateRoasterId(int id);
  14870. void updateCapacityCheck(int value);
  14871. void updateCapacity(const QString &value);
  14872. void updateCapacityUnit(const QString &value);
  14873. };
  14874. @ Aside from the ID number used to identify the roaster in the database we also
  14875. need controls to add child nodes. In order to limit the number of options in
  14876. menus for adding child nodes, these are organized into groups that are
  14877. available through different controls.
  14878. @<RoasterConfWidget implementation@>=
  14879. RoasterConfWidget::RoasterConfWidget(DeviceTreeModel *model, const QModelIndex &index)
  14880. : BasicDeviceConfigurationWidget(model, index)
  14881. {
  14882. QVBoxLayout *layout = new QVBoxLayout;
  14883. QPushButton *addDeviceButton = new QPushButton(tr("Add Device"));
  14884. QMenu *deviceMenu = new QMenu;
  14885. NodeInserter *insertAction;
  14886. foreach(insertAction, AppInstance->topLevelNodeInserters)
  14887. {
  14888. connect(insertAction, SIGNAL(triggered(QString, QString)),
  14889. this, SLOT(insertChildNode(QString, QString)));
  14890. deviceMenu->addAction(insertAction);
  14891. }
  14892. addDeviceButton->setMenu(deviceMenu);
  14893. layout->addWidget(addDeviceButton);
  14894. QPushButton *addAnnotationControlButton = new QPushButton(tr("Add Annotation Control"));
  14895. QMenu *annotationMenu = new QMenu;
  14896. NodeInserter *basicButtonInserter = new NodeInserter(tr("Annotation Button"), tr("Annotation Button"), "annotationbutton");
  14897. NodeInserter *countingButtonInserter = new NodeInserter(tr("Counting Button"), tr("Counting Button"), "reconfigurablebutton");
  14898. NodeInserter *spinBoxInserter = new NodeInserter(tr("Numeric Entry"), tr("Numeric Entry"), "annotationspinbox");
  14899. NodeInserter *freeAnnotationInserter = new NodeInserter(tr("Free Text"),
  14900. tr("Free Text"),
  14901. "freeannotation");
  14902. annotationMenu->addAction(basicButtonInserter);
  14903. annotationMenu->addAction(countingButtonInserter);
  14904. annotationMenu->addAction(spinBoxInserter);
  14905. annotationMenu->addAction(freeAnnotationInserter);
  14906. connect(basicButtonInserter, SIGNAL(triggered(QString, QString)),
  14907. this, SLOT(insertChildNode(QString, QString)));
  14908. connect(countingButtonInserter, SIGNAL(triggered(QString, QString)),
  14909. this, SLOT(insertChildNode(QString, QString)));
  14910. connect(spinBoxInserter, SIGNAL(triggered(QString, QString)),
  14911. this, SLOT(insertChildNode(QString, QString)));
  14912. connect(freeAnnotationInserter, SIGNAL(triggered(QString, QString)),
  14913. this, SLOT(insertChildNode(QString, QString)));
  14914. @<Add annotation control node inserters@>@;
  14915. addAnnotationControlButton->setMenu(annotationMenu);
  14916. layout->addWidget(addAnnotationControlButton);
  14917. QPushButton *timersButton = new QPushButton(tr("Extra Timers"));
  14918. QMenu *timersMenu = new QMenu;
  14919. NodeInserter *coolingTimerInserter = new NodeInserter(tr("Cooling Timer"), tr("Cooling Timer"), "coolingtimer");
  14920. NodeInserter *rangeTimerInserter = new NodeInserter(tr("Range Timer"), tr("Range Timer"), "rangetimer");
  14921. NodeInserter *multirangeTimerInserter = new NodeInserter(tr("Multi-Range Timer"), tr("Multi-Range Timer"), "multirangetimer");
  14922. timersMenu->addAction(coolingTimerInserter);
  14923. timersMenu->addAction(rangeTimerInserter);
  14924. timersMenu->addAction(multirangeTimerInserter);
  14925. connect(coolingTimerInserter, SIGNAL(triggered(QString, QString)),
  14926. this, SLOT(insertChildNode(QString, QString)));
  14927. connect(rangeTimerInserter, SIGNAL(triggered(QString, QString)),
  14928. this, SLOT(insertChildNode(QString, QString)));
  14929. connect(multirangeTimerInserter, SIGNAL(triggered(QString, QString)),
  14930. this, SLOT(insertChildNode(QString, QString)));
  14931. timersButton->setMenu(timersMenu);
  14932. layout->addWidget(timersButton);
  14933. QPushButton *advancedButton = new QPushButton(tr("Advanced Features"));
  14934. QMenu *advancedMenu = new QMenu;
  14935. NodeInserter *linearsplineinserter = new NodeInserter(tr("Linear Spline Interpolated Series"), tr("Linear Spline Interpolated Series"), "linearspline");
  14936. advancedMenu->addAction(linearsplineinserter);
  14937. NodeInserter *translationinserter = new NodeInserter(tr("Profile Translation"), tr("Profile Translation"), "translation");
  14938. advancedMenu->addAction(translationinserter);
  14939. connect(linearsplineinserter, SIGNAL(triggered(QString, QString)), this, SLOT(insertChildNode(QString, QString)));
  14940. connect(translationinserter, SIGNAL(triggered(QString, QString)), this, SLOT(insertChildNode(QString, QString)));
  14941. @<Add node inserters to advanced features menu@>@;
  14942. advancedButton->setMenu(advancedMenu);
  14943. layout->addWidget(advancedButton);
  14944. QHBoxLayout *idLayout = new QHBoxLayout;
  14945. QLabel *idLabel = new QLabel(tr("Machine ID for database:"));
  14946. idLayout->addWidget(idLabel);
  14947. QSpinBox *id = new QSpinBox;
  14948. idLayout->addWidget(id);
  14949. idLayout->addStretch();
  14950. layout->addLayout(idLayout);
  14951. QHBoxLayout *capacityLayout = new QHBoxLayout;
  14952. QCheckBox *capacityCheckEnabled = new QCheckBox(tr("Maximum batch size:"));
  14953. QDoubleSpinBox *capacity = new QDoubleSpinBox;
  14954. capacity->setMinimum(0.0);
  14955. capacity->setDecimals(3);
  14956. capacity->setMaximum(999999.999);
  14957. QComboBox *capacityUnit = new QComboBox;
  14958. capacityUnit->addItem("g");
  14959. capacityUnit->addItem("Kg");
  14960. capacityUnit->addItem("oz");
  14961. capacityUnit->addItem("Lb");
  14962. capacityUnit->setCurrentIndex(3);
  14963. capacityLayout->addWidget(capacityCheckEnabled);
  14964. capacityLayout->addWidget(capacity);
  14965. capacityLayout->addWidget(capacityUnit);
  14966. capacityLayout->addStretch();
  14967. layout->addLayout(capacityLayout);
  14968. layout->addStretch();
  14969. @<Get device configuration data for current node@>@;
  14970. for(int i = 0; i < configData.size(); i++)
  14971. {
  14972. node = configData.at(i).toElement();
  14973. if(node.attribute("name") == "databaseid")
  14974. {
  14975. id->setValue(node.attribute("value").toInt());
  14976. }
  14977. else if(node.attribute("name") == "checkcapacity")
  14978. {
  14979. capacityCheckEnabled->setChecked(node.attribute("value") == "true");
  14980. }
  14981. else if(node.attribute("name") == "capacity")
  14982. {
  14983. capacity->setValue(node.attribute("value").toDouble());
  14984. }
  14985. else if(node.attribute("name") == "capacityunit")
  14986. {
  14987. capacityUnit->setCurrentIndex(capacityUnit->findText(node.attribute("value")));
  14988. }
  14989. }
  14990. updateRoasterId(id->value());
  14991. connect(id, SIGNAL(valueChanged(int)), this, SLOT(updateRoasterId(int)));
  14992. connect(capacityCheckEnabled, SIGNAL(stateChanged(int)), this, SLOT(updateCapacityCheck(int)));
  14993. connect(capacity, SIGNAL(valueChanged(QString)), this, SLOT(updateCapacity(QString)));
  14994. connect(capacityUnit, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateCapacityUnit(QString)));
  14995. setLayout(layout);
  14996. }
  14997. @ Iterating over the configuration data associated with the current node is
  14998. required in nearly every configuration widget. The specifics of the loop
  14999. vary, but there is likely a better way to generalize that. Until then,
  15000. obtaining a |QDomNodeList| with the required data to iterate over has been
  15001. split off as its own chunk to reduce the risk of errors associated with typing
  15002. the same code many times.
  15003. @<Get device configuration data for current node@>=
  15004. QDomElement referenceElement =
  15005. model->referenceElement(model->data(index, Qt::UserRole).toString());
  15006. QDomNodeList configData = referenceElement.elementsByTagName("attribute");
  15007. QDomElement node;
  15008. @ We need to propagate changes to the configuration fields to the device
  15009. configuration document. The |updateAttribute()| method in the base class
  15010. makes this trivial.
  15011. @<RoasterConfWidget implementation@>=
  15012. void RoasterConfWidget::updateRoasterId(int id)
  15013. {
  15014. updateAttribute("databaseid", QString("%1").arg(id));
  15015. }
  15016. void RoasterConfWidget::updateCapacityCheck(int value)
  15017. {
  15018. updateAttribute("checkcapacity", value == Qt::Checked ? "true" : "false");
  15019. }
  15020. void RoasterConfWidget::updateCapacity(const QString &value)
  15021. {
  15022. updateAttribute("capacity", value);
  15023. }
  15024. void RoasterConfWidget::updateCapacityUnit(const QString &value)
  15025. {
  15026. updateAttribute("capacityunit", value);
  15027. }
  15028. @ Finally we must register the configuration widget so that it can be
  15029. instantiated at the appropriate time.
  15030. @<Register device configuration widgets@>=
  15031. app.registerDeviceConfigurationWidget("roaster", RoasterConfWidget::staticMetaObject);
  15032. @** Configuration for NI-DAQmx Base devices.
  15033. \noindent The primary concern in supporting hardware that communicates through
  15034. NI-DAQmx Base is in configurations using a single NI USB 9211 (for NI-DAQmx
  15035. Base 2.x) or NI USB 9211A (for NI-DAQmx Base 3.x), but if it is reasonable to
  15036. do so I'@q'@>d like to later add support for multiple device configurations and
  15037. limited support for other devices including the ability to use devices with
  15038. voltage inputs for non-temperature measurement data. The top priority, however,
  15039. is in continuing to support hardware that people are already using with Typica.
  15040. In order to more easily implement these future plans, device configuration is
  15041. handled with three configuration tiers. The top level configuration node
  15042. indicates that we are using NI-DAQmx Base. Here we can add a child node
  15043. representing either a NI USB 9211 or NI USB 9211A. From a configuration
  15044. perspective these are identical with the default node name as the only
  15045. difference. From the device configuration we can specify the device identifier
  15046. and add channels to the device. In the channel nodes we specify the
  15047. thermocouple type.
  15048. @<Class declarations@>=
  15049. class NiDaqMxBaseDriverConfWidget : public BasicDeviceConfigurationWidget
  15050. {
  15051. @[Q_OBJECT@]@;
  15052. public:@/
  15053. @[Q_INVOKABLE@]@, NiDaqMxBaseDriverConfWidget(DeviceTreeModel *model,@|
  15054. const QModelIndex &index);
  15055. };
  15056. @ There is very little to configure here so there isn'@q'@>t much for the
  15057. constructor to do. We do need to keep a reference to the node we are
  15058. configuring so that child nodes can later be added. At present, no real
  15059. configuration data other than the existence of the node is required so
  15060. there is no need to read any configuration data here.
  15061. @<NiDaqMxBaseDriverConfWidget implementation@>=
  15062. NiDaqMxBaseDriverConfWidget::NiDaqMxBaseDriverConfWidget(
  15063. DeviceTreeModel *model, const QModelIndex &index) :
  15064. BasicDeviceConfigurationWidget(model, index)
  15065. {
  15066. QHBoxLayout *layout = new QHBoxLayout;
  15067. QToolButton *addDeviceButton = new QToolButton;
  15068. addDeviceButton->setText(tr("Add Device"));
  15069. NodeInserter *add9211 = new NodeInserter("NI USB 9211", "NI USB 9211",
  15070. "nidaqmxbase9211series");
  15071. NodeInserter *add9211a = new NodeInserter("NI USB 9211A", "NI USB 9211A",
  15072. "nidaqmxbase9211series");
  15073. connect(add9211, SIGNAL(triggered(QString, QString)),
  15074. this, SLOT(insertChildNode(QString, QString)));
  15075. connect(add9211a, SIGNAL(triggered(QString, QString)),
  15076. this, SLOT(insertChildNode(QString, QString)));
  15077. QMenu *deviceMenu = new QMenu;
  15078. deviceMenu->addAction(add9211);
  15079. deviceMenu->addAction(add9211a);
  15080. addDeviceButton->setMenu(deviceMenu);
  15081. addDeviceButton->setPopupMode(QToolButton::InstantPopup);
  15082. layout->addWidget(addDeviceButton);
  15083. setLayout(layout);
  15084. }
  15085. @ Both the NI USB 9211 and NI USB 9211A are identical from a configuration
  15086. perspective. The only difference is the version of NI-DAQmx Base required for
  15087. use. As the API does not provide a way of determining which version is
  15088. installed, ensuring that the appropriate software is installed without
  15089. conflicts is left as an exercise for the person attempting to use \pn{}.
  15090. @<Class declarations@>=
  15091. class NiDaqMxBase9211ConfWidget : public BasicDeviceConfigurationWidget
  15092. {
  15093. Q_OBJECT
  15094. public:
  15095. Q_INVOKABLE NiDaqMxBase9211ConfWidget(DeviceTreeModel *device,
  15096. const QModelIndex &index);
  15097. private slots:
  15098. void addChannel();
  15099. void updateDeviceId(const QString &newId);
  15100. };
  15101. @ There are two controls required in a configuration widget for this device.
  15102. The first is the device identifier (for example, "Dev1"), the second is a
  15103. button for adding channels to the device. On a generic device we would also
  15104. need to set the clock rate, but with this hardware it is possible to determine
  15105. the maximum clock rate from the channels defined.
  15106. @<NiDaqMxBase9211ConfWidget implementation@>=
  15107. NiDaqMxBase9211ConfWidget::NiDaqMxBase9211ConfWidget(DeviceTreeModel *model,
  15108. const QModelIndex &index)
  15109. : BasicDeviceConfigurationWidget(model, index)
  15110. {
  15111. QVBoxLayout *layout = new QVBoxLayout;
  15112. QHBoxLayout *deviceIdLayout = new QHBoxLayout;
  15113. QLabel *label = new QLabel(tr("Device ID:"));
  15114. QLineEdit *deviceId = new QLineEdit;
  15115. deviceIdLayout->addWidget(label);
  15116. deviceIdLayout->addWidget(deviceId);
  15117. QPushButton *addChannelButton = new QPushButton(tr("Add Channel"));
  15118. layout->addLayout(deviceIdLayout);
  15119. layout->addWidget(addChannelButton);
  15120. @<Get device configuration data for current node@>@;
  15121. for(int i = 0; i < configData.size(); i++)
  15122. {
  15123. node = configData.at(i).toElement();
  15124. if(node.attribute("name") == "deviceID")
  15125. {
  15126. deviceId->setText(node.attribute("value", "Dev1"));
  15127. break;
  15128. }
  15129. }
  15130. updateDeviceId(deviceId->text());
  15131. connect(addChannelButton, SIGNAL(clicked()),
  15132. this, SLOT(addChannel()));
  15133. connect(deviceId, SIGNAL(textEdited(QString)),
  15134. this, SLOT(updateDeviceId(QString)));
  15135. setLayout(layout);
  15136. }
  15137. @ Updating the attribute tag under the reference element associated with the
  15138. current node is handled in the base class so we just need to pass in the
  15139. appropriate name value pair.
  15140. @<NiDaqMxBase9211ConfWidget implementation@>=
  15141. void NiDaqMxBase9211ConfWidget::updateDeviceId(const QString &newId)
  15142. {
  15143. updateAttribute("deviceID", newId);
  15144. }
  15145. @ Adding channels is just like adding any other sort of node.
  15146. @<NiDaqMxBase9211ConfWidget implementation@>=
  15147. void NiDaqMxBase9211ConfWidget::addChannel()
  15148. {
  15149. insertChildNode(tr("Thermocouple channel"), "ni9211seriestc");
  15150. }
  15151. @ Finally, we need a configuration widget to handle our thermocouple channels.
  15152. Ordinarily we would need three pieces of information for each channel. First
  15153. there is the thermocouple channel. Previously this was left implied by the
  15154. order of requests for a new channel, but more flexible configuration options
  15155. become possible with a more explicit specification. Since this widget is device
  15156. specific, all of the options can be easily enumerated to match markings on the
  15157. device. Next is the thermocouple type. Many options are supported, but I would
  15158. like to ensure that the most commonly used choices are listed first. The other
  15159. piece of information that DAQmx or DAQmx Base require is the measurement unit.
  15160. As all of Typica'@q'@>s internal operations are in Fahrenheit there is no need to
  15161. make this configurable so long as everything else that can display temperature
  15162. measurements can perform the appropriate conversions.
  15163. Note that as there are no configuration differences between the various
  15164. device combinations using an NI 9211 module with regard to thermocouple channel
  15165. configuration, we can use this widget with all device combinations that make
  15166. use of such a module.
  15167. @<Class declarations@>=
  15168. class Ni9211TcConfWidget : public BasicDeviceConfigurationWidget
  15169. {@/
  15170. @[Q_OBJECT@]@;
  15171. public:@/
  15172. Q_INVOKABLE@,@, Ni9211TcConfWidget(DeviceTreeModel *device,
  15173. const QModelIndex &index);
  15174. @[private slots@]:@/
  15175. void updateThermocoupleType(const QString &type);
  15176. void updateColumnName(const QString &name);
  15177. void updateHidden(bool hidden);
  15178. };
  15179. @ This follows the same pattern of previous device configuration widgets. The
  15180. constructor provides the required configuration controls and slot methods
  15181. catch configuration changes and update the underlying XML document
  15182. appropriately.
  15183. @<Ni9211TcConfWidget implementation@>=
  15184. Ni9211TcConfWidget::Ni9211TcConfWidget(DeviceTreeModel *model,
  15185. const QModelIndex &index) :
  15186. BasicDeviceConfigurationWidget(model, index)
  15187. {
  15188. QFormLayout *layout = new QFormLayout;
  15189. QLineEdit *columnName = new QLineEdit;
  15190. layout->addRow(tr("Column Name:"), columnName);
  15191. QComboBox *typeSelector = new QComboBox;
  15192. typeSelector->addItem("J");
  15193. typeSelector->addItem("K");
  15194. typeSelector->addItem("T");
  15195. typeSelector->addItem("B");
  15196. typeSelector->addItem("E");
  15197. typeSelector->addItem("N");
  15198. typeSelector->addItem("R");
  15199. typeSelector->addItem("S");
  15200. layout->addRow(tr("Thermocouple Type:"), typeSelector);
  15201. QCheckBox *hideSeries = new QCheckBox("Hide this channel");
  15202. layout->addRow(hideSeries);
  15203. setLayout(layout);
  15204. @<Get device configuration data for current node@>@;
  15205. for(int i = 0; i < configData.size(); i++)
  15206. {
  15207. node = configData.at(i).toElement();
  15208. if(node.attribute("name") == "type")
  15209. {
  15210. typeSelector->setCurrentIndex(
  15211. typeSelector->findText(node.attribute("value")));
  15212. }
  15213. else if(node.attribute("name") == "columnname")
  15214. {
  15215. columnName->setText(node.attribute("value"));
  15216. }
  15217. else if(node.attribute("name") == "hidden")
  15218. {
  15219. hideSeries->setChecked(node.attribute("value") == "true");
  15220. }
  15221. }
  15222. updateThermocoupleType(typeSelector->currentText());
  15223. updateColumnName(columnName->text());
  15224. updateHidden(hideSeries->isChecked());
  15225. connect(typeSelector, SIGNAL(currentIndexChanged(QString)),
  15226. this, SLOT(updateThermocoupleType(QString)));
  15227. connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
  15228. connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
  15229. }
  15230. @ Two slots are used to pass configuration changes back to the underlying XML
  15231. representation.
  15232. @<Ni9211TcConfWidget implementation@>=
  15233. void Ni9211TcConfWidget::updateThermocoupleType(const QString &type)
  15234. {
  15235. updateAttribute("type", type);
  15236. }
  15237. void Ni9211TcConfWidget::updateColumnName(const QString &name)
  15238. {
  15239. updateAttribute("columnname", name);
  15240. }
  15241. void Ni9211TcConfWidget::updateHidden(bool hidden)
  15242. {
  15243. updateAttribute("hidden", hidden ? "true" : "false");
  15244. }
  15245. @ These three widgets need to be registered so the configuration widget can
  15246. instantiate them when the nodes are selected.
  15247. @<Register device configuration widgets@>=
  15248. app.registerDeviceConfigurationWidget("nidaqmxbase",
  15249. NiDaqMxBaseDriverConfWidget::staticMetaObject);
  15250. app.registerDeviceConfigurationWidget("nidaqmxbase9211series",
  15251. NiDaqMxBase9211ConfWidget::staticMetaObject);
  15252. app.registerDeviceConfigurationWidget("ni9211seriestc",
  15253. Ni9211TcConfWidget::staticMetaObject);
  15254. @ Furthermore, we should create the NodeInserter objects for adding top level
  15255. nodes to the configuration. Preferably we would only allow top level nodes to
  15256. be inserted when all prerequisite software is available.
  15257. @<Register top level device configuration nodes@>=
  15258. NodeInserter *inserter = new NodeInserter(tr("NI DAQmx Base Device"),
  15259. tr("NI DAQmx Base"),
  15260. "nidaqmxbase", NULL);
  15261. topLevelNodeInserters.append(inserter);
  15262. @** Configuration of NI-DAQmx devices.
  15263. \noindent The other main class of hardware currently supported in Typica is a
  15264. small set of devices that require NI-DAQmx. This includes a few combinations of
  15265. the NI 9211 in different carriers and the NI USB TC01. Additional hardware may
  15266. be added to this set in the future.
  15267. The approach here is very similar to the approach used to configure NI-DAQmx
  15268. Base devices, starting with a widget for adding child device nodes.
  15269. @<Class declarations@>=
  15270. class NiDaqMxDriverConfWidget : public BasicDeviceConfigurationWidget
  15271. {
  15272. @[Q_OBJECT@]@;
  15273. public:
  15274. @[Q_INVOKABLE@]@, NiDaqMxDriverConfWidget(DeviceTreeModel *model,
  15275. const QModelIndex &index);
  15276. };
  15277. @ Under our driver node we want to have the ability to insert device specific
  15278. child nodes.
  15279. @<NiDaqMxDriverConfWidget implementation@>=
  15280. NiDaqMxDriverConfWidget::NiDaqMxDriverConfWidget(DeviceTreeModel *model,
  15281. const QModelIndex &index)
  15282. : BasicDeviceConfigurationWidget(model, index)
  15283. {
  15284. QHBoxLayout *layout = new QHBoxLayout;
  15285. QToolButton *addDeviceButton = new QToolButton;
  15286. addDeviceButton->setText(tr("Add Device"));
  15287. NodeInserter *add9211a = new NodeInserter("NI USB 9211A", "NI USB 9211A",
  15288. "nidaqmx9211series");
  15289. NodeInserter *addtc01 = new NodeInserter("NI USB TC01", "NI USB TC01",
  15290. "nidaqmxtc01");
  15291. connect(add9211a, SIGNAL(triggered(QString, QString)),
  15292. this, SLOT(insertChildNode(QString, QString)));
  15293. connect(addtc01, SIGNAL(triggered(QString, QString)),
  15294. this, SLOT(insertChildNode(QString, QString)));
  15295. QMenu *deviceMenu = new QMenu;
  15296. deviceMenu->addAction(add9211a);
  15297. deviceMenu->addAction(addtc01);
  15298. addDeviceButton->setMenu(deviceMenu);
  15299. addDeviceButton->setPopupMode(QToolButton::InstantPopup);
  15300. layout->addWidget(addDeviceButton);
  15301. setLayout(layout);
  15302. }
  15303. @ Devices based on the 9211 module are essentially the same aside from device
  15304. naming convention. Configuring these is very similar to configuring similar
  15305. devices when using NI-DAQmx Base.
  15306. @<Class declarations@>=
  15307. class NiDaqMx9211ConfWidget : public BasicDeviceConfigurationWidget
  15308. {
  15309. @[Q_OBJECT@]@;
  15310. public:@/
  15311. @[Q_INVOKABLE@]@, NiDaqMx9211ConfWidget(DeviceTreeModel *model,
  15312. const QModelIndex &index);
  15313. @[private slots@]:@/
  15314. void addChannel();
  15315. void updateDeviceId(const QString &newId);
  15316. };
  15317. @ Implementation is essentially identical to the NI-DAQmx Base class. It is
  15318. likely that there will be differences if this is ever extended to support
  15319. automatic detection of connected hardware. While NI-DAQmx Base does not
  15320. provide a way to query device identifiers, it uses a consistent naming scheme
  15321. by which device identifiers can be discovered. While NI-DAQmx lacks the same
  15322. consistency in device identifiers, it does provide a way to query that
  15323. information.
  15324. @<NiDaqMx9211ConfWidget implementation@>=
  15325. NiDaqMx9211ConfWidget::NiDaqMx9211ConfWidget(DeviceTreeModel *model,
  15326. const QModelIndex &index)
  15327. : BasicDeviceConfigurationWidget(model, index)
  15328. {
  15329. QVBoxLayout *layout = new QVBoxLayout;
  15330. QHBoxLayout *deviceIdLayout = new QHBoxLayout;
  15331. QLabel *label = new QLabel(tr("Device ID:"));
  15332. QLineEdit *deviceId = new QLineEdit;
  15333. deviceIdLayout->addWidget(label);
  15334. deviceIdLayout->addWidget(deviceId);
  15335. QPushButton *addChannelButton = new QPushButton(tr("Add Channel"));
  15336. layout->addLayout(deviceIdLayout);
  15337. layout->addWidget(addChannelButton);
  15338. @<Get device configuration data for current node@>@;
  15339. for(int i = 0; i < configData.size(); i++)
  15340. {
  15341. node = configData.at(i).toElement();
  15342. if(node.attribute("name") == "deviceID")
  15343. {
  15344. deviceId->setText(node.attribute("value","Dev1"));
  15345. break;
  15346. }
  15347. }
  15348. updateDeviceId(deviceId->text());
  15349. connect(addChannelButton, SIGNAL(clicked()), this, SLOT(addChannel()));
  15350. connect(deviceId, SIGNAL(textEdited(QString)),
  15351. this, SLOT(updateDeviceId(QString)));
  15352. setLayout(layout);
  15353. }
  15354. void NiDaqMx9211ConfWidget::updateDeviceId(const QString &newId)
  15355. {
  15356. updateAttribute("deviceID", newId);
  15357. }
  15358. void NiDaqMx9211ConfWidget::addChannel()
  15359. {
  15360. insertChildNode(tr("Thermocouple channel"), "ni9211seriestc");
  15361. }
  15362. @ There is no need to create a configuration widget specific to the 9211 module
  15363. used in NI-DAQmx. The widget already used for NI-DAQmx Base can be used without
  15364. modification.
  15365. Configuring Typica for use with the NI USB-TC01 can be slightly simplified as
  15366. the device only has a single thermocouple channel. We can configure this without
  15367. requiring another node.
  15368. @<Class declarations@>=
  15369. class NiDaqMxTc01ConfWidget : public BasicDeviceConfigurationWidget
  15370. {
  15371. @[Q_OBJECT@]@;
  15372. public:@/
  15373. @[Q_INVOKABLE@]@, NiDaqMxTc01ConfWidget(DeviceTreeModel *model,
  15374. const QModelIndex &index);
  15375. @[private slots@]:@/
  15376. void updateDeviceId(const QString &newId);
  15377. void updateThermocoupleType(const QString &type);
  15378. void updateColumnName(const QString &name);
  15379. void updateHidden(bool hidden);
  15380. };
  15381. @ The implementation is similar to the other configuration widgets.
  15382. @<NiDaqMxTc01ConfWidget implementation@>=
  15383. NiDaqMxTc01ConfWidget::NiDaqMxTc01ConfWidget(DeviceTreeModel *model,
  15384. const QModelIndex &index)
  15385. : BasicDeviceConfigurationWidget(model, index)
  15386. {
  15387. QFormLayout *layout = new QFormLayout;
  15388. QLineEdit *deviceId = new QLineEdit;
  15389. layout->addRow(tr("Device ID:"), deviceId);
  15390. QLineEdit *columnName = new QLineEdit;
  15391. layout->addRow(tr("Column Name:"), columnName);
  15392. QComboBox *typeSelector = new QComboBox;
  15393. typeSelector->addItem("J");
  15394. typeSelector->addItem("K");
  15395. typeSelector->addItem("T");
  15396. typeSelector->addItem("B");
  15397. typeSelector->addItem("E");
  15398. typeSelector->addItem("N");
  15399. typeSelector->addItem("R");
  15400. typeSelector->addItem("S");
  15401. layout->addRow(tr("Thermocouple Type:"), typeSelector);
  15402. QCheckBox *hideSeries = new QCheckBox(tr("Hide this channel"));
  15403. layout->addRow(hideSeries);
  15404. @<Get device configuration data for current node@>@;
  15405. for(int i = 0; i < configData.size(); i++)
  15406. {
  15407. node = configData.at(i).toElement();
  15408. if(node.attribute("name") == "deviceID")
  15409. {
  15410. deviceId->setText(node.attribute("value"));
  15411. }
  15412. else if(node.attribute("name") == "type")
  15413. {
  15414. typeSelector->setCurrentIndex(typeSelector->findText(node.attribute("value")));
  15415. }
  15416. else if(node.attribute("name") == "columnname")
  15417. {
  15418. columnName->setText(node.attribute("value"));
  15419. }
  15420. else if(node.attribute("name") == "hidden")
  15421. {
  15422. hideSeries->setChecked(node.attribute("value") == "true");
  15423. }
  15424. }
  15425. updateDeviceId(deviceId->text());
  15426. updateThermocoupleType(typeSelector->currentText());
  15427. updateColumnName(columnName->text());
  15428. updateHidden(hideSeries->isChecked());
  15429. connect(deviceId, SIGNAL(textEdited(QString)), this, SLOT(updateDeviceId(QString)));
  15430. connect(typeSelector, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateThermocoupleType(QString)));
  15431. connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
  15432. setLayout(layout);
  15433. connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
  15434. }
  15435. void NiDaqMxTc01ConfWidget::updateDeviceId(const QString &newId)
  15436. {
  15437. updateAttribute("deviceID", newId);
  15438. }
  15439. void NiDaqMxTc01ConfWidget::updateThermocoupleType(const QString &type)
  15440. {
  15441. updateAttribute("type", type);
  15442. }
  15443. void NiDaqMxTc01ConfWidget::updateColumnName(const QString &name)
  15444. {
  15445. updateAttribute("columnname", name);
  15446. }
  15447. void NiDaqMxTc01ConfWidget::updateHidden(bool hidden)
  15448. {
  15449. updateAttribute("hidden", hidden ? "true" : "false");
  15450. }
  15451. @ These configuration widgets need to be registered so they can be instantiated
  15452. in response to node selections.
  15453. @<Register device configuration widgets@>=
  15454. app.registerDeviceConfigurationWidget("nidaqmx", NiDaqMxDriverConfWidget::staticMetaObject);
  15455. app.registerDeviceConfigurationWidget("nidaqmx9211series", NiDaqMx9211ConfWidget::staticMetaObject);
  15456. app.registerDeviceConfigurationWidget("nidaqmxtc01", NiDaqMxTc01ConfWidget::staticMetaObject);
  15457. @ A |NodeInserter| for the driver configuration widget is also needed. Note that
  15458. at present NI DAQmx is only available on Windows so we do not bother to show
  15459. the option on other platforms. It would be generally preferable to replace this
  15460. with a check at runtime to determine if the required library exists. That could
  15461. be done with anything that requires third party installed code, leaving by
  15462. default only those options which have no external dependencies.
  15463. @<Register top level device configuration nodes@>=
  15464. #ifdef Q_OS_WIN32
  15465. inserter = new NodeInserter(tr("NI DAQmx Device"), tr("NI DAQmx"), "nidaqmx", NULL);
  15466. topLevelNodeInserters.append(inserter);
  15467. #endif
  15468. @** Configuration of Serial Port Devices.
  15469. \noindent It is possible to communicate with a number of devices through a
  15470. serial port. To do this, the appropriate settings for opening the port are
  15471. required and the communications protocol understood by the device must be
  15472. known. Serial port communications are provided by QextSerialPort. That
  15473. project was released under the MIT license.\nfnote{See the license text for
  15474. more information.} Additional headers are required.
  15475. @<Header files to include@>=
  15476. #include "qextserialport.h"
  15477. #include "qextserialenumerator.h"
  15478. @ Some custom widgets are provided which allow selecting the relevant
  15479. connection options from combo boxes. First there is a widget for selecting
  15480. the desired serial port. The drop down is pre-populated with any serial ports
  15481. that could be automatically detected, but the field can also be edited to
  15482. other values as may be required if the hardware is not connected during
  15483. configuration.
  15484. @<Class declarations@>=
  15485. class PortSelector : public QComboBox
  15486. {
  15487. Q_OBJECT
  15488. public:
  15489. PortSelector(QWidget *parent = NULL);
  15490. private slots:
  15491. void addDevice(QextPortInfo port);
  15492. private:
  15493. QextSerialEnumerator *lister;
  15494. };
  15495. @ The implementation is trivial.
  15496. @<PortSelector implementation@>=
  15497. PortSelector::PortSelector(QWidget *parent) : QComboBox(parent),
  15498. lister(new QextSerialEnumerator)
  15499. {
  15500. QList<QextPortInfo> ports = QextSerialEnumerator::getPorts();
  15501. QextPortInfo port;
  15502. foreach(port, ports)
  15503. {
  15504. #ifdef Q_OS_WIN32
  15505. addItem(port.portName);
  15506. #else
  15507. addItem(port.physName);
  15508. #endif
  15509. }
  15510. lister->setUpNotifications();
  15511. connect(lister, SIGNAL(deviceDiscovered(QextPortInfo)),
  15512. this, SLOT(addDevice(QextPortInfo)));
  15513. setEditable(true);
  15514. }
  15515. void PortSelector::addDevice(QextPortInfo port)
  15516. {
  15517. addItem(port.portName);
  15518. }
  15519. @ Next is a widget which allows selecting the baud rate. Only rates supported
  15520. by the current operating system are available to select.
  15521. A later version of QextSerialPort than is used by \pn{} provides a helper
  15522. class which can be used more conveniently to create this sort of control. As
  15523. this is not yet available to \pn{}, we instead copy the |enum| specifying
  15524. the appropriate values into the class and use Qt'@q'@>s meta-object system to
  15525. populate the combo box based on the values in that |enum|.
  15526. @<Class declarations@>=
  15527. class BaudSelector : public QComboBox
  15528. {
  15529. Q_OBJECT
  15530. Q_ENUMS(BaudRateType)
  15531. public:
  15532. BaudSelector(QWidget *parent = NULL);
  15533. enum BaudRateType
  15534. {
  15535. #if defined(Q_OS_UNIX) || defined(qdoc)
  15536. BAUD50 = 50, //POSIX ONLY
  15537. BAUD75 = 75, //POSIX ONLY
  15538. BAUD134 = 134, //POSIX ONLY
  15539. BAUD150 = 150, //POSIX ONLY
  15540. BAUD200 = 200, //POSIX ONLY
  15541. BAUD1800 = 1800, //POSIX ONLY
  15542. #if defined(B76800) || defined(qdoc)
  15543. BAUD76800 = 76800, //POSIX ONLY
  15544. #endif
  15545. #if (defined(B230400) && defined(B4000000)) || defined(qdoc)
  15546. BAUD230400 = 230400, //POSIX ONLY
  15547. BAUD460800 = 460800, //POSIX ONLY
  15548. BAUD500000 = 500000, //POSIX ONLY
  15549. BAUD576000 = 576000, //POSIX ONLY
  15550. BAUD921600 = 921600, //POSIX ONLY
  15551. BAUD1000000 = 1000000, //POSIX ONLY
  15552. BAUD1152000 = 1152000, //POSIX ONLY
  15553. BAUD1500000 = 1500000, //POSIX ONLY
  15554. BAUD2000000 = 2000000, //POSIX ONLY
  15555. BAUD2500000 = 2500000, //POSIX ONLY
  15556. BAUD3000000 = 3000000, //POSIX ONLY
  15557. BAUD3500000 = 3500000, //POSIX ONLY
  15558. BAUD4000000 = 4000000, //POSIX ONLY
  15559. #endif
  15560. #endif
  15561. #if defined(Q_OS_WIN) || defined(qdoc)
  15562. BAUD14400 = 14400, //WINDOWS ONLY
  15563. BAUD56000 = 56000, //WINDOWS ONLY
  15564. BAUD128000 = 128000, //WINDOWS ONLY
  15565. BAUD256000 = 256000, //WINDOWS ONLY
  15566. #endif
  15567. BAUD110 = 110,
  15568. BAUD300 = 300,
  15569. BAUD600 = 600,
  15570. BAUD1200 = 1200,
  15571. BAUD2400 = 2400,
  15572. BAUD4800 = 4800,
  15573. BAUD9600 = 9600,
  15574. BAUD19200 = 19200,
  15575. BAUD38400 = 38400,
  15576. BAUD57600 = 57600,
  15577. BAUD115200 = 115200
  15578. };
  15579. };
  15580. @ As the |enum| values are identical to the baud rates represented, we only
  15581. the numeric values, ignoring the names which are rather ugly.
  15582. @<BaudSelector implementation@>=
  15583. BaudSelector::BaudSelector(QWidget *parent) : QComboBox(parent)
  15584. {
  15585. QMetaObject meta = BaudSelector::staticMetaObject;
  15586. QMetaEnum type = meta.enumerator(meta.indexOfEnumerator("BaudRateType"));
  15587. for(int i = 0; i < type.keyCount(); i++)
  15588. {
  15589. addItem(QString("%1").arg(type.value(i)));
  15590. }
  15591. }
  15592. @ This same technique is used in a widget for selecting parity.
  15593. @<Class declarations@>=
  15594. class ParitySelector : public QComboBox
  15595. {
  15596. Q_OBJECT
  15597. Q_ENUMS(ParityType)
  15598. public:
  15599. ParitySelector(QWidget *parent = NULL);
  15600. enum ParityType
  15601. {
  15602. PAR_NONE,
  15603. PAR_ODD,
  15604. PAR_EVEN,
  15605. #if defined(Q_OS_WIN) || defined(qdoc)
  15606. PAR_MARK, //WINDOWS ONLY
  15607. #endif
  15608. PAR_SPACE
  15609. };
  15610. };
  15611. @ Implementation is similar to |BaudSelector| but as the values have no
  15612. apparent relation to what is represented we present the value names, placing
  15613. the corresponding value in the user data space associated with each entry.
  15614. The names here are ugly and not amenable to localization so this approach
  15615. should be reconsidered later.
  15616. @<ParitySelector implementation@>=
  15617. ParitySelector::ParitySelector(QWidget *parent) : QComboBox(parent)
  15618. {
  15619. QMetaObject meta = ParitySelector::staticMetaObject;
  15620. QMetaEnum type = meta.enumerator(meta.indexOfEnumerator("ParityType"));
  15621. for(int i = 0; i < type.keyCount(); i++)
  15622. {
  15623. addItem(QString(type.key(i)), QVariant(type.value(i)));
  15624. }
  15625. }
  15626. @ Similarly, we have a widget for selecting a method for flow control.
  15627. @<Class declarations@>=
  15628. class FlowSelector : public QComboBox
  15629. {
  15630. @[Q_OBJECT@]@;
  15631. @[Q_ENUMS(FlowType)@]@;
  15632. public:@/
  15633. FlowSelector(QWidget *parent = NULL);
  15634. enum FlowType
  15635. {
  15636. FLOW_OFF,
  15637. FLOW_HARDWARE,
  15638. FLOW_XONXOFF
  15639. };
  15640. };
  15641. @ Implementation follows the same pattern as in |ParitySelector|.
  15642. @<FlowSelector implementation@>=
  15643. FlowSelector::FlowSelector(QWidget *parent) : QComboBox(parent)
  15644. {
  15645. QMetaObject meta = FlowSelector::staticMetaObject;
  15646. QMetaEnum type = meta.enumerator(meta.indexOfEnumerator("FlowType"));
  15647. for(int i = 0; i < type.keyCount(); i++)
  15648. {
  15649. addItem(QString(type.key(i)), QVariant(type.value(i)));
  15650. }
  15651. }
  15652. @ We assume that the number of data bits will always be 8, though it may be
  15653. useful to later provide a control for selecting this for use with other devices
  15654. where this may not be assumed or for the sake of completion. This only leaves
  15655. specifying the number of stop bits.
  15656. @<Class declarations@>=
  15657. class StopSelector : public QComboBox
  15658. {
  15659. @[Q_OBJECT@]@;
  15660. @[Q_ENUMS(StopBitsType)@]@;
  15661. public:@/
  15662. StopSelector(QWidget *parent = NULL);
  15663. enum StopBitsType
  15664. {
  15665. STOP_1,
  15666. #if defined(Q_OS_WIN) || defined(qdoc)
  15667. STOP_1_5, //WINDOWS ONLY
  15668. #endif
  15669. STOP_2
  15670. };
  15671. };
  15672. @ Implementation should be familiar by now.
  15673. @<StopSelector implementation@>=
  15674. StopSelector::StopSelector(QWidget *parent) : QComboBox(parent)
  15675. {
  15676. QMetaObject meta = StopSelector::staticMetaObject;
  15677. QMetaEnum type = meta.enumerator(meta.indexOfEnumerator("StopBitsType"));
  15678. for(int i = 0; i < type.keyCount(); i++)
  15679. {
  15680. addItem(QString(type.key(i)), QVariant(type.value(i)));
  15681. }
  15682. }
  15683. @** Configuration of Serial Devices Using Modbus RTU.
  15684. \noindent The following sections contain the details of older code related to
  15685. Modbus RTU support. While the various selector widgets are shared with the new
  15686. code, the configuration widgets and device interfaces are being replaced.
  15687. One protocol that is used across a broad class of devices is called
  15688. Modbus RTU. This protocol allows multiple devices to be chained together on a
  15689. two wire bus which can be connected to a single serial port. The communication
  15690. protocol involves a single message which is sent from a master device (in this
  15691. case the computer running Typica) to a slave device (the device we would like
  15692. to obtain information from) which is followed by a response message from the
  15693. slave to the master. After a brief wait the master can then send another
  15694. message to any slave on the bus and this process repeats. Every outgoing
  15695. message provides a station address to identify which slave on the bus should
  15696. respond, a function code to identify which of a broad class of operations has
  15697. been requested, the required data for the function specified, and a cyclic
  15698. redundancy check to validate the message.
  15699. @** A Spin Box with Hexadecimal Representation.
  15700. \noindent Common convention for communications documentation for devices that
  15701. use Modbus RTU is that relative addresses are specified in hexadecimal
  15702. representation. In order to simplify initial device configuration, it would be
  15703. best that input widgets both accept input in base 16 and display values as a
  15704. four digit hexadecimal value.
  15705. @<Class declarations@>=
  15706. class ShortHexSpinBox : public QSpinBox
  15707. {
  15708. @[Q_OBJECT@]@;
  15709. public:@/
  15710. ShortHexSpinBox(QWidget *parent = NULL);
  15711. virtual QValidator::State validate(QString &input, int &pos) const;@/
  15712. protected:@/
  15713. virtual int valueFromText(const QString &text) const;
  15714. virtual QString textFromValue(int value) const;@/
  15715. };
  15716. @ For this we can set some new defaults in the constructor and must override
  15717. three methods.
  15718. @<ShortHexSpinBox implementation@>=
  15719. ShortHexSpinBox::ShortHexSpinBox(QWidget *parent) : QSpinBox(parent)
  15720. {
  15721. setMinimum(0);
  15722. setMaximum(0xFFFF);
  15723. setPrefix("0x");
  15724. setMinimumWidth(65);
  15725. }
  15726. QValidator::State ShortHexSpinBox::validate(QString &input, int &) const
  15727. {
  15728. if(input.size() == 2)
  15729. {
  15730. return QValidator::Intermediate;
  15731. }
  15732. bool okay;
  15733. input.toInt(&okay, 16);
  15734. if(okay)
  15735. {
  15736. return QValidator::Acceptable;
  15737. }
  15738. return QValidator::Invalid;
  15739. }
  15740. int ShortHexSpinBox::valueFromText(const QString &text) const
  15741. {
  15742. return text.toInt(NULL, 16);
  15743. }
  15744. QString ShortHexSpinBox::textFromValue(int value) const
  15745. {
  15746. QString retval;
  15747. retval.setNum(value, 16);
  15748. while(retval.size() < 4)
  15749. {
  15750. retval.prepend("0");
  15751. }
  15752. return retval.toUpper();
  15753. }
  15754. @** Configuration Widgets for Modbus RTU Devices.
  15755. \noindent While the top level configuration widgets seen so far have not had
  15756. any configuration details beyond the ability to add devices under the driver,
  15757. in the case of a serial port with Modbus RTU devices it is reasonable to
  15758. provide the connection details which will be shared by all devices on the bus.
  15759. @<Class declarations@>=
  15760. class ModbusRtuPortConfWidget : public BasicDeviceConfigurationWidget
  15761. {
  15762. @[Q_OBJECT@]@;
  15763. public:
  15764. @[Q_INVOKABLE@]@, ModbusRtuPortConfWidget(DeviceTreeModel *model,
  15765. const QModelIndex &index);
  15766. @[private slots@]:@/
  15767. void updatePort(const QString &newPort);
  15768. void updateBaudRate(const QString &newRate);
  15769. void updateParity(const QString &newParity);
  15770. void updateFlowControl(const QString &newFlow);
  15771. void updateStopBits(const QString &newStopBits);
  15772. };
  15773. @ Aside from the extra information compared with other configuration widgets
  15774. previously described, there is nothing surprising about the implementation.
  15775. @<ModbusRtuPortConfWidget implementation@>=
  15776. ModbusRtuPortConfWidget::ModbusRtuPortConfWidget(DeviceTreeModel *model,
  15777. const QModelIndex &index)
  15778. : BasicDeviceConfigurationWidget(model, index)
  15779. {
  15780. QFormLayout *layout = new QFormLayout;
  15781. QToolButton *addDeviceButton = new QToolButton;
  15782. addDeviceButton->setText(tr("Add Device"));
  15783. NodeInserter *addModbusRtuDevice = new NodeInserter("Modbus RTU Device",
  15784. "Modbus RTU Device",
  15785. "modbusrtudevice");
  15786. connect(addModbusRtuDevice, SIGNAL(triggered(QString, QString)),
  15787. this, SLOT(insertChildNode(QString, QString)));
  15788. QMenu *deviceMenu = new QMenu;
  15789. deviceMenu->addAction(addModbusRtuDevice);
  15790. addDeviceButton->setMenu(deviceMenu);
  15791. addDeviceButton->setPopupMode(QToolButton::InstantPopup);
  15792. layout->addRow(QString(), addDeviceButton);
  15793. PortSelector *port = new PortSelector;
  15794. layout->addRow(tr("Port:"), port);
  15795. connect(port, SIGNAL(currentIndexChanged(QString)),
  15796. this, SLOT(updatePort(QString)));
  15797. connect(port, SIGNAL(editTextChanged(QString)),
  15798. this, SLOT(updatePort(QString)));
  15799. BaudSelector *rate = new BaudSelector;
  15800. layout->addRow(tr("Baud:"), rate);
  15801. connect(rate, SIGNAL(currentIndexChanged(QString)),
  15802. this, SLOT(updateBaudRate(QString)));
  15803. ParitySelector *parity = new ParitySelector;
  15804. layout->addRow(tr("Parity:"), parity);
  15805. connect(parity, SIGNAL(currentIndexChanged(QString)),
  15806. this, SLOT(updateParity(QString)));
  15807. FlowSelector *flow = new FlowSelector;
  15808. layout->addRow(tr("Flow Control:"), flow);
  15809. connect(flow, SIGNAL(currentIndexChanged(QString)),
  15810. this, SLOT(updateFlowControl(QString)));
  15811. StopSelector *stop = new StopSelector;
  15812. layout->addRow(tr("Stop Bits:"), stop);
  15813. connect(stop, SIGNAL(currentIndexChanged(QString)),
  15814. this, SLOT(updateStopBits(QString)));
  15815. @<Get device configuration data for current node@>@;
  15816. for(int i = 0; i < configData.size(); i++)
  15817. {
  15818. node = configData.at(i).toElement();
  15819. if(node.attribute("name") == "port")
  15820. {
  15821. int j = port->findText(node.attribute("value"));
  15822. if(j >= 0)
  15823. {
  15824. port->setCurrentIndex(j);
  15825. }
  15826. else
  15827. {
  15828. port->insertItem(0, node.attribute("value"));
  15829. port->setCurrentIndex(0);
  15830. }
  15831. }
  15832. else if(node.attribute("name") == "baudrate")
  15833. {
  15834. rate->setCurrentIndex(rate->findText(node.attribute("value")));
  15835. }
  15836. else if(node.attribute("name") == "parity")
  15837. {
  15838. parity->setCurrentIndex(parity->findText(node.attribute("value")));
  15839. }
  15840. else if(node.attribute("name") == "flowcontrol")
  15841. {
  15842. flow->setCurrentIndex(flow->findText(node.attribute("value")));
  15843. }
  15844. else if(node.attribute("name") == "stopbits")
  15845. {
  15846. stop->setCurrentIndex(stop->findText(node.attribute("value")));
  15847. }
  15848. }
  15849. updatePort(port->currentText());
  15850. updateBaudRate(rate->currentText());
  15851. updateParity(parity->currentText());
  15852. updateFlowControl(flow->currentText());
  15853. updateStopBits(stop->currentText());
  15854. setLayout(layout);
  15855. }
  15856. void ModbusRtuPortConfWidget::updatePort(const QString &newPort)
  15857. {
  15858. updateAttribute("port", newPort);
  15859. }
  15860. void ModbusRtuPortConfWidget::updateBaudRate(const QString &newRate)
  15861. {
  15862. updateAttribute("baudrate", newRate);
  15863. }
  15864. void ModbusRtuPortConfWidget::updateParity(const QString &newParity)
  15865. {
  15866. updateAttribute("parity", newParity);
  15867. }
  15868. void ModbusRtuPortConfWidget::updateFlowControl(const QString &newFlow)
  15869. {
  15870. updateAttribute("flowcontrol", newFlow);
  15871. }
  15872. void ModbusRtuPortConfWidget::updateStopBits(const QString &newStopBits)
  15873. {
  15874. updateAttribute("stopbits", newStopBits);
  15875. }
  15876. @ From here we need to provide a widget for configuring a particular device.
  15877. At a minimum this would require setting the station number to a value between
  15878. 0 and 255. Zero is typically the broadcast address which reaches all devices
  15879. on the bus and is not generally recommended for use except in particular
  15880. circumstances. There are, however, a number of settings that influence all of
  15881. the currently supported child nodes and these settings are in the device
  15882. configuration widget instead of requiring that information to be duplicated
  15883. across multiple child nodes.
  15884. The Modbus RTU protocol is very general in scope and leaves many of the
  15885. details of how to do certain things up to the manufacturer. For rudimentary
  15886. support of devices using this protocol, the documentation for several devices
  15887. was consulted and a test rig with one device was set up. There are a number of
  15888. assumptions made for this initial support and to better support additional
  15889. device classes it may become necessary to expand on what is provided initially.
  15890. The primary focus presently is on the use of PID controllers as temperature
  15891. indicators with the ability to modify a set value in the case where this is
  15892. used as a controller rather than just a display.
  15893. All of the devices studied prior to adding this support made use of scaled
  15894. integer representation. In order to correctly determine the measured process
  15895. value it is necessary to know the unit of the measurement and the position of
  15896. the decimal point. It is generally possible to query this information, however
  15897. it may be useful to provide a way to specify fixed values in the event that a
  15898. device exposes these details in a way that is incompatible with my assumptions.
  15899. @<Class declarations@>=
  15900. class ModbusRtuDeviceConfWidget : public BasicDeviceConfigurationWidget
  15901. {
  15902. @[Q_OBJECT@]@;
  15903. public:@/
  15904. @[Q_INVOKABLE@]@, ModbusRtuDeviceConfWidget(DeviceTreeModel *model,
  15905. const QModelIndex &index);
  15906. @[private slots@]:@/
  15907. void updateStationNumber(int newStation);
  15908. void updateFixedUnit(bool newFixed);
  15909. void updateFixedDecimal(bool newFixed);
  15910. void updateUnit(const QString &newUnit);
  15911. void updateUnitAddress(int newAddress);
  15912. void updateValueF(int newValue);
  15913. void updateValueC(int newValue);
  15914. void updatePrecisionAddress(int newAddress);
  15915. void updatePrecisionValue(int newValue);
  15916. private:@/
  15917. QStackedLayout *unitSpecificationLayout;
  15918. QStackedLayout *decimalSpecificationLayout;
  15919. };
  15920. @ This widget has a number of differences from previous configuration widgets.
  15921. Perhaps most significantly there are controls which do not provide a text based
  15922. signal on state change. We also set certain controls as disabled when the
  15923. provided values are not relevant to operations such as when switching between
  15924. fixed decimal position and looking up decimal position from the device. Aside
  15925. from these details the widget operates according to the same principles as the
  15926. other widgets already seen.
  15927. @<ModbusRtuDeviceConfWidget implementation@>=
  15928. ModbusRtuDeviceConfWidget::ModbusRtuDeviceConfWidget(DeviceTreeModel *model,
  15929. const QModelIndex &index)
  15930. : BasicDeviceConfigurationWidget(model, index),
  15931. unitSpecificationLayout(new QStackedLayout),
  15932. decimalSpecificationLayout(new QStackedLayout)
  15933. {
  15934. QVBoxLayout *layout = new QVBoxLayout;
  15935. QToolButton *addChannelButton = new QToolButton;
  15936. addChannelButton->setText(tr("Add Channel"));
  15937. NodeInserter *addTemperaturePV = new NodeInserter("Temperature Process Value",
  15938. "Temperature Process Value",
  15939. "modbustemperaturepv");
  15940. NodeInserter *addTemperatureSV = new NodeInserter("Temperature Set Value",
  15941. "Temperature Set Value",
  15942. "modbustemperaturesv");
  15943. connect(addTemperaturePV, SIGNAL(triggered(QString, QString)),
  15944. this, SLOT(insertChildNode(QString, QString)));
  15945. connect(addTemperatureSV, SIGNAL(triggered(QString, QString)),
  15946. this, SLOT(insertChildNode(QString, QString)));
  15947. QMenu *channelMenu = new QMenu;
  15948. channelMenu->addAction(addTemperaturePV);
  15949. channelMenu->addAction(addTemperatureSV);
  15950. addChannelButton->setMenu(channelMenu);
  15951. addChannelButton->setPopupMode(QToolButton::InstantPopup);
  15952. layout->addWidget(addChannelButton);
  15953. QHBoxLayout *stationLayout = new QHBoxLayout;
  15954. QLabel *stationLabel = new QLabel(tr("Station:"));
  15955. QSpinBox *stationNumber = new QSpinBox;
  15956. stationNumber->setMinimum(0);
  15957. stationNumber->setMaximum(255);
  15958. stationLayout->addWidget(stationLabel);
  15959. stationLayout->addWidget(stationNumber);
  15960. layout->addLayout(stationLayout);
  15961. QCheckBox *fixedUnit = new QCheckBox(tr("Fixed Temperature Unit"));
  15962. layout->addWidget(fixedUnit);
  15963. QWidget *fixedUnitPlaceholder = new QWidget(this);
  15964. QHBoxLayout *fixedUnitLayout = new QHBoxLayout;
  15965. QLabel *fixedUnitLabel = new QLabel(tr("Temperature Unit:"));
  15966. QComboBox *fixedUnitSelector = new QComboBox;
  15967. fixedUnitSelector->addItem("Fahrenheit");
  15968. fixedUnitSelector->addItem("Celsius");
  15969. fixedUnitLayout->addWidget(fixedUnitLabel);
  15970. fixedUnitLayout->addWidget(fixedUnitSelector);
  15971. fixedUnitPlaceholder->setLayout(fixedUnitLayout);
  15972. unitSpecificationLayout->addWidget(fixedUnitPlaceholder);
  15973. QWidget *queriedUnitPlaceholder = new QWidget(this);
  15974. QFormLayout *queriedUnitLayout = new QFormLayout;
  15975. ShortHexSpinBox *unitAddress = new ShortHexSpinBox;
  15976. queriedUnitLayout->addRow(tr("Function 0x03 Unit Address:"), unitAddress);
  15977. QSpinBox *valueF = new QSpinBox;
  15978. valueF->setMinimum(0);
  15979. valueF->setMaximum(65535);
  15980. queriedUnitLayout->addRow(tr("Value for Fahrenheit"), valueF);
  15981. QSpinBox *valueC = new QSpinBox;
  15982. valueC->setMinimum(0);
  15983. valueC->setMaximum(65535);
  15984. queriedUnitLayout->addRow(tr("Value for Celsius"), valueC);
  15985. queriedUnitPlaceholder->setLayout(queriedUnitLayout);
  15986. unitSpecificationLayout->addWidget(queriedUnitPlaceholder);
  15987. layout->addLayout(unitSpecificationLayout);
  15988. QCheckBox *fixedPrecision = new QCheckBox(tr("Fixed Precision"));
  15989. layout->addWidget(fixedPrecision);
  15990. QWidget *fixedPrecisionPlaceholder = new QWidget(this);
  15991. QFormLayout *fixedPrecisionLayout = new QFormLayout;
  15992. QSpinBox *fixedPrecisionValue = new QSpinBox;
  15993. fixedPrecisionValue->setMinimum(0);
  15994. fixedPrecisionValue->setMaximum(9);
  15995. fixedPrecisionLayout->addRow("Places after the decimal point:",
  15996. fixedPrecisionValue);
  15997. fixedPrecisionPlaceholder->setLayout(fixedPrecisionLayout);
  15998. decimalSpecificationLayout->addWidget(fixedPrecisionPlaceholder);
  15999. QWidget *queriedPrecisionPlaceholder = new QWidget(this);
  16000. QFormLayout *queriedPrecisionLayout = new QFormLayout;
  16001. ShortHexSpinBox *precisionAddress = new ShortHexSpinBox;
  16002. queriedPrecisionLayout->addRow("Function 0x03 Decimal Position Address:",
  16003. precisionAddress);
  16004. queriedPrecisionPlaceholder->setLayout(queriedPrecisionLayout);
  16005. decimalSpecificationLayout->addWidget(queriedPrecisionPlaceholder);
  16006. layout->addLayout(decimalSpecificationLayout);
  16007. @<Get device configuration data for current node@>@;
  16008. for(int i = 0; i < configData.size(); i++)
  16009. {
  16010. node = configData.at(i).toElement();
  16011. if(node.attribute("name") == "station")
  16012. {
  16013. stationNumber->setValue(node.attribute("value").toInt());
  16014. }
  16015. else if(node.attribute("name") == "fixedunit")
  16016. {
  16017. if(node.attribute("value") == "true")
  16018. {
  16019. fixedUnit->setCheckState(Qt::Checked);
  16020. }
  16021. else if(node.attribute("value") == "false")
  16022. {
  16023. fixedUnit->setCheckState(Qt::Unchecked);
  16024. }
  16025. }
  16026. else if(node.attribute("name") == "fixedprecision")
  16027. {
  16028. fixedPrecisionValue->setValue(node.attribute("value").toInt());
  16029. }
  16030. else if(node.attribute("name") == "unit")
  16031. {
  16032. fixedUnitSelector->setCurrentIndex(fixedUnitSelector->findText(node.attribute("value")));
  16033. }
  16034. else if(node.attribute("name") == "unitaddress")
  16035. {
  16036. unitAddress->setValue(node.attribute("value").toInt());
  16037. }
  16038. else if(node.attribute("name") == "fvalue")
  16039. {
  16040. valueF->setValue(node.attribute("value").toInt());
  16041. }
  16042. else if(node.attribute("name") == "cvalue")
  16043. {
  16044. valueC->setValue(node.attribute("value").toInt());
  16045. }
  16046. else if(node.attribute("name") == "precisionaddress")
  16047. {
  16048. precisionAddress->setValue(node.attribute("value").toInt());
  16049. }
  16050. else if(node.attribute("name") == "precision")
  16051. {
  16052. fixedPrecisionValue->setValue(node.attribute("value").toInt());
  16053. }
  16054. }
  16055. updateStationNumber(stationNumber->value());
  16056. updateFixedUnit(fixedUnit->isChecked());
  16057. updateFixedDecimal(fixedPrecision->isChecked());
  16058. updateUnit(fixedUnitSelector->currentText());
  16059. updateUnitAddress(unitAddress->value());
  16060. updateValueF(valueF->value());
  16061. updateValueC(valueC->value());
  16062. updatePrecisionAddress(precisionAddress->value());
  16063. updatePrecisionValue(fixedPrecisionValue->value());
  16064. connect(stationNumber, SIGNAL(valueChanged(int)),
  16065. this, SLOT(updateStationNumber(int)));
  16066. connect(fixedUnitSelector, SIGNAL(currentIndexChanged(QString)),
  16067. this, SLOT(updateUnit(QString)));
  16068. connect(unitAddress, SIGNAL(valueChanged(int)),
  16069. this, SLOT(updateUnitAddress(int)));
  16070. connect(valueF, SIGNAL(valueChanged(int)),
  16071. this, SLOT(updateValueF(int)));
  16072. connect(valueC, SIGNAL(valueChanged(int)),
  16073. this, SLOT(updateValueC(int)));
  16074. connect(fixedUnit, SIGNAL(toggled(bool)),
  16075. this, SLOT(updateFixedUnit(bool)));
  16076. connect(fixedPrecision, SIGNAL(toggled(bool)),
  16077. this, SLOT(updateFixedDecimal(bool)));
  16078. connect(fixedPrecisionValue, SIGNAL(valueChanged(int)),
  16079. this, SLOT(updatePrecisionValue(int)));
  16080. connect(precisionAddress, SIGNAL(valueChanged(int)),
  16081. this, SLOT(updatePrecisionAddress(int)));
  16082. setLayout(layout);
  16083. }
  16084. void ModbusRtuDeviceConfWidget::updateStationNumber(int newStation)
  16085. {
  16086. updateAttribute("station", QString("%1").arg(newStation));
  16087. }
  16088. void ModbusRtuDeviceConfWidget::updateFixedUnit(bool newFixed)
  16089. {
  16090. if(newFixed)
  16091. {
  16092. unitSpecificationLayout->setCurrentIndex(0);
  16093. updateAttribute("fixedunit", "true");
  16094. }
  16095. else
  16096. {
  16097. unitSpecificationLayout->setCurrentIndex(1);
  16098. updateAttribute("fixedunit", "false");
  16099. }
  16100. }
  16101. void ModbusRtuDeviceConfWidget::updateFixedDecimal(bool newFixed)
  16102. {
  16103. if(newFixed)
  16104. {
  16105. decimalSpecificationLayout->setCurrentIndex(0);
  16106. updateAttribute("fixedprecision", "true");
  16107. }
  16108. else
  16109. {
  16110. decimalSpecificationLayout->setCurrentIndex(1);
  16111. updateAttribute("fixedprecision", "false");
  16112. }
  16113. }
  16114. void ModbusRtuDeviceConfWidget::updateUnit(const QString &newUnit)
  16115. {
  16116. updateAttribute("unit", newUnit);
  16117. }
  16118. void ModbusRtuDeviceConfWidget::updateUnitAddress(int newAddress)
  16119. {
  16120. updateAttribute("unitaddress", QString("%1").arg(newAddress));
  16121. }
  16122. void ModbusRtuDeviceConfWidget::updateValueF(int newValue)
  16123. {
  16124. updateAttribute("fvalue", QString("%1").arg(newValue));
  16125. }
  16126. void ModbusRtuDeviceConfWidget::updateValueC(int newValue)
  16127. {
  16128. updateAttribute("cvalue", QString("%1").arg(newValue));
  16129. }
  16130. void ModbusRtuDeviceConfWidget::updatePrecisionAddress(int newAddress)
  16131. {
  16132. updateAttribute("precisionaddress", QString("%1").arg(newAddress));
  16133. }
  16134. void ModbusRtuDeviceConfWidget::updatePrecisionValue(int newValue)
  16135. {
  16136. updateAttribute("precision", QString("%1").arg(newValue));
  16137. }
  16138. @ Initial Modbus RTU support is very limited and only considers temperature
  16139. process and set values. While in some cases it would be possible to cleverly
  16140. adapt this support to broader categories this is an area that must be extended
  16141. later to cover at least unitless control values and on/off status values. It
  16142. would be ideal to cover a broad range of useful properties. To read process
  16143. values we need to know the address that the current process value can be read
  16144. from.
  16145. @<Class declarations@>=
  16146. class ModbusRtuDeviceTPvConfWidget : public BasicDeviceConfigurationWidget
  16147. {
  16148. @[Q_OBJECT@]@/
  16149. public:@/
  16150. @[Q_INVOKABLE@]@, ModbusRtuDeviceTPvConfWidget(DeviceTreeModel *model,
  16151. const QModelIndex &index);
  16152. @[private slots@]:@/
  16153. void updateAddress(int newAddress);
  16154. };
  16155. @ This requires only a single field to store the address to query the current
  16156. process value.
  16157. @<ModbusRtuDeviceTPvConfWidget implementation@>=
  16158. ModbusRtuDeviceTPvConfWidget::ModbusRtuDeviceTPvConfWidget(DeviceTreeModel *model,
  16159. const QModelIndex &index)
  16160. : BasicDeviceConfigurationWidget(model, index)
  16161. {
  16162. QFormLayout *layout = new QFormLayout;
  16163. ShortHexSpinBox *address = new ShortHexSpinBox;
  16164. layout->addRow(tr("Function 0x04 Process Value Address"), address);
  16165. @<Get device configuration data for current node@>@;
  16166. for(int i = 0; i < configData.size(); i++)
  16167. {
  16168. node = configData.at(i).toElement();
  16169. if(node.attribute("name") == "address")
  16170. {
  16171. address->setValue(node.attribute("value").toInt());
  16172. break;
  16173. }
  16174. }
  16175. updateAddress(address->value());
  16176. connect(address, SIGNAL(valueChanged(int)), this, SLOT(updateAddress(int)));
  16177. setLayout(layout);
  16178. }
  16179. void ModbusRtuDeviceTPvConfWidget::updateAddress(int newAddress)
  16180. {
  16181. updateAttribute("address", QString("%1").arg(newAddress));
  16182. }
  16183. @ Set values are slightly more complicated as we may want either a fixed range
  16184. or the ability to query the device for its current allowed range, but nothing
  16185. is here that hasn'@q'@>t been seen elsewhere.
  16186. @<Class declarations@>=
  16187. class ModbusRtuDeviceTSvConfWidget : public BasicDeviceConfigurationWidget@/
  16188. {@/
  16189. @[Q_OBJECT@]@;
  16190. public:@/
  16191. Q_INVOKABLE@, ModbusRtuDeviceTSvConfWidget(DeviceTreeModel *model,
  16192. const QModelIndex &index);
  16193. @[private slots@]:@/
  16194. void updateReadAddress(int newAddress);
  16195. void updateWriteAddress(int newAddress);
  16196. void updateFixedRange(bool fixed);
  16197. void updateLower(const QString &lower);
  16198. void updateUpper(const QString &upper);
  16199. void updateLowerAddress(int newAddress);
  16200. void updateUpperAddress(int newAddress);@/
  16201. private:@/
  16202. QStackedLayout *boundsLayout;
  16203. };
  16204. @ Upper and lower bounds when operating on a fixed range are still subject to
  16205. decimal position rules in the parent node. It may be a good idea to enforce
  16206. this, however at present the person configuring the system is trusted to know
  16207. what they are doing.
  16208. @<ModbusRtuDeviceTSvConfWidget implementation@>=
  16209. ModbusRtuDeviceTSvConfWidget::ModbusRtuDeviceTSvConfWidget(DeviceTreeModel *model,
  16210. const QModelIndex &index)
  16211. : BasicDeviceConfigurationWidget(model, index), boundsLayout(new QStackedLayout)
  16212. {
  16213. QVBoxLayout *layout = new QVBoxLayout;
  16214. QFormLayout *addressLayout = new QFormLayout;
  16215. ShortHexSpinBox *readAddress = new ShortHexSpinBox;
  16216. ShortHexSpinBox *writeAddress = new ShortHexSpinBox;
  16217. addressLayout->addRow(tr("Function 0x04 Read Set Value Address:"), readAddress);
  16218. addressLayout->addRow(tr("Function 0x06 Write Set Value Address:"), writeAddress);
  16219. layout->addLayout(addressLayout);
  16220. QCheckBox *fixedRange = new QCheckBox(tr("Fixed Set Value Range"));
  16221. layout->addWidget(fixedRange);
  16222. QWidget *queriedRangePlaceholder = new QWidget(this);
  16223. QFormLayout *queriedRangeLayout = new QFormLayout;
  16224. ShortHexSpinBox *lowerAddress = new ShortHexSpinBox;
  16225. ShortHexSpinBox *upperAddress = new ShortHexSpinBox;
  16226. queriedRangeLayout->addRow(tr("Function 0x03 Minimum Set Value Address"),
  16227. lowerAddress);
  16228. queriedRangeLayout->addRow(tr("Function 0x03 Maximum Set Value Address"),
  16229. upperAddress);
  16230. queriedRangePlaceholder->setLayout(queriedRangeLayout);
  16231. boundsLayout->addWidget(queriedRangePlaceholder);
  16232. QWidget *fixedRangePlaceholder = new QWidget(this);
  16233. QFormLayout *fixedRangeLayout = new QFormLayout;
  16234. QLineEdit *fixedLower = new QLineEdit;
  16235. QLineEdit *fixedUpper = new QLineEdit;
  16236. fixedRangeLayout->addRow(tr("Minimum Set Value:"), fixedLower);
  16237. fixedRangeLayout->addRow(tr("Maximum Set Value:"), fixedUpper);
  16238. fixedRangePlaceholder->setLayout(fixedRangeLayout);
  16239. boundsLayout->addWidget(fixedRangePlaceholder);
  16240. layout->addLayout(boundsLayout);
  16241. @<Get device configuration data for current node@>@;
  16242. for(int i = 0; i < configData.size(); i++)
  16243. {
  16244. node = configData.at(i).toElement();
  16245. if(node.attribute("name") == "readaddress")
  16246. {
  16247. readAddress->setValue(node.attribute("value").toInt());
  16248. }
  16249. else if(node.attribute("name") == "writeaddress")
  16250. {
  16251. writeAddress->setValue(node.attribute("value").toInt());
  16252. }
  16253. else if(node.attribute("name") == "fixedrange")
  16254. {
  16255. if(node.attribute("value") == "true")
  16256. {
  16257. fixedRange->setCheckState(Qt::Checked);
  16258. }
  16259. else if(node.attribute("value") == "false")
  16260. {
  16261. fixedRange->setCheckState(Qt::Unchecked);
  16262. }
  16263. }
  16264. else if(node.attribute("name") == "fixedlower")
  16265. {
  16266. fixedLower->setText(node.attribute("value"));
  16267. }
  16268. else if(node.attribute("name") == "fixedupper")
  16269. {
  16270. fixedUpper->setText(node.attribute("value"));
  16271. }
  16272. else if(node.attribute("name") == "loweraddress")
  16273. {
  16274. lowerAddress->setValue(node.attribute("value").toInt());
  16275. }
  16276. else if(node.attribute("name") == "upperaddress")
  16277. {
  16278. upperAddress->setValue(node.attribute("value").toInt());
  16279. }
  16280. }
  16281. updateReadAddress(readAddress->value());
  16282. updateWriteAddress(writeAddress->value());
  16283. updateFixedRange(fixedRange->isChecked());
  16284. updateLower(fixedLower->text());
  16285. updateUpper(fixedUpper->text());
  16286. updateLowerAddress(lowerAddress->value());
  16287. updateUpperAddress(upperAddress->value());
  16288. connect(readAddress, SIGNAL(valueChanged(int)),
  16289. this, SLOT(updateReadAddress(int)));
  16290. connect(writeAddress, SIGNAL(valueChanged(int)),
  16291. this, SLOT(updateWriteAddress(int)));
  16292. connect(fixedRange, SIGNAL(toggled(bool)), this, SLOT(updateFixedRange(bool)));
  16293. connect(fixedLower, SIGNAL(textChanged(QString)),
  16294. this, SLOT(updateLower(QString)));
  16295. connect(fixedUpper, SIGNAL(textChanged(QString)),
  16296. this, SLOT(updateUpper(QString)));
  16297. connect(lowerAddress, SIGNAL(valueChanged(int)),
  16298. this, SLOT(updateLowerAddress(int)));
  16299. connect(upperAddress, SIGNAL(valueChanged(int)),
  16300. this, SLOT(updateUpperAddress(int)));
  16301. setLayout(layout);
  16302. }
  16303. void ModbusRtuDeviceTSvConfWidget::updateReadAddress(int newAddress)
  16304. {
  16305. updateAttribute("readaddress", QString("%1").arg(newAddress));
  16306. }
  16307. void ModbusRtuDeviceTSvConfWidget::updateWriteAddress(int newAddress)
  16308. {
  16309. updateAttribute("writeaddress", QString("%1").arg(newAddress));
  16310. }
  16311. void ModbusRtuDeviceTSvConfWidget::updateFixedRange(bool fixed)
  16312. {
  16313. if(fixed)
  16314. {
  16315. updateAttribute("fixedrange", "true");
  16316. boundsLayout->setCurrentIndex(1);
  16317. }
  16318. else
  16319. {
  16320. updateAttribute("fixedrange", "false");
  16321. boundsLayout->setCurrentIndex(0);
  16322. }
  16323. }
  16324. void ModbusRtuDeviceTSvConfWidget::updateLower(const QString &lower)
  16325. {
  16326. updateAttribute("fixedlower", lower);
  16327. }
  16328. void ModbusRtuDeviceTSvConfWidget::updateUpper(const QString &upper)
  16329. {
  16330. updateAttribute("fixedupper", upper);
  16331. }
  16332. void ModbusRtuDeviceTSvConfWidget::updateLowerAddress(int newAddress)
  16333. {
  16334. updateAttribute("loweraddress", QString("%1").arg(newAddress));
  16335. }
  16336. void ModbusRtuDeviceTSvConfWidget::updateUpperAddress(int newAddress)
  16337. {
  16338. updateAttribute("upperaddress", QString("%1").arg(newAddress));
  16339. }
  16340. @ The configuration widgets need to be registered.
  16341. @<Register device configuration widgets@>=
  16342. app.registerDeviceConfigurationWidget("modbusrtuport", ModbusRtuPortConfWidget::staticMetaObject);
  16343. app.registerDeviceConfigurationWidget("modbusrtudevice", ModbusRtuDeviceConfWidget::staticMetaObject);
  16344. app.registerDeviceConfigurationWidget("modbustemperaturepv", ModbusRtuDeviceTPvConfWidget::staticMetaObject);
  16345. app.registerDeviceConfigurationWidget("modbustemperaturesv", ModbusRtuDeviceTSvConfWidget::staticMetaObject);
  16346. @ A |NodeInserter| for the driver configuration widget is also needed. Note
  16347. that this is temporarily disabled. These configuration widgets will become
  16348. useful when I rearchitect the Modbus RTU support in a future release.
  16349. @<Register top level device configuration nodes@>=
  16350. #if 0
  16351. inserter = new NodeInserter(tr("Modbus RTU Port"), tr("Modbus RTU Port"), "modbusrtuport", NULL);
  16352. topLevelNodeInserters.append(inserter);
  16353. #endif
  16354. @** Configuration of Annotation Controls.
  16355. \noindent Aside from the details of hardware devices, the logging view must
  16356. also be able to set up log annotation controls. A few different control types
  16357. are offered. These include simple push buttons which insert a fixed annotation
  16358. when activated, push buttons which insert a value that includes a number which
  16359. is incremented every time the button is pressed, free text entry fields, and
  16360. numeric entry fields.
  16361. The basic push button control should allow configuration of both the button
  16362. text and the annotation text.
  16363. @<Class declarations@>=
  16364. class AnnotationButtonConfWidget : public BasicDeviceConfigurationWidget
  16365. {
  16366. @[Q_OBJECT@]@;
  16367. public:@/
  16368. @[Q_INVOKABLE@]@, AnnotationButtonConfWidget(DeviceTreeModel *model, const QModelIndex &index);
  16369. @[private slots@]:@/
  16370. void updateButtonText(const QString &text);
  16371. void updateAnnotationText(const QString &text);
  16372. };
  16373. @ The constructor sets up the controls for editing this data.
  16374. @<AnnotationButtonConfWidget implementation@>=
  16375. AnnotationButtonConfWidget::AnnotationButtonConfWidget(DeviceTreeModel *model, const QModelIndex &index)
  16376. : BasicDeviceConfigurationWidget(model, index)
  16377. {
  16378. QFormLayout *layout = new QFormLayout;
  16379. QLineEdit *buttonTextEdit = new QLineEdit;
  16380. QLineEdit *annotationTextEdit = new QLineEdit;
  16381. layout->addRow(tr("Button Text:"), buttonTextEdit);
  16382. layout->addRow(tr("Annotation Text:"), annotationTextEdit);
  16383. @<Get device configuration data for current node@>@;
  16384. for(int i = 0; i < configData.size(); i++)
  16385. {
  16386. node = configData.at(i).toElement();
  16387. if(node.attribute("name") == "buttontext")
  16388. {
  16389. buttonTextEdit->setText(node.attribute("value"));
  16390. }
  16391. else if(node.attribute("name") == "annotationtext")
  16392. {
  16393. annotationTextEdit->setText(node.attribute("value"));
  16394. }
  16395. }
  16396. updateButtonText(buttonTextEdit->text());
  16397. updateAnnotationText(annotationTextEdit->text());
  16398. connect(buttonTextEdit, SIGNAL(textEdited(QString)), this, SLOT(updateButtonText(QString)));
  16399. connect(annotationTextEdit, SIGNAL(textEdited(QString)), this, SLOT(updateAnnotationText(QString)));
  16400. setLayout(layout);
  16401. }
  16402. @ The slots are implemented trivially.
  16403. @<AnnotationButtonConfWidget implementation@>=
  16404. void AnnotationButtonConfWidget::updateButtonText(const QString &text)
  16405. {
  16406. updateAttribute("buttontext", text);
  16407. }
  16408. void AnnotationButtonConfWidget::updateAnnotationText(const QString &text)
  16409. {
  16410. updateAttribute("annotationtext", text);
  16411. }
  16412. @ The control must be registered.
  16413. @<Register device configuration widgets@>=
  16414. app.registerDeviceConfigurationWidget("annotationbutton", AnnotationButtonConfWidget::staticMetaObject);
  16415. @ Closely related to the previous control is one which provides parameterized
  16416. text. Technically this is not needed as both this and the previous
  16417. configuration control map to the same widget in the logging view and
  16418. parameterized annotation text can be used with either. The reason for
  16419. separating these is to indicate that it should be possible to change the text
  16420. and reset the number without altering the default configuration or requiring a
  16421. reinitialization of the logging view.
  16422. @<Class declarations@>=
  16423. class ReconfigurableAnnotationButtonConfWidget : public BasicDeviceConfigurationWidget@/
  16424. {@/
  16425. @[Q_OBJECT@]@;
  16426. public:@/
  16427. Q_INVOKABLE ReconfigurableAnnotationButtonConfWidget(DeviceTreeModel *model, const QModelIndex &index);
  16428. @[private slots@]:@/
  16429. void updateButtonText(const QString &text);
  16430. void updateAnnotationText(const QString &text);
  16431. };
  16432. @ The key difference in implementation is the addition of some documentation on
  16433. how to specify a numeric placeholder in the annotation text.
  16434. @<AnnotationButtonConfWidget implementation@>=
  16435. ReconfigurableAnnotationButtonConfWidget::ReconfigurableAnnotationButtonConfWidget(DeviceTreeModel *model, const QModelIndex &index)
  16436. : BasicDeviceConfigurationWidget(model, index)
  16437. {
  16438. QFormLayout *layout = new QFormLayout;
  16439. QLineEdit *buttonTextEdit = new QLineEdit;
  16440. QLineEdit *annotationTextEdit = new QLineEdit;
  16441. annotationTextEdit->setText("%A-%1");
  16442. layout->addRow(tr("Button Text:"), buttonTextEdit);
  16443. layout->addRow(tr("Annotation Text:"), annotationTextEdit);
  16444. @<Get device configuration data for current node@>@;
  16445. for(int i = 0; i < configData.size(); i++)
  16446. {
  16447. node = configData.at(i).toElement();
  16448. if(node.attribute("name") == "buttontext")
  16449. {
  16450. buttonTextEdit->setText(node.attribute("value"));
  16451. }
  16452. else if(node.attribute("name") == "annotationtext")
  16453. {
  16454. annotationTextEdit->setText(node.attribute("value"));
  16455. }
  16456. }
  16457. updateButtonText(buttonTextEdit->text());
  16458. updateAnnotationText(annotationTextEdit->text());
  16459. connect(buttonTextEdit, SIGNAL(textEdited(QString)), this, SLOT(updateButtonText(QString)));
  16460. connect(annotationTextEdit, SIGNAL(textEdited(QString)), this, SLOT(updateAnnotationText(QString)));
  16461. QTextEdit *documentation = new QTextEdit;
  16462. documentation->setHtml(tr("If the <b>Annotation Text</b> contains <tt>%1</tt>, this will be replaced in the annotation with a number that increments each time the button is pressed."));
  16463. documentation->setReadOnly(true);
  16464. layout->addRow("", documentation);
  16465. setLayout(layout);
  16466. }
  16467. void ReconfigurableAnnotationButtonConfWidget::updateButtonText(const QString &text)
  16468. {
  16469. updateAttribute("buttontext", text);
  16470. }
  16471. void ReconfigurableAnnotationButtonConfWidget::updateAnnotationText(const QString &text)
  16472. {
  16473. updateAttribute("annotationtext", text);
  16474. }
  16475. @ The control must be registered as usual.
  16476. @<Register device configuration widgets@>=
  16477. app.registerDeviceConfigurationWidget("reconfigurablebutton", ReconfigurableAnnotationButtonConfWidget::staticMetaObject);
  16478. @ While it is generally better to have all measurements logged automatically,
  16479. many roasters would like to keep track of infrequently altered control
  16480. variables which have not been set up for automated logging. A reading from the
  16481. manometer after a fuel adjustment, for example, is frequently not available by
  16482. automated means. In cases such as this, providing for numeric annotation entry
  16483. may be desired. The |AnnotationSpinBox| provides for this. There are a few
  16484. details that are important in this. First is a label to better indicate to the
  16485. operator what values in this control represent. The range of allowed values and
  16486. the number of decimal places is important. This control also allows the
  16487. specification of text to precede and/or follow the numeric value and this must
  16488. be configurable.
  16489. @<Class declarations@>=
  16490. class NoteSpinConfWidget : public BasicDeviceConfigurationWidget
  16491. {
  16492. @[Q_OBJECT@]@;
  16493. public:@/
  16494. @[Q_INVOKABLE@]@, NoteSpinConfWidget(DeviceTreeModel *model, const QModelIndex &index);
  16495. @[private slots@]:@/
  16496. void updateLabel(const QString &text);
  16497. void updateMinimum(const QString &minimum);
  16498. void updateMaximum(const QString &maximum);
  16499. void updatePrecision(int precision);
  16500. void updatePretext(const QString &text);
  16501. void updatePosttext(const QString &text);
  16502. };
  16503. @ There is nothing new in the implementation of note.
  16504. @<NoteSpinConfWidget implementation@>=
  16505. NoteSpinConfWidget::NoteSpinConfWidget(DeviceTreeModel *model, const QModelIndex &index)
  16506. : BasicDeviceConfigurationWidget(model, index)
  16507. {
  16508. QFormLayout *layout = new QFormLayout;
  16509. QLineEdit *labelEdit = new QLineEdit;
  16510. layout->addRow(tr("Control Label: "), labelEdit);
  16511. QLineEdit *minimumEdit = new QLineEdit;
  16512. layout->addRow(tr("Minimum Value: "), minimumEdit);
  16513. QLineEdit *maximumEdit = new QLineEdit;
  16514. layout->addRow(tr("Maximum Value: "), maximumEdit);
  16515. QSpinBox *precisionEdit = new QSpinBox;
  16516. precisionEdit->setMinimum(0);
  16517. precisionEdit->setMaximum(9);
  16518. layout->addRow(tr("Precision"), precisionEdit);
  16519. QLineEdit *pretext = new QLineEdit;
  16520. layout->addRow(tr("Prefix text"), pretext);
  16521. QLineEdit *posttext = new QLineEdit;
  16522. layout->addRow(tr("Suffix text"), posttext);
  16523. @<Get device configuration data for current node@>@;
  16524. for(int i = 0; i < configData.size(); i++)
  16525. {
  16526. node = configData.at(i).toElement();
  16527. if(node.attribute("name") == "label")
  16528. {
  16529. labelEdit->setText(node.attribute("value"));
  16530. }
  16531. else if(node.attribute("name") == "minimum")
  16532. {
  16533. minimumEdit->setText(node.attribute("value"));
  16534. }
  16535. else if(node.attribute("name") == "maximum")
  16536. {
  16537. maximumEdit->setText(node.attribute("value"));
  16538. }
  16539. else if(node.attribute("name") == "precision")
  16540. {
  16541. precisionEdit->setValue(node.attribute("value").toInt());
  16542. }
  16543. else if(node.attribute("name") == "pretext")
  16544. {
  16545. pretext->setText(node.attribute("value"));
  16546. }
  16547. else if(node.attribute("name") == "posttext")
  16548. {
  16549. posttext->setText(node.attribute("value"));
  16550. }
  16551. }
  16552. updateLabel(labelEdit->text());
  16553. updateMinimum(minimumEdit->text());
  16554. updateMaximum(maximumEdit->text());
  16555. updatePrecision(precisionEdit->value());
  16556. updatePretext(pretext->text());
  16557. updatePosttext(posttext->text());
  16558. connect(labelEdit, SIGNAL(textEdited(QString)), this, SLOT(updateLabel(QString)));
  16559. connect(minimumEdit, SIGNAL(textEdited(QString)), this, SLOT(updateMinimum(QString)));
  16560. connect(maximumEdit, SIGNAL(textEdited(QString)), this, SLOT(updateMaximum(QString)));
  16561. connect(precisionEdit, SIGNAL(valueChanged(int)), this, SLOT(updatePrecision(int)));
  16562. connect(pretext, SIGNAL(textEdited(QString)), this, SLOT(updatePretext(QString)));
  16563. connect(posttext, SIGNAL(textEdited(QString)), this, SLOT(updatePosttext(QString)));
  16564. setLayout(layout);
  16565. }
  16566. void NoteSpinConfWidget::updateLabel(const QString &text)
  16567. {
  16568. updateAttribute("label", text);
  16569. }
  16570. void NoteSpinConfWidget::updateMinimum(const QString &minimum)
  16571. {
  16572. updateAttribute("minimum", minimum);
  16573. }
  16574. void NoteSpinConfWidget::updateMaximum(const QString &maximum)
  16575. {
  16576. updateAttribute("maximum", maximum);
  16577. }
  16578. void NoteSpinConfWidget::updatePrecision(int precision)
  16579. {
  16580. updateAttribute("precision", QString("%1").arg(precision));
  16581. }
  16582. void NoteSpinConfWidget::updatePretext(const QString &text)
  16583. {
  16584. updateAttribute("pretext", text);
  16585. }
  16586. void NoteSpinConfWidget::updatePosttext(const QString &text)
  16587. {
  16588. updateAttribute("posttext", text);
  16589. }
  16590. @ Configuration widget registration is as usual.
  16591. @<Register device configuration widgets@>=
  16592. app.registerDeviceConfigurationWidget("annotationspinbox", NoteSpinConfWidget::staticMetaObject);
  16593. @i freeannotation.w
  16594. @i settings.w
  16595. @** Communicating with a Device through Modbus RTU.
  16596. \noindent The classes described here need to be further generalized to support
  16597. communications with multiple devices on the same port. The interface is based
  16598. on the |DAQ| class but extended to support additional functionality.
  16599. @<Class declarations@>=
  16600. class ModbusRTUDevice : public QObject
  16601. {
  16602. @[Q_OBJECT@]@;
  16603. public:@/
  16604. ModbusRTUDevice(DeviceTreeModel *model, const QModelIndex &index);
  16605. ~ModbusRTUDevice();
  16606. void queueMessage(QByteArray request, QObject *object, const char *callback);
  16607. @[Q_INVOKABLE@,@, double@]@, SVLower();@t\2\2@>@/
  16608. @[Q_INVOKABLE@,@, double@]@, SVUpper();@t\2\2@>@/
  16609. @[Q_INVOKABLE@,@, int@]@, decimals();@t\2\2@>@/
  16610. QList<Channel*> channels;@/
  16611. @[public slots@]:@/
  16612. void outputSV(double sv);@/
  16613. signals:@/
  16614. void SVLowerChanged(double);
  16615. void SVUpperChanged(double);
  16616. void SVDecimalChanged(int);
  16617. void queueEmpty();@/
  16618. @[private slots@]:@/
  16619. void dataAvailable();
  16620. void sendNextMessage();
  16621. void decimalResponse(QByteArray response);
  16622. void unitResponse(QByteArray response);
  16623. void svlResponse(QByteArray response);
  16624. void svuResponse(QByteArray response);
  16625. void requestMeasurement();
  16626. void mResponse(QByteArray response);
  16627. void ignore(QByteArray response);
  16628. void timeout();@/
  16629. private:@/
  16630. QextSerialPort *port;
  16631. QByteArray responseBuffer;
  16632. QList<QByteArray> messageQueue;
  16633. QList<QObject *> retObjQueue;
  16634. QList<char *> callbackQueue;
  16635. quint16 calculateCRC(QByteArray data);
  16636. QTimer *messageDelayTimer;
  16637. QTimer *commTimeout;
  16638. int delayTime;
  16639. char station;
  16640. int decimalPosition;
  16641. int valueF;
  16642. int valueC;
  16643. bool unitIsF;
  16644. double outputSVLower;
  16645. double outputSVUpper;
  16646. QByteArray outputSVStub;
  16647. QByteArray pvStub;
  16648. QByteArray svStub;
  16649. QByteArray mStub;
  16650. quint16 pvaddress;
  16651. quint16 svaddress;
  16652. bool svenabled;
  16653. bool readingsv;
  16654. double savedpv;
  16655. bool waiting;
  16656. };
  16657. @ The constructor reads its configuration from the configuration sub-tree of the
  16658. port node. This was adapted from a prototype implementation which used
  16659. |QSettings| to store this data. Note that this will only process the first
  16660. device specified on the port, the first process value on that device, and the
  16661. first set value on that device. A much more versatile architecture has been
  16662. planned for a future release which allows multiple devices per bus and
  16663. arbitrarily many monitored addresses per device. Communications are initiated
  16664. immediately upon construction.
  16665. @<ModbusRTUDevice implementation@>=
  16666. ModbusRTUDevice::ModbusRTUDevice(DeviceTreeModel *model,@| const QModelIndex &index)
  16667. : QObject(NULL), messageDelayTimer(new QTimer), commTimeout(new QTimer), unitIsF(@[true@]), readingsv(@[false@]),
  16668. waiting(@[false@])@/
  16669. {@/
  16670. QDomElement portReferenceElement = model->referenceElement(model->data(index,
  16671. Qt::UserRole).toString());
  16672. QDomNodeList portConfigData = portReferenceElement.elementsByTagName("attribute");
  16673. QDomElement node;
  16674. QVariantMap attributes;
  16675. for(int i = 0; i < portConfigData.size(); i++)
  16676. {
  16677. node = portConfigData.at(i).toElement();
  16678. attributes.insert(node.attribute("name"), node.attribute("value"));
  16679. }
  16680. port = new QextSerialPort(attributes.value("port").toString(),
  16681. QextSerialPort::EventDriven);
  16682. int baudRate = attributes.value("baud").toInt();
  16683. port->setBaudRate((BaudRateType)baudRate);
  16684. double temp = ((double)(1) / (double)(baudRate)) * 48;
  16685. delayTime = (int)(temp * 3000);
  16686. messageDelayTimer->setSingleShot(true);
  16687. commTimeout->setSingleShot(true);
  16688. connect(messageDelayTimer, SIGNAL(timeout()), this, SLOT(sendNextMessage()));
  16689. connect(commTimeout, SIGNAL(timeout()), this, SLOT(timeout()));
  16690. port->setDataBits(DATA_8);
  16691. port->setParity((ParityType)attributes.value("parity").toInt());
  16692. port->setStopBits((StopBitsType)attributes.value("stop").toInt());
  16693. port->setFlowControl((FlowType)attributes.value("flow").toInt());
  16694. connect(port, SIGNAL(readyRead()), this, SLOT(dataAvailable()));
  16695. port->open(QIODevice::ReadWrite);
  16696. station = (char)attributes.value("station").toInt();
  16697. if(attributes.value("decimalQuery") == "true")
  16698. {
  16699. decimalPosition = 0;
  16700. QByteArray message;
  16701. message.append(station);
  16702. message.append((char)0x03);
  16703. quint16 address = (quint16)attributes.value("decimalAddress").toInt();
  16704. char *addressBytes = (char*)&address;
  16705. message.append(addressBytes[1]);
  16706. message.append(addressBytes[0]);
  16707. message.append((char)0x00);
  16708. message.append((char)0x01);
  16709. queueMessage(message, this, "decimalResponse(QByteArray)");
  16710. }
  16711. else
  16712. {
  16713. decimalPosition = attributes.value("decimalPosition").toInt();
  16714. }
  16715. valueF = attributes.value("valueF").toInt();
  16716. valueC = attributes.value("valueC").toInt();
  16717. if(attributes.value("unitQuery") == "true")
  16718. {
  16719. QByteArray message;
  16720. message.append(station);
  16721. message.append((char)0x03);
  16722. quint16 address = (quint16)attributes.value("unitAddress").toInt();
  16723. char *addressBytes = (char*)&address;
  16724. message.append(addressBytes[1]);
  16725. message.append(addressBytes[0]);
  16726. message.append((char)0x00);
  16727. message.append((char)0x01);
  16728. queueMessage(message, this, "unitResponse(QByteArray)");
  16729. }
  16730. else
  16731. {
  16732. if(attributes.value("fixedUnit") == "Celsius")
  16733. {
  16734. unitIsF = @[false@];
  16735. }
  16736. }
  16737. if(attributes.value("sVWritable") == "true")
  16738. {
  16739. if(attributes.value("deviceLimit") == "true")
  16740. {
  16741. QByteArray lmessage;
  16742. lmessage.append(station);
  16743. lmessage.append((char)0x03);
  16744. quint16 laddress = (quint16)attributes.value("sVLowerAddr").toInt();
  16745. char *addressBytes = (char*)&laddress;
  16746. lmessage.append(addressBytes[1]);
  16747. lmessage.append(addressBytes[0]);
  16748. lmessage.append((char)0x00);
  16749. lmessage.append((char)0x01);
  16750. queueMessage(lmessage, this, "svlResponse(QByteArray)");
  16751. QByteArray umessage;
  16752. umessage.append(station);
  16753. umessage.append((char)0x03);
  16754. quint16 uaddress = (quint16)attributes.value("sVUpperAddr").toInt();
  16755. addressBytes = (char*)&uaddress;
  16756. umessage.append(addressBytes[1]);
  16757. umessage.append(addressBytes[0]);
  16758. umessage.append((char)0x00);
  16759. umessage.append((char)0x01);
  16760. queueMessage(umessage, this, "svuResponse(QByteArray)");
  16761. }
  16762. else
  16763. {
  16764. outputSVLower = attributes.value("sVLower").toDouble();
  16765. outputSVUpper = attributes.value("sVUpper").toDouble();
  16766. }
  16767. outputSVStub.append(station);
  16768. outputSVStub.append((char)0x06);
  16769. quint16 address = (quint16)attributes.value("sVOutputAddr").toInt();
  16770. char *addressBytes = (char*)&address;
  16771. outputSVStub.append(addressBytes[1]);
  16772. outputSVStub.append(addressBytes[0]);
  16773. }
  16774. Channel *pv = new Channel;
  16775. channels.append(pv);
  16776. pvStub.append(station);
  16777. pvStub.append((char)0x04);
  16778. pvaddress = (quint16)attributes.value("pVAddress").toInt();
  16779. char *pvac = (char*)&pvaddress;
  16780. pvStub.append(pvac[1]);
  16781. pvStub.append(pvac[0]);
  16782. pvStub.append((char)0x00);
  16783. pvStub.append((char)0x01);
  16784. svenabled = attributes.value("sVEnabled").toBool();
  16785. if(svenabled)
  16786. {
  16787. Channel *sv = new Channel;
  16788. channels.append(sv);
  16789. svStub.append(station);
  16790. svStub.append((char)0x04);
  16791. svaddress = (quint16)attributes.value("sVReadAddress").toInt();
  16792. char *svac = (char*)&svaddress;
  16793. svStub.append(svac[1]);
  16794. svStub.append(svac[0]);
  16795. svStub.append((char)0x00);
  16796. svStub.append((char)0x01);
  16797. if(svaddress - pvaddress == 1)
  16798. {
  16799. mStub.append(station);
  16800. mStub.append((char)0x04);
  16801. mStub.append(pvac[1]);
  16802. mStub.append(pvac[0]);
  16803. mStub.append((char)0x00);
  16804. mStub.append((char)0x02);
  16805. }
  16806. }
  16807. connect(this, SIGNAL(queueEmpty()), this, SLOT(requestMeasurement()));
  16808. requestMeasurement();
  16809. }
  16810. double ModbusRTUDevice::SVLower()
  16811. {
  16812. return outputSVLower;
  16813. }
  16814. double ModbusRTUDevice::SVUpper()
  16815. {
  16816. return outputSVUpper;
  16817. }
  16818. int ModbusRTUDevice::decimals()
  16819. {
  16820. return decimalPosition;
  16821. }
  16822. void ModbusRTUDevice::decimalResponse(QByteArray response)
  16823. {
  16824. quint16 temp;
  16825. char *tchar = (char*)&temp;
  16826. tchar[1] = response.at(3);
  16827. tchar[0] = response.at(4);
  16828. decimalPosition = temp;
  16829. emit SVDecimalChanged(decimalPosition);
  16830. }
  16831. void ModbusRTUDevice::unitResponse(QByteArray response)
  16832. {
  16833. quint16 temp;
  16834. char *tchar = (char*)&temp;
  16835. tchar[1] = response.at(3);
  16836. tchar[0] = response.at(4);
  16837. int value = temp;
  16838. if(value == valueF)
  16839. {
  16840. unitIsF = @[true@];
  16841. }
  16842. else
  16843. {
  16844. unitIsF = @[false@];
  16845. }
  16846. }
  16847. void ModbusRTUDevice::svlResponse(QByteArray response)
  16848. {
  16849. quint16 temp;
  16850. char *tchar = (char*)&temp;
  16851. tchar[1] = response.at(3);
  16852. tchar[0] = response.at(4);
  16853. outputSVLower = (double)temp;
  16854. for(int i = 0; i < decimalPosition; i++)
  16855. {
  16856. outputSVLower /= 10;
  16857. }
  16858. emit SVLowerChanged(outputSVLower);
  16859. }
  16860. void ModbusRTUDevice::svuResponse(QByteArray response)
  16861. {
  16862. quint16 temp;
  16863. char *tchar = (char*)&temp;
  16864. tchar[1] = response.at(3);
  16865. tchar[0] = response.at(4);
  16866. outputSVUpper = (double)temp;
  16867. for(int i = 0; i < decimalPosition; i++)
  16868. {
  16869. outputSVUpper /= 10;
  16870. }
  16871. emit SVUpperChanged(outputSVUpper);
  16872. }
  16873. void ModbusRTUDevice::requestMeasurement()
  16874. {
  16875. if(mStub.length() > 0)
  16876. {
  16877. queueMessage(mStub, this, "mResponse(QByteArray)");
  16878. }
  16879. else
  16880. {
  16881. queueMessage(pvStub, this, "mResponse(QByteArray)");
  16882. if(svenabled)
  16883. {
  16884. queueMessage(svStub, this, "mResponse(QByteArray)");
  16885. }
  16886. }
  16887. }
  16888. void ModbusRTUDevice::mResponse(QByteArray response)
  16889. {
  16890. QTime time = QTime::currentTime();
  16891. if(response.at(2) == 0x04)
  16892. {
  16893. @<Process PV and SV@>@;
  16894. }
  16895. else
  16896. {
  16897. @<Process PV or SV@>@;
  16898. }
  16899. }
  16900. @ There are two ways that we might request measurement data. All of the
  16901. devices I'@q'@>ve seen documented provide function 0x4 addresses for PV and SV
  16902. such that SV can be obtained from the address immediately after the address
  16903. from which we obtain PV. In this case we request both values at the same time.
  16904. @<Process PV and SV@>=
  16905. quint16 pv;
  16906. quint16 sv;
  16907. char *pvBytes = (char*)&pv;
  16908. char *svBytes = (char*)&sv;
  16909. pvBytes[1] = response.at(3);
  16910. pvBytes[0] = response.at(4);
  16911. svBytes[1] = response.at(5);
  16912. svBytes[0] = response.at(6);
  16913. double pvOut = (double)pv;
  16914. double svOut = (double)sv;
  16915. for(int i = 0; i < decimalPosition; i++)
  16916. {
  16917. pvOut /= 10;
  16918. svOut /= 10;
  16919. }
  16920. if(!unitIsF)
  16921. {
  16922. pvOut = pvOut * 9 / 5 + 32;
  16923. svOut = svOut * 9 / 5 + 32;
  16924. }
  16925. Measurement pvm(pvOut, time, Units::Fahrenheit);
  16926. Measurement svm(svOut, time, Units::Fahrenheit);
  16927. channels.at(0)->input(pvm);
  16928. channels.at(1)->input(svm);
  16929. @ When not measuring PV and SV at the same time, there are two possibilities.
  16930. One possibility is that SV is not enabled and we will only be reading from PV.
  16931. The other possibility is that we are alternating between reading PV and SV.
  16932. @<Process PV or SV@>=
  16933. quint16 value;
  16934. char *valueBytes = (char*)&value;
  16935. valueBytes[1] = response.at(3);
  16936. valueBytes[0] = response.at(4);
  16937. double valueOut = (double)value;
  16938. for(int i = 0; i < decimalPosition; i++)
  16939. {
  16940. valueOut /= 10;
  16941. }
  16942. if(!unitIsF)
  16943. {
  16944. valueOut = valueOut * 9 / 5 + 32;
  16945. }
  16946. if(!svenabled)
  16947. {
  16948. Measurement vm(valueOut, time, Units::Fahrenheit);
  16949. channels.at(0)->input(vm);
  16950. }
  16951. else
  16952. {
  16953. if(readingsv)
  16954. {
  16955. Measurement pvm(savedpv, time, Units::Fahrenheit);
  16956. Measurement svm(valueOut, time, Units::Fahrenheit);
  16957. channels.at(0)->input(pvm);
  16958. channels.at(1)->input(svm);
  16959. readingsv = false;
  16960. }
  16961. else
  16962. {
  16963. savedpv = valueOut;
  16964. readingsv = true;
  16965. }
  16966. }
  16967. @ The destructor should close the port.
  16968. @<ModbusRTUDevice implementation@>=
  16969. ModbusRTUDevice::~ModbusRTUDevice()
  16970. {
  16971. commTimeout->stop();
  16972. messageDelayTimer->stop();
  16973. port->close();
  16974. }
  16975. @ When data is available it should be read into a buffer. The start of the
  16976. buffer should always be the start of a response and there should never be
  16977. more than one response in the buffer at a time. It is, however, likely that
  16978. this buffer will have incomplete data. This means that we must determine when
  16979. the full response is available before passing the complete response along to
  16980. the appropriate method. If the response has not been received in full, nothing
  16981. is done. We'@q'@>ll be notified of more data shortly.
  16982. When the message we see the response for was queued, a callback was also
  16983. registered to handle the response. Once we have the complete message, we pass
  16984. the response along to the callback that was registered for that message,
  16985. remove the message and callback information from the message queue, and start
  16986. a timer which will trigger sending the next message after a safe amount of
  16987. time has passed.
  16988. If a response is received with an invalid CRC, we do not pass that message
  16989. out. Instead, the message handling queues are kept as they are and the previous
  16990. command will be sent again once the message delay timer is finished.
  16991. @<ModbusRTUDevice implementation@>=
  16992. void ModbusRTUDevice::dataAvailable()
  16993. {
  16994. if(messageDelayTimer->isActive())
  16995. {
  16996. messageDelayTimer->stop();
  16997. }
  16998. responseBuffer.append(port->readAll());
  16999. @<Check Modbus RTU message size@>@;
  17000. commTimeout->stop();
  17001. if(calculateCRC(responseBuffer) == 0)
  17002. {
  17003. QObject *object = retObjQueue.at(0);
  17004. char *method = callbackQueue.at(0);
  17005. QMetaMethod metamethod = @| object->metaObject()->
  17006. method(object->metaObject()->
  17007. indexOfMethod(@|QMetaObject::normalizedSignature(method)));
  17008. metamethod.invoke(object, Qt::QueuedConnection,
  17009. Q_ARG(QByteArray, responseBuffer));
  17010. messageQueue.removeAt(0);
  17011. retObjQueue.removeAt(0);
  17012. callbackQueue.removeAt(0);
  17013. }
  17014. else
  17015. {
  17016. qDebug() << "CRC failed";
  17017. }
  17018. messageDelayTimer->start(delayTime);
  17019. waiting = @[false@];
  17020. responseBuffer.clear();
  17021. }
  17022. @ In Modbus RTU, a response message starts with one byte identifying the device
  17023. the message was sent from, one byte indicating the function, a variable number
  17024. of bytes with the response data, and two bytes used to verify that the response
  17025. was correctly received. In the event of a normal response, messages will be at
  17026. least six bytes long, but in the event of an error it is possible for a message
  17027. to be five bytes long.
  17028. Messages with a function number of 0x01 or 0x02 will be 6 bytes in length.
  17029. Messages with a function number of 0x03 or 0x04 will be at least 7 bytes in
  17030. length with the total length determined by the sum of 5 and the value in the
  17031. fifth byte. Messages with a function number of 0x05, 0x06, or 0x10 will be 8
  17032. bytes in length. Messages with a function number greater than 0x80 will be five
  17033. bytes in length.
  17034. @<Check Modbus RTU message size@>=
  17035. if(responseBuffer.size() < 5)
  17036. {
  17037. return;
  17038. }
  17039. switch(responseBuffer.at(1))
  17040. {
  17041. case 0x01:
  17042. case 0x02:
  17043. if(responseBuffer.size() < 6)
  17044. {
  17045. return;
  17046. }
  17047. responseBuffer = responseBuffer.left(6);
  17048. break;
  17049. case 0x03:
  17050. case 0x04:
  17051. if(responseBuffer.size() < 5 + responseBuffer.at(2))
  17052. {
  17053. return;
  17054. }
  17055. responseBuffer = responseBuffer.left(5 + responseBuffer.at(2));
  17056. break;
  17057. case 0x05:
  17058. case 0x06:
  17059. case 0x10:
  17060. if(responseBuffer.size() < 8)
  17061. {
  17062. return;
  17063. }
  17064. responseBuffer = responseBuffer.left(8);
  17065. break;
  17066. }
  17067. @ When sending and receiving messages, it is necessary to calculate a 16 bit
  17068. cyclic redundancy check code. The algorithm used to calculate this is specified
  17069. by the Modbus RTU protocol documentation. When sending a message, |data| should
  17070. be the message to send except for the CRC bytes which will be appended once
  17071. this method calculates them. When receiving a message, passing the complete
  17072. message back through this method should result in a return value of |0|. Any
  17073. other value indicates an error.
  17074. @<ModbusRTUDevice implementation@>=
  17075. quint16 ModbusRTUDevice::calculateCRC(QByteArray data)
  17076. {
  17077. quint16 retval = 0xFFFF;
  17078. int i = 0;
  17079. while(i < data.size())
  17080. {
  17081. retval ^= 0x00FF & (quint16)data.at(i);
  17082. for(int j = 0; j < 8; j++)
  17083. {
  17084. if(retval & 1)
  17085. {
  17086. retval = (retval >> 1) ^ 0xA001;
  17087. }
  17088. else
  17089. {
  17090. retval >>= 1;
  17091. }
  17092. }
  17093. i++;
  17094. }
  17095. return retval;
  17096. }
  17097. @ When preparing an instance of ModbusRTUDevice, several messages may need to
  17098. be sent to the device in order to determine important details such as how
  17099. measurement data should be interpreted. During normal operation, messages
  17100. might be sent interactively between regular messages to acquire data. When
  17101. queueing a message, we also specify an object and method the response should be
  17102. sent to.
  17103. @<ModbusRTUDevice implementation@>=
  17104. void ModbusRTUDevice::queueMessage(QByteArray request, QObject *object,
  17105. const char *callback)
  17106. {
  17107. messageQueue.append(request);
  17108. retObjQueue.append(object);
  17109. callbackQueue.append(const_cast<char*>(callback));
  17110. if(messageQueue.size() == 1 && !(messageDelayTimer->isActive()))
  17111. {
  17112. sendNextMessage();
  17113. }
  17114. }
  17115. void ModbusRTUDevice::sendNextMessage()
  17116. {
  17117. if(messageQueue.size() > 0 && !waiting)
  17118. {
  17119. QByteArray message = messageQueue.at(0);
  17120. quint16 crc = calculateCRC(message);
  17121. char *check = (char*)&crc;
  17122. message.append(check[0]);
  17123. message.append(check[1]);
  17124. port->write(message);
  17125. commTimeout->start(2000);
  17126. messageDelayTimer->start(delayTime);
  17127. waiting = @[true@];
  17128. }
  17129. else
  17130. {
  17131. emit queueEmpty();
  17132. }
  17133. }
  17134. void ModbusRTUDevice::outputSV(double value)
  17135. {
  17136. for(int i = 0; i < decimalPosition; i++)
  17137. {
  17138. value *= 10;
  17139. }
  17140. quint16 outval = (quint16)value;
  17141. QByteArray message(outputSVStub);
  17142. char *valBytes = (char*)&outval;
  17143. message.append(valBytes[1]);
  17144. message.append(valBytes[0]);
  17145. queueMessage(message, this, "ignore(QByteArray)");
  17146. }
  17147. @ We don'@q'@>t care about the response when sending a new SV.
  17148. @<ModbusRTUDevice implementation@>=
  17149. void ModbusRTUDevice::ignore(QByteArray)
  17150. {
  17151. return;
  17152. }
  17153. @ Sometimes a communications failure will occur in which a response to a
  17154. command is never received. To reset communications we set a timer whenever a
  17155. command is sent and stop that once a full response is received. If the timer
  17156. times out, we should clear the response buffer and attempt to re-establish
  17157. communications. Currently this timeout is hard coded at 2 seconds, however
  17158. this should be configurable and smaller values may well be acceptable.
  17159. @<ModbusRTUDevice implementation@>=
  17160. void ModbusRTUDevice::timeout()
  17161. {
  17162. qDebug() << "Communications timeout.";
  17163. responseBuffer.clear();
  17164. waiting = false;
  17165. messageDelayTimer->start();
  17166. }
  17167. @ This class must be exposed to the host environment.
  17168. @<Function prototypes for scripting@>=
  17169. QScriptValue constructModbusRTUDevice(QScriptContext *context, QScriptEngine *engine);
  17170. QScriptValue ModbusRTUDevice_pVChannel(QScriptContext *context, QScriptEngine *engine);
  17171. QScriptValue ModbusRTUDevice_sVChannel(QScriptContext *context, QScriptEngine *engine);
  17172. void setModbusRTUDeviceProperties(QScriptValue value, QScriptEngine *engine);
  17173. @ The host environment is informed of the constructor.
  17174. @<Set up the scripting engine@>=
  17175. constructor = engine->newFunction(constructModbusRTUDevice);
  17176. value = engine->newQMetaObject(&ModbusRTUDevice::staticMetaObject, constructor);
  17177. engine->globalObject().setProperty("ModbusRTUDevice", value);
  17178. @ The constructor takes the configuration model and the index to the device of
  17179. interest as arguments rather than provide a large number of property setters to
  17180. handle initialization.
  17181. @<Functions for scripting@>=
  17182. QScriptValue constructModbusRTUDevice(QScriptContext *context, QScriptEngine *engine)
  17183. {
  17184. QScriptValue object;
  17185. if(context->argumentCount() == 2)
  17186. {
  17187. object = engine->newQObject(new ModbusRTUDevice(argument<DeviceTreeModel *>(0, context),
  17188. argument<QModelIndex>(1, context)),
  17189. QScriptEngine::ScriptOwnership);
  17190. setModbusRTUDeviceProperties(object, engine);
  17191. }
  17192. else
  17193. {
  17194. context->throwError("Incorrect number of arguments passed to "@|
  17195. "ModbusRTUDevice constructor. This takes the configuration model "@|
  17196. "and an index.");
  17197. }
  17198. return object;
  17199. }
  17200. @ The host environment needs a way to gain access to the channel objects.
  17201. @<Functions for scripting@>=
  17202. QScriptValue ModbusRTUDevice_pVChannel(QScriptContext *context, QScriptEngine *engine)
  17203. {
  17204. ModbusRTUDevice *self = getself<ModbusRTUDevice *>(context);
  17205. QScriptValue object;
  17206. if(self)
  17207. {
  17208. if(self->channels.size() > 0)
  17209. {
  17210. object = engine->newQObject(self->channels.at(0));
  17211. setChannelProperties(object, engine);
  17212. }
  17213. }
  17214. return object;
  17215. }
  17216. QScriptValue ModbusRTUDevice_sVChannel(QScriptContext *context, QScriptEngine *engine)
  17217. {
  17218. ModbusRTUDevice *self = getself<ModbusRTUDevice *>(context);
  17219. QScriptValue object;
  17220. if(self)
  17221. {
  17222. if(self->channels.size() > 1)
  17223. {
  17224. object = engine->newQObject(self->channels.at(1));
  17225. setChannelProperties(object, engine);
  17226. }
  17227. }
  17228. return object;
  17229. }
  17230. @ These methods are set as properties when the object is created.
  17231. @<Functions for scripting@>=
  17232. void setModbusRTUDeviceProperties(QScriptValue value, QScriptEngine *engine)
  17233. {
  17234. setQObjectProperties(value, engine);
  17235. value.setProperty("pVChannel", engine->newFunction(ModbusRTUDevice_pVChannel));
  17236. value.setProperty("sVChannel", engine->newFunction(ModbusRTUDevice_sVChannel));
  17237. }
  17238. @* Modbus RTU device configuration widget.
  17239. \noindent This class was minimally adapted from a prototype implementation to
  17240. use the new configuration system introduced in \pn{} 1.4.
  17241. With all of the custom widgets for specifying a device configuration in place,
  17242. we can proceed to combine these in a form. As all of the options might use more
  17243. screen space than is available we make this scrollable. Some reorganization of
  17244. this will be done prior to release to enable the use of multiple devices on the
  17245. port which may obviate the need for this, but as there are those who prefer to
  17246. have a small screen it may be better to leave the scroll area in place even
  17247. after such a change.
  17248. @<Class declarations@>=
  17249. class ModbusConfigurator : public BasicDeviceConfigurationWidget
  17250. {
  17251. @[Q_OBJECT@]@;
  17252. public:@/
  17253. Q_INVOKABLE@,@, ModbusConfigurator(DeviceTreeModel *model, const QModelIndex &index);@/
  17254. @[private slots@]:@/
  17255. void updatePort(const QString &newPort);
  17256. void updateBaudRate(const QString &newRate);
  17257. void updateParity(const QString &newParity);
  17258. void updateFlowControl(const QString &newFlow);
  17259. void updateStopBits(const QString &newStopBits);
  17260. void updateStation(int station);
  17261. void updateFixedDecimal(bool fixed);
  17262. void updateDecimalAddress(int address);
  17263. void updateDecimalPosition(int position);
  17264. void updateFixedUnit(bool fixed);
  17265. void updateUnitAddress(int address);
  17266. void updateValueForF(int value);
  17267. void updateValueForC(int value);
  17268. void updateUnit(const QString &newUnit);
  17269. void updatePVAddress(int address);
  17270. void updateSVEnabled(bool enabled);
  17271. void updateSVReadAddress(int address);
  17272. void updateDeviceLimit(bool query);
  17273. void updateSVLowerAddress(int address);
  17274. void updateSVUpperAddress(int address);
  17275. void updateSVLower(double value);
  17276. void updateSVUpper(double value);
  17277. void updateSVWritable(bool canWriteSV);
  17278. void updateSVWriteAddress(int address);
  17279. void updatePVColumnName(const QString &name);
  17280. void updateSVColumnName(const QString &name);
  17281. void updatePVHidden(bool hidden);
  17282. void updateSVHidden(bool hidden);@/
  17283. private:@/
  17284. PortSelector *port;
  17285. BaudSelector *baud;
  17286. ParitySelector *parity;
  17287. FlowSelector *flow;
  17288. StopSelector *stop;
  17289. QSpinBox *station;
  17290. QCheckBox *decimalQuery;
  17291. ShortHexSpinBox *decimalAddress;
  17292. QSpinBox *decimalPosition;
  17293. QCheckBox *unitQuery;
  17294. ShortHexSpinBox *unitAddress;
  17295. QSpinBox *valueF;
  17296. QSpinBox *valueC;
  17297. QComboBox *fixedUnit;
  17298. ShortHexSpinBox *pVAddress;
  17299. QCheckBox *sVEnabled;
  17300. ShortHexSpinBox *sVReadAddress;
  17301. QCheckBox *deviceLimit;
  17302. ShortHexSpinBox *sVLowerAddr;
  17303. ShortHexSpinBox *sVUpperAddr;
  17304. QDoubleSpinBox *sVLower;
  17305. QDoubleSpinBox *sVUpper;
  17306. QCheckBox *sVWritable;
  17307. ShortHexSpinBox *sVOutputAddr;
  17308. QLineEdit *pVColumnName;
  17309. QLineEdit *sVColumnName;
  17310. };
  17311. @ Implementation.
  17312. @<ModbusConfigurator implementation@>=
  17313. ModbusConfigurator::ModbusConfigurator(DeviceTreeModel *model, const QModelIndex &index)
  17314. : BasicDeviceConfigurationWidget(model, index),
  17315. port(new PortSelector), baud(new BaudSelector), parity(new ParitySelector),
  17316. flow(new FlowSelector), stop(new StopSelector), station(new QSpinBox),
  17317. decimalQuery(new QCheckBox(tr("Enable"))),
  17318. decimalAddress(new ShortHexSpinBox), decimalPosition(new QSpinBox),
  17319. unitQuery(new QCheckBox(tr("Enable"))),
  17320. unitAddress(new ShortHexSpinBox), valueF(new QSpinBox),
  17321. valueC(new QSpinBox), fixedUnit(new QComboBox),
  17322. pVAddress(new ShortHexSpinBox),
  17323. sVEnabled(new QCheckBox(tr("Enable"))),
  17324. sVReadAddress(new ShortHexSpinBox),
  17325. deviceLimit(new QCheckBox(tr("Enable"))),
  17326. sVLowerAddr(new ShortHexSpinBox), sVUpperAddr(new ShortHexSpinBox),
  17327. sVLower(new QDoubleSpinBox), sVUpper(new QDoubleSpinBox),
  17328. sVWritable(new QCheckBox(tr("Enable"))),
  17329. sVOutputAddr(new ShortHexSpinBox),
  17330. pVColumnName(new QLineEdit), sVColumnName(new QLineEdit)
  17331. {
  17332. QHBoxLayout *layout = new QHBoxLayout;
  17333. QWidget *form = new QWidget;
  17334. QHBoxLayout *masterLayout = new QHBoxLayout;
  17335. QVBoxLayout *portAndDeviceLayout = new QVBoxLayout;
  17336. QVBoxLayout *seriesLayout = new QVBoxLayout;
  17337. QFormLayout *serialSection = new QFormLayout;
  17338. serialSection->addRow(QString(tr("Port:")), port);
  17339. serialSection->addRow(QString(tr("Baud rate:")), baud);
  17340. serialSection->addRow(QString(tr("Parity:")), parity);
  17341. serialSection->addRow(QString(tr("Flow control:")), flow);
  17342. serialSection->addRow(QString(tr("Stop bits:")), stop);
  17343. QGroupBox *serialSectionBox = new QGroupBox(tr("Serial Port Configuration"));
  17344. serialSectionBox->setLayout(serialSection);
  17345. portAndDeviceLayout->addWidget(serialSectionBox);
  17346. QFormLayout *deviceSection = new QFormLayout;
  17347. station->setMinimum(1);
  17348. station->setMaximum(255);
  17349. decimalPosition->setMinimum(0);
  17350. decimalPosition->setMaximum(9);
  17351. valueF->setMinimum(0);
  17352. valueF->setMaximum(0xFFFF);
  17353. valueC->setMinimum(0);
  17354. valueC->setMaximum(0xFFFF);
  17355. fixedUnit->addItem(tr("Fahrenheit"), QVariant(QString("F")));
  17356. fixedUnit->addItem(tr("Celsius"), QVariant(QString("C")));
  17357. deviceSection->addRow(tr("Station:"), station);
  17358. deviceSection->addRow(tr("Decimal position from device:"), decimalQuery);
  17359. deviceSection->addRow(tr("Decimal position relative address:"), decimalAddress);
  17360. deviceSection->addRow(tr("Fixed decimal position:"), decimalPosition);
  17361. deviceSection->addRow(tr("Measurement unit from device:"), unitQuery);
  17362. deviceSection->addRow(tr("Current unit relative address:"), unitAddress);
  17363. deviceSection->addRow(tr("Value for Fahrenheit:"), valueF);
  17364. deviceSection->addRow(tr("Value for Celsius:"), valueC);
  17365. deviceSection->addRow(tr("Fixed unit:"), fixedUnit);
  17366. QGroupBox *deviceSectionBox = new QGroupBox(tr("Device Configuration"));
  17367. deviceSectionBox->setLayout(deviceSection);
  17368. portAndDeviceLayout->addWidget(deviceSectionBox);
  17369. QFormLayout *pVSection = new QFormLayout;
  17370. pVSection->addRow(tr("Value relative address:"), pVAddress);
  17371. pVSection->addRow(tr("PV column name:"), pVColumnName);
  17372. QCheckBox *pVHideBox = new QCheckBox(tr("Hide this channel"));
  17373. pVSection->addRow(pVHideBox);
  17374. QGroupBox *processValueBox = new QGroupBox(tr("Process Value"));
  17375. processValueBox->setLayout(pVSection);
  17376. seriesLayout->addWidget(processValueBox);
  17377. QFormLayout *sVSection = new QFormLayout;
  17378. sVLower->setDecimals(1);
  17379. sVLower->setMinimum(0.0);
  17380. sVLower->setMaximum(999.9);
  17381. sVUpper->setDecimals(1);
  17382. sVUpper->setMinimum(0.0);
  17383. sVUpper->setMaximum(999.9);
  17384. sVSection->addRow(tr("Set value:"), sVEnabled);
  17385. sVSection->addRow(tr("Read relative address:"), sVReadAddress);
  17386. sVSection->addRow(tr("SV column name:"), sVColumnName);
  17387. sVSection->addRow(tr("Limits from device:"), deviceLimit);
  17388. sVSection->addRow(tr("Lower limit relative address:"), sVLowerAddr);
  17389. sVSection->addRow(tr("Upper limit relative address:"), sVUpperAddr);
  17390. sVSection->addRow(tr("Lower limit:"), sVLower);
  17391. sVSection->addRow(tr("Upper limit:"), sVUpper);
  17392. sVSection->addRow(tr("Output set value:"), sVWritable);
  17393. sVSection->addRow(tr("Output relative address:"), sVOutputAddr);
  17394. QCheckBox *sVHideBox = new QCheckBox(tr("Hide this channel"));
  17395. sVSection->addRow(sVHideBox);
  17396. QGroupBox *setValueBox = new QGroupBox(tr("Set Value"));
  17397. setValueBox->setLayout(sVSection);
  17398. seriesLayout->addWidget(setValueBox);
  17399. masterLayout->addLayout(portAndDeviceLayout);
  17400. masterLayout->addLayout(seriesLayout);
  17401. form->setLayout(masterLayout);
  17402. @<Get device configuration data for current node@>@;
  17403. for(int i = 0; i < configData.size(); i++)
  17404. {
  17405. node = configData.at(i).toElement();
  17406. if(node.attribute("name") == "port")
  17407. {
  17408. QString portname = node.attribute("value");
  17409. int idx = port->findText(portname);
  17410. if(idx >= 0)
  17411. {
  17412. port->setCurrentIndex(idx);
  17413. }
  17414. else
  17415. {
  17416. port->addItem(portname);
  17417. }
  17418. }
  17419. else if(node.attribute("name") == "baud")
  17420. {
  17421. baud->setCurrentIndex(baud->findText(node.attribute("value")));
  17422. }
  17423. else if(node.attribute("name") == "parity")
  17424. {
  17425. parity->setCurrentIndex(parity->findData(node.attribute("value")));
  17426. }
  17427. else if(node.attribute("name") == "flow")
  17428. {
  17429. flow->setCurrentIndex(flow->findData(node.attribute("value")));
  17430. }
  17431. else if(node.attribute("name") == "stop")
  17432. {
  17433. stop->setCurrentIndex(stop->findData(node.attribute("value")));
  17434. }
  17435. else if(node.attribute("name") == "station")
  17436. {
  17437. station->setValue(node.attribute("value").toInt());
  17438. }
  17439. else if(node.attribute("name") == "decimalQuery")
  17440. {
  17441. if(node.attribute("value") == "true")
  17442. {
  17443. decimalQuery->setChecked(true);
  17444. }
  17445. else
  17446. {
  17447. decimalQuery->setChecked(false);
  17448. }
  17449. }
  17450. else if(node.attribute("name") == "decimalAddress")
  17451. {
  17452. decimalAddress->setValue(node.attribute("value").toInt());
  17453. }
  17454. else if(node.attribute("name") == "decimalPosition")
  17455. {
  17456. decimalPosition->setValue(node.attribute("value").toInt());
  17457. }
  17458. else if(node.attribute("name") == "unitQuery")
  17459. {
  17460. if(node.attribute("value") == "true")
  17461. {
  17462. unitQuery->setChecked(true);
  17463. }
  17464. else
  17465. {
  17466. unitQuery->setChecked(false);
  17467. }
  17468. }
  17469. else if(node.attribute("name") == "unitAddress")
  17470. {
  17471. unitAddress->setValue(node.attribute("value").toInt());
  17472. }
  17473. else if(node.attribute("name") == "valueF")
  17474. {
  17475. valueF->setValue(node.attribute("value").toInt());
  17476. }
  17477. else if(node.attribute("name") == "valueC")
  17478. {
  17479. valueC->setValue(node.attribute("value").toInt());
  17480. }
  17481. else if(node.attribute("name") == "fixedUnit")
  17482. {
  17483. fixedUnit->setCurrentIndex(fixedUnit->findText(node.attribute("value")));
  17484. }
  17485. else if(node.attribute("name") == "pVAddress")
  17486. {
  17487. pVAddress->setValue(node.attribute("value").toInt());
  17488. }
  17489. else if(node.attribute("name") == "sVEnabled")
  17490. {
  17491. if(node.attribute("value") == "true")
  17492. {
  17493. sVEnabled->setChecked(true);
  17494. }
  17495. else
  17496. {
  17497. sVEnabled->setChecked(false);
  17498. }
  17499. }
  17500. else if(node.attribute("name") == "sVReadAddress")
  17501. {
  17502. sVReadAddress->setValue(node.attribute("value").toInt());
  17503. }
  17504. else if(node.attribute("name") == "deviceLimit")
  17505. {
  17506. if(node.attribute("value") == "true")
  17507. {
  17508. deviceLimit->setChecked(true);
  17509. }
  17510. else
  17511. {
  17512. deviceLimit->setChecked(false);
  17513. }
  17514. }
  17515. else if(node.attribute("name") == "sVLowerAddr")
  17516. {
  17517. sVLowerAddr->setValue(node.attribute("value").toInt());
  17518. }
  17519. else if(node.attribute("name") == "sVUpperAddr")
  17520. {
  17521. sVUpperAddr->setValue(node.attribute("value").toInt());
  17522. }
  17523. else if(node.attribute("name") == "sVLower")
  17524. {
  17525. sVLower->setValue(node.attribute("value").toDouble());
  17526. }
  17527. else if(node.attribute("name") == "sVUpper")
  17528. {
  17529. sVUpper->setValue(node.attribute("value").toDouble());
  17530. }
  17531. else if(node.attribute("name") == "sVWritable")
  17532. {
  17533. if(node.attribute("value") == "true")
  17534. {
  17535. sVWritable->setChecked(true);
  17536. }
  17537. else
  17538. {
  17539. sVWritable->setChecked(false);
  17540. }
  17541. }
  17542. else if(node.attribute("name") == "sVOutputAddr")
  17543. {
  17544. sVOutputAddr->setValue(node.attribute("value").toInt());
  17545. }
  17546. else if(node.attribute("name") == "pvcolname")
  17547. {
  17548. pVColumnName->setText(node.attribute("value"));
  17549. }
  17550. else if(node.attribute("name") == "svcolname")
  17551. {
  17552. sVColumnName->setText(node.attribute("value"));
  17553. }
  17554. else if(node.attribute("name") == "pvhidden")
  17555. {
  17556. pVHideBox->setChecked(node.attribute("value") == "true");
  17557. }
  17558. else if(node.attribute("name") == "svhidden")
  17559. {
  17560. sVHideBox->setChecked(node.attribute("value") == "true");
  17561. }
  17562. }
  17563. updatePort(port->currentText());
  17564. updateBaudRate(baud->currentText());
  17565. updateParity(parity->itemData(parity->currentIndex()).toString());
  17566. updateFlowControl(flow->itemData(flow->currentIndex()).toString());
  17567. updateStopBits(stop->itemData(stop->currentIndex()).toString());
  17568. updateStation(station->value());
  17569. updateFixedDecimal(decimalQuery->isChecked());
  17570. updateDecimalAddress(decimalAddress->value());
  17571. updateDecimalPosition(decimalPosition->value());
  17572. updateFixedUnit(unitQuery->isChecked());
  17573. updateUnitAddress(unitAddress->value());
  17574. updateValueForF(valueF->value());
  17575. updateValueForC(valueC->value());
  17576. updateUnit(fixedUnit->currentText());
  17577. updatePVAddress(pVAddress->value());
  17578. updateSVEnabled(sVEnabled->isChecked());
  17579. updateSVReadAddress(sVReadAddress->value());
  17580. updateDeviceLimit(deviceLimit->isChecked());
  17581. updateSVLowerAddress(sVLowerAddr->value());
  17582. updateSVUpperAddress(sVUpperAddr->value());
  17583. updateSVLower(sVLower->value());
  17584. updateSVUpper(sVUpper->value());
  17585. updateSVWritable(sVWritable->isChecked());
  17586. updateSVWriteAddress(sVOutputAddr->value());
  17587. updatePVColumnName(pVColumnName->text());
  17588. updateSVColumnName(sVColumnName->text());
  17589. updatePVHidden(pVHideBox->isChecked());
  17590. updateSVHidden(sVHideBox->isChecked());
  17591. connect(port, SIGNAL(currentIndexChanged(QString)), this, SLOT(updatePort(QString)));
  17592. connect(port, SIGNAL(editTextChanged(QString)), this, SLOT(updatePort(QString)));
  17593. connect(baud, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateBaudRate(QString)));
  17594. connect(parity, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateParity(QString)));
  17595. connect(flow, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateFlowControl(QString)));
  17596. connect(stop, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateStopBits(QString)));
  17597. connect(station, SIGNAL(valueChanged(int)), this, SLOT(updateStation(int)));
  17598. connect(decimalQuery, SIGNAL(toggled(bool)), this, SLOT(updateFixedDecimal(bool)));
  17599. connect(decimalAddress, SIGNAL(valueChanged(int)), this, SLOT(updateDecimalAddress(int)));
  17600. connect(decimalPosition, SIGNAL(valueChanged(int)), this, SLOT(updateDecimalPosition(int)));
  17601. connect(unitQuery, SIGNAL(toggled(bool)), this, SLOT(updateFixedUnit(bool)));
  17602. connect(unitAddress, SIGNAL(valueChanged(int)), this, SLOT(updateUnitAddress(int)));
  17603. connect(valueF, SIGNAL(valueChanged(int)), this, SLOT(updateValueForF(int)));
  17604. connect(valueC, SIGNAL(valueChanged(int)), this, SLOT(updateValueForC(int)));
  17605. connect(fixedUnit, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateUnit(QString)));
  17606. connect(pVAddress, SIGNAL(valueChanged(int)), this, SLOT(updatePVAddress(int)));
  17607. connect(sVEnabled, SIGNAL(toggled(bool)), this, SLOT(updateSVEnabled(bool)));
  17608. connect(sVReadAddress, SIGNAL(valueChanged(int)), this, SLOT(updateSVReadAddress(int)));
  17609. connect(deviceLimit, SIGNAL(toggled(bool)), this, SLOT(updateDeviceLimit(bool)));
  17610. connect(sVLowerAddr, SIGNAL(valueChanged(int)), this, SLOT(updateSVLowerAddress(int)));
  17611. connect(sVUpperAddr, SIGNAL(valueChanged(int)), this, SLOT(updateSVUpperAddress(int)));
  17612. connect(sVLower, SIGNAL(valueChanged(double)), this, SLOT(updateSVLower(double)));
  17613. connect(sVUpper, SIGNAL(valueChanged(double)), this, SLOT(updateSVUpper(double)));
  17614. connect(sVWritable, SIGNAL(toggled(bool)), this, SLOT(updateSVWritable(bool)));
  17615. connect(sVOutputAddr, SIGNAL(valueChanged(int)), this, SLOT(updateSVWriteAddress(int)));
  17616. connect(pVColumnName, SIGNAL(textEdited(QString)), this, SLOT(updatePVColumnName(QString)));
  17617. connect(sVColumnName, SIGNAL(textEdited(QString)), this, SLOT(updateSVColumnName(QString)));
  17618. connect(pVHideBox, SIGNAL(toggled(bool)), this, SLOT(updatePVHidden(bool)));
  17619. connect(sVHideBox, SIGNAL(toggled(bool)), this, SLOT(updateSVHidden(bool)));
  17620. layout->addWidget(form);
  17621. setLayout(layout);
  17622. }
  17623. void ModbusConfigurator::updatePort(const QString &newPort)
  17624. {
  17625. updateAttribute("port", newPort);
  17626. }
  17627. void ModbusConfigurator::updateBaudRate(const QString &newRate)
  17628. {
  17629. updateAttribute("baud", newRate);
  17630. }
  17631. void ModbusConfigurator::updateParity(const QString &)
  17632. {
  17633. updateAttribute("parity", parity->itemData(parity->currentIndex()).toString());
  17634. }
  17635. void ModbusConfigurator::updateFlowControl(const QString &)
  17636. {
  17637. updateAttribute("flow", flow->itemData(flow->currentIndex()).toString());
  17638. }
  17639. void ModbusConfigurator::updateStopBits(const QString &)
  17640. {
  17641. updateAttribute("stop", stop->itemData(stop->currentIndex()).toString());
  17642. }
  17643. void ModbusConfigurator::updateStation(int station)
  17644. {
  17645. updateAttribute("station", QString("%1").arg(station));
  17646. }
  17647. void ModbusConfigurator::updateFixedDecimal(bool fixed)
  17648. {
  17649. updateAttribute("decimalQuery", fixed ? "true" : "false");
  17650. }
  17651. void ModbusConfigurator::updateDecimalAddress(int address)
  17652. {
  17653. updateAttribute("decimalAddress", QString("%1").arg(address));
  17654. }
  17655. void ModbusConfigurator::updateDecimalPosition(int position)
  17656. {
  17657. updateAttribute("decimalPosition", QString("%1").arg(position));
  17658. }
  17659. void ModbusConfigurator::updateFixedUnit(bool fixed)
  17660. {
  17661. updateAttribute("unitQuery", fixed ? "true" : "false");
  17662. }
  17663. void ModbusConfigurator::updateUnitAddress(int address)
  17664. {
  17665. updateAttribute("unitAddress", QString("%1").arg(address));
  17666. }
  17667. void ModbusConfigurator::updateValueForF(int value)
  17668. {
  17669. updateAttribute("valueF", QString("%1").arg(value));
  17670. }
  17671. void ModbusConfigurator::updateValueForC(int value)
  17672. {
  17673. updateAttribute("valueC", QString("%1").arg(value));
  17674. }
  17675. void ModbusConfigurator::updateUnit(const QString &newUnit)
  17676. {
  17677. updateAttribute("fixedUnit", newUnit);
  17678. }
  17679. void ModbusConfigurator::updatePVAddress(int address)
  17680. {
  17681. updateAttribute("pVAddress", QString("%1").arg(address));
  17682. }
  17683. void ModbusConfigurator::updateSVEnabled(bool enabled)
  17684. {
  17685. updateAttribute("sVEnabled", enabled ? "true" : "false");
  17686. }
  17687. void ModbusConfigurator::updateSVReadAddress(int address)
  17688. {
  17689. updateAttribute("sVReadAddress", QString("%1").arg(address));
  17690. }
  17691. void ModbusConfigurator::updateDeviceLimit(bool query)
  17692. {
  17693. updateAttribute("deviceLimit", query ? "true" : "false");
  17694. }
  17695. void ModbusConfigurator::updateSVLowerAddress(int address)
  17696. {
  17697. updateAttribute("sVLowerAddr", QString("%1").arg(address));
  17698. }
  17699. void ModbusConfigurator::updateSVUpperAddress(int address)
  17700. {
  17701. updateAttribute("sVUpperAddr", QString("%1").arg(address));
  17702. }
  17703. void ModbusConfigurator::updateSVLower(double value)
  17704. {
  17705. updateAttribute("sVLower", QString("%1").arg(value));
  17706. }
  17707. void ModbusConfigurator::updateSVUpper(double value)
  17708. {
  17709. updateAttribute("sVUpper", QString("%1").arg(value));
  17710. }
  17711. void ModbusConfigurator::updateSVWritable(bool canWriteSV)
  17712. {
  17713. updateAttribute("sVWritable", canWriteSV ? "true" : "false");
  17714. }
  17715. void ModbusConfigurator::updateSVWriteAddress(int address)
  17716. {
  17717. updateAttribute("sVOutputAddr", QString("%1").arg(address));
  17718. }
  17719. void ModbusConfigurator::updatePVColumnName(const QString &name)
  17720. {
  17721. updateAttribute("pvcolname", name);
  17722. }
  17723. void ModbusConfigurator::updateSVColumnName(const QString &name)
  17724. {
  17725. updateAttribute("svcolname", name);
  17726. }
  17727. void ModbusConfigurator::updatePVHidden(bool hidden)
  17728. {
  17729. updateAttribute("pvhidden", hidden ? "true" : "false");
  17730. }
  17731. void ModbusConfigurator::updateSVHidden(bool hidden)
  17732. {
  17733. updateAttribute("svhidden", hidden ? "true" : "false");
  17734. }
  17735. @ This is registered with the configuration system.
  17736. @<Register device configuration widgets@>=
  17737. app.registerDeviceConfigurationWidget("modbusrtu", ModbusConfigurator::staticMetaObject);
  17738. @ A |NodeInserter| for the driver configuration widget is also needed.
  17739. @<Register top level device configuration nodes@>=
  17740. inserter = new NodeInserter(tr("Modbus RTU Device"), tr("Modbus RTU Device"), "modbusrtu", NULL);
  17741. topLevelNodeInserters.append(inserter);
  17742. @i modbus.w
  17743. @i unsupportedserial.w
  17744. @i phidgets.w
  17745. @i phidget22.w
  17746. @* Configuration widget for a calibrated data series.
  17747. \noindent This control is used for adding a |LinearSplineInterpolator| to the
  17748. logging view.
  17749. @<Class declarations@>=
  17750. class LinearSplineInterpolationConfWidget : public BasicDeviceConfigurationWidget
  17751. {
  17752. @[Q_OBJECT@]@/
  17753. public:@/
  17754. @[Q_INVOKABLE@]@, LinearSplineInterpolationConfWidget(DeviceTreeModel *model,
  17755. const QModelIndex &index);
  17756. @[private slots@]:@/
  17757. void updateSourceColumn(const QString &source);
  17758. void updateDestinationColumn(const QString &dest);
  17759. void updateKnots();
  17760. private:@/
  17761. SaltModel *tablemodel;
  17762. };
  17763. @ This is configured by specifying a source column name, a destination column
  17764. name, and a two column table. Note that while we only have one widget handling
  17765. the mapping data, we store each column of the table in its own attribute.
  17766. @<LinearSplineInterpolationConfWidget implementation@>=
  17767. LinearSplineInterpolationConfWidget::LinearSplineInterpolationConfWidget(DeviceTreeModel *model, const QModelIndex &index)
  17768. : BasicDeviceConfigurationWidget(model, index), tablemodel(new SaltModel(2))
  17769. {
  17770. QFormLayout *layout = new QFormLayout;
  17771. QLineEdit *source = new QLineEdit;
  17772. layout->addRow(tr("Source column name:"), source);
  17773. QLineEdit *destination = new QLineEdit;
  17774. layout->addRow(tr("Destination column name:"), destination);
  17775. tablemodel->setHeaderData(0, Qt::Horizontal, "Input");
  17776. tablemodel->setHeaderData(1, Qt::Horizontal, "Output");
  17777. QTableView *mappingTable = new QTableView;
  17778. mappingTable->setModel(tablemodel);
  17779. NumericDelegate *delegate = new NumericDelegate;
  17780. mappingTable->setItemDelegate(delegate);
  17781. layout->addRow(tr("Mapping data:"), mappingTable);
  17782. @<Get device configuration data for current node@>@;
  17783. for(int i = 0; i < configData.size(); i++)
  17784. {
  17785. node = configData.at(i).toElement();
  17786. if(node.attribute("name") == "source")
  17787. {
  17788. source->setText(node.attribute("value"));
  17789. }
  17790. else if(node.attribute("name") == "destination")
  17791. {
  17792. destination->setText(node.attribute("value"));
  17793. }
  17794. else if(node.attribute("name") == "sourcevalues")
  17795. {
  17796. @<Convert numeric array literal to list@>@;
  17797. int column = 0;
  17798. @<Populate model column from list@>@;
  17799. }
  17800. else if(node.attribute("name") == "destinationvalues")
  17801. {
  17802. @<Convert numeric array literal to list@>@;
  17803. int column = 1;
  17804. @<Populate model column from list@>@;
  17805. }
  17806. }
  17807. updateSourceColumn(source->text());
  17808. updateDestinationColumn(destination->text());
  17809. updateKnots();
  17810. connect(source, SIGNAL(textEdited(QString)), this, SLOT(updateSourceColumn(QString)));
  17811. connect(destination, SIGNAL(textEdited(QString)), this, SLOT(updateDestinationColumn(QString)));
  17812. connect(tablemodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateKnots()));
  17813. setLayout(layout);
  17814. }
  17815. @ The saved data will have come from a previous call to
  17816. |SaltModel::arrayLiteral()| to repopulate the model we need to strip off the
  17817. the start and end of the strings and split them back into separate elements.
  17818. @<Convert numeric array literal to list@>=
  17819. QString data = node.attribute("value");
  17820. if(data.length() > 3)
  17821. {
  17822. data.chop(2);
  17823. data = data.remove(0, 2);
  17824. }
  17825. QStringList itemList = data.split(",");
  17826. @ Once the saved string has been split, the values can be inserted into the
  17827. model.
  17828. @<Populate model column from list@>=
  17829. for(int j = 0; j < itemList.size(); j++)
  17830. {
  17831. tablemodel->setData(tablemodel->index(j, column),
  17832. QVariant(itemList.at(j).toDouble()),
  17833. Qt::DisplayRole);
  17834. }
  17835. @ When data in the table is changed we simply overwrite any previously saved
  17836. data with the current data.
  17837. @<LinearSplineInterpolationConfWidget implementation@>=
  17838. void LinearSplineInterpolationConfWidget::updateKnots()
  17839. {
  17840. updateAttribute("sourcevalues", tablemodel->arrayLiteral(0, Qt::DisplayRole));
  17841. updateAttribute("destinationvalues", tablemodel->arrayLiteral(1, Qt::DisplayRole));
  17842. }
  17843. void LinearSplineInterpolationConfWidget::updateSourceColumn(const QString &source)
  17844. {
  17845. updateAttribute("source", source);
  17846. }
  17847. void LinearSplineInterpolationConfWidget::updateDestinationColumn(const QString &dest)
  17848. {
  17849. updateAttribute("destination", dest);
  17850. }
  17851. @ The widget is registered with the configuration system.
  17852. @<Register device configuration widgets@>=
  17853. app.registerDeviceConfigurationWidget("linearspline", LinearSplineInterpolationConfWidget::staticMetaObject);
  17854. @* Additional Timers.
  17855. \noindent \pn{} 1.6.4 adds support for more timer indicators than just the
  17856. default batch timer. Three new timer types are supported. The first is a
  17857. cooling timer. This is a timer that is initially set to 0, but at the end of a
  17858. batch this is set to a configured time and starts counting down. There are no
  17859. data logging requirements and this is purely a convenience feature, but one
  17860. that supports product and personnel safety best practices as it helps to
  17861. eliminate the practice of a person reaching into the cooling tray as it
  17862. agitates to test if the coffee has cooled.
  17863. @<Class declarations@>=
  17864. class CoolingTimerConfWidget : public BasicDeviceConfigurationWidget
  17865. {
  17866. @[Q_OBJECT@]@/
  17867. public:@/
  17868. @[Q_INVOKABLE@]@, CoolingTimerConfWidget(DeviceTreeModel *model,
  17869. const QModelIndex &index);
  17870. @[private slots@]:@/
  17871. void updateResetTime(QTime time);
  17872. };
  17873. @ The only configurable detail is the vaue the timer should reset to. For this
  17874. a |QTimeEdit| is fine.
  17875. @<CoolingTimerConfWidget implementation@>=
  17876. CoolingTimerConfWidget::CoolingTimerConfWidget(DeviceTreeModel *model,
  17877. const QModelIndex &index)
  17878. : BasicDeviceConfigurationWidget(model, index)
  17879. {
  17880. QHBoxLayout *layout = new QHBoxLayout;
  17881. QLabel *label = new QLabel(tr("Cooling Time: "));
  17882. QTimeEdit *editor = new QTimeEdit;
  17883. editor->setDisplayFormat("mm:ss");
  17884. @<Get device configuration data for current node@>@;
  17885. for(int i = 0; i < configData.size(); i++)
  17886. {
  17887. node = configData.at(i).toElement();
  17888. if(node.attribute("name") == "reset")
  17889. {
  17890. editor->setTime(QTime::fromString(node.attribute("value"), "mm:ss"));
  17891. }
  17892. }
  17893. updateResetTime(editor->time());
  17894. connect(editor, SIGNAL(timeChanged(QTime)),
  17895. this, SLOT(updateResetTime(QTime)));
  17896. layout->addWidget(label);
  17897. layout->addWidget(editor);
  17898. setLayout(layout);
  17899. }
  17900. void CoolingTimerConfWidget::updateResetTime(QTime time)
  17901. {
  17902. updateAttribute("reset", time.toString("mm:ss"));
  17903. }
  17904. @ The widget is registered with the configuration system.
  17905. @<Register device configuration widgets@>=
  17906. app.registerDeviceConfigurationWidget("coolingtimer",
  17907. CoolingTimerConfWidget::staticMetaObject);
  17908. @ The implementation chunk for now is in the main source file.
  17909. @<Class implementations@>=
  17910. @<CoolingTimerConfWidget implementation@>
  17911. @ The other two timer types are intended for measuring ranges of interest
  17912. within a batch. These have more configuration options. First is the range
  17913. timer. Someone setting this up needs to decide how the timer is started.
  17914. Sensible options include starting the timer at the start of the batch,
  17915. starting the timer when a button is pressed, or starting the timer when a set
  17916. point is reached on the ascent of a named data series. Stopping the timer is
  17917. also a configurable concern. This will be stopped at the end of the batch,
  17918. but it might be stopped sooner on reaching some threshold or when a button is
  17919. pressed. There are also questions of how the information is persisted with the
  17920. roasting records.
  17921. @<Class declarations@>=
  17922. class RangeTimerConfWidget : public BasicDeviceConfigurationWidget
  17923. {
  17924. @[Q_OBJECT@]@;
  17925. public:@/
  17926. @[Q_INVOKABLE@]@, RangeTimerConfWidget(DeviceTreeModel *model, const QModelIndex &index);
  17927. @[private slots@]:@/
  17928. void updateStartButtonText(const QString &text);
  17929. void updateStopButtonText(const QString &text);
  17930. void updateStartColumnName(const QString &text);
  17931. void updateStopColumnName(const QString &text);
  17932. void updateStartValue(const QString &text);
  17933. void updateStopValue(const QString &text);
  17934. void updateStartTrigger(int option);
  17935. void updateStopTrigger(int option);
  17936. };
  17937. @ The constructor sets up controls for configuring these details.
  17938. @<RangeTimerConfWidget implementation@>=
  17939. RangeTimerConfWidget::RangeTimerConfWidget(DeviceTreeModel *model, const QModelIndex &index)
  17940. : BasicDeviceConfigurationWidget(model, index)
  17941. {
  17942. QVBoxLayout *layout = new QVBoxLayout;
  17943. QGroupBox *startConfigurationGroup = new QGroupBox(tr("Start trigger"));
  17944. QRadioButton *startBatchOption = new QRadioButton(tr("Start of batch"));
  17945. QRadioButton *buttonOption = new QRadioButton(tr("Manual"));
  17946. QRadioButton *thresholdOption = new QRadioButton(tr("At temperature"));
  17947. QButtonGroup *startOptionGroup = new QButtonGroup;
  17948. startOptionGroup->addButton(startBatchOption, 1);
  17949. startOptionGroup->addButton(buttonOption, 2);
  17950. startOptionGroup->addButton(thresholdOption, 3);
  17951. startBatchOption->setChecked(true);
  17952. QGridLayout *startOptions = new QGridLayout;
  17953. startOptions->addWidget(startBatchOption, 0, 0);
  17954. startOptions->addWidget(buttonOption, 1, 0);
  17955. startOptions->addWidget(thresholdOption, 2, 0);
  17956. QLabel *buttonTextLabel = new QLabel(tr("Button Text: "));
  17957. QLineEdit *buttonTextEdit = new QLineEdit;
  17958. QHBoxLayout *buttonTextOptions = new QHBoxLayout;
  17959. buttonTextOptions->addWidget(buttonTextLabel);
  17960. buttonTextOptions->addWidget(buttonTextEdit);
  17961. startOptions->addLayout(buttonTextOptions, 1, 1);
  17962. QFormLayout *thresholdOptions = new QFormLayout;
  17963. QLineEdit *startColumnName = new QLineEdit;
  17964. QLineEdit *startValue = new QLineEdit;
  17965. thresholdOptions->addRow(tr("Column Name: "), startColumnName);
  17966. thresholdOptions->addRow(tr("Value: "), startValue);
  17967. startOptions->addLayout(thresholdOptions, 2, 1);
  17968. startConfigurationGroup->setLayout(startOptions);
  17969. layout->addWidget(startConfigurationGroup);
  17970. QGroupBox *stopConfigurationGroup = new QGroupBox(tr("Stop trigger"));
  17971. QRadioButton *stopBatchOption = new QRadioButton(tr("End of batch"));
  17972. QRadioButton *stopButtonOption = new QRadioButton(tr("Manual"));
  17973. QRadioButton *stopThresholdOption = new QRadioButton(tr("At temperature"));
  17974. QButtonGroup *stopOptionGroup = new QButtonGroup;
  17975. stopOptionGroup->addButton(stopBatchOption, 1);
  17976. stopOptionGroup->addButton(stopButtonOption, 2);
  17977. stopOptionGroup->addButton(stopThresholdOption, 3);
  17978. stopBatchOption->setChecked(true);
  17979. QGridLayout *stopOptions = new QGridLayout;
  17980. stopOptions->addWidget(stopBatchOption, 0, 0);
  17981. stopOptions->addWidget(stopButtonOption, 1, 0);
  17982. stopOptions->addWidget(stopThresholdOption, 2, 0);
  17983. QLabel *stopButtonLabel = new QLabel(tr("Button Text: "));
  17984. QLineEdit *stopButtonEdit = new QLineEdit;
  17985. QHBoxLayout *stopButtonTextOptions = new QHBoxLayout;
  17986. stopButtonTextOptions->addWidget(stopButtonLabel);
  17987. stopButtonTextOptions->addWidget(stopButtonEdit);
  17988. stopOptions->addLayout(stopButtonTextOptions, 1, 1);
  17989. QLineEdit *stopColumnName = new QLineEdit;
  17990. QLineEdit *stopValue = new QLineEdit;
  17991. QFormLayout *stopThresholdOptions = new QFormLayout;
  17992. stopThresholdOptions->addRow(tr("Column Name: "), stopColumnName);
  17993. stopThresholdOptions->addRow(tr("Value: "), stopValue);
  17994. stopOptions->addLayout(stopThresholdOptions, 2, 1);
  17995. stopConfigurationGroup->setLayout(stopOptions);
  17996. layout->addWidget(stopConfigurationGroup);
  17997. @<Get device configuration data for current node@>@;
  17998. for(int i = 0; i < configData.size(); i++)
  17999. {
  18000. node = configData.at(i).toElement();
  18001. if(node.attribute("name") == "startbuttontext")
  18002. {
  18003. buttonTextEdit->setText(node.attribute("value"));
  18004. }
  18005. else if(node.attribute("name") == "stopbuttontext")
  18006. {
  18007. stopButtonEdit->setText(node.attribute("value"));
  18008. }
  18009. else if(node.attribute("name") == "startcolumnname")
  18010. {
  18011. startColumnName->setText(node.attribute("value"));
  18012. }
  18013. else if(node.attribute("name") == "stopcolumnname")
  18014. {
  18015. stopColumnName->setText(node.attribute("value"));
  18016. }
  18017. else if(node.attribute("name") == "startvalue")
  18018. {
  18019. startValue->setText(node.attribute("value"));
  18020. }
  18021. else if(node.attribute("name") == "stopvalue")
  18022. {
  18023. stopValue->setText(node.attribute("value"));
  18024. }
  18025. else if(node.attribute("name") == "starttrigger")
  18026. {
  18027. if(node.attribute("value") == "batch")
  18028. {
  18029. startBatchOption->setChecked(true);
  18030. }
  18031. else if(node.attribute("value") == "manual")
  18032. {
  18033. buttonOption->setChecked(true);
  18034. }
  18035. else if(node.attribute("value") == "value")
  18036. {
  18037. thresholdOption->setChecked(true);
  18038. }
  18039. }
  18040. else if(node.attribute("name") == "stoptrigger")
  18041. {
  18042. if(node.attribute("value") == "batch")
  18043. {
  18044. stopBatchOption->setChecked(true);
  18045. }
  18046. else if(node.attribute("value") == "manual")
  18047. {
  18048. stopButtonOption->setChecked(true);
  18049. }
  18050. else if(node.attribute("value") == "value")
  18051. {
  18052. stopThresholdOption->setChecked(true);
  18053. }
  18054. }
  18055. }
  18056. updateStartButtonText(buttonTextEdit->text());
  18057. updateStopButtonText(stopButtonEdit->text());
  18058. updateStartColumnName(startColumnName->text());
  18059. updateStopColumnName(stopColumnName->text());
  18060. updateStartValue(startValue->text());
  18061. updateStopValue(stopValue->text());
  18062. updateStartTrigger(startOptionGroup->checkedId());
  18063. updateStopTrigger(stopOptionGroup->checkedId());
  18064. setLayout(layout);
  18065. connect(buttonTextEdit, SIGNAL(textChanged(QString)),
  18066. this, SLOT(updateStartButtonText(QString)));
  18067. connect(stopButtonEdit, SIGNAL(textChanged(QString)),
  18068. this, SLOT(updateStopButtonText(QString)));
  18069. connect(startColumnName, SIGNAL(textChanged(QString)),
  18070. this, SLOT(updateStartColumnName(QString)));
  18071. connect(stopColumnName, SIGNAL(textChanged(QString)),
  18072. this, SLOT(updateStopColumnName(QString)));
  18073. connect(startValue, SIGNAL(textChanged(QString)),
  18074. this, SLOT(updateStartValue(QString)));
  18075. connect(stopValue, SIGNAL(textChanged(QString)),
  18076. this, SLOT(updateStopValue(QString)));
  18077. connect(startOptionGroup, SIGNAL(buttonClicked(int)),
  18078. this, SLOT(updateStartTrigger(int)));
  18079. connect(stopOptionGroup, SIGNAL(buttonClicked(int)),
  18080. this, SLOT(updateStopTrigger(int)));
  18081. }
  18082. @ Small methods update the configuration as usual.
  18083. @<RangeTimerConfWidget implementation@>=
  18084. void RangeTimerConfWidget::updateStartButtonText(const QString &text)
  18085. {
  18086. updateAttribute("startbuttontext", text);
  18087. }
  18088. void RangeTimerConfWidget::updateStopButtonText(const QString &text)
  18089. {
  18090. updateAttribute("stopbuttontext", text);
  18091. }
  18092. void RangeTimerConfWidget::updateStartColumnName(const QString &text)
  18093. {
  18094. updateAttribute("startcolumnname", text);
  18095. }
  18096. void RangeTimerConfWidget::updateStopColumnName(const QString &text)
  18097. {
  18098. updateAttribute("stopcolumnname", text);
  18099. }
  18100. void RangeTimerConfWidget::updateStartValue(const QString &text)
  18101. {
  18102. updateAttribute("startvalue", text);
  18103. }
  18104. void RangeTimerConfWidget::updateStopValue(const QString &text)
  18105. {
  18106. updateAttribute("stopvalue", text);
  18107. }
  18108. void RangeTimerConfWidget::updateStartTrigger(int option)
  18109. {
  18110. switch(option)
  18111. {
  18112. case 1:
  18113. updateAttribute("starttrigger", "batch");
  18114. break;
  18115. case 2:
  18116. updateAttribute("starttrigger", "manual");
  18117. break;
  18118. case 3:
  18119. updateAttribute("starttrigger", "value");
  18120. break;
  18121. default:
  18122. break;
  18123. }
  18124. }
  18125. void RangeTimerConfWidget::updateStopTrigger(int option)
  18126. {
  18127. switch(option)
  18128. {
  18129. case 1:
  18130. updateAttribute("stoptrigger", "batch");
  18131. break;
  18132. case 2:
  18133. updateAttribute("stoptrigger", "manual");
  18134. break;
  18135. case 3:
  18136. updateAttribute("stoptrigger", "value");
  18137. break;
  18138. default:
  18139. break;
  18140. }
  18141. }
  18142. @ The widget is registered with the configuration system.
  18143. @<Register device configuration widgets@>=
  18144. app.registerDeviceConfigurationWidget("rangetimer",
  18145. RangeTimerConfWidget::staticMetaObject);
  18146. @ The implementation chunk for now is in the main source file.
  18147. @<Class implementations@>=
  18148. @<RangeTimerConfWidget implementation@>
  18149. @ The multirange timer is a little different. To keep configuration tractible,
  18150. this is slightly less general purpose than the range timer and only supports
  18151. automatic triggering on a single data series.
  18152. @<Class declarations@>=
  18153. class MultiRangeTimerConfWidget : public BasicDeviceConfigurationWidget
  18154. {
  18155. @[Q_OBJECT@]@/
  18156. public:@/
  18157. @[Q_INVOKABLE@]@, MultiRangeTimerConfWidget(DeviceTreeModel *model,
  18158. const QModelIndex &index);
  18159. @[private slots@]:@/
  18160. void updateColumnName(const QString &text);
  18161. void updateRangeData();
  18162. private:@/
  18163. SaltModel *tablemodel;
  18164. };
  18165. @ These limitations allow a rather small set of controls for configuration. A
  18166. line edit to specify the data series used for automatic triggering and a table
  18167. which specifies the name of the timed range and an ending temperature.
  18168. @<MultiRangeTimerConfWidget implementation@>=
  18169. MultiRangeTimerConfWidget::MultiRangeTimerConfWidget(DeviceTreeModel *model,
  18170. const QModelIndex &index)
  18171. : BasicDeviceConfigurationWidget(model, index), tablemodel(new SaltModel(2))
  18172. {
  18173. QFormLayout *layout = new QFormLayout;
  18174. QLineEdit *trigger = new QLineEdit;
  18175. layout->addRow(tr("Trigger column name:"), trigger);
  18176. tablemodel->setHeaderData(0, Qt::Horizontal, "Range Name");
  18177. tablemodel->setHeaderData(1, Qt::Horizontal, "End Temperature");
  18178. QTableView *rangeTable = new QTableView;
  18179. rangeTable->setModel(tablemodel);
  18180. layout->addRow(tr("Range data:"), rangeTable);
  18181. @<Get device configuration data for current node@>@;
  18182. for(int i = 0; i < configData.size(); i++)
  18183. {
  18184. node = configData.at(i).toElement();
  18185. if(node.attribute("name") == "trigger")
  18186. {
  18187. trigger->setText(node.attribute("value"));
  18188. }
  18189. else if(node.attribute("name") == "rangenames")
  18190. {
  18191. QString data = node.attribute("value");
  18192. if(data.length() > 3)
  18193. {
  18194. data.chop(2);
  18195. data = data.remove(0, 2);
  18196. }
  18197. QStringList itemList = data.split(", ");
  18198. for(int j = 0; j < itemList.size(); j++)
  18199. {
  18200. QString item = itemList.at(j);
  18201. item.chop(1);
  18202. item = item.remove(0, 1);
  18203. tablemodel->setData(tablemodel->index(j, 0),
  18204. QVariant(item), Qt::DisplayRole);
  18205. }
  18206. }
  18207. else if(node.attribute("name") == "endtemps")
  18208. {
  18209. @<Convert numeric array literal to list@>@;
  18210. int column = 1;
  18211. @<Populate model column from list@>@;
  18212. }
  18213. }
  18214. updateColumnName(trigger->text());
  18215. updateRangeData();
  18216. connect(trigger, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
  18217. connect(tablemodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateRangeData()));
  18218. setLayout(layout);
  18219. }
  18220. @ The update mechanisms are reasonably straightforward. Table updates just
  18221. refresh the entire table instead of attempting to be clever about updating only
  18222. the element that changed.
  18223. @<MultiRangeTimerConfWidget implementation@>=
  18224. void MultiRangeTimerConfWidget::updateRangeData()
  18225. {
  18226. updateAttribute("rangenames", tablemodel->quotedArrayLiteral(0, Qt::DisplayRole));
  18227. updateAttribute("endtemps", tablemodel->arrayLiteral(1, Qt::DisplayRole));
  18228. }
  18229. void MultiRangeTimerConfWidget::updateColumnName(const QString &text)
  18230. {
  18231. updateAttribute("trigger", text);
  18232. }
  18233. @ The widget is registered with the configuration system.
  18234. @<Register device configuration widgets@>=
  18235. app.registerDeviceConfigurationWidget("multirangetimer",
  18236. MultiRangeTimerConfWidget::staticMetaObject);
  18237. @ The implementation is in the main source file.
  18238. @<Class implementations@>=
  18239. @<MultiRangeTimerConfWidget implementation@>
  18240. @* Profile Translation Configuration Widget.
  18241. \noindent Configuring profile translation requires knowing which column to use
  18242. for matching purposes and the value to match.
  18243. @<Class declarations@>=
  18244. class TranslationConfWidget : public BasicDeviceConfigurationWidget
  18245. {
  18246. @[Q_OBJECT@]@;
  18247. public:@/
  18248. @[Q_INVOKABLE@]@, TranslationConfWidget(DeviceTreeModel *model, const QModelIndex &index);
  18249. @[private slots@]:@/
  18250. void updateMatchingColumn(const QString &column);
  18251. void updateTemperature();
  18252. void updateDelay();
  18253. private:@/
  18254. QDoubleSpinBox *temperatureValue;
  18255. QComboBox *unitSelector;
  18256. QSpinBox *delaySelector;
  18257. };
  18258. @ The constructor sets up our user interface.
  18259. @<TranslationConfWidget implementation@>=
  18260. TranslationConfWidget::TranslationConfWidget(DeviceTreeModel *model, const QModelIndex &index)
  18261. : BasicDeviceConfigurationWidget(model, index),
  18262. temperatureValue(new QDoubleSpinBox), unitSelector(new QComboBox),
  18263. delaySelector(new QSpinBox)
  18264. {
  18265. unitSelector->addItem("Fahrenheit");
  18266. unitSelector->addItem("Celsius");
  18267. temperatureValue->setMinimum(0);
  18268. temperatureValue->setMaximum(1000);
  18269. QFormLayout *layout = new QFormLayout;
  18270. QLineEdit *column = new QLineEdit;
  18271. layout->addRow(tr("Column to match:"), column);
  18272. layout->addRow(tr("Unit:"), unitSelector);
  18273. layout->addRow(tr("Value:"), temperatureValue);
  18274. layout->addRow(tr("Start of batch safety delay:"), delaySelector);
  18275. @<Get device configuration data for current node@>@;
  18276. for(int i = 0; i < configData.size(); i++)
  18277. {
  18278. node = configData.at(i).toElement();
  18279. if(node.attribute("name") == "column")
  18280. {
  18281. column->setText(node.attribute("value"));
  18282. }
  18283. else if(node.attribute("name") == "unit")
  18284. {
  18285. unitSelector->setCurrentIndex(unitSelector->findText(node.attribute("value")));
  18286. }
  18287. else if(node.attribute("name") == "value")
  18288. {
  18289. temperatureValue->setValue(node.attribute("value").toDouble());
  18290. }
  18291. else if(node.attribute("name") == "delay")
  18292. {
  18293. delaySelector->setValue(node.attribute("value").toInt());
  18294. }
  18295. }
  18296. updateMatchingColumn(column->text());
  18297. updateTemperature();
  18298. updateDelay();
  18299. connect(column, SIGNAL(textEdited(QString)), this, SLOT(updateMatchingColumn(QString)));
  18300. connect(unitSelector, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateTemperature()));
  18301. connect(temperatureValue, SIGNAL(valueChanged(double)), this, SLOT(updateTemperature()));
  18302. connect(delaySelector, SIGNAL(valueChanged(int)), this, SLOT(updateDelay()));
  18303. setLayout(layout);
  18304. }
  18305. @ To update the temperature at which to match we save both the values of the
  18306. two widgets which control this and the value in Fahrenheit so we don'@q'@>t need to
  18307. run unit conversions during view initialization.
  18308. @<TranslationConfWidget implementation@>=
  18309. void TranslationConfWidget::updateTemperature()
  18310. {
  18311. updateAttribute("unit", unitSelector->currentText());
  18312. updateAttribute("value", QString("%1").arg(temperatureValue->value()));
  18313. if(unitSelector->currentText() == "Fahrenheit")
  18314. {
  18315. updateAttribute("FValue", QString("%1").arg(temperatureValue->value()));
  18316. }
  18317. else
  18318. {
  18319. updateAttribute("FValue", QString("%1").arg(temperatureValue->value() * 9 / 5 + 32));
  18320. }
  18321. }
  18322. void TranslationConfWidget::updateMatchingColumn(const QString &column)
  18323. {
  18324. updateAttribute("column", column);
  18325. }
  18326. void TranslationConfWidget::updateDelay()
  18327. {
  18328. updateAttribute("delay", QString("%1").arg(delaySelector->value()));
  18329. }
  18330. @ This is registered with the configuration system.
  18331. @<Register device configuration widgets@>=
  18332. app.registerDeviceConfigurationWidget("translation", TranslationConfWidget::staticMetaObject);
  18333. @i rate.w
  18334. @i mergeseries.w
  18335. @i dataqsdk.w
  18336. @i scales.w
  18337. @i valueannotation.w
  18338. @i thresholdannotation.w
  18339. @i user.w
  18340. @i roastcoloredit.w
  18341. @** Local changes.
  18342. \noindent This is the end of \pn{} as distributed by its author. It is expected
  18343. that some might have need of a program like \pn, but require some modification.
  18344. The patching capabilities of \cweb{} can be used to produce these local
  18345. modifications. This section is provided for those whose change requirements are
  18346. sufficiently extensive to require the introduction of new sections to the
  18347. program.
  18348. @** Index.
  18349. \noindent Following is a list of identifiers used in \pn, with underlined
  18350. entries pointing to where \cweb{} has guessed that the identifier was defined.
  18351. All references are to section numbers instead of page numbers.
  18352. \def\nullsec{--}