TweetFollow Us on Twitter

Window Menu
Volume Number:5
Issue Number:7
Column Tag:ABC's of Macintosh

Related Info: Menu Manager

A Window Menu

By Clifford Story, Goleta, CA

The Standard Window Menu

A Macintosh program with several windows really ought to have a Window menu. This menu is a list of all open application windows; choosing a window from the menu brings it to the front.

The problem for the programmer is menu maintenance. Every time the program opens a new window, the window’s name has to be added to the Window menu, and, what is worse, every time a window is closed, its name must be removed from the menu. There is no ROM trap to delete a menu item in the 64K ROM.

Still uglier problems arise if the menu is supposed to be kept in order. Every time a window is selected, the window order changes, and the menu must be re-ordered.

One solution: each time the menu must be updated, delete the whole menu and recreate it from the window list. That isn’t very pretty, but it would work.

The solution I present here is similar but less work: Draw the menu with an MDEF that reads the window list. This approach requires virtually no menu maintenance (only a call to CalcMenuSize when a window is opened or closed). As an extra, the menu includes a “Zoom Front Window” item.

Advertisement

This is my third article for MacTutor, and I’ve got a fourth and a fifth in the works. Maybe I’m becoming a regular? Anyway, I thought I might mention ways to respond to my stuff.

The cheapest way is by mail:

Clifford Story

Attic Software

P.O. Box 219

Goleta, California 93116

The fastest way is by modem. I run a BBS called THE ATTIC, phone number (805) 683-0322, hours 6:00 PM to 2:00 AM, Pacific time, daily. The board isn’t very active, but I read and respond each morning.

#****************************************    
#Window.make
#MPW make file for
#dynamic Window menu demo
#(c) 1988, by Clifford Story
#& Attic Software
#****************************************
#****************************************
#Compile the resources
#****************************************
Window ƒƒ Window.make Window.r
 Rez Window.r -append -o Window
#****************************************
#Compile common declarations
#****************************************
Common.p.o ƒƒ Window.make Common.p
 Pascal Common.p
#****************************************
#Compile and link the MDEF
#****************************************
‘Window Menu’.p.o ƒ Window.make 
 ‘Window Menu’.p Common.p.o
 Pascal ‘Window Menu’.p
Window ƒƒ Window.make ‘Window Menu’.p.o
 Link -m MENUDEF -w -t ‘APPL’ 
 -c ‘????’  -rt MDEF=1001 
 -sn Main=’Window Menu’ 
 ‘Window Menu’.p.o 
 “{Libraries}”Interface.o 
 “{Libraries}”Runtime.o 
 “{PLibraries}”PasLib.o 
 “{PLibraries}”SANELib.o 
 -o Window
#****************************************
#Compile and link the main program
#****************************************
Window.p.o ƒ Window.make 
 Window.p Common.p.o
 Pascal Window.p
Window ƒƒ Window.make Window.p.o
 Link -w -t APPL -c ‘????’ 
 Window.p.o 
 “{Libraries}”Interface.o 
 “{Libraries}”Runtime.o 
 “{PLibraries}”PasLib.o 
 “{PLibraries}”SANELib.o 
 -o Window
#****************************************

The Program’s Resources

The only feature that needs to be pointed out is the declaration of the Window menu. The “Zoom Front Window” item must be declared here, so MenuKey will be able to find its keyboard equivalent.

/****************************************
 Window.r
 Resources for 
 dynamic Window menu demo.
 (c) 1988, by Clifford Story
 & Attic Software
****************************************/
#include “types.r”
/****************************************
 Menu resources:
****************************************/
resource ‘MENU’ (1001) {
 1001,
 textMenuProc,
 $7FFFFFFB,
 enabled,
 apple,
 { /* array: 3 elements */
 /* [1] */
 “About Window...”, noicon,
 “”, “”, plain,
 /* [2] */
 “About Attic Software...”,
 noicon, “”, “”, plain,
 /* [3] */
 “-”, noIcon, “”, “”, plain
 }
};
resource ‘MENU’ (1002, preload) {
 1002,
 textMenuProc,
 0x7FFFFFFB,
 enabled,
 “File”,
 { /* array: 4 elements */
 /* [1] */
 “New”, noIcon, “N”, “”, plain,
 /* [2] */
 “Close”, noIcon, “K”, “”, plain,
 /* [3] */
 “-”, noIcon, “”, “”, plain,
 /* [4] */
 “Quit”, noIcon, “Q”, “”, plain
 }
};
resource ‘MENU’ (1003, preload) {
 1003,
 textMenuProc,
 0x7FFFFFFD,
 enabled,
 “Edit”,
 { /* array: 6 elements */
 /* [1] */
 “Undo”, noIcon, “Z”, “”, plain,
 /* [2] */
 “-”, noIcon, “”, “”, plain,
 /* [3] */
 “Cut”, noIcon, “X”, “”, plain,
 /* [4] */
 “Copy”, noIcon, “C”, “”, plain,
 /* [5] */
 “Paste”, noIcon, “V”, “”, plain,
 /* [6] */
 “Clear”, noIcon, “”, “”, plain
 }
};
resource ‘MENU’ (1004, preload) {
 1004,
 1001,
 0x7FFFFFFC,
 enabled,
 “Window”,
 { /* array: 2 elements */
 /* [1] */
 “Zoom Front Window”, noIcon,
 “W”, “”, plain,
 /* [2] */
 “-”, noIcon, “”, “”, plain
 }
};
/***********************
Picture resources
************************************/
resource ‘PICT’ (1001, “About”, 
purgeable) {1157,
 {7, 7, 306, 497},
 $”1101 A000 82A0 008C”
 $”0100 0A00 0700 0701"
 $”3201 F10A 0000 0000"
 $”0000 0000 0B00 1B00"
 $”1B44 0009 0009 0130"
 $”01EF 0700 0200 0248"
 $”A100 9600 0606 0000"
 $”0002 03A1 009A 0008"
 $”FFFD 0000 00D4 0000"
 $”A000 9803 0003 0D00"
 $”0A28 0128 0024 22A9"
 $”2031 3938 3820 6279"
 $”2043 6C69 6666 6F72"
 $”6420 5374 6F72 7920"
 $”616E 6420 4174 7469"
 $”6329 AC22 2053 6F66"
 $”7477 6172 652C 2050"
 $”2E4F 2E20 426F 7820"
 $”3231 392C 2047 6F6C”
 $”6574 612C 2043 29A6"
 $”1161 6C69 666F 726E”
 $”6961 2020 2039 3331"
 $”3136 A000 99A0 0097"
 $”A100 9600 0605 0000"
 $”0002 03A1 009A 0008"
 $”004C 0000 00D8 0000"
 $”A000 980D 000C 2800"
 $”5200 2222 5769 6E64"
 $”6F77 2064 656D 6F6E”
 $”7374 7261 7465 7320"
 $”6120 6479 6E61 6D69"
 $”6320 5769 6E64 29ED”
 $”1D6F 7720 6D65 6E75"
 $”2E20 2045 6163 6820"
 $”7469 6D65 2061 2077"
 $”696E 646F 770D A000"
 $”99A1 009A 0008 003C”
 $”0000 00D8 0000 A000"
 $”9828 0062 0022 2269"
 $”7320 6F70 656E 6564"
 $”206F 7220 636C 6F73"
 $”6564 2C20 6F72 2074"
 $”6865 2077 696E 646F”
 $”7729 D81C 206F 7264"
 $”6572 2063 6861 6E67"
 $”6573 2C20 7468 6520"
 $”6D65 6E75 2069 730D”
 $”A000 99A1 009A 0008"
 $”002C 0000 00D8 0000"
 $”A000 9828 0072 0022"
 $”2261 7574 6F6D 6174"
 $”6963 616C 6C79 2075"
 $”7064 6174 6564 2074"
 $”6F20 7265 666C 6563"
 $”7420 7429 D61E 6865"
 $”206E 6577 2077 696E”
 $”646F 7720 6F72 6465"
 $”722C 2077 6974 6820"
 $”7468 650D A000 99A1"
 $”009A 0008 001C 0000"
 $”00D8 0000 A000 9828"
 $”0082 0022 2274 6F70"
 $”2077 696E 646F 7720"
 $”6174 2074 6865 2074"
 $”6F70 206F 6620 7468"
 $”6520 6D65 6E75 2C29"
 $”DA22 2061 6E64 2073"
 $”6F20 6F6E 2E20 2054"
 $”6869 7320 6973 2061"
 $”6C6C 2064 6F6E 6520"
 $”6279 2074 29C4 0368"
 $”650D A000 99A1 009A”
 $”0008 000C 0000 00D8"
 $”0000 A000 9828 0092"
 $”0022 224D 4445 463B”
 $”2074 6865 2070 726F”
 $”6772 616D 2069 7473"
 $”656C 6620 6E65 6564"
 $”206E 6F74 2029 D61E”
 $”6265 2063 6F6E 6365"
 $”726E 6564 2077 6974"
 $”6820 6D61 696E 7461"
 $”696E 696E 670D A000"
 $”99A1 009A 0008 FFFC”
 $”0000 00D8 0000 A000"
 $”9828 00A2 0022 0A74"
 $”6865 206D 656E 752E”
 $”0DA0 0099 A100 9A00"
 $”08FF EC00 0000 D800"
 $”00A0 0098 2A10 010D”
 $”A000 99A1 009A 0008"
 $”FFDC 0000 00D8 0000"
 $”A000 982A 1022 596F”
 $”7520 6361 6E20 6164"
 $”6420 7468 6973 206D”
 $”656E 7520 746F 2079"
 $”6F75 7220 6F77 6E20"
 $”29D9 1F70 726F 6772"
 $”616D 7320 6279 2073"
 $”696D 706C 7920 636F”
 $”7079 696E 6720 7468"
 $”650D A000 99A1 009A”
 $”0008 FFCC 0000 00D8"
 $”0000 A000 9828 00D2"
 $”0022 224D 4445 4620"
 $”7265 736F 7572 6365"
 $”2077 6974 6820 5265"
 $”7365 6469 742E 2020"
 $”416E 2061 7229 D921"
 $”7469 636C 6520 6F6E”
 $”2074 6869 7320 7072"
 $”6F67 7261 6D2C 2069"
 $”6E63 6C75 6469 6E67"
 $”0DA0 0099 A100 9A00"
 $”08FF BC00 0000 D800"
 $”00A0 0098 2800 E200"
 $”2222 636F 6D70 6C65"
 $”7465 2050 6173 6361"
 $”6C20 736F 7572 6365"
 $”2063 6F64 652C 2068"
 $”6173 2062 29DB 1F65"
 $”656E 2073 7562 6D69"
 $”7474 6564 2074 6F20"
 $”4D61 6320 5475 746F”
 $”7220 2869 660D A000"
 $”99A1 009A 0008 FFAC”
 $”0000 00D8 0000 A000"
 $”9828 00F2 0022 2269"
 $”7420 646F 6573 6E27"
 $”7420 6765 7420 7075"
 $”626C 6973 6865 642C”
 $”2049 276C 6C20 7265"
 $”6C29 C218 6561 7365"
 $”2074 6865 2073 6F75"
 $”7263 6520 6D79 7365"
 $”6C66 292E A000 99A0"
 $”0097 A100 9600 0606"
 $”0000 0002 03A1 009A”
 $”0008 FFFA 0000 0025"
 $”0000 A000 9804 050D”
 $”0012 2800 2B00 D806"
 $”5769 6E64 6F77 A000"
 $”99A0 0097 A000 8DA0"
 $”0083 FF”
};
resource ‘PICT’ (1002, “Attic”, 
purgeable) {
 1125,
 {7, 7, 306, 497},
 $”1101 A000 82A0 008C”
 $”0100 0A00 0700 0701"
 $”3201 F10A 0000 0000"
 $”0000 0000 0B00 1B00"
 $”1B44 0009 0009 0130"
 $”01EF 0700 0200 0248"
 $”A100 9600 0606 0000"
 $”0002 03A1 009A 0008"
 $”FFFA 0000 004B 0000"
 $”A000 9803 0003 0405"
 $”0D00 122B BA2B 0E41"
 $”7474 6963 2053 6F66"
 $”7477 6172 65A0 0099"
 $”A000 97A1 0096 0006"
 $”0500 0000 0203 A100"
 $”9A00 0800 5C00 0000"
 $”E600 00A0 0098 0400"
 $”0D00 0C28 004D 001D”
 $”2241 7474 6963 2053"
 $”6F66 7477 6172 6520"
 $”6973 2061 2073 6D61"
 $”6C6C 204D 6163 696E”
 $”746F 7329 DC22 6820"
 $”7072 6F67 7261 6D6D”
 $”696E 6720 636F 6D70"
 $”616E 792E 2069 6E20"
 $”6275 7369 6E65 7373"
 $”29E2 010D A000 99A1"
 $”009A 0008 004C 0000"
 $”00E6 0000 A000 9828"
 $”005D 001D 2273 696E”
 $”6365 2031 3938 362E”
 $”2020 5765 2064 6F20"
 $”6120 7661 7269 6574"
 $”7920 6F66 2077 6F29"
 $”D722 726B 3B20 D249"
 $”6465 616C 696E 6572"
 $”D32C 2061 2073 6861"
 $”7265 7761 7265 206F”
 $”7574 6C69 29D1 056E”
 $”6572 2C0D A000 99A1"
 $”009A 0008 003C 0000"
 $”00E6 0000 A000 9828"
 $”006D 001D 2269 7320"
 $”6F75 7220 6265 7374"
 $”2D6B 6E6F 776E 2070"
 $”726F 6475 6374 2E20"
 $”2057 6520 616C 7329"
 $”D81B 6F20 646F 2063"
 $”6F6E 7472 6163 7420"
 $”7072 6F67 7261 6D6D”
 $”696E 672E 0DA0 0099"
 $”A100 9A00 0800 2C00"
 $”0000 E600 00A0 0098"
 $”2800 7D00 1D01 0DA0"
 $”0099 A100 9A00 0800"
 $”1C00 0000 E600 00A0"
 $”0098 2A10 1E57 6520"
 $”6361 6E20 6265 2072"
 $”6561 6368 6564 2062"
 $”7920 6D61 696C 2061"
 $”743A 0DA0 0099 A100"
 $”9A00 0800 0C00 0000"
 $”E600 00A0 0098 2A10"
 $”010D A000 99A1 009A”
 $”0008 FFFC 0000 00E6"
 $”0000 A000 982A 1022"
 $”2020 2020 2020 2020"
 $”2020 2020 2020 2020"
 $”2020 2020 2020 2020"
 $”2020 2020 2020 2041"
 $”7474 2991 0C69 6320"
 $”536F 6674 7761 7265"
 $”0DA0 0099 A100 9A00"
 $”08FF EC00 0000 E600"
 $”00A0 0098 2800 BD00"
 $”1D22 2020 2020 2020"
 $”2020 2020 2020 2020"
 $”2020 2020 2020 2020"
 $”2020 2020 2020 2020"
 $”2050 2E4F 298F 0A2E”
 $”2042 6F78 2032 3139"
 $”0DA0 0099 A100 9A00"
 $”08FF DC00 0000 E600"
 $”00A0 0098 2800 CD00"
 $”1D22 2020 2020 2020"
 $”2020 2020 2020 2020"
 $”2020 2020 2020 2020"
 $”2020 2020 2020 2020"
 $”2047 6F6C 2990 1865"
 $”7461 2C20 4361 6C69"
 $”666F 726E 6961 2020"
 $”2039 3331 3136 0DA0"
 $”0099 A100 9A00 08FF”
 $”CC00 0000 E600 00A0"
 $”0098 2800 DD00 1D01"
 $”0DA0 0099 A100 9A00"
 $”08FF BC00 0000 E600"
 $”00A0 0098 2A10 2257"
 $”6520 616C 736F 206F”
 $”7065 7261 7465 2061"
 $”2062 756C 6C65 7469"
 $”6E20 626F 6172 6420"
 $”7329 D422 7973 7465"
 $”6D20 6174 2028 3830"
 $”3529 2036 3833 2D30"
 $”3332 322C 2062 6574"
 $”7765 656E 2074 29E6"
 $”0368 650D A000 99A1"
 $”009A 0008 FFAC 0000"
 $”00E6 0000 A000 9828"
 $”00FD 001D 2268 6F75"
 $”7273 206F 6620 363A”
 $”3030 2050 4D20 616E”
 $”6420 323A 3030 2041"
 $”4D2C 2050 6163 6929"
 $”D822 6669 6320 7469"
 $”6D65 2C20 7365 7665"
 $”6E20 6461 7973 2061"
 $”2077 6565 6B2E 2020"
 $”506C 6561 29D3 0373"
 $”650D A000 99A1 009A”
 $”0008 FF9C 0000 00E6"
 $”0000 A000 9828 010D”
 $”001D 1566 6565 6C20"
 $”6672 6565 2074 6F20"
 $”6361 6C6C 2069 6E21"
 $”A000 99A0 0097 A100"
 $”9600 0606 0000 0002"
 $”03A1 009A 0008 FFFD”
 $”0000 006D 0000 A000"
 $”980D 000A 2B73 1722"
 $”A920 3139 3838 2062"
 $”7920 436C 6966 666F”
 $”7264 2053 746F 7279"
 $”2061 6E64 2041 7474"
 $”6963 29AC 0920 536F”
 $”6674 7761 7265 A000"
 $”99A0 0097 A000 8DA0"
 $”0083 FF”
};
/**************************
Alert resource
****************************************/
resource ‘ALRT’ (1001,
 “Message”, purgeable) {
 {0, 0, 122, 300},
 1001,
 { /* array: 4 elements */
 /* [1] */
 OK, visible, sound1,
 /* [2] */
 OK, visible, sound1,
 /* [3] */
 OK, visible, sound1,
 /* [4] */
 OK, visible, sound1
 }
};
/****************************
Item list resource
****************************************/
resource ‘DITL’ (1001,
 “Message”, purgeable) {
 { /* array DITLarray: 2 elements */
 /* [1] */
 {92, 120, 112, 180},
 Button {
 enabled,
 “OK”
 },
 /* [2] */
 {10, 10, 74, 290},
 StaticText {
 disabled,
 “Sorry!  This program”
 “ can open only 18 wi”
 “ndows at one time (i”
 “f it opened more, th”
 “e menu would be too “
 “long for the screen)”
 “.”
 }
 }
};
/****************************
Window resources
*************************************/
resource ‘WIND’ (1001, purgeable) {
 {0, 0, 200, 320},
 documentProc,
 invisible,
 goaway,
 0x0,
 “untitled”
};
resource ‘WIND’ (1002, purgeable) {
 {0, 0, 200, 320},
 zoomDocProc,
 invisible,
 -1,
 0x0,
 “untitled”
};
/****************************
Multifinder resource
****************************************/
resource ‘SIZE’ (-1) {
 saveScreen,
 acceptSuspendResumeEvents,
 enableOptionSwitch,
 cannotBackground,
 MultiFinderAware,
 98304,
 98304
};
/***************************************/

Some Standard Declarations

This first bit of code is a simple unit that I include in all my stuff these days. Every time I use a new low-memory global, I stick it in here. I keep the unit in the “PInterfaces” folder, so I can use it easily in any program.

(****************************************
Common.p
Declarations for
 dynamic Window menu demo.
(c) 1988, by Clifford Story
 & Attic Software
****************************************)
unit Common;
(***************************************)
interface
(****************************************
Key codes:
****************************************)
const
 enterkey = 3;
 backspace= 8;
 tabkey = 9;
 returnkey= 13;
 clearkey = 27;
 leftarrow= 28;
 rightarrow =  29;
 uparrow= 30;
 downarrow= 31;
 periodkey= 46;

(****************************************
Dialog items:
****************************************)
 themask= 3;
(****************************************
Low-memory globals:
****************************************)
 applscratch=  $A78;
 bootdrive= $210;
 curappname =  $910;
 curdirstore=  $398;
 currenta5= $904;
 findername =  $2E0;
 fsfcblen = $3F6;
 grayrgn= $9EE;
 iaznotify= $33C;
 mbarheight =  $BAA;
 menuflash= $A24;
 resload= $A5E;
 rom85  = $28E;
 sfsavedisk =  $214;
 sysmap = $A58;
 windowlist =  $9D6;

(****************************************
Standard types:
****************************************)

type
 logical= boolean;
 long   = longint;
 
 shortpointer    = ^integer;
 longpointer=  ^long;

(***************************************)
end.
(***************************************)

The Menu Definition

An MDEF is called to do three things: draw the menu, hit-test the mouse location, and calculate the size of the menu rectangle.

Drawing the Menu

The menu has two parts: the first two items, which are fixed, and the list of open windows. I’ve hard-coded the first two items, since I know what they are. Note that if the “Zoom Front Window” item is disabled, then it is grayed out by drawing a gray rectangle over it.

To draw the list of open windows, I start with the window whose pointer is stored in the low-memory global “windowlist”. The windows form a chain, with each window record including a pointer to the next one; the final window has a nil pointer in the “nextwindow” field. So I walk down this list, looking for windows to add to the menu. I’m only going to show application windows, since DA windows may not have titles, and of course I’ll omit hidden windows.

Hit-Testing the Mouse

This is simple, since each menu item is 16 pixels high - just divide the distance from the top of the menu by 16 (result: a number in the range from 0 to the number of menu items minus one) and add one (since the first item is item one, not item zero).

There’s a small complication: if the item is disabled, then the routine should return zero (no choice).

The “whichitem” argument is a var parameter that initially contains the value of the last choice. If the newly-calculated item is the same as the last choice, then nothing has changed, so there’s nothing else to do. If it is different, then the old item should be un-highlighted and the new highlighted.

Calculating the Menu Size

This routine must compute the width and height of the menu rectangle and update the menu record accordingly. The approach here is similar to that of the draw routine: hard-code the fixed items and walk the window list for the rest, looking for the widest menu item.

Note the formulae by which the width of an item is calculated. I worked these out with screen shots and fatbits, and they (with the corresponding formula in the draw routine) will produce a menu identical to one created with the standard MDEF.

(****************************************
Window Menu.p
MDEF for dynamic Window menu demo.
(c) 1988, by Clifford Story
 & Attic Software
****************************************)

unit WDEF;

(***************************************)
interface
(***************************************)

uses memtypes, quickdraw, osintf,
 toolintf, Common;

(***************************************)
procedure menudef(message : integer; themenu : MenuHandle;
 var menurect : Rect; hitpoint : Point; var whichitem : integer);
(***************************************)
implementation
(***************************************)

type
 QDrecord = record
 randSeed : long;
 screenBits :  BitMap;
 arrow  : Cursor;
 dkGray : Pattern;
 ltGray : Pattern;
 gray   : Pattern;
 black  : Pattern;
 white  : Pattern;
 thePort: GrafPtr;
 end;
 QDpointer= ^QDrecord;

(***************************************)

procedure menudraw(themenu : MenuHandle; var menurect : Rect); forward;
procedure menuchoose(themenu : MenuHandle; var menurect : Rect;
 hitpoint : Point; var whichitem : integer); forward;
procedure menusize(themenu : MenuHandle); forward;

(***************************************)
{$R-}
{$SC+}
(***************************************)
procedure menudef(message : integer; themenu : MenuHandle;
 var menurect : Rect; hitpoint : Point; var whichitem : integer);
begin
 case message of
 mDrawMsg : menudraw(themenu, menurect);
 mChooseMsg : menuchoose(themenu, menurect,
 hitpoint, whichitem);
 mSizeMsg : menusize(themenu);
 end;
end;

(**************************************)
function QDglobals : QDpointer;
var
 thepointer :  longpointer;
begin
 thepointer := longpointer(currenta5);
 thepointer := longpointer(thepointer^);
 QDglobals := QDpointer(long(thepointer^)
 - sizeof(QDrecord) + sizeof(GrafPtr));
end;

(***************************************)
procedure menudraw(themenu: MenuHandle; var menurect: Rect);
var
 height : integer;
 width  : integer;
 therect: Rect;
 thewindow: WindowPeek;
begin
 height := menurect.top + 12;
 width := menurect.left + 12;
 
 MoveTo(width, height);
 DrawString(‘Zoom Front Window’);
 MoveTo(menurect.right - CharWidth(‘W’) - 15, height);
 DrawChar(chr(17));
 DrawChar(‘W’);
 
 if not BitTst(@themenu^^.enableFlags, 30) then begin
 PenPat(QDglobals^.gray);
 PenMode(patBic);
 with menurect do
 SetRect(therect, left, top, right, top + 16);
 PaintRect(therect);
 PenNormal;
 end;
 
 height := height + 16;
 MoveTo(menurect.left, menurect.top + 24);
 Line(menurect.right - menurect.left, 0);
 
 thewindow := WindowPeek(longpointer(windowlist)^);
 
 while thewindow <> nil do
 with thewindow^ do begin
 if visible and (windowkind = userKind) then begin
 height := height + 16;
 MoveTo(width, height);
 DrawString(titlehandle^^);
 end;
 thewindow := nextwindow;
 end;
end;

(***************************************)
procedure menuchoose(themenu : MenuHandle; var menurect : Rect;
 hitpoint : Point; var whichitem : integer);
var
 theitem: integer;
 therect: Rect;
begin
 if PtInRect(hitpoint, menurect) then
 theitem := 1 + ((hitpoint.v - menurect.top) div 16)
 else
 theitem := 0;
 
 if not BitTst(@themenu^^.enableFlags, 31 - theitem) then
 theitem := 0;
 
 if theitem <> whichitem then begin
 therect := menurect;
 therect.bottom := therect.top + 16 * theitem;
 therect.top := therect.bottom - 16;
 InvertRect(therect);
 if whichitem > 0 then begin
 therect.bottom := menurect.top + 16 * whichitem;
 therect.top := therect.bottom - 16;
 InvertRect(therect);
 end;
 whichitem := theitem;
 end;
end;

(***************************************)
procedure menusize(themenu : MenuHandle);
var
 thewidth : integer;
 theheight: integer;
 thewindow: WindowPeek;
 newwidth : integer;
begin
 TextFont(systemFont);
 TextSize(12);
 TextFace([]);
 
 theheight := 32;
 thewidth := StringWidth(‘Zoom Front Window’) + CharWidth(‘W’) + 39;
 
 thewindow := WindowPeek(longpointer(windowlist)^);
 
 while thewindow <> nil do
 with thewindow^ do begin
 if visible and (windowkind = userKind) then begin
 theheight := theheight + 16;
 newwidth := StringWidth(titlehandle^^) + 16;
 if newwidth > thewidth then
 thewidth := newwidth;
 end;
 thewindow := nextwindow;
 end;
 
 themenu^^.menuHeight := theheight;
 themenu^^.menuWidth := thewidth;
end;

(***************************************)
end.
(***************************************)

The Main Program

This is, I think, a pretty straight-forward program, so I won’t say much about it except for four features: menu maintenance, opening and closing windows, zooming windows, and window selection.

Menu Maintenance

I thought I said there wouldn’t be any maintenance? Well, there is a little, but not very much.

When the length of the menu changes, I have to call CalcMenuSize to provoke the Menu Manager into calling the MDEF with an “mSizeMsg” message. If I don’t do this, either there will be a blank space at the bottom of the menu, or the last item will be missing.

The size of the menu changes whenever I open or close a window, so I call CalcMenuSize at these times.

Opening and Closing Windows

The Window menu doesn’t scroll; that limits it to 18 windows (20 items minus the zoom item and the dividing line). So, logically, I ought to limit the program to 18 windows as well.

If the program can open only 18 windows, there’s no reason to open them dynamically and clutter up the heap with a bunch of fixed objects (WindowRecords). So instead I’ll declare a global array of WindowRecords. I’ll just pass the address of one of these (instead of a nil pointer) for the wStorage argument of GetNewWindow.

Ah, but now I have to keep track of which records belong to open windows, and which are free. This is actually easy to do.

First, extend the WindowRecord with a couple of new fields (see the datarecord declaration below): an integer ID and an integer “pointer” to the next record. The ID is just the index of the record in the array, and the “next” field initially points to the next record in the array (with the last one -1). Then there’s another global, an integer called FREE, which initially points to the first record in the array.

What I’m doing is keeping a list of all the free WindowRecords. Initially, they’re all free and, sure enough, they’re all on the list. Each time I open a window, I pass the address of the Window-Record pointed to by FREE, and set FREE to that record’s “next” field. That pops it off the list. When I close a window, I add the WindowRecord back to the free list, by setting its “next” field to FREE, and FREE to its ID. If FREE is ever -1, then the list is empty, and I can’t open a window.

Zooming: When?

The first question is, when should I allow zooming?

Since I’m zooming with ZoomWindow (see my Corners article, April 1989, for another method that works on all ROMs), I don’t want to allow zooming on the 64K ROM. So I keep a global flag, OLDROM, to tell me if the program is running on the 64K ROM, and I keep the “Zoom Front Window” item disabled if it is.

Also, I only want to zoom application windows. (I tried zooming the Alarm Clock, once, just to see what would happen...) So if the front window isn’t an application window, I disable the “Zoom Front Window” item. I do this in the “anactivate” routine, disabling on deactivates and enabling on activates.

Zooming: How?

The short answer to this is: with ZoomWindow. Unfortunately, ZoomWindow ain’t the smartest trap in the world, or maybe it’s just to subtle for me. The problem is this “inZoomIn”, “inZoomOut” nonsense. If you send it an “inZoomIn” part code, and it thinks the window is already zoomed in, it will do nothing. So I have to figure out when it thinks it’s zoomed in, and when it thinks it’s zoomed out. Maybe there’s an easy way; for my part, this is the last time I plan to use ZoomWindow.

This wouldn’t trouble me if I was only zooming from the zoom box, since FindWindow will tell me which part code to use. The problem arises because I’m also zooming from the menu.

What I decided to do is keep a flag in the reference constant of each window, setting it when the window is zoomed out, and clearing it otherwise. So when the time comes to zoom, I just look at the flag and zoom in if it’s set, and out if it’s cleared. This is done in the routine “zoomthewindow”.

Now I’ve got a flag to maintain. Besides the obvious in the “zoomthewindow” routine, I also must initialize the flag (in “donew”), and clear it if the window is dragged (in “clickindrag”) or re-sized from the grow box (in “clickingrow”).

Selecting a Window from the Menu

There is a little bit of work to do when a selection is made from the window list in the Window menu. After all, what the Menu Manager returns is an integer, not a WindowPtr. I have to walk the window list, just like in the MDEF, until I find the correct window, and then select it.

(****************************************
Window.p
Demo of a dynamic Window menu.
(c) 1988, by Clifford Story
 & Attic Software
****************************************)

program Window;

(***************************************)
uses memtypes, quickdraw, osintf,
 toolintf, packintf, Common;

(****************************************
Program constants:
****************************************)
const
 applenum = 1001;
 aboutitem= 1;
 atticitem= 2;

 filenum= 1002;
 newitem= 1;
 closeitem= 2;
 quititem = 4;

 editnum= 1003;
 undoitem = 1;
 cutitem= 3;
 copyitem = 4;
 pasteitem= 5;
 clear  = 6;
 
 windnum= 1004;
 zoomitem = 1;
 
 messagedialog   = 1001;
 
 windownum= 1001;
 zoomwindnum=  1002;
 
 hoffset= 32;
 voffset= 20;
 
(****************************************
Program types:
****************************************)
type
 
 datarecord =  record
 dummy  : WindowRecord;
 recordid : integer;
 next   : integer;
 end;
 datapointer=  ^datarecord;

(****************************************
Program variables:
****************************************)
var
 APPLEMENU: MenuHandle;
 FILEMENU : MenuHandle;
 EDITMENU : MenuHandle;
 WINDOWMENU :  MenuHandle;
 
 MENUHEIGHT :  integer;
 DRAGRECT : Rect;
 GROWRECT : Rect;
 SCREENRECT :  Rect;
 
 WINDOWS: array [0..17] of datarecord;
 FREE   : integer;

 OLDROM : logical;
 COLUMNS: integer;
 ROWS   : integer;
 WINDOWCOUNT:  integer;
 
 DONE   : logical;
 JEVENT : logical;
 MAINEVENT: EventRecord;
(***************************************)
procedure _datainit; external;
(***************************************)
{$R-}
{$SC+}
(***************************************)
procedure panic;
begin
 ExitToShell;
end;

(***************************************)
procedure centerdialog( thetype : OSType; theid : integer);
var
 thehandle: AlertTHndl;
begin
 thehandle := AlertTHndl(GetResource(thetype, theid));
 HLock(Handle(thehandle));
 with thehandle^^ do begin
 with boundsRect do
 SetRect(boundsRect, 0, 0, right - left, bottom - top);
 with screenBits.bounds,
 boundsRect.botright do
 OffsetRect(boundsRect, (right - left - h) div 2,
 (bottom - top - v + 2 * MENUHEIGHT) div 3);
 end;
 HUnlock(Handle(thehandle));
end;

(***************************************)
procedure initmac;
begin
 MaxApplZone;
 InitGraf(@thePort);
 InitFonts;
 InitWindows;
 InitCursor;
 InitMenus;
 TEInit;
 InitDialogs(@panic);
 UnloadSeg(@_datainit);
end;

(***************************************)
procedure setupmenus;
begin
 APPLEMENU := GetMenu(applenum);
 AddResMenu(APPLEMENU, ‘DRVR’);
 InsertMenu(APPLEMENU, 0);
 FILEMENU := GetMenu(filenum);
 InsertMenu(FILEMENU, 0);
 EDITMENU := GetMenu(editnum);
 InsertMenu(EDITMENU, 0);
 WINDOWMENU := GetMenu(windnum);
 InsertMenu(WINDOWMENU, 0);
 DrawMenuBar;
end;

(***************************************)
procedure initglobals;
var
 index  : integer;
begin
 for index := 1 to 10 do
 MoreMasters;
 
 OLDROM := BitTst(Ptr(rom85), 0);
 
 if OLDROM then
 MENUHEIGHT := 20
 else
 MENUHEIGHT := shortpointer(mbarheight)^;
 
 if GetResource(‘PACK’, 0) = nil then
 JEVENT := false
 else
 JEVENT := (NGetTrapAddress($A860, ToolTrap)
 <> NGetTrapAddress($A89F, ToolTrap));
 
 with screenBits.bounds do begin
 SetRect(DRAGRECT, left + 5, top + MENUHEIGHT + 5,
 right - 5, bottom - 25);
 SetRect(GROWRECT, 160, 100, right - left - 10, bottom
 - top - MENUHEIGHT - 10);
 SetRect(SCREENRECT, left + 5, top + MENUHEIGHT + 25,
 right - 5, bottom - 5);
 COLUMNS := 1 + ((right - left - 330) div hoffset);
 ROWS := 1 + ((bottom -top -MENUHEIGHT - 230) div voffset);
 end;
 for index := 0 to 17 do
 with WINDOWS[index] do begin
 recordid := index;
 next := index + 1;
 end;
 WINDOWS[17].next := -1;
 
 FREE := 0;
 WINDOWCOUNT := 0;
 DONE := false;
end;

(***************************************)
procedure clickapplemenu(theitem : integer);
var
 itemname : Str255;
 savedport: GrafPtr;
 dummy  : integer;
 newport: GrafPort;
 thepicture :  PicHandle;
 therect: Rect;
begin
 if theitem > 3 then begin
 GetItem(APPLEMENU, theitem, itemname);
 GetPort(savedport);
 dummy := OpenDeskAcc(itemname);
 SetPort(savedport);
 end else if theitem < 3 then begin
 InitCursor;
 GetPort(savedport);
 OpenPort(@newport);
 SetPort(@newport);
 thepicture:=PicHandle(GetResource(‘PICT’, 1000+ theitem));
 with thepicture^^.picFrame do
 SetRect(therect, 0, 0, right - left, bottom - top);
 with screenBits.bounds, therect.botright do
 OffsetRect(therect, (right - left - h) div 2,
 (bottom - top - v) div 3);
 DrawPicture(thepicture, therect);
 repeat until Button;
 ClosePort(@newport);
 EnableItem(EDITMENU, 0);
 DrawMenuBar;
 PaintBehind(WindowPeek(FrontWindow), RgnHandle(
 longpointer(grayrgn)^));
 SetPort(savedport);
 FlushEvents(everyEvent, 0);
 end;
end;

(***************************************)
procedure placewindow(thewindow : WindowPtr);
var
 left   : integer;
 top    : integer;
begin
 left := 5 + hoffset * (WINDOWCOUNT mod COLUMNS);
 top := 5 +MENUHEIGHT +voffset *(1 + (WINDOWCOUNT mod ROWS));
 MoveWindow(thewindow, left, top, true);
end;

(***************************************)
procedure donew;
label
 100;
var
 dummy  : integer;
 windowtype :  integer;
 thewindow: WindowPtr;
 thestring: Str255;
begin
 if FREE < 0 then begin
 InitCursor;
 centerdialog(‘ALRT’, messagedialog);
 dummy := Alert(messagedialog, nil);
 goto 100;
 end;
 
 if OLDROM then
 windowtype := windownum
 else
 windowtype := zoomwindnum;
 
 thewindow := GetNewWindow(windowtype,
 @WINDOWS[FREE], WindowPtr(-1));
 placewindow(thewindow);
 
 WINDOWCOUNT := WINDOWCOUNT + 1;
 NumToString(WINDOWCOUNT, thestring);
 SetWTitle(thewindow, concat(
 ‘Window Without a Title #’, thestring));
 
 ShowWindow(thewindow);
 
 CalcMenuSize(WINDOWMENU);
 
 FREE := WINDOWS[FREE].next;
100:  end;

(***************************************)
procedure doclose(thepeek : WindowPeek);
begin
 if thepeek^.windowkind < 0 then
 CloseDeskAcc(thepeek^.windowkind)
 else begin
 CloseWindow(WindowPtr(thepeek));
 with datapointer(thepeek)^
 do begin
 next := FREE;
 FREE := recordid;
 end;
 CalcMenuSize(WINDOWMENU);
 end;
end;

(***************************************)
procedure doquit;
var
 thelong: longpointer;
 thepeek: WindowPeek;
begin
 thelong := longpointer(windowlist);
 thepeek := WindowPeek(thelong^);
 while thepeek <> nil do begin
 doclose(thepeek);
 thepeek := thepeek^.nextwindow;
 end;
 DONE := true;
end;

(***************************************)
procedure clickfilemenu(theitem : integer);
begin
 case theitem of
 newitem: donew;
 closeitem: doclose(WindowPeek(FrontWindow));
 quititem : doquit;
 end;
end;

(***************************************)
procedure zoomthewindow(thewindow : WindowPtr);
begin
 if GetWRefCon(thewindow) = 0
 then begin
 ZoomWindow(thewindow, inZoomOut, true);
 SetWRefCon(thewindow, 1);
 end else begin
 ZoomWindow(thewindow, inZoomIn, true);
 SetWRefCon(thewindow, 0);
 end;
end;

(***************************************)
procedure clickwindowmenu(theitem : integer);
var
 thewindow: WindowPeek;
begin
 if theitem = 1 then
 zoomthewindow(FrontWindow)
 else if theitem > 2 then begin
 theitem := theitem - 2;
 thewindow := WindowPeek(longpointer(windowlist)^);
 while (not thewindow^.visible)
 or (thewindow^.windowkind <> userKind) do
 thewindow := thewindow^.nextwindow;
 while theitem > 1 do begin
 theitem := theitem - 1;
 repeat
 thewindow:= thewindow^.nextwindow;
 until thewindow^.visible and (thewindow^.windowkind = userKind);
 end;
 SelectWindow(WindowPtr(thewindow));
 end;
end;

(***************************************)
procedure checkmenu(thewindow : WindowPeek);
begin
 DisableItem(EDITMENU, 0);
 DisableItem(WINDOWMENU, zoomitem);
 if thewindow = nil then
 DisableItem(FILEMENU, closeitem)
 else begin
 if thewindow^.windowkind <> userKind then
 EnableItem(EDITMENU, 0)
 else if not OLDROM then
 EnableItem(WINDOWMENU, zoomitem);
 EnableItem(FILEMENU, closeitem);
 end;
end;

(***************************************)
procedure clickinmenu;
var
 choice : long;
begin
 checkmenu(WindowPeek(FrontWindow));
 choice := MenuSelect(MAINEVENT.where);
 case HiWord(choice) of
 applenum : clickapplemenu(LoWord(choice));
 filenum: clickfilemenu(LoWord(choice));
 editnum: if SystemEdit(LoWord(choice) - 1) then;
 windnum: clickwindowmenu(LoWord(choice));
 end;
 HiliteMenu(0);
end;

(***************************************)
procedure clickindrag(thewindow : WindowPtr);
begin
 DragWindow(thewindow,
 MAINEVENT.where, DRAGRECT);
 SetWRefCon(thewindow, 0);
end;

(***************************************)
procedure clickingrow(thewindow : WindowPtr);
var
 newsize: long;
begin
 newsize := GrowWindow(thewindow, MAINEVENT.where, GROWRECT);
 if newsize <> 0 then begin
 InvalRect(thewindow^.portRect);
SizeWindow(thewindow, LoWord(newsize), HiWord(newsize), true);
 SetWRefCon(thewindow, 0);
 end;
end;

(***************************************)
procedure clickingoaway(thewindow : WindowPtr);
begin
 if TrackGoAway(thewindow, MAINEVENT.where) then
 doclose(WindowPeek(thewindow));
end;

(***************************************)
procedure clickinzoom(thewindow : WindowPtr; thezoom : integer);
begin
 if TrackBox(thewindow, MAINEVENT.where, thezoom) then
 zoomthewindow(thewindow);
end;

(***************************************)
procedure aclick;
var
 location : integer;
 thewindow: WindowPtr;
begin
 location := FindWindow(MAINEVENT.where, thewindow);
 case location of
 inDesk : SysBeep(1);
 inMenuBar: clickinmenu;
 inSysWindow: SystemClick(MAINEVENT,thewindow);
 inContent: SelectWindow(thewindow);
 inDrag : clickindrag(thewindow);
 inGrow : clickingrow(thewindow);
 inGoAway : clickingoaway(thewindow);
 inZoomIn : clickinzoom(thewindow, inZoomIn);
 inZoomOut: clickinzoom(thewindow, inZoomOut);
 end;
end;

(***************************************)
procedure akey;
var
 charcode : integer;
 choice : long;
begin
 if BitAnd(MAINEVENT.modifiers, cmdKey) <> 0 then begin
 charcode := BitAnd(MAINEVENT.message, charCodeMask);
 checkmenu(WindowPeek(FrontWindow));
 choice := MenuKey(chr(charcode));
 if choice <> 0 then begin
 case HiWord(choice) of
 applenum : clickapplemenu(LoWord(choice));
 filenum: clickfilemenu(LoWord(choice));
 editnum: if SystemEdit(LoWord(choice) - 1) then;
 windnum: clickwindowmenu(LoWord(choice));
 end;
 HiliteMenu(0);
 end;
 end;
end;

(***************************************)
procedure anactivate(thewindow : WindowPtr);
var
 savedport: GrafPtr;
begin
 SetPort(thewindow);
 DrawGrowIcon(thewindow);
end;

(***************************************)
procedure anupdate(thewindow : WindowPtr);
var
 savedport: GrafPtr;
begin
 GetPort(savedport);
 SetPort(thewindow);
 BeginUpdate(thewindow);
 ClipRect(thewindow^.portRect);
 EraseRect(thewindow^.portRect);
 DrawGrowIcon(thewindow);
 EndUpdate(thewindow);
 SetPort(savedport);
end;

(***************************************)
procedure mainloop;
var
 dummy  : logical;
begin
 repeat
 if JEVENT then
 dummy := waitnextevent(everyEvent, MAINEVENT,
 GetCaretTime, nil)
 else begin
 SystemTask;
 dummy := GetNextEvent(everyEvent, MAINEVENT);
 end;
 if dummy then begin
 case MAINEVENT.what of
 mouseDown: aclick;
 keyDown: akey;
 activateEvt:
 anactivate(WindowPtr(MAINEVENT.message));
 updateEvt:
 anupdate(WindowPtr(MAINEVENT.message));
 end;
 end;
 until DONE;
end;

(***************************************)
begin
 initmac;
 setupmenus;
 initglobals;
 mainloop;
end.

 
AAPL
$112.00
Apple Inc.
-0.65
MSFT
$47.70
Microsoft Corpora
+0.18
GOOG
$507.91
Google Inc.
-3.19

MacTech Search:
Community Search:

Software Updates via MacUpdate

calibre 2.13 - Complete e-library manage...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
Mellel 3.3.7 - Powerful word processor w...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
ScreenFlow 5.0.1 - Create screen recordi...
Save 10% with the exclusive MacUpdate coupon code: AFMacUpdate10 Buy now! ScreenFlow is powerful, easy-to-use screencasting software for the Mac. With ScreenFlow you can record the contents of your... Read more
Simon 4.0 - Monitor changes and crashes...
Simon monitors websites and alerts you of crashes and changes. Select pages to monitor, choose your alert options, and customize your settings. Simon does the rest. Keep a watchful eye on your... Read more
BBEdit 11.0.2 - Powerful text and HTML e...
BBEdit is the leading professional HTML and text editor for the Mac. Specifically crafted in response to the needs of Web authors and software developers, this award-winning product provides a... Read more
ExpanDrive 4.2.1 - Access cloud storage...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
Adobe After Effects CC 2014 13.2 - Creat...
After Effects CC 2014 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous After Effects customer). After Effects CS6 is still available... Read more
Evernote 6.0.5 - Create searchable notes...
Evernote allows you to easily capture information in any environment using whatever device or platform you find most convenient, and makes this information accessible and searchable at anytime, from... Read more
Command-C 1.1.7 - Clipboard sharing tool...
Command-C is a revolutionary app which makes easy to share your clipboard between iOS and OS X using your local WiFi network, even if the app is not currently opened. Copy anything (text, pictures,... Read more
Tidy Up 4.0.2 - Find duplicate files and...
Tidy Up is a complete duplicate finder and disk-tidiness utility. With Tidy Up you can search for duplicate files and packages by the owner application, content, type, creator, extension, time... Read more

Latest Forum Discussions

See All

A Bunch of Halfbrick Games Are Going Fre...
A Bunch of Halfbrick Games Are Going Free for the Holidays Posted by Ellis Spice on December 19th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Shift - Photo Filters Designed By You (...
Shift - Photo Filters Designed By You 1.0 Device: iOS Universal Category: Photography Price: $.99, Version: 1.0 (iTunes) Description: | Read more »
Elastic Drums (Music)
Elastic Drums 1.0 Device: iOS iPhone Category: Music Price: $3.99, Version: 1.0 (iTunes) Description: *** Introduction price 3,99$ instead of 7,99$ *** Elastic Drums is a music app with 6 channels of synthesized drum sounds, a step... | Read more »
Fireworks Simulator (Games)
Fireworks Simulator 1.0.8 Device: iOS Universal Category: Games Price: $.99, Version: 1.0.8 (iTunes) Description: *** 50% discount – For a short time only *** You can play Fireworks Simulator on these devices: - iPhone 5, 5s, 5c, 6,... | Read more »
Nicky's Gift (Games)
Nicky's Gift 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: Everybody! Merry Christmas! There's 48 levels in the game. Let's go! Nicky's Gift | Read more »
The Hit List — Simply Powerful Tasks, To...
The Hit List — Simply Powerful Tasks, To-Dos, Projects, & Reminders 2.0 Device: iOS iPhone Category: Productivity Price: $9.99, Version: 2.0 (iTunes) Description: >> LAUNCH SPECIAL: The Hit List 2 for iPhone is ONLY $9.99... | Read more »
Mahjong Journey Review
Mahjong Journey Review By Jennifer Allen on December 18th, 2014 Our Rating: :: STEADY MATCHINGiPad Only App - Designed for the iPad Aimed at the more laid back gamer, Mahjong Journey isn’t for everyone, but those looking for some... | Read more »
Emoji Type - custom keyboard with predic...
Emoji Type - custom keyboard with predictive emojis 0.4.0 Device: iOS iPhone Category: Utilities Price: $.99, Version: 0.4.0 (iTunes) Description: Emoji Type is custom keyboard for iOS 8 that auto suggests emojis as you type. ABOUT... | Read more »
Game of the Year 2014 – 148Apps Staff Pi...
The end of 2014 is almost here, which can only mean one thing. Okay it can mean a lot of things, but in this specific context it means Game of the Year lists! Which is why the 148Apps staff have all picked their favorites from the past year. And why... | Read more »
UponPixels Review
UponPixels Review By Jennifer Allen on December 18th, 2014 Our Rating: :: CREATIVE TYPOGRAPHYUniversal App - Designed for iPhone and iPad Add cool typography and objects to your photos with the easy to use UponPixels.   | Read more »

Price Scanner via MacPrices.net

Holiday sales this weekend: MacBook Pros for...
 B&H Photo has new MacBook Pros on sale for up to $300 off MSRP as part of their Holiday pricing. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina MacBook Pro: $1699... Read more
Holiday sales this weekend: MacBook Airs for...
B&H Photo has 2014 MacBook Airs on sale for up to $120 off MSRP, for a limited time, for the Thanksgiving/Christmas Holiday shopping season. Shipping is free, and B&H charges NY sales tax... Read more
Holiday sales this weekend: iMacs for up to $...
B&H Photo has 21″ and 27″ iMacs on sale for up to $200 off MSRP including free shipping plus NY sales tax only. B&H will also include a free copy of Parallels Desktop software: - 21″ 1.4GHz... Read more
Holiday sales this weekend: Mac minis availab...
B&H Photo has new 2014 Mac minis on sale for up to $80 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 1.4GHz Mac mini: $459 $40 off MSRP - 2.6GHz Mac mini: $629 $70 off MSRP... Read more
Holiday sales this weekend: Mac Pros for up t...
B&H Photo has Mac Pros on sale for up to $500 off MSRP. Shipping is free, and B&H charges sales tax in NY only: - 3.7GHz 4-core Mac Pro: $2599, $400 off MSRP - 3.5GHz 6-core Mac Pro: $3499, $... Read more
Save up to $400 on MacBooks with Apple Certif...
The Apple Store has Apple Certified Refurbished 2014 MacBook Pros and MacBook Airs available for up to $400 off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
Save up to $300 on Macs, $30 on iPads with Ap...
Purchase a new Mac or iPad at The Apple Store for Education and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free,... Read more
iOS and Android OS Targeted by Man-in-the-Mid...
Cloud services security provider Akamai Technologies, Inc. has released, through the company’s Prolexic Security Engineering & Research Team (PLXsert), a new cybersecurity threat advisory. The... Read more
KMI MIDI K-Board Great Gift for Amateur &...
The K-Board is a MIDI Nano keyboard for music creation for iPad, Android, And computers; the easiest way to make music with iPads & Android tablets, and Mac, Windows, or Linux computers. Ultra-... Read more
Amazon offers 15-inch 2.2GHz Retina MacBook P...
 Amazon.com has the 15″ 2.2GHz Retina MacBook Pro on sale for $1699 including free shipping. Their price is $300 off MSRP. Stock is limited, so act now if you’re interested. Read more

Jobs Board

Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.