On our first Valentine’s Day Melissa was mad at me for some reason. So for our second Valentine’s Day I decided to make her a really special gift. I stayed up until about 2-3 a.m. (which was quite rare, as I like to get to bed early) the night before writing a computer program that made a heart shaped window with a customized message in it. With the state of software in 1998 this was actually a pretty challenging task, but I worked hard and made something I thought we could both be proud of.
When I showed it to Melissa the next day (2/14/1998) I didn’t quite get the reaction I was hoping for. Her attitude was basically that if I hadn’t spent so much time working on the program I would have had more time to spend with her. Sheesh.
Anyway, here’s what it looked like:
And here’s the source code (wow I used to write some really ugly code):
//
// HeartWDEF.c
//
#define HASGOAWAY 0
pascal long main(short, CWindowPtr, short, long);
long DoDraw(CWindowPtr, long);
long DoHit(CWindowPtr, long);
long DoCalcRgns(CWindowPtr, long);
void GetStrucRegion(CWindowPtr window, RgnHandle rgn);
void GetGoAwayBox(CWindowPtr w, Rect *r);
OSErr PictToRgn(PicHandle pic, RgnHandle rgn){
int width = 0, height = 0;
GrafPtr mask, savePort;
Rect r = (*pic)->picFrame;
OSErr err;
width = r.right - r.left;
height = r.bottom - r.top;
SetRect(&r, 0, 0, width, height);
//OffsetRect(&r, r.left, r.top);
mask = (GrafPtr) NewPtr(sizeof(GrafPort));
OpenPort(mask);
mask->portBits.bounds = r;
mask->portBits.rowBytes = ((width + 15) / 8);
mask->portBits.baseAddr = NewPtr(mask->portBits.rowBytes * height);
GetPort(&savePort);
SetPort(mask);
EraseRect(&r);
DrawPicture(pic, &r);
SetPort(savePort);
err = BitMapToRegion(rgn, &(mask->portBits));
DisposePtr((Ptr) mask->portBits.baseAddr);
DisposePtr((Ptr) mask);
return err;
}
pascal long main(short varCode, CWindowPtr w, short message, long param){
long val = 0;
switch(message){
case wDraw:
val = DoDraw(w, param);
break;
case wHit:
val = DoHit(w, param);
break;
case wCalcRgns:
val = DoCalcRgns(w, param);
break;
case wNew:
case wDispose:
break;
}
return val;
}
long DoDraw(CWindowPtr w, long param){
long val = 0;
WindowPeek wp;
Rect r;
RGBColor save;
PicHandle pict;
Point p;
WindowPtr window;
GWorldPtr saveWorld;
GDHandle device;
PenState savePen;
GetForeColor(&save);
GetPenState(&savePen);
PenNormal();
wp = (WindowPeek) w;
if(param == 0){
// copy the regions so we can offset them
RgnHandle diff = NewRgn();
DiffRgn(wp->strucRgn, wp->contRgn, diff);
ForeColor(redColor);
PaintRgn(diff);
PenNormal();
/*if(wp->hilited){
ForeColor(blackColor);
FrameRgn(diff);
}*/
DisposeRgn(diff);
#if HASGOAWAY
if(wp->hilited){
GetGoAwayBox(w, &r);
ForeColor(whiteColor);
PaintRect(&r);
}
#endif
}
#if HASGOAWAY
else if(param == wInGoAway){
GetGoAwayBox(w, &r);
InvertRect(&r);
}
#endif
SetPenState(&savePen);
RGBForeColor(&save);
return val;
}
long DoHit(CWindowPtr w, long param){
long val = 0;
WindowPeek wp;
Point p;
Rect r;
p.h = LoWord(param);
p.v = HiWord(param);
wp = (WindowPeek) w;
if(PtInRgn(p, wp->contRgn)) val = wInContent;
else if(PtInRgn(p, wp->strucRgn)){
val = wInDrag;
#if HASGOAWAY
GetGoAwayBox(w, &r);
if(PtInRect(p, &r)) val = wInGoAway;
#endif
}
else val = wNoHit;
return val;
}
long DoCalcRgns(CWindowPtr w, long param){
SInt32 val = 0;
WindowPeek wp;
PicHandle pic;
Rect r;
RgnHandle rgn;
wp = (WindowPeek) w;
r = w->portRect;
OffsetRect(&r, - ((WindowPtr)w)->portBits.bounds.left, - ((WindowPtr)w)->portBits.bounds.top);
rgn = NewRgn();
pic = GetPicture(4096);
PictToRgn(pic, rgn);
ReleaseResource((Handle) pic);
OffsetRgn(rgn, r.left, r.top);
CopyRgn(rgn, wp->strucRgn);
DisposeRgn(rgn);
rgn = NewRgn();
pic = GetPicture(4097);
PictToRgn(pic, rgn);
ReleaseResource((Handle) pic);
OffsetRgn(rgn, r.left, r.top);
CopyRgn(rgn, wp->contRgn);
DisposeRgn(rgn);
return val;
}
void GetGoAwayBox(CWindowPtr w, Rect *r){
WindowPeek wp = (WindowPeek) w;
*r = (*(wp->contRgn))->rgnBBox;
r->top -= 0;
r->bottom = r->top + 10;
r->left += 12;
r->right = r->left + 10;
}
//
// HeartWindow.c
//
#define kNumFields 7
typedef struct{
Str255 str[kNumFields];
} Message;
typedef Message* MessagePtr;
typedef MessagePtr* MessageHandle;
void DoUpdate(void);
Boolean quitting;
WindowPtr window;
Message message;
void LiveDragger(WindowPtr w, Point p, Rect *r){
Point mouse, newmouse, original, origin;
EventRecord e;
GrafPtr save;
EventAvail(mouseDown, &e);
if(!(e.modifiers & optionKey)){
DragWindow(w, p, r);
}
else{
GetPort(&save);
SetPort(w);
GetMouse(&mouse);
LocalToGlobal(&mouse);
SetPt(&origin, 0, 0);
LocalToGlobal(&origin);
original = origin;
SetPort(save);
while(StillDown()){
if(WaitNextEvent(updateMask, &e, 0, NULL)){
DoUpdate();
}
else{
GetPort(&save);
SetPort(w);
GetMouse(&newmouse);
LocalToGlobal(&newmouse);
if(newmouse.h != mouse.h || newmouse.v != mouse.v){
if(PtInRect(newmouse, r)){
MoveWindow(w, origin.h + newmouse.h - mouse.h, origin.v + newmouse.v - mouse.v, false);
SetPt(&origin,0,0);
LocalToGlobal(&origin);
mouse = newmouse;
}
else{
MoveWindow(w, original.h, original.v, false);
}
}
SetPort(save);
}
}
GetPort(&save);
while(w != nil){
SetPort(w);
InvalRect(&w->portRect);
w = (WindowPtr) ((WindowPeek) w)->nextWindow;
}
SetPort(save);
}
}
void GetItemHandle(DialogPtr d, short index, Handle *h){
short type;
Rect r;
GetDialogItem(d, index, &type, h, &r);
}
void EraseWindow(){
Rect r;
GrafPtr savePort;
GetPort(&savePort);
SetPort(window);
r = (*(((WindowPeek) window)->contRgn))->rgnBBox;
OffsetRect(&r, -r.left, -r.top);
EraseRect(&r);
InvalRect(&r);
SetPort(savePort);
}
void PStringCopy( ConstStr255Param srcString, Str255 destString ){
SInt16 index = StrLength( srcString );
do {
*destString++ = *srcString++;
} while ( --index >= 0 );
}
void CopyMessage(MessagePtr src, MessagePtr dst){
int i;
for(i = 0; i < kNumFields; i++) PStringCopy(src->str[i], dst->str[i]);
}
void LoadMessage(){
int i;
MessageHandle mh = (MessageHandle) GetResource('hwms', 128);
if(mh == nil){
for(i = 0; i < kNumFields; i++){
GetIndString(message.str[i], 128, i + 1);
}
}
else{
CopyMessage(*mh, &message);
ReleaseResource((Handle) mh);
}
}
void EditMessage(){
short item = 0, i;
Str255 str;
Handle h;
DialogPtr d = GetNewDialog(129, nil, (WindowPtr) -1);
SetDialogDefaultItem(d, 1);
SetDialogCancelItem(d, 2);
SetDialogTracksCursor(d, true);
for(i = 0; i < kNumFields; i++){
GetItemHandle(d, i + 3, &h);
SetDialogItemText(h, message.str[i]);
}
ShowWindow(d);
while(item != 1 && item != 2){
ModalDialog(nil, &item);
}
HideWindow(d);
// if the user hit ok
if(item == 1){
MessageHandle old;
// save the info to the app's resource fork
for(i = 0; i < kNumFields; i++){
GetItemHandle(d, i + 3, &h);
GetDialogItemText(h, message.str[i]);
}
old = (MessageHandle) GetResource('hwms', 128);
if(old == nil){
old = (MessageHandle) NewHandle(sizeof(Message));
if(old == nil){
StopAlert(130, nil);
}
else{
CopyMessage(&message, *old);
AddResource((Handle) old, 'hwms', 128, "\p");
ChangedResource((Handle) old);
ReleaseResource((Handle) old);
}
}
else{
CopyMessage(&message, *old);
WriteResource((Handle) old);
ChangedResource((Handle) old);
ReleaseResource((Handle) old);
}
EraseWindow();
}
DisposeDialog(d);
InitCursor();
}
void DoAbout(){
Alert(128, nil);
}
void DoAppleChoice(short item){
if(item == 1) DoAbout();
else{
Str255 daName;
GetMenuItemText(GetMenuHandle(128), item, daName);
OpenDeskAcc(daName);
}
}
void DoFileChoice(short item){
switch(item){
case 1:
// edit the text
EditMessage();
break;
default:
quitting = true;
break;
}
}
void DoEditChoice(short item){
}
void DoMenuChoice(long choice, EventModifiers modifiers){
short id, item;
id = HiWord(choice);
item = LoWord(choice);
switch(id){
case 128:
DoAppleChoice(item);
break;
case 129:
DoFileChoice(item);
break;
case 130:
DoEditChoice(item);
break;
}
HiliteMenu(0);
}
void DoDiskEvent(const EventRecord *e){
Point dialogCorner;
if((e->message >> 16 ) != noErr){
SetPt(&dialogCorner, 112, 80);
DIBadMount(dialogCorner, e->message);
}
}
void DoOSEvent(const EventRecord *e){
SInt16 osMessage;
WindowPtr w;
osMessage = (e->message & osEvtMessageMask) >> 24;
switch(osMessage){
case suspendResumeMessage:
break;
case mouseMovedMessage:
break;
}
}
void DoNullEvent(const EventRecord *e){
WindowPtr w;
}
void DrawCenteredString(Str255 str, short y){
MoveTo((328 - StringWidth(str))/2, y);
DrawString(str);
}
void DoUpdate(){
GrafPtr savePort;
RGBColor saveColor;
Rect r;
int size = 18, i, y = 116;
Str255 str;
BeginUpdate(window);
GetForeColor(&saveColor);
GetPort(&savePort);
SetPort(window);
ForeColor(redColor);
TextFont(applFont);
TextSize(size);
for(i = 0; i < kNumFields; i++, y += (size + 6)){
DrawCenteredString(message.str[i], y);
}
SetPort(savePort);
RGBForeColor(&saveColor);
EndUpdate(window);
}
void DoMouseDown(EventRecord *e){
WindowPtr w;
SInt16 part;
part = FindWindow(e->where, &w);
switch(part){
case inMenuBar:
DoMenuChoice(MenuSelect(e->where), e->modifiers);
break;
case inSysWindow:
SystemClick(e, w);
break;
case inContent:
//quitting = true;
break;
case inDrag:
//DragWindow(w, e->where, &qd.screenBits.bounds);
LiveDragger(w, e->where, &qd.screenBits.bounds);
break;
case inGoAway:
if(TrackGoAway(w, e->where)){
quitting = true;
}
break;
}
}
void DoKeyDown(const EventRecord *e){
SInt16 key;
key = (e->message & charCodeMask);
if(e->modifiers & cmdKey){
DoMenuChoice(MenuKey(key), e->modifiers);
}
}
void ProcessEvents(void){
EventRecord e;
if(WaitNextEvent(everyEvent, &e, 0, nil)){
SetCursor(&qd.arrow);
switch(e.what){
case nullEvent:
DoNullEvent(&e);
break;
case mouseDown:
DoMouseDown(&e);
break;
case keyDown:
case autoKey:
DoKeyDown(&e);
break;
case updateEvt:
DoUpdate();
case diskEvt:
DoDiskEvent(&e);
break;
case kHighLevelEvent:
AEProcessAppleEvent(&e);
break;
}
}
DoNullEvent(&e);
}
pascal OSErr HandleOpenDocument(const AppleEvent *ae, AppleEvent *reply, SInt32 refCon){
return noErr;
}
pascal OSErr HandleOpenApplication(const AppleEvent *ae, AppleEvent *reply, SInt32 refCon){
return noErr;
}
pascal OSErr HandleQuitApplication(const AppleEvent *ae, AppleEvent *reply, SInt32 refCon){
quitting = true;
return noErr;
}
OSErr InitEvents(void){
OSErr err;
if((err = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, NewAEEventHandlerProc(HandleOpenApplication), 0, false)) != noErr)
return err;
if((err = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, NewAEEventHandlerProc(HandleOpenDocument), 0, false)) != noErr)
return err;
if((err = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments, NewAEEventHandlerProc(HandleOpenDocument), 0, false)) != noErr)
return err;
if((err = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerProc(HandleQuitApplication), 0, false)) != noErr)
return err;
return noErr;
}
void CheckMachine(){
SysEnvRec thisMac;
SysEnvirons(7, &thisMac);
if(!thisMac.hasColorQD){
StopAlert(131, nil);
ExitToShell();
}
if(thisMac.systemVersion < 0x0700){
StopAlert(131, nil);
ExitToShell();
}
}
OSErr Initialize(void){
OSErr err;
short w1, w2, h1, h2;
Handle code;
MenuHandle menu;
InitGraf(&qd.thePort);
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs(nil);
InitCursor();
FlushEvents(everyEvent, 0);
MaxApplZone();
MoreMasters();
MoreMasters();
MoreMasters();
CheckMachine();
err = InitEvents();
if(err != noErr) return err;
LoadMessage(&message);
SetMenuBar(GetNewMBar(128));
menu = GetMenu(128);
AppendResMenu(menu, 'DRVR');
DrawMenuBar();
quitting = false;
window = GetNewCWindow(128, nil, (WindowPtr) -1);
code = GetResource('WDEF',4096);
if(code != nil) ((WindowPeek) window)->windowDefProc = code;
ShowWindow(window);
return noErr;
}
void Finalize(void){
HideWindow(window);
DisposeWindow(window);
}
void main(void){
if(Initialize() == noErr){
while(!quitting){
ProcessEvents();
}
}
Finalize();
}