The Highest Point in Tennessee

I enjoy cycling. Long time readers might recall that a long term goal of mine is to cycle to the highest point (well, highest paved road) in every U.S. state. The first one I checked off the list was Tennessee, four years ago today.

Living most of my life in flat, flat central Illinois I have a special appreciation for mountains. The first mountain I rode was Mt. Tam in California, in 2004. I rode it again in 2005, at which point I was hooked. I needed more mountains, but I had none near home. So I started looking around for places where I could take trips to ride mountains.

Having spent every summer during college working in central Appalachia I was somewhat familiar with this mountain range, plus it’s an easy day’s drive to get there. But where to go. If I was going to take a special trip I wanted some big, big mountains. The two biggest are Mt. Mitchell (6,684 feet, the highest point in North Carolina, near Asheville, also the highest point east of the Mississippi river), and nearby Clingman’s Dome (6,643 feet, on the TN/NC border, the highest point in Tennessee).

Iris packed for vacation

What started out as kind of pie-in-the-sky wishful thinking quickly turned into a week-long family vacation in August 2005 (i.e. best vacation ever®). We packed the Insight full of camping gear, food, clothes, and two (yes, two) bikes and headed to the Great Smoky Mountains National Park.

Camping at Great Smoky Mountains National Park

We camped in the park (just outside of Gatlinburg, TN) the first night and woke up bright and early, ready to tackle Clingman’s Dome: me on the bike, my wonderful wife driving the support vehicle. The ascent was 20 miles long and rose around 5,000 ft. The road wasn’t terribly steep (4-5%) since it was in the national park and it was built so RV’s could make it up.

View of Gatlinburg, TN

The first mile or two was very easy. I was cruising along at 16-17 mph thinking this will be a piece of cake. The gradient increased gradually until I was closer to 8-10 mph, where I would remain for most of the climb. I didn’t know what to expect with such a long climb (twice as long as the only other mountain I had climbed), and since I have a double chainring I switched to a mountain bike cassette with a 32-tooth cog before the ride. I could have gotten by comfortably with a 28. My regular road cassette only had a 25. This would have been usable, but much more difficult.

Clingman's dome

The first 12.5 miles on Newfound Gap Road had just a little bit of traffic, but it wasn’t bad. All the cars were going very slowly anyway, so my presence didn’t seem to cause any problems.

Newfound Gap

At Newfound Gap I stopped to get new water bottles from Melissa, then turned off onto Clingman’s Dome Road for the final 7.5 miles. There was very little traffic on this road. Aside from a short downhill this road was also a little bit steeper.

Rob at top of Clingman's Dome

What had been foggy and overcast weather all morning turned into a light drizzle at the summit of Clingman’s Dome. Fortunately, we made another trip back there a few days later for a better view from the observation tower at the top.

View from Clingman's Dome

Appalachian Trail at Clingman's Dome

I chose to descend the other side of the mountain, into North Carolina, rather than go back down the way I came up. Like the other side, this side wasn’t terribly steep, but it did have some long straight sections that relatively safely allowed for high speeds.

Rob on Clingman's Dome descent

This was only the third time I had ridden a mountain, so my descending skills were a little lacking. Nonetheless I was able to get up to 48 mph or so on the descent.

Clingman's Dome descent

At the bottom Melissa picked me up and we drove off to the next campground where we would continue our great adventure. I really enjoyed the ride up to and back down from Clingman’s Dome. It was challenging, but not ridiculous (like Mauna Kea). It was a good warmup for Mt. Mitchell a few days later.

Clingmans Dome map

Clingmans Dome  profile

I had some GPS wonkiness that caused the square looking sections

Ride Information
Date: 2005-08-08 7:39 AM EDT
Mountain: Clingman’s Dome
Road Elevation: 6318 feet
Climb Distance: 20.0 miles
Climb Ascent: 4837 feet
Climb Average Grade: 4.8%
Climb Maximum Grade: ?
Ride Distance: 50.6 miles
Ride Total Ascent: 5895 feet
Ride Maximum Speed: 48 miles/hour
Ride Start: Elkmont Campground, GSMNP, TN (2285 feet)
Ride End: Oconaluftee Vistior Center, GSMNP, NC (2046 feet)

A I mentioned, we came back to Clingman’s dome a few days later and parked at Newfound Gap. From there Melissa & I rode together the last 7.5 miles to the top. Despite her fear, I think she really enjoyed the ride. Conquering that mountain was certainly difficult for her. She often mentions that experience in the same breath as other difficult moments in her life (i.e. marathon #1, marathon #2, marathon #3, etc.) that have helped her learn to persevere.

The PowerBook 5300cs

For many years I acquired (either inexpensively or freely) a lot of computer parts and other miscellaneous electronic equipment. I’ve been hanging on to these things just in case I ever needed to use them.

I never needed to use them.

A lot of this stuff was sitting around for years at our old house. Then it was put into storage when we moved to Nicaragua. Then it was moved into our new house. Close to two years later it still hasn’t been used. So it’s time we parted ways.

Today was the annual free electronics recycling event in Champaign-Urbana. Over the past few weeks I’ve been cleaning out my office so I could take this stuff to be recycled.

Box of electronics for recycling

The large box of items I took for recycling included:

  • 1 desktop computer (a Mac IIci)
  • 2 laptop computers (a PowerBook 5300cs and a PowerBook Duo 250)
  • 3 PDAs (including 1 smartphone)
  • 2 keyboards
  • 2 ISA-slot modem cards
  • numerous hard drives (all erased, one with a hammer)
  • more obsolete cables and connecters than you can imagine

You get the idea.

Of note, the box contained my first PDA (a Palm iii) and my first smartphone (a Samsung SPH-I300).

Samsung SPH-I300

The most sentimentally valuable obsolete item in the box was my old PowerBook 5300cs. This was my second computer and my first laptop. I got it in the fall of 1996 and used it regularly until the fall of 2000, when its duties were usurped by my work-issued PowerBook G3.

PowerBook 5300cs

Everyone has a laptop now, but in 1996 it was somewhat rare. I took it to most of my college classes to take notes (I can type way faster than I can write). Being the only person with a laptop in a lecture hall with hundreds of students meant that I stood out like a sore thumb, but I didn’t particularly care.

PowerBook 5300cs

One funny story about this laptop occurred in the summer of 1998. I was finishing up coursework for my Math 285 class (differential equations) while I was working for ASP in southeastern Kentucky. I must have tripped over the power cord or something, because the power connector inside the laptop broke off from the motherboard. I had course work that had to be completed (using Mathematica, on my laptop) by a certain date… and I had about two hours of battery left and no way to recharge the battery.

I used what precious little battery power I had left to connect my external modem to the internet via a long distance phone call. I shopped online for 20-30 minutes until I found a replacement part that would solve my dilemma (an expansion bay power supply). I ordered it and received it a few days later via FedEx. I completed my work and received an A in the class.

This computer and I have been through a lot together.

The College for Kids

During the summers when I was growing up in Danville, the local community college put on courses covering a variety of topics for, well, kids. It was aptly named College for Kids. The courses counted for no credit, just pure educational enrichment. As you can imagine, they weren’t wildly popular, but there were a few highly motivated youngsters who showed up ready to learn.

One summer I took an arts & crafts class. The summer between 5th & 6th grade I took a photography class. I don’t really remember why I thought I was interested in photography. Perhaps it had something to do with the camera shop down the street from my grandparents’ house. I would walk down there and look at all the neat equipment for hours. The course was taught by a photographer at the local newspaper.

The first day of class I showed up with my mom’s fixed-focus point-and-shoot 35mm Vivitar camera. The instructor gave each of the students a roll of film and assigned homework to take a bunch of photographs, enough to fill the roll, before the next class. He would develop them all (for free), then we would start learning what we could do to take better photos. Each class we picked up a new roll of film and turned in our previous role. In these days, long before the advent of the digital camera, film and development were somewhat tedious and expensive, so this was the first time I really had the opportunity to waste film by taking whatever types of pictures I wanted.

The class was amazing. We learned about framing & lighting. We learned how to operate really nice cameras. We even learned how to develop black & white film (color film being more difficult to develop). I was hooked. A few weeks after the class ended I took $120 I earned from my job as official scorekeeper at the little league park all summer, walked to the camera store down the street from my grandparents’ house, and purchased a used Vivitar SLR camera body and a 50mm prime lens. A couple months later I saved up some more money and purchased the matching 135mm telephoto lens. Nothing on this camera was automatic. It required manual focusing, aperture, shudder speed. It even required manually winding the film between photos. It was actually somewhat difficult to operate, but it was worth it.

I loved that camera. I used it to take photographs for school yearbooks and newspapers. I photographed family gatherings and sporting events. I took the camera with me to my job at ASP the summer after my freshman year in college. I used it all summer long to preserve many wonderful memories. Unfortunately, that’s where the story ends. At the end of the summer I packed up the camera (and all my other stuff) in one of our vans. We stopped at a few work sites in a different county to help out for a few days. I never saw the camera again. I still don’t know what happened to it. My best guess is that somebody stole it, though I can’t rule out the possibility I simply misplaced it somewhere.

I replaced my beloved Vivitar with a cheap point-and-shoot camera. The photographs weren’t as good, but it was much cheaper and easier to operate than a SLR. In 2001 I replaced it with my first digital point-and-shoot camera, which was amazing (I took 240 photos while hiking on the first day of our honeymoon). It wasn’t until 2003 that I finally got another SLR camera, the Digital Rebel.

The Marathon

The 2008 Chicago Marathon was yesterday. A number of acquaintances from the local Second Wind Running Club ran it, along with 40,000 others. Seven years ago I ran the Chicago Marathon. It was one of the most miserable experiences of my life.

To put things into perspective, I used to be a very good runner. I’m not going to say I’m bad now, but I used to be much, much better. My freshman year of college, when I was running cross country and track at the University of Illinois, I did a 20 mile training run in around 2h06m (6:20-ish pace) and it hardly phased me. The following day I did 5 x 1 mile intervals at 5:00 pace. If I had run a marathon when I was in this peak condition I probably could have done it in 2h45m or so.

I continued running even after my collegiate career ended. I ran relatively well, but I still foolishly held myself to the same standards as I did at my peak, so I was never really satisfied with my performances. My last year of college my friend Mike Perry decided to run the Chicago Marathon and I trained with him a couple times each week. He ran really well, finishing in a time of 2h55m.

Shortly after that I got a job. I worked insane hours for the first 6-8 months. I ran almost every day, but I was no longer walking 4 miles to and from class each day. I ran less. I sat more. I gained weight. By early summer 2001 I had had enough. I decided to run the Chicago Marathon. I roughly followed a training plan I found on the internet that lasted 14-16 weeks or so. I worked my way back up to running 17-18 miles.

As the race approached, I thought I was ready. People would ask how fast I thought I was going to run but I didn’t have an answer. I had no idea. I now realize this was a bad sign, but at the time I didn’t think too much of it. I thought I was going to cruise through this race, just like 100 other races I had previously run. I didn’t have the respect for the marathon distance that it deserves. And it bit me in the ass.

The morning of the race I was so nervous I couldn’t eat anything. I ate nothing. My stomach felt like it was filled with vomiting butterflies. I put an enormous amount of pressure on myself. After all, this was my marathon debut.

I started fast. The first 8 miles I cruised, exactly as I intended. One third of the way through the race I was on pace to run 3h00m. By mile 10 my stomach was growling (a lot). At mile 14 I ate a banana at an aid station, but it was too late. I stopped to walk for the first time. I was barely half way through the 26.2 mile race and I had bonked.

By mile 16 my hamstrings started to cramp up. I couldn’t run farther than a quarter mile before having to stop to stretch out. The last 10 miles of the race were truly miserable, and they lasted an eternity. I had to stop in the middle of the finishing straight in front of the huge crowd to stretch out a cramp in my calf. The calf cramped again about 10 meters from the finish but I couldn’t stop at that point, so I hopped the last 10 meters across the line. I finished in 4h18m, about an hour slower than my (somewhat unrealistic) worst case scenario estimate. I now have a much greater respect for the distance.

Rob, hobbling back to car, beaten

I ran a much faster time (3h50m) in my second marathon (which I ran in a snowstorm), but the experience was almost as miserable as the first. I have also run two 28.4 mile ultramarathons since then, both of which went much, much better. If my knees hold out I will probably run another road marathon next April at the inaugural Illinois Marathon. I think I’m due for a good one (i.e. enjoyable) someday.

The Kiss

I was just going through a box of old photos when I came across my all time favorite, the kiss. Melissa lived in Paris her junior year of college and I went to visit her over the semester break.

This photo was taken January 1, 2000. The previous night we celebrated l’an 2000 at the Arc de Triomphe, before walking 12 miles across town back to her dorm. On January 1st we watched a parade (I seem to recall a number of French police officers on rollerblades). Following the parade we walked to the Eiffel Tower and took this photograph. I framed the photo with Melissa in front of the tower. I then rested the camera precariously on a fence post, set the timer, and ran over to her. As I heard the shutter click I knew we had a keeper. We developed the film at a French camera shop so the prints have thin white borders all the way around (as is the style there).

A few months later two straight male friends of mine from ASP saw this photo and decided to imitate it when they backpacked through Europe together. Unfortunately, I don’t have a copy of it, but it was pretty funny.

The Falls

My body still on Central Daylight Time, I woke up early last Sunday morning in Portland. My flight back to Champaign (by way of Chicago O’Hare) didn’t leave until 1 p.m. so I had a few hours to do some touristy things. My first thought was to check out downtown Portland. I found some brochures in the hotel lobby, but unfortunately most places opened late on Sundays. Instead I packed up and checked out of the hotel at 7:30 am not really knowing what was going to come next. Sometimes this makes for great adventure, and other times it’s a big let down.

At first I did head to downtown Portland just to look around a little. I got caught up in quite a lot of traffic, both auto and pedestrian. I quickly realized everyone was headed to the Portland Race for the Cure. I pressed on a few blocks when I started thinking to myself, I should run the race. I was a little bit tired from the previous day’s triathlon, but it was only a 5K run and I can do that in my sleep (I think I actually did do that in my sleep a few times during college when we had 6:30 am practices). Then came the minutia of finding a place to park, finding the race start, dealing with registration (if it was even possible on race day), changing clothes, flying home sweaty and gross, etc. I quickly lost interest.

My next plan was to head out east of town on I-84 to the Columbia River Gorge and maybe Mt. Hood. It was drizzly and foggy all morning, so I never did see Mt. Hood. The river was nice though. The trip got interesting once I turned off the interstate onto a “scenic highway.”

I came to a waterfall along the side of the road and decided to go take a look at it. I didn’t really take anything with me other than my camera, because I thought I would get right back into the car. Then I realized there was a short trail up to the base of the waterfall.

Wahkeena Falls

Once I got there I realized there was another trail to the top of the waterfall.

There were 11 switchbacks on the way to the top

The view near the top of Wahkeena Falls

Once I got there I realized the trail kept going and going.

I hiked about 1.5 miles uphill before turning around and hiking another 1.5 miles back down to my car. The forest was very beautiful.

Thinking my adventure was probably done for the day I continued down the scenic highway just a scant half mile before coming across another waterfall and accompanying trail system. This waterfall was even more impressive (and correspondingly more touristy) than the first.

Multnomah Falls actually reminded me quite a lot of the waterfall in San Ramon on Ometepe island in Nicaragua.

Multnomah Falls is the second highest waterfall in the U.S.

Again I hiked up to the base of the waterfall for a closer view. Again I continued on to the top. There were a number of unprepared tourists hiking the “1 mile” trail up to the top, quite a few of whom I passed on the way up but not on the way down (i.e. they turned around). Hiking 1 mile isn’t very challenging. Hiking 1 mile that gains 600 ft of elevation is.

By this point I was running short on time so I was really moving up the hill. My legs weren’t really sore from the previous day’s triathlon, but the next day my legs would be quite sore from this hike.

The view from the top was spectacular. There was a little platform that went right out over the water and you could watch it falling off the cliff.

Amazing.

On the way out I noticed several other tourists at the falls wearing their USA Triathlon Age Group National Championship finisher’s jacket, just as I was. We smiled, nodded, and continued on our way.

I made my way back towards Portland. Along the way I stopped for a veggie burger at Burger King. My bike was still assembled so I packed it up into the suitcase in the BK parking lot. I arrived at the airport 90 minutes before my departure to find half of the airport wearing the race finisher’s jacket. What an adventure.

The Pizza Dilemma

Homer Simpson:I like pizza. I like bagels. I like hot dogs with mustard and beer. I’ll eat eggplant. I could even eat a baby deer. La la la la la la la la la la. Who’s that baby deer on the lawn

As some C4 attendees are complaining on Twitter, ordering the right kinds of pizza for a group of people presents challenges. There are people who like meat. There are people who like veggies. Some of the meat eaters won’t eat veggie pizza. Some of the veggie eaters won’t eat meat pizza.

In this particular case there was apparently an abundance of veggie pizza and not enough meat. That’s unfortunate for some. On the flip side, I cannot even begin to tell you how many times I’ve eaten pizza in a group who has ordered all meat pizzas, except for one cheese, and everybody goes straight for the cheese… leaving a hungry vegetarian and a bunch of uneaten meat pizza. This has happened to me literally dozens of times in the past 10 years (mostly in college).

The solution to the pizza dilemma is obvious, yet people often get it wrong. Order fewer meat pizzas and fewer veggie pizzas, while adding a boat load of plain cheese pizza. Sure, cheese may not be the favorite of some meat eaters or some veggie eaters, but they will eat it. Some may argue they won’t… but those people are wrong.

So instead of running out of meat pizzas and having meat eaters go away hungry, or (as is more often the case) running out of veggie pizzas and having the veggie eaters go away hungry, everyone will be able to eat cheese when their favorites run out and nobody goes away hungry.

The Masters Swim Team

Today was the first day of practice of the (school) year for the UIUC Masters Swim Team. The term masters generally means old people. In running this means age 40 and older. In cycling it’s 30 and older. In swimming it’s generally any adult (18 and older).

I began swimming with the masters team in the summer of 2002, shortly before my first triathlon. I had only started swimming a couple months earlier and I was looking to improve with some sort of coaching and training regimen. It worked. Even in my very first triathlon I was near the front of the pack out of the water.

I enjoy swimming, but not to the extent I enjoy running & bicycling. If it weren’t for triathlon I probably wouldn’t swim at all. There, I said it. It was really helpful for my motivation to have a coach, a schedule, and training partners waiting for me four times per week. That’s why I stuck with the masters team for the next couple years. When my knee injury sidelined my running, triathlon went along with it. I continued to swim for maybe a year or so, but as my knee continued to cause problems I eventually lost interest in swimming.

In the mean time, IMPE underwent renovations, closing both the indoor and outdoor pools. There was another pool on campus but it was much smaller and now had much more traffic, so it was pretty crowded.

After about four years off I started running again and it went well. Triathlon was the next natural progression for me, and that meant swimming. The pool situation was still less than ideal. I swam at the other pool on campus a couple times in the spring. In the summer I swam twice per week at the Crystal Lake Pool. By the end of August the renovations were completed at IMPE (now named ARC) and the outdoor pool was reopened.

Now it’s time for masters swimming again. The team meets for 90 minute practices 5 times per week (all optional). Some people train for actual races (swim meets, or in some cases triathlons), others train for fitness, others are there to socialize, and others are there simply to learn how to swim better.

Every once in a while the team organizes practice meets where the team members race each other (for time) at various distances. I participated in one of these meets about five years ago and targeted the 1000 yard race. Most swimmers are terrified of racing that far. I kind of wanted something longer. Interestingly I also participated in a 4×50 yard relay because pretty much everyone else did it. That was an experience.

time lapse video of Rob racing 1000 yards & Joe racing 1600 yards at a practice meet in 2003

The Reunion

Last weekend in Washington D.C. there was a reunion of former ASP summer staff members. I worked for the Appalachia Service Project during the summers of 1997-2000 while I was in college. Around 75 former staffers descended upon the nation’s capital (well, a number of them already live there) for festivities.

ASP Summer Staff 1998 (I’m the one in the bright yellow shirt)

After a 12 hour drive (and a one hour bike ride) I arrived at the Potter’s House bookstore and cafe for some live bluegrass music. As soon as I walked in I recognized four or five familiar friendly faces… and about 30 very unfamiliar ones. Has it been that long?

I scanned the store and noticed my former staffer Jill on the other side of the room. The way ASP works is there is a separate center in each of 20 or 25 counties throughout central Appalachia. Each center is run by four staffers. Jill & I worked together at the same center in the summer of 1999. Great, I thought, I’ll go say hello.

Jill & Rob in 1999

As I walked about half way across the room I took another look, then I stopped in my tracks. Wait a minute… that’s not Jill… is it? She looks like she hasn’t aged at all. My confusion was further compounded when she made eye contact with me and showed absolutely no sign of recognition. This was a person I worked with very closely for several months, what’s going on???

While I was standing there, not knowing what to do, Phoebe walked by and I asked her if that was Jill. No. It was Jill’s younger sister, who looks exactly like Jill, and who apparently also worked for ASP. Well, that solves that mystery. The real Jill was present at the banquet on Saturday night. We had a good chance to catch up a little. She is finishing up with medical school and getting married soon. ¡Felicitaciones!

After the long drive and a few hours in the coffee house I was really ready for bed. Will works in D.C. for CEDC, a nonprofit that has dormitories, and he arranged for me (and a few others) to stay there fairly inexpensively. It was nice, and the price couldn’t be beat. I slept in a little Saturday morning before heading downtown to see the monuments.

In the afternoon, the event organizers planned a service project at the Capital Area Food Bank. Around 20 or so of us from ASP helped unbox bulk canned foods and reorganize and repackage them for distribution to individuals and food kitchens. It was a good time. I used the opportunity to get to know some of the younger people who were on staff in the years following me. Interestingly, a few of them already knew who I was. You get bonus points if you can guess how — The Rob Song.

After finishing up at the food bank I got ready and headed over to the banquet on the other side of town. During my cross town trip I encountered four roundabouts (traffic circles). Fantastic. Anyone who knows me knows that I love roundabouts. I believe they are the greatest traffic control device in existence. The problem is that D.C. has the worst roundabouts I’ve ever seen (in the U.S., Nicaragua, France, or Belgium). Typically, traffic entering the roundabout has to yield to traffic already in the roundabout, but traffic in the roundabout should keep moving. Not only did these D.C. roundabouts have stop lights (rather than yield signs) upon entering the circle, they also had stoplights inside the circle itself. ¡Que terrible! So instead of none of the traffic stopping ever, all of the traffic stopped multiple times. This is how the first three roundabouts were. When I reached the fourth roundabout, it was normal (no stop lights, no stopping). Traffic moved so much more smoothly and safely through the fourth roundabout. I cannot possibly fathom why the first three were so messed up.

Once I got to the reunion banquet I saw many more familiar faces. There were a couple dozen people whose employment with ASP overlapped mine. One of the first people I saw was Ben, who immediately asked me “were you ever on stage during a Steve Jobs keynote?” That came out of nowhere. Yes, I was on stage during Steve Jobs’ 2005 WWDC keynote presentation. My boss (Theo) & I demonstrated Mathematica running on the just announced Intel based Mac computers. Ben watched the video of the keynote (here’s the relevant clip), recognized me, and remembered to ask me about it the next time he saw me (three years later). Funny.

Besides Jill, my only other former staffer (with whom I worked closely) at the reunion was Meryl (a.k.a. Marl, who was recently married). We too had a good chance to catch up with each other (though we didn’t get the chance to reenact our infamous, no holds barred wrestling matches).

Rob stuffs Meryl into a trash can in 2000

Meryl gets her revenge

Some poor, innocent bystander gets caught in the middle

The banquet was a really good time.

  • We ate Appalachian food (whatever that is).
  • We viewed a wonderful slideshow of old ASP photos.
  • A preselected representative of each decade (70’s, 80’s, 90’s, 00’s) shared some of their fondest ASP memories. Michelle K. (from my decade, the 90’s) did an exceptional job.
  • We sang a few of the songs we used to sing on a daily basis while working for ASP. It was a little emotional.
  • There was an auction of memorabilia to benefit ASP. I ended up with a really neat framed photograph of a curvy mountain road. I like mountains.
  • There was a contest to guess the number of skittles in a jar. Michelle R. & I both guessed 1200, which was the closest to the actual count of 1106. For this we won the skittles, which she yielded to me. Fortunately, the rest of the group helped pare that number down a little before we left.

After the official reunion ended, the unofficial reunion moved to some bar in an extremely busy area of D.C. I should have planned a little better, but I didn’t, and I ended up driving there. I searched for 25 minutes to find a parking place and I ended up in the tightest spot in which I’ve ever parked (and remember Iris is a very small car).

We all got a little carried away chatting it up with old friends. I ended up getting back to my quarters at 3:30 am. I haven’t stayed up that late in many a year. Juech was planning to run the Cherry Blossom 10 mile race at 7:30 am on Sunday. I since found out that he did indeed finish… barely. It was still probably before I even woke up.

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