Initial revision
[ssr] / StraySrc / Libraries / Sapphire / README
1 A very brief overview of Sapphire
2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3
4 _____________________________________________________________________________
5
6 IS THIS ALL THERE IS?
7
8
9 The Grand Plan was to write a really good library, sell some spiffy
10 applications which used it, and then sell the library. This
11 spectacularly failed to work, mainly because we had no ideas for killer
12 applications. The result is Sapphire.
13
14 Since we'd planned to write some big applications before releasing
15 Sapphire to anyone else, we took a rather relaxed attitude to
16 documentation. There is some LaTeX documentation lying around, but
17 mostly it's initial versions of things, with big gaps where less
18 interesting text should be filled in later. In short, it's no use to
19 anyone.
20
21 On the other hand, we realised that a library of this complexity with no
22 documentation at all would be a total disaster, so all the header files
23 are profusely commented, mostly to the standard of a final-copy
24 reference manual.
25
26 This text file is intended as a brief hacker's eye view of how to write
27 Sapphire applications, and how some of the more complicated and/or
28 interesting bits work.
29
30 _____________________________________________________________________________
31
32 ABOUT THE AUTHORS
33
34
35 Sapphire was designed and written, over the course of a couple of years,
36 by Mark Wooding and Tim `Tycho' Armes. Each source file usually bears
37 the initials (`MDW' or `TMA') of the person mainly responsible for
38 writing it. In some cases, this is misleading. For example, although
39 the `msgs' code for translating message tokens into strings is labelled
40 `MDW', the first two versions of this file were written by Tim: I took
41 over later because I rewrote it to kill the bugs. Similarly, the fact
42 that one of us wrote a source file doesn't tell you who designed it. For
43 example, the `menu' code was entirely written by Tim, although we both
44 worked on the design for several days beforehand.
45
46 _____________________________________________________________________________
47
48 A HISTORY LESSON
49
50
51 Sapphire is an attempt to learn from the experience we both had with
52 STEEL. STEEL (Straylight's Extensive Event-driven Library) is a
53 more-or-less complete re-write of RISC_OSLib, and although it had given
54 up being call-compatible, and had diverged quite considerably by the
55 release of Desktop C, it suffered because of the limitations of its
56 structure. All the original non-RISC_OSLib code had been written by me,
57 in a rather ragged way.
58
59 The name Sapphire comes from the old ITV sci-fi series `Sapphire and
60 Steel', obviously. It's a rotten joke, but that's what you get when you
61 think up names for libraries in a pub. The idea was that two of us
62 could do much better than either acting alone, by bouncing ideas between
63 each other until we sorted out the problems. We threw out any thought
64 of compatibility with any existing software, and any hope of interfacing
65 a high-level language. Then we started coding.
66
67 _____________________________________________________________________________
68
69 INFLUENCES
70
71
72 The design of Sapphire has been influenced by looking at lots of other
73 pieces of software. A lot of structure is based on RISC_OSLib: STEEL
74 was derived from RISC_OSLib, and it did a lot of things right. Another
75 particularly strong influence is Computer Concepts' Advanced Box
76 Interface (ABI) as used in Impression and ArtWorks. (I even went as far
77 as buying the ArtWorks SDK more-or-less to get my hands on some ABI
78 documentation.) There was an influence from what I perceived to be `the
79 way OS/2's Presentation Manager does it', although this wasn't always
80 positive.
81
82 _____________________________________________________________________________
83
84 SUPPORTED LANGUAGES
85
86
87 Sapphire supports ARM assembler. That's about it. One of the last
88 Sapphire-related projects was interfacing C to Sapphire; this is still
89 mostly incomplete. The basic job of making C code call Sapphire code
90 and vice-versa is finished and reliable. The hard bit, handling
91 Sapphire-style definition tables, isn't even begun yet.
92
93 One of the projects I set myself over a year ago was a language, then
94 called `cdata', which would act as a pre-processor for C and allow
95 definition tables to be created without the amount of pain currently
96 required. (If you don't believe me, see the definition of the icon bar
97 menu in the `csapph' test program.)
98
99 _____________________________________________________________________________
100
101 THE SAPPHIRE ENVIRONMENT
102
103
104 Sapphire doesn't use APCS. Let's get that clear right away. You try to
105 use APCS, and Sapphire will hate you for the rest of your life.
106
107 Because the target language is assembler, Sapphire itself sometimes
108 plays fast and loose with the rules, but the basic ideas, such as they
109 are, are presented here.
110
111 Registers R10-R15 are assigned special purposes.
112
113 For architectural reasons, R15 is the program counter, and R14 is the
114 subroutine link register. Because R14 is modified by subroutine calls,
115 it is often used as a temorary register in computations.
116
117 R13 points to a full descending stack. Sapphire doesn't allocate a very
118 big stack unless you ask it to, so be careful with recursion. There's
119 no stack limit checking either.
120
121 R10 and R12 provide the program's current state. R12 is the `workspace'
122 pointer, and R10 is the `object' pointer. The idea is that R12 points
123 to a statically allocated data block, and R10 points to a `currently
124 interesting object'. For example, in a multi-document editor, R10 would
125 point to an information block describing the document currently being
126 processed. Because it's important to hide information between
127 components of a program, different components have different workspace
128 areas. Similarly, different parts of a program see the world at
129 different levels: the part of a drawing program which is concerned with
130 file-level operations keeps R10 as a pointer to the document anchor; the
131 part concerned with manipulating individual objects within a document
132 might keep R10 referring to an object. Note that R10 and R12 rarely
133 take part in inter-component interfaces. Object handles are passed
134 between components in low-numbered registers, as normal arguments, and
135 then copied into R10 later.
136
137 R11 is Sapphire's `application context' pointer. It's also known as the
138 `scratchpad' pointer. From R11 upwards is the `scratchpad': a block of
139 memory available for any purpose. Any procedure may corrupt the
140 scratchpad, so it's never safe to assume that it is preserved over
141 inter-component calls. Immediately below R11 are workspace offsets.
142 These allow Sapphire and its client applications to find statically
143 allocated workspace. If R11 is pointing to the wrong place, Sapphire
144 will crash!
145
146 On a procedure call, this is what usually happens:
147
148 R0--R9 == arguments
149 R10, R12 == variables owned by caller
150 R13 == pointer to full descending stack
151 R11 == application context pointer (Scratchpad)
152
153 On return:
154
155 R0--R9 == return values, or usually preserved
156 R10, R12 preserved
157 R13 == preserved
158 R11 == application context pointer
159 Flags either preserved or set as a return value
160
161 Sapphire's fond of returning information in the flags. For example, if
162 you ask for memory and there isn't any, allocation functions return with
163 carry set. Sometimes, errors are returned in the normal way, by setting
164 overflow and pointing R0 at an error block. If you don't return
165 something useful in a flag, preserve it.
166
167 Sapphire behaves slightly different when it's calling its client back.
168 When you register a handler function, you supply a pointer to the code,
169 and R10 and R12 values to pass it. It will ensure that these values are
170 in those registers at call time.
171
172 _____________________________________________________________________________
173
174 SAPPHIRE'S MEMORY MAP
175
176
177 Sapphire has strong ideas about how an application's memory is laid out.
178 Here's a poor attempt at a diagram:
179
180
181 _____________ <--- Top of current wimpslot
182 | |
183 : Flex :
184 : heap :
185 | |
186 |-------------|
187 | Resizing |
188 | heap |
189 |-------------| <--- Top of initial wimpslot
190 R13 | Stack |
191 |-------------|
192 R11 | Scratchpad |
193 | App context |
194 |-------------|
195 | |
196 |Default heap |
197 | |
198 |-------------|
199 R12 | Workspace |
200 |-------------|
201 | Application |
202 | code |
203 &8000 |_____________| <--- Base of application memory
204
205
206 Hmmm. Not too shabby.
207
208 Now, in words:
209
210 During initialisation, Sapphire's kernel finds the top of available
211 memory and puts the stack there, growing downwards. Below the stack, it
212 allocates the scratchpad area, so that there's a small amount of `slop'
213 between the the stack growing down and the scratchpad growing up. The
214 kernel then examines the available library components, and allocates
215 workspace for them, starting just above the application's read-write
216 area. (A Sapphire application shouldn't actually have any read-write
217 data -- R12-space is a much better way of doing the same job.) Once
218 that's done, it initialises an OS_Heap heap between the top of workspace
219 and the bottom of the application context area just below the
220 scratchpad. This is the `default heap'.
221
222 Above the initial wimpslot is Flex space. Flex is Sapphire's shifting
223 heap manager. It's named after RISC_OSLib's shifting heap, although
224 it's completely rewritten, and much extended. Flex has complete control
225 over the area above the initial wimpslot. The first Flex block is taken
226 by a resizing heap manager, which creates a nonshifting heap there. This
227 is registered automatically with Sapphire's allocation manager, so you
228 can forget about it completely.
229
230 This brings me on to...
231
232 _____________________________________________________________________________
233
234 MEMORY MANAGEMENT
235
236
237 Sapphire sees three sorts of memory:
238
239 * Static memory. This is allocated at initialisation time by the
240 kernel, and put into the workspace area. R12 points here.
241
242 * Fixed-size blocks representing objects. These are allocated and
243 freed at run-time by the program, using Sapphire's allocation
244 manager. R10 points to one of these, usually.
245
246 * Large or variable-size blocks representing data. These are
247 allocated and freed by the program using Flex.
248
249 Typically each large block in Flex-space has an accompanying `anchor'
250 block in heap-space.
251
252 The allocation manager attempts to paper over the mess involved with
253 having more than one nonshifting heap. STEEL actually does this job
254 better, putting the client in control of where blocks get allocated.
255 Sapphire's approach, to fill up one heap and move on to the next, is
256 simpler, but can lead to fragmentation.
257
258 _____________________________________________________________________________
259
260 INITIALISATION
261
262
263 RISC_OSLib (and STEEL) programs start off with a huge chunk of
264 initialisation code, most of which is really boring:
265
266 wimpt_init("progname");
267 dbox_init();
268 win_init();
269 /* ... */
270
271 Sapphire gets rid of all that by using a neat feature of AOF. Each
272 library component, or `unit' as we called them, contains a four-word
273 table entry. The linker collects the entries together, and the kernel
274 can examine them all to see which units are linked in. Each entry
275 contains:
276
277 * The address to store the unit's workspace offset.
278 * The size of workspace required.
279 * Minimum acceptable Scratchpad size (if more than 256 bytes).
280 * Address of initialisation procedure.
281
282 By convention, the first word of a unit's workspace contains flags, bit
283 zero of which indicates that the unit has been initialised. A unit's
284 initialisation procedure performs the following actions:
285
286 * It checks it's not already initialised.
287 * It initialises anything it depends on (this causes the required
288 units to be linked too).
289 * It initialises itself.
290 * It sets its initialised flag and returns.
291
292 Library initialisation is a two-step process. The first step,
293 `sapphire_init', initialises the kernel and the basic memory map. The
294 second step, `sapphire_libInit', initialises the library units.
295
296 _____________________________________________________________________________
297
298 DYNAMIC LINKING
299
300
301 The original idea was that Sapphire would be really small. Well, take a
302 look at its feature set and tell me that it's big. So we paid no
303 attention to making Sapphire dynamically linkable. When it became
304 obvious that this wasn't working properly, I started investigating
305 methods for making Sapphire into a DLL.
306
307 I was saved by two features of Sapphire code:
308
309 * Sapphire programs preserve R11 everywhere.
310 * Sapphire's kernel allocates workspace for the units at run-time.
311
312 The first fact gives me re-entrancy automatically. R11 becomes the one
313 thing that the dynamically-linked Sapphire library can use to decide
314 where its workspace is.
315
316 Finding workspace is rather grubby in Sapphire, so it's hidden beneath a
317 macro. For the curious, this is what happens:
318
319 LDR R12,workoff_addr
320 LDR R14,[R11,#-magic_offset]
321 ADD R12,R14,R12
322
323 The trick works because the allocation of workspace /offsets/ depends
324 only on the order of units within the library, so they can be shared;
325 the value loaded from R11-space is worked out by the kernel and put in
326 application memory on startup.
327
328 Most of the kernel ends up in the DLL. The application is linked
329 against a really small stub, which basically contains front ends for a
330 few kernel functions to (maybe) find the Sapphire DLLs, point to a
331 collection of tables, and call the corresponding function in the core.
332
333 The exact mechanism is complicated. Interested readers are referred to
334 the source code.
335
336 The upshot of all of this is that, although SDLS was designed
337 specifically to solve the problem of APCS shared libraries, Sapphire's
338 style of shared library requires much less code to be linked into the
339 client, much thinner veneering (because the kernel handles re-entrancy),
340 and the result is both more robust and cleaner.
341
342 _____________________________________________________________________________
343
344 ERROR HANDLING
345
346
347 Sapphire programs know about two sorts of errors:
348
349 * Program errors, which should never occur.
350 * Environmental errors, which must be anticipated.
351
352 Environmental errors are reported by setting the V flag on exit from a
353 function. Sapphire routines are documented according to whether they
354 can return errors: it is safe to ignore the V flag on exit from a
355 routine whose documentation does not state that it returns errors. An
356 environmental error typically requires some sort of user intervention,
357 and suggests a friendly explanation that some sort of fault has
358 occurred.
359
360 Program errors are raised by OS_GenerateError and caught by Sapphire's
361 error handler. There's a rule of thumb which states: `Never test for an
362 error you can't handle.' Sapphire follows this advice. Most calls to
363 the operating system have the X bit clear, causing errors to be sent to
364 the error handler. Program error messages are intentionally technical.
365 The idea is to encourage naive users to write them down, so that they
366 remember them and report them accurately.
367
368 Part of the initial design was that a Sapphire application should
369 /never/ exit as a result of program fault. A user should always be able
370 to try to get to a save box and save data. (Too much experience of
371 Paint falling over with type 5s prompted this decision.) The
372 application pops up an error report giving the user a choice between
373 killing the program or trying to soldier on. Soldiering on seemed
374 remarkably successful in general.
375
376 A later addition to Sapphire was `Structured Exception Handling', which
377 is a lower-level version of C++'s try/throw/catch exception handling.
378 This builds upon the existing base, and allows programs to tidy up in
379 the event of errors before propagating them back down to the bottom
380 level `Continue/Quit' handler.
381
382
383 This concludes the tour of Sapphire's run-time support functionality.
384 The rest describes plain ol' library features.
385
386 _____________________________________________________________________________
387
388 EVENT HANDLING
389
390
391 Sapphire has a fairly simple but highly effective strategy for dealing
392 with events, inspired in part by the FilterManager module in RISC OS 3.
393
394 The `event' unit is responsible for low-level event handling.
395 Applications call `event_poll' instead of calling the SWI Wimp_PollIdle
396 directly; the calling sequences are almost the same for both routines.
397 `event_poll' returns C set if it handled the event itself, and C clear
398 if it failed to handle it. A defalt handler procedure is provided to
399 perform any required actions for a particular event. A Sapphire
400 application's poll loop typically looks like this:
401
402 loop BL event_poll
403 BLCC handle_unknowns
404 BLCC defHandler
405 B loop
406
407 and that's it.
408
409 Most of the hard work is done by filters. Sapphire knows about three
410 types of filters:
411
412 * Pre-filters are shown arguments to Wimp_Poll before it's called, and
413 can modify them in controlled ways, e.g., clearing event mask bits,
414 or changing the `time-to-return' argument. A pre-filter can
415 `claim' a call to Wimp_Poll by storing a `fake' event in the buffer
416 and returning carry set. Other pre-filters are skipped, and the
417 fake event is passed to the post-filters.
418
419 * Post-filters are shown the result from a call to Wimp_Poll. They
420 can perform any action they like, except for modifying the event. A
421 post-filter can claim an event by returning C set; other
422 post-filters are not called, and `event_poll' returns C set.
423
424 * Fake-event filters are a novel idea introduced to handle transient
425 dialogue boxes. Immediately after a return from Wimp_Poll, Sapphire
426 scans the list of fake-event handlers. Each one is entitled to
427 `steal' this event and replace it with another by returning carry
428 set. Once an event has been stolen, it is passed through the
429 post-filters. When `event_poll' is called again, Sapphire
430 restores the previous genuine event and continues calling fake-event
431 filters from the point at which it left off. Thus, Sapphire
432 provides a general mechanism for inserting events.
433
434 Everything else is based off this mechanism.
435
436 The `win' unit dispatches window-specific events to window handlers by
437 registering a post-filter. The `menu' unit also establishes a
438 post-filter, and passes events to the owner of the current menu.
439
440 The main use for fake-event handlers is to handle transient windows. A
441 separate Sapphire unit, `transWin', remembers the handle of the current
442 transient window. When an event arrives, its fake-event handler checks
443 whether the transient window is open. If it has been closed, it steals
444 the event, whatever it was, and substitutes a fake `window close' event
445 to be picked up by the owner of the transient window.
446
447 [Actually, `transWin' is careful to allow redraw events through
448 unmolested. Bad window flicker results if redraw requests are tampered
449 with.]
450
451 _____________________________________________________________________________
452
453 MENUS IN SAPPHIRE
454
455
456 Sapphire knows about two types of menus, and its interface to
457 applications reflects this. Standard RISC OS transient menus will be
458 familiar to most readers. Straylight tearoff menus will be less well
459 known -- see Glass for a real-life example of tearoff menus in action.
460
461 Sapphire's interface to handling menus is inspired by Computer Concepts'
462 Advanced Box Interface system. Menus are always constructed
463 dynamically, as needed. The procedure `menu_create' is given four
464 arguments:
465
466 * The address of a menu definition.
467 * The address of a menu event handler.
468 * R10 and R12 values to pass to the handler.
469
470 The `menu_create' procedure may be called any number of times before the
471 next Wimp_Poll: the menu definitions are concatenated. This allows
472 applications to create dynamic and context-sensitive menus easily.
473
474 The first `menu_create' call is different from the others: the menu
475 definition must contain a menu title. (It need not contain any menu
476 items, though.) Both the menu title and the items are /variable size/
477 objects -- the more odd features a menu item has, the more space its
478 definition occupies. Menu definitions are also /read-only/. Dynamic
479 features like ticking and shading must be handled separately.
480
481 Each menu item (and title) is begun with a `feature mask' -- a word
482 containing a flag bit for each feature. The feature mask is followed by
483 data appropriate to the various features selected. For example, the
484 `has a submenu' feature requires a pointer to the submenu definition and
485 a pointer to the procedure which will handle events for the submenu.
486
487 Menu features which require dynamic information (e.g., context-sensitive
488 message strings, or ticking and shading) have, as part of their feature
489 data, offsets from the menu handler's R10 containing the required
490 information. The `shade item' feature requires an offset from R10 and a
491 bit position -- it will shade the item when the appropriate bit in the
492 word at [R10, #offset] is set. Sapphire's menu system understands the
493 concept of `radio items' -- groups of items of which only one may be
494 ticked at a time. The feature data for a radio item consists of an R10
495 offset and a value to match -- the application then sets [R10, #offset]
496 to be (for example) 0 for the first item in the group, 1 for the second,
497 and so on.
498
499 It's important to get the hang of the concepts here, because lots of
500 other bits of Sapphire use this same idea.
501
502 The `menuDefs.sh' header file contains a large number of macros which
503 hide most of the mess of this.
504
505 _____________________________________________________________________________
506
507 DIALOGUE BOXES
508
509
510 Sapphire's dialogue box handling is extremely powerful. There's also a
511 lot of code in `dbox.s' which does jobs that the WindowManager ought to
512 do, or does wrong.
513
514 Dialogue boxes are `resources'. Sapphire tries to keep the
515 interface to handling resources consistent. When you want one, you
516 `create' it, and when you don't need it any more, you `destroy' it.
517 A successful `create' of a dialogue box yields a `dbox' -- a handle
518 which represents the dialogue box.
519
520 Sapphire is carefully constructed so that dialogue boxes don't need to
521 exist unless they're actually in use. Most programs create dialogue
522 boxes only when they need to appear on-screen, and destroy them again
523 once they're closed. To this end, Sapphire generates fake close events
524 for transient windows.
525
526 Dialogue boxes can be created from several different sources of
527 information, and to handle this, there are actually three different
528 `create' routines:
529
530 * `dbox_create' is the standard call. A client passes a string naming
531 the template from which to create the dialogue, and the dialogue box
532 manager returns a handle. The window definition is copied, along
533 with all the indirected data, so that you can create multiple
534 instances of a template without tripping over aliasing problems.
535
536 * `dbox_fromEmbedded' creates a dialogue from an `embedded template'
537 -- a compact and easily expanded representation of a window template
538 which is statically linked into the client program. Again, the data
539 is copied from the template, so aliasing problems don't exist.
540
541 * `dbox_fromDefn' is the most powerful call (and the others are
542 implemented in terms of it). The client passes a pointer to a
543 complete window definition, which the Sapphire uses `as is',
544 without copying it.
545
546 Embedded templates were introduced for very small programs (e.g.,
547 DLLMerge). Sapphire makes use of them because common dialogue boxes
548 have been stored in a shared `Sapphire.Resources' DLL as embedded
549 templates. This makes programs easier to upgrade for new versions of
550 Sapphire (just copy the new DLLs in and you get the new dialogues free)
551 and reduces resource requirements since there's only one copy in memory
552 at any given time.
553
554 Displaying dialogue boxes introduces more flexibility. The procedure
555 `dbox_open' is given a dbox and an `open style', and a collection of
556 arguments appropriate to the style. Styles permitted are:
557
558 * In its current position (or in the position it was in the template
559 file).
560
561 * In the middle of the screen.
562
563 * Centred over the mouse pointer.
564
565 * At a given Y coordinate (but the current X coordinate).
566
567 * At a given X and Y position.
568
569 The open style also contains a `transient or persistent' flag, which is
570 useful for dialogue boxes hanging off of menu items. If a menu is open
571 currently, Sapphire will automatically open the dbox as a submenu, in
572 the correct place, unless you explicitly instruct it not to do this
573 using another open style bit.
574
575 Sapphire doesn't pass Wimp events directly on to client event handlers.
576 Instead, it pre-processes them, putting the important information from
577 the event into registers, and sending a `dbox event' to the handler.
578 The handler can then either `claim' the event for itself (by saying that
579 it's handled it) or pass it on. Unclaimed events are acted upon by
580 Sapphire.
581
582 As an example of the sort of preprocessing Sapphire does for dialogue
583 box events, it breaks a redraw request into an individual event for each
584 rectangle. If its redraw events are unclaimed (as they ought to be), it
585 will call Sculptrix to fill in the 3D borders around the icons.
586
587 Sapphire handles radio buttons itself, if click events on them are
588 unclaimed. Using type-11 buttons and a non-zero ESG means that the user
589 can deselect all buttons in a set by clicking the selected one with
590 `adjust'. An application can deal with this by selecting the icon
591 explicitly, but (a) this causes flicker, and (b) if the application is
592 going to the trouble of having explicit code to deal with the situation,
593 it may as well do the job properly. Sapphire considers an icon with
594 button type 3 and non-zero ESG to be a `Sapphire radio button' and does
595 the right thing with it.
596
597 Shaded icons are also handled by Sapphire rather than the Wimp, because
598 the standard behaviour is highly inconsistent. For example, sometimes
599 icon backgrounds are shaded in addition to foregrounds. In text-and-
600 sprite icons, the text is /not/ shaded when it ought to be (for
601 consistency with text-only icons). It's basically a mess. Sapphire
602 uses a complicated shading algorithm which seems fairly close to the
603 behaviour of ABI.
604
605 Sapphire also handles caret blinking and cursor changing over writable
606 icons automatically.
607
608 Keypresses also receive default handling from Sapphire. Unclaimed
609 cursor keys cause the caret to move between writable icons, scrolling
610 the window where necessary (both horizontally and vertically) to ensure
611 that the new focus icon is visible.
612
613 The procedure to set a string in an indirected icon is probably
614 excessive. It ensures that the string really needs changing before
615 flickering the icon. It truncates the string rather than overflowing
616 the icon's buffer, discarding either the start or the end depending on
617 the icon's `right align' flag, optionally putting a `...' to indicate
618 that a truncation was performed.
619
620 Sapphire inherited a curious feature called `embedded titles' from
621 STEEL, which in turn was inspired by RISC OS version of the game
622 `Elite'. A client nominates an icon within a window which has no title
623 bar as being an `embedded title'. Sapphire will thereafter
624 automatically draw a Sculptrix group box around this icon, using the
625 window's real title text as the group title. This is used in `Info'
626 windows, as well as the warning, note and error windows.
627
628 Sapphire can handle `nonpolling' windows. In some cases it's desirable
629 to arrest the user's attention by requiring acknowledgement of a message
630 or the taking of a decision, and sometimes continuing to poll would be
631 dangerous or undesirable for other reasons. For example, a serious
632 error report mustn't multitask, because the error might recur. The `To
633 save...' message caused by attempting to save a file without a full
634 pathname shouldn't multitask, because the click on the OK button will
635 close the menu tree (which is extremely irritating). The only way to
636 handle a prequit message under RISC OS 2 is to ask the user whether
637 quitting is permitted in a nonpolling way, so the decision to cancel a
638 shutdown sequence can be made before an acknowledgement is returned to
639 the task manager. The handling for nonpolling windows is split into two
640 parts. The `nopoll' unit displays dialogue boxes and reports mouse
641 clicks and keypresses as fake events through Sapphire's pre-handler
642 mechanism (so nonpolling dialogues present an identical programmer
643 interface to normal ones). The `buttons' unit displays a given
644 dialogue, permitting a collection of action buttons to be given
645 appropriate pieces of text.
646
647 The real richness of Sapphire's dialogue box handling comes from custom
648 controls. A whole subsystem, `dbx' is provided for dealing with custom
649 controls.
650
651 The client registers a table defining the required controls for a
652 particular dialogue box. The table contains variable size entries, each
653 one beginning with a standard header:
654
655 * Number of the icon which hosts the control.
656 * Pointer to the control definition.
657 * Various flags (control specific).
658 * The offset of the control's writable data within the dialogue
659 handler's R10-space.
660
661 This is followed by whatever constant data the control handler requires.
662 Sapphire dbx controls are always hosted by icons. This makes them easy
663 to lay out in template editors.
664
665 Custom controls generate their own events which are sent to the client's
666 handler procedure. They are also given dialogue box events (after the
667 client has had a chance to claim them for itself) as `dbx events'. For
668 example, Sapphire will only ask a control to draw itself if (a) the
669 control requests redraw events and (b) the current rectangle intersects
670 the control's host icon.
671
672 Sapphire takes redrawing of controls very seriously. It ensures that
673 the graphics clipping area is set to the intersection of the control's
674 host icon and the redraw rectangle, to prevent any `accidents' from
675 messing up the rest of the display. It passes this amended rectangle to
676 the control to allow it to perform source-level clipping. The custom
677 RGB-coloursquare and HSV-colourcircle controls in the (unfinished)
678 colour selector use the provided rectangle to plot the new graphics by
679 direct screen access.
680
681 Predefined custom controls are:
682
683 * A slider. This is written very carefully to avoid any flicker while
684 the bar is being dragged or updated.
685
686 * A `bump' arrow button, which presses its icon in while it's being
687 held down, and autorepeats. The arrow control keeps a count of how
688 many `ticks' have been missed, and sends them to the dialogue box as
689 large packets, rather than individually. This makes programs which
690 spend a long time processing bump requests feel responsive even when
691 they're really being slow.
692
693 * A file icon, which represents a filetype in a window. File icons
694 can optionally be draggable (for save operations). When solid drags
695 are requested by the user (and the DragASprite module is available),
696 the control will make the icon disappear while the icon is being
697 dragged, to produce the illusion of the icon being `lifted off' the
698 dialogue box.
699
700 * A `string set' control, which drops down a menu of strings and
701 allows the user to choose one, which it displays.
702
703 * A simple `colour pot' which allows a user to choose one of the
704 sixteen standard Wimp colours. This control pops open a small
705 dialogue box of its own when requsted.
706
707 Custom controls provide a great deal of functionality at very little
708 programmer effort. Why no other library for RISC OS handles them
709 properly is a mystery to me. The Toolbox has a go, but fails miserably;
710 compared to the power of Sapphire's `dbx' subsystem, it might as well
711 not bother.
712
713 (Sapphire was mature when the Toolbox betas were available. The idea
714 for proper custom controls came from OS/2, but the implementation is
715 very much in Sapphire's own style.)
716
717 _____________________________________________________________________________
718
719 DATA TRANSFER
720
721
722 It used to be the case that data transfer was one of the really hard
723 bits of an application. I remember trying to put it off as long as
724 possible whenever I wrote a STEEL application. Sapphire tries very hard
725 to make data transfer as painless as possible.
726
727 Both loading and saving have two layers to them. Underneath, there's a
728 low-level interface which exposes the nature of the transfer (whether
729 it's to a file, or in-memory). Above that, there's an abstract
730 interface which presents both transfer types as a (non-seekable) stream
731 of data, providing buffering and a simple programming interface. This
732 high-level interface works perfectly well on files even without a data
733 transfer context, and occasionally gets (ab)used as a general stream
734 interface to file data, for example, when saving preferences files.
735
736 The lower-level code was designed to be useful both in conjunction with
737 the high-level interfaces and on its own. Often, the low-level
738 interface is the better choice.
739
740 Oddly, the low-level interface is higher-level than RISC_OSLib's.
741
742 A low-level data save operation is initiated by the `save' call. It
743 requires a large number of arguments, detailing the recipient's window
744 handle, the filename, filetype, and a block of handling routines. The
745 handling routines are:
746
747 * Save: write your data to a file, given its name. Return an error if
748 saving failed.
749
750 * Send: return (giving a base pointer and size) a block to send to the
751 recipient. Return CS if this is the last block, or CC if there's
752 more to come. The first time this handler is called, it's given the
753 value 0 in R2; after that, the return value of R2 is passed back in
754 the next call, to provide some sort of context. It's called over
755 and over until either the recipient complains or the handler returns
756 end-of-data.
757
758 * Success: the data was transferred successfully (and may or may not
759 be permanently safe).
760
761 * Failed: the data transfer failed, either because of a local error,
762 or because the receiver failed.
763
764 Note that the `send' handler can return a block of any size. It's
765 Sapphire's responsibility to handle the recipient's buffer size. In the
766 case where all the data is in one big flex block, the send routine
767 becomes trivial. Sapphire makes RAM transfer /easier/ than writing to a
768 file!
769
770 Loading data in similar in spirit. There's a single routine, `load',
771 which is given a block of handlers:
772
773 * InitBuf: create a buffer in which to receive data, and perform any
774 other setting up necessary to start a RAM transfer. The handler is
775 given an estimated file size, and the proposed leafname.
776
777 * KillBuf: RAM transfer has failed, so destroy the buffer (if it was
778 only temporary). This is called when RAM transfer was unsuccessful
779 for some reason.
780
781 * Extend: the current buffer was filled: I need a new one. (It's
782 called `extend' because the normal reaction is to extend a flex
783 block and return the new area.)
784
785 * DoneBuf: RAM transfer has succeeded, so do whatever's necessary.
786 (Typically, an application might free some `slop space' at the end
787 of the buffer.)
788
789 * File: load the data from a given file.
790
791 * Done: data transfer succeeded.
792
793 * Failed: data transfer failed, either because of something we did
794 wrong or because the sender had problems.
795
796 Sapphire provides routines to do most of the work of the InitBuf,
797 KillBuf, Extend, DoneBuf and File handlers in the case where we're just
798 loading directly into a flex block, and in many cases, the
799 supplied KillBuf, Extend and DoneBuf routines can be used directly.
800
801 The higher-level interfaces `xsave' and `xload' are designed to link
802 onto the low-level interfaces, and provide handlers which can (in many
803 cases) be given directly to the low-level calls. They take as an
804 argument a tranfer routine, which is called to do the actual save or
805 load operation. It is called just like a normal subroutine. When it
806 returns, the transfer is ended. It calls `xsave' or `xload' routines
807 to read and write bytes, words, strings or whole blocks of memory;
808 Sapphire buffers data where this would be useful. Sapphire provides a
809 separate stack for the transfer routine, so that the read or write
810 routines can pause for a Wimp_Poll to send or receive more data by RAM
811 transfer, although this is invisible to the transfer routine, which just
812 sees a simple interface to a stream of data. Errors from the transfer
813 are carefully propagated back to the transfer routine, which sees error
814 returns from read or write operations.
815
816 Sapphire includes a standard `saveAs' box, whose interface is slightly
817 simpler than the `save' routine.
818
819 _____________________________________________________________________________
820
821 THE SAPPHIRE CHUNK FORMAT, AND PREFERENCES HANDLING
822
823
824 A Sapphire `chunk' file is very simply a file representation of a list
825 of named chunks of data. Sapphire provides functions to read chunk
826 files, claim chunks, and write chunk files back again.
827
828 When a chunk file is loaded, each chunk is placed in a Flex block. A
829 client can `claim' a chunk. Once this is done, the client can either
830 modify the data in-place, or free the block and provide a pointer to a
831 procedure to write equivalent data to the chunk file when it's saved.
832
833 The original idea was to support preferences files in a modular way --
834 each component could find its own preferences chunk without needing to
835 know about anything other than the chunk system. We noticed that this
836 could easily be extended to handle binary data, and we could base a
837 general file format on the idea. A typical Straylight document would
838 look something like this:
839
840 ;
841 ; Generated by Straylight Frobnitz
842 ;
843
844 [Header]
845 Format = Frobnitz data
846 FormatVersion = 3.15
847 Author = J. R. Hacker
848
849 [FrobData]
850 Bin[00]....[binary rubbish]
851
852 [FrobOptions]
853 AutoDelay = 300
854 AutoMods = 50
855 ...
856
857 You get the idea. The text chunks can be modified by anyone with a text
858 editor -- the binary areas are self-contained and don't care where they
859 are in a file.
860
861 Some user preferences are best stored as raw chunks. For example, the
862 `FontDir' program keeps a chunk containing the list of font directories
863 known to it. Most are better expressed through the sort of key/value
864 syntax shown in the above example. Sapphire provides a separate
865 mechanism for handling these which interwork with the chunk file system.
866
867 The `options' unit will, when requested, claim a chunk from a chunk
868 file, and translate its textual contents into an easily digestable
869 binary format. When the chunk file is saved, Sapphire will
870 automatically read the binary data, and translate whatever's there back
871 into text. Using text as an external representation insulates the
872 client program from version changes -- default values can be provided
873 for keys which aren't defined, for example.
874
875 Each key/value pair in the chunk has the general format:
876
877 <key> [=] <string> [;<comment>]
878
879 A <string> is either a sequence of unquoted characters, or quoted by one
880 of the pairs `', '', or "". A quote character can be duplicated to
881 insert a literal quote in the string. Any of the characters `#', `|' or
882 `;' begins a comment (although `;' is preferred because we're assembler
883 programmers).
884
885 The set of permissable options is provided to Sapphire as a table, each
886 entry of which states the name of the key, the offset within the options
887 block to store the (binary) value, the type of the value, and any data
888 the type handler needs for its own purposes. It's another
889 variable-size-entry table, like a menu definition or a dbx control list.
890 More types can easily be written by client programs. Standard handlers
891 are provided:
892
893 * The string type reads quoted or unquoted strings. It always writes
894 quoted strings.
895
896 * The integer type reads signed or unsigned integers in any base (up
897 to 36). It usually writes decimal numbers although it can be
898 persuaded to use a different base.
899
900 * The literal type never reads anything. It writes out a string
901 (maybe some kind of comment) when requested.
902
903 * The enum type reads a string, looks it up in a table, and stores the
904 index. The match is case-insensitive. Abbreviations are
905 accepted; ambiguous abbreviations match the first string found in
906 the table. It writes out the full correct-case string.
907
908 * The bool type is a special case of the enum type -- it reads either
909 one of `true', `on' or 'yes' for a true value, or `false', `off' or
910 `no' for false, and sets a single bit for its answer (for space
911 efficiency). On output, it writes one of `true' or `false', unless
912 requested to use `on' or `off', in which case it also suppresses the
913 optional `=' sign on output.
914
915 * The version type reads version numbers of the form `xxx.yz' and
916 stores them as integers, multiplied by 100. It writes them out
917 in the same form, with trailing zeros if necessary.
918
919 The preferences system DoggySoft added into WimpExtension is the nearest
920 thing which comes close, but Sapphire does the job in a much more
921 powerful way. This is actually quite surprising, since the unit isn't
922 particularly large. (This is partly due to an interesting coding trick
923 I came up with to reduce typing, which I've entirely failed to make use
924 of in any high-level language. See `choices/options.s' for details.)
925
926 _____________________________________________________________________________
927
928 HIGH LEVEL LANGUAGE INTERFACE
929
930
931 This is stil very sketchy, but a discussion may interest some readers.
932 The C interface has to deal with several problems:
933
934 * Sapphire's procedure calls aren't even a little bit compatible with
935 APCS. Sapphire gains a lot of its power and elegance from its lax
936 calling conventions.
937
938 * C programmers tend to use different names for commonly used
939 functions.
940
941 * Sapphire's variable-size-entry tables aren't easy to represent in C.
942
943 The last problem hasn't been addressed. The trivial `Hello, world'
944 example program contains a horrible block of hex where the menu
945 definition is meant to be. To be fair, we envisaged the user-interface
946 parts of Sapphire applications to be in assembler, to keep the
947 user-facing parts of a program light and responsive, while the back-end
948 bits were in C.
949
950 The second problem just involves writing a lot of veneers and playing
951 with macros. I didn't bother trying to make the functions ANSI
952 compliant. Only the bare bones of a standard library is provided.
953
954 The first is the only technically interesting problem. Making C call
955 Sapphire is a fairly simple task of inserting a veneer in the right
956 place. I wrote a `_call' function which works rather like `_swi', to
957 allow C to call any Sapphire function. There's a small problem to do
958 with R11 which needs dealing with, though. My solution was simple --
959 since Sapphire doesn't have any stack checking anyway, compile code with
960 stack limit checking turned off and move R11 into R10 (APCS's stack
961 limit pointer) and back to R11 on inter-language borders.
962
963 The really interesting problem, then, is making Sapphire understand how
964 to call C functions. Again, some hacking is performed. Any C function
965 which is intended to be called by Sapphire is declared as follows:
966
967 __sapph(funcname)(regset *r [, type *r10 [, type *r12 [, char *scratch]]])
968
969 (APCS's way of passing arguments in registers allows the flexibility of
970 arguments -- if a function doesn't need R10 or R12 it doesn't need to
971 declare them. More arguments can be read by putting them after
972 `scratch' -- they just get pulled off the stack.)
973
974 The macro constructs a function with the given name, just before the
975 actual compiled function, whose name is changed to `__sapph__funcname'.
976
977 The actual code generated by the compiler for this atrocity is:
978
979 funcname STMFD R13!,{R14}
980 MOV R14,PC
981 B __sapph_veneer
982
983 __sapph__funcname
984 <APCS entry sequence>
985 ...
986
987 The `__sapph_veneer' routine knows that the function it's meant to call
988 is pointed to by R14. It stores all the registers on the stack, points
989 R0 at them (to make the regset variable), sets up the other arguments,
990 puts R11 into R10, and calls the C routine. On exit, it pulls the
991 registers off the stack, puts R11 back in its proper place, sets
992 flags appropriately, and returns.
993
994 The C function can set return values by modifying the `regset' argument
995 it's passed. It can also modify the flags by returning an appropriate
996 value as its result. For example
997
998 return (C_set | V_clear);
999
1000 returns with C set, V clear and the other flags unaltered.
1001
1002 This is a very hamfisted way of dealing with the problem. But Sapphire
1003 was never designed to work with C. In fact, it's almost true to say
1004 that Sapphire was designed never to work with C -- the fact that it's
1005 possible after all is more a credit to humen ingenuity than Sapphire's
1006 design.
1007
1008 _____________________________________________________________________________
1009
1010 CONCLUSION
1011
1012
1013 Your tour through the interesting bits of Sapphire is complete. There
1014 are plenty of other `neat toys' in there, waiting for you to find them.
1015 Most source files contain some sort of interesting twist on an old idea,
1016 or a neat programming trick.
1017
1018 The sources are commented to excess. Almost every line contains a
1019 comment. Obscure tricks and other bits of non-obvious code are usually
1020 tagged `Horrid hack' or `Neat trick', at least the first time we used
1021 that particular construction. Searching for `hack' should produce lots
1022 of interesting code to read.
1023
1024 Anything else you want to know: just read the header file documentation
1025 and/or the source code. Or you can ask me. Just bear in mind that it's
1026 been a long time since I wrote anything which used Sapphire and I'll
1027 probably have to read the headers and/or the source code anyway.
1028
1029 Sapphire, for Tim and myself, has been a labour of love. I'm extremely
1030 proud of it: of its small size, its speed, its elegant design, its power
1031 and versatility. Please, treat it with respect.
1032
1033 I'm almost in tears as I type this, so I'd better stop. Thanks for
1034 reading,
1035
1036
1037
1038 Mark Wooding, mdw@excessus.demon.co.uk
1039 17 December 1997