February 14, 2008

The Heart Shaped Window

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();
}

5 comments:

Anonymous said...

so what did you do THIS YEAR???????? :) Don't want you to have to sleep outside with the snow person!!! happy V day... hugs, nan

gutzville said...

Hey I have been advertizing this as the non-technical Rob blah blog. Now you think you can just cram in a big chunk of code. Ughh

Yeah, I remember that, she was pissed. This year I got cara the most romantic gift ever... A memory chip :)

Cara said...

sir loves a lot, I wanted lord hugginton...oh well...

Yuhong Bao said...

I think this could easily be ported to Carbon.

Yuhong Bao said...

GetNewCWindow refers to a resource without for which the source would not be complete.