NUMINPUT.MOD 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933
  1. IMPLEMENTATION MODULE NumInput;
  2. (*
  3. * REPERTOIRE
  4. * Release 1.6
  5. * By Charles Bradford and Cole Brecheen
  6. * (c) Copyright 1985-1992 PMI
  7. * Green Bay, Wisconsin
  8. * All rights reserved
  9. * (414) 468-6040
  10. *
  11. * $Header: D:/logfiles/mods/numinput.mov 1.4 10 Mar 1991 15:35:32 coleb $
  12. *
  13. *)
  14. (*
  15. Author: Mike Carter
  16. Description:
  17. Enter or edit a numeric field.
  18. Replacement for PMI StrInput.ReadNumeric. Note that parameters
  19. are slightly different:
  20. 1. PMI FloatingPoint and InDecimals parameters have been
  21. removed. They simply have no meaning the way real number
  22. input is now handled.
  23. 2. minusOK has been added - see below.
  24. 3. The meaning of DecimalPlaces has been changed - see
  25. below.
  26. Some of the features of this version:
  27. * General editing functions are very close to PMI string
  28. input. (Keys like Ins, Del, Home, End, PgUp, PgDn, Left and
  29. Right Arrows, Up and Down Arrows, Backspace, Tab, Shift
  30. Tab, etc. work almost the same way as they do in string
  31. fields).
  32. * Numbers are entered from left to right, then right-
  33. justified on field exit.
  34. * Both insert and overstrike modes are supported.
  35. * The number of digits after the decimal place is strictly
  36. enforced. The user is beeped if too many are entered; if
  37. two few are entered, zeroes are appended. If there is not
  38. enough room in the field, the rightmost character is thrown
  39. out, the decimal point repositioned, and the user beeped.
  40. Notes on parameters:
  41. * A new field was added in ScrnTypes.InputFieldRecord called
  42. "decimalPlace", with corresponding type DecimalPlaceType.
  43. This was designed to be passed to this routine: a -1 value
  44. indicates user-specified decimal points (or none); a 0
  45. means no digits after the decimal point, a 1 one digit,
  46. etc.
  47. * The value of minusOK determines whether negative numbers
  48. are allowed; this can be set from rMin, iMin within
  49. UserOps. Must be FALSE for strings that are later to be
  50. interpreted as CARDINAL.
  51. Notes on Methodology:
  52. NumInput combines state transition tables with a series of
  53. rules. The three state tables reflect the current input mode
  54. and the character that the cursor is under; they generate
  55. actions to be taken based on each key that may be hit as well
  56. as the next state. The tables are:
  57. InputTable = entry on top of blanks.
  58. EditOverTable = overstrike mode.
  59. EditInsertTable = insert mode.
  60. The entries in the state tables are formed by three-character
  61. sequences. (See the initialization code at the end of this
  62. module).
  63. (1) The first character is a non-mnemonic that stands for a
  64. sequence of actions to be taken if a key in that keyclass
  65. is hit. See the KeyAction procedure guts to see which
  66. procedures are executed for which letters. Generally,
  67. these procedures are qualifying procedures that determine
  68. whether or not a keystroke is legal from a given state.
  69. (2) The next character specifies the action to take if the
  70. qualifications are met. "I" stands for insert; "E"
  71. stands for error.
  72. (3) The last character in the state table triplet is the next
  73. state. You'll notice that this is missing in many cases -
  74. that is because of the fact that during editing, the next
  75. state must be dynamically re-computed from the numeric
  76. string AFTER the change has been made.
  77. The state tables are difficult to understand without
  78. diagrams, from which they were created (state transition
  79. diagrams).
  80. The rules referred to above are generally conditions that
  81. would have caused the state tables to grow exponentially had
  82. they been included. InsertCharacter contains a number of
  83. these rules, such as what to do when you are on the last
  84. character in the field under a number of circumstances.
  85. *)
  86. IMPORT BigSets;
  87. IMPORT ErrorManager;
  88. IMPORT KbdInput;
  89. IMPORT Key;
  90. IMPORT LowLevel;
  91. IMPORT M2Strings;
  92. IMPORT Numbers;
  93. IMPORT PosUtils;
  94. IMPORT ScrnTypes;
  95. IMPORT Spkr;
  96. IMPORT StrConv;
  97. IMPORT StrEdit;
  98. IMPORT UserOps;
  99. IMPORT SYSTEM;
  100. IMPORT VWindows;
  101. VAR
  102. Initialized : BOOLEAN;
  103. TYPE
  104. TableIndexType = (InputTable, EditOverTable, EditInsertTable);
  105. (* InputTable = entry on top of blanks *)
  106. (* EditOverTable = overstrike mode *)
  107. (* EditInsertTable = insert mode *)
  108. KeyClassType = (DigitsClass, MinusClass, DecimalPointClass, DollarClass);
  109. CONST
  110. ActionLetters = 2; (* state table action letters + next state - 1 *)
  111. MaxStates = 6; (* max number of states in table *)
  112. VAR
  113. stateTable : ARRAY [InputTable..EditInsertTable],
  114. [DigitsClass..DollarClass],
  115. [0..MaxStates * (ActionLetters+1)] OF CHAR;
  116. (* actions and next states *)
  117. PROCEDURE ReadNumericField
  118. ( WindowHandle : VWindows.AWindowHandle; (* in *)
  119. VAR TheStr : ARRAY OF CHAR; (* in/out - field contents *)
  120. col1 : CARDINAL; (* in - field loc *)
  121. row1 : CARDINAL; (* in - field loc *)
  122. FieldWidth : CARDINAL; (* in - # chars. in field *)
  123. DecimalPlaces: ScrnTypes.DecimalPlaceType;
  124. (* in - # digits after . *)
  125. minusOK : BOOLEAN; (* in - "-" permitted *)
  126. VAR LastKey : CARDINAL; (* in/out - last key hit *)
  127. VAR ExitKeys : KbdInput.KeyNumSet (* in - keys to exit on *)
  128. );
  129. (*
  130. This is a general-purpose numeric input routine that replaces the PMI
  131. StrInput.ReadNumeric procedure. The parameters are slightly different.
  132. *)
  133. CONST
  134. Minus = ORD('-'); (* numerics required due to LastKey *)
  135. DecimalKey = ORD('.');
  136. DollarSignKey = ORD('$');
  137. ZeroKey = ORD('0');
  138. NineKey = ORD('9');
  139. CurrencySymbol = '$';
  140. blank = ' ';
  141. periodChar = '.';
  142. minusChar = '-';
  143. zeroChar = '0';
  144. MaxFieldSize = 255;
  145. ErrorState = 9999; (* arbitrary error state *)
  146. LeaveLast = TRUE; (* last character in field can't be overstruck *)
  147. VAR
  148. (* old PMI variables still used *)
  149. SavedHeight : CARDINAL;
  150. curPos : CARDINAL; (* index to TheStr *)
  151. curTable : TableIndexType; (* current state table *)
  152. editInProgress : BOOLEAN; (* TRUE if editing started *)
  153. escapeHit : BOOLEAN; (* ESC restores string *)
  154. fieldEnd : CARDINAL; (* FieldWidth - 1 *)
  155. originalStr : ARRAY [0..MaxFieldSize] OF CHAR;
  156. (* copy to restore by ESC *)
  157. periodOK : BOOLEAN; (* FALSE for INT and CARD *)
  158. states : ARRAY [InputTable..EditInsertTable] OF CARDINAL;
  159. (* state for each table *)
  160. (*
  161. The following group of procedures supports editing functions and are
  162. NOT involved with the state tables.
  163. *)
  164. PROCEDURE DeleteCharacter;
  165. (* Delete the character at the current cursor position *)
  166. (* Any characters to the right of curPos move left *)
  167. (* Implements part of DEL key. Doesn't move cursor position. *)
  168. VAR
  169. shiftIndex : CARDINAL;
  170. BEGIN (* procedure DeleteCharacter *)
  171. IF curPos < fieldEnd THEN
  172. FOR shiftIndex := curPos TO fieldEnd DO
  173. TheStr[ shiftIndex ] := TheStr[ shiftIndex + 1 ];
  174. END; (* for shiftIndex *)
  175. END; (* if curPos *)
  176. TheStr[ fieldEnd ] := blank;
  177. escapeHit := FALSE;
  178. END DeleteCharacter; (* procedure *)
  179. PROCEDURE PreviousContents;
  180. (* replace current contents of field with original contents *)
  181. (* Be sure to recompute table state afterwards *)
  182. (* Implements ESC key in part. *)
  183. BEGIN (* procedure PreviousContents *)
  184. StrEdit.AssignStr( originalStr, TheStr );
  185. curPos := 0;
  186. END PreviousContents; (* procedure *)
  187. PROCEDURE BlankOutField( startPos : CARDINAL );
  188. (* Replace the designated portion of the field with blanks. *)
  189. VAR
  190. strIndex : CARDINAL;
  191. BEGIN (* procedure BlankOutField *)
  192. FOR strIndex := startPos TO fieldEnd DO
  193. TheStr[ strIndex ] := blank;
  194. END; (* for strIndex *)
  195. END BlankOutField; (* procedure *)
  196. PROCEDURE CleanUpTheStr(): BOOLEAN;
  197. (* "Cleans up" the string in the field that the user wants to leave *)
  198. (* This procedure is meant to be executed whenever field movement keys *)
  199. (* have been hit. E.g. TAB, PgUp, PgDn, Shift TAB, etc. *)
  200. (* Actions performed: *)
  201. (* Leading Zeroes stripped. *)
  202. (* Decimal point forced in if not there. *)
  203. (* Trailing Zeroes added after decimal point without enough *)
  204. (* digits after it. *)
  205. (* Decimal point with too many digits after it causes truncation *)
  206. (* of extra digits, beep, and cursor left in field. *)
  207. (* Fields with no numeric digits are blanked out. *)
  208. (* Be sure to recompute table state afterwards *)
  209. VAR
  210. strIndex, decSpot, decDigits : CARDINAL;
  211. anyNumerics : BOOLEAN; (* TRUE if field contains any digits *)
  212. PROCEDURE StopFieldExit;
  213. (* An adjustment to the field has been made that requires user's *)
  214. (* attention - prevent exit from the field (aborting key action). *)
  215. BEGIN (* procedure StopFieldExit *)
  216. (* Don't allow the field to be exited - set up as just entered *)
  217. StrEdit.LeftJustify( TheStr, FieldWidth );
  218. LastKey := 0;
  219. curPos := 0;
  220. (* And try to wake up the user that this has happened! *)
  221. Spkr.Noise( Spkr.Beep, Spkr.High, Spkr.Medium );
  222. END StopFieldExit; (* procedure *)
  223. PROCEDURE NoDecimalPoint;
  224. (* Process a string that has no decimal point. For Reals only. *)
  225. PROCEDURE InsertLeftJustified
  226. ( insChar : CHAR; (* character to insert *)
  227. pos : CARDINAL (* position in TheStr *)
  228. );
  229. (* puts insChar into TheStr at location pos; rightmost char gone *)
  230. VAR
  231. strIndex : CARDINAL;
  232. BEGIN (* procedure InsertLeftJustified *)
  233. (* move what's there over; forget the leftmost character *)
  234. IF fieldEnd > 0 THEN
  235. FOR strIndex := fieldEnd-1 TO pos BY -1 DO
  236. TheStr[ strIndex+1 ] := TheStr[ strIndex ];
  237. END; (* for strIndex *)
  238. TheStr[ pos ] := insChar;
  239. END; (* if fieldEnd *)
  240. END InsertLeftJustified; (* procedure *)
  241. VAR
  242. decPointPos : CARDINAL; (* where the decimal point goes *)
  243. rightSubStr : ARRAY [0..80] OF CHAR; (* str in front of dec pt. *)
  244. trailingDig : BOOLEAN; (* true if digits after dec. point *)
  245. BEGIN (* procedure NoDecimalPoint *)
  246. (* note that TheStr does not necessarily have length = FieldWidth! *)
  247. StrEdit.LeftJustify( TheStr, FieldWidth );
  248. (* if necessary, sacrifice the last digit to make room for '.' *)
  249. decPointPos := FieldWidth-VAL(CARDINAL,DecimalPlaces)-1;
  250. InsertLeftJustified( periodChar, decPointPos );
  251. (* right justify with period as a boundary *)
  252. IF decPointPos > 0 THEN
  253. M2Strings.Copy( TheStr, 0, decPointPos, rightSubStr );
  254. StrEdit.RightJustify( rightSubStr, decPointPos );
  255. StrEdit.OverWrite( rightSubStr, TheStr, 0 ); (* put back in, shifted *)
  256. END; (* if decPointPos *)
  257. (* Fill in any gaps with zeroes. *)
  258. trailingDig := FALSE;
  259. FOR strIndex := decPointPos+1 TO fieldEnd DO
  260. IF TheStr[ strIndex ] = blank THEN
  261. TheStr[ strIndex ] := zeroChar;
  262. ELSE
  263. trailingDig := TRUE;
  264. END; (* if TheStr *)
  265. END; (* for strIndex *)
  266. (* Alert the user only if dec. point was added in middle of digits *)
  267. IF trailingDig THEN
  268. StopFieldExit;
  269. END; (* if trailingDig *)
  270. END NoDecimalPoint; (* procedure *)
  271. BEGIN (* procedure CleanUpTheStr *)
  272. (* Blank fields ignored *)
  273. IF PosUtils.IsBlank( TheStr ) THEN
  274. RETURN( TRUE );
  275. END; (* if PosUtils.IsBlank *)
  276. (* Another check - if there were no numeric digits, blank out. *)
  277. anyNumerics := FALSE;
  278. FOR strIndex := 0 TO fieldEnd DO
  279. anyNumerics := anyNumerics OR
  280. PosUtils.IsNumericChar( TheStr[ strIndex ] )
  281. END; (* for strIndex *)
  282. IF NOT anyNumerics THEN
  283. BlankOutField( 0 );
  284. RETURN( TRUE ); (* not much point in doing anything else *)
  285. END; (* if NOT *)
  286. (* First, out with the leading zeroes. Leave last one. *)
  287. (* Note also removes zeroes for -00.33 or $00.33 *)
  288. strIndex := 0;
  289. LOOP
  290. IF TheStr[ strIndex ] = zeroChar THEN
  291. TheStr[ strIndex ] := blank;
  292. END; (* if TheStr *)
  293. IF (strIndex = fieldEnd) OR (TheStr[strIndex] = periodChar) OR
  294. ((TheStr[strIndex] # zeroChar) AND (* stop on non-zero digit *)
  295. PosUtils.IsNumericChar( TheStr[strIndex] ))
  296. THEN
  297. EXIT;
  298. END; (* if strIndex *)
  299. INC( strIndex );
  300. END; (* loop *)
  301. (* Don't leave just a period in the field, though *)
  302. IF (TheStr[strIndex] = periodChar) AND
  303. (NOT PosUtils.IsNumericChar( TheStr[strIndex+1] )) THEN
  304. StrEdit.InsertRightJustified( zeroChar, TheStr, strIndex );
  305. END; (* if TheStr *)
  306. IF PosUtils.IsBlank( TheStr ) THEN
  307. TheStr[ 0 ] := zeroChar; (* at least one there *)
  308. END; (* if PosUtils.IsBlank *)
  309. (* Get rid of any blanks after punctuation ($ 33.) *)
  310. StrEdit.DeleteChar( blank, TheStr ); (* trailing gone too *)
  311. (* find decimal point (if real type) *)
  312. (* User-specified decimal points not enforced (DecimalPlaces=-1) *)
  313. IF DecimalPlaces > 0 THEN
  314. IF PosUtils.PresentPos( periodChar, TheStr, decSpot ) THEN
  315. (* count the number of digits after the decimal point *)
  316. decDigits := 0;
  317. FOR strIndex := decSpot+1 TO fieldEnd DO
  318. IF PosUtils.IsNumericChar( TheStr[ strIndex ] ) THEN
  319. INC( decDigits );
  320. END; (* if PosUtils.IsNumericChar *)
  321. END; (* for strIndex *)
  322. IF VAL(INTEGER,decDigits) < DecimalPlaces THEN
  323. (* not enough digits after decimal point *)
  324. (* so force-feed the zeroes *)
  325. FOR strIndex := decSpot+decDigits+1 TO
  326. Numbers.Min( decSpot+VAL(CARDINAL,DecimalPlaces), fieldEnd ) DO
  327. TheStr[ strIndex ] := zeroChar;
  328. END; (* for strIndex *)
  329. IF (fieldEnd - decSpot) < VAL(CARDINAL,DecimalPlaces) THEN
  330. (* not enough room in field for required decimal places *)
  331. (* take out the decimal point and shift it as necessary *)
  332. StrEdit.DeleteChar( periodChar, TheStr );
  333. NoDecimalPoint;
  334. RETURN( FALSE );
  335. END; (* if fieldEnd *)
  336. ELSIF VAL(INTEGER,decDigits) > DecimalPlaces THEN
  337. (* they typed in too many digits after the decimal point - remove *)
  338. FOR strIndex := decSpot+VAL(CARDINAL,DecimalPlaces)+1 TO
  339. decSpot+decDigits+1 DO
  340. TheStr[ strIndex ] := blank;
  341. END; (* for strIndex *)
  342. StopFieldExit;
  343. RETURN( FALSE ); (* Something typed was lost - notify *)
  344. END; (* if decDigits *)
  345. ELSE
  346. NoDecimalPoint;
  347. END; (* if PosUtils.PresentPos *)
  348. END; (* if DecimalPlaces *)
  349. RETURN( TRUE ); (* OK to go ahead and leave field *)
  350. END CleanUpTheStr; (* procedure *)
  351. PROCEDURE ComputeNextState;
  352. (* Used to switch state tables from the various modes *)
  353. (* (Numeric Entry, Insert Editing, Overstrike Editing) *)
  354. (* Called after keys that move the cursor in editing modes *)
  355. BEGIN (* procedure ComputeNextState *)
  356. CASE curTable OF
  357. InputTable:
  358. IF NOT((TheStr[ curPos ] = blank) OR (curPos = fieldEnd)) THEN
  359. (* Must change to an edit mode *)
  360. IF UserOps.InsertMode THEN
  361. curTable := EditInsertTable;
  362. ELSE
  363. curTable := EditOverTable;
  364. END; (* if UserOps.InsertMode *)
  365. END; (* if not *)
  366. | EditInsertTable, EditOverTable:
  367. (* change to InputTable if cursor is on a blank *)
  368. IF (TheStr[ curPos ] = blank) THEN
  369. curTable := InputTable;
  370. ELSIF NOT UserOps.InsertMode THEN
  371. curTable := EditOverTable;
  372. ELSE
  373. curTable := EditInsertTable;
  374. END; (* if TheStr *)
  375. END; (* case curTable *)
  376. (* Now, have to figure out which state we are left in *)
  377. (* This is because the editing actions change the states *)
  378. (* NOTE: This is where the states are documented. Each state *)
  379. (* is represented by three character positional substrings in the *)
  380. (* third dimension of the stateTable array. *)
  381. CASE curTable OF
  382. InputTable:
  383. IF PosUtils.IsBlank( TheStr ) THEN
  384. states[ InputTable ] := 0; (* back to initial *)
  385. ELSIF PosUtils.Present( periodChar, TheStr ) THEN
  386. states[ InputTable ] := 4; (* after period *)
  387. ELSIF PosUtils.Present( CurrencySymbol, TheStr ) THEN
  388. states[ InputTable ] := 3; (* after currency *)
  389. ELSIF PosUtils.Present( minusChar, TheStr ) THEN
  390. IF PosUtils.IsNumericChar( TheStr[curPos] ) THEN
  391. states[ InputTable ] := 5; (* no currency can follow *)
  392. ELSE
  393. states[ InputTable ] := 2; (* bare - *)
  394. END; (* if PosUtils.IsNumber *)
  395. ELSE
  396. states[ InputTable ] := 1; (* numbers only so far *)
  397. END; (* if IsBlank *)
  398. | EditInsertTable, EditOverTable: (* same 4 states for both *)
  399. CASE ORD( TheStr[curPos] ) OF
  400. ZeroKey..NineKey : states[ curTable ] := 0;
  401. | Minus : states[ curTable ] := 1;
  402. | DollarSignKey : states[ curTable ] := 2;
  403. | DecimalKey : states[ curTable ] := 3;
  404. ELSE
  405. states[ curTable ] := ErrorState;
  406. END; (* case TheStr *)
  407. END; (* case curTable *)
  408. END ComputeNextState; (* procedure *)
  409. PROCEDURE FirstSpaceOrEnd;
  410. (* position cursor on first space or at end of field *)
  411. BEGIN (* procedure FirstSpaceOrEnd *)
  412. IF NOT( PosUtils.PresentPos( blank, TheStr, curPos )) THEN
  413. curPos := fieldEnd;
  414. END; (* if NOT *)
  415. END FirstSpaceOrEnd; (* procedure *)
  416. PROCEDURE LeftMove() : BOOLEAN;
  417. (* Move the cursor to the left - TRUE if can do within field *)
  418. BEGIN (* procedure LeftMove *)
  419. IF curPos = 0 THEN
  420. (* Change of plans - you're headed out of the field! *)
  421. LastKey := Key.BackTab; (* get out of the field *)
  422. IF NOT CleanUpTheStr() THEN
  423. ComputeNextState;
  424. ELSE
  425. StrEdit.RightJustify( TheStr, FieldWidth );
  426. END; (* if NOT *)
  427. RETURN( FALSE );
  428. ELSE
  429. DEC( curPos );
  430. RETURN( TRUE );
  431. END; (* if curPos *)
  432. END LeftMove; (* procedure *)
  433. PROCEDURE RightMove;
  434. (* Move the cursor to the right *)
  435. BEGIN (* procedure RightMove *)
  436. IF curPos = fieldEnd THEN
  437. (* go to the next frame! *)
  438. LastKey := Key.Tab;
  439. StrEdit.RightJustify( TheStr, FieldWidth );
  440. ELSE
  441. INC( curPos );
  442. END; (* if curPos *)
  443. END RightMove; (* procedure *)
  444. (*
  445. Well, at last we've reached the end of the edit-oriented
  446. procedures. What follows are the procedures to support the
  447. stateTable actions and transitions. These substrings are coded
  448. "AAT" in the stateTable, where AA are characters indicating
  449. actions to take and T is an optional next state. Next states
  450. have to be computed for both EditOver and EditInsert tables.
  451. *)
  452. PROCEDURE KeyAction( keyClass : KeyClassType );
  453. (* process the key struck - keyClass indicates type of key struck *)
  454. (* The current table, the current state, and the key class all *)
  455. (* determine the actions to take and the next state, if any, to go to. *)
  456. (*
  457. Next state is only provided in input table; the edit tables
  458. must compute the next state based on the character that the
  459. cursor is on after the edit action; they are not input-driven.
  460. *)
  461. CONST
  462. MaxProcs = 3; (* max number of letters for one state *)
  463. VAR
  464. whichLetter : CARDINAL;
  465. procLetters : ARRAY [0..MaxProcs] OF CHAR;
  466. passedTests : BOOLEAN; (* used during edit modes *)
  467. nextState : CARDINAL;
  468. PROCEDURE ErrorInKeyStroke;
  469. (* What to do when an error occurs - also used for parsing errors *)
  470. (* Called by 'E' command. *)
  471. BEGIN (* procedure ErrorInKeyStroke *)
  472. Spkr.Noise( Spkr.Beep, Spkr.Normal, Spkr.Short );
  473. nextState := states[ curTable ]; (* stay where you are *)
  474. END ErrorInKeyStroke; (* procedure *)
  475. PROCEDURE InsertCharacter;
  476. (* Put the keystroke into TheStr at curPos *)
  477. (* If any tests done previous have failed, refuse character and beep. *)
  478. (* Called by 'I' command. *)
  479. VAR
  480. targetPos : CARDINAL; (* for moving characters *)
  481. BEGIN (* procedure InsertCharacter *)
  482. (* not OK to shift out characters *)
  483. IF passedTests AND NOT(UserOps.InsertMode AND (TheStr[ fieldEnd ] # blank))
  484. THEN
  485. IF UserOps.InsertMode THEN (* insert mode *)
  486. (* move string to the right by 1, then put in character *)
  487. (* NOT OK to shift right on out of the field *)
  488. IF (FieldWidth >= 2) AND (curPos < fieldEnd) THEN
  489. FOR targetPos := FieldWidth-2 TO curPos BY -1 DO
  490. TheStr[ targetPos+1 ] := TheStr[ targetPos ];
  491. END; (* for targetPos *)
  492. END; (* if FieldWidth *)
  493. END; (* if UserOps.InsertMode *)
  494. IF (curPos = FieldWidth - 1) AND (TheStr[ curPos ] # blank)
  495. AND (UserOps.InsertMode AND LeaveLast)
  496. THEN
  497. (* Don't replace character if you're on the last field position, *)
  498. (* a non-blank character is already there, and the constant *)
  499. (* LeaveLast is TRUE, and you're in insert mode. *)
  500. ErrorInKeyStroke;
  501. ELSE (* All other combinations generate replacement *)
  502. IF (curPos = FieldWidth - 1) AND (TheStr[ curPos ] # blank) THEN
  503. (* You're on the last character in the field *)
  504. Spkr.Noise( Spkr.Beep, Spkr.Normal, Spkr.Short );
  505. END; (* if curPos *)
  506. (* replace character under cursor and move cursor right *)
  507. TheStr[ curPos ] := VAL( CHAR, LastKey );
  508. INC( curPos );
  509. IF curPos >= FieldWidth THEN
  510. curPos := FieldWidth - 1;
  511. END; (* if curPos *)
  512. END; (* if curpos *)
  513. ELSE
  514. ErrorInKeyStroke;
  515. END; (* if passedTests *)
  516. END InsertCharacter; (* procedure *)
  517. PROCEDURE PresentChar;
  518. (* tests to see if the key struck is already present - fails if so. *)
  519. (* Called by 'P', 'A', 'B', 'F', 'G', 'H', 'J', and 'K' commands. *)
  520. VAR
  521. strKey : ARRAY [0..1] OF CHAR;
  522. BEGIN (* procedure PresentChar *)
  523. strKey[0] := CHR(LastKey);
  524. strKey[1] := CHR(0);
  525. passedTests := passedTests AND
  526. (NOT (PosUtils.Present( strKey, TheStr )));
  527. END PresentChar; (* procedure *)
  528. PROCEDURE DigitToLeft;
  529. (* Fails if there is a digit to the left of the current position *)
  530. (* Called by 'D', 'A', 'C', 'F', 'J', 'K' commands. *)
  531. BEGIN (* procedure DigitToLeft *)
  532. IF curPos > 0 THEN
  533. passedTests := passedTests AND
  534. (NOT PosUtils.IsNumericChar( TheStr[curPos-1] ));
  535. END; (* if curPos *)
  536. END DigitToLeft; (* procedure *)
  537. PROCEDURE LeftCurrency;
  538. (* Fails if there is a currency symbol to the left of the cursor *)
  539. (* Called by 'L', 'A', and 'C' commands. *)
  540. BEGIN (* procedure LeftCurrency *)
  541. IF curPos > 0 THEN
  542. passedTests := passedTests AND
  543. (NOT (TheStr[curPos-1] = CurrencySymbol ));
  544. END; (* if curPos *)
  545. END LeftCurrency; (* procedure *)
  546. PROCEDURE RightCurrency;
  547. (* Fails if there is a currency symbol to the right of the cursor *)
  548. (* Called by 'R' and 'H' commands. *)
  549. BEGIN (* procedure RightCurrency *)
  550. IF curPos < fieldEnd THEN
  551. passedTests := passedTests AND
  552. (NOT (TheStr[curPos+1] = CurrencySymbol ));
  553. END; (* if curPos *)
  554. END RightCurrency; (* procedure *)
  555. PROCEDURE PeriodToTheLeft;
  556. (* Fails if there is a period to the left of the cursor *)
  557. (* Called by 'Z', 'C', and 'K' commands. *)
  558. BEGIN (* procedure PeriodToTheLeft *)
  559. IF curPos > 0 THEN
  560. passedTests := passedTests AND
  561. (NOT (TheStr[curPos-1] = periodChar ));
  562. END; (* if curPos *)
  563. END PeriodToTheLeft; (* procedure *)
  564. PROCEDURE WithinDecimalPlaces;
  565. (* if REAL and decimal hit, have too many digits been typed? *)
  566. (* Called by 'W' command. *)
  567. VAR
  568. strIndex : CARDINAL;
  569. BEGIN (* procedure WithinDecimalPlaces *)
  570. IF (DecimalPlaces > 0 ) AND
  571. PosUtils.PresentPos( periodChar, TheStr, strIndex ) AND
  572. (strIndex < curPos) AND
  573. (VAL(INTEGER,(curPos - strIndex)) > DecimalPlaces) THEN
  574. passedTests := FALSE;
  575. END; (* if PosUtils.PresentPos *)
  576. END WithinDecimalPlaces; (* procedure *)
  577. PROCEDURE MinusOK;
  578. (* called only when minusChar key hit. Fails if minusOK FALSE. *)
  579. (* Called by 'M', 'A', 'B', 'C', and 'F' commands. *)
  580. BEGIN (* procedure MinusOK *)
  581. passedTests := passedTests AND minusOK;
  582. END MinusOK; (* procedure *)
  583. PROCEDURE PeriodOK;
  584. (* called only when '.' key hit. Fails if periodOK FALSE. *)
  585. (* Called by 'Y', 'G', and 'H' commands. *)
  586. BEGIN (* procedure PeriodOK *)
  587. passedTests := passedTests AND periodOK;
  588. END PeriodOK; (* procedure *)
  589. PROCEDURE GetNextState() : CARDINAL;
  590. (* compute the next state from the current state and state table *)
  591. BEGIN (* procedure GetNextState *)
  592. IF stateTable[ curTable ][ keyClass ][ states[curTable]*MaxProcs+2 ]
  593. = blank THEN
  594. RETURN( states[ curTable ] ); (* no change if blank *)
  595. ELSIF StrConv.StrToCardinal( stateTable[ curTable ][ keyClass ]
  596. [ states[curTable]*MaxProcs+2 ], 0,
  597. nextState ) THEN
  598. RETURN( nextState );
  599. ELSE
  600. RETURN( ErrorState );
  601. END; (* if stateTable *)
  602. END GetNextState; (* procedure *)
  603. BEGIN (* procedure KeyAction *)
  604. (* index into the state table to pick up the two action characters *)
  605. procLetters[0] := stateTable[ curTable ][ keyClass ]
  606. [ states[curTable]*MaxProcs ];
  607. procLetters[1] := stateTable[ curTable ][ keyClass ]
  608. [ states[curTable]*MaxProcs+1 ];
  609. IF states[ curTable ] = ErrorState THEN
  610. (* This is an internal error *)
  611. Spkr.Noise( Spkr.Beep, Spkr.RealHigh, Spkr.Long );
  612. BlankOutField( 0 );
  613. curPos := 0;
  614. ComputeNextState;
  615. ELSE
  616. (* Ever optimistic, set up the series of edit tests *)
  617. passedTests := TRUE;
  618. (* Execute all the procs called for in the state table *)
  619. FOR whichLetter := 0 TO MaxProcs-2 DO
  620. CASE procLetters[ whichLetter ] OF
  621. 'A' : MinusOK; (* MPLD *)
  622. PresentChar;
  623. LeftCurrency;
  624. DigitToLeft; |
  625. 'B' : MinusOK; (* MP *)
  626. PresentChar; |
  627. 'C' : MinusOK; (* MPLDZ *)
  628. PresentChar;
  629. LeftCurrency;
  630. DigitToLeft;
  631. PeriodToTheLeft; |
  632. 'D' : DigitToLeft; |
  633. 'E' : ErrorInKeyStroke; |
  634. 'F' : MinusOK; (* MPD *)
  635. PresentChar;
  636. DigitToLeft; |
  637. 'G' : PeriodOK; (* YP *)
  638. PresentChar; |
  639. 'H' : PeriodOK; (* YPR *)
  640. PresentChar;
  641. RightCurrency; |
  642. 'I' : InsertCharacter; |
  643. 'J' : PresentChar; (* PD *)
  644. DigitToLeft; |
  645. 'K' : PresentChar; (* PDZ *)
  646. DigitToLeft;
  647. PeriodToTheLeft; |
  648. 'L' : LeftCurrency; |
  649. 'M' : MinusOK; |
  650. 'P' : PresentChar; |
  651. 'R' : RightCurrency; |
  652. 'W' : WithinDecimalPlaces; |
  653. 'Y' : PeriodOK; |
  654. 'Z' : PeriodToTheLeft; |
  655. ' ' : ; (* do nothing on blank *)
  656. ELSE
  657. ErrorInKeyStroke;
  658. END; (* case ord *)
  659. END; (* for whichLetter *)
  660. escapeHit := FALSE; (* after a real character *)
  661. states[ curTable ] := GetNextState(); (* one change per keystroke *)
  662. END; (* if states *)
  663. END KeyAction; (* procedure *)
  664. (*
  665. Here is the start of ReadNumericField, proper.
  666. *)
  667. BEGIN
  668. (* First we make sure the FieldWidth is okay. *)
  669. IF FieldWidth = 0 THEN
  670. FieldWidth := HIGH( TheStr ) + 1;
  671. ELSE
  672. FieldWidth := Numbers.Min( HIGH(TheStr) + 1, FieldWidth );
  673. END;
  674. FieldWidth := Numbers.Min( VWindows.EndCol(WindowHandle) - col1,
  675. FieldWidth );
  676. SavedHeight := VWindows.GetCursorHeight(WindowHandle);
  677. IF UserOps.InsertMode THEN
  678. VWindows.SetCursorHeight( WindowHandle, 6 )
  679. ELSE
  680. VWindows.SetCursorHeight( WindowHandle, 2);
  681. END;
  682. fieldEnd := FieldWidth - 1;
  683. (* save the original string. ESC key will restore *)
  684. StrEdit.AssignStr( TheStr, originalStr );
  685. (* set the original states *)
  686. states[ InputTable ] := 0;
  687. states[ EditOverTable ] := 0;
  688. states[ EditInsertTable ] := 0;
  689. editInProgress := FALSE; (* must first hit editing key to start *)
  690. escapeHit := TRUE; (* allow esc exit until another key *)
  691. IF DecimalPlaces = 0 THEN (* Note: Real numbers with 0 decimal places *)
  692. periodOK := FALSE; (* cannot have a decimal point in them. *)
  693. ELSE
  694. periodOK := TRUE;
  695. END; (* if DecimalPlaces *)
  696. (* initially in input mode unless there is something already there. *)
  697. curPos := 0; (* appropriate for new entry *)
  698. curTable := InputTable; (* will be adjusted if required *)
  699. REPEAT
  700. VWindows.DrawStr( WindowHandle, col1, row1, SYSTEM.ADR(TheStr), 0 );
  701. VWindows.GotoXY( WindowHandle, col1 + curPos, row1 );
  702. LastKey := KbdInput.KeyHit( KbdInput.AnyKeyNum );
  703. IF NOT editInProgress THEN
  704. (* completely different actions until "editing" key hit. *)
  705. CASE LastKey OF
  706. Key.Home, Key.End, Key.Right, Key.Del, Key.Ins:
  707. StrEdit.LeftJustify( TheStr, FieldWidth );
  708. editInProgress := TRUE;
  709. | Key.AltD, Key.AltE, Key.Space:
  710. (* perform action in the next section *)
  711. editInProgress := TRUE;
  712. | ZeroKey..NineKey, Minus, DecimalKey, DollarSignKey :
  713. BlankOutField( 0 );
  714. editInProgress := TRUE;
  715. ComputeNextState;
  716. ELSE
  717. IF NOT BigSets.InSet( ExitKeys, LastKey ) THEN
  718. Spkr.Noise( Spkr.Beep, Spkr.Normal, Spkr.Short );
  719. END; (* if NOT *)
  720. END; (* case Lastkey *)
  721. END; (* if NOT *)
  722. IF editInProgress THEN
  723. CASE LastKey OF
  724. ZeroKey..NineKey: (* ASCII values of zeroChar to '9' *)
  725. KeyAction( DigitsClass );
  726. IF curTable # InputTable THEN
  727. ComputeNextState;
  728. END; (* if curtable *)
  729. | Minus: (* minusChar *)
  730. KeyAction( MinusClass );
  731. IF curTable # InputTable THEN
  732. ComputeNextState;
  733. END; (* if curtable *)
  734. | DecimalKey:
  735. KeyAction( DecimalPointClass );
  736. IF curTable # InputTable THEN
  737. ComputeNextState;
  738. END; (* if curtable *)
  739. | DollarSignKey:
  740. KeyAction( DollarClass );
  741. IF curTable # InputTable THEN
  742. ComputeNextState;
  743. END; (* if curtable *)
  744. | Key.Ins:
  745. UserOps.InsertMode := NOT UserOps.InsertMode;
  746. IF UserOps.InsertMode THEN
  747. VWindows.SetCursorHeight( WindowHandle, 6 )
  748. ELSE
  749. VWindows.SetCursorHeight( WindowHandle, 2);
  750. END;
  751. | Key.Del:
  752. DeleteCharacter;
  753. ComputeNextState;
  754. LastKey := 0; (* Don't want it seen as exit key *)
  755. | Key.BackSpace:
  756. IF LeftMove() THEN
  757. DeleteCharacter;
  758. ComputeNextState;
  759. END; (* if LeftMove *)
  760. | Key.Left: (* left arrow *)
  761. LastKey := 0; (* prevent leaving field *)
  762. IF LeftMove() THEN
  763. ComputeNextState;
  764. END; (* if LeftMove *)
  765. | Key.Right: (* right arrow *)
  766. LastKey := 0; (* prevent leaving field *)
  767. RightMove;
  768. ComputeNextState;
  769. | Key.Escape:
  770. IF NOT escapeHit THEN (* second escape gets out of frame *)
  771. PreviousContents; (* restore original value *)
  772. ComputeNextState;
  773. escapeHit := TRUE;
  774. LastKey := 0; (* prevent frame exit *)
  775. editInProgress := FALSE; (* and take out of edit mode *)
  776. ELSE
  777. StrEdit.RightJustify( TheStr, FieldWidth ); (* allow exit *)
  778. END; (* if not escapeHit *)
  779. | Key.Home:
  780. curPos := 0; (* move to leftmost char in field *)
  781. ComputeNextState;
  782. | Key.End:
  783. FirstSpaceOrEnd; (* put cursor after/on last char *)
  784. ComputeNextState;
  785. | Key.AltD, Key.Space: (* delete entire entry *)
  786. BlankOutField( 0 );
  787. curPos := 0;
  788. ComputeNextState;
  789. | Key.AltE: (* erase from cursor to field end *)
  790. BlankOutField( curPos );
  791. ComputeNextState;
  792. ELSE
  793. IF NOT BigSets.InSet( ExitKeys, LastKey ) THEN
  794. Spkr.Noise( Spkr.Beep, Spkr.Normal, Spkr.Short );
  795. ELSE (* always clean up before you can leave - might fail! *)
  796. (* CleanUpTheStr will change LastKey if it fails - prevent exit. *)
  797. IF NOT CleanUpTheStr() THEN
  798. ComputeNextState;
  799. ELSE
  800. StrEdit.RightJustify( TheStr, FieldWidth );
  801. END; (* if NOT *)
  802. END; (* if NOT *)
  803. END;
  804. END; (* if editInProgress *)
  805. UNTIL BigSets.InSet( ExitKeys, LastKey );
  806. VWindows.SetCursorHeight( WindowHandle, SavedHeight );
  807. END ReadNumericField;
  808. PROCEDURE LoadTableRow
  809. ( inputStr : ARRAY OF CHAR; (* action string for input *)
  810. overStr : ARRAY OF CHAR; (* string for overstrike *)
  811. insertStr : ARRAY OF CHAR; (* string for insert mode *)
  812. keyClass : KeyClassType (* second dimension of table *)
  813. );
  814. (* Loads the three action strings into the state table array *)
  815. (* Used to initialize the array *)
  816. BEGIN (* procedure LoadTableRow *)
  817. StrEdit.AssignStr( inputStr, stateTable[ InputTable ] [ keyClass ] );
  818. StrEdit.AssignStr( overStr, stateTable[ EditOverTable ] [ keyClass ] );
  819. StrEdit.AssignStr( insertStr, stateTable[ EditInsertTable ] [ keyClass ] );
  820. END LoadTableRow; (* procedure *)
  821. PROCEDURE InitStateTable;
  822. (* Initialize the state table; called once when field entered *)
  823. (* Invoke from the initialization section of the module *)
  824. BEGIN (* procedure InitStateTable *)
  825. LoadTableRow( " I1 I1 I5 I3WI4 I5", " I RI I I ", " I E E I ",
  826. DigitsClass );
  827. LoadTableRow( "MI2 E E E E E ", "AI MI BI AI ", "CI E FI AI ",
  828. MinusClass );
  829. LoadTableRow( "YI4YI4YI4YI4 E YI4", "GI HI GI YI ", "GI E E E ",
  830. DecimalPointClass );
  831. LoadTableRow( " I3 E I3 E E E ", "JI PI I JI ", "KI E E JI ",
  832. DollarClass );
  833. END InitStateTable; (* procedure *)
  834. PROCEDURE Init();
  835. BEGIN
  836. IF Initialized THEN
  837. RETURN;
  838. ELSE
  839. Initialized := TRUE;
  840. END;
  841. BigSets.Init();
  842. ErrorManager.Init();
  843. KbdInput.Init();
  844. Key.Init();
  845. LowLevel.Init();
  846. M2Strings.Init();
  847. Numbers.Init();
  848. PosUtils.Init();
  849. ScrnTypes.Init();
  850. Spkr.Init();
  851. StrConv.Init();
  852. StrEdit.Init();
  853. UserOps.Init();
  854. VWindows.Init();
  855. InitStateTable(); (* load the table with action strings *)
  856. END Init;
  857. BEGIN
  858. Initialized := FALSE;
  859. Init();
  860. END NumInput.