-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathANATOMY.TXT
1691 lines (1344 loc) · 54.5 KB
/
ANATOMY.TXT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Anatomy of an Application
-- or --
What HP Don't Tell You About Developing
System Manager Applications for the HP95LX
by
Craig A. Finseth
Imperial College, London, September 1992
[ This version has been revised slightly to take into account
some of the changes in Version 2.0 of MemUtil. ]
This paper describes how an HP95LX system manager-compliant
application is put together. It tries to cover the various things
that HP left out of their documentation. It assumes that you:
- know what an HP95LX is,
- have used one enough to understand what the system manager is,
- are generally familiar with C and Intel assembly language programming,
- know the overall structure of the 8086-series CPU (registers,
segments, etc.),
- have a copy of the HP95 Internal Documentation supplied by HP to
their developers, and
- have a copy of the HP95 development tools supplied by HP (available
via anonymous FTP).
Essentially, this paper does not _replace_ HP's documentation, it
_supplements_ it.
Code examples are from the MemUtil and Freyja applications written by
the author. These are both freely available (see the last section),
and the distributions include the full source code.
All of that said, let's dive in.
SEGMENT STRUCTURE
A system manager-compliant application (from now on, just called "an
application") starts life as an MS/DOS .EXE file. Either the tiny
(code + data up to 64 KBytes) or small (both code and data can be up
to 64 KBytes each) model can be used. Almost everyone except the
Forth people use the small model, as there is no advantage to using
the tiny model.
The program's code space must be "pure" (i.e., no data can be stored
there). The system manager tracks which part of the application is
code and which part is data. Only one code area is allocated in the
95, and that area is shared among all applications. When a non-ROM
application is activated, its code is swapped into that area. The old
code is simply discarded; it is not saved back to disk (memory).
Hence, if an application were to modify its code segment, that
modification can be discarded at any time. Each application has a
separate data area, and that area is never swapped out (it may be
moved, though, anytime the system manager is invoked).
Other general notes:
- Only system manager (preferred) and documented MS/DOS calls should
be used. Going directly to the hardware is _not_ advised.
- They really mean that for the serial port: there are a number of
bugs / oddities about the serial port hardware.
- They mean that a little less about the video hardware. However,
unlike the ROM BIOS routines, the system manage display routines are
fairly fast so you have much less incentive to go around them than
when writing traditional MS/DOS programs.
- Everyone will ignore this recommendation about the keyboard, in
particular for various TSRs. However, a real system-manager compliant
application _must_ only obtain keyboard input by means of the
m_event-related calls.
- Applications _must_ do their own memory management using system
manager or MS/DOS calls. Do not use language-supplied primitives, as
they assume a pure MS/DOS environment and are apt to get confused by,
for example, having the data segment move around beind their backs.
- FAR calls and data references must be calculated at run time. (See
the full description of the fixup bug presented in a later section.)
All of that stuff sounds good, but where's some code? Here is the
first code snippet. This code sets up the segment structure for the
application. It _replaces_ the c0s.obj module ordinarily linked in.
This -- like all examples -- is for Borland C, but will work with
Microsoft C with a few minor changes:
DOSSEG ; macro that defines segment structure
.model small ; specifies model
.stack 10240 ; this defines your stack size
.data? ; start data segment
segpad db 15 dup (?) ; this is unused, but ensures that
; the code ends in a different
; paragraph from where the usable data
; starts
.code ; switch to code
org 10H ; skip 16 bytes
end ; that's it, the program counter will
; drop through to whatever is next
Aside from this different object header (and a host of special calls
and assumptions within the program!), there are only two differences
between .EXE files and their corresponding .EXM application files.
First, the first two bytes of the .EXE file are set to a different
"magic number" than that used for .EXE files. In this case, the bytes
are set to 0x44 and 0x4c. This changed number tells the system
manager that the file is a system manager-compliant application.
Second, the (unused) overlay count field (bytes 26 and 27 from the
start) are patched to the location of the divider between the code
segment and the data segment. This value is used by the system
manager to figure out how much of your application can be shared. (The
overlay count field must be unused, as the total code space can be at
most 64 KBytes.)
For precise details of the changes, see the "makeexm.c" file included
with the MemUtil and Freyja distributions.
MAIN LOOP
Up above, the comment said that the program counter will drop through
to whatever is next. So, what's next? This:
static FLAG isterm = FALSE; /* are we quitting the program? */
void
main(void)
{
EVENT e;
m_init();
e.kind = E_KEY;
do {
if (e.kind == E_KEY) Display();
m_event(&e);
switch (e.kind) {
case E_NONE:
m_telltime(2, -3, 23);
break;
case E_KEY:
/* lots more code */
break;
case E_ACTIV:
Refresh();
Display();
break;
}
} while (!isterm && e.kind != E_TERM);
m_fini();
}
The main() routine has a declaration that conforms to the ANSI-C
standard, but is somewhat unusual. It accepts no parameters as the
system manager has no concept of a command line, and returns no value.
The central local variable is an event.
The program starts by calling m_init(), which sets things up for the
system manager. This, like all system manger calls, is actually a
macro. It looks like this:
#define SC_PM 6
#define F_M_INIT (SC_PM * 256) + 0
#define m_init() c_service(F_M_INIT)
(This code Copyright by Lotus Development Corp.) This call passes one
parameter to the c_service() routine. This parameter tells, using a
tedious but simple coding scheme, which call to invoke. A call that
takes parameters uses a function prototype to cast the parameter to
the proper type. For example, the m_event() call looks like:
#define SC_EVENT 1
#define F_M_EVENT (SC_EVENT * 256) + 0
#define m_event(a) \
c_service(F_M_EVENT,(void far *)(a) )
(This code Copyright by Lotus Development Corp.) Values are returned
using call-by-reference (you pass a pointer to a buffer) or, if the
value fits into a single, 16-bit value, as a normal return in the AX
register. The carry flag and other such holdovers from assembly
language are not used. (This is the last function calls whose
definition we will expand.)
What is this c_service() routine? It is an assembly language stub
that links between the C code and the system manager call. It looks
like this:
_c_service proc near
push BP ; save old base pointer
mov BP,SP ; set up for base-relative addressing
xchg DI,[BP+4] ; put operation code in DI, and old
; contents of DI where the operation
; code was (this is presumably
; restored by the system manager code)
pop BP ; clean up the stack a little
int 60H ; invoke the system manager
ret ; all done
_c_service endp
The version of this routine supplied by HP has hooks for patching the
int instruction's 60H to 61H. These are probably left over from
internal development as there is no way to make use of that hook since
you are not allowed to alter your code image.
Returning to our top-level loop, we have:
e.kind = E_KEY;
do {
if (e.kind == E_KEY) Display();
The initialization of e.kind provides for a clean loop structure. The
first time -- and each time through the loop after a key press -- the
Display() routine is called. This routine constructs (almost all) of
the display and will be discussed later.
The program then calls the m_event() routine to obtain the next event.
MemUtil only handles three types of events.
m_event(&e);
switch (e.kind) {
case E_NONE:
m_posttime();
break;
case E_KEY:
/* lots more code */
break;
case E_ACTIV:
Refresh();
Display();
break;
}
The E_NONE event is returned whenever the event manager hasn't got
anything else to return. In an idle, running system, this event will
be returned to the active application about every half-second. It is
used for updating displays, in this case, the time of day clock update
handled with m_posttime().
The E_KEY event is returned when a keystroke is available. We will go
into this process in more depth later.
The E_ACTIV event is returned when your application is being "woken
up." From your application's point of view, the following steps
happen during the deactivate / activate cyle started when the user
selects another application:
- You are running fine, and have called m_event(), so your application
is blocked waiting on input.
- The user presses the "hot key" for another application.
- The m_event() call returns with a E_DEACT event.
- You do whatever cleanup is required (for example, updating the
clipboard), then call m_event() again. At this point, you have been
deactivated.
- Your application hangs for a long time.
- Eventually, the user presses the "hot key" for your application.
- The m_event() call returns with a E_ACTIV event.
- Your application gets itself started again.
In MemUtil's case, getting started involves calling the Refresh()
routine to update any changed data (this application's purpose is to
read and display system data, so it has to fetch any changed data),
then calling Display() to reconstruct the display.
Other events that can be returned are:
- E_BREAK: a Control-Break has been encountered.
- E_TERM: your application is being shut down (for example, the user
has requested closure from the low memory screen). The next call to
m_event() won't return but it is nicer if you close down and clean up
by calling m_fini().
- E_ALARM_EXP: your application's alarm has expired.
- E_ALARM_DAY: daily chance to set an alarm.
- E_TIMECHANGE: the system date or time has been changed.
Finally, the main loop is closed with this code:
} while (!isterm && e.kind != E_TERM);
m_fini();
}
The isterm flag is a global. When the loop exits, the program calls
m_fini(), which never returns.
HANDLING KEYBOARD INPUT
The main loop handles all events. Most events are of a "housekeeping"
nature and your program must handle them properly or it won't work.
Keyboard events, on the other hand, are what gives your application
its "feel." Before we look at too many details, let's review the
overall structure of MemUtil and see what we want to implement.
MemUtil is organized around "tasks" or "views" of six types of data.
The tasks are help, short applications, long applications, memory
chains, memory, and characters. All six are presented to the user in
the same way: the task appears as a one-dimensional array of something
and the user is "at" a current selection. The details vary among the
tasks:
array of... can see...at a time
help lines 12
short applications applications 12
long applications applications 1
chains chain links 12
memory paragraphs 6
characters character set entries 12
(From now on, I'll use the term "object" to refer to the "something"
for the current task.) I use the following enum to list the task
types:
/* display status */
enum dstate { DHELP, DAPPS, DAPLONG, DCHAINS, DMEM, DCHARS };
and have an array of structures that holds the current status of each
task:
/* task control structures */
struct task {
enum dstate disp; /* which type of display to use */
unsigned height; /* height of screen in objects */
unsigned start; /* first object */
unsigned num; /* number of objects */
unsigned cur; /* current object, >= 0 and < num */
};
static struct task t[] = {
{ DHELP, HEIGHT, 0, 0, 0 },
{ DAPPS, HEIGHT, 0, NUMAPPS, DEFAULTAPP },
{ DAPLONG, 1, 0, NUMAPPS, DEFAULTAPP },
{ DCHAINS, HEIGHT, 0, 1, 0 },
{ DMEM, HEIGHT / 2, 0, 64, 0 },
{ DCHARS, HEIGHT, 0, 512, 0 } };
For the short applications, long applications, and characters tasks,
the only structure entry that changes is the current object. For the
help and chains tasks, the number of items is set up when the program
starts and remains essentially unchanged. The memory task is the only
one that regularly varies the start and number of items entries. The
current task is tracked with a pointer variable:
static struct task *curt = &t[DAPPS]; /* current task */
The program does two forms of sanity checking at the top of the main
loop. While this code could be placed somewhere else, putting the
check here means that it need not be duplicated.
First, the program checks to ensure that the current object is within
the array bounds:
if (curt->cur > curt->num - 1) curt->cur = curt->num - 1;
if (curt->num == 0) curt->cur = 0;
Since the values are all unsigned, it is meaningless to check for
negative values.
Second, the short application and long application tasks always have
the same current object. We slave them together with this code:
/* ensure that the APPS and APLONG selections stay in sync */
if (curt == &t[DAPPS]) t[DAPLONG].cur = curt->cur;
if (curt == &t[DAPLONG]) t[DAPPS].cur = curt->cur;
MemUtil's main display is modeless: pressing a key always has the same
effect, regardless of the current task. (Of course, the menu, file
getter, and dialog boxes are modes, but they are not part of the main
screen.) The program implements this "modelessness" by having most
functions simply ignore the current state. For example, this code
implements the F1 key, which selects the help task:
case 0x3b00: /* F1 */
Push_Task();
curt = &t[DHELP];
break;
The Push_Task routine saves the current task for use with the Esc key.
This "modelessness" is not quite perfect: the program implements two
useful "warts." The first of these is the Enter key:
case CR:
if (curt == &t[DAPPS] || curt == &t[DAPLONG]) {
Push_Task();
curt = &t[DMEM];
curt->start = acbs[t[DAPPS].cur].ds;
curt->num = APPSEGSIZE;
curt->cur = 0;
}
else if (curt == &t[DCHAINS]) {
Push_Task();
curt = &t[DMEM];
curt->start = links[t[DCHAINS].cur].seg;
curt->num = links[t[DCHAINS].cur].size;
curt->cur = 0;
}
break;
This code simply "redirects" the function in this manner:
in makes it appear as if this key was typed:
short application F5 (long application)
long application F4 (memory)
chains F4 (memory)
But, there's more. If you switch _from_ the long application task, it
automatically initializes the memory task to point to the current
application's data space. If you switch from the chains task, it
automatically initializes the memory task to point to the current
chain area.
The fact that all tasks use the same model makes it very easy to
implement the arrow keys. The Home key moves you to the first object
and the End key moves you to the last:
case 0x4700: /* Home */
curt->cur = 0;
break;
case 0x4f00: /* End */
curt->cur = curt->num - 1;
break;
The Up and Down Arrow keys move you by one object. Note the special
checking required due to the use of unsigned values:
case 0x4800: /* Up */
if (curt->cur >= 1) curt->cur--;
break;
case 0x5000: /* Down */
curt->cur++;
break;
The PgUp and PgDn keys move you up one screen's worth of objects:
case 0x4900: /* PgUp */
if (curt->cur >= curt->height)
curt->cur -= curt->height;
else curt->cur = 0;
break;
case 0x5100: /* PgDn */
curt->cur += curt->height;
break;
The Left and Right Arrow keys have no meaning in a one-dimensional
array model, and I wanted to be able to move by larger steps than one
screen. Hence, I have these keys move by 10% of the objects:
case 0x4b00: /* Left */
amt = curt->num / 10;
if (amt < 1) amt = 1;
if (curt->cur >= amt)
curt->cur -= amt;
else curt->cur = 0;
break;
case 0x4d00: /* Right */
amt = curt->num / 10;
if (amt < 1) amt = 1;
curt->cur += amt;
break;
The handling of the rest of the keystrokes will be discussed in later
sections.
DISPLAYING THE SCREEN
The keyboard input handling defines part of your application's
personality: the display establishes the rest. The HP Internal
Documentation suggests (when handling E_ACTIV events) that you
regenerate the display from first data instead of storing a copy of
the screen. I chose to follow the suggested philosophy throughout
MemUtil (Freyja does this regeneration too, but its redisplay was
devised long before the 95 and this method was selected for different
reasons).
The display code is divided into two parts: the generic "framing" code
and the task-specific code.
I added a new (to the HP95) display feature: a slider bar that shows
the approximate size and location of the current screen in relation to
the entire array of objects. Much of the apparent complexity of the
framing code is to implement this feature. This feature entailed two
major design choices.
First, a slider bar would in general be more familiar if it ran along
the left or right side of the display than along the top. However,
screen space is at a premium in the HP95, and the existing application
standards had already defined a standard display structure: the double
bar in the second display line. I chose to elaborate on that
structure by making use of that bar instead of inventing something
incompatible. As a side effect of this decision, the separation of
framing code and per-task code was kept clean as the per-task code
could display complete lines.
Second, I chose to implement the slider as a single "pinched" line.
There are a number of other characters that I could have selected.
However, Edward Tufte in his "The Visual Display of Quantitative
Information" (1983, Graphics Press, Cheshire, Connecticut) recommends
minimizing the amount of "ink" used, and I felt that the minimal
approach improved the quality of the display.
The framing code looks like this:
void
Display(void)
{
char buf[41]; /* we know how big the screen is */
int start; /* this stuff is for calculating the slider bar */
int stop;
unsigned ustart;
unsigned ustop;
unsigned tmp;
VidCurOff(); /* force the cursor off */
/* display the top line */
m_disp(-3, 0, "MemUtil V2.0 ", 40, 0, 0);
/* build the slider bar, using 16-bit
arithmetic */
memset(buf, '\xCD', 40);
if (curt->num > 0) {
if (curt->num > 512) { /* losing precision isn't so bad... */
tmp = curt->num / 40;
ustart = curt->cur / tmp;
if (ustart > 39) ustart = 39;
ustop = (curt->cur + curt->height) / tmp;
if (ustop <= ustart) ustop = ustart + 1;
if (ustop > 40) ustop = 40;
memset(&buf[ustart], '\xC4', ustop - ustart);
}
else { /* don't worry about overflow */
start = (curt->cur * 40) / curt->num;
if (start < 0) start = 0;
if (start > 39) start = 39;
stop = ((curt->cur + curt->height) * 40 ) / curt->num;
if (stop <= start) stop = start + 1;
if (stop > 40) stop = 40;
memset(&buf[start], '\xC4', stop - start);
}
}
m_disp(-2, 0, buf, 40, 0, 0);
/* select the per-task display */
switch (curt->disp) {
case DHELP: Disp_Help(); break;
case DAPPS: Disp_Apps(); break;
case DAPLONG: Disp_ApLong(); break;
case DCHAINS: Disp_Chains(); break;
case DMEM: Disp_Mem(); break;
case DCHARS: Disp_Chars(); break;
}
/* put up the function key labels; note the
"1" in the second-to-last position that
specifies inverse video */
m_disp(11, 0, "Help Apps ApLong Para Length ", 40, 1, 0);
m_disp(12, 0, " Chains Mem Chars Key Offset", 40, 1, 0);
}
In general, I have found the system manager display routines to be
fast and well-matched to the required functionality. However, their
screen model has line -3 at the top and line 12 at the bottom. I have
found no good reason for this (other than historical), and it has
caused me numerous bugs during development.
[ The historical reason for this choice stems from the evolution of
the system manager. Most system manager interface functions are taken
from the Lotus internal development environment. This explains both
why they are so well-suited to an application's needs and also why
they are so idiosyncratic. Anyway, a standard Lotus screen has three
lines of heading before you get to the spreadsheet, which then starts
at line zero. That explains the -3. However, an HP95 application
will in general have only two lines of header (title and double bar)
before you get to the screen, which therefore starts at line -1.
That's life. ]
The per-task display routines all do essentially the same job, but
they vary in how complicated it is to build the display. The help
task's routine is simple and will be used to illustrate the basic
structure:
void
Disp_Help(void)
{
char buf[BUFFSIZE];
int cnt;
/* Loop until you get to the end of the screen or
run out of objects. */
for (cnt = 0; cnt < HEIGHT && cnt + curt->cur < curt->num; cnt++) {
/* Put into a buffer, with 40 trailing spaces
xsprintf is a private version of sprintf that
has somewhat different performance tradeoffs. */
xsprintf(buf, "%s ",
Help_Line(curt->cur + cnt));
/* Display the first 40 characters of the
buffer. */
m_disp(-1 + cnt, 0, buf, 40, 0, 0);
}
/* If you ran out of lines and have not run
out of screen, blank the rest of the screen. */
if (cnt < HEIGHT) {
m_clear(-1 + cnt, 0, HEIGHT - (-1 + cnt), 40);
}
}
and that's it.
MENUS
It is important that applications handle the menu bar properly. The
most important place is in the main event loop, and my menu key code
is this simple:
case 0xc800: /* MENU */
GetMenu();
break;
Of course, implementing that call is a little less simple. The routine
that does this operates in three phases.
The first phase handles the declarations and menu setup. You have to
declare a MENUDATA structure and an EVENT structure, along with a
couple of housekeeping variables. (Note: this program does not make
use of the GetMenu routine's return value. However, Freyja does and I
like to keep the structure of and interfaces to the routines the same
among my programs.)
FLAG
GetMenu(void)
{
MENUDATA u;
EVENT e;
int which = 0;
FLAG isdone = FALSE;
/* This routine initializes the menu
structure. Note the use of embedded \0
characters to delimit the menu entries
and ANSI concatenated strings to handle
the hex constants. \x1a is a right
arrow character. */
menu_setup(&u, "A)\x1a" "Clip\0B)\x1a" "File\0C)\x1a"
"Clip(bin)\0D)\x1a" "File(bin)\0Quit\0", 5, 1, NULL, 0, NULL);
VidCurOff(); /* turn the cursor off */
menu_on(&u); /* turn on the menus */
The second phase handles the menu entry proper. It has the same
overall structure as the main loop. Instead of calling the
application's display routine, it calls menu_dis() to display the
current menu (remember that the selected entry is highlit, so the
"current" menu _does_ change) and the menu bar. (The double bar
display wasn't built into the menu routine because Lotus 1-2-3 doesn't
use the double bar.)
e.kind = E_KEY;
while (!isdone) {
if (e.kind == E_KEY || e.kind == E_ACTIV) {
menu_dis(&u);
m_disp(-1, 0, "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD", 40, 0, 0);
}
m_event(&e); /* get the next event */
switch (e.kind) {
/* You've been suspended and are back. Do a
full refresh/display build. When you come
around again, the menu will be redisplayed by
means of the above code. */
case E_ACTIV:
Refresh();
Display();
break;
/* Got a terminate, so clean up and exit. */
case E_TERM:
isterm = TRUE;
isdone = TRUE;
break;
/* Got a key. */
case E_KEY:
if (e.data == ESC) { /* you have to handle */
which = -1; /* ESC yourself */
isdone = TRUE;
}
else { /* handle all other key
presses; which is set to
0..#entries1 when an item is
selected */
menu_key(&u, e.data, &which);
if (which != -1) isdone = TRUE;
}
break;
}
}
In the third phase, the menu interaction is over and it is time to
actually do something.
menu_off(&u); /* Turn off the menu display; this
also restores what's underneath, but
you're going to regenerate it
anyway. */
if (isterm) return(TRUE); /* ESC pressed, so don't do
anything. */
switch (which) {
case 0:
/* do the first selection... */
break;
case 1:
/* do the second */
break;
...
case 4:
isterm = TRUE; /* Quit selected. */
break;
}
return(TRUE);
}
And that's about it. Menus aren't all that difficult, are they?
DISPLAYING A MESSAGE / ACCEPTING ONE KEY
These are two separate, but related functions. The first function is
to display a one-line message and wait for the user to acknowledge it
by pressing a key. The second function is to display a prompt,
accept a key of input, and return the entered key.
A typical call might look lie this:
key = GetKey("Press key to view");
(This example is part of the code that handles the F8 key. The
function on this key asks the user to press any key, then brings up
the Characters task with that key selected.)
The GetKey routine looks like this:
int
GetKey(char *str)
{
EVENT e;
m_lock(); /* Lock out application switching. If the
user presses a "hot key," it won't do anything. */
/* Display the message. The call actually
displays two lines, although we never use the
second. Note that the system manager calls
tend to take (pointer to string, length of
string) pairs and not use NUL-terminated
strings. */
message(str, strlen(str), "", 0);
do { /* Until you get a key press... */
m_event(&e);
} while (e.kind != E_KEY);
msg_off(); /* Turn off the message, which restores what
was underneath. */
m_unlock(); /* Turn application switching back on. */
return(e.data); /* Return the keystroke. */
}
ACCEPTING A STRING
String entry involves another event loop. As with menus, you must
fully support activation, deactivation, etc. The GetStr routine has
this interface:
FLAG GetStr(char *prompt, char *buf, char *deflt)
The first parameter is a NUL-terminated prompt string. The second
parameter is a pointer to an 80-character long buffer to hold the
response. The third parameter is a pointer to a default value, which
must be less than 80 characters long. The routine returns True on
successful string entry or False if there was a problem. As with the
GetMenu routine, it also sets isterm if the application should exit.
MemUtil uses the GetStr routine in only one place: when accepting a
hexadecimal value. Thus, the call looks like this:
char buf[80]; /* response buffer */
char buf2[80]; /* default buffer */
/* construct the default value from the passed-in
(numeric) default value */
xsprintf(buf2, "%x", *val);
/* call the routine, using the passed-in prompt */
if (!GetStr(prompt, buf, buf2)) return(FALSE);
Here's the routine itself:
FLAG
GetStr(char *prompt, char *buf, char *deflt)
{
EDITDATA ed; /* our first exposure to this */
EVENT e; /* you've seen this one before */
FLAG isdone = FALSE;
FLAG ok = TRUE;
int result;
/* Set up the edit data structure. It gets loaded
with the default value, the maximum input length (16),
the prompt, and the (unused) second prompt line. */
edit_top(&ed, deflt, strlen(deflt), 16, prompt, strlen(prompt), "", 0);
/* You've seen this before. We use edit_dis to
update the display. */
e.kind = E_KEY;
while (!isdone) {
if (e.kind == E_KEY || e.kind == E_ACTIV) edit_dis(&ed);
m_event(&e);
switch (e.kind) {
case E_ACTIV:
Refresh();
break;
case E_TERM: /* handle termination */
isterm = TRUE;
ok = FALSE;
isdone = TRUE;
break;
case E_BREAK: /* handle break key */
ok = FALSE;
isdone = TRUE;
break;
case E_KEY:
if (e.data == ESC) { /* handle Esc key */
ok = FALSE;
isdone = TRUE;
}
else { /* handle each keystroke */
edit_key(&ed, e.data, &result);
if (result == 1) isdone = TRUE;
}
break;
}
}
Display(); /* Put the display back: no routine to do
this for you. */
if (!ok) return(FALSE); /* cancel */
xstrcpy(buf, ed.edit_buffer); /* have a valid string, so
copy it to the return
buffer */
return(TRUE);
}
USING THE FILE GETTER
Now we come to the file getter. This is the last -- and messiest --
of the event loops. Much of it will be familiar, yet it has its own
twists.
The interface is:
FLAG GetFile(char *prompt, char *fname, FLAG usestar)
As before, you pass it a prompt string and a pointer to a filename.
The area pointed to by this filename must be at least 79 character
long (the maximum legal length of an MS/DOS file name.) It serves
_both_ as the place to return the new name and as the source for the
default value, so be sure to initialize it before calling this
routine. If usestar is True, force the file name part to "*.*", thus
bringing up a display of all files in the directory. The routine
returns True on successful file name entry or False if there was a
problem. As with the GetMenu routine, it also sets isterm if the
application should exit.
The routine uses these local variables:
FILEINFO fi[100]; /* Can display up to 100 files at
once. Change this constant to change
the maximum number of files that can
be displayed at once.*/
FMENU f; /* file menu extra data structure */
EDITDATA ed; /* you've seen these */
EVENT e;
FLAG isdone = FALSE;
FLAG ok = TRUE;
char dn[FNAMEMAX]; /* working copy of the directory part */
char fn[FNAMEMAX]; /* working copy of the file part */
char *cptr; /* scratch variable */
xstrcpy(dn, fname); /* make a copy of the name */
/* This code splits the full name into the directory
and file parts. It starts at the end and searches
backwards until it finds a /, \, or :. If there isn't
one, it sets the directory part to "". If there isn't
a file part, that is set to "". */
for (cptr = dn + strlen(dn); cptr > dn; --cptr) {
if (*cptr == ':' || *cptr == '/' || *cptr == '\\') break;
}
if (*cptr == ':' || *cptr == '/' || *cptr == '\\') {
xstrcpy(fn, cptr + 1);
*(cptr + 1) = NUL;
}
else {
xstrcpy(fn, cptr);
*cptr = NUL;
}
/* If there is no file part or usestar is True, force
the file name to "*.*". */
if (*fn == NUL || usestar) {
xstrcpy(fn, "*.*");
}
/* Initialize the required parts of the file menu
structure. */
f.fm_path = dn; /* directory name part */
f.fm_pattern = fn; /* file name part */
f.fm_buffer = fi; /* place to put the working names */
f.fm_buf_size = sizeof(fi); /* how big (many) */
f.fm_startline = -2; /* top line... *sigh* */
f.fm_startcol = 0; /* left edge */
f.fm_numlines = 16; /* use all lines */
f.fm_numcols = 40; /* and all columns */
f.fm_filesperline = 3; /* can fit this many across */
/* Now, set up the edit buffer part. Just copy these
values... */
ed.prompt_window = 1;
ed.prompt_line_length = 0;
ed.message_line = prompt;
ed.message_line_length = strlen(prompt);
/* clear the screen */
m_clear(-3, 0, 16, 40);
/* start things up */
if (fmenu_init(&f, &ed, "", 0, 0) != RET_OK) {
GetKey("can't init file getter");
return(TRUE);
}
/* the usual, you've seen all this */
VidCurOff();
e.kind = E_KEY;
while (!isdone) {
if (e.kind == E_KEY || e.kind == E_ACTIV) fmenu_dis(&f, &ed);
m_event(&e);
switch (e.kind) {
case E_ACTIV:
Refresh();
break;
case E_TERM: