TweetFollow Us on Twitter

Macinkeys
Volume Number:4
Issue Number:12
Column Tag:Pascal Procedµres

Inside Macinkeys

By Fabien Samuel, Paris France

Fabien Samuel is a mathematics teacher in Paris, as well as a convinced Macintosh adept and Programmer.

Getting Info

This article is meant to give a comprehensive overview about the various resources, structures and procedures involved in keyboard events, and especially about the relation between Keycodes and Ascii Codes. There has already been a couple of very good articles on that subject in MacTutor, and I must thank Joel West and Jörg Langowski for the help I found in these as a starting point. The present article tries to update and complete the information with respect to the new resources in System 4.0 or later.

As an example, I have included the KeyEdDemo application, which allows you to remap your keyboard according to specific needs and save that configuration both in RAM, for immediate access, and in your system file, so as to keep the changes after reboot. This is a very simplified version of my shareware application KeyEd 0.5, that features a fully functional file menu, multiple scrolling windows, extensive font and view options, and advanced error checking. The main purpose of this demo version is to show how the resources described below can be used.

The information presented here has been obtained by various means. Lacking documentation about the new resources introduced with system 4.0, the most valuable help was found in MacNosy. I had quite the feeling of being a detective, looking through the nosied code of INIT 0 and _KeyTrans to deduce the structure of KCHR resources.

When I finally got Inside Mac Vol V, I was able to confirm some of my discoveries and find some more information, although the information presented there is not always very reliable.

Transitions and Codes

The keyboard contains its own micro-processor, which returns to the Mac OS Transitions Codes, also known as Raw Key Codes, every time the user types a key. Those Raw Key Codes are then translated in Virtual Key Codes, involving the use of a translation table contained in KMAP resources. The details of this translation are beyond the scope of this article, and in everything that follows we will call Key Codes the virtual key codes that were so obtained.

Key Codes can take any value between 0 and 127, not all values being actually used (at least until they release a super-extended keyboard with 128 keys!). A 16 bytes long variable, found in the low-memory global KeyMap ($174), tells which keys are down at any given time: each set bit indicating that the corresponding key is down. At least 2 normal keys can be thus indicated, along with any combination of the modifiers keys (Shift, option, etc...).

Figure 1: Relation between KeyMap and Key Codes. KeyCaps from the Extended US ADB Keyboard have been shown along with the hex key codes , and the bit offset information .

The contents of this variable can also be obtained through the Pascal procedure GetKeys (TheKeys: KeyMap). A few important notes must be made here. First of all, although the standard interface for Type KeyMap is Packed Array [0..127] of Boolean, a limitation of the LightSpeed Pascal compiler redefines it as Array [0..3] of LongInt. The underlying structure remains the same, but it cannot be accessed in the same manner. Second, as shown in Figure 1, KeyCodes do not directly correspond to a bit offset in the KeyMap variable, the order of bits being reversed in each byte. This can be very convenient at the assembly language level, where you just need to shift the KeyCode by 3 bits to get the byte offset, the remainder being the bit offset within that byte that can be used along with instructions like BTST.

Things get more complicated in Pascal if one wants to use the bit-wise ToolBox utilities, as bits are computed in the reverse order (Higher to Lower bits, Lower to Higher Bytes) than they are in the corresponding assembly instructions. However the following code shows a way to access a KeyMap that will work with any compiler:

Var TheKeys:KeyMap; KeyCode,Offset:Integer;
GetKeys(TheKeys);
Offset:= KeyCode + 7 - 2 * (KeyCode mod 8);
if BitTst(@TheKeys,Offset) then { Key is down }

Different KeyBoards

According to the countries and Mac models, the relation between physical keys and their key codes can vary greatly. This can be due to distinct physical mappings, for instance the return key or the cursor keys can be found at different locations from one keyboard to another. To make things even simpler, keys having the same function can have different key codes, or, better still, keys having the same keycodes can have different meanings! We will try to summarize these exceptions in a moment.

There currently are 6 different Apple keyboards, each one having its own keyboard type (Found in low-memory global KybdType = $21E - Byte value). However the keyboard types for the US classic and Euro classic keyboards are both equal to 3, despite their important differences: a way to distinguish between them is shown in the demo program (Function GetKbd) . A display map of these keyboards can be found in KCAP resources, contained in the system file “Key Layout”, that we will discuss later.

In chronological order, the Mac Classic (KybdType = 3) keyboards exist in US Version (KCAP ID = 3) and International Version (KCAP ID = 259). For archaeologists that might be interested, those are the keyboards that were released with the antiques 128 and 512 Macs. Then came the Mac Plus keyboard (KybdType = KCAP ID = 11), and the 3 ADB Keyboards released with the SE and Mac II: Standard (ID = 1), Extended (ID = 2) and ISO (ID = 4), this last one satisfying with the international ISO specifications, unlike the Standard ADB Keyboard.You can find representations of all those keyboards in Figures 2 to 7, along with the corresponding key codes.

Figure 2.

Figure 3

Figure 4.

Figure 5.

Figure 6.

Figure 7

We can now see better what are the main differences between keyboards:

1. Return and \: On the US Classic Keyboard the keycode for Return is $24, whereas on the Euro Classic it is $2A (which maps to the \ character on the US Classic). In all other keyboards, the keycode for Return is still $24, and in all cases the ascii code is and should be $0D.

2. Enter and Space: again the corresponding keycodes ($34 for Enter and $31 for Space on the MacClassic) have been inverted on the EuroClassic keyboard. On all subsequent keyboards, the Enter key has been moved to the numeric keypad, with keycode $4C, the Space key keeping its $31 keycode.

3. Cursor Keys: originally, those keys were only available on the optional numeric keypad, where they also served as operator keys when shifted. With the introduction of the Mac Plus built-in numeric keypad, the cursor keys were moved to the main keyboard, while the numeric operator keys stayed where they belonged on the keypad. The only problem is that they kept the same keycodes! To solve this conflict, when you type one of the numeric operator keys on the Plus, it acts as if you had held the shift key down (and likewise, holding down the shift key while typing one of the cursor keys will return the corresponding numeric operator). On subsequent models, this conflict was resolved by giving altogether different keycodes to both the numeric operator and cursor keys: this means 8 new key codes have been introduced for that purpose. The situation is summarized in Figure 8. Also note that the + and - keys on the keypad have been inverted between the ADB extended and standard keyboards.

The main conclusion of all this is that you should only rely on the ascii code of such control characters, which always keeps the same value whatever keyboard you use.

Figure 8.

Keyboard Events

For most purposes, dealing with keyboard input mainly involves polling KeyDown and AutoKey events. Information about such an event is given in the Message and Modifiers fields of the EventRecord returned by GetNextEvent. The Message field has the following 4 bytes long structure:

<Reserved, ADBAddress, keycode, ascii code>

ADBAddress is used with the Mac SE and II, and contains a reference number of the keyboard the typing came from, useful if various keyboards are simultaneously connected (see January 88 MacTutor for an example of this). Ascii code is obtained through the conversion routines that are discussed in the next paragraphs, taking into account both the key code and the state of modifiers keys.

The Modifiers field of the EventRecord is made up of 2 bytes, the high byte giving the state of the modifiers keys in the following bit-arrangement:

<x,y,z,control,option,caps lock,shift,command>

Flags x,y and z are normally zero. However, the extended ADB keyboard can be reconfigured so as to distinguish between the left and right Shift,Option and Control Keys. In that case, the right keys will generate new key codes, and flags x,y,z could be renamed: R.Control,R.Option,R.Shift. This possibility is supported in order to use the Mac with an operating system Inside Mac doesn’t dare to mention, so neither will I! It can be implemented through the use of the ADB operators mechanism, which might be the subject of another article. Anyway, this capacity is strongly discouraged because it doesn’t comply with the User Interface.

Finally, let’s note that some keys do not immediately return an event: those are the keys that are used in conjunction with another key to produce exotic objects like accented characters. I will refer to them as “Double Strike Keys”, also the official terminology appears to be “Dead Keys”, but I just don’t like the idea of my keys being dead!

Figure 9.

In the beginning, there was INIT 0 and 1...

On the first Macs, and until the Mac Plus with systems 3.x, all the information related to converting key codes to ascii codes was contained in system resources INIT 0 and 1. The Localizer was in charge of adapting these resources according to the various countries. Those resources contained both the conversion code and the associated translation tables.

With systems 4.x and later, The INIT’s now mainly contain code, the translation tables being now separate and contained in the KCHR 0 system resource. At boot time, the INIT’s are loaded into the system heap and jumped to. They are mainly in charge of installing pointers to the conversions routines in low-memory globals Key1Trans and Key2Trans.

The reason for there being two separate routines is due to the fact that originally, the numeric keypad was optional. Key1Trans will thus deal with keycodes from 0 to 63, while Key2Trans treats keycodes from 64 to 127, that used to be found exclusively on the numeric keypad. This distinction now appears completely artificial (if not intelligent...) since the Mac Plus was introduced. It seems that those routines have only stayed for compatibility reasons, as their codes are now almost the same, and both refer to the new _KeyTrans ToolBox trap, which does most of the work.

The Inner Workings

To start with, I will describe the main steps involved in INIT 0, INIT 1 having a similar functionality. As any INIT resource, it gets executed at boot time after being loaded as a locked relocatable block, accessed through a handle. It’s made up of two essential routines that I’ll call SetUp and Key1TransMain. SetUp is the part that gets jumped to at boot time and does the following :

1. Load KCHR 0 into the system heap, if it’s not already there.

2. Create a non-relocatable copy of KCHR 0 in system heap and store a pointer to it at offset 14 in a parameter block that is accessed through the pointer maintained in BasicGlob (= $2B6) low-memory global. Actually, this copy is expanded by at least 512 bytes, probably in order to allow its modification by another program.

3. Copy Byte 6 from the itlc 0 in a Data zone internal to INIT 0. This is an international utilities resource, and the value of this byte will be used to distinguish between the US Classic and Euro Classic keyboards, as illustrated in the GetKbd function of the demo program. This actually must mean that running with an Euro Classic keyboard and a US system would make believe the keyboard was US [This is true, recalling all the mixups we had using different systems - JL]: there doesn’t seem to be a hardware method to distinguish between the two keyboards.

4. Put the entry point to Key1TransMain in low-memory global Key1Trans.

5. Cut down the size of its own handle before returning, the SetUp routine being now useless (this is accomplished by putting those last instructions at the beginning of the block, unless one wants to call this the Auto-Destroy routine!).

Key1Trans

Key1transMain is the part currently used every time a keydown or keyup event is detected by the Event Manager. This is a register-based routine, and calling it from an high level language involves assembler glue-code (see the Demo Program). It takes on entry:

D2 contains the keycode (less than 64, or nothing will happen: in that case one must call Key2Trans)

D1 contains the state of the modifiers keys (as it appears in the third word of the KeyMap structure: this differs from the EventRecord Modifiers field).

D3 should be 0 for most purposes: bit 7 can be set to indicate that the event should be treated as a key-up event rather than a key-down event.

On exit, D0 contains the resulting ascii code.

This routine works as follows:

1. Make up a word that has the following structure:

- the high byte contains the modifier flags as they would appear in the EventRecord modifiers field:

<x,y,z,ctl,opt,caps,shift,cmd>

Note that the command key state is read directly on the keyboard when the routine is executed, and so doesn’t depend on the one you pass in D1.

- the low byte contains the key code, with a flag in bit 7 indicating KeyUp status.

2. Call the ToolBox function

KeyTrans (transData:Ptr; KeyCode:Integer; VAR State: LongInt): Longint;

where KeyCode is the word obtained in step 1, TransData a pointer to the copy of KCHR 0 accessed through BasicGlob as described above, and State an offset used for double strike keys that is maintained at offset 10 in the parameter block pointed to by BasicGlob. We will see how this works in just a moment .

The function result contains in its Low Word the ascii code of the current key, taking into account its possible combination with a previous double strike key. The High Word returns the ascii code for an eventually waiting double strike if it couldn’t be combined with the current key.

3. In case the High Word is non-zero, Key1Trans will post an event for it, thus causing the default ascii value of the previous double strike to be reported before that of the current key.

4. In any case, the ascii code for the current key and modifiers is returned in D0.

KeyTrans and KCHR 0

What finally remains to complete this overview is to understand how the KeyTrans function is working. In order to do that, we first need to know the structure of the KCHR 0 resource, which will incidentally give us the means to modify it.

As I said, KCHR 0 is the fundamental resource that allows to translate a keycode, along with the associated modifiers, to a standard ascii code.

1. The first word of a KCHR resource presently contains zero, and doesn’t seem to be currently used.

2. The next 256 bytes serve as a kind of offset table, each one containing a block number used as follows: the high byte of the Event Record modifiers field can theoretically take any value between 0 and 255, assuming the right modifiers keys are activated. This value serves as an offset into the table to get the corresponding block number. Each block is 128 bytes long, and contains the mapping for every key code when typed along with the above modifiers.

3. The following word contains the actual number of blocks that are used (currently 8, numbered from 0 to 7). This means different modifiers combinations can lead to the same block.

4. Then come the block themselves, each of its 128 bytes giving the ascii code for the corresponding key code, which serves as an offset into the block. If the ascii code is zero, it means it’s either a double strike key, a modifier key, or no key at all.

5. Finally, we find a variable length part that deals with double strikes.

a) The first word contains the total number of double strike keys.

b) For each one of them, we find a structure organized as follows:

b1) 2 bytes, giving the block number and key code of that double strike.

b2) Word giving the total number of associated variants.

b3) List of all those variants in the form: Current Ascii Code / Resulting Ascii Code

b4) Word giving the default ascii value of that double strike, in case the current ascii code wouldn’t combine with it to produce a resulting ascii code.

Let’s give an example to try and make things a bit clearer: on the US keyboard, Option E followed by E results in the accented Character é, but if followed by P it will result in the two consecutive characters ´p. The keycode for E is $E, while the option key gives a value of 8 to the modifiers flags high byte. Counting from zero, byte number 8 in the block numbers table has a value of 3, which leads us to block number 3. Looking at byte number $E in that block, we see that its value is zero! This confirms us in thinking that Option E is a double strike. Now, let’s look at the list of double strike keys: the first of them is precisely the one we want: Block Number = 3, KeyCode = $E. The next word tells us 7 variants are associated with it, and among them we find the variant $65,$8F: ascii code $65 (Character e) thus combines with our double strike to produce ascii code $8F (Character é). On the other hand, no variant is associated with ascii code $70 (Character p), and it will thus be the default value $60 (´ accent) that will be returned, followed by the character p.

We finally can explain how the KeyTrans function does its trick. The Modifiers byte is used as an offset into the block numbers table, while the keycode byte serves as an offset into the designated block. If the ascii code it finds at the designated position is non-zero, KeyTrans just returns with it. If it is zero, KeyTrans looks into the double-strike table and tries to match the BlockNumber/Keycode combination with one in the list. If it succeeds, the State variable is loaded with the byte offset between the start of the first block and the location of the “number of variants” entry associated with that double strike. In all other cases, State will be cleared. When KeyTrans is called again, and State is non-zero, it assumes there’s a pending double strike and, after getting the current ascii code, it will look right away at the designated offset to find a match between the current ascii code and an eventual resulting ascii code. If it finds one, it returns with the resulting ascii code. Otherwise it returns with the current ascii code in its low word, and the default ascii code of the pending double strike in its high word. In the last case, and if it has been called by Key1Trans, Key1Trans will post the appropriate event as described above. We can thus see how important it is to maintain the value of State between calls to KeyTrans if we want to get double strikes responses.

As a final point, note that KeyTrans actually does some pre-processing related to the Script Manager.

The Demo Program

The following LightSpeed Pascal program tries to give a simple example of how all this stuff can be put to use. It needs system 4.0 or later to run. It only allows editing of normal keys, and I left out all memory and resources error checking in order to keep it as short as possible. Note that the Shareware version KeyEd 0.5 does implement advanced error-checking features. I’ll leave it as an exercise to the reader to check for Nil Handles and call MemError or ResError when appropriate. However, I couldn’t resist showing how the KCAP’s resources mentioned above can be used in giving a graphical interface to keyboard editing, and that accounts for a large part of the program code. So I first have to say a word about their structure (obtained once again by nosying the KeyCaps DA):

1. Window Rectangle

2. KeyCaps DA TextEdit view rectangle

3. Number of different key shapes

4. For each shape:

a) Number of points used to define it, minus 1.

b) List of those points.

c) Number of associated keys, minus 1.

d) List of those keys in the form:

 <KeyCode,Vertical Offset, Horizontal Offset>.

The points define a region made up of rectangles, that will then be offset to the adequate position for each consecutive key. Note that the keycode field actually contains additional information, so I take it modulo 128. The procedure MainCaps that runs through this structure shows you type-casting in Pascal can do wonders, but would evidently be more elegant in assembly! The only thing I wasn’t able to do with type-casting was to access a byte at a time in memory, so I wrote the short assembly routines Peek and Poke for that purpose, the names being a tribute to Basic programmers.

The program also shows an example of using procedural parameters, a most convenient feature of the LSP compiler. This way the region obtained in MainCaps can be used either for drawing the key or for dealing with a click inside it, or for any other purpose you might want. However, if you want to port that program to another compiler, you’ll need to implement a Case statement within MainCaps to achieve the same result.

Finally, the program also shows the use of a modeless dialog with an User Item. The user procedure UserDraw is attached to that item with the SetUserProc procedure. The Dialog Manager will call it automatically on each update event for the dialog window, and it is in charge of drawing the keyboard. Likewise, if there was a click in that User Item rectangle, we call MainCaps to find if it was in a key, and handle the event accordingly in the ActiveClick procedure. Note that although the Dialog Manager deals with mouse-down events when the Dialog Window is active, you still have to explicitly activate it by calling Select Window when the Event Manager reports a click in the contents region. You also have to pre-process keydown events, to filter out command key equivalents and any other typing you don’t want passed to the edit field. One important warning if you use LightsBug while running this program is that if you inadvertently click in your switched-out Dialog Window, LSP 1.11 will bomb after displaying the usual “You can only drag your program’s windows while it is halted” message. This could be due to the fact that LSP gets notified of the event only after the Dialog Manager has tried to run your User Proc, which will rely on globals that have become invalid since your program is switched out. Anyway, I nearly got my hard-disk trashed while trying to figure out the problem!

Note that the RMaker source file for the program doesn’t include definitions for the KCAP resources used by the program. So after compiling this file with RMaker, you should use ResEdit to copy these resources from the Key Layout file in your system folder and paste them into your resource file. While you’re at it, you could also install BNDL or SIZE resources to make the program look more fancy. It should run at ease under a 50K partition. There are many more possible improvements, but I’ll leave them to your imagination.

{1}
Listing:  KeyEdDemo.pas
PROGRAM KeyEdDemo;
{ copyright F.Samuel and MacTutor 1988 }
{ use only with system 4.x or later }
{$I-}
 { turn off automatic initialization }
{$L keyEdDemoRes}
 { load resource file }

 CONST
 DialogID = 128;
 EditCodeItem = 2;
 LoadBtn = 5;
 UserItem = 6;
 AsciiCharItem = 7;
 KeyCodeItem = 8;
 AboutAlrt = 129;
 LoadAlrt = 130;
 AppleID = 1;
 AboutItem = 1;
 FileID = 2;
 EditID = 3;
 CutItem = 3;
 CopyItem = 4;
 PasteItem = 5;
 ClearItem = 6;

{ Low-memory globals }
 KybdType = $21E;
 ScsiFlag = $B22;
 Key1Trans = $29E;
 BasicGlob = $2B6;

{ KCaps ID of various keyboards }
 MacPlusKbd = 11;
 MacClassicKbd = 3;
 EuroMacKbd = 259;
 ADBKbd = 1;
 ADBExtKbd = 2;
 ADBIsoKbd = 4;

 TYPE
 Prect = ^Rect; { for type-casting a pointer }
 PLong = ^LongInt;
 PWord = ^integer;
 PPoint = ^Point;

 VAR
 Finished, EditOn : Boolean;
 DragRect : Rect;
 MouseLocal : Point;
 KCapsHandle, KChrHandle : Handle;
 DemoDialog : DialogPtr;
 HiliteKeys : SET OF 0..127;
 EditKey, EditModifs : Integer;

{ utilities to acces properties of items in a dialog }

 PROCEDURE SetDItemText (TheDialog : DialogPtr;
 TheItem : Integer;
 TheText : Str255);
 VAR
 ItemType : integer; { should be a text item }
 ItemHandle : Handle;
 DispRect : Rect;
 BEGIN
 GetDItem(TheDialog, TheItem, ItemType, ItemHandle, DispRect);
 SetIText(ItemHandle, TheText)
 END;

 FUNCTION GetDItemText (TheDialog : DialogPtr;
 TheItem : Integer) : Str255;
 VAR
 ItemType : integer; { should be a text item }
 ItemHandle : Handle;
 DispRect : Rect;
 TheText : Str255;
 BEGIN
 GetDItem(TheDialog, TheItem, ItemType, ItemHandle, DispRect);
 GetIText(ItemHandle, TheText);
 GetDItemText := TheText
 END;

 FUNCTION GetDItemRect (TheDialog : DialogPtr;
 TheItem : Integer) : Rect;
 VAR
 ItemType : integer;
 ItemHandle : Handle;
 DispRect : Rect;
 BEGIN
 GetDItem(TheDialog, TheItem, ItemType, ItemHandle, DispRect);
 GetDItemRect := DispRect
 END;

 PROCEDURE SetUserProc (TheDialog : DialogPtr;
 TheItem : Integer;
 TheProc : procPtr);
 VAR
 ItemType : integer; { should be an UserItem ! }
 ItemHandle : Handle;
 DispRect : Rect;
 BEGIN
 GetDItem(TheDialog, TheItem, ItemType, ItemHandle, DispRect);
 SetDItem(TheDialog, TheItem, ItemType, Handle(theProc), DispRect)
 END;

{ function NumToString , more convenient that way ... }

 FUNCTION FNumToString (TheNum : LongInt) : Str255;
 VAR
 TheString : Str255;
 BEGIN
 NumToString(TheNum, TheString);
 FNumToString := TheString
 END;

{ interface for external procedures and functions : }

 FUNCTION KeyTrans (transData : Ptr;
 keycode : INTEGER;
 VAR state : LONGINT) : LONGINT;
 INLINE
 $A9C3;

 PROCEDURE poke (address : longint;
 value : integer);
 external; { puts low byte of value at address }

 FUNCTION peek (address : longint) : integer;
 external; { returns byte at address }

 FUNCTION Key12Trans (KeyCode, KeyModifs : Integer) : Integer;
 external;

{ Pascal procedures and functions follow ... }

 FUNCTION GetAscii (KeyCode, Modifs : integer;
 VAR Ascii : integer) : Boolean;
 { Returns true if it’s a normal key , Ascii returns ascii code even 
if it’s a double strike}
 VAR
 State : LongInt;
 BEGIN
 State := 0;
 Ascii := LoWord(KeyTrans(KChrHandle^, KeyCode + Modifs, State));
 IF Ascii = 0 THEN
 BEGIN
 GetAscii := false;
 Ascii := LoWord(KeyTrans(KChrHandle^, KeyCode + Modifs, State))
 END
 ELSE
 GetAscii := true
 END;

 FUNCTION GetKbd : Integer;
 { returns KCAP ID of current keyboard }
 VAR
 TempID : integer;
 addr : Plong;
 SCSIMac : boolean;
 BEGIN
 TempId := peek(KybdType);
 SCSIMac := BitTst(Ptr(SCSIFlag), 5);
 IF (NOT SCSIMac) AND (TempID <> MacPlusKbd) THEN
 BEGIN
 tempId := MacClassicKbd;
 addr := Plong(Key1Trans);
 IF peek(addr^ + 10) <> 0 THEN { test itlc byte }
 TempId := EuroMacKbd
 END;
 GetKbd := TempID
 END;

 PROCEDURE SetUpMenus;
 VAR
 ID : integer;
 BEGIN
 FOR ID := AppleID TO EditID DO
 InsertMenu(GetMenu(ID), 0);
 AddResMenu(GetMHandle(AppleID), ‘DRVR’);
 DrawMenuBar
 END;

 PROCEDURE MainCaps (PROCEDURE treatIt (rgn : RgnHandle;
 Code : integer));
 VAR
 Hrgn : RgnHandle;
 addr : PWord;
 Paddr : PPoint;
 NumRgn, NumRect, NumKeys, i, j : integer;
 Keycode, dh, dv : Integer;
 TL, BR : point;
 KRect : rect;
 BEGIN
 BEGIN
 SetPort(DemoDialog);
 GetMouse(MouseLocal);
 ClipRect(DemoDialog^.PortRect);
 Hlock(KcapsHandle);
 Addr := Pword(Ord4(KcapsHandle^) + 16);
 NumRgn := Addr^;
 IF NumRgn > 0 THEN
 FOR i := 1 TO NumRgn DO
 BEGIN
 Addr := Pword(Ord4(Addr) + 2);
 NumRect := addr^;
 Hrgn := NewRgn;
 OpenRgn;
 SetPt(TL, 0, 0);
 Addr := Pword(Ord4(Addr) + 2);
 FOR j := 0 TO NumRect DO
 BEGIN
 PAddr := PPoint(addr);
 BR := Paddr^;
 Pt2Rect(TL, BR, Krect);
 FrameRect(Krect);
 TL := BR;
 Addr := Pword(Ord4(Addr) + 4);
 END;
 CloseRgn(Hrgn);
 NumKeys := addr^;
 FOR j := 0 TO NumKeys DO
 BEGIN
 Addr := Pword(Ord4(Addr) + 2);
 KeyCode := addr^;
 Addr := Pword(Ord4(Addr) + 2);
 dv := addr^;
 Addr := Pword(Ord4(Addr) + 2);
 dh := addr^;
 OffsetRgn(Hrgn, dh, dv);
 TreatIt(Hrgn, Keycode MOD 128);
 END;
 DisposeRgn(Hrgn);
 END;
 Hunlock(KcapsHandle)
 END
 END;

 PROCEDURE InvertKey (Rgn : RgnHandle);
 VAR
 InnerRgn : RgnHandle;
 BEGIN
 InnerRgn := NewRgn;
 CopyRgn(Rgn, InnerRgn);
 InsetRgn(InnerRgn, 2, 2);
 InvertRgn(InnerRgn);
 DisposeRgn(InnerRgn)
 END;

 PROCEDURE DrawKey (rgn : RgnHandle;
 Code : integer);
 VAR
 DrawRgn : RgnHandle;
 AsciiCode : Integer;
 NormalKey : boolean;
 BEGIN
 FrameRgn(Rgn);
 DrawRgn := NewRgn;
 CopyRgn(Rgn, DrawRgn);
 InsetRgn(DrawRgn, 1, 1);
 SetClip(DrawRgn);
 EraseRgn(DrawRgn);
 NormalKey := GetAscii(Code, EditModifs, AsciiCode);
 WITH DrawRgn^^.rgnBBox DO
 MoveTo(left + 1, bottom - 2);
 DrawChar(Chr(AsciiCode));
 DisposeRgn(DrawRgn);
 IF Code IN HiliteKeys THEN
 InvertKey(Rgn);
 ClipRect(DemoDialog^.PortRect)
 END;

 PROCEDURE UserDraw (TheWindow : WindowPtr;
 ItemNum : Integer);
 VAR
 FillPat : Pattern;
 TheRect : Rect;
 BEGIN
 TheRect := GetDItemRect(DemoDialog, ItemNum);
 GetIndPattern(FillPat, 0, 10);
 FillRect(TheRect, FillPat);
 FrameRect(TheRect);
 MainCaps(DrawKey)
 END;

 PROCEDURE InitThings;
 VAR
 i, Error : Integer;
 BEGIN { Get KybdID and KCaps ; SetUserProc;Show dialog }
 FlushEvents(EveryEvent, 0);
 InitGraf(@ThePort);
 InitFonts;
 InitWindows;
 InitMenus;
 TEInit;
 InitDialogs(NIL);
 InitCursor;
 SetEventMask(EveryEvent - KeyUpMask);
 MaxApplZone;
 FOR i := 1 TO 5 DO
 MoreMasters;
 WITH ScreenBits.Bounds DO
 SetRect(DragRect, left + 4, top + 24, right - 4, bottom - 4);
 SetUpMenus;
 KCapsHandle := GetResource(‘KCAP’, GetKbd);
 DetachResource(KCapsHandle); { don’t let a DA release it on your back 
! }
 KChrHandle := GetResource(‘KCHR’, 0);
 Error := HandToHand(KChrHandle); { make a copy , so we can modify it 
}
 MoveHHi(KChrHandle); { keep heap unfragmented }
 HLock(KChrHandle);
 HiliteKeys := []; { no keys to hilite until the user selects one }
 EditModifs := 0;
 EditOn := False; { wait for the user to select a key to edit }
 DemoDialog := GetNewDialog(DialogID, NIL, WindowPtr(-1));
 SetUserProc(DemoDialog, UserItem, @UserDraw);
 ShowWindow(DemoDialog);
 Finished := False
 END;

 PROCEDURE DoMenu (Code : longint);
 VAR
 MenuNum, ItemNum, Temp : integer;
 DeskAccName : str255;
 BEGIN
 IF code <> 0 THEN
 BEGIN
 MenuNum := HiWord(Code);
 ItemNum := LoWord(Code);
 CASE MenuNum OF
 AppleID : 
 IF ItemNum = AboutItem THEN
 Temp := Alert(AboutAlrt, NIL)
 ELSE
 BEGIN
 GetItem(GetMHandle(AppleID), ItemNum, DeskAccName);
 Temp := OpenDeskAcc(DeskAccName);
 END;
 FileID : 
 Finished := True;
 EditID : 
 IF NOT SystemEdit(ItemNum - 1) THEN
 IF (FrontWindow = DemoDialog) AND EditOn THEN
 CASE ItemNum OF
 CutItem : 
 DlgCut(DemoDialog);
 CopyItem : 
 DlgCopy(DemoDialog);
 PasteItem : 
 DlgPaste(DemoDialog);
 ClearItem : 
 DlgDelete(DemoDialog);
 OTHERWISE
 END;
 OTHERWISE
 END;
 HiliteMenu(0)
 END
 END;

 PROCEDURE StartEdit (KeyCode, AsciiCode : Integer);
 { enable editing  the ascii code of the selected key }
 BEGIN
 EditKey := KeyCode;
 HiliteKeys := HiliteKeys + [EditKey];
 SetDItemText(DemoDialog, EditCodeItem, FNumToString(AsciiCode));
 SelIText(DemoDialog, EditCodeItem, 0, MaxInt);
 SetDItemText(DemoDialog, KeyCodeItem, FNumToString(EditKey));
 SetDItemText(DemoDialog, AsciiCharItem, Chr(AsciiCode));
 EditOn := true
 END;

 PROCEDURE DrawEditKRgn (Rgn : RgnHandle;
 TheCode : Integer);
 { update key contents in case it changed }
 BEGIN
 IF TheCode = EditKey THEN
 DrawKey(Rgn, EditKey)
 END;

 PROCEDURE ValidateEdit;
 { validate user editing of the selected key }
 VAR
 BlockNumber : Integer;
 MapAddress, NewAsciiCode : LongInt;
 BEGIN
 IF EditOn THEN
 BEGIN
 StringToNum(GetDItemText(DemoDialog, EditCodeItem), NewAsciiCode);
 MapAddress := Ord4(KChrHandle^);
 BlockNumber := Peek(MapAddress + BitShift(EditModifs, -8) + 2);
 Poke(MapAddress + 260 + BlockNumber * 128 + EditKey, NewAsciiCode);
 EditOn := False;
 SetDItemText(DemoDialog, EditCodeItem, ‘’);
 SetDItemText(DemoDialog, KeyCodeItem, ‘’);
 SetDItemText(DemoDialog, AsciiCharItem, ‘’);
 HiliteKeys := HiliteKeys - [EditKey];
 MainCaps(DrawEditKRgn);
 END
 END;

 PROCEDURE CodeXORModifs (ModifCode : Integer;
 VAR Modifs : Integer);
 { XOR new modifier with the already selected ones }
 VAR
 TempModifs : Integer;
 BEGIN
 TempModifs := 0;
 BitSet(@TempModifs, $3E - ModifCode);
 Modifs := BitXor(Modifs, TempModifs)
 END;

 FUNCTION EditPerm (KeyCode, Modifs : Integer;
 VAR AsciiCode : Integer) : Boolean;
{ can that key  be edited ? }
 BEGIN
 IF GetAscii(KeyCode, Modifs, AsciiCode) THEN
 EditPerm := true
 ELSE { check that it’s not a double strike nor a modifier }
 EditPerm := (AsciiCode = 0) AND NOT (KeyCode IN [$3C..$3E])
 END;

 PROCEDURE ActiveClick (rgn : RgnHandle;
 Code : integer);
 VAR
 AsciiCode : Integer;
 BEGIN
 IF PtInRgn(MouseLocal, Rgn) THEN
 IF (Code <> EditKey) OR NOT EditOn THEN
 BEGIN
 ValidateEdit;
 IF Code IN [$37..$3B] THEN { modifier key was clicked }
 BEGIN
 CodeXORModifs(Code, EditModifs);
 IF Code IN HiliteKeys THEN
 HiliteKeys := HiliteKeys - [Code]
 ELSE
 HiliteKeys := HiliteKeys + [Code];
 IF EditPerm(EditKey, EditModifs, AsciiCode) THEN
 StartEdit(EditKey, AsciiCode);
 MainCaps(DrawKey)  { redraw the keyboard to update hiliting }
 END
 ELSE IF EditPerm(Code, EditModifs, AsciiCode) THEN
 BEGIN
 InvertKey(Rgn); { hilite it , and let user edit it }
 StartEdit(Code, AsciiCode)
 END
 END
 END;

 FUNCTION CheckSysTrans (TransData : Ptr) : Boolean;
 { is TransData really a pointer to the system mapping table ?? }
 VAR
 KeyCode, AsciiCode : Integer;
 BEGIN
 CheckSysTrans := true;
 FOR KeyCode := 0 TO 16 DO
 BEGIN
 AsciiCode := Key12Trans(KeyCode, 0); { let system compute it }
 IF Peek(Ord4(TransData) + 260 + KeyCode) <> AsciiCode THEN
 CheckSysTrans := False; { compare with our table }
 IF AsciiCode = 0 THEN
 BEGIN
 AsciiCode := Key12Trans(KeyCode, 0);
 FlushEvents(EveryEvent, 0) { double strike may have posted an event 
, flush it }
 END
 END
 END;

 FUNCTION GetSysTrans (VAR TheTrans : Ptr) : boolean;
 VAR
 BGlob, Addr : PLong;
 BEGIN
 Bglob := PLong(BasicGlob);
 Addr := PLong(BGlob^ + 14);
 { not documented , so better check if we can rely on it ! }
 TheTrans := Ptr(Addr^);
 GetSysTrans := CheckSysTrans(TheTrans)
 END;

 PROCEDURE DoLoad;
 VAR
 TheSize : LongInt;
 RamTransPtr : Ptr;
 SysKchr : Handle;
 AppResFile : integer;
 BEGIN
 IF Alert(LoadAlrt, NIL) = Ok THEN
 BEGIN
 TheSize := GetHandleSize(KChrHandle);
 IF GetSysTrans(RamTransPtr) THEN { see above ... }
 BlockMove(KChrHandle^, RamTransPtr, TheSize);
 AppResFile := CurResFile;
 UseResFile(0);
 SysKchr := GetResource(‘KCHR’, 0);
 BlockMove(KChrHandle^, SysKChr^, TheSize);
 ChangedResource(SysKChr);
 UpdateResFile(0);
 UseResFile(AppResFile)
 END
 END;

 PROCEDURE DoDialog (TheEvent : EventRecord);
 CONST
 Enter = $03;
 Return = $0D;
 VAR
 TheDialog : DialogPtr;
 ItemHit, CharCode : Integer;
 PassIt : Boolean;
 BEGIN
 WITH TheEvent DO
 IF What = KeyDown THEN
 BEGIN { filter key down events }
 CharCode := BitAnd(Message, CharCodeMask);
 PassIt := False;
 IF BitAnd(Modifiers, CmdKey) <> 0 THEN
 DoMenu(MenuKey(Chr(CharCode)))
 ELSE IF EditOn THEN
 IF CharCode IN [Return, Enter] THEN
 ValidateEdit
 ELSE
 PassIt := True
 END
 ELSE
 PassIt := True;
 IF PassIt THEN
 IF DialogSelect(TheEvent, TheDialog, ItemHit) THEN
 CASE ItemHit OF
 UserItem : 
 MainCaps(ActiveClick);
 LoadBtn : 
 DoLoad;
 OTHERWISE
 END
 END;

 PROCEDURE MainLoop;
 VAR
 GotEvent : Boolean;
 TheEvent : EventRecord;
 TheWindow : WindowPtr;
 BEGIN
 SystemTask;
 GotEvent := GetNextEvent(EveryEvent, TheEvent);
 IF IsDialogEvent(TheEvent) THEN
 DoDialog(TheEvent)
 ELSE IF GotEvent THEN
 WITH TheEvent DO
 CASE What OF
 MouseDown : 
 CASE FindWindow(Where, TheWindow) OF
 inMenuBar : 
 DoMenu(MenuSelect(Where));
 inSysWindow : 
 SystemClick(TheEvent, TheWindow);
 inContent : 
 IF TheWindow <> FrontWindow THEN
 SelectWindow(TheWindow);
 inDrag : 
 DragWindow(TheWindow, Where, DragRect);
 inGoaway : 
 IF TheWindow <> FrontWindow THEN
 SelectWindow(TheWindow)
 ELSE IF TrackGoAway(TheWindow, Where) THEN
 Finished := True;
 OTHERWISE
 END;
 KeyDown : 
 IF BitAnd(Modifiers, CmdKey) <> 0 THEN
 DoMenu(MenuKey(Chr(BitAnd(Message, CharCodeMask))));
 UpdateEvt : 
 BEGIN { just in case ... }
 TheWindow := WindowPtr(Message);
 BeginUpdate(TheWindow);
 EndUpdate(TheWindow)
 END;
 OTHERWISE
 END
 END;

BEGIN
 InitThings;
 REPEAT
 MainLoop
 UNTIL Finished
END.
{2}
Listing:  KeyEdDemo.asm

;MDS source code for KeyEDDemo External procedures
;copyright F.Samuel and Mac Tutor 1988
; After compiling , turn the .rel file into a LSP
; library using rel.Converter , then include the lib
; into your LSP project 

;MDS Glues for Pascal Peek and Poke
;procedure poke(address:ptr;value:byte)
;function peek(address:ptr): byte;

XDEF peek
XDEF poke

value EQU 8
addr1 EQU 10

poke
 LINK A6,#0
 MOVEA.Laddr1(A6),A0
 MOVE value(A6),D0
 MOVE.B D0,(A0)
 UNLK A6
 MOVEA.L(SP)+,A0
 ADDQ.L #6,SP
 JMP  (A0)
 
addr2 EQU 8
result  EQU 12
 
peek
 LINK A6,#0
 MOVEA.Laddr2(A6),A0
 MOVEQ  #0,D0
 MOVE.B (A0),D0
 MOVE D0,result(A6)
 UNLK A6
 MOVEA.L(SP)+,A0
 ADDQ.L #4,SP
 JMP  (A0)
 
; Function Key12Trans(KeyCode,KeyModifs:Integer) : Integer;
; KeyModifs are as they appear in the third word of KeyMap
; Function returns AsciiCode

XDEF    Key12Trans

Key1Trans EQU  $29E
Key2Trans EQU  $2A2

KeyModifs EQU  8
KeyCode EQU 10
FunRslt EQU 12

Key12Trans

 LINK A6,#0
 MOVEM.LA0/D0-D3,-(SP)
 MOVE KeyCode(A6),D2
 MOVE KeyModifs(A6),D1
 MOVEQ  #0,D3
 CMPI #64,D2
 BHS.S  @1
 MOVEA.LKey1Trans,A0
 BRA.S  @2
@1 MOVEA.LKey2Trans,A0
@2 JSR  (A0)
 MOVE D0,FunRslt(A6)
 MOVEM.L(SP)+,A0/D0-D3
 UNLK A6
 MOVEA.L(SP)+,A0
 ADDQ.L #4,SP 
 JMP  (A0)
{3}
Listing:  KeyEdDemoRes.R
* KeyEdDemo resource definitions for RMaker *
* Copyright 1988 - F.Samuel and Mac Tutor   *
* After compiling , don’t forget to add the *
* KCAP resources from Key Layout in your    *
* System Folder , using ResEdit  . Then          *
* include the file into your LSP project    *
* using Run Options from the Project Menu   *


KeyEdDemoRes


Type MENU

     ,1
\14
About KeyEdDemo
(-

     ,2
File
Quit/Q

     ,3
Edit
Undo/Z
(-
Cut/X
Copy/C
Paste /V
Clear

Type DLOG

     ,128
Key Ed Demo
76 14 306 504
Invisible GoAway
4;definition ID
0;ref con
128;DITL ID

Type ALRT

     ,129
42 102 94 410
129;DITL ID
CCCC  ;Alert stages

     ,130
42 136 142 376
130
5555

Type DITL

     ,128
8
*   1
StatText Disabled
5 3 25 89
Ascii Code : 

*   2
EditText Disabled
5 96 25 133

*   3
StatText Disabled
5 279 25 357
Key Code : 

*   4
StatText Disabled
5 144 25 221
Ascii Char : 

*   5
BtnItem Enabled
5 418 25 478
Load

*   6
UserItem Enabled
32 5 222 482

*   7
StatText Disabled
5 223 25 257

*   8
StatText Disabled
5 360 25 393

     ,129
1
*   1
StatText Enabled
5 3 48 305
Key Ed Demo\0DCopyright Fabien Samuel and Mac Tutor 1988

     ,130
3
*   1
BtnItem Enabled
75 6 95 66
OK

*   2
BtnItem Enabled
75 170 95 230
Cancel

*   3
StatText Disabled
6 5 65 232
Are you sure you want to load this mapping into the System File and into 
RAM ? 
 
AAPL
$567.34
Apple Inc.
+42.59
MSFT
$39.37
Microsoft Corpora
-0.32
GOOG
$527.22
Google Inc.
+0.28

MacTech Search:
Community Search:

Software Updates via MacUpdate

Cyberduck 4.4.4 - FTP and SFTP browser....
Cyberduck is a robust FTP/FTP-TLS/SFTP browser for the Mac whose lack of visual clutter and cleverly intuitive features make it easy to use. Support for external editors and system technologies such... Read more
TechTool Pro 7.0.3 - Hard drive and syst...
TechTool Pro is now 7, and this is the most advanced version of the acclaimed Macintosh troubleshooting utility created in its 20-year history. Micromat has redeveloped TechTool Pro 7 to be fully 64... Read more
MacFamilyTree 7.1.6 - Create and explore...
MacFamilyTree gives genealogy a facelift: it's modern, interactive, incredibly fast, and easy to use. We're convinced that generations of chroniclers would have loved to trade in their genealogy... Read more
EtreCheck 1.9.9 - For troubleshooting yo...
EtreCheck is a simple little app to display the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support... Read more
TeamViewer 9.0.28116 - Establish remote...
TeamViewer gives you remote control of any computer or Mac over the Internet within seconds, or can be used for online meetings. Find out why more than 200 million users trust TeamViewer! Free for... Read more
Viber 4.1.0 - Send messages and make cal...
Viber lets you send free messages and make free calls to other Viber users, on any device and network, in any country! Viber syncs your contacts, messages and call history with your mobile device,... Read more
Apple iOS 7.1.1 - The latest version of...
The latest version of iOS can be downloaded through iTunes. Apple iOS 7 brings an all-new design and all-new features. Simplicity Simplicity is often equated with minimalism. Yet true simplicity is... Read more
1Password 4.3 - Powerful password manage...
1Password is a password manager that uniquely brings you both security and convenience. It is the only program that provides anti-phishing protection and goes beyond password management by adding Web... Read more
Lens Blur 1.3.0 - True out-of-focus boke...
Let Lens Blur transform your existing photo into true SLR-quality out-of-focus bokeh effect! Everyone needs a gorgeous personalized background for a social profile, blog, Web/UI design, presentation... Read more
VMware Fusion 6.0.3 - Run Windows apps a...
VMware Fusion allows you to create a Virtual Machine on your Mac and run Windows (including Windows 8.1) and Windows software on your Mac. Run your favorite Windows applications alongside Mac... Read more

Latest Forum Discussions

See All

Yomi Review
Yomi Review By Rob Thomas on April 24th, 2014 Our Rating: :: C-C-C-COMBO BREAKERiPad Only App - Designed for the iPad Round One – Fight! No quarters required for this iOS adaptation of a tabletop adaptation of the arcade fighting... | Read more »
Injustice: Gods Among Us Updated with Ne...
Injustice: Gods Among Us Updated with New Characters, Leaderboards, Gear, and Online Multiplayer Posted by Rob Rich on April 24th, 2014 [ | Read more »
Spin It Review
Spin It Review By Jordan Minor on April 24th, 2014 Our Rating: :: SPIN ME RIGHT ROUNDUniversal App - Designed for iPhone and iPad Spin It has a fine puzzle game model, but its execution lacks energy.   | Read more »
Productivity App NoteSuite is Having its...
Productivity App NoteSuite is Having its Biggest Sale Ever, Just for One Week Posted by Rob Rich on April 24th, 2014 [ permalink ] | Read more »
Wayward Souls Review
Wayward Souls Review By Carter Dotson on April 24th, 2014 Our Rating: :: CARRY ON, WAYWARD SONUniversal App - Designed for iPhone and iPad Wayward Souls is a roguelike-inspired action-RPG that sets a high bar for other games to... | Read more »
The Sandbox Gets Update, Receives New Ca...
The Sandbox Gets Update, Receives New Campaign and New Elements Posted by Tre Lawrence on April 24th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Football Management Simulator One For El...
Football Management Simulator One For Eleven Released Worldwide Today for iOS Posted by Simon Reed on April 24th, 2014 [ permalink ] Free-To-Play football management title One For E | Read more »
Leo’s Fortune Review
Leo’s Fortune Review By Jordan Minor on April 24th, 2014 Our Rating: :: FORTUNATE SONUniversal App - Designed for iPhone and iPad Leo’s Fortune delivers a platforming experience as creative and refined as any console game.   | Read more »
Suited Up (Games)
Suited Up 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Suited Up is a difficult, one-touch platformer that requires players to visualize each jump. The controls in Suited Up are simple,... | Read more »
MyTP One Mountain - Ski, Freeski and Sno...
MyTP One Mountain - Ski, Freeski and Snowboard 1.0.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0.0 (iTunes) Description: As real snow is melting away in the snow parks around the northern hemisphere, it's now time... | Read more »

Price Scanner via MacPrices.net

16GB 1st generation iPad mini available for $...
Radio Shack has a select number of refurbished 1st generation 16GB WiFi iPad minis available for $199.99 on their online store. Choose free shipping or free ship-to-store. We expect these to sell out... Read more
13-inch 2.5GHz MacBook Pro on sale for $100 o...
B&H Photo has the 13″ 2.5GHz MacBook Pro on sale for $1099 including free shipping plus NY sales tax only. Their price is $100 off MSRP. Read more
iPad Sales “Lull” A Reality Correction Of Unm...
I have lots of time for Jean-Louis Gassée, the former Apple Computer executive (1981 to 1990) who succeeded Steve Jobs as head of Macintosh development when the latter was dismissed in 1985. Mr.... Read more
Apple Makes OS X Betas Available To All – Wit...
Apple’s OS X Beta Seed Program, which lets you install the latest pre-release builds, try it out, and submit your feedback, is now open to anyone who wants to sign on rather than to developers and... Read more
Apple Releases iOS 7.1.1 Update
The latest iOS 7.1.1 update contains improvements, bug fixes and security updates, including: • Further improvements to Touch ID fingerprint recognition • Fixes a bug that could impact keyboard... Read more
Logitech Announces Thinner, Lighter, More Fle...
Logitech has announced an update to its Ultrathin for iPad Air, iPad mini and iPad mini with Retina display, improving the flexibility and design of its award-winning predecessor with an even thinner... Read more
Logitech Introduces Hinge, Big Bang and Turna...
Logitech has announced expansion of its tablet product line with three new cases – the Logitech Hinge, the Logitech Big Bang and the Logitech Turnaround – each for the iPad Air, iPad mini and iPad... Read more
WaterField’s Rough Rider Leather Messenger Ba...
WaterField Designs have announced the new 15-inch size of their popular Rough Rider leather messenger bag, a vintage-looking bag that combines Old West charm and ruggedness with distinctly modern... Read more
New Mac Pro on sale, save $100 on the 4-Core...
J&R has the new 4-Core Mac Pro in stock today and on sale for $2899 including free shipping plus NY sales tax only. Their price is $100 off MSRP, and it’s the lowest price available for this... Read more
Apple refurbished iMacs available for up to $...
The Apple Store has Apple Certified Refurbished 2013 iMacs available for up to $300 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free. - 27″ 3.4GHz iMac – $1699... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Position Opening at *Apple* - Apple (United...
…customers purchase our products, you're the one who helps them get more out of their new Apple technology. Your day in the Apple Store is filled with a range of Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Inc. Research Data Specialist - Appl...
…of Worldwide Market Research & Intelligence. The team is responsible for conducting Apple branded consumer market research. It is also responsible for analyzing data Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.