The iPhone App

App

You may recall that I was slightly busy from shortly before Will was born until early October. Well, my big project was finally released yesterday. The Wolfram|Alpha iPhone application is now available in the iTunes app store.

The app has generated quite a lot of buzz over the past 24 hours, mostly because the price is significantly greater than the vast majority of iPhone applications. While I was intimately involved in the development of the application, I am completely in the dark about the business and marketing side of the product. On the bright side, most of the reviews speak relatively highly of the app itself, even though many are quite critical of the price.

Since I work primarily on Mathematica, I’ve been fairly uninvolved with the Wolfram|Alpha project prior to this iPhone app. I’m still not an expert on innards of Wolfram|Alpha but I do understand the big picture a little better than I did before.

1.0.0

If you’ve never used Wolfram|Alpha before, go ahead and give it a try on the website. It’s kind of hard to describe what it does, simply because it’s not like any other application you’ve ever used before. Despite certain visual similarities to web search engines like Google or Yahoo, Wolfram|Alpha is not a search engine. It doesn’t find web pages that might be related to your query, it computes factual answers to your query (except when it doesn’t).

Typically this means your query must be constructed in a slightly different way (perhaps using slightly different language) than you would use for a search engine. It’s worth taking the time to experiment to see what works and what doesn’t work. Perhaps my best description of Wolfram|Alpha is that it is a combination of a calculator and an encyclopedia.

The iPhone app features optimized input and output for the interesting and useful Wolfram|Alpha computation engine.

W|A knows all sorts of interesting facts. For instance, Robert was a more popular given name than William in the U.S. for most of the 20th century (though William recently overtook Robert… a sign of things to come?).

The app provides a number of ways to share the interesting results you find. Click the “share” button in the upper-right corner, or press and hold on a result.

The app also has numerous built-in examples to help you get started.

It also contains a complete history of all your queries.

Many of the computations have parameters that can be fine tuned for more precise results.

So there you have it. The app was a lot of fun to write, even if the release schedule was a bit hectic. The next version should be even better.

Late night coding

The WiFi Camera Remote

I recently picked up a cheap RF remote for my new Canon 50D.

Camera remote

Unfortunately, it arrived a little bit too late for the two family portraits I was enlisted to take over Thanksgiving. Additionally, the 50D doesn’t have an infrared remote sensor like my old 300D, so I couldn’t use my old infrared remote. My options were limited. I really didn’t want to have to set a 10 second timer then run into the picture, wait, pop, run back, repeat. Instead, I took advantage of the wireless networking capabilities of the attached WFT-E3 (wireless file transmitter) unit and I used my iPhone as a WiFi remote. It wasn’t ideal, but it definitely did the trick in a pinch.

This would have been much easier by connecting to an existing 802.11 network, but I could not rely on a network being present. So I instead chose to use an ad hoc direct wireless network connection between the camera and the phone. Additionally, since the iPhone doesn’t support creating an ad hoc network, I needed to setup the network on the camera first then connect to that network with the phone.

It wasn’t completely obvious how to get this setup working, so I will explain here in case anyone else out there on the internet ever finds themselves in a situation where something like this might come in handy. The required gadgets are:

  • Apple iPhone (or any mobile device with WiFi and a decent web browser)
  • Canon 50D or 40D (or probably 5D) camera
  • Canon WFT-E3 wireless file transfer unit (or probably the comparable 5D-compatible unit)

WFT Configuration

The first step is to configure the WFT to create an 802.11G Ad Hoc wireless network.

Under the WFT settings > Set up > LAN settings choose Set 2 (assuming Set 1 is your home network) and choose the Change button.

&nbsp&nbsp 

Under the WFT settings > Set up > LAN settings > Set 2 menu set the LAN type to Wireless.

Under the WFT settings > Set up > LAN settings > Set 2 > TCP/IP menu set:

  • IP address set to Manual setting
  • DNS server to Manual setting
  • DNS address to 0.0.0.0
  • IP address to 192.168.1.2
  • Subnet mask to 255.255.255.0
  • Gateway to 0.0.0.0
  • I just left Security tuned off for simplicity, but feel free to use WEP if you want

 

Under the WFT settings > Set up > LAN settings > Set 2 > Wireless LAN > SSID menu enter the name of the wireless network you are going to create. I called mine ragfield-50D.

  

Under the WFT settings > Set up > LAN settings > Set 2 > Wireless LAN > Advanced settings menu set:

  • Conn. method to Ad hoc 11g
  • Channel to Auto
  • Encryption to None

    

Now it is time to activate the WFT’s HTTP server. Under the WFT settings menu set Communication mode to HTTP.

In order to access the HTTP server we need to set up an account with a user name and password. Under the WFT settings > Set up > HTTP settings > HTTP account > User 1 set the Login name and Password to your desired login name and password.

     

At this point the green LAN indicator light on the back of the WFT should be blinking and we can now connect the iPhone (or other device) to the camera. Go ahead and take one last look at WFT settings > Set up > Confirm settings and scroll through the four screens to verify the settings are correct.

   

iPhone Configuration

The iPhone configuration is much simpler than the camera/WFT configuration, and the user interface is much better so this goes quickly. In the Settings application choose the Wi-Fi list item. A list of available wireless networks will be displayed and, if the WFT is configured correctly and running, the WFT’s network name will appear in this list. Choose this network (in my case ragfield-50D).

After the iPhone switches to your WFT’s ad hoc wireless network click the blue circle button to the right of your network’s name to open the TCP/IP settings. Configure the network by setting:

  • IP Address type to Static
  • IP Address to 192.168.1.3
  • Subnet Mask to 255.255.255.0
  • Router leave blank
  • DNS leave blank
  • Search Domains leave blank
  • HTTP Proxy to Off

Now the iPhone is correctly configured to connect to the camera/WFT. One interesting thing to note is that even though the iPhone is using WiFi to connect to the camera the WiFi icon does not appear in the status bar. A 3G or E(dge) icon will appear instead. This is fine.

Press the home button to exit the Settings application and tap on the Safari icon to launch the web browser. Type 192.186.1.2 in the address bar and press Go. Safari should prompt you for the user name and password you entered into the WFT settings > Set up &gt HTTP settings > HTTP account > User 1 menu. Enter these values and press Log In.

The WFT’s default web page will appear. Press the Capture button next to the image of the camera on the web page.

The button to do a remote capture is the little blue circle on the image of the wired remote control on the web page. Zoom in for a target large enough to hit with your finger while you’re looking at the camera.

 

It helps if the hand holding the iPhone is hidden behind another person. Thanks for covering for me Melissa!

The Raguet family

The Bassetts

The Geoffender

You read me my rights and then you said “Let’s go” and nothing more.

Blondie (as covered by The Mr. T Experience).

The Iron Coder competition from the recent C4[2] conference I attended had a required API (iPhone OS’s CoreLocation) and a theme (paranoia). I actually did take a couple hours on Sunday morning to throw together a submission. Sure, it wasn’t going to be polished, but still creative perhaps. I figured several people would do something like a crime map. I tried a variant on that, dealing only with one (particularly nasty) type of crime.

The app I threw together is quite simple. You press the “Geoffend” button. The app determines your location from the iPhone’s built in GPS. The app fetches from the internet and displays a list of registered sex offenders who live near your current location. If that doesn’t induce paranoia, I don’t know what will. I call it Geoffender (combining Geo with offender).

I got the app working in the iPhone simulator on my computer, but I ran into problems running the app on my actual iPhone hardware. I recently acquired a new iPhone, and I hadn’t yet set it up for development. When I tried to set it up before the contest I absolutely could not get it working. I tried everything. The iPhone platform is pretty well locked down. In order to do development you have to have various digital certificates and keys from Apple. I have these. The problem is installing them correctly is not completely straightforward. So the demo was a no go.

It’s just as well. There were many other submissions to the contest which were much better. I also learned a few things, so the time wasn’t wasted.

The Backscatter

Yesterday was mostly wasted. At 7:37 A.M., shortly before I intended to sit down to start working, I started receiving spam backscatter emails. What happened was some asshole, somewhere in the world, sent probably hundreds of thousands of spam emails with my email address forged in the From: line. Tens of thousands of the email addresses they spammed were invalid. When one sends email to an invalid address the mail server usually sends an automated reply informing you the address was invalid. Since my address was the From: address of these spams, I received automated responses to these spam emails yesterday. It continued all day. I received dozens per minute. All day.

I was completely overwhelmed and there was absolutely nothing I could do about it. Over the past few years I have received a few random backscatters. A couple months ago I received 8-10 in a single instance. A few weeks ago I received about 400 at once. Yesterday I received 15,673. The first several hundred went straight to my inbox. Eventually my junk mail filter started recognizing them and filtered thousands automatically, but there were still several hundred in my inbox. I literally spent all day dealing with this shit. There was at least one legitimate, work-related email that I completely missed because of this. I only learned about it by searching my junk mailbox after someone asked me about the missing email. Who knows how many other legitimate emails are mixed in with them? I am still receiving them, but thankfully they are becoming less frequent (maybe one per hour now).

What an asshole.

The Moose

Every year when I was growing up my grandpa (Grampy) would call me up on the morning of April 1st. Each year the story was the same. He would say there was a moose in my back yard and that I should run out there to see it. As a young child I fell for this–probably multiple times, as April 1st doesn’t come all that often. Eventually I recognized the pattern and refused to be made a fool, but that didn’t prevent him from calling anyway, and we all still had a good laugh.

A couple years ago I got him back. Good.

I copied the exact layout of a news item on CNN’s website and replaced the text and images with my own news story about a Bismarck, IL resident (Grampy) who found a moose in his back yard. I emailed a link to my fabricated story (click here to view) to several family members. Everyone had a good chuckle.

I wasn’t expecting this, but everyone who received the link to the story thought it was so funny they started to forward it to their friends and extended relatives, nearly all of whom were unfamiliar with the running joke. I’d say a good 95% of those people believed it, and Grampy started getting phone calls and emails from people who saw his story on CNN. He was left with some explaining to do.

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