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