Wednesday 24 July 2024
Why does Sin(a)≈a for small a in radians?
Saturday 6 April 2024
Space Shuttle CPU MMU, It's Not Rocket Science
The NASA Space Shuttle used a cut-down IBM System/360 influenced CPU called the AP-101S aimed mostly at military aeronautical applications. It's fairly weird, but then again, many architectures from the 1960s and even 1970s are weird.
I've known for a long time that the Space Shuttle initially had an addressing range of about 176K and because one of the weird things is that it's 16-bit word addressed (what they call half-words), this means 352kB. Later this was expanded to 1024kB (i.e. 512k half-words). How did they do this?
You might imagine that, being jolly clever people at NASA, they'd come up with a super-impressive extended memory technique, but in fact it's a simple bank-switched Harvard architecture where the address range for both code and data is split in two and 10 x 4-bit bank registers are used to map the upper half to 64kB (32k half-word) banks.
So, the scheme is simple and can be summarised as:
Documentation
- Budget cuts and pressure in the 1970s and 1980s led to poor documentation. This can be checked by reading the quality of the documentation prior to this mid-80s document and/or later if standards improved.
- Justification: all NASA hardware, including the AP-101S was expensive, so convoluted documentation helps convey the idea that the public and NASA were getting their money's worth: if you can't comprehend it, you won't grasp how simple and standard it was.
- Small developer-base: documentation tends to suffer when not many people are developing for a given product. That's partly because there's a lack of resources dedicated to documentation, but it's also because documentation is frequently passed on verbally (what I sarcastically call Cognitive documentation, i.e. no documentation 😉 ). I don't know the size of the Shuttle software developer team, but I guess it was in the low hundreds at any one time, because although the memory space was only a few hundred kB; I believe they loaded in various programs to handle different mission stages (e.g. ascent, docking, orbital corrections, satellite deployment, landing) and that means if there's a few MB of code, that's about 300,000 lines and given the safety requirements, perhaps only a few thousand lines per developer.
Conclusion
Thursday 28 March 2024
Mouse Detechification! A PS/2 To Archimedes Quadrature Mouse conversion!
After an embarrassing amount of work I managed to convert a PS/2 mouse into a quadrature encoding mouse for my Acorn Archimedes A3020.
A Mini-Mouse History
I know I could have bought a USB to quadrature encoded mouse adapter (from here on, QE mouse, because I don't like the term Bus-mouse), but that seemed like a cop-out, so instead I decided to make things as painful as possible. The first stage was to cut out the blobtronics that made it a PS/2 mouse.
Then I added some pull-down / level-shifting resistors to see if the A3020 would recognise it as valid values but it didn't. Then I went through a long process of getting an Arduino to recognise the analog voltages from the phototransistor (yep, I also know I could use a Schmitt-trigger buffer chip) and write a small program to actually process them into quadrature encoded X and Y grey code (Ref: Dir are 00, 01, 11, 10, 00... for forward/up and 00, 10, 11, 01, 00.. for backward/down). It turned out it wasn't very digital!
I figured I could solve the problem in software using a small MCU that would fit in the mouse, so I chose an ATTINY24, a 14-pin AVR MCU with 8 ADC 10-bit channels and 4 other digital IO pins. I used a simple hysteresis algorithm: it first figures out the range of values it can get from the phototransistors; then allows a bit more time to see if the range is bigger; then once it's interpreted any given state, the ADC values have to change by 2/3 of the range to flip into the next state.
I went through quite a bit of a debugging process, because obviously when you go from a fairly lush environment like an Arduino (plenty of libraries, plenty of IO pins, lots of code space and relatively abundant RAM (2kB ;) )) to an ATTINY24 (2kB flash, 128 bytes of RAM, just 12 IO pins) and then stick it in a mouse, the fewer opportunities you have for debugging - and you can't even use an LED inside a trad, ball mouse, because ta-dah, you won't see it! So it's best to debug as much as possible in simulation, before you take the final step. In fact the program only ended up being about 906 bytes, because 'C' compiles pretty well on an 8-bit AVR.
I made a lot of mistakes at pretty much every stage - but amazingly getting the mouse directions inverted wasn't one of them :) . I started with 10K resistors for the phototransistors, but then ended up deducing 15K was best (22K || 47K) so that was 8 resistors! When soldering the analogue connections to the AVR I was out by one pin all the way along, because from the underside, I mistook the decoupling cap pins for the Pin1 and Pin 14 of the AVR. I had to desolder the phototransistor connections to the main cable - which I'd been using when analysing on the Arduino; then solder up the photo transistors to the analog inputs and then the digital outputs to the original wires. I tried to keep the wires no longer than they needed to be because it was cramped in there, but I ended up making the Y axis analog wires just 1mm too short (because, d'uh they get a bit shorter when you strip them and feed them through a PCB) so they had to be redone. Because there were a lot of wires I needed to glue them down as otherwise the casing wouldn't close, and I was particularly concerned about the phototransistor wires getting caught in the Y axis wheel, but then I glued them down directly underneath that wheel so it couldn't clip into place! I also glued the grey and green wires running up the right so that they got in the way of the case closing - so all of these had to be very carefully cut out and moved. Near the end I remembered I had to add a 10K resistor between reset and power so that the MCU would actually come out of reset and execute code! I also had to add an input signal, to software switch the Y axis inputs to the X axis grey code, because the only header I could find to fit the mouse cable plug for testing didn't leave room to test both X and Y grey codes! Hence what looks like an extra button!
Finally I connected it all up, glued the board and wires down (after correcting the locations) and got: A BIG FAT NOTHING! I thought maybe I'd messed up the analogue ranges and retried it with a different range and that didn't work. Then I realised I could output debug serial out of one of the grey code outputs to the Arduino and see what the ATTINY was reading! Bit-banged serial code can be very compact!
void PutCh(uint8_t ch)
{ // (ch<<1) adds a start bit in bit 0 and |0x200 adds a
uint16_t frame=((uint16_t)ch<<1)|0x200; // stop bit.
do {
if(frame&1) {
PORTB|=kSerTxPin;
}
else {
PORTB&=~kSerTxPin;
}
frame>>=1;
_delay_us(52.083333-11/8); // 19200 baud.
}while(frame); // & after the stop's been shifted out,
// the frame is 0 and we're done.
}
Before I tried it, I did a sanity check for power and ground only to find I hadn't actually connected up VCC properly!!!! I'd forgotten the final stage of solder-bridging the decoupling cap's +ve lead to Pin1 of the AVR and I ended up ungluing everything so I could see underneath.
But when I fixed this and packed it all back in the case: It Worked! I tried reading the digital outputs at the end of the cable on the Arduino and when I was satisfied (which only took a few minutes of testing) I decided to hook it up to my A3020 and hey-presto! I have a working QE Mouse!
I had been a bit concerned that doing analog sampling wouldn't be fast enough and so my algorithm has a heuristic whereby if it sees a jump of 2 transitions (00 <=> 11 or 01 <=> 10) it assumes it's a single step in the same direction as before. I could manage about 20K x 4 samples per second, but a little maths shows this will be fine, because a full 640 pixels on the Arc's screen can be sampled OK if you cover it in about 3.2ms and clearly we don't do that.
!Paint, which is like, 96kB I think! It's terribly unintuitive though! Note on the video that a 12MHz ARM250 can drag whole, 4-bpp x 640x480 windows around rather than just outlines! Note also the Taskbar, which was standard on the Archimedes before Windows 95, or Mac OS X (NeXT step had a floating Dock).
Here, again is the link to the project and source code.
Sunday 24 March 2024
Colour Me Stupid - An Early Archimedes 8-bit Colour Quest!
I'm assuming, naïvely again, that I can write a short blog post, but on past performance this isn't likely. I recently managed to get my Acorn Archimedes A3020 working again by converting a PS/2 mouse to a Quadrature mouse as early Archimedes' expect (see this blog post) and this has given me more of an interest in the system, particularly from a hardware viewpoint.
Wonderfully, I'm currently writing this on my MacBook Air M2, a 32-year later descendent of the ARM250 that powers that original A3020, so things have come full circle in a sense.
These early Arcs had a video chip called VIDC which supports 1, 2, 4 and 8-bit colour video modes at different resolutions, but for decades (probably even since the first review I saw in Personal Computer World August 1987), I was confused as to why the 8-bit colour mode was described as merely having a 64 colour palette with 4 tints instead of proper 8-bit RGB colours.
Why create something complex, which doesn't even do the job? What kind of terminology is 'tints'? How do you really use them?
The confusion deepened when I started to look into the VIDC hardware, because it never supported a 64-colour palette and some tints, instead it always supported a 16 x 12-bit colour palette for all modes, including the 8-bit colour mode. So, how did that work?
Standard VIDC Palette Entry
Sup | Blue | Green | Red |
---|---|---|---|
S | B3 B2 B1 B0 | G3 G2 G1 G0 | R3 R2 R1 R0 |
Direct | Palette |
---|---|
B3 G3 G2 R3 | Palette 0..15 |
Direct | Palette |
---|---|
B3 G3 G2 R3 | B2 G1 R2 R1 |
How To Mess Up Palette Settings
B2G1\R2R1 | R2R1=0 | R2R1=2 | R2R1=4 | R2R1=6 |
---|---|---|---|---|
B2G1=00 | 0x000 | 0x002 | 0x004 | 0x006 |
B2G1=01 | 0x020 | 0x022 | 0x024 | 0x026 |
B2G1=10 | 0x400 | 0x402 | 0x404 | 0x406 |
B2G1=11 | 0x420 | 0x422 | 0x424 | 0x426 |
A Hint On Tints: Exact 8-bit RGB Is Impossible
The Alternative Palette Approach
Direct | Palette |
---|---|
B3 G3 G2 R3 | B2 R2 T1 T0 |
B2R2\Tint | 0 | 1 | 2 | 3 |
---|---|---|---|---|
B2R2=00 | 0x000 | 0x111 | 0x222 | 0x333 |
B2R2=01 | 0x004 | 0x115 | 0x226 | 0x337 |
B2R2=10 | 0x400 | 0x511 | 0x622 | 0x733 |
B2R2=11 | 0x404 | 0x515 | 0x626 | 0x737 |
- The RGB primaries are all evenly distributed, they get 2-bits each.
- There are 16 levels of grey, which means that anti-aliased black text on a white background (or its inverse) can be done pretty well.
- There's a brighter range because it goes up to 0xfff.
- Human vision is more attuned to brightness than colour, which is why the HSV colour model is effective, so it's possible that this convention can represent images that perceive better to us.
Dithering
* | 7/16 | |
3/16 | 5/16 | 1/16 |
3D Projection
Conclusion
Monday 22 January 2024
Starring The Computer: Holly's Psion MC400 In Die Hard 2
Well Die Hard 2 fans! This is the moment you’ve been waiting for… What laptop was Holly Gennaro-McClaine using on the plane???
It turns out the answer is A Psion MC400: Ie an early 1990s notebook computer! There aren't many shots of the computer in the movie and the only purpose they seem to serve is to contrast Holly's pro-technology attitude with John's anti-technology attitude. We'll start with the last shot where she packs it away, at about 1:16h into the film:
Friday 29 December 2023
The Humble Microfloppy Disk: A Vehicle of Insidious Cultural Imperialism
I think this is the longest title I've had for a blog post!
And yet the post should be relatively short.
I came across this video about the history of the microfloppy disk, the 720kB / 800kB, 1.4MB removable disk format that lives on in the shape of the Save Icon and the classic (but only marginally funny) joke about a kid thinking that one is a 3-D print of the Save Icon.
[https://youtu.be/djsyVgTGaRk?si=Kd0Z1nrqXfmUG15c]
It's an intriguing history, mostly because there was a fairly rapid transition from 8" floppy disks to 5.25" floppy disks in the 1970s, but then, despite Sony's microfloppy arriving in at the very beginning of the 1980s, and being so superior, it took about 5 to 7 years before it started to dominate (hint: the IBM PC standard held it back).
But one fact really blew my mind: it turns out the 3.5" microfloppy doesn't exist. Let's say that again - the 3.5" microfloppy doesn't exist.
In reality it's 9cm, not 3.5". I've used them since the mid 1980s and in all those 40 years, I've never known this - I was duped by some Cultural Imperialism!
In retrospect, it should be pretty obvious that the 3.5" microfloppy is unlikely to have a specification in inches, simply because it was made by Sony, a Japanese company. Japan uses metric. CDs, for example are 12cm - they were designed in Europe and Japan. 3.5 inches is 8.89cm, making it just over 1mm less than the correct size for a microfloppy disk, but that 1mm matters.
We can prove this to ourselves by measuring it (which I did) and then taking a photo. The trick though is to compensate for the parallax, since if you're looking at the disk from the centre, then the width could indeed look about 1mm shorter depending on the thickness of the ruler you use. In this photo, I did it by using a panoramic shot. That way I can measure 0cm (actually 20cm) directly above the left-hand side of the disk and 9cm (actually 29cm) directly above the right-hand side of the disk and you can see that I didn't move the ruler, or cheat by some other mechanism (though vertically, you can see it isn't straight).
Why is cultural imperialism important? The answer is that metric versus imperial measurements is a practical issue, blocked by political games. Namely, metric measurements are objectively better, but many people in power have an agenda to maintain historical measurement systems.
Why would they do that? The reason is because Imperial measurements are more complex and that makes it easier to manipulate people, to pull the wool over their eyes. And this happens because different types of units aren't easily comparable (e.g. weight, mass, volume, lengths and time) and different scales for the same kind of unit use different bases (e.g. 12 inches per foot, 3 feet per yard, and almost no-one knows how many yards there are in a mile).
This presents a barrier of understanding which reduces people's ability to process units down to just comparing values from the same kind of unit. It has an actual impact on maths attainment in the UK [@todo MetricViewsLink].
For example, someone sells 7oz of cherries for 2 crowns and 1lb of cherries for £1.5s.6d. Which is better value? To know that you need to know there are 16 ounces in a pound; 5 shillings in a crown; 20 shillings in a pound (money) and 12 pennies in a shilling. Then you convert everything into ounces and shillings (or maybe pennies) leading to 7oz for 10 shillings and 16oz for 25.5 shillings. Now you know that the 7oz price is cheaper (just).
That's how it was in the UK before February 1971 when we switched from £sd on decimal day. It took well over a century, from the mid-1800s to the mid-1960s before the UK finally managed to agree. At the time, people were worried that decimalisation would cause traders to con customers, yet they never considered that it was much easier to con people using £sd money.
Nobody alive in the UK would consider shifting back to that awful system, yet we, who are generally in favour of metric measurements are quite happy to let Imperialists force us to use non-metric units. And, that's because there is effectively a deliberate attempt by them to switch everyone back: they convert metric to imperial units and then delete references to the metric units, and when questioned they appeal to ‘patriotism’ or your compassion for their stubbornness.
A case in point is the 2022 UK government consultation on Imperial measurements, billed as allowing us to use imperial measurements. But it was a lie, since we can already use imperial measurements in the UK, we just have to include metric measurements and make them at least as prominent. What the government wanted to do instead was to be able to omit metric measurements; and to further that aim, they rigged the consultation so that it wasn't possible to let the government know you preferred metric. All the questions were along the lines of “Do you want things to remain as they are, or allow metric to be omitted?” Therefore the balance of responses had to tilt in favour of eliminating metric.
In the end, over 100,000 responses were submitted and respondents, including myself found ways of being able to assert their preference for metric (via the occasional "other comments" boxes). Because the consultation didn't go the way the government wanted, they didn't publish the findings within the 12 week period they promised, but waited a year.
We found out the results on December 27th. Over 98.7% as clearly as possible said they preferred the current rules or only metric, so the government... introduced imperial measurements to bottles of wine “as a first step” towards more Imperialism, which no-one wanted, supermarkets already are saying they won't sell and are impossible to sell on a global market either.
It's all covered in the pro-metric UK society, metric views.uk. How to respond to the survey; mistakes & bias in the consultation; how the survey could have been fixed; government ignores complaints about the survey; why no response after a year; and finally government confirms 99% don't want more Imperialism.
In conclusion, imperial measurements are embarrassing in the 21st century, but coercion is being used to perpetuate them. What we need is #MoreMetric.
Thursday 28 December 2023
Dialog Disillusion - The Mac Programming Primer Let Me Down
Introduction
The Problem
- Edit the parameters
- Start/Restart the simulation
- Maybe stop the simulation before it ends
- Go back to steps 1 or 2 or Quit.
Analysis
The New Dialog Box Demo
Re-source¹ | Fields.. [Close Window] | Info².. [Close, Close] |
---|---|---|
DITL [OK] | (see image below. Start with the Save, then Cancel buttons, then the other fields) |
ID=400, Name="Alarm", Purgeable (only) |
DITL [OK] | (see image below. Start with the OK button, then the text field) |
ID=401, Name="About", Purgeable (only) |
ALRT [OK] | TOP=40, Bottom=142, Left=40, Right=332, DITL=401, Default Color | ID=401, Name="About", Purgeable (only) |
DLOG [OK] | TOP=40, Bottom=200, Left=60, Right=320, DITL=400, Default Color, Standard double-border Dialog style, Not Initially visible, No close box | ID=400, Name="Alarm", Purgeable (only) |
MENU [OK] | [X] Enabled, Title=• Apple Menu[ENTER], [X] Enabled, Title="About..."[ENTER] [ ]Enabled, • Separator line | ID=400, No attributes. |
MENU [OK] | [X] Enabled, Title="File"[ENTER], [X] Enabled, Title="Settings..", Cmd-Key:S[ENTER], Title="Run", Cmd-Key:R[ENTER], Title="Quit", Cmd-Key:Q[ENTER] | ID=401, No attributes. |
MENU [OK] | [X] Enabled, Title="Edit"[ENTER], no options [ ] Enabled: Title="Undo", Cmd-Key:Z[ENTER], Separator Line[Enter], Title="Cut", Cmd-Key:X[ENTER], Title="Copy", Cmd-Key:C[ENTER], Title="Paste", Cmd-Key:V[ENTER], Title="Clear", Cmd-Key:none[ENTER] | ID=402, No attributes. |
MBAR [OK] | Each time, click in '****', choose Resource:Insert New Field(s) for Menu Res IDs 400, 401, 402. Top should say "# of menus 3 at the end." | ID=400, No attributes. |
WIND [OK] | Close, then choose Resource:Open Using Template [WIND] [OK]. Bounds Rect= 70, 36, 106, 156 [Set], Proc ID=0, Visible=false, GoAway=false, RefCon=0, Title="Countdown", Auto Position=$0000. | ID=400, Name="Countdown", Purgeable (only) |
Parameters Ditl
About Ditl
When you've finished, close the .rsrc file. ResEdit will ask you to save it - save it. Then open up the Dlog.π project. Choose File:New and create a stub of a C program:
int main(void)
{
return 0;
}
Choose File:Save to save it as Dlog.c. Choose Project:Add "Dlog.c" to add the file to the project. You don't need to do anything clever to add the rsrc file to the project, THINK C will automatically associate the .rsrc with the same prefix as your application.
Now you want to replace the dummy program with the rest of file. When you've finished...
Dlog.h
* @file: Reminder.h
*/
#ifndef Reminder_h
#define Reminder_h
#define kBaseResId 400
#define kAboutAlert 401
#define kBadSysAlert 402
#define kSleep 60
#define kSaveButton 1
#define kCancelButton 2
#define kTimeField 4
#define kSOrMField 5
#define kSoundOnBox 6
#define kIconOnBox 7
#define kAlertOnBox 8
#define kSecsRadio 10
#define kMinsRadio 11
#define kDefaultSecsId 401
#define kDefaultMinsId 402
#define kOff 0
#define kOn 1
#define kSecondsPerMinute 60
#define kTop 25
#define kLeft 12
#define kMarkApplication 1
#define kAppleMenuId (kBaseResId)
#define kFileMenuId (kBaseResId+1)
#define kAboutItem 1
#define kChangeItem 1
#define kStartStopItem 2
#define kQuitItem 3
#define kSysVersion 2
typedef enum{
kBoolFalse=0,
kBoolTrue=1
}tBool;
typedef enum {
kTimeUnitSeconds=0,
kTimeUnitMinutes=1
}tTimeUnit;
typedef struct {
long iTime;
int iSound, iIcon, iAlert;
tTimeUnit iUnit;
}tSettings;
extern Handle DlogItemGet(DialogPtr aDialog, int aItem);
extern void CtlSet(DialogPtr aDialog, int aItem, int aValue);
extern int CtlGet(DialogPtr aDialog, int aItem);
extern void CtlFlip(DialogPtr aDialog, int aItem);
extern void ITextSet(DialogPtr aDialog, int aItem, Str255 *aStr);
extern void StartCountDown(long aNumSecs);
extern void HandleCountDown(void);
extern void UpdateCountDown(void);
extern void RestoreSettings(DialogPtr aSettingsDialog);
extern void SaveSettings(DialogPtr aSettingsDialog);
extern void HandleDialog(void);
extern void HandleFileChoice(int aTheItem);
extern void HandleAppleChoice(int aTheItem);
extern void HandleMenuChoice(long aMenuChoice);
extern void HandleMouseDown(void);
extern void HandleEvent(void);
extern void MainLoop(void);
extern void MenuBarInit(void);
extern void DialogInit(void);
extern void WinInit(void);
extern tBool Sys6OrLater(void);
extern void ToolboxInit(void);
extern int main(void);
#endif // Reminder_h
Dlog.c
* Dlog.c
*/
#include "Dlog.h"
tBool gDone;
EventRecord gTheEvent;
tSettings gSavedSettings;
WindowPtr gCountDownWindow;
long gTimeout, gOldTime;
tBool gIsCounting;
Handle DlogItemGet(DialogPtr aDialog, int aItem)
{
int itemType;
Rect itemRect;
Handle itemHandle;
GetDItem(aDialog, aItem, &itemType, &itemHandle, &itemRect);
return itemHandle;
}
void CtlSet(DialogPtr aDialog, int aItem, int aValue)
{
Handle itemHandle=DlogItemGet(aDialog, aItem);
SetCtlValue((ControlHandle)itemHandle, aValue);
}
int CtlGet(DialogPtr aDialog, int aItem)
{
Handle itemHandle=DlogItemGet(aDialog, aItem);
return GetCtlValue((ControlHandle)itemHandle);
}
/*
void ITextSet(DialogPtr aDialog, int aItem, Str255 *aStr)
{
Handle itemHandle=DlogItemGet(aDialog, aItem);
SetIText(itemHandle, aStr);
}
*/
void CtlFlip(DialogPtr aDialog, int aItem)
{
Handle itemHandle=DlogItemGet(aDialog, aItem);
SetCtlValue((ControlHandle)itemHandle,
(GetCtlValue((ControlHandle)itemHandle)==kOn)? kOff:kOn);
}
void StartCountDown(long aNumSecs)
{
GetDateTime(&gOldTime);
if(gSavedSettings.iUnit==kTimeUnitMinutes) {
aNumSecs*=kSecondsPerMinute;
}
gTimeout=gOldTime+aNumSecs; // this is the timeout.
gIsCounting=kBoolTrue;
}
// Called on Null event.
void HandleCountDown(void)
{
if(gIsCounting==kBoolTrue) {
long myTime;
GetDateTime(&myTime);
if(myTime!=gOldTime) {
GrafPtr oldPort;
gOldTime=myTime; // gTimeout-gOldTime==remaining seconds.
// gen update, but how?
GetPort(&oldPort);
SetPort((GrafPtr)gCountDownWindow);
InvalRect(&gCountDownWindow->portRect);
SetPort(oldPort);
}
}
}
void UpdateCountDown(void)
{
//
WindowPtr win=(WindowPtr)gTheEvent.message;
if(win==gCountDownWindow) {
long remaining=gTimeout-gOldTime;
Str255 myTimeString;
BeginUpdate(win);
MoveTo(kLeft, kTop);
if(remaining<=0 || gIsCounting==kBoolFalse) {
remaining=0;
gIsCounting=kBoolFalse;
}
NumToString(remaining, myTimeString);
EraseRect(&(gCountDownWindow->portRect));
DrawString(myTimeString);
EndUpdate(win);
}
}
void RestoreSettings(DialogPtr aSettingsDialog)
{
Handle itemHandle;
Str255 timeString;
tBool isInSeconds=(gSavedSettings.iUnit==kTimeUnitSeconds)?
kBoolTrue:kBoolFalse;
itemHandle=DlogItemGet(aSettingsDialog, kTimeField);
NumToString(gSavedSettings.iTime, &timeString);
SetIText(itemHandle, timeString);
CtlSet(aSettingsDialog, kSoundOnBox, gSavedSettings.iSound);
CtlSet(aSettingsDialog, kIconOnBox, gSavedSettings.iIcon);
CtlSet(aSettingsDialog, kAlertOnBox, gSavedSettings.iAlert);
CtlSet(aSettingsDialog, kSecsRadio, (isInSeconds==kBoolTrue)?kOn:kOff);
CtlSet(aSettingsDialog, kMinsRadio, (isInSeconds==kBoolFalse)?kOn:kOff);
itemHandle=DlogItemGet(aSettingsDialog, kSOrMField);
SetIText(itemHandle,(gSavedSettings.iUnit==kTimeUnitSeconds)?
"\pseconds":"\pminutes");
}
void SaveSettings(DialogPtr aSettingsDialog)
{
Handle itemHandle;
Str255 timeString;
itemHandle=DlogItemGet(aSettingsDialog, kTimeField);
GetIText(itemHandle, &timeString);
StringToNum(timeString, &gSavedSettings.iTime);
gSavedSettings.iSound=CtlGet(aSettingsDialog, kSoundOnBox);
gSavedSettings.iIcon=CtlGet(aSettingsDialog, kIconOnBox);
gSavedSettings.iAlert=CtlGet(aSettingsDialog, kAlertOnBox);
gSavedSettings.iUnit=(CtlGet(aSettingsDialog, kSecsRadio)==kOn)?
kTimeUnitSeconds:kTimeUnitMinutes;
}
void HandleDialog(void)
{
tBool dialogDone;
int itemHit;
long alarmDelay;
Handle itemHandle;
DialogPtr settingsDialog;
settingsDialog=GetNewDialog(kBaseResId, NULL, (WindowPtr)-1);
ShowWindow(settingsDialog);
RestoreSettings(settingsDialog);
dialogDone=kBoolFalse;
while(dialogDone==kBoolFalse) {
ModalDialog(NULL, &itemHit);
switch(itemHit) {
case kSaveButton:
SaveSettings(settingsDialog); // update them.
dialogDone=kBoolTrue;
break;
case kCancelButton:
dialogDone=kBoolTrue;
break;
case kSoundOnBox:
case kIconOnBox:
case kAlertOnBox:
CtlFlip(settingsDialog, itemHit);
break;
case kSecsRadio:
CtlSet(settingsDialog, kSecsRadio, kOn);
CtlSet(settingsDialog, kMinsRadio, kOff);
itemHandle=DlogItemGet(settingsDialog, kSOrMField);
SetIText(itemHandle, "\pseconds");
break;
case kMinsRadio:
CtlSet(settingsDialog, kSecsRadio, kOff);
CtlSet(settingsDialog, kMinsRadio, kOn);
itemHandle=DlogItemGet(settingsDialog, kSOrMField);
SetIText(itemHandle, "\pminutes");
break;
}
}
DisposeDialog(settingsDialog);
}
void HandleFileChoice(int aTheItem)
{
switch(aTheItem) {
case kChangeItem:
HandleDialog();
break;
case kStartStopItem:
HiliteMenu(0);
StartCountDown(gSavedSettings.iTime);
break;
case kQuitItem:
gDone=true;
break;
}
}
void HandleAppleChoice(int aTheItem)
{
Str255 accName;
int accNumber, itemNumber, dummy;
MenuHandle appleMenu;
switch(aTheItem) {
case kAboutItem:
NoteAlert(kAboutAlert, NULL);
break;
default:
appleMenu=GetMHandle(kAppleMenuId);
GetItem(appleMenu, aTheItem, &accName);
OpenDeskAcc(accName);
break;
}
}
void HandleMenuChoice(long aMenuChoice)
{
int theMenu, theItem;
if(aMenuChoice!=0) {
theMenu=HiWord(aMenuChoice);
theItem=LoWord(aMenuChoice);
switch(theMenu) {
case kAppleMenuId:
HandleAppleChoice(theItem);
break;
case kFileMenuId:
HandleFileChoice(theItem);
break;
}
HiliteMenu(0);
}
}
void HandleMouseDown(void)
{
WindowPtr whichWindow;
int thePart;
long menuChoice, windSize;
thePart=FindWindow(gTheEvent.where, &whichWindow);
switch(thePart) {
case inMenuBar:
menuChoice=MenuSelect(gTheEvent.where);
HandleMenuChoice(menuChoice);
break;
case inSysWindow:
SystemClick(&gTheEvent, whichWindow);
break;
case inDrag:
DragWindow(whichWindow, gTheEvent.where, &screenBits.bounds);
break;
case inGoAway:
gDone=kBoolTrue;
break;
}
}
void HandleEvent(void)
{
char theChar;
tBool dummy;
WaitNextEvent(everyEvent, &gTheEvent, kSleep, NULL);
switch(gTheEvent.what){
case mouseDown:
HandleMouseDown();
break;
case keyDown: case autoKey:
theChar=(char)(gTheEvent.message & charCodeMask);
if((gTheEvent.modifiers & cmdKey)!=0) {
HandleMenuChoice(MenuKey(theChar));
}
break;
case nullEvent:
HandleCountDown();
break;
case updateEvt:
UpdateCountDown();
break;
}
}
void MainLoop(void)
{
gDone=kBoolFalse;
while(gDone==kBoolFalse) {
HandleEvent();
}
}
void MenuBarInit(void)
{
Handle myMenuBar;
MenuHandle aMenu;
myMenuBar=GetNewMBar(kBaseResId);
SetMenuBar(myMenuBar);
DisposHandle(myMenuBar);
aMenu=GetMHandle(kAppleMenuId);
AddResMenu(aMenu, 'DRVR');
DrawMenuBar();
}
void WinInit(void)
{
gCountDownWindow=GetNewWindow(kBaseResId, NULL, (WindowPtr)-1);
gIsCounting=kBoolFalse;
SetPort(gCountDownWindow);
TextFace(bold); // it's the same in THINK C.
TextSize(24);
ShowWindow(gCountDownWindow);
}
void DialogInit(void)
{
gSavedSettings.iTime=12;
gSavedSettings.iSound=kOn;
gSavedSettings.iIcon=kOn;
gSavedSettings.iAlert=kOn;
gSavedSettings.iUnit=kTimeUnitSeconds;
}
tBool Sys6OrLater(void)
{
OSErr status;
SysEnvRec SysEnvData;
int dummy;
tBool result=kBoolTrue;
status=SysEnvirons(kSysVersion, &SysEnvData);
if(status!=noErr || SysEnvData.systemVersion<0x600) {
StopAlert(kBadSysAlert, NULL);
result=kBoolFalse;
}
return result;
}
void ToolboxInit(void)
{
InitGraf(&thePort);
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs(NULL);
MaxApplZone();
}
int main(void)
{
ToolboxInit();
if(Sys6OrLater()) {
DialogInit();
MenuBarInit();
WinInit();
InitCursor();
MainLoop();
}
return 0;
}