Undefined Valuehttps://undefinedvalue.com/2023-04-08T13:12:37-04:00Kris Johnson's BlogSimpler Résumé Updates2023-04-08T13:12:37-04:002023-04-08T13:12:37-04:00Kristopher Johnsontag:undefinedvalue.com,2023-04-08:/simpler-résumé-updates.html<p>I decided to update my résumé this week. No, I haven't lost my job, and I'm not
actively looking for a new one, but a little while ago I asked people on
Mastodon how to acquire practical experience with "cloud stuff" if your job
doesn't provide it, and someone suggested …</p><p>I decided to update my résumé this week. No, I haven't lost my job, and I'm not
actively looking for a new one, but a little while ago I asked people on
Mastodon how to acquire practical experience with "cloud stuff" if your job
doesn't provide it, and someone suggested
<a href="https://cloudresumechallenge.dev">The Cloud Resume Challenge</a>. (Unfortunately
I can't find the post that suggested it, and can't remember who made it, but
thank you anonymous helpful Mastodon person.)</p>
<p>The challenge is to create a website that displays your résumé, but uses various
features of AWS, Azure, Google Cloud Platform, or whatever cloud provider you
want to explore. So, when you go looking for a cloud-related job, you have your
résumé online and you can talk to an interviewer about all the interesting stuff
you learned about deploying a website to the cloud with load balancing and
serverless code and whatever else you want to brag about.</p>
<p>I've started the learning process (and I've passed the AWS Certified Cloud
Practioner exam), but before I go further I decided I should probably have an
up-to-date résumé. For the past couple of decades I've had a résumé in multiple
formats (Word, PDF, RTF, etc.) and when I've needed to update it, I've had to
make edits to all of those files and then deploy them to all the places where it
is available for download.</p>
<p>Making similar small edits to a few files every few years isn't a lot of work,
but it does seem like the kind of thing I should automate for my fancy new
cloud-native résumé website deployment.</p>
<p>This turned out to be pretty easy. I started with the plain-text ("ASCII")
format of my résumé and converted it into
<a href="https://daringfireball.net/projects/markdown/syntax">Markdown</a> format by adding
a few formatting tags. The result of that process is visible here:
<a href="https://github.com/kristopherjohnson/resume/blob/main/src/kjresume.md">https://github.com/kristopherjohnson/resume/blob/main/src/kjresume.md</a>.</p>
<p>Then I set up a
<a href="https://github.com/kristopherjohnson/resume/blob/main/Makefile">Makefile</a> with
rules for using <a href="https://pandoc.org">Pandoc</a> to generate all the other formats
from that source file. So, whenever I need to update my résumé, I just edit
<code>kjresume.md</code> and then I can execute <code>make all</code> to generate all the other
formats, and then push that back up to my
<a href="https://github.com/kristopherjohnson/resume">https://github.com/kristopherjohnson/resume</a> repository.</p>
<p>Finally, I just have to update all the pages that have links to my résumé (for
example, the <a href="https://undefinedvalue.com/pages/kjresume.html">My Résumé</a> page on
this website) so that they point to the auto-updated files in my GitHub repo.</p>
<p>The best thing is now I don't have to install Microsoft Word again whenever I
want to update my résumé.</p>542021-01-17T17:38:33-05:002021-01-17T17:38:33-05:00Kristopher Johnsontag:undefinedvalue.com,2021-01-17:/54.html<p>Well, 2020 was quite a year. The COVID-19 pandemic, the killing of George Floyd and the subsequent protests, the Presidential election, the subsequent unfounded accusations of widespread election fraud, and incitement of an attack on the Capitol. Talking about how the year went for me seems incredibly self-centered, but this …</p><p>Well, 2020 was quite a year. The COVID-19 pandemic, the killing of George Floyd and the subsequent protests, the Presidential election, the subsequent unfounded accusations of widespread election fraud, and incitement of an attack on the Capitol. Talking about how the year went for me seems incredibly self-centered, but this is what i do every birthday.</p>
<p>Thankfully, my family and I have been lucky this year. My stepson lost his food-service job when the pandemic hit, and my wife has struggled to keep up with all the demands being made of teachers, but we're healthy and secure, as are our families and friends. </p>
<p>As I write this, Joe Biden is going to be inaugurated in a few days. We can hope for more competent and responsible leadership (it really can't get any worse). About 4,000 people are dying in the US each day. Vaccines are becoming available, but the government distribution has been yet another screwup.</p>
<p>Donald Trump has been impeached for a second time, due to his involvement in the attack on the Capitol, and the Senate will try him in the coming weeks. Polls show that 75% of Republicans don't believe Biden legitimately won the election. I don't know what will come in the next few months, but I think we can expect the partisanship of the past few decades to continue.</p>
<p>The company I worked for was acquired by another company at the end of October. For now, I still have my job and still get to work remotely, but it's unclear if they will still have a place for me after we integrate our product's best features into their product, and if they do it's unclear whether that's a place I will want to occupy. So this is one of those years where I really need to figure out what I want to do when I grow up.</p>
<p>I've completed my first semester in the online master's degree program at Georgia Tech. I failed badly at the time-management aspect of it, completing none of the projects on time, but I ended up with a B in the course. I've just started the Spring 2021 semester, taking two courses, and have committed myself to diligently stay ahead of schedule on all the coursework. If all goes well, I expect to get the degree at the end of 2022.</p>
<p>My wife bought me a nice <a href="https://en.wikipedia.org/wiki/Gibson_ES-335">ES-335</a> guitar for our anniversary, so I've started teaching myself jazz guitar. The last 40 years of trying to teach myself rock guitar hasn't gotten me very far, so I figured a new approach was needed. I've been pretty good about practicing on most days. I can play a lot of chords, arpeggios, and scales, but haven't really learned how to play any complete songs.</p>
<p>I let my hair grow long, for about 10 months, during the year. I thought I was developing a cool <a href="https://en.wikipedia.org/wiki/Snake_Plissken">Snake Plissken</a> look, but apparently I was the only one who felt that way. So a couple of weeks ago, I cut it myself. It's now short and spiky, and my wife and I both like how it looks. I think this will be my new hairstyle even after it's safe to get professional haircuts again.</p>
<p>My wife talked me into joining a bowling league. I was concerned about maintaining social distancing, but there are only eight people in the league, grouped into teams of two, and they skip every other lane, so it seems relatively safe.</p>
<p>I am hopeful that by the end of the year COVID will no longer be an issue. I'd like to start going to the gym again, and take Brazilian Jiu-Jitsu classes again. It would be really nice to spend Thanksgiving and Christmas with my parents.</p>532020-03-18T19:34:12-04:002020-03-18T19:34:12-04:00Kristopher Johnsontag:undefinedvalue.com,2020-03-18:/53.html<p>Another belated birthday post, and another reminder to myself that I really want to get into the habit of blogging often.</p>
<p>We moved into a house last year, after several years of renting. We like it. It is a little farther away from our jobs than we would like, but …</p><p>Another belated birthday post, and another reminder to myself that I really want to get into the habit of blogging often.</p>
<p>We moved into a house last year, after several years of renting. We like it. It is a little farther away from our jobs than we would like, but we got a good deal on it. We moved in a little too late to do springtime yard maintenance last year, so it's looking a little ragged, but this year we plan to clean up the pine island in front and make the backyard look nicer.</p>
<p>My job last year was more interesting than I would have liked. I was "promoted" to Team Lead after our former leader stepped down (and departed completely soon thereafter). What was supposed to be a short-term keep-everything-running position while we searched for a new software-development executive turned into six unpleasant months of trying to keep people around me happy and productive. I wasn't successful in that, but the position was eventually filled and I was able to settle back into an individual-contributor role.</p>
<p>Every other time I've been promoted to management at a job, I've quit soon thereafter, so maybe I'm maturing.</p>
<p>I'm applying for a Master of Science degree program at Georgia Tech, starting this fall. I've long wanted to return to school, and they offer an online program, so I can do it part-time while still keeping my job. I've enjoyed taking online courses in data science and machine learning, and I would like to try something more challenging and get something more significant than "certificates" for my ongoing studies.</p>
<p>I updated my ten-year-old <a href="menubar-countdown-21.html">Menubar Countdown</a> application and put it on the <a href="https://apps.apple.com/us/app/menubar-countdown/id1485343244?mt=12">Mac App Store</a> late last year. It's nice to see that two or three people download it every day and it's gotten some good reviews.</p>
<p>I lost some loved ones this year. My paternal grandmother passed away a few weeks after her 99th birthday, and we lost our little dog <a href="blitz.html">Blitz</a> to cancer.</p>
<p>Otherwise, my wife, stepson, and four other dogs are doing well, as are my parents and brothers and their families.</p>
<p>My wife is still bowling with her leagues and enjoying it a lot. I dropped out of my martial arts classes last spring when work got in the way too often, but I hope to resume that this year.</p>
<p>As I write this, the country has just started working-from-home and social-distancing due to the COVID-19 virus. I hope life will be back to normal, maybe a "new normal", when it's time to write my "54" post.</p>Menubar Countdown 2.12020-01-02T09:14:53-05:002020-01-02T09:14:53-05:00Kristopher Johnsontag:undefinedvalue.com,2020-01-02:/menubar-countdown-21.html<p>Way back in 2009, I released <a href="/menubar-countdown-10-mac-os-x-released.html">Menubar Countdown 1.0</a>. It's a simple app that displays a countdown timer in the Mac menu bar. I created it because I was experimenting with the <a href="https://en.wikipedia.org/wiki/Pomodoro_Technique">Pomodoro Technique</a> and I didn't like any of the other timer apps I tried.</p>
<p>I made a …</p><p>Way back in 2009, I released <a href="/menubar-countdown-10-mac-os-x-released.html">Menubar Countdown 1.0</a>. It's a simple app that displays a countdown timer in the Mac menu bar. I created it because I was experimenting with the <a href="https://en.wikipedia.org/wiki/Pomodoro_Technique">Pomodoro Technique</a> and I didn't like any of the other timer apps I tried.</p>
<p>I made a few updates in 2009, culminating with <a href="/menubar-countdown-12-released.html">Menubar Countdown 1.2</a> in June. And for a long time, that was it. It was simple, and I liked it that way. I'd occasionally get feature requests, but the suggested features didn't appeal to me.</p>
<p>Back in 2015, I did translate the code from Objective-C to Swift 2.1. There was no reason to do this other than to get some experience using Swift. I had a vague plan to submit it to the Mac App Store, but I just kept it to myself. The 2009 version was still working, and nobody was complaining.</p>
<p>That changed in early 2019. Menubar Countdown 1.2 is a 32-bit application, and <a href="https://github.com/kristopherjohnson/MenubarCountdown/issues/2">macOS Mojave warned</a> that 32-bit applications would not be supported in the next version of macOS.</p>
<p>"Hmm, I should probably do something about that," I thought. Then I forgot about it.</p>
<p>I was reminded in October 2019 when a user, John Cornell, emailed me to let me know he really wanted Menubar Countdown working on Catalina, so he had tried to update the code himself, but couldn't get it to work.</p>
<p>With a user motivated enough to try to fix it himself, I finally got off my virtual butt and made the necessary updates to get the Menubar Countdown 1.x codebase updated so that it would build with Xcode 11 and run on macOS Catalina. I released that version as <a href="https://github.com/kristopherjohnson/MenubarCountdown/releases/tag/1.3">Menubar Countdown 1.3</a>.</p>
<p>But that wasn't enough. With my hands in the code, I remembered all the changes I've thought about making over the past decade. I wanted to release it through the Mac App Store.</p>
<p>I did those things. The result is Menubar Countdown 2.1. You can now download the app from the Mac App Store at <a href="https://apps.apple.com/us/app/menubar-countdown/id1485343244?mt=12">https://apps.apple.com/us/app/menubar-countdown/id1485343244?mt=12</a>. If you don't like the Mac App Store, you can download a notarized build from <a href="https://github.com/kristopherjohnson/MenubarCountdown/releases/tag/2.1">https://github.com/kristopherjohnson/MenubarCountdown/releases/tag/2.1</a>.</p>
<p>It's still free and open-source, with a project page at <a href="https://github.com/kristopherjohnson/MenubarCountdown">https://github.com/kristopherjohnson/MenubarCountdown</a>. I've changed the license from <a href="https://opensource.org/licenses/lgpl-license">LGPL</a> to <a href="https://opensource.org/licenses/MIT">MIT-style</a>.</p>
<p>Now I hope I can ignore it for another decade.</p>LUNAR for C and Rust2019-09-21T10:23:54-04:002019-09-21T10:23:54-04:00Kristopher Johnsontag:undefinedvalue.com,2019-09-21:/lunar-for-c-and-rust.html<p>I've written before that my first exposure to computers was a simple text-based
"<a href="lunar-lander.html">Lunar Lander</a>" game.</p>
<p>Earlier this year I did some research
and I found the
<a href="http://www.cs.brandeis.edu/~storer/LunarLander/LunarLander/LunarLanderListing.jpg">original LUNAR source code</a>,
which is in the
<a href="https://en.wikipedia.org/wiki/FOCAL_(programming_language)">FOCAL</a>
programming language, and some
<a href="https://www.atariarchives.org/basicgames/showpage.php?page=106">translations to BASIC</a>.</p>
<p>I decided I needed to port this …</p><p>I've written before that my first exposure to computers was a simple text-based
"<a href="lunar-lander.html">Lunar Lander</a>" game.</p>
<p>Earlier this year I did some research
and I found the
<a href="http://www.cs.brandeis.edu/~storer/LunarLander/LunarLander/LunarLanderListing.jpg">original LUNAR source code</a>,
which is in the
<a href="https://en.wikipedia.org/wiki/FOCAL_(programming_language)">FOCAL</a>
programming language, and some
<a href="https://www.atariarchives.org/basicgames/showpage.php?page=106">translations to BASIC</a>.</p>
<p>I decided I needed to port this to a modern programming language like
<a href="https://en.wikipedia.org/wiki/C_(programming_language)">C</a>.</p>
<p>(Yes, that's intended ironically.)</p>
<p>The result is "LUNAR for C", which you can find on GitHub:
<a href="https://github.com/kristopherjohnson/lunar-c">kristopherjohnson/lunar-c</a>.</p>
<p>I also decided to port it to <a href="https://www.rust-lang.org">Rust</a>, which I was
exploring at the time. Porting to Rust was not straightforward, because the
original source code has <a href="https://en.wikipedia.org/wiki/Goto">goto-based</a>
control flow, and Rust doesn't have a <code>goto</code> statement. One way to deal with
this is to implement a simple
<a href="https://en.wikipedia.org/wiki/Finite-state_machine">state machine</a> in Rust, and
the result of that experiment is available in a Gist:
<a href="https://gist.github.com/kristopherjohnson/83c6a6b8a1b7c6929ced83e922abccc1">lunar.rs</a>.
But I also planned to do some machine-learning experiments to find the ideal
solution to the lunar-landing problem, so I created a very over-engineered
"LUNAR for Rust" version that is available on GitHub:
<a href="https://github.com/kristopherjohnson/lunar-rust">kristopherjohnson/lunar-rust</a>.</p>
<p>If you want to play the game yourself, and you have a C compiler, I recommend
building the C version.</p>
<p>Thank you to Jim Storer (the original author of LUNAR), to David H. Ahl (author
of the BASIC ports), and to every other programmer who has created a
lunar-landing game that I've played.</p>Blitz2019-05-14T19:04:41-04:002019-05-14T19:04:41-04:00Kristopher Johnsontag:undefinedvalue.com,2019-05-14:/blitz.html<p><img src="/files/blitz_sitting.jpg" alt="Blitz sitting on grass" height="320px" width="320px" style="float: right; margin-left: 8px;"></p>
<p>Last week we had to say goodbye to our nine-year-old Miniature Schnauzer, Blitz.</p>
<p>She was such a sweet, playful dog. She always had a confused-but-curious expression on her face, and big floppy ears. She always pranced when she walked around outside.</p>
<p>Many mornings, we would wrestle. She would pose in …</p><p><img src="/files/blitz_sitting.jpg" alt="Blitz sitting on grass" height="320px" width="320px" style="float: right; margin-left: 8px;"></p>
<p>Last week we had to say goodbye to our nine-year-old Miniature Schnauzer, Blitz.</p>
<p>She was such a sweet, playful dog. She always had a confused-but-curious expression on her face, and big floppy ears. She always pranced when she walked around outside.</p>
<p>Many mornings, we would wrestle. She would pose in front of me as if she was going to pounce, then I would lightly grab one of her hind legs, then she would playfully bite at my hand, and I'd flip her over onto her back, and she would spin herself back to her feet, and we'd do it again and again. Eventually she would get bored with that and run around the room, trying to get the other dogs to chase her.</p>
<p>She was an explorer. She often found her way out of our yard, and we would have to go around the neighborhood shouting her name until she decided to come home. She would dutifully follow us around, but if we put a leash on her she would just sit and refuse to move.</p>
<p><img src="/files/blitz_face.jpg" alt="Close-up of Blitz's face" height="320px" width="320px" style="float: right; margin-left: 8px;"></p>
<p>A couple of months ago, we noticed she was losing weight. We tried a few different foods, but she wouldn't eat much. We took her to the vet, who did blood tests and x-rays, but he couldn't identify the problem. He suggested a specialist, who did an ultrasound. He gave us the bad news: Blitz had tumors in her stomach and liver, and they were too large and widespread to be removed.</p>
<p>There were some treatments available that could have prolonged her life by a few months, but we didn't want to put her through that. At that point, she was still running around chasing squirrels and barking at birds, so we decided we would do what we could to keep her happy and comfortable as long as we could.</p>
<p>Our dogs are all around ten years old, so we knew we'd have some sad times coming up, but this happened too soon. I am grateful that we had a few weeks knowing what was coming so we spend all the time we could with her. We will miss her.</p>TSCP Translated to Rust2019-02-28T19:25:45-05:002019-02-28T19:25:45-05:00Kristopher Johnsontag:undefinedvalue.com,2019-02-28:/tscp-translated-to-rust.html<p>A couple of years ago, I wrote <a href="my-first-chess-program.html">My First Chess Program</a>. I wrote it in <a href="https://swift.org">Swift</a>. It was slow, due more to my lack of knowledge rather than due to any problems with Swift. While I was writing it, I decided not to study any other chess programs, but I …</p><p>A couple of years ago, I wrote <a href="my-first-chess-program.html">My First Chess Program</a>. I wrote it in <a href="https://swift.org">Swift</a>. It was slow, due more to my lack of knowledge rather than due to any problems with Swift. While I was writing it, I decided not to study any other chess programs, but I did actually sneak a peak at some code in <a href="http://www.tckerrigan.com/Chess/TSCP/">Tom Kerrigan's Simple Chess Program</a> (TSCP) to get an idea of what a better chess program might look like. At the time, I meant to eventually go back and study TSCP and incorporate its good ideas into my Swift chess engine.</p>
<p>But that was a couple of years ago. I'm not interested in Swift anymore (Swift is fine; it just doesn't interest me), but I have been learning <a href="https://www.rust-lang.org">Rust</a>, and I thought translating TSCP's <a href="https://en.wikipedia.org/wiki/C_(programming_language)">C</a> code to Rust would be a way to both learn more about Rust and to study how a better chess engine works.</p>
<p>The result is available here: <a href="https://github.com/kristopherjohnson/tscp-rust">https://github.com/kristopherjohnson/tscp-rust</a>. Tom Kerrigan has given me permission to distribute my translation, but note that he still holds the copyright, and any additional redistribution is under his terms.</p>
<p>A lot of Rust's syntax is inspired by C, so the guts of the program look very similar. I decided early to do as straightforward a translation as I could, without any re-design or changes to make it more "Rust-like". However, I also wanted to minimize the amount of <code>unsafe</code> code (in the Rust sense), so I did tinker with a couple of data structures, and moved the global variables into a <code>struct</code> that gets passed between functions.</p>
<p>During development, I added a lot of trace output to both the C and Rust code, so I could verify that the Rust code was evaluating identical positions and getting identical results at all stages of thinking about the next move. I found several transcription errors that I might not have found through simple testing, because the incorrect Rust program "worked" in the sense that it made valid moves, but it picked different moves to be best</p>
<p>The result has pretty good performance. The <code>bench</code> command for the Rust version runs in about 580 ms, whereas the same command in the C version runs in about 520 ms. I could probably improve the Rust performance, but I'm happy that it's in the same ballpark. </p>
<p>Embarrassing story: When I first contacted Tom Kerrigan about my Rust translation, I told him that the Rust version surprisingly ran a lot faster than the C version. He didn't believe me, but I was <em>sure</em> that I had built the C version with all optimizations enabled, and theorized that Rust's memory safety guarantees had somehow enabled optimizations that weren't available to a C compiler. It turned out that while the C Makefile did specify the <code>-O3</code> option for the compiler, the rule wasn't actually being applied when building the program. So when I fixed the Makefile so that the rule worked, the C code was faster, as expected. The good news there is that I was able to contribute the fixed Makefile back to the main TSCP codebase.</p>
<p>With the program translated to Rust, there are a few exercises that I want to try to explore Rust's concurrency mechanisms. My first goal is to get it to think ahead while the opponent thinks about their move (known as "pondering" in chess-programming circles). My next goal is to implement multicore parallel search using <a href="http://www.tckerrigan.com/Chess/Parallel_Search/">ideas on Tom Kerrigan's web site</a>.</p>
<p>My thanks to Tom Kerrigan for this cool toy.</p>522019-02-23T17:55:41-05:002019-02-23T17:55:41-05:00Kristopher Johnsontag:undefinedvalue.com,2019-02-23:/52.html<p>It's been a couple of years since <a href="https://undefinedvalue.com/50.html">my last birthday post</a>, and I want to get into the habit of blogging again, so here's another intermittently-annual <em>State of the Kris</em> post.</p>
<p>The last year has been pretty good, and the new year has started with some significant changes. I've been …</p><p>It's been a couple of years since <a href="https://undefinedvalue.com/50.html">my last birthday post</a>, and I want to get into the habit of blogging again, so here's another intermittently-annual <em>State of the Kris</em> post.</p>
<p>The last year has been pretty good, and the new year has started with some significant changes. I've been elevated to "Team Lead" at work, so apparently all the work I've done to exude an air of irresponsibility was for naught. The last couple of times I became a manager, I quit within a year, but I hope this time will be different. I like the people here, and don't feel like I'm in a hopeless position like I did those other times.</p>
<p>The other big change is that my wife and I are finally buying a house after a few years of renting. It will be nice to be able to do some landscaping and to put whatever holes in whatever walls we want. It's a little farther away from work for both of us, but we like the area, and the houses are $100K cheaper than the equivalent houses in the area where we've been living. We weren't really planning to buy a house this year, but our landlord told us he was thinking of putting the property on the market, so we looked at our options and were actually surprised to find that buying a house was doable.</p>
<p>Wife, kid, and dogs are still doing fine, as are parents and siblings. One of my brothers moved into a house near my parents' house, so we've all been seeing one another more often.</p>
<p>I've taken several online courses in <a href="https://en.wikipedia.org/wiki/Data_science">data science</a> and <a href="https://en.wikipedia.org/wiki/Machine_learning">machine learning</a> over the past couple of years. I can't call myself an expert in either field, but it's cool to see things that were considered science fiction when I was in school becoming mainstream. I don't know if my career will steer into either of those directions, but they do seem more interesting than what web and mobile-app development have turned into.</p>
<p>Last year I started studying martial arts. I've been taking classes in <a href="https://en.wikipedia.org/wiki/Wing_Chun">Wing Chun Kung fu</a> for several months, and have just started adding some <a href="https://en.wikipedia.org/wiki/Brazilian_jiu-jitsu">Brazilian Jiu-Jitsu</a> classes. I don't plan to fight anyone, and I haven't learned enough for effective self-defense, but it's been a great way to get some exercise and do something completely different from my other hobbies. A BJJ class is a lot like spending an hour doing push-ups, sit-ups, squats, and crunches, and I'm always exhausted and sore at the end, but I love it.</p>
<p>My wife joined a bowling league. I never thought I would have my own bowling ball and shoes, but she talked me into it. I haven't joined a league yet, as the martial arts takes most of my free evenings, but I bet she will eventually convince me.</p>
<p>I'm studying Chinese using <a href="https://www.duolingo.com">Duolingo</a> and French using <a href="https://www.rosettastone.com">Rosetta Stone</a>. My wife bought me an electronic drum kit last Christmas, so I've been studying the drums too. It sometimes feels like I have a lot of "hobby chores" to get done, and my TV-watching time has really suffered, but it's been fun to dabble in all these things.</p>
<p>So, anyway, I'm having a pretty good life. I'm always ready for that to change, but I've also been able to shed a lot of the worry that has plagued me all my life. I've been reading a lot of <a href="https://en.wikipedia.org/wiki/Stoicism">Stoic philosophy</a>, which is <a href="https://qz.com/866030/stoicism-silicon-valley-tech-workers-are-reading-ryan-holiday-to-use-an-ancient-philosophy-as-a-life-hack/">a bit of a fad</a> right now, but it's something I first encountered when I was younger. It's always made logical sense to me, but now I'm absorbing it and following its principles better than I could before.</p>
<p>Anyway, that's all for now. I do plan to blog more often, but don't know about what yet.</p>Review: Coursera's "Applied Data Science with Python" Specialization2018-03-25T12:58:42-04:002018-03-25T12:58:42-04:00Kristopher Johnsontag:undefinedvalue.com,2018-03-25:/coursera-applied-data-science-with-python-specialization-review.html<p>I recently completed Coursera's <a href="https://www.coursera.org/specializations/data-science-python">Applied Data Science with Python</a> specialization, and received the <a href="https://www.coursera.org/account/accomplishments/specialization/certificate/HFVQHV6Q3B4B">accompanying certificate</a>. This is a review of my experience with the online courses.</p>
<p>This specialization is a series of five courses, each of which focuses on some aspect of using Python for data-science applications. Each course is …</p><p>I recently completed Coursera's <a href="https://www.coursera.org/specializations/data-science-python">Applied Data Science with Python</a> specialization, and received the <a href="https://www.coursera.org/account/accomplishments/specialization/certificate/HFVQHV6Q3B4B">accompanying certificate</a>. This is a review of my experience with the online courses.</p>
<p>This specialization is a series of five courses, each of which focuses on some aspect of using Python for data-science applications. Each course is focused on use of one or more free Python libraries. The courses and the libraries covered by each are</p>
<ol>
<li>Introduction to Data Science in Python: <a href="http://www.numpy.org">NumPy</a>, <a href="https://www.scipy.org">SciPy</a>, and <a href="https://pandas.pydata.org">Pandas</a></li>
<li>Applied Plotting, Charting, & Data Representation in Python: <a href="https://matplotlib.org">Matplotlib</a> and <a href="https://seaborn.pydata.org">seaborn</a></li>
<li>Applied Machine Learning in Python: <a href="http://scikit-learn.org/">scikit-learn</a></li>
<li>Applied Text Mining in Python: <a href="https://www.nltk.org">NLTK</a> and <a href="https://radimrehurek.com/gensim/">Gensim</a></li>
<li>Applied Social Network Analysis in Python: <a href="https://networkx.github.io">NetworkX</a></li>
</ol>
<p>The courses build on one another. For example, all the courses after the first assume that you are proficient with NumPy and Pandas, and all courses after the second assume you are proficient at creating plots with Matplotlib, and the last two courses assume you know how to train a machine-learning model. So you should take the courses consecutively in the specified order, although you could take the last two at the same time.</p>
<p>This is an intermediate-level specialization, and it is assumed you already know how to write programs in Python. It also assumes some elementary knowledge of statistics and discrete mathematics, but nothing too advanced. I had previously taken Andrew Ng's <a href="https://www.coursera.org/learn/machine-learning">Machine Learning</a> course, so I already had some familiarity with the terminology and the math. I just needed to do a little googling once in a while to refresh my memory or get an introduction to unfamiliar concepts.</p>
<p>As hinted at by the word "Applied" in the specialization and course titles, there is not much theory presented in these courses. There is just enough theory to understand the exercises. I took the specialization concurrently with Andrew Ng's <a href="https://www.deeplearning.ai">deeplearning.ai</a> specialization, so I got a nice dose of neural-network theory mixed in with my data science, but people who want to understand the algorithms in detail will need further study.</p>
<p>The programming assignments are both good and terrible. They are good in that I feel like I got a solid introduction to what libraries are available and how to use them, with enough challenge to make me dig into the documentation and Stack Overflow a lot, but they were not so difficult that I didn't know what to do. They are terrible in that the exercises are automatically graded, and the auto-grader has a lot of problems. Assignments that should take an hour or two instead take two or three times that due to the auto-grader rejecting correct answers with a "wrong" data type, or "wrong" number of significant figures, or expecting a different result than what the assignment's instructions specify. After doing each assignment, one has to spend an hour reading the course forums to find out the tricks to getting correct work accepted. If these were brand-new courses, I could excuse the auto-grader imperfections, but these courses have all been run multiple times, so it's very frustrating that the bugs haven't been worked out and the teaching assistants don't understand the issues.</p>
<p>Most of the courses include some lectures or assignments dealing with the ethics of data science. I was glad to see that. We all need to be thinking about the consequences of these technologies.</p>
<p>I found the courses in this specialization useful. It was nice to find a set of courses in Python; I've abandoned a few other courses that use <a href="https://www.r-project.org">R</a> or <a href="https://www.gnu.org/software/octave/">Octave</a>, because I found learning and writing code in those languages while also learning new concepts to be too frustrating. I especially liked that the later courses used the tools and techniques introduced in earlier courses, and a few of the later assignments had the feel of doing real work. More than once, I was amazed at how easy it was to do things that I would have said were impossible before I took the courses.</p>
<p>I think the complete specialization is roughly equivalent to a one-semester college course. I know I don't know enough to call myself a competent data-science practitioner, but I do feel like I would at least know where to start looking if faced with a data-science task.</p>Everything on the Internet Is Not Directed at You2017-09-22T18:59:49-04:002017-09-22T18:59:49-04:00Kristopher Johnsontag:undefinedvalue.com,2017-09-22:/everything-on-the-internet-is-not-directed-at-you.html<p>The Internet has made it easy for anyone to say whatever they want to say to a worldwide audience. This is great. Some people have decided that if they don't like something that somebody else has posted, it is their right and obligation to insult and attack those people. That's …</p><p>The Internet has made it easy for anyone to say whatever they want to say to a worldwide audience. This is great. Some people have decided that if they don't like something that somebody else has posted, it is their right and obligation to insult and attack those people. That's not so great.</p>
<p>Everything on the Internet is not directed at <em>you</em>. If you don't like it, maybe it's just <em>not for you</em>. If that's the case, you don't need to respond.</p>
<p>A post on the Internet is usually not an invitation to debate. Most people just want to share a thought, tell a joke, vent some frustration or anger, or let their loved ones know how they are doing.</p>
<p>Leave those people alone. They probably aren't hurting anybody. If they aren't your friends, and they aren't asking for replies, they probably won't welcome yours.</p>
<p>Even if you think their post <em>is</em> harmful, and an unsolicited response <em>is</em> necessary, consider that you may not know the whole context. That post may be part of a larger conversation to which you are not a party. The poster may assume the reader has some background knowledge that you don't have. The poster may have a very different point of view from yours. The poster may just be having a bad day. Maybe you just don't get the joke.</p>
<p>There are some good old-fashioned rules of courtesy we should all follow when engaging others in conversation:</p>
<ul>
<li>If you want to talk to somebody you don't know, politely introduce yourself first. Say something nice before you start detailing your disagreements.</li>
<li>If the other person doesn't want to talk to you, leave them alone.</li>
<li>Be kind. Don't twist the other person's words unfairly. Assume the other person is as intelligent and well-meaning as you are, and is as interested in finding the right answers as you are.</li>
<li>If you are angry, consider taking a break to cool down instead of saying something hurtful.</li>
<li>Don't demand that any person explain things or provide an education for you. If you can find the answers you want with Google, then do that instead of wasting someone's time.</li>
<li>Accept that you probably won't change anyone's mind. Once you've made your case or said your piece, you can stop.</li>
<li>If the other person has taught you something or has been helpful, thank them. If you've said something insulting or unfair, apologize.</li>
</ul>
<p>If somebody is consistently writing things on the Internet that make your blood boil, do yourself a favor and stop following that person.</p>
<p>It's easy to be nasty on the Internet. Distinguish yourself by being kind. You won't always be successful, but it's always worth a try.</p>My White-Supremacist Childhood2017-08-18T18:30:00-04:002017-08-18T18:30:00-04:00Kristopher Johnsontag:undefinedvalue.com,2017-08-18:/my-white-supremacist-childhood.html<p>I was born in North Dakota, but at the age of seven my family moved to <a href="https://en.wikipedia.org/wiki/Roswell,_Georgia">Roswell, Georgia</a>, a small (at that time) town north of Atlanta. I lived there until I went off to college. It was a nice place to live, full of nice people, but when I …</p><p>I was born in North Dakota, but at the age of seven my family moved to <a href="https://en.wikipedia.org/wiki/Roswell,_Georgia">Roswell, Georgia</a>, a small (at that time) town north of Atlanta. I lived there until I went off to college. It was a nice place to live, full of nice people, but when I look back at it, there were some disturbing elements of my upbringing.</p>
<p>The town was established in the 1830's. There are several antebellum houses, and as a kid we went on many field trips to those houses, because they were on the same street as my elementary school. The tour guides told us stories of the industriousness of the town's founders, and the bravery of the citizens when Sherman's army marched through during the war. They didn't talk about the slave quarters in the backyards of those houses. They didn't mention that the founder, <a href="https://en.wikipedia.org/wiki/Roswell_King">Roswell King</a>, was notorious for <a href="http://www.nytimes.com/1988/02/10/us/roswell-journal-the-darker-side-of-a-beloved-founder.html">beating and raping his slaves</a>. People still use these houses for wedding receptions, parties, and other special events.</p>
<p>In fifth grade, while studying the events leading up to the Civil War, the teacher staged a debate about the merits of slavery, as if we were Southern statesmen. I was assigned to the pro-slavery team. So, at the age of 10, I stood up in front of the class and talked about how great slavery was, both for the white citizens and for the slaves themselves, and that abolishing it would be a mistake. I don't remember how the black kids in class reacted to this. Throughout my schooling, the Civil War was taught as the great <a href="https://en.wikipedia.org/wiki/Lost_Cause_of_the_Confederacy">Lost Cause</a> where brave honorable white people resisted tyranny.</p>
<p>We took field trips to <a href="https://en.wikipedia.org/wiki/Stone_Mountain">Stone Mountain</a>. We all put on Confederate army hats and had a great time while the tour guides told us stories of the brave Confederate soldiers tricking the evil Union invaders. They didn't mention that Stone Mountain was the site of the founding of the KKK in 1915.</p>
<p>I remember my parents going to a costume ball where all the men dressed as Confederate officers and the women dressed as Southern belles. All in good fun.</p>
<p>When the first black family moved into our lily-white neighborhood, there was some concern. I was pleased that the attitude was generally positive ("We met them. They seem nice. It should be OK."), but it bothered me to hear all the adults discussing it in hushed tones at their parties.</p>
<p>It would be easy to dismiss such events as a "Southern thing", but whenever we went on family trips to other parts of the country to visit family and friends, after a few drinks there was always an uncle, aunt, cousin, or in-law who would give me a lecture about the problems with blacks/Hispanics/Asians/Native Americans, and offer some solutions (non-violent, but often involving ex-patriation or sterilization).</p>
<p>I don't recount these events to shame anyone. All the people involved in raising me were kind, generous people who made me feel happy and secure. I love them and thank them. I know some will consider my telling of these stories to be shockingly impolite. I hope you will forgive me, but I'm not going to apologize.</p>
<p>It doesn't matter whether you think you're racist: there is racism. Systemic racism exists. White people are the ones who perpetuate it. Non-white people are the ones who suffer its effects.</p>
<p>We are so steeped in it that we don't even notice it, just as we don't notice air or gravity. It seems to just be the way things are. We say we hate racism, but we keep it going.</p>
<p>White people are the only ones who can put an end to it.</p>
<p>We can't deny it, and we shouldn't passively accept it or just hope that it goes away. It's hard to fight against something that benefits you and your family, but it's the right thing to do.</p>
<p>Stop teaching our kids this stuff. Nobody deserves a white-supremacist childhood. This is especially true for the non-white children.</p>Thoughts After Charlottesville2017-08-13T17:14:00-04:002017-08-13T17:14:00-04:00Kristopher Johnsontag:undefinedvalue.com,2017-08-13:/thoughts-after-charlottesville.html<p>After the Charlottesville incident this weekend, it is easy to denounce "Nazis" and "white supremacists" and think you're on the good side of things. That is missing the bigger picture.</p>
<p>People want to think "This is not who we are as Americans," but this is exactly who we are. White …</p><p>After the Charlottesville incident this weekend, it is easy to denounce "Nazis" and "white supremacists" and think you're on the good side of things. That is missing the bigger picture.</p>
<p>People want to think "This is not who we are as Americans," but this is exactly who we are. White supremacy has been ingrained in American culture for centuries, before the Confederacy or the Nazis existed. The Charlottesville demonstrators didn't invent their racist ideology. They learned it from their parents and grandparents, from their friends, from the people in the towns where they grew up, from TV, books, and movies, and from their political leaders.</p>
<p>Most white nationalists don't attend rallies, fly Confederate flags, or tattoo swastikas on their bodies. They are your family, your friends, your coworkers. They are us.</p>
<p>Many white people think "Racism is terrible, but I'm not a racist, and my friends and family aren't racists, so it's not my fault, and there is nothing I need to do about it." But that's not true. The US is a racist society, and white people are complicit in the maintenance of white supremacy.</p>
<p>We live in an unfair and unjust society. It is broken, and we should all want to fix it. We all have work to do. Pointing fingers at "racists" accomplishes nothing.</p>
<p>Make fun of the tiki-torch terrorists in Virginia if you want, but if you really care about justice, work to tear down white supremacy where you can. Stop treating racism as somebody else's problem. Stop claiming white males face oppression. Stop laughing at racist jokes. Stop spreading lies about affirmative action. Stop excusing police brutality against minorities. Stop romanticizing the Lost Cause of the Confederacy. Confront your friends and relatives when they say awful things in private. Ask employers why they don't hire and promote more black people. Ask politicians what they are doing, and demand that they do more. March alongside people who should be treated as equals. Speak up.</p>
<p>As Martin Luther King, Jr. said in "Letter from Birmingham Jail", the real roadblock to progress is not the avowed racist; it is the "white moderate" who claims to want justice but wants change to be easy and gradual. We owe it to our fellow citizens to make things better now, without regard for how hard it might be for us.</p>Adding a Jinja2 Filter with a Pelican Plugin2017-07-22T10:58:00-04:002017-07-22T10:58:00-04:00Kristopher Johnsontag:undefinedvalue.com,2017-07-22:/adding-a-jinja2-filter-with-a-pelican-plugin.html<p>With my <a href="https://undefinedvalue.com/rebuilding-my-blog-again.html">new blog setup</a>, I decided to give <a href="https://en.wikipedia.org/wiki/AdSense">Google AdSense</a> a try. I don't have many regular readers, but I do have a few pages that attract search-engine traffic. So, to monetize those older pages without shoving ads in my regular readers' faces, I decided to show ads on …</p><p>With my <a href="https://undefinedvalue.com/rebuilding-my-blog-again.html">new blog setup</a>, I decided to give <a href="https://en.wikipedia.org/wiki/AdSense">Google AdSense</a> a try. I don't have many regular readers, but I do have a few pages that attract search-engine traffic. So, to monetize those older pages without shoving ads in my regular readers' faces, I decided to show ads on posts that were over a month old. That should be easy enough to add to the <a href="https://github.com/kristopherjohnson/undefinedvalue-pelican/blob/master/themes/undefinedvalue/templates/article.html">article.html</a> template:</p>
<div class="highlight"><pre><span></span><code><span class="cp">{%</span> <span class="k">if</span> <span class="nv">article.date</span><span class="o">|</span><span class="nf">age_in_days</span> <span class="o">></span> <span class="m">30</span> <span class="cp">%}</span><span class="x"></span>
<span class="x"> <!-- ... Include AdSense code ... --></span>
<span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span><span class="x"></span>
</code></pre></div>
<p>Only one problem: there is no <code>age_in_days</code> filter provided by Pelican or Jinja. I'd have to write a custom filter myself.</p>
<p>A Jinja <a href="http://jinja.pocoo.org/docs/2.9/api/#custom-filters">custom filter</a> is just a Python function that takes an argument and produces a result. My filter function is pretty simple:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
<span class="k">def</span> <span class="nf">age_in_days</span><span class="p">(</span><span class="n">dt</span><span class="p">):</span>
<span class="sd">"""Return a number of days between now and a specified datetime."""</span>
<span class="n">now</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">(</span><span class="n">dt</span><span class="o">.</span><span class="n">tzinfo</span><span class="p">)</span>
<span class="n">delta</span> <span class="o">=</span> <span class="n">now</span> <span class="o">-</span> <span class="n">dt</span>
<span class="k">return</span> <span class="n">delta</span><span class="o">.</span><span class="n">days</span>
</code></pre></div>
<p>I had to figure out how to add that to Pelican's Jinja2 template-rendering environment. I found a <a href="https://linkpeek.com/blog/how-to-add-a-custom-jinja-filter-to-pelican.html">How to add a custom Jinja filter to Pelican post</a> that showed how to import a function and set the Pelican's <code>JINJA_FILTERS</code> variable in <code>pelicanconf.py</code>, but I didn't like the idea of adding imports and functions to a configuration file. I decided it would be cleaner (and more fun) to figure out how to make a <a href="http://docs.getpelican.com/en/3.7.1/plugins.html">Pelican plugin</a> that provided my filter.</p>
<p>It wasn't hard to figure this out, because I found a <a href="https://github.com/MinchinWeb/minchin.pelican.jinja_filters">Jinja Filters plugin</a> that provides a set of filters, so I could just copy the little bits I needed from that for registering my filter. A Pelican plugin must <a href="http://docs.getpelican.com/en/3.7.1/plugins.html#how-to-create-plugins">provide a <code>register()</code> function</a> that sets up a mapping of Pelican "signals" to functions that are to be called. My plugin handles the <code>generator_init</code> signal by adding the <code>age_in_days</code> filter to Pelican's Jinja environment:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">pelican</span> <span class="kn">import</span> <span class="n">signals</span>
<span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">ageindays</span>
<span class="k">def</span> <span class="nf">add_filter</span><span class="p">(</span><span class="n">pelican</span><span class="p">):</span>
<span class="sd">"""Add age_in_days filter to Pelican."""</span>
<span class="n">pelican</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">filters</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="s1">'age_in_days'</span><span class="p">:</span> <span class="n">ageindays</span><span class="o">.</span><span class="n">age_in_days</span><span class="p">})</span>
<span class="k">def</span> <span class="nf">register</span><span class="p">():</span>
<span class="sd">"""Plugin registration."""</span>
<span class="n">signals</span><span class="o">.</span><span class="n">generator_init</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">add_filter</span><span class="p">)</span>
</code></pre></div>
<p>Then I had to update my <code>pelicanconf.py</code> to load this plugin in addition to the other plugins I use:</p>
<div class="highlight"><pre><span></span><code><span class="n">PLUGIN_PATHS</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'../plugins'</span><span class="p">,</span> <span class="s1">'../pelican-plugins'</span><span class="p">]</span>
<span class="n">PLUGINS</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'age_in_days'</span><span class="p">,</span> <span class="s1">'pelican_alias'</span><span class="p">,</span> <span class="s1">'neighbors'</span><span class="p">,</span> <span class="s1">'sitemap'</span><span class="p">]</span>
</code></pre></div>
<p>And that's it.</p>
<p>You can see the whole plugin here: <a href="https://github.com/kristopherjohnson/undefinedvalue-pelican/tree/master/plugins/age_in_days">https://github.com/kristopherjohnson/undefinedvalue-pelican/tree/master/plugins/age_in_days</a></p>Re-Cloudifying2017-07-15T08:00:00-04:002017-07-15T08:00:00-04:00Kristopher Johnsontag:undefinedvalue.com,2017-07-15:/re-cloudifying.html<p>With my <a href="/rebuilding-my-blog-again.html">blog rebuild</a> out of the way, my next task was to replace my old 2010-era <a href="https://en.wikipedia.org/wiki/Amazon_Elastic_Compute_Cloud">Amazon EC2</a> t1.micro instance with a new less-expensive <a href="https://aws.amazon.com/blogs/aws/ec2-update-t2-nano-instances-now-available/">t2.nano</a> instance. Without the blog, my EC2 needs are minimal, and the nano instances are really cheap.</p>
<p>My plan was to poke around …</p><p>With my <a href="/rebuilding-my-blog-again.html">blog rebuild</a> out of the way, my next task was to replace my old 2010-era <a href="https://en.wikipedia.org/wiki/Amazon_Elastic_Compute_Cloud">Amazon EC2</a> t1.micro instance with a new less-expensive <a href="https://aws.amazon.com/blogs/aws/ec2-update-t2-nano-instances-now-available/">t2.nano</a> instance. Without the blog, my EC2 needs are minimal, and the nano instances are really cheap.</p>
<p>My plan was to poke around the old micro instance, make a list of what was worth saving, spin up the new nano instance in the same AWS availability zone, copy things over, then terminate the old instance.</p>
<p>That was a solid plan.</p>
<p>Unfortunately, I couldn't resist the desire to play around with upgrading the micro instance to newest Ubuntu release (just to see what the process was like). The result was that my micro instance would no longer boot. I tried attaching a volume created from an EBS volume snapshot to that instance, but that didn't work. I tried creating a new AMI and instance from the old snapshot, but that didn't work either. I'd completely lost all access to my instance.</p>
<p>(Note to self: Test your restore-from-backup plan once in a while, dumbass.)</p>
<p>So, fine, new plan: Create the new nano instance, attach the old volume to it, then copy whatever is needed from that volume.</p>
<p>I also wanted my new setup to meet these requirements:</p>
<ul>
<li>Ensure the blog is no longer dependent on anything on the server</li>
<li>Require minimal configuration of the server (so this will be easier next time)</li>
<li>Ensure everything I want on my server is easily available from external sources (GitHub, etc.)</li>
<li>Ensure I can easily restore from an EBS snapshot if this ever happens again</li>
</ul>
<p>My longer-term plan is to experiment with Docker containers and other mechanisms for deploying and managing stuff in the cloud, but for now I just want a new server to fill the roles of my old server.</p>
<h2>Creating the New Instance</h2>
<p>I referred to my original <a href="https://undefinedvalue.com/setting-drupal-6-ubuntu-1010-ec2.html">Drupal-on-EC2</a> post while setting up the new nano instance. I did basically the same thing, except that I started with Amazon's "Ubuntu Server 16.04 LTS (HVM)" AMI, and chose the "t2.nano" instance type.</p>
<p>I set up the a new security group that allows SSH and HTTP/HTTPS from outside.</p>
<p>I wanted to assign my existing Elastic IP to the new nano instance, but the EC2 console wouldn't let me do that. After some head-scratching and Googling, I learned that elastic IPs for the old "Classic EC2" and new "VPC" environments are in separate pools, so I couldn't reuse the old one for the new instance. So I created a new Elastic IP and assigned it to my new instance, then I deallocated the old one, and updated my DNS records.</p>
<h2>Mounting the Volume from the Old Instance</h2>
<p>Now I wanted to get my old stuff. I created a new EBS volume from a snapshot from the old instance's boot volume, in the same availability zone as my new instance. Then I attached that volume to my new instance as <code>/dev/sdf</code>. To figure out how Ubuntu saw that new device, I ran this command:</p>
<div class="highlight"><pre><span></span><code>lsblk
</code></pre></div>
<p>which indicated that the available devices were named <code>xvda</code> (mounted as <code>/</code>) and <code>xvdf</code>. I mounted the volume as <code>/olddata</code> using these commands:</p>
<div class="highlight"><pre><span></span><code>sudo mkdir /olddata
sudo mount /dev/xvdf /olddata
</code></pre></div>
<p>then a quick <code>ls /olddata/home/ubuntu</code> and <code>ls /olddata/usr/share/drupal6</code> verified that all my stuff from the old instance was indeed present. (Phew!)</p>
<p>I created a compressed archive of the volume's contents so I could download it to my laptop for easy browsing and permanent backup:</p>
<div class="highlight"><pre><span></span><code>sudo tar -zcvf olddata.tar.gz /olddata
</code></pre></div>
<p>The archive was 2.7 GB, and full of lots of stuff I'll never need, but it's good to be thorough.</p>
<h2>Migrating SSH Keys</h2>
<p>I copied my SSH keys from the old instance so that I could use GitHub and other resources without generating and uploading new keys:</p>
<div class="highlight"><pre><span></span><code>cp /olddata/home/ubuntu/.ssh/id_rsa* ~/.ssh
</code></pre></div>
<h2>Setting Up Git</h2>
<p>Git was already included in the Ubuntu install, but I needed to configure the username and email that would be used for any local commits;</p>
<div class="highlight"><pre><span></span><code>git config --global user.name <span class="s2">"Kristopher Johnson"</span>
git config --global user.email <span class="s2">"kris@kristopherjohnson.net"</span>
</code></pre></div>
<p>I also added this setting to squelch an annoying Git warning message:</p>
<div class="highlight"><pre><span></span><code>git config --global push.default simple
</code></pre></div>
<h2>What's Good on TCM?</h2>
<p>My <a href="http://secretspacelab.com/tcm.html">What's Good on TCM?</a> page is static HTML that is generated by a <a href="https://en.wikipedia.org/wiki/Cron">cron</a> job at 11:00 AM every morning. All the code <a href="https://github.com/kristopherjohnson/kjtcmws">is on GitHub</a>.</p>
<p>My plan is to eventually move the generated page to a GitHub Project Page, so that it is not served by this server anymore, but for the sake of expediency I just set up the same cron job on the new server and configured Apache as needed to serve the generated page at that URL.</p>
<p>I had to install Apache to serve the generated page, and Node, npm, and Make to be able to run the page generator:</p>
<div class="highlight"><pre><span></span><code>sudo apt-get install apache2 nodejs npm make
</code></pre></div>
<p>My scripts expect to be able to run an executable named "node", but Ubuntu installs <code>/usr/bin/nodejs</code>, so I set up a symlink in <code>/usr/local/bin</code> to allow "node" to work:</p>
<div class="highlight"><pre><span></span><code>sudo ln -s /usr/bin/nodejs /usr/local/bin/node
</code></pre></div>
<p>I had to change my script so that it would copy the generated <code>tcm.html</code> file to <code>/var/www/html</code>, rather than to <code>/var/www</code> (which was the correct location for Ubuntu 12.04).</p>
<p>To give the ubuntu account write access to the <code>/var/www/html</code> directory, I ran these commands:</p>
<div class="highlight"><pre><span></span><code>sudo usermod -a -G www-data ubuntu
sudo chgrp www-data /var/www/html
sudo chmod g+x /var/www/html
</code></pre></div>
<h2>Enabling cgi-bin</h2>
<p>I have some Perl CGI scripts that people depend on. Here is what I did to enable the <code>/cgi-bin/</code> paths:</p>
<div class="highlight"><pre><span></span><code>sudo apt-get install libcgi-pm-perl libapache2-mod-perl2
sudo a2enmod cgi
sudo chgrp www-data /usr/lib/cgi-bin
sudo chmod g+w /usr/lib/cgi-bin
<span class="c1"># ... Move my scripts into /usr/lib/cgi-bin ...</span>
sudo service apache2 restart
</code></pre></div>
<h2>Eliminating Blog Dependencies</h2>
<p>My blog is now hosted at GitHub. However, some articles contain links to images and other files that were served by Drupal on the old server, so I needed to extract those from the old Drupal site directories and copy them to my blog repo. I was able to use Pelican's <a href="http://docs.getpelican.com/en/stable/settings.html#metadata">EXTRA_PATH_METDATA</a> settings to give these static files the same URL path that they had in Drupal, so I didn't have to update any links in the blog posts that referred to them.</p>
<h2>All for Now</h2>
<p>So, the result is I have a reasonably-up-to-date Ubuntu server, and my blog is no longer dependent on it. I can reproduce this configuration easily by following the instructions above.</p>
<p>I have an archive of everything that was on the old server, so at my leisure I can poke around and find anything worth saving.</p>
<p>(Note to self: Make a backup.)</p>Achievement Unlocked: Colonoscopy2017-07-14T16:44:26-04:002017-07-14T16:44:26-04:00Kristopher Johnsontag:undefinedvalue.com,2017-07-14:/achievement-unlocked-colonoscopy.html<p>When you turn 50, they make you get a <a href="https://en.wikipedia.org/wiki/Colonoscopy">colonoscopy</a>. That doesn't seem fair.</p>
<p>But it really isn't too bad. The worst part is the "prep", which is a euphemistic way of saying "Spend a day on a clear-liquid diet, take a bunch of laxatives, then spend a few hours …</p><p>When you turn 50, they make you get a <a href="https://en.wikipedia.org/wiki/Colonoscopy">colonoscopy</a>. That doesn't seem fair.</p>
<p>But it really isn't too bad. The worst part is the "prep", which is a euphemistic way of saying "Spend a day on a clear-liquid diet, take a bunch of laxatives, then spend a few hours in or near the restroom."</p>
<p>One is anesthetized for the procedure. I remember the anesthesiologist asking the doctor "Should I put him to sleep now?" and the next thing I remember is waking up in the recovery room. After a meal and a long nap at home, it's like nothing ever happened.</p>
<p>I have some pictures, but don't worry, I won't share them.</p>
<p>So now I'm part of the colonoscopy club. I don't plan to attend another meeting until I'm 60.</p>Simple NSMachPort Example2017-07-08T22:35:49-04:002017-07-08T22:35:49-04:00Kristopher Johnsontag:undefinedvalue.com,2017-07-08:/simple-nsmachport-example.html<p>I recently needed to use <a href="https://developer.apple.com/documentation/foundation/nsmachport">NSMachPort</a> for some interprocess communication on macOS. However, these days it is hard to find examples of how to use it in Apple's official documentation, as they are steering everyone toward <a href="https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html">XPC services</a> and <a href="https://developer.apple.com/documentation/foundation/nsxpcconnection">NSXPCConnection</a> for sandboxed IPC.</p>
<p>So, I wrote my own simple example …</p><p>I recently needed to use <a href="https://developer.apple.com/documentation/foundation/nsmachport">NSMachPort</a> for some interprocess communication on macOS. However, these days it is hard to find examples of how to use it in Apple's official documentation, as they are steering everyone toward <a href="https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html">XPC services</a> and <a href="https://developer.apple.com/documentation/foundation/nsxpcconnection">NSXPCConnection</a> for sandboxed IPC.</p>
<p>So, I wrote my own simple example code for NSMachPort. If you need it, you can find it at <a href="https://github.com/kristopherjohnson/KJMachPortServer">https://github.com/kristopherjohnson/KJMachPortServer</a>.</p>Rebuilding My Blog Again, with Pelican2017-07-08T15:00:00-04:002017-07-08T15:00:00-04:00Kristopher Johnsontag:undefinedvalue.com,2017-07-08:/rebuilding-my-blog-again.html<p>I've moved this blog from a <a href="https://www.drupal.org">Drupal</a> CMS running on an AWS EC2 instance to a statically generated set of pages hosted on <a href="https://pages.github.com">GitHub Pages</a>. If you're curious about the reasons and the process, read on.</p>
<p>For several years, my blog has been running on Drupal 6. Drupal <a href="https://undefinedvalue.com/2009/04/18/drupal-rocks">seemed like …</a></p><p>I've moved this blog from a <a href="https://www.drupal.org">Drupal</a> CMS running on an AWS EC2 instance to a statically generated set of pages hosted on <a href="https://pages.github.com">GitHub Pages</a>. If you're curious about the reasons and the process, read on.</p>
<p>For several years, my blog has been running on Drupal 6. Drupal <a href="https://undefinedvalue.com/2009/04/18/drupal-rocks">seemed like a good idea when I started</a>, as it was a mature easy-to-use content management system, but I've been wanting to get away from it for a while. I spend more time maintaining Drupal then I do writing articles for the blog. When I started I had to do <a href="https://undefinedvalue.com/2010/11/12/setting-drupal-6-ubuntu-1010-ec2">a lot of work</a> to get Drupal configured and running well. There are always new versions and security updates to apply, and I really should have upgraded to Drupal 7 and then Drupal 8 somewhere along the way, but I haven't and now I can't update to newer versions of Ubuntu. The care and feeding of Drupal is too much work for the benefits it provides for my simple blog.</p>
<p>I decided to move to a static site generator, as I don't need dynamic features and I want to avoid the pain of setting up any more CMSes or blogging engines on any server.</p>
<p>My basic requirements for the new generator were these:</p>
<ul>
<li>Provide a similar front page layout, with a list of ten "teasers" from most recent entries, a list of archives, an "About" page, and a few links to other stuff.</li>
<li>Support existing URLs for existing pages (so old links will work).</li>
<li>Support tags.</li>
<li>Be able to run the generator on my laptop and easily upload the generated static pages to my Apache web server, or S3, or GitHub Pages.</li>
<li>Let me write posts in <a href="https://daringfireball.net/projects/markdown/syntax">Markdown</a> format.</li>
</ul>
<h2>The Drupal 6 Database</h2>
<p>My first step was to export the existing articles and metadata from the Drupal database on my blog server, and recreate it on my development machine. Looking at my Drupal configuration, I was able to find the database name and login information (which I forgot years ago). I used <code>mysqldump</code> to export the data.</p>
<div class="highlight"><pre><span></span><code>mysqldump --user<span class="o">=</span><span class="nv">$dbuser</span> --password<span class="o">=</span><span class="nv">$dbpass</span> --tz-utc --databases <span class="s2">"drupal6_undefinedvalue"</span> > <span class="s2">"drupal6_undefinedvalue.sql"</span>
</code></pre></div>
<p>Then, after copying the file to my dev machine, I loaded it with the MySQL command <code>source drupal6_undefinedvalue.sql</code>.</p>
<p>Rather than reading Drupal source code or documentation, I just used a lot of MySQL "show tables", "describe", and <code>SELECT</code> commands to figure out the database schema. These are the tables and columns that I found useful:</p>
<ul>
<li><code>node</code>: metadata about all the Drupal "nodes", or pages<ul>
<li><code>nid</code>: node ID (primary key)</li>
<li><code>type</code>: <code>book</code>, <code>page</code>, or <code>story</code>. My blog posts are all of type <code>story</code>.</li>
<li><code>title</code>: the original title of the post</li>
<li><code>status</code>: 0 for an unpublished post, or 1 for a published post.</li>
<li><code>created</code> and <code>changed</code>: timestamps</li>
</ul>
</li>
<li><code>node_revisions</code>: the content of each node<ul>
<li><code>nid</code>: node ID</li>
<li><code>title</code>: current title</li>
<li><code>body</code>: main content</li>
<li><code>timestamp</code></li>
<li><code>format</code>: foreign key into the <code>filter_formats</code> table</li>
</ul>
</li>
<li><code>filter_formats</code>: list of filter formats for posts<ul>
<li><code>format</code>: key</li>
<li><code>name</code>: "Filtered HTML", "Full HTML", "Filtered Markdown", or "Full Markdown"</li>
</ul>
</li>
<li><code>term_node</code>: association of posts with tags</li>
<li><code>term_data</code>: tags</li>
<li><code>url_alias</code>: provides mappings from URLs like "node/288" to "2017/04/22/my-first-chess-program"</li>
</ul>
<p>The <code>body</code> of a <code>node_revisions</code> record contains a special token <code><!--break--></code> between the "teaser" portion and the rest.</p>
<p>I wrote a Python script that could extract the relevant fields from these tables and print out the attributes I expected to see. If you want to see it, check out <a href="https://github.com/kristopherjohnson/drupal6tostatic">my drupal6tostatic repository</a>.</p>
<h2>Static Site Generators</h2>
<p>The next step was to decide on a static site generator.</p>
<p>A "static site generator" (SSG) is a program that, given a bunch of input files and templates and configuration files and code, will generate a tree of static HTML pages that can be uploaded to a web server.</p>
<p>I briefly thought about writing my own SSG, but my goal here was to get my blog moved as quickly and painlessly as possible, so I decided to go with an off-the-shelf solution. The most popular SSG is currently <a href="https://jekyllrb.com">Jekyll</a>, but I don't like Ruby much and would prefer a Python 3-based tool. After a brief survey of the available Python SSGs, I decided to give these a try:</p>
<ul>
<li><a href="https://github.com/eudicots/Cactus">Cactus</a></li>
<li><a href="http://hyde.github.io">Hyde</a></li>
<li><a href="http://getpelican.com">Pelican</a></li>
</ul>
<p>Cactus didn't work with Python 3. It almost worked, but the blog plugin wasn't compatible with Python 3, and after I fixed that, it generated an empty index page. With Python 2, it worked, but its blog plugin didn't support the directory hierarchy I wanted to maintain. Rather than fix these issues, I moved on to the next candidate.</p>
<p>Hyde didn't support Python 3. (The website says Python 3 support is "in progress".) I gave it a try anyway, and while I didn't hate it, I really want something that works with Python 3 and doesn't require a lot of configuration. (For every YAML file I see, I expect at least one hour of messing around with it.)</p>
<p>Pelican worked pretty well on my first try (except for a CRITICAL error when running <code>pelican generate</code> with a not-well-formed Markdown file) and it seemed to be the most well-documented and well-supported of the generators I tried. Out-of-the-box, it handled pagination and other desirable features that would have required configuration or additional coding in Cactus or Hyde.</p>
<p>So I decided to use Pelican as my site generator.</p>
<h2>Python 3 Virtual Environment</h2>
<p>To ensure I'd be able to easily run the Pelican generator on any machine, now or in the future, I set up a Python 3 <a href="https://docs.python.org/3.5/library/venv.html">virtual environment</a> and installed the necessary packages:</p>
<div class="highlight"><pre><span></span><code>python3 -m venv-pelican-blog # create the environment
source venv-pelican-blog/bin/activate # activate the environment
pip install pelican pelican-alias markdown # install dependencies
</code></pre></div>
<p>And then before running any Pelican commands, I have to make sure I run the command <code>source venv-pelican-blog/bin/activate</code>.</p>
<h2>Configuring Pelican</h2>
<p>To support automatic redirects from old URLs to the new pages, I installed the <a href="https://github.com/Nitron/pelican-alias">pelican-alias</a> plugin, and updated my generator script so that it would include an <code>Alias:</code> item for each old article.</p>
<p>I added an <code>about-me.md</code> file to the <code>content/pages</code> subdirectory, so that would also appear at the top of the page as it does in the Drupal site.</p>
<p>I set the <code>LINKS</code> and <code>SOCIAL</code> settings in <code>pelicanconf.py</code> so that I would get lists of links similar to what was in the Drupal blog's "blocks".</p>
<p>I set the <code>YEAR_ARCHIVE_SAVE_AS</code>, <code>MONTH_ARCHIVE_SAVE_AS</code>, and <code>DAY_ARCHIVE_SAVE_AS</code> variables in <code>pelicanconf.py</code> to generate archive pages with paths identical to what was used in Drupal.</p>
<h2>Theme and Styles</h2>
<p>Finally, I wanted a theme similar to that of the <a href="https://www.drupal.org/project/whitejazz">WhiteJazz</a> theme I had used with Drupal 6. Pelican comes with two built-in themes: <code>simple</code>, which is just plain text with no styling, and <code>notmyidea</code>, which is what the <a href="http://getpelican.com">getpelican.com</a> website uses. Rather than perusing all the other themes available for Pelican, I decided to try to replicate WhiteJazz myself, with the expectatation that I would give up in frustruation after a few hours and look for an off-the-shelf theme.</p>
<p>Thankfully, it turned out to be easier than I expected to create my own theme. I started by copying the <code>simple</code> theme files to a new directory, then updated the templates that didn't quite do what I wanted, and added some CSS. For the basic CSS, I chose <a href="http://getskeleton.com">Skeleton</a>, a simple CSS framework which I had used before in my <a href="http://secretspacelab.com/tcm.html">What's Good on TCM?</a> site. It was easy to set up its grid to provide the basic layout of WhiteJazz. I then added my own CSS rules and template customizations, borrowing liberally from the WhiteJazz CSS and notmyidea templates, until I had what I wanted.</p>
<p>One problem I ran into was that the <code>simple</code> framework had an empty (0 bytes) <code>tag.html</code> template, and was missing other files related to tags. So I copied the missing files from <code>notmyidea</code>.</p>
<h2>Publishing the New Site</h2>
<p>I had already set up my personal <code>kristopherjohnson.github.io</code> repo as a GitHub Pages site. I set up a Makefile rule that would copy the output from Pelican to that local repo, commit the change, and then push that repo to GitHub.</p>
<p>So, all I have to do to regenerate the site and push it to the cloud is this:</p>
<div class="highlight"><pre><span></span><code>cd ~/work/undefinedvalue-pelican
source venv-pelican-blog/bin/activate
make github
</code></pre></div>
<p>It takes about four seconds to run <code>make github</code>. After that, it takes a minute or so before the changes are actually visible on the site.</p>
<p>Finally, I went to my domain registrar and updated the <code>undefinedvalue.com</code> address records so it now points to GitHub rather than to my old server, following the instructions on these pages:</p>
<ul>
<li><a href="https://help.github.com/articles/quick-start-setting-up-a-custom-domain/">Quick start: Setting up a custom domain</a></li>
<li><a href="https://help.github.com/articles/setting-up-an-apex-domain/">Setting up an apex domain</a></li>
</ul>
<h2>Conclusion</h2>
<p>I've wanted to do this migration for over a year, but feared it would take up a few weekends and be full of frustration. It was actually a lot easier than I expected. The hardest part for me was getting the CSS right. It feels good that I figured out how to do a full conversion by writing a few scripts and modifying some configuration files and templates.</p>
<p>One thing I miss is the simplicity of editing with Drupal. If I saw a typo on the site, I could just click an <em>Edit</em> button, fix it, and <em>Save</em>. Now if I see something wrong, I have to find the right input file in my tree of files, edit it, and then republish, and then wait a minute to see if it looks right. But I am very happy to no longer be dependent upon a MySQL database, an old version of a PHP-based CMS, and an EC2 web server.</p>
<p>If you want to see my Pelican setup, it's available here for all to see: <a href="https://github.com/kristopherjohnson/undefinedvalue-pelican">https://github.com/kristopherjohnson/undefinedvalue-pelican</a>.</p>My First Chess Program2017-04-22T19:07:16-04:002017-04-22T19:07:16-04:00Kristopher Johnsontag:undefinedvalue.com,2017-04-22:/my-first-chess-program.html<p>When I was a kid, back in the 70's and 80's, I thought chess programs were the most sophisticated computer programs in the world. That was back when the average personal-computer chess program wasn't very good, and dedicated chess computers cost hundreds or thousands of dollars, so it seemed to …</p><p>When I was a kid, back in the 70's and 80's, I thought chess programs were the most sophisticated computer programs in the world. That was back when the average personal-computer chess program wasn't very good, and dedicated chess computers cost hundreds or thousands of dollars, so it seemed to me that chess was something very difficult for computers to do. At the time, many experts still thought a computer would never be able to beat human grandmasters. I dreamed that I would one day write The World's Greatest Chess Program, and that would prove I was a great computer programmer.</p>
<p>But one's priorities change. I did look into how chess programs worked when I was in college, and while I found the topic interesting, I didn't try writing my own program. I still thought that writing a chess program was something every Real Computer Programmer should do, but I didn't get around to it.</p>
<p>Now, at age 50, I've finally done it. A few weeks ago I started writing a chess engine in <a href="https://swift.org">Swift</a>, and it is now capable of playing a real chess game. I call it "kjchess".</p>
<p>It works, but it's slow. On my 2013 MacBook Pro, my move search function can only search about four half-moves deep to find a move in a reasonable time, even when using all the CPU cores. That is terrible, considering that it is worse than the 4MHz 8-bit <a href="http://www.spacious-mind.com/html/classic.html">Fidelity "Classic" chess computer</a> I had 25 years ago.</p>
<p>Why is my engine so slow? I initially gave myself a few constraints:</p>
<ul>
<li>Write it in "high-level" Swift (that is, it shouldn't look like a C or C++ program).</li>
<li>Write it in a functional-programming style (in particular, use immutable data structures wherever possible).</li>
<li>Don't refer to any other chess programs' source code. Use only the knowledge of chess programming I've gleaned over the last few decades.</li>
</ul>
<p>These constraints made it an interesting exercise, but I relaxed them. For example, I copied the <a href="https://en.wikipedia.org/wiki/Alpha–beta_pruning">minimax algorthm with alpha-beta pruning</a> from Wikipedia, and I've incorporated some positional evaluation ideas from <a href="https://chessprogramming.wikispaces.com/Simplified+evaluation+function">the Chess Programming Wiki</a>.</p>
<p>Now that I've seen how slow it is, I think I need to abandon the "only immutable data structures" constraint. I think the main reason my engine is so slow is that it creates a brand-new "position" structure for every possible move, leading to a lot of memory allocation, deallocation, and copying, whereas other engines have a single global position on which they make and unmake moves very quickly.</p>
<p>Also, my engine doesn't save any search state between moves, and it doesn't do any pondering during the opponent's move. I could probably make it a lot better by adding those features.</p>
<p>Despite its limitations, it plays well enough to beat me once in a while. That's not impressive, because I am a terrible chess player, but I figure I'm a winner either way. (I'm also a loser either way, but I can ignore that.)</p>
<p>If you want to play it, and you have a Mac and know how to build Swift programs, you can check it out here: <a href="https://github.com/kristopherjohnson/kjchess">https://github.com/kristopherjohnson/kjchess</a></p>502017-01-17T05:27:46-05:002017-01-17T05:27:46-05:00Kristopher Johnsontag:undefinedvalue.com,2017-01-17:/50.html<p>So, suddenly, I'm 50 years old. Almost a quarter of my life behind me.</p>
<p>50 really doesn't seem that bad. 40 felt much more like an <em>oh-man-I'm-old-and-have-wasted-my-life</em> event. I'm much happier now than I was then. This has been a great decade for me. I met a wonderful woman, and …</p><p>So, suddenly, I'm 50 years old. Almost a quarter of my life behind me.</p>
<p>50 really doesn't seem that bad. 40 felt much more like an <em>oh-man-I'm-old-and-have-wasted-my-life</em> event. I'm much happier now than I was then. This has been a great decade for me. I met a wonderful woman, and we are celebrating our ninth wedding anniversary this year. I live in a comfortable house with people and dogs who make me feel connected and loved (most of the time). Being 50 and happy is much better than being younger and miserable.</p>
<!--break-->
<p>I skipped my last <a href="https://undefinedvalue.com/category/tags/birthday">annual birthday update</a>. Something important was going on in January and I wanted to wait until it got resolved before writing the update, but by the time it was resolved it seemed too late to be an annual update. So I have a bit of time to cover since <a href="https://undefinedvalue.com/2015/01/30/48">my last update at 48</a>.</p>
<p>(I also need an updated picture. That picture in the corner of this web page is from several years ago. I'm much better looking now.)</p>
<p>In the past year we've moved into a ranch-style house with a big wooded back yard. It's in a nice part of Johns Creek, Georgia, close to everything we need to be close to, about fifteen minutes away from my parents' house. We finally sold our Dahlonega cabin, so we are thinking about buying this house we're currently renting, and will be working on that in the coming months.</p>
<p>My wife Pebble is now a science teacher at Chattahoochee High School. Bailey is finishing his last year of high school. We have another young man, De'John, staying with us while he finishes high school as well. Not sure what either of them will be doing after high school, but I'm sure they will figure something out.</p>
<p>My parents are still in Roswell. Mom has joined Dad in retirement. Dad has been having some health issues, so I worry about him, but I'm hoping it turns out OK.</p>
<p>My brother Eric is engaged to be married. We've enjoyed meeting his fiancée and her kids.</p>
<p>I'm starting my third year working at <a href="http://mobilelabsinc.com">Mobile Labs</a> as a Senior Software Developer. It's been interesting work. I've learned a lot about the internals of iOS and Android, and am starting to get into the reverse-engineering work that attracted me to the position. I get to write code for iOS, Android, Windows and macOS, in C#, C++, Objective-C, Swift, and other languages. I work at home three days a week, and go into the office in Buckhead twice a week. We will be expanding our product offerings in the coming year, so I look forward to having a lot of new challenges.</p>
<p>I got into long-distance running a few years ago, running my <a href="https://undefinedvalue.com/2014/03/24/2014-publix-georgia-marathon">first marathon</a> in March 2014. Unfortunately, a couple of days after that marathon, I was hit with <a href="https://en.wikipedia.org/wiki/Plantar_fasciitis">plantar fasciitis</a>, and as a result had to quit running for about a year. After resuming my running, I still haven't been able to get back into the groove of running more than about six miles. So I'm setting a goal for this year of running another half marathon, and depending on how that goes, might try another marathon in 2018. I hope to lose about 50 pounds along the way.</p>
<p>My politics have changed and become more important to me in the past couple of years. I've always favored equality for women and minorities, but my conservative upbringing left me with an underlying assumption that the world is basically a meritocracy, and anybody who works hard enough can achieve the same things I have. And I felt that whatever inequality exists is not my fault, so it wasn't my problem to fix. I no longer believe those things. Women and minorities simply don't get the same opportunities that white men do, and they face obstacles that white men don't understand or recognize. It's not fair, and we should all want to fix it.</p>
<p>So I've decided that, as a white man with unearned privilege, I have a responsibility to do what I can to help correct these imbalances. It makes me unpopular with my friends and family at the dinner table and on Facebook, but I will no longer just sit quietly when I hear that all Muslims should be exterminated, or that unarmed black man shot in the back by police probably did something to deserve it, or that women really should dress more modestly if they want to be taken seriously. We all need to speak up when we hear such things. I've decided I will speak up, and will do what I can to amplify the voices of those who often go unheard.</p>
<p>It might make me unpopular, but I don't care. That's one of the best things about being 50.</p>What's Good on TCM This Week?2016-11-04T22:34:41-04:002016-11-04T22:34:41-04:00Kristopher Johnsontag:undefinedvalue.com,2016-11-04:/whats-good-tcm-week.html<p>I like old movies. In the old days, we had TV stations known as "UHF channels" that showed lots of old movies all night long. But UHF channels are gone, and today all we have is the Turner Classic Movies (TCM) channel and website.</p>
<p>I like film noir, sci-fi, horror …</p><p>I like old movies. In the old days, we had TV stations known as "UHF channels" that showed lots of old movies all night long. But UHF channels are gone, and today all we have is the Turner Classic Movies (TCM) channel and website.</p>
<p>I like film noir, sci-fi, horror, and westerns. TCM has a lot of these movies, but it also shows a lot of stuff I'm not interested in. I don't like browsing the upcoming schedule via the TCM website or via my cable provider's interface, so I wanted a way to generate a schedule just for me so that I can record good stuff on my DVR, or watch available movies on <a href="http://www.tcm.com/watchtcm/">Watch TCM</a> or my cable provider's on-demand service.</p>
<p>It turns out that TCM offers a <a href="http://www.tcm.com/tcmws/v1/docs/welcome.html">web services APIs</a> that allow one to retrieve schedule information. So I've whipped up a script that that grabs the schedule for the next week and identifies the movies that match my preferences.</p>
<p>The result is a web page: <a href="http://secretspacelab.com/tcm.html">What's Good on TCM?</a>.</p>
<p>The page displays the movies coming up during the next week that might be "good" according to my taste. You can click the name of a movie or name of a director, actor, or screenwriter to search the TCM database for more information.</p>
<p>Your preferences may not match mine. That's OK; if you are a computer person, you might be able to customize my script to find your musicals or romances or screwball comedies or whatever it is you want. It's available on GitHub: <a href="https://github.com/kristopherjohnson/kjtcmws">https://github.com/kristopherjohnson/kjtcmws</a>.</p>cxxforth: A Simple Forth Implementation in C++2016-02-23T11:05:48-05:002016-02-23T11:05:48-05:00Kristopher Johnsontag:undefinedvalue.com,2016-02-23:/cxxforth-simple-forth-implementation-c.html<p>Because implementing obsolete programming languages is a great use of my free time, I've created <a href="https://github.com/kristopherjohnson/cxxforth">cxxforth</a>, an implementation of <a href="https://en.wikipedia.org/wiki/Forth_(programming_language)">Forth</a> in <a href="https://en.wikipedia.org/wiki/C%2B%2B">C++</a>.</p>
<p>Inspired by <a href="http://git.annexia.org/?p=jonesforth.git;a=blob;f=jonesforth.S;h=45e6e854a5d2a4c3f26af264dfce56379d401425;hb=HEAD">JONESFORTH</a>, I wrote it in the style of a tutorial. The source code is automatically converted to a pretty-printed format: <a href="https://github.com/kristopherjohnson/cxxforth/blob/master/cxxforth.cpp.md">https://github.com/kristopherjohnson/cxxforth/blob …</a></p><p>Because implementing obsolete programming languages is a great use of my free time, I've created <a href="https://github.com/kristopherjohnson/cxxforth">cxxforth</a>, an implementation of <a href="https://en.wikipedia.org/wiki/Forth_(programming_language)">Forth</a> in <a href="https://en.wikipedia.org/wiki/C%2B%2B">C++</a>.</p>
<p>Inspired by <a href="http://git.annexia.org/?p=jonesforth.git;a=blob;f=jonesforth.S;h=45e6e854a5d2a4c3f26af264dfce56379d401425;hb=HEAD">JONESFORTH</a>, I wrote it in the style of a tutorial. The source code is automatically converted to a pretty-printed format: <a href="https://github.com/kristopherjohnson/cxxforth/blob/master/cxxforth.cpp.md">https://github.com/kristopherjohnson/cxxforth/blob/master/cxxforth.cpp.md</a>.</p>
<p>Now, on to the next waste of my time...</p>A Gforth Interface to the wiringPi Library2016-01-24T13:50:16-05:002016-01-24T13:50:16-05:00Kristopher Johnsontag:undefinedvalue.com,2016-01-24:/gforth-interface-wiringpi-library.html<p>I recently obtained a <a href="https://www.raspberrypi.org">Raspberry Pi</a>, an <a href="https://www.arduino.cc">Arduino</a>, and a model train set. This should keep me busy and out of trouble for a while.</p>
<p>My plan is to use the Arduino to read sensors and control the turnout switches on the track, but I think it is easier to …</p><p>I recently obtained a <a href="https://www.raspberrypi.org">Raspberry Pi</a>, an <a href="https://www.arduino.cc">Arduino</a>, and a model train set. This should keep me busy and out of trouble for a while.</p>
<p>My plan is to use the Arduino to read sensors and control the turnout switches on the track, but I think it is easier to use the RPi to do some initial experimentation with driving relays and other electronic components. I did the typical <a href="https://gist.github.com/kristopherjohnson/1f3c0e70899766447dd0">make-an-LED-blink</a> exercise using the <a href="http://wiringpi.com">wiringPi</a> library. It wasn't difficult, but I wished there was an interactive way to play with the <a href="https://www.raspberrypi.org/documentation/usage/gpio/README.md">GPIO</a> pins, rather than going through a cycle of editing, compiling, and executing a C program whenever I wanted to try something.</p>
<p>Then I remembered <a href="https://en.wikipedia.org/wiki/Forth_%28programming_language%29">Forth</a>. Forth was designed as an interactive language for controlling hardware, and seemed like the perfect solution for me.</p>
<p>After looking at a few possibilities for Forth-based solutions, I decided to use <a href="https://www.gnu.org/software/gforth/">Gforth</a>, as it provides a straightforward way to call functions in the wiringPi library without the need to build my own Forth.</p>
<p>Here is my Gforth interface to the wiringPi library: <a href="https://github.com/kristopherjohnson/wiringPi_gforth">https://github.com/kristopherjohnson/wiringPi_gforth</a></p>
<p>Now, back to the train set...</p>Performing Privileged Operations in an OS X Application2015-12-10T04:16:27-05:002015-12-10T04:16:27-05:00Kristopher Johnsontag:undefinedvalue.com,2015-12-10:/performing-privileged-operations-os-x-application.html<p>I am currently developing an OS X application. The application needs to perform some operations that require root privileges (installing and uninstalling launchd daemons, sending signals to other users' processes, etc.) So I started looking for some documentation about how to do that. I figured it would take a few …</p><p>I am currently developing an OS X application. The application needs to perform some operations that require root privileges (installing and uninstalling launchd daemons, sending signals to other users' processes, etc.) So I started looking for some documentation about how to do that. I figured it would take a few minutes.</p>
<p>A day later, I finally think I understand it. The solution isn't complicated, but learning about it was difficult for a few reasons:</p>
<ul>
<li>Apple's "Authorization Services Programming Guide" is woefully out of date. It tells you to use <code>AuthorizationExecuteWithPrivileges()</code>, but that has been deprecated since OS X 10.7. It also has links to sample code that doesn't exist anymore. (Apple developer documentation people: It seems many of your docs are way out of date. Pls fix.)</li>
<li>Apple provides a <a href="https://developer.apple.com/library/mac/samplecode/SMJobBless/Introduction/Intro.html">SMJobBless</a> sample that purports to demonstrate how to use the new <code>SMJobBless()</code> API that replaces <code>AuthorizationExecuteWithPrivileges()</code>. However, while it does indeed install some kind of service that could in theory run as root, the service it installs doesn't do anything, so it is useless as an example. (But you could waste a lot of time with it before you figure that out.)</li>
<li>A lot of old documentation and sample code for the original low-level XPC API is still floating around, but once you have a clue (which I didn't), you'll just use the high-level <code>NSXPCConnection</code> API.</li>
</ul>
<p>So, in the hope I can help others avoid this tortuous path, here is how i would suggest others learn how to do this:</p>
<ul>
<li>Study the <a href="https://developer.apple.com/library/mac/samplecode/EvenBetterAuthorizationSample/Listings/Read_Me_About_EvenBetterAuthorizationSample_txt.html">EvenBetterAuthorizationSample</a>, which is up to date (as of OS X 10.11, anyway) and actually does something you can test and copy. Especially study the README file, which provides more information about <code>SMJobBless()</code> than any other Apple document.</li>
<li>Watch the <a href="https://developer.apple.com/videos/play/wwdc2012-241/">WWDC 2012 Session 241 "Cocoa Interprocess Communication with XPC"</a> video if you are unfamiliar with XPC.</li>
</ul>
<p>And here is what I wish somebody could have told me before I started:</p>
<ul>
<li>What you will need to do is create a "helper tool" application to perform the privileged operations. This will be a command-line app that creates an XPC listener and handles requests.</li>
<li>The helper app has to have an info.plist and a launchd plist with some magical keys (see the EvenBetterAuthorizationSample for details)</li>
<li>The helper app will be embedded in the main app's package.</li>
<li>The main app will use the <code>AuthorizationCreate()</code> and <code>SMJobBless()</code> functions to authenticate the user and install the helper tool as a privileged launchd service. (This is the point where the system will display a dialog asking the user for an admin username and password.)</li>
<li>After the helper tool is installed, use <code>-[NSXPCConnection initWithMachServiceName:options:]</code> to connect to the helper tool. This will launch the helper tool on demand, running with root privileges.</li>
</ul>Marathon Aborted2015-11-09T14:32:31-05:002015-11-09T14:32:31-05:00Kristopher Johnsontag:undefinedvalue.com,2015-11-09:/marathon-aborted.html<p>A few months ago, I signed up for the <a href="http://www.battlefieldmarathon.com">Chickamauga Battlefield Marathon</a>, which will take place this Saturday (November 14). I thought I would have adequate time to train, but it hasn't worked out that way. I ran a half marathon last week, with a time of 2:30 (not …</p><p>A few months ago, I signed up for the <a href="http://www.battlefieldmarathon.com">Chickamauga Battlefield Marathon</a>, which will take place this Saturday (November 14). I thought I would have adequate time to train, but it hasn't worked out that way. I ran a half marathon last week, with a time of 2:30 (not great) and in my longer training runs over the past month, I've found that after mile 12, I have to do a lot of walking.</p>
<p>So I've canceled my hotel reservations and will not be running that marathon this weekend. Instead, I've decided I will concentrate on the half-marathon distance. When I get that down to around 2 hours, I'll start thinking about a full marathon again.</p>A Brainf__k compiler in C++2015-10-11T14:41:53-04:002015-10-11T14:41:53-04:00Kristopher Johnsontag:undefinedvalue.com,2015-10-11:/brainfk-compiler-c.html<p>For fun, I wrote an interpreter for <a href="https://en.wikipedia.org/wiki/Brainfuck">a programming language with a rude name</a>, in C++.</p>
<p>It's available here: <a href="https://gist.github.com/kristopherjohnson/e5fc3d19c251dc561f62">https://gist.github.com/kristopherjohnson/e5fc3d19c251dc561f62</a></p>
<p>There are other C++ interpreters for this language, but most of them look a lot more like C than like C++. My goal was to …</p><p>For fun, I wrote an interpreter for <a href="https://en.wikipedia.org/wiki/Brainfuck">a programming language with a rude name</a>, in C++.</p>
<p>It's available here: <a href="https://gist.github.com/kristopherjohnson/e5fc3d19c251dc561f62">https://gist.github.com/kristopherjohnson/e5fc3d19c251dc561f62</a></p>
<p>There are other C++ interpreters for this language, but most of them look a lot more like C than like C++. My goal was to avoid archaic C-isms like pointers, fixed-size arrays, global variables, and the use of C stdio and UNIX functions, and write something that looks like "modern C++" (which may be an oxymoron). </p>
<p>C++ beginners may prefer to look at the code for a simplified implementation that represents my first pass at the problem. That is available here: <a href="https://gist.github.com/kristopherjohnson/55092ba62e82c2166125">https://gist.github.com/kristopherjohnson/55092ba62e82c2166125</a></p>
<p>A collection of BF example programs is available here: <a href="http://esoteric.sange.fi/brainfuck/bf-source/prog/">http://esoteric.sange.fi/brainfuck/bf-source/prog/</a>. It pleases me that the ones I've tried all work with my interpreter. The <a href="http://esoteric.sange.fi/brainfuck/bf-source/prog/hanoi.bf">hanoi.bf</a> program is especially impressive.</p>Labor Day Run for Autism Half Marathon 20152015-09-07T22:57:04-04:002015-09-07T22:57:04-04:00Kristopher Johnsontag:undefinedvalue.com,2015-09-07:/labor-day-run-autism-half-marathon-2015.html<p>Today I ran the "Labor Day Run for Autism Half Marathon" at Fowler Park/Big Creek Greenway.</p>
<p>My time was 2:37. Not as good as I had expected. I was hoping for a time in the 2:20-2:30 range. I ran out of gas around mile 9, and …</p><p>Today I ran the "Labor Day Run for Autism Half Marathon" at Fowler Park/Big Creek Greenway.</p>
<p>My time was 2:37. Not as good as I had expected. I was hoping for a time in the 2:20-2:30 range. I ran out of gas around mile 9, and walked a lot of the last few miles.</p>
<p>But, the November marathon for which I'm training still seems doable.</p>A Clock, using D3.js2015-08-09T21:47:27-04:002015-08-09T21:47:27-04:00Kristopher Johnsontag:undefinedvalue.com,2015-08-09:/clock-using-d3js.html<p>Hey, look! I made a clock! The Apple Watch is now obsolete.</p>
<iframe width="100%" height="400" src="//jsfiddle.net/oldmankris/0sgjf7kj/embedded/result,js,css,html/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
<p>I am learning how to use the <a href="http://d3js.org">D3.js</a> library to do data visualizations. It doesn't really make sense to consider hour, minute, and second as data to be "visualized", but this was a simple exercise to learn …</p><p>Hey, look! I made a clock! The Apple Watch is now obsolete.</p>
<iframe width="100%" height="400" src="//jsfiddle.net/oldmankris/0sgjf7kj/embedded/result,js,css,html/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
<p>I am learning how to use the <a href="http://d3js.org">D3.js</a> library to do data visualizations. It doesn't really make sense to consider hour, minute, and second as data to be "visualized", but this was a simple exercise to learn how to create graphical elements and animate them as the data changes.</p>
<p>If you're interested, you can click the <strong>JavaScript</strong>, <strong>CSS</strong>, and <strong>HTML</strong> links at the top to see exactly how it is implemented. (Note that JSFiddle is magically importing the D3 library, so you don't see that in the HTML.)</p>
<p>Here is another way to view the clock along with its source: <a href="http://bl.ocks.org/kristopherjohnson/1db0d39a9ed54d224ee7">http://bl.ocks.org/kristopherjohnson/1db0d39a9ed54d224ee7</a></p>Setting Up a Personal TiddlyWiki Server on OS X2015-04-02T12:17:28-04:002015-04-02T12:17:28-04:00Kristopher Johnsontag:undefinedvalue.com,2015-04-02:/setting-personal-tiddlywiki-server-os-x.html<p>For a new job, I decided to set up a personal <a href="http://en.wikipedia.org/wiki/Wiki">wiki</a> to keep notes. I wanted to keep it simple, meeting these requirements:</p>
<ul>
<li>All the data is in a Dropbox folder (so it can be automatically synced between machines)</li>
<li>It must support <a href="http://daringfireball.net/projects/markdown/">Markdown</a> syntax</li>
</ul>
<p>After looking at the options …</p><p>For a new job, I decided to set up a personal <a href="http://en.wikipedia.org/wiki/Wiki">wiki</a> to keep notes. I wanted to keep it simple, meeting these requirements:</p>
<ul>
<li>All the data is in a Dropbox folder (so it can be automatically synced between machines)</li>
<li>It must support <a href="http://daringfireball.net/projects/markdown/">Markdown</a> syntax</li>
</ul>
<p>After looking at the options, I settled on <a href="http://tiddlywiki.com">TiddlyWiki</a>. I've used "<a href="http://classic.tiddlywiki.com">classic TiddlyWiki</a>" before, and liked its simplicity, but I was always a little annoyed with the weird steps you have to go through to save changes. The new version of TiddlyWiki ("TiddlyWiki5") includes support for running it as an HTTP server, so you can use it just like an online wiki.</p>
<p>But it took me a couple of hours to figure out how to set that up. The TiddlyWiki documentation is not clear ("not clear" is a euphemistic way of saying "terrible"). So I've written up these instructions in the hope it will spare somebody else all the frustration I had.</p>
<h2>Prerequisites</h2>
<p>The following instructions assume you are using a Mac running OS X, and that you know how to use the Terminal to run commands and how to create and edit text files.</p>
<p>(If you're using Linux or BSD, you can probably figure out what you need to do differently. If you're running Windows, you have my sympathy.)</p>
<h3>Dropbox</h3>
<p>Dropbox is not required to run TiddlyWiki, but I use it so that my personal notes will be available on all my machines.</p>
<p>If you don't already have Dropbox, go to <a href="https://www.dropbox.com">https://www.dropbox.com</a> to get started.</p>
<h3>Node</h3>
<p>The TiddlyWiki server requires <a href="nodejs.org">Node</a>, so you will need to install that if you don't already have it.</p>
<p>If you are already using <a href="http://brew.sh">Homebrew</a>, then installation is as easy as this:</p>
<div class="highlight"><pre><span></span><code>brew install node
</code></pre></div>
<p>If you aren't using Homebrew, then go to <a href="https://nodejs.org">https://nodejs.org</a> and click the <em>Install</em> button.</p>
<h3>Time Machine</h3>
<p>The first rule of using TiddlyWiki is <strong><a href="http://tiddlywiki.com/static/The%2520First%2520Rule%2520of%2520Using%2520TiddlyWiki.html">back up your data</a></strong>. Using Dropbox serves as a rudimentary backup system, but it's not a real backup system.</p>
<p>If you haven't already set up Time Machine on your Mac, then go do it <em>right now</em>. See <a href="https://support.apple.com/en-us/HT201250">https://support.apple.com/en-us/HT201250</a> for details.</p>
<h2>Installing TiddlyWiki</h2>
<p>The TiddlyWiki server is available as an <a href="https://www.npmjs.com/package/tiddlywiki">NPM module</a>, so once you have Node installed, all you have to do is this:</p>
<div class="highlight"><pre><span></span><code>npm install -g tiddlywiki
</code></pre></div>
<p>You can do this to verify it is installed and usable:</p>
<div class="highlight"><pre><span></span><code>tiddlywiki --help
</code></pre></div>
<h2>Initializing Your Wiki Directory</h2>
<p>You'll need to decide where to store your TiddlyWiki data. As I'm using Dropbox, I'll store everything in <code>/Users/kdj/Dropbox/tw</code>, but you can use whatever directory makes sense for you.</p>
<p>Run this command to initialize the directory for a TiddlyWiki server:</p>
<div class="highlight"><pre><span></span><code>tiddlywiki /Users/kdj/Dropbox/tw --init server
</code></pre></div>
<p>Note: you can run <code>tiddlywiki --editions</code> to see if any edition other than <code>server</code> might serve as a better starting point for you. I know that <code>server</code> works.</p>
<p>After running the above command, you should see that the specified directory contains a <code>tiddlywiki.info</code> file. This is the configuration file that controls how the server works.</p>
<h2>Enabling the Markdown Plugin</h2>
<p>TiddlyWiki's <a href="http://tiddlywiki.com/plugins/tiddlywiki/markdown/">Markdown plugin</a> is included with the distribution, but is not enabled by default. To enable it, you have to edit your <code>tiddlywiki.info</code> file and add <code>"tiddlywiki/markdown"</code> to the <code>plugins</code> section. When you have finished editing, the file should look like this:</p>
<div class="highlight"><pre><span></span><code>{
"description": "Basic client-server edition",
"plugins": [
"tiddlywiki/tiddlyweb",
"tiddlywiki/filesystem",
"tiddlywiki/codemirror",
"tiddlywiki/highlight",
"tiddlywiki/markdown"
],
"themes": [
"tiddlywiki/vanilla",
"tiddlywiki/snowwhite"
]
}
</code></pre></div>
<h2>Running the Server</h2>
<p>With everything set up, you can do this to run the server:</p>
<div class="highlight"><pre><span></span><code>tiddlywiki /Users/kdj/Dropbox/tw --server 19671
</code></pre></div>
<p>And then view it in a web browser: <a href="http://localhost:19671">http://localhost:19671</a></p>
<p>Run <code>tiddlywiki --help server</code> to see what other options are available. You may want to use a different port, set a username/password, or otherwise customize the behavior.</p>
<h2>Starting the Server Automatically When You Log In</h2>
<p>It would get annoying to have to type "<code>tiddlywiki /Users/kdj/Dropbox/tw --server 19671</code>" every time you wanted to use your personal wiki. Let's create a <a href="https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man5/launchd.plist.5.html#//apple_ref/doc/man/5/launchd.plist">launchd configuration file</a> in the <code>~/Library/LaunchAgents</code> directory that will cause it to be automatically started every time we log in.</p>
<p>Go to your <code>~/Library/LaunchAgents</code> directory and create a file named <code>com.tiddlywiki.plist</code> with the following contents, substituting the appropriate path for your data directory and the paths to the <code>node</code> and <code>tiddlywiki</code> executables. (Run <code>which node</code> and <code>which tiddlywiki</code> if you don't know what those paths are.)</p>
<div class="highlight"><pre><span></span><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="cp"><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span>
<span class="nt"><plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">></span>
<span class="nt"><dict></span>
<span class="nt"><key></span>Label<span class="nt"></key></span>
<span class="nt"><string></span>com.tiddlywiki<span class="nt"></string></span>
<span class="nt"><key></span>ProgramArguments<span class="nt"></key></span>
<span class="nt"><array></span>
<span class="nt"><string></span>/usr/local/bin/node<span class="nt"></string></span>
<span class="nt"><string></span>/usr/local/bin/tiddlywiki<span class="nt"></string></span>
<span class="nt"><string></span>/Users/kdj/Dropbox/tw<span class="nt"></string></span>
<span class="nt"><string></span>--server<span class="nt"></string></span>
<span class="nt"><string></span>19671<span class="nt"></string></span>
<span class="nt"></array></span>
<span class="nt"><key></span>EnvironmentVariables<span class="nt"></key></span>
<span class="nt"><dict></span>
<span class="nt"><key></span>PATH<span class="nt"></key></span>
<span class="nt"><string></span>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin<span class="nt"></string></span>
<span class="nt"></dict></span>
<span class="nt"><key></span>RunAtLoad<span class="nt"></key></span>
<span class="nt"><true/></span>
<span class="nt"><key></span>WorkingDirectory<span class="nt"></key></span>
<span class="nt"><string></span>/Users/kdj/Dropbox/tw<span class="nt"></string></span>
<span class="nt"><key></span>StandardErrorPath<span class="nt"></key></span>
<span class="nt"><string></span>error.log<span class="nt"></string></span>
<span class="nt"><key></span>StandardOutputPath<span class="nt"></key></span>
<span class="nt"><string></span>output.log<span class="nt"></string></span>
<span class="nt"></dict></span>
<span class="nt"></plist></span>
</code></pre></div>
<p>After saving that file, log out and then log back in, and try to visit <a href="http://localhost:19671">http://localhost:19671</a>. If it works, great! If not, look for an <code>error.log</code> or <code>output.log</code> file in your data directory that may explain what went wrong.</p>
<h2>Restarting the Server</h2>
<p>Unfortunately, using Dropbox to sync TiddlyWiki data between machines does not work as expected. The TiddlyWiki server does not monitor changes to the filesystem, so even though Dropbox will copy changed files between machines, each TiddlyWiki instance just keeps displaying whatever data it read when it was launched.</p>
<p>So, after saving changes on one machine, we have to restart the TiddlyWiki server on the other machines to have those changes displayed everywhere.</p>
<p>The TiddlyWiki developers <a href="http://tiddlywiki.narkive.com/npq5d9XI/tw-tiddlywiki-desktop-command-to-restart-node-instances">may eventually fix this</a>, but in the meantime, we can define some shell commands to make it easy to restart the server when necessary. Add these lines to your <code>~/.bashrc</code> file:</p>
<div class="highlight"><pre><span></span><code><span class="k">export</span><span class="w"> </span><span class="n">TWPLIST</span><span class="o">=~/</span><span class="n">Library</span><span class="o">/</span><span class="n">LaunchAgents</span><span class="o">/</span><span class="n">com</span><span class="o">.</span><span class="n">tiddlywiki</span><span class="o">.</span><span class="n">plist</span><span class="w"></span>
<span class="n">alias</span><span class="w"> </span><span class="n">twstart</span><span class="o">=</span><span class="s2">"launchctl load $TWPLIST"</span><span class="w"></span>
<span class="n">alias</span><span class="w"> </span><span class="n">twstop</span><span class="o">=</span><span class="s2">"launchctl unload $TWPLIST"</span><span class="w"></span>
<span class="n">alias</span><span class="w"> </span><span class="n">twreload</span><span class="o">=</span><span class="s2">"twstop && sleep 1 && twstart && echo 'TiddlyWiki restarted'"</span><span class="w"></span>
</code></pre></div>
<p>With these definitions, you can just execute <code>twreload</code> and refresh your browser whenever you sit down at one of your computers, and that local TiddlyWiki will refresh itself from Dropbox.</p>
<h2>Further Steps</h2>
<p>Once you have everything working, explore the <a href="http://tiddlywiki.com">TiddlyWiki</a> website to learn more about how to use it.</p>
<p>One of the first things you'll want to do is click the gear icon to go to the Control Panel and customize the site title and other settings.</p>Getting Root on Huawei U8665 Fusion 2 Phone2015-02-20T00:33:07-05:002015-02-20T00:33:07-05:00Kristopher Johnsontag:undefinedvalue.com,2015-02-20:/getting-root-huawei-u8665-fusion-2-phone.html<p>I've had an old <a href="https://undefinedvalue.com/2011/09/11/my-visit-android-land">Samsung Galaxy S Captivate</a> phone, running Android 2.2, that I've used as a test device while developing Android apps. In my new job, I no longer need to support Android 2.2 (hooray!), but I do need to support Android 2.3 (boo!). I tried …</p><p>I've had an old <a href="https://undefinedvalue.com/2011/09/11/my-visit-android-land">Samsung Galaxy S Captivate</a> phone, running Android 2.2, that I've used as a test device while developing Android apps. In my new job, I no longer need to support Android 2.2 (hooray!), but I do need to support Android 2.3 (boo!). I tried installing <a href="http://www.cyanogenmod.org">CyanogenMod</a> to update the Captivate to Android 2.3, but I ended up bricking the device.</p>
<p>Rather than spend more time trying to figure out how to fix that, I looked around for a cheap new Android 2.3 device, and found the <a href="http://www.amazon.com/Unlocked-Android-Touchscreen-Bluetooth-Microsd/dp/B00BGYOO60">Huawei U8865</a>, also known as the "Fusion 2", for $60. That seemed like a reasonable price for a brand-new old phone, so I purchased it and it arrived the next day.</p>
<p>Unfortunately, when I tried using it for development, I hit a snag. My work requires using <a href="http://developer.android.com/tools/help/adb.html"><code>adb shell</code></a> and related utilities, and whenever I tried those, I just got a "permission denied" response. I couldn't even take a screenshot.</p>
<p>I looked around for instructions to "root" the phone (that is, get privileged access). They aren't hard to find, but the people who write up these instructions all assume that (a) their readers have no idea what they are doing, and (b) everybody uses Windows. Neither of those are true for me, so I had to translate those instructions into developer-speak.</p>
<p>So, anyway, if you are running Mac OS X and you already know how to use Terminal and <code>adb</code>, then here are instructions for you:</p>
<p>(These instructions are based on those at <a href="http://androidforums.com/threads/step-by-step-root-walkthrough-for-huawei-fusion-2.685504/">http://androidforums.com/threads/step-by-step-root-walkthrough-for-huawei-fusion-2.685504/</a>.)</p>
<ol>
<li>Download <a href="https://www.dropbox.com/s/qtc37pley0vinj8/Huawei-Fusion-2-Recovery-Root.zip">https://www.dropbox.com/s/qtc37pley0vinj8/Huawei-Fusion-2-Recovery-Root.zip</a> and extract the contents.</li>
<li>Open Terminal and <code>cd</code> to the extracted <code>Huawei-Fusion-2-Recovery-Root/Huawei-Fusion-2-Recovery-Root</code> directory</li>
<li>Turn off phone</li>
<li>Reboot phone into fastboot by holding Volume Down and Power buttons simultaneously for 10-20 seconds. (It will freeze at the AT&T logo.)</li>
<li>Connect phone to computer.</li>
<li>Type <code>fastboot devices</code> to verify phone is connected.</li>
<li>Type <code>fastboot flash recovery recovery.superrecovery.img</code></li>
<li>After it finishes, unplug phone.</li>
<li>Remove back of phone, remove battery, wait 15 seconds, then re-insert battery and attach back.</li>
<li>Hold Volume-Up and Power button simultaneously until it boots into recovery (15-20 seconds of holding both buttons)</li>
<li>In recovery menu, select the "reboot device" option with the volume buttons (it should already start with reboot highlighted) and press the power button to reboot.</li>
</ol>
<p>After that, you will "have root". So if you use <code>adb shell</code> to get a command shell on the device, you can run <code>su</code> to get superuser privileges. There is also a new <a href="https://play.google.com/store/apps/details?id=eu.chainfire.supersu&hl=en">SuperSU</a> app on the phone.</p>
<p>But that's not really what I wanted. What I want to do is run <code>adb shell <some-command></code> from the Mac. If you read the <code>adb</code> documentation, you might think you can run <code>adb root</code> to enable that, but you will just get an error message saying "adbd cannot run as root in production builds".</p>
<p>One needs to install an alternative version of <code>adbd</code>. I downloaded and installed this one from Google Play: <a href="https://play.google.com/store/apps/details?id=eu.chainfire.adbd&hl=en">adbd insecure</a>. There is an option to have it automatically grant superuser access at boot, and I enabled that.</p>Monochrome Color Themes for Xcode and Sublime Text2015-02-01T15:38:05-05:002015-02-01T15:38:05-05:00Kristopher Johnsontag:undefinedvalue.com,2015-02-01:/monochrome-color-themes-xcode-and-sublime-text.html<p>A lot of programmers like brightly colored syntax-highlighting themes for their source code editors. I do not. I find colorful high-contrast themes to be fatiguing, distracting, and annoying, so I've gravitated toward low-contrast themes like <a href="http://kippura.org/zenburnpage/">Zenburn</a> and <a href="http://eclipsecolorthemes.org/?view=theme&id=25">Havenjark</a>. But even those feel too "busy" for me.</p>
<p>I've been on a …</p><p>A lot of programmers like brightly colored syntax-highlighting themes for their source code editors. I do not. I find colorful high-contrast themes to be fatiguing, distracting, and annoying, so I've gravitated toward low-contrast themes like <a href="http://kippura.org/zenburnpage/">Zenburn</a> and <a href="http://eclipsecolorthemes.org/?view=theme&id=25">Havenjark</a>. But even those feel too "busy" for me.</p>
<p>I've been on a retrocomputing kick lately, and I've missed the clean look of source code on the monochrome monitors I used in the good old days. So I've created some monochrome Xcode themes. If you like them, you can download them from here:</p>
<ul>
<li><a href="http://kristopherjohnson.github.io/MonochromeXcode/">http://kristopherjohnson.github.io/MonochromeXcode/</a></li>
</ul>
<p>and copy them to your <code>~/Library/Developer/Xcode/UserData/FontAndColorThemes</code> directory, and then select them from the <em>Xcode → Preferences → Fonts & Colors</em> window.</p>
<p>These themes are also available as Sublime Text color schemes:</p>
<ul>
<li><a href="http://kristopherjohnson.github.io/MonochromeSublimeText/">http://kristopherjohnson.github.io/MonochromeSublimeText/</a></li>
</ul>
<p>These were my simple rules for creating the themes:</p>
<ul>
<li>All colors have the same hue. They differ only in saturation and brightness.</li>
<li>Comments are dim.</li>
<li>Keywords are dim, but not as dim as comments.</li>
<li>Numeric and string constants are bright, almost white.</li>
<li>Other textual elements all have the same color.</li>
</ul>
<p>I use <a href="http://adobe-fonts.github.io/source-code-pro/">Source Code Pro Light</a> as my font, as I like the way the thin lines give a vector-graphics look.</p>
<h3>Amber</h3>
<p><img src="http://kristopherjohnson.github.io/MonochromeXcode/images/AmberScreenshot.png" alt="Amber theme screenshot" width="600"></p>
<h3>Green</h3>
<p><img src="http://kristopherjohnson.github.io/MonochromeXcode/images/GreenScreenshot.png" alt="Green theme screenshot" width="600"></p>
<h3>Blue</h3>
<p><img src="http://kristopherjohnson.github.io/MonochromeXcode/images/BlueScreenshot.png" alt="Blue theme screenshot" width="600"></p>
<h3>Blueprint</h3>
<p><img src="http://kristopherjohnson.github.io/MonochromeXcode/images/BlueprintScreenshot.png" alt="Blueprint theme screenshot" width="600"></p>
<h3>daVinci</h3>
<p><img src="http://kristopherjohnson.github.io/MonochromeXcode/images/daVinciScreenshot.png" alt="daVinci theme screenshot" width="600"></p>482015-01-31T02:50:58-05:002015-01-31T02:50:58-05:00Kristopher Johnsontag:undefinedvalue.com,2015-01-31:/48.html<p>I'm writing this yearly update later than I usually do. A few things have been unsettled, and I didn't want to write until I had some idea how they would turn out.</p>
<!--break-->
<p>We had been planning to buy the house we were living in, but that deal fell through in …</p><p>I'm writing this yearly update later than I usually do. A few things have been unsettled, and I didn't want to write until I had some idea how they would turn out.</p>
<!--break-->
<p>We had been planning to buy the house we were living in, but that deal fell through in October, and we had only a couple of weeks to find a new home. (Lesson learned: Get <em>everything</em> in writing.) We are now renting a house in Suwanee, Georgia. We'll probably stay here until Bailey graduates from high school, then buy a house near the school where Pebble works. We were disappointed to lose that house, but it's probably for the best. There were many things wrong with it that we didn't notice until we'd been there a while.</p>
<p>We are still trying to sell our house in Dahlonega. Paying that mortgage and also paying rent has been a bit of a financial hardship, but moving back to Dahlonega isn't a viable option.</p>
<p>Over the past year, I haven't been working as much as I would like. My client's client went through a reorganization, and projects I was going to be working on have been held up as they were reviewed and replanned. This led to several weeks of me repeatedly asking when my next project would start, with the answer always being "any day now, we hope". It's been frustrating and a little scary.</p>
<p>The unexpected idle periods gave me time to reflect on my career path and what kinds of opportunities I should be pursuing. I've been working as an independent contractor, alone, for the past few years, and I realized that I missed being part of a team and having some ownership of the products that I worked on. So I decided I should consider giving up contract work and look for a permanent position.</p>
<p>I was lucky to find a great opportunity. On February 9, I start work as a Senior Software Engineer with <a href="http://mobilelabsinc/">Mobile Labs</a>. I'll be working on systems that let people remotely test mobile apps. I'll get to do development work on Windows, Mac OS X, iOS, and Android. A lot of the work involves reverse-engineering stuff and delving into operating system internals. I'll be working with a small team of experienced people, and I'll get to continue working from home. It seems like a great match for my experience and interests.</p>
<p>My family is doing well. The unexpected move at the end of October was stressful for everyone, but we've settled in to the new house. Bailey can now take the bus to school, and on the occasions when I do need to drive to his school, it is only a couple of miles away. The dogs love the fenced backyard. Pebble enjoys being closer to work.</p>
<p>Bailey traveled to London over Christmas break with his high-school marching band, to participate in the <a href="http://lnydp.com">London New Year's Day Parade</a>. I think he enjoyed it, but it's hard to get any information out of a 15-year-old.</p>
<p>I <a href="https://undefinedvalue.com/2014/03/24/2014-publix-georgia-marathon">ran my first marathon</a> in March. Unfortunately, a few days afterward, I was hit with <a href="http://en.wikipedia.org/wiki/Plantar_fasciitis">plantar fasciitis</a>, which kept me from doing any running at all for a few months and is still pretty painful. I was hoping to run the marathon again this March, but I haven't been able to train for the past couple of months. Maybe I'll run the Savannah marathon in the fall.</p>
<p>My beard is gone. I shaved it off while doing the job-search thing. Haven't decided yet whether to re-grow it.</p>
<p>So, in summary, things have been chaotic for the past few months, but I expect them to settle down. I'll have a steady paycheck and cool new stuff to work on and cool people to work with. We know where we'll be living for the next couple of years. Everybody is happy and healthy.</p>
<p>I've just noticed that this is the tenth <a href="https://undefinedvalue.com/category/tags/birthday">birthday post</a> I've written. Happy Tenth Anniversary to me!</p>A Backup Restoration Story2014-12-29T03:17:16-05:002014-12-29T03:17:16-05:00Kristopher Johnsontag:undefinedvalue.com,2014-12-29:/backup-restoration-story.html<p>For most of my computing lifetime, I didn't bother with backups. They were too much trouble, and back when it took 20 floppy disks to back up a Mac hard disk, they took too much time. But now with services like <a href="http://en.wikipedia.org/wiki/Time_Machine_(OS_X)">Time Machine</a>, <a href="http://www.code42.com/crashplan/">CrashPlan</a>, <a href="https://www.backblaze.com">Backblaze</a>, <a href="https://www.dropbox.com">Dropbox</a>, and <a href="https://www.google.com/drive/">Google Drive</a>, it …</p><p>For most of my computing lifetime, I didn't bother with backups. They were too much trouble, and back when it took 20 floppy disks to back up a Mac hard disk, they took too much time. But now with services like <a href="http://en.wikipedia.org/wiki/Time_Machine_(OS_X)">Time Machine</a>, <a href="http://www.code42.com/crashplan/">CrashPlan</a>, <a href="https://www.backblaze.com">Backblaze</a>, <a href="https://www.dropbox.com">Dropbox</a>, and <a href="https://www.google.com/drive/">Google Drive</a>, it is pretty easy to keep redundant copies of everything. I had files in these locations:</p>
<ul>
<li>my main laptop</li>
<li>my old laptop (given to stepson for schoolwork), which still had all my files on it from the time before I got the new laptop</li>
<li>family iMac, which in addition to having copies of my important files also had an external hard drive that held Time Machine backups for all home computers</li>
<li>CrashPlan (offsite backup)</li>
<li>Dropbox (which is not really a "backup", but it means it's easy to maintain multiple copies of important files)</li>
</ul>
<p>So I thought I was pretty well backed up, until a few events happened in a short period of time:</p>
<ol>
<li>My main laptop's SSD filled up with work-related stuff, so I deleted some big non-work-related stuff (Aperture library, virtual machine images), because I knew I had copies of those things on my old laptop, our iMac, and in CrashPlan offsite backup.</li>
<li>My old laptop died when the kid dumped a glass of milk on it. So that's one old copy gone, but hey, we have others, right?</li>
<li>The external hard drive that held our Time Machine backups failed. So I bought a new external drive and reconfigured Time Machine on all our machines to back up to it. That meant we lost our old Time Machine backups, I didn't worry because I knew I had copies of important stuff on the family iMac, and we'd have fresh new Time Machine backups in no time.</li>
<li>The old family iMac died. We were lucky in that Apple botched the repair, and gave us a brand new machine to replace it, but the downside was that we lost everything that was on that machine's internal hard drive.</li>
</ol>
<p>These events all happened within a month. In hindsight, I wish I'd reacted faster, but at the time, I just thought, "It's OK, we still have other backups."</p>
<p>So, anyway, we get this new iMac, and I figure I can just plug the external Time Machine drive into it and we'll have all our data back. But no: apparently I when I configured all the other family Macs to back themselves up to the family iMac's external drive, I neglected to configure the iMac to back <em>itself</em> up.</p>
<p>I had to fall back to the CrashPlan backup. I am very glad we had it, because otherwise we would have lost our family photo archives and some other important stuff. But the downside is that it has taken about five days to restore everything from CrashPlan. I don't know whether to blame our ISP or CrashPlan for the slowness of the restoration, but being unable to use that new machine for five days has been annoying.</p>
<p>CrashPlan's restoration functions suck. When I set up the CrashPlan app on the new iMac, it asked whether I would like to synchronize that new iMac with the old iMac's backup. "Sure, that would be awesome" I thought, and I clicked Yes. Then it took <em>two days</em> for the synchronization to complete, and during that time I couldn't restore anything.</p>
<p>Then, when synchronization finally completed, I checked the box to restore the entire hard drive and clicked the <em>Restore</em> button. CrashPlan spent a couple of hours counting up how many files that was and how big they were, and then it crashed. I tried again, waited a couple of hours again, and it crashed again. (So you need to have a plan for when CrashPlan crashes.)</p>
<p>Because it apparently couldn't restore the entire hard drive at once, I selectively restored individual folders (<code>Applications</code>, <code>/Users/kdj</code>, <code>/Users/pebble</code>, etc.). This worked, but again I had to sit at the computer for a long time while CrashPlan counted up all the files, because after selecting something, you can't click <em>Restore</em> until it finishes counting them up.</p>
<p>And then after restoring, I noticed some files missing, so I had to go back into CrashPlan and play with the options to get it to restore files from the date our old iMac died, and to include deleted/hidden files.</p>
<p>So, lessons learned:</p>
<ul>
<li>Make sure <em>all</em> machines are backing up to Time Machine. Check this every week or so.</li>
<li>Restoring from CrashPlan sucks. Maybe that's just the nature of restoring a few hundred gigabytes of data over the Internet, but I may look into <a href="https://www.backblaze.com">other options</a> when my annual subscription expires.</li>
<li>When some link in the backup chain breaks, fix it right away.</li>
</ul>The Good Old Days and Tiny BASIC2014-12-21T02:14:03-05:002014-12-21T02:14:03-05:00Kristopher Johnsontag:undefinedvalue.com,2014-12-21:/good-old-days-and-tiny-basic.html<p>This week, we learned that <a href="http://www.drdobbs.com/architecture-and-design/farewell-dr-dobbs/240169421"><em>Dr. Dobb's Journal</em> is shutting down after 38 years</a>. Admittedly, I haven't paid much attention to <em>Dr. Dobb's</em> for the past few years, but back when I was a kid who wanted to be a programmer, I anxiously awaited each monthly issue so that I …</p><p>This week, we learned that <a href="http://www.drdobbs.com/architecture-and-design/farewell-dr-dobbs/240169421"><em>Dr. Dobb's Journal</em> is shutting down after 38 years</a>. Admittedly, I haven't paid much attention to <em>Dr. Dobb's</em> for the past few years, but back when I was a kid who wanted to be a programmer, I anxiously awaited each monthly issue so that I could read every single article multiple times. We didn't have the Internet to give us whatever training we needed whenever we wanted, so magazines like <em>Dr. Dobb's</em> were precious.</p>
<p>While reminiscing about <em>Dr. Dobb's</em> with other grieving Twitter users, somebody brought up the fact that the first issue was titled <a href="http://www.drdobbs.com/architecture-and-design/sourcecode/dr-dobbs-journal-30/30000144">"Dr. Dobb's Journal of Tiny BASIC Calisthenics & Orthodontia: Running Light Without Overbyte"</a> (which I think is the coolest magazine title I've ever heard). It started as a xerographed newsletter to tell people about <a href="http://en.wikipedia.org/wiki/Tiny_BASIC"><em>Tiny BASIC</em></a>, a simplified <a href="http://en.wikipedia.org/wiki/BASIC">BASIC programming language</a> interpreter that could run in 2 or 3 kilobytes of memory. That was an important feature back when personal computers had only 4 kilobytes of memory.</p>
<p>Having just completed an implementation of a <a href="https://github.com/kristopherjohnson/suwaneeforth">Forth programming language interpreter</a> in Apple's new <a href="https://developer.apple.com/swift/">Swift</a> programming language, I got the idea of implementing a Tiny BASIC interpreter in Swift. It didn't make any sense. I didn't want to write any programs in Tiny BASIC. I didn't think anybody else would want to write any programs in Tiny BASIC. There are more important things I could be doing with my free time.</p>
<p>But, apparently, reimplementing 50-year-old programming languages in Swift is my thing now</p>
<p>I did it. I call it "Finch", and if you want it, you can find it here:</p>
<ul>
<li>https://github.com/kristopherjohnson/FinchBasic</li>
</ul>
<p>Useless as it may be, it was an interesting exercise. Most of the Tiny BASIC implementations you find are focused on the original goal: getting a full implementation to fit in a few kilobytes. That isn't an important goal anymore, but I liked the idea of implementing a very-simple programming language. I focused on these goals:</p>
<ul>
<li>Use Swift's high-level abstraction features, rather than writing code that looks like C or assembly language.</li>
<li>Make it easy for new Swift programmers to understand, so it could be useful as a beginner's example or in a tutorial.</li>
<li>Make it easy for people to hack on to add new features.</li>
</ul>
<p>I think I did alright. It's about a thousand lines of code (not counting blank lines and comments), which is smaller than a <a href="http://www.ittybittycomputers.com/IttyBitty/TinyBasic/TinyBasic.c">C-based Tiny BASIC implementation</a> I found, and I don't think I did anything too complicated. I will have to wait and see if anyone else wants to hack on it.</p>
<p>BASIC was the first programming language I learned. I expressed an interest in programming after attending an IBM open house, and my father brought home a BASIC programming manual. I studied it intently, but there weren't any computers around, so it was a while before I could try out what I had learned. But eventually the Radio Shack at the local mall started selling the <a href="http://en.wikipedia.org/wiki/TRS-80">TRS-80</a>, and I could walk into that store and write my very first program:</p>
<div class="highlight"><pre><span></span><code><span class="mf">10</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="s">"TRS-80 SUCKS! "</span><span class="p">;</span><span class="w"></span>
<span class="mf">20</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="mf">10</span><span class="w"></span>
<span class="kr">RUN</span><span class="w"></span>
</code></pre></div>
<p>With great power comes great responsibility.</p>SuwaneeForth: A Forth Implementation in Swift2014-12-14T15:43:13-05:002014-12-14T15:43:13-05:00Kristopher Johnsontag:undefinedvalue.com,2014-12-14:/suwaneeforth-forth-implementation-swift.html<p>Using a new high-level programming language to implement an old low-level language is a strange thing to do, but I've done just that. <em>SuwaneeForth</em> is an implementation of <a href="http://en.wikipedia.org/wiki/Forth_(programming_language)">Forth</a> interpreter, written in <a href="https://developer.apple.com/swift/">Swift</a>. If you are interested in such a thing, you can find it here:</p>
<ul>
<li><a href="https://github.com/kristopherjohnson/suwaneeforth">https://github.com/kristopherjohnson …</a></li></ul><p>Using a new high-level programming language to implement an old low-level language is a strange thing to do, but I've done just that. <em>SuwaneeForth</em> is an implementation of <a href="http://en.wikipedia.org/wiki/Forth_(programming_language)">Forth</a> interpreter, written in <a href="https://developer.apple.com/swift/">Swift</a>. If you are interested in such a thing, you can find it here:</p>
<ul>
<li><a href="https://github.com/kristopherjohnson/suwaneeforth">https://github.com/kristopherjohnson/suwaneeforth</a></li>
</ul>
<p>SuwaneeForth is a translation/port of the system described in "A sometimes minimal FORTH compiler and tutorial for Linux/i386 systems" (a.k.a. "<a href="http://rwmj.wordpress.com/2010/08/07/jonesforth-git-repository/">JONESFORTH</a>") by Richard W.M. Jones. I'd suggest that all programmers read the source to that, as it is a very readable tutorial for bootstrapping a programming language implementation.</p>
<ul>
<li><a href="http://git.annexia.org/?p=jonesforth.git;a=blob_plain;f=jonesforth.S;hb=HEAD">jonesforth.S</a></li>
<li><a href="http://git.annexia.org/?p=jonesforth.git;a=blob_plain;f=jonesforth.f;hb=HEAD">jonesforth.f</a></li>
</ul>
<p>I heartily agree with the first paragraph of <code>jonesforth.S</code>:</p>
<blockquote>
<p>FORTH is one of those alien languages which most working programmers regard in the same way as Haskell, LISP, and so on. Something so strange that they'd rather any thoughts of it just go away so they can get on with writing this paying code. But that's wrong and if you care at all about programming then you should at least understand all these languages, even if you will never use them.</p>
</blockquote>Markingbird: A Markdown Processor for Swift2014-08-18T12:43:35-04:002014-08-18T12:43:35-04:00Kristopher Johnsontag:undefinedvalue.com,2014-08-18:/markingbird-markdown-processor-swift.html<p><a href="https://github.com/kristopherjohnson/Markingbird">Markingbird</a> is a <a href="http://daringfireball.net/projects/markdown/">Markdown</a> processor written in <a href="https://developer.apple.com/swift/">Swift</a> for OS X and iOS. It is a translation/port of the <a href="https://code.google.com/p/markdownsharp/">MarkdownSharp</a> processor used by the <a href="http://blog.stackoverflow.com/2009/12/introducing-markdownsharp/">Stack Overflow</a> website.</p>
<p>(If you have no idea what "Markdown" and "Swift" are, you can just stop reading now.)</p>
<!--break-->
<p>Repository: <a href="https://github.com/kristopherjohnson/Markingbird">https://github.com/kristopherjohnson/Markingbird …</a></p><p><a href="https://github.com/kristopherjohnson/Markingbird">Markingbird</a> is a <a href="http://daringfireball.net/projects/markdown/">Markdown</a> processor written in <a href="https://developer.apple.com/swift/">Swift</a> for OS X and iOS. It is a translation/port of the <a href="https://code.google.com/p/markdownsharp/">MarkdownSharp</a> processor used by the <a href="http://blog.stackoverflow.com/2009/12/introducing-markdownsharp/">Stack Overflow</a> website.</p>
<p>(If you have no idea what "Markdown" and "Swift" are, you can just stop reading now.)</p>
<!--break-->
<p>Repository: <a href="https://github.com/kristopherjohnson/Markingbird">https://github.com/kristopherjohnson/Markingbird</a></p>
<p>See the <a href="https://github.com/kristopherjohnson/Markingbird/blob/master/README.md">README</a> for details on how to use it, and some of the details of how the C# code was translated to Swift.</p>
<p>I can't claim much credit for this project. It is really just a line-by-line translation of <a href="http://blog.stackoverflow.com/2009/12/introducing-markdownsharp/">Jeff Atwood's C# code</a> into Swift. The result is not very pretty, as the C# code is itself based upon translations of Perl and PHP code. I don't recommend this as an example of how to write Swift code, but it does seem to work, and so I hope it is valuable to somebody someday.</p>
<p>This project was useful to me in that it let me write a lot of Swift code without thinking too much about it. It helped me develop Swift-editing muscle memory, quick scanning and correction of Swift syntax issues, and interpretation of the Swift compiler's not-always-helpful error messages. I thought about giving up a few times, as manual translation of 1,800 lines of C# and regular expressions wasn't a great way to spend nights and weekends, but I just couldn't leave it partially finished.</p>
<p>I did the translation by just copying and pasting the C# code into Xcode, and then fixing whatever was wrong. This was a real workout for Xcode 6 beta 5, as the SourceKitService or Xcode itself would often crash or lock up when it encountered C# code.</p>
<p>The most annoying and fiddly part of the process was translating the C# regular-expression strings into Swift. It would be nice if Swift had a "raw" string literal like other languages do, but it doesn't, so I manually transformed each regular expression into a Swift/NSRegularExpression-compatible form. Sublime Text's multiple-selection feature was really helpful for this. I probably could have written some sort of a script to automate the translations, but actually looking at every line was valuable in finding edge cases.</p>
<p>I also discovered a few differences between .NET regular expressions and Cocoa regular expressions. For example, in .NET you can use <code>[ ]</code> (a single space between square brackets) to mean a single space, but <code>NSRegularExpression</code> won't accept this, so I had to use <code>\\p{Z}</code> instead. Also, if a regular expression pattern ends with a <code>|</code> character, .NET apparently ignores it, but <code>NSRegularExpression</code> just lets it match empty strings.</p>
<p>Some of the messiest code that I wrote myself (rather than simply translating) is code that manipulates individual characters and substrings. Swift's <code>String</code> type can make this difficult, due to its extensive Unicode support, so I found myself going back and forth between using <code>String</code> and <code>NSString</code> for string manipulation. The automatic bridging between those two types provided by Swift makes it pretty easy, but it would be nice if somebody made a library of utility functions for dealing with <code>String</code>. (BTW, I strongly recommend that all Swift programmers read Ole Begemann's <a href="http://oleb.net/blog/2014/07/swift-strings/">Strings in Swift</a> article to find out what's going on with <code>String</code>.)</p>
<p>As of now, the <code>SimpleTests</code> test suite from MarkdownSharp works. I plan to port the remaining unit tests from MarkdownSharp, and add a few tests of my own. There are also a few TODOs left in the code that need to be addressed.</p>
<p>I make no promises about the robustness or performance of Markingbird. If you find problems, please submit issues and pull requests via GitHub.</p>
<p>I know some people will want to tell me that using regular expressions for this is bad, and this should be a real parser written in pure Swift. I agree completely: let me know when you have implemented that, and I'll post links to it.</p>F#'s Pipe-Forward Operator in Swift2014-07-14T01:09:09-04:002014-07-14T01:09:09-04:00Kristopher Johnsontag:undefinedvalue.com,2014-07-14:/fs-pipe-forward-operator-swift.html<p><em>Note: At WWDC 2015, Apple announced Swift 2, which includes changes and a new feature called "protocol extensions" that render much of the code below either irrelevant or incorrect. This article applies to Swift 1.x.</em></p>
<p>Apple's new <a href="https://developer.apple.com/swift/">Swift</a> programming language isn't really a <a href="http://en.wikipedia.org/wiki/Functional_programming">functional programming language</a>. However, it does …</p><p><em>Note: At WWDC 2015, Apple announced Swift 2, which includes changes and a new feature called "protocol extensions" that render much of the code below either irrelevant or incorrect. This article applies to Swift 1.x.</em></p>
<p>Apple's new <a href="https://developer.apple.com/swift/">Swift</a> programming language isn't really a <a href="http://en.wikipedia.org/wiki/Functional_programming">functional programming language</a>. However, it does support generic types and functional-programming patterns, so many FP aficionados are implementing their favorite functional idioms and libraries from other programming languages in Swift.</p>
<p>I've been a functional-programming enthusiast for a couple of decades, and I'm playing with this myself. A feature I like in the <a href="http://en.wikipedia.org/wiki/F_Sharp_(programming_language)">F# programming language</a> is its <a href="http://www.kevinberridge.com/2012/12/neat-f-pipe-forward.html">pipe-forward operator</a>, <code>|></code>, which helps to write readable functional code. This article explains what that operator is, why you would want to use it, and how to do it in Swift.</p>
<p>Disclaimer: The code in this article is based upon the versions of the Swift language in Xcode 6.3. Future changes to Swift syntax or its standard library may render all of this incorrect or obsolete.</p>
<!--break-->
<h2>Life without pipe-forward</h2>
<p>Before describing pipe-forward, let's look at an example that demonstrates why we might want it. Say we need to write code that takes an array of integers, filters out the odd numbers, sorts the remaining even numbers in descending order, and then renders the result as a string of comma-separated numbers. Using methods of Swift's <a href="https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/Array.html#//apple_ref/doc/uid/TP40014608-CH5-SW1">Array</a> and <a href="https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/index.html#//apple_ref/doc/uid/TP40014608-CH7-SW1">String</a> classes, we could do it like this:</p>
<div class="highlight"><pre><span></span><code>let numbers = [67, 83, 4, 99, 22, 18, 21, 24, 23, 2, 86]
let result = ", ".join(numbers.filter { $0 % 2 == 0 }
.sorted { $1 < $0 }
.map { $0.description })
</code></pre></div>
<p>The value of <code>result</code> is the string <code>"86, 24, 22, 18, 4, 2"</code>.</p>
<p>That <code>result</code> expression might be a little complicated, but at a glance it is easy to see that it filters the numbers, sorts them, maps the values to strings, and then uses <code>String.join()</code> to construct the result.</p>
<p>If you aren't familiar with using <code>map</code> and <code>filter</code> to transform collections, read <a href="http://robnapier.net/maps">the Cocoaphony article</a>.</p>
<p>Now what if, instead of an <em>array</em> of numbers, we have a <em>sequence</em> of numbers? A sequence doesn't have methods like <code>Array</code> does. Instead we will have to use the <code>filter()</code>, <code>sorted()</code>, and <code>map()</code> free functions provided by the Swift standard library. If we try to write the code as a single expression like we did above, we end up with something like this:</p>
<div class="highlight"><pre><span></span><code><span class="x">let seq = SequenceOf(numbers)</span>
<span class="x">let seqResult =</span>
<span class="x"> ", ".join(map(sorted(filter(seq)</span><span class="cp">{</span><span class="err">$</span><span class="m">0</span> <span class="o">%</span> <span class="m">2</span> <span class="o">==</span> <span class="m">0</span><span class="cp">}</span><span class="x">)</span><span class="cp">{</span><span class="err">$</span><span class="m">1</span> <span class="o"><</span> <span class="err">$</span><span class="m">0</span><span class="cp">}</span><span class="x">)</span><span class="cp">{</span><span class="err">$</span><span class="m">0.d</span><span class="na">escription</span><span class="cp">}</span><span class="x">)</span>
</code></pre></div>
<p>or like this:</p>
<div class="highlight"><pre><span></span><code>let seqResultMultiLine =
", ".join(
map(
sorted(
filter(seq) { $0 % 2 == 0 }
) { $1 < $0 }
) { $0.description })
</code></pre></div>
<p>That's not very readable, is it? We can see at a glance that <code>filter</code>, <code>map</code>, <code>sorted</code>, and <code>join</code> are being used, but we have to read it inside-out and backwards to follow the order of evaluation and figure out which closure goes with which function call and how the data is flowing through those functions. We probably wouldn't even try to write it as a single expression, and would instead write it as a sequence of intermediate results to make it understandable:</p>
<div class="highlight"><pre><span></span><code><span class="n">let</span><span class="w"> </span><span class="n">filteredNumbers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">filter</span><span class="p">(</span><span class="n">seq</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="err">$</span><span class="mi">0</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="n">let</span><span class="w"> </span><span class="n">sortedNumbers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">sorted</span><span class="p">(</span><span class="n">filteredNumbers</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="err">$</span><span class="mi">1</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="err">$</span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="n">let</span><span class="w"> </span><span class="n">numbersAsStrings</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">map</span><span class="p">(</span><span class="n">sortedNumbers</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">(</span><span class="n">n</span><span class="o">:</span><span class="w"> </span><span class="n">Int</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="kt">String</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="n">n</span><span class="p">.</span><span class="n">description</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="n">let</span><span class="w"> </span><span class="n">commaSeparatedResult</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">", "</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">numbersAsStrings</span><span class="p">)</span><span class="w"></span>
</code></pre></div>
<p>That's easier to understand, but it's noisier than the original <code>Array</code>-based code.</p>
<h2>Life with pipe-forward</h2>
<p>Wouldn't it be nice if we could call a function on a value using a postfix notation like we call a method on an object? That is, wouldn't it be nice if we could write something like this?</p>
<div class="highlight"><pre><span></span><code><span class="k">let</span><span class="w"> </span><span class="nv">pipeResult</span><span class="w"> </span><span class="o">=</span><span class="w"></span>
<span class="w"> </span><span class="n">seq</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">filteredWithPredicate</span><span class="w"> </span><span class="o">{</span><span class="w"> </span><span class="o">$</span><span class="mi">0</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">}</span><span class="w"></span>
<span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">sortedByPredicate</span><span class="w"> </span><span class="o">{</span><span class="w"> </span><span class="o">$</span><span class="mi">1</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="o">$</span><span class="mi">0</span><span class="w"> </span><span class="o">}</span><span class="w"></span>
<span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">mappedWithTransform</span><span class="w"> </span><span class="o">{</span><span class="w"> </span><span class="o">$</span><span class="mi">0</span><span class="o">.</span><span class="n">description</span><span class="w"> </span><span class="o">}</span><span class="w"></span>
<span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="nn">String</span><span class="p">.</span><span class="n">join</span><span class="o">(</span><span class="s">", "</span><span class="o">)</span><span class="w"></span>
</code></pre></div>
<p>We can. That <code>|></code> operator is called the "pipe-forward" or "forward pipe" operator in F#.</p>
<p>This:</p>
<div class="highlight"><pre><span></span><code><span class="n">x</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">f</span><span class="w"></span>
</code></pre></div>
<p>is equivalent to this:</p>
<div class="highlight"><pre><span></span><code>f(x)
</code></pre></div>
<p>and this:</p>
<div class="highlight"><pre><span></span><code><span class="n">x</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">f</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">g</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">h</span><span class="w"></span>
</code></pre></div>
<p>is equivalent to this:</p>
<div class="highlight"><pre><span></span><code>h(g(f(x)))
</code></pre></div>
<p>It's just syntactic sugar, but it is often clearer to express a sequence of function calls as a chain read left-to-right/top-to-bottom than as a cluster of nested subexpressions.</p>
<p>It can be defined as an operator in Swift like this:</p>
<div class="highlight"><pre><span></span><code><span class="n">infix</span><span class="w"> </span><span class="n">operator</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">precedence</span><span class="w"> </span><span class="mi">50</span><span class="w"> </span><span class="n">associativity</span><span class="w"> </span><span class="n">left</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="n">public</span><span class="w"> </span><span class="k">func</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="o"><</span><span class="n">T</span><span class="p">,</span><span class="n">U</span><span class="o">></span><span class="p">(</span><span class="n">lhs</span><span class="p">:</span><span class="w"> </span><span class="n">T</span><span class="p">,</span><span class="w"> </span><span class="n">rhs</span><span class="p">:</span><span class="w"> </span><span class="n">T</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">U</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">U</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">rhs</span><span class="p">(</span><span class="n">lhs</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>We have some work to do before we can evaluate <code>pipeResult</code> above with our <code>|></code> operator. The problem is that the <code>filter()</code>, <code>sorted()</code>, and <code>map()</code> functions provided by the Swift standard library don't have their parameters in the order we need, and they are not written to support <a href="http://en.wikipedia.org/wiki/Partial_application">partial application</a>. For example, let's look at the first subexpression:</p>
<div class="highlight"><pre><span></span><code><span class="n">seq</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">filteredWithPredicate</span><span class="w"> </span><span class="o">{</span><span class="w"> </span><span class="o">$</span><span class="mi">0</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">}</span><span class="w"></span>
</code></pre></div>
<p>The pipe-forward operator transforms it to this:</p>
<div class="highlight"><pre><span></span><code>filteredWithPredicate({ $0 % 2 == 0 })(seq)
</code></pre></div>
<p>So we need <code>filteredWithPredicate</code> to be a <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_591">curried function</a> that takes the predicate as the first argument and sequence as second argument. Unfortunately, the library's <code>fillter/sorted/map</code> functions are not curried and are called with the sequence as the first argument and function as second argument. To make the <code>pipeResult</code> expression compile and run, we need to provide some "adapter functions" that have the necessary signatures:</p>
<div class="highlight"><pre><span></span><code><span class="o">//</span><span class="w"> </span><span class="n">Curried</span><span class="w"> </span><span class="n">adapter</span><span class="w"> </span><span class="k">function</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">Swift</span><span class="w"> </span><span class="n">Standard</span><span class="w"> </span><span class="n">Library</span><span class="s1">'s filter() function</span>
<span class="s1">public func filteredWithPredicate<S : SequenceType></span>
<span class="s1"> (includeElement: (S.Generator.Element) -> Bool)</span>
<span class="s1"> (source: S)</span>
<span class="s1"> -> [S.Generator.Element]</span>
<span class="s1">{</span>
<span class="s1"> return filter(source, includeElement)</span>
<span class="s1">}</span>
<span class="s1">// Curried adapter function for Swift Standard Library'</span><span class="n">s</span><span class="w"> </span><span class="n">sorted</span><span class="p">()</span><span class="w"> </span><span class="k">function</span><span class="w"></span>
<span class="k">public</span><span class="w"> </span><span class="n">func</span><span class="w"> </span><span class="n">sortedByPredicate</span><span class="o"><</span><span class="n">S</span><span class="w"> </span><span class="err">:</span><span class="w"> </span><span class="n">SequenceType</span><span class="o">></span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="nl">predicate</span><span class="p">:</span><span class="w"> </span><span class="p">(</span><span class="n">S</span><span class="p">.</span><span class="n">Generator</span><span class="p">.</span><span class="k">Element</span><span class="p">,</span><span class="w"> </span><span class="n">S</span><span class="p">.</span><span class="n">Generator</span><span class="p">.</span><span class="k">Element</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">Bool</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="nl">source</span><span class="p">:</span><span class="w"> </span><span class="n">S</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="o">[</span><span class="n">S.Generator.Element</span><span class="o">]</span><span class="w"></span>
<span class="err">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">sorted</span><span class="p">(</span><span class="n">source</span><span class="p">,</span><span class="w"> </span><span class="n">predicate</span><span class="p">)</span><span class="w"></span>
<span class="err">}</span><span class="w"></span>
<span class="o">//</span><span class="w"> </span><span class="n">Curried</span><span class="w"> </span><span class="n">adapter</span><span class="w"> </span><span class="k">function</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">Swift</span><span class="w"> </span><span class="n">Standard</span><span class="w"> </span><span class="n">Library</span><span class="err">'</span><span class="n">s</span><span class="w"> </span><span class="k">map</span><span class="p">()</span><span class="w"> </span><span class="k">function</span><span class="w"></span>
<span class="k">public</span><span class="w"> </span><span class="n">func</span><span class="w"> </span><span class="n">mappedWithTransform</span><span class="o"><</span><span class="nl">S</span><span class="p">:</span><span class="w"> </span><span class="n">SequenceType</span><span class="p">,</span><span class="w"> </span><span class="n">T</span><span class="o">></span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="nl">transform</span><span class="p">:</span><span class="w"> </span><span class="p">(</span><span class="n">S</span><span class="p">.</span><span class="n">Generator</span><span class="p">.</span><span class="k">Element</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">T</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="nl">source</span><span class="p">:</span><span class="w"> </span><span class="n">S</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="o">[</span><span class="n">T</span><span class="o">]</span><span class="w"></span>
<span class="err">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="k">map</span><span class="p">(</span><span class="n">source</span><span class="p">,</span><span class="w"> </span><span class="n">transform</span><span class="p">)</span><span class="w"></span>
<span class="err">}</span><span class="w"></span>
</code></pre></div>
<p>These adapter declarations may look complicated, but all we did was copy the signatures of the original functions, then curry the parameters and reverse their order.</p>
<p>Note that we don't need to write an adapter function to use the <code>String.join()</code> method, because instance methods of structs and classes can be used as curried functions. See <a href="http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/">Ole Begemann's article</a> for more details.</p>
<p>With those adapter function declarations, the <code>pipeResult</code> expression works as expected. Complete code that can be pasted into a Swift playground is available here: <a href="https://gist.github.com/kristopherjohnson/ed97acf0bbe0013df8af">https://gist.github.com/kristopherjohnson/ed97acf0bbe0013df8af</a></p>
<p>It might seem like we have done a <em>lot</em> of work just to have a pretty-looking filter-sort-map-reduce expression, but thanks to generic types, all of the above can be reused wherever we need it. All our data-processing code can look like simple transformation pipelines, and that's worth the effort.</p>
<p>In Microsoft's Visual Studio, if you are writing F# code and you type <code>|></code>, the IDE pops up an auto-completion list of all the functions that can be applied to the value on the left, just like typing a <code>.</code> after an object brings up a list of its methods and properties. Xcode can do this too, but without <code>|></code> being a standard Swift operator, few developers will be able to take advantage of it. If you think <code>|></code> and functional auto-complete would be a useful addition to Swift and Xcode, you can submit a bug report to Apple requesting it. (You can <a href="http://www.openradar.me/radar?id=5837161337192448">dupe my radar</a>.)</p>
<h2>Optional chaining</h2>
<p>What if we have functions that return Optionals, and want to pipe those to functions that take non-Optional values? We can define a couple of operators to handle those cases:</p>
<div class="highlight"><pre><span></span><code><span class="n">infix</span><span class="w"> </span><span class="n">operator</span><span class="w"> </span><span class="o">|>!</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">precedence</span><span class="w"> </span><span class="mi">50</span><span class="w"> </span><span class="n">associativity</span><span class="w"> </span><span class="n">left</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="n">infix</span><span class="w"> </span><span class="n">operator</span><span class="w"> </span><span class="o">|>&</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">precedence</span><span class="w"> </span><span class="mi">50</span><span class="w"> </span><span class="n">associativity</span><span class="w"> </span><span class="n">left</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="n">public</span><span class="w"> </span><span class="k">func</span><span class="w"> </span><span class="o">|>!</span><span class="w"> </span><span class="o"><</span><span class="n">T</span><span class="p">,</span><span class="n">U</span><span class="o">></span><span class="p">(</span><span class="n">lhs</span><span class="p">:</span><span class="w"> </span><span class="n">T</span><span class="err">?</span><span class="p">,</span><span class="w"> </span><span class="n">rhs</span><span class="p">:</span><span class="w"> </span><span class="n">T</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">U</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">U</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">rhs</span><span class="p">(</span><span class="n">lhs</span><span class="o">!</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="n">public</span><span class="w"> </span><span class="k">func</span><span class="w"> </span><span class="o">|>&</span><span class="w"> </span><span class="o"><</span><span class="n">T</span><span class="p">,</span><span class="n">U</span><span class="o">></span><span class="p">(</span><span class="n">lhs</span><span class="p">:</span><span class="w"> </span><span class="n">T</span><span class="err">?</span><span class="p">,</span><span class="w"> </span><span class="n">rhs</span><span class="p">:</span><span class="w"> </span><span class="n">T</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">U</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">U</span><span class="err">?</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">lhs</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">rhs</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>The <code>|>!</code> operator unwraps its left-side argument and passes it to the function on the right. This is safe if we know that the Optional value will never be nil.</p>
<p>The <code>|>&</code> operator checks whether the left-side argument is <code>nil</code>. If so, the value of the expression is <code>nil</code>. If not, then the Optional is unwrapped and the right-side function is applied to it. (I'd rather name this operator <code>|>?</code>, but Swift doesn't allow the <code>?</code> character in operator names.) This is the same as just calling <code>Optional.map()</code>, but if we are using a pipeline it is useful to use just pipe operators rather than mixing pipes and method calls.</p>
<p>Here are some simple usage examples, using the Swift Standard Library's <code>find()</code> function, which returns an <code>Int?</code> which is the index of the searched-for value, or <code>nil</code> if not found:</p>
<div class="highlight"><pre><span></span><code><span class="n">let</span><span class="w"> </span><span class="n">elements</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">]</span><span class="w"></span>
<span class="k">func</span><span class="w"> </span><span class="n">reportIndexOfValue</span><span class="p">(</span><span class="n">value</span><span class="p">:</span><span class="w"> </span><span class="n">Int</span><span class="p">)(</span><span class="n">index</span><span class="p">:</span><span class="w"> </span><span class="n">Int</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="nb nb-Type">String</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">let</span><span class="w"> </span><span class="n">message</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Found \(value) at index \(index)"</span><span class="w"></span>
<span class="w"> </span><span class="n">println</span><span class="p">(</span><span class="n">message</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">message</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="n">find</span><span class="p">(</span><span class="n">elements</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">)</span><span class="w"> </span><span class="o">|>!</span><span class="w"> </span><span class="n">reportIndexOfValue</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="s2">"Found 6 at index 2"</span><span class="w"></span>
<span class="n">find</span><span class="p">(</span><span class="n">elements</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="o">|>&</span><span class="w"> </span><span class="n">reportIndexOfValue</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">nil</span><span class="w"></span>
<span class="n">find</span><span class="p">(</span><span class="n">elements</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">)</span><span class="w"> </span><span class="o">|>&</span><span class="w"> </span><span class="n">reportIndexOfValue</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="p">{</span><span class="n">Some</span><span class="w"> </span><span class="s2">"Found 4 at index 1"</span><span class="p">}</span><span class="w"></span>
</code></pre></div>
<h2>Functions with multiple parameters, and tuples</h2>
<p>It might seem that the pipe-forward operator is only useful when passing an argument to a function that takes a single parameter. However, a Swift function can be applied to a tuple of arguments, so you can use a tuple on the left side of <code>|></code> and a function taking multiple arguments on the right side.</p>
<p>In other words, this:</p>
<div class="highlight"><pre><span></span><code><span class="o">(</span><span class="n">x</span><span class="o">,</span><span class="w"> </span><span class="n">y</span><span class="o">,</span><span class="w"> </span><span class="n">z</span><span class="o">)</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">f</span><span class="w"></span>
</code></pre></div>
<p>is equivalent to this:</p>
<div class="highlight"><pre><span></span><code>f(x, y, z)
</code></pre></div>
<p>For example:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">Foundation</span>
<span class="n">func</span> <span class="n">diagonalLength</span><span class="p">(</span><span class="n">width</span><span class="p">:</span> <span class="n">Double</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="n">Double</span><span class="p">)</span> <span class="o">-></span> <span class="n">Double</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">sqrt</span><span class="p">(</span><span class="n">width</span> <span class="o">*</span> <span class="n">width</span> <span class="o">+</span> <span class="n">height</span> <span class="o">*</span> <span class="n">height</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">let</span> <span class="n">length</span> <span class="o">=</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span> <span class="o">|></span> <span class="n">diagonalLength</span> <span class="o">//</span> <span class="n">result</span> <span class="ow">is</span> <span class="mf">5.0</span>
<span class="n">func</span> <span class="n">multiplyAndDivide</span><span class="p">(</span><span class="n">multiplier1</span><span class="p">:</span> <span class="n">Double</span><span class="p">,</span> <span class="n">multiplier2</span><span class="p">:</span> <span class="n">Double</span><span class="p">,</span> <span class="n">divisor</span><span class="p">:</span> <span class="n">Double</span><span class="p">)</span> <span class="o">-></span> <span class="n">Double</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">multiplier1</span> <span class="o">*</span> <span class="n">multiplier2</span> <span class="o">/</span> <span class="n">divisor</span>
<span class="p">}</span>
<span class="n">let</span> <span class="n">value</span> <span class="o">=</span> <span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">50</span><span class="p">)</span> <span class="o">|></span> <span class="n">multiplyAndDivide</span> <span class="o">//</span> <span class="n">result</span> <span class="ow">is</span> <span class="mf">4.0</span>
</code></pre></div>
<p>These can be useful in more complex pipeline expressions that require use of tuples as arguments, results, or intermediate values. We can use <a href="https://gist.github.com/kristopherjohnson/04dbc470e17f67f836a2">zip() or zip3()</a> to combine values from other sequences into tuples to be piped into functions.</p>
<p>You could even do something like this:</p>
<div class="highlight"><pre><span></span><code><span class="k">let</span><span class="w"> </span><span class="nv">evenNumbers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">(</span><span class="n">seq</span><span class="o">,</span><span class="w"> </span><span class="o">{</span><span class="w"> </span><span class="o">$</span><span class="mi">0</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">})</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">filter</span><span class="w"></span>
</code></pre></div>
<p>That seems unnecessarily weird in isolation, but it may be useful in the context of a larger pipe expression.</p>
<h2>For more</h2>
<p>Martin Fowler's <a href="http://martinfowler.com/articles/collection-pipeline/">Collection Pipeline</a> article is a good overview of the general pattern, with examples in multiple programming languages.</p>
<p>For more about F#'s pipe-forward operator and related operators, see the "Function Composition and Pipelining" section of this page: <a href="http://msdn.microsoft.com/en-us/library/dd233229.aspx">http://msdn.microsoft.com/en-us/library/dd233229.aspx</a></p>
<p><a href="https://twitter.com/airspeedswift">@AirspeedSwift</a> has some advanced examples of using pipe-forward with other FP techniques in Swift. See these articles:</p>
<ul>
<li><a href="http://airspeedvelocity.net/2014/12/03/a-straw-man-argument-for-more-trying-functional-programming-in-swift/">A straw man argument for trying more functional-style programming in Swift</a></li>
<li><a href="http://airspeedvelocity.net/2014/12/05/zipwith-pipe-forward-and-treating-functions-like-objects/">zipWith, pipe forward, and treating functions like objects</a></li>
</ul>
<h2>Acknowledgements</h2>
<p>Thanks to <a href="https://twitter.com/gregtitus">Greg Titus</a> and <a href="https://twitter.com/cocoaphony">Rob Napier</a> for assistance and critique.</p>
<h2>Afterword</h2>
<p>The above was originally written with an early beta of Swift. As of Xcode 6 beta 4, the Swift standard library provides a <code>lazy()</code> function that transforms a sequence into an object that has lazily evaluated <code>filter()</code> and <code>map()</code> methods, so we could solve the problem like this:</p>
<div class="highlight"><pre><span></span><code><span class="x">let lazyResult =</span>
<span class="x"> ", ".join(lazy(seq).filter(</span><span class="cp">{</span> <span class="err">$</span><span class="m">0</span> <span class="o">%</span> <span class="m">2</span> <span class="o">==</span> <span class="m">0</span> <span class="cp">}</span><span class="x">).array</span>
<span class="x"> .sorted(</span><span class="cp">{</span><span class="err">$</span><span class="m">1</span> <span class="o"><</span> <span class="err">$</span><span class="m">0</span><span class="cp">}</span><span class="x">)</span>
<span class="x"> .map </span><span class="cp">{</span> <span class="err">$</span><span class="m">0.d</span><span class="na">escription</span> <span class="cp">}</span><span class="x">)</span>
</code></pre></div>
<p>I still prefer the <code>pipeResult</code> expression. I think it looks more like a data-flow illustration. The lazy version has some excess noise (<code>lazy()</code>, <code>.array</code>), and it may be even more mystifying to non-functional programmers than the pipeline notation is.</p>
<h2 id="pipeAdapted">Another afterword</h2>
<p>Above, after the definitions of the "adapter functions", I wrote "all we did was copy the signatures of the original functions, then curry the parameters and reverse their order." That seems like something we should be able to do with a higher-level function, right?</p>
<div class="highlight"><pre><span></span><code><span class="o">//</span><span class="w"> </span><span class="n">Given</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="n">with</span><span class="w"> </span><span class="n">signature</span><span class="w"> </span><span class="p">(</span><span class="n">A</span><span class="p">,</span><span class="w"> </span><span class="n">B</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">T</span><span class="p">,</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">curried</span><span class="w"></span>
<span class="o">//</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="n">with</span><span class="w"> </span><span class="n">signature</span><span class="w"> </span><span class="p">(</span><span class="n">B</span><span class="p">)(</span><span class="n">A</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">T</span><span class="w"></span>
<span class="k">func</span><span class="w"> </span><span class="n">pipeAdapted</span><span class="o"><</span><span class="n">A</span><span class="p">,</span><span class="w"> </span><span class="n">B</span><span class="p">,</span><span class="w"> </span><span class="n">T</span><span class="o">></span><span class="p">(</span><span class="n">f</span><span class="p">:</span><span class="w"> </span><span class="p">(</span><span class="n">A</span><span class="p">,</span><span class="w"> </span><span class="n">B</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">T</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="p">(</span><span class="n">B</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">A</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">f</span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="n">let</span><span class="w"> </span><span class="n">pipeAdaptedResult</span><span class="w"> </span><span class="o">=</span><span class="w"></span>
<span class="w"> </span><span class="n">seq</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">pipeAdapted</span><span class="p">(</span><span class="n">filter</span><span class="p">)({</span><span class="w"> </span><span class="o">$</span><span class="mi">0</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">})</span><span class="w"></span>
<span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">pipeAdapted</span><span class="p">(</span><span class="n">sorted</span><span class="p">)({</span><span class="w"> </span><span class="o">$</span><span class="mi">1</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="o">$</span><span class="mi">0</span><span class="w"> </span><span class="p">})</span><span class="w"></span>
<span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="n">pipeAdapted</span><span class="p">(</span><span class="n">map</span><span class="p">)({</span><span class="w"> </span><span class="o">$</span><span class="mf">0.</span><span class="n">description</span><span class="w"> </span><span class="p">})</span><span class="w"></span>
<span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="nb nb-Type">String</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">", "</span><span class="p">)</span><span class="w"></span>
</code></pre></div>
<p>Here, we use a <code>pipeAdapted()</code> function to transform the standard library's <code>filter</code>, <code>sorted</code>, and <code>map</code> functions into what we need, instead of writing those adapters. If you and your team members are all FP ninjas who don't bat an eye at throwing functions around, then you may prefer this, but my preference is still for the version that uses the adapter functions.</p>
<p>An alternative that may be a little more palatable to non-FP people is to define another pipe operator that lets us specify a function and predicate, like so:</p>
<div class="highlight"><pre><span></span><code><span class="n">infix</span><span class="w"> </span><span class="n">operator</span><span class="w"> </span><span class="o">|>*</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">precedence</span><span class="w"> </span><span class="mi">50</span><span class="w"> </span><span class="n">associativity</span><span class="w"> </span><span class="n">left</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="o">//</span><span class="w"> </span><span class="nb nb-Type">Transform</span><span class="w"> </span><span class="s2">"x |>* (f, predicate)"</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="s2">"f(x, predicate)"</span><span class="w"></span>
<span class="n">public</span><span class="w"> </span><span class="k">func</span><span class="w"> </span><span class="o">|>*</span><span class="w"> </span><span class="o"><</span><span class="n">A</span><span class="p">,</span><span class="w"> </span><span class="n">B</span><span class="p">,</span><span class="w"> </span><span class="n">C</span><span class="p">,</span><span class="w"> </span><span class="n">T</span><span class="o">></span><span class="p">(</span><span class="n">lhs</span><span class="p">:</span><span class="w"> </span><span class="n">A</span><span class="p">,</span><span class="w"> </span><span class="n">rhs</span><span class="p">:</span><span class="w"> </span><span class="p">((</span><span class="n">A</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">B</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">C</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">T</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="n">B</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">C</span><span class="p">))</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">T</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">(</span><span class="n">rhs</span><span class="o">.</span><span class="mi">0</span><span class="p">)(</span><span class="n">lhs</span><span class="p">,</span><span class="w"> </span><span class="n">rhs</span><span class="o">.</span><span class="mi">1</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="n">let</span><span class="w"> </span><span class="n">pipeStarResult</span><span class="w"> </span><span class="o">=</span><span class="w"></span>
<span class="w"> </span><span class="n">seq</span><span class="w"> </span><span class="o">|>*</span><span class="w"> </span><span class="p">(</span><span class="n">filter</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">$</span><span class="mi">0</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">})</span><span class="w"></span>
<span class="w"> </span><span class="o">|>*</span><span class="w"> </span><span class="p">(</span><span class="n">sorted</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">$</span><span class="mi">1</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="o">$</span><span class="mi">0</span><span class="w"> </span><span class="p">})</span><span class="w"></span>
<span class="w"> </span><span class="o">|>*</span><span class="w"> </span><span class="p">(</span><span class="n">map</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">$</span><span class="mf">0.</span><span class="n">description</span><span class="w"> </span><span class="p">})</span><span class="w"></span>
<span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="nb nb-Type">String</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">", "</span><span class="p">)</span><span class="w"></span>
</code></pre></div>
<p>Note that a big problem with either of these approaches is that the Swift compiler takes a very, very, VERY long time to compile them, presumably due to a combinatorial explosion in the type-inferencing mechanism with all those generic types. This may be a temporary issue that will be resolved as the Swift compiler matures, but for now, it's hard to recommend these approaches for general-purpose use.</p>
<p>We can hope that a future version of Swift will provide better built-in support for curried functions and partial application.</p>
<p>Finally, one could skip all this "currying" and "adapter" business and use some closures to define ad-hoc anonymous functions that can be applied to sequences in a pipeline:</p>
<div class="highlight"><pre><span></span><code><span class="k">let</span><span class="w"> </span><span class="nv">closuresResult</span><span class="w"> </span><span class="o">=</span><span class="w"></span>
<span class="w"> </span><span class="n">seq</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="o">{</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">filter</span><span class="o">(</span><span class="n">s</span><span class="o">)</span><span class="w"> </span><span class="o">{</span><span class="w"> </span><span class="o">$</span><span class="mi">0</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">}</span><span class="w"> </span><span class="o">}</span><span class="w"></span>
<span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="o">{</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">sorted</span><span class="o">(</span><span class="n">s</span><span class="o">)</span><span class="w"> </span><span class="o">{</span><span class="w"> </span><span class="o">$</span><span class="mi">1</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="o">$</span><span class="mi">0</span><span class="w"> </span><span class="o">}</span><span class="w"> </span><span class="o">}</span><span class="w"></span>
<span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="o">{</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">map</span><span class="o">(</span><span class="n">s</span><span class="o">)</span><span class="w"> </span><span class="o">{</span><span class="w"> </span><span class="o">$</span><span class="mi">0</span><span class="o">.</span><span class="n">description</span><span class="w"> </span><span class="o">}</span><span class="w"> </span><span class="o">}</span><span class="w"></span>
<span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="nn">String</span><span class="p">.</span><span class="n">join</span><span class="o">(</span><span class="s">", "</span><span class="o">)</span><span class="w"></span>
</code></pre></div>
<p>I still find something like this hard to read, but with more FP-in-Swift experience, it may be as clear as <code>pipeResult</code>. And thankfully, it compiles quickly.</p>KJTipCalculator: A Simple iOS Swift App2014-06-21T15:16:43-04:002014-06-21T15:16:43-04:00Kristopher Johnsontag:undefinedvalue.com,2014-06-21:/kjtipcalculator-simple-ios-swift-app.html<div style="float: right; margin-left: 1em;">
<img alt="Screenshot" src="https://s3.amazonaws.com/undefinedvalue/KJTipCalculatorScreenshot.png" width="161px">
</div>
<p>As an experiment in using Apple's new <a href="https://developer.apple.com/swift/">Swift programming language</a>, I whipped up a simple tip-calculator app for iOS 8.</p>
<p>Yes, the world really needs <em>Yet Another Tip Calculator</em>, and it also really needs <em>Yet Another Swift App Example</em>.</p>
<p>In addition to using Swift, the app also uses an embedded …</p><div style="float: right; margin-left: 1em;">
<img alt="Screenshot" src="https://s3.amazonaws.com/undefinedvalue/KJTipCalculatorScreenshot.png" width="161px">
</div>
<p>As an experiment in using Apple's new <a href="https://developer.apple.com/swift/">Swift programming language</a>, I whipped up a simple tip-calculator app for iOS 8.</p>
<p>Yes, the world really needs <em>Yet Another Tip Calculator</em>, and it also really needs <em>Yet Another Swift App Example</em>.</p>
<p>In addition to using Swift, the app also uses an embedded framework, which is a new feature of iOS 8. The framework contains functions and classes for converting between numbers and text, and it might be useful in other apps.</p>
<p>Source is available on GitHub: <a href="https://github.com/kristopherjohnson/KJTipCalculator">https://github.com/kristopherjohnson/KJTipCalculator</a></p>
<p>If you actually want to use this app, you'll have to build it yourself. But if you have iOS 8 on your phone now, you should know how to do that.</p>A Web Page for Reformatting JSON Text, using AngularJS2014-05-29T03:49:54-04:002014-05-29T03:49:54-04:00Kristopher Johnsontag:undefinedvalue.com,2014-05-29:/web-page-reformatting-json-text-using-angularjs.html<p>A little over a year ago, I published <a href="https://undefinedvalue.com/2013/03/02/web-page-reformatting-json-text">A Web Page for Reformatting JSON Text</a>, which is a simple web page for pretty-printing JSON data. I'm now learning <a href="https://angularjs.org">AngularJS</a>, so as an exercise I reimplemented the page using Angular rather than the original's <a href="http://jquery.com">jQuery</a>.</p>
<p>This version is a little nicer …</p><p>A little over a year ago, I published <a href="https://undefinedvalue.com/2013/03/02/web-page-reformatting-json-text">A Web Page for Reformatting JSON Text</a>, which is a simple web page for pretty-printing JSON data. I'm now learning <a href="https://angularjs.org">AngularJS</a>, so as an exercise I reimplemented the page using Angular rather than the original's <a href="http://jquery.com">jQuery</a>.</p>
<p>This version is a little nicer than the original in the following ways:</p>
<ul>
<li>You can type or paste text into the top box and the formatted JSON immediately appears in the lower box. There is no longer a need to press a <strong>Format</strong> button because Angular's data-binding mechanism takes care of automatically updating the output whenever the input changes. (This is possible with jQuery too, but takes more work.)</li>
<li>You can control the indentation width of the formatted JSON.</li>
<li>When the input is not valid JSON, the output panel changes its background color to red when displaying the error message.</li>
</ul>
<p>Here it is: <a href="https://s3.amazonaws.com/undefinedvalue/ng-formatjson.html">JSON Formatter with AngularJS</a></p>
<p>Short link: <a href="http://bit.ly/formatjson">http://bit.ly/formatjson</a></p>
<p>You can look at the full source here: <a href="https://gist.github.com/kristopherjohnson/176dc5cc09dfc77cd4a6">Gist</a></p>
<p>or as a fiddle: <a href="http://jsfiddle.net/oldmankris/qK9LK/">http://jsfiddle.net/oldmankris/qK9LK/</a></p>
<p>If don't know anything about AngularJS, but are curious about it, look at the source and note the following:</p>
<ul>
<li>The magic of Angular comes from the <code>ng-</code> attributes attached to HTML elements. There is no need for a separate template language or explicit DOM manipulation.</li>
<li>The <a href="https://docs.angularjs.org/api/ng/directive/ngApp">ng-app</a> attribute attached to the <code>html</code> element sets up the <a href="https://docs.angularjs.org/guide/module">module</a> that contains our app's code.</li>
<li>The <a href="https://docs.angularjs.org/api/ng/directive/ngController">ng-controller</a> attribute attached to the <code>div</code> element defines which controller function to use.</li>
<li>The <a href="https://docs.angularjs.org/api/ng/directive/ngModel">ng-model</a> attribute attached to the first <code>textarea</code> element binds its content to the <code>inputText</code> variable</li>
<li>The <a href="https://docs.angularjs.org/api/ng/directive/ngBind">ng-bind</a> attribute attached to the second <code>textarea</code> element binds its content to the value of the <code>outputText</code> variable, and the <a href="https://docs.angularjs.org/api/ng/directive/ngClass">ng-class</a> attribute binds its CSS class to the value of the <code>outputClass</code> variable.</li>
<li>The <a href="https://docs.angularjs.org/api/ng/directive/select">ng-options</a> and <a href="https://docs.angularjs.org/api/ng/directive/ngModel">ng-model</a> directives on the <code>select</code> element bind it to the <code>indentOptions</code> and <code>selectedIndentOption</code> variable.</li>
<li>In the controller code, we set initial values for the <code>inputText</code>, <code>indentOptions</code>, and <code>selectedInputOption</code> variables, and then use <a href="https://docs.angularjs.org/api/ng/type/$rootScope.Scope">$scope.$watch()</a> to update the values of <code>outputText</code> and <code>outputClass</code> whenever any of the inputs change.</li>
</ul>OS X Server Local Websites for Web Developers2014-05-25T12:10:59-04:002014-05-25T12:10:59-04:00Kristopher Johnsontag:undefinedvalue.com,2014-05-25:/os-x-server-local-websites-web-developers.html<p><a href="https://itunes.apple.com/us/app/os-x-server/id714547929">OS X Server</a> is now available free of charge for members of Apple's iOS Developer Program, so I have it installed on most of my machines. Unfortunately, having OS X Server installed complicates the use of the built-in local Apache web server which I use for web development. I've figured …</p><p><a href="https://itunes.apple.com/us/app/os-x-server/id714547929">OS X Server</a> is now available free of charge for members of Apple's iOS Developer Program, so I have it installed on most of my machines. Unfortunately, having OS X Server installed complicates the use of the built-in local Apache web server which I use for web development. I've figured out how to make things work the way I like, and I have written this article so that I can find the information again when I need it. Maybe it will help somebody else too.</p>
<!--break-->
<h2>Enabling Local Websites</h2>
<p>First, to enable the web server, open the OS X Server app, select <strong>Websites</strong> in the sidebar, and flip the switch to <strong>On</strong>. You should be able to visit <a href="http://localhost/">http://localhost/</a> in a web browser and see a page titled "Welcome to OS X Server".</p>
<p>While you are on the Websites configuration screen, you may want to enable PHP and Python web applications.</p>
<p>If you have the Xcode service enabled, then instead of "Welcome to OS X Server" you may see the Xcode Bots page. This may be OK with you, but if you would rather not see the Xcode stuff in place of the default website, then in the Server app, go to the <strong>Websites</strong> screen, double-click <strong>Server Website</strong> to edit it, click the <strong>Edit...</strong> button next to <strong>Index Files</strong>, and remove the <code>/xcode</code> entry. Now, visiting <a href="http://localhost/">http://localhost/</a> should take you to the "Welcome to OS X Server" screen, and you can visit <a href="http://localhost/xcode">http://localhost/xcode</a> to see Xcode Bots.</p>
<h2>Website Directories</h2>
<p>Next, you need to know where you can put your website files so that they will be served by the server. By default, the server will look for files in <code>/Library/Server/Web/Data/Sites/Default</code>. You could put your files in that directory, but I prefer to use symlinks so that I can keep all my files in my home directory. So, for example, if your main website file is <code>/Users/kdj/work/mywebapp/index.html</code>, you can symlink it like this:</p>
<div class="highlight"><pre><span></span><code>ln -s /Users/kdj/work/mywebapp /Library/Server/Web/Data/Sites/Default/mywebapp
</code></pre></div>
<p>Then if you visit <code>http://localhost/mywebapp</code>, you should see your website.</p>
<h2>Per-user Website Directories</h2>
<p>Alternatively, instead of making modifications to the <code>/Library/Server/Web/Data/Sites</code> directory, you might want to set up per-user websites as are available on OS X when Server is not installed and "Web Sharing" is enabled. To do this, you have to edit the <code>https_server_app.conf</code> configuration file. This file is only writable by root by default, so you will need to use <code>sudo</code>. So, for example, if you use <code>vi</code>, then do this to edit the file:</p>
<div class="highlight"><pre><span></span><code>sudo vi /Library/Server/Web/Config/apache2/httpd_server_app.conf
</code></pre></div>
<p>You need to uncomment two lines. First, uncomment the line for <code>apple_userdir_module</code> to enable it:</p>
<div class="highlight"><pre><span></span><code><span class="c1">#Server-specific modules</span><span class="w"></span>
<span class="c1"># SERVER_INSTALL_PATH_PREFIX should be set as Environment variable in launchd.plist</span><span class="w"></span>
<span class="n">LoadModule</span><span class="w"> </span><span class="n">apple_userdir_module</span><span class="w"> </span><span class="o">$</span><span class="p">{</span><span class="n">SERVER_INSTALL_PATH_PREFIX</span><span class="p">}</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">libexec</span><span class="o">/</span><span class="n">apache2</span><span class="o">/</span><span class="n">mod_userdir_apple</span><span class="o">.</span><span class="n">so</span><span class="w"></span>
</code></pre></div>
<p>Then uncomment the line that includes the user configurations:</p>
<div class="highlight"><pre><span></span><code>#<span class="w"> </span><span class="nv">User</span><span class="w"> </span><span class="nv">home</span><span class="w"> </span><span class="nv">directories</span><span class="w"></span>
<span class="k">Include</span><span class="w"> </span><span class="o">/</span><span class="nv">private</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">apache2</span><span class="o">/</span><span class="nv">extra</span><span class="o">/</span><span class="nv">httpd</span><span class="o">-</span><span class="nv">userdir</span>.<span class="nv">conf</span><span class="w"></span>
</code></pre></div>
<p>Then, after stopping and restarting the Websites service, you should be able to access directories under your <code>~/Sites</code> directory. For example, if your user account name is <code>kdj</code>, and you have a website at <code>/Users/kdj/Sites/mywebapp/index.html</code>, then you should be able to visit <a href="http://localhost/~kdj/mywebapp">http://localhost/~kdj/mywebapp</a>.</p>
<h2>MAMP</h2>
<p>If you want to quickly set up an Apache/MySQL/PHP/Perl/Python stack, then check out <a href="http://www.mamp.info/en/">MAMP</a> as an alternative to using the built-in Apache server.</p>
<h2>Credits</h2>
<p>Thanks to Pascal Qyy for providing <a href="http://apple.stackexchange.com/a/59836/1017">an Ask Different answer</a> that was helpful.</p>Zenburn for Xamarin Studio2014-05-20T12:17:37-04:002014-05-20T12:17:37-04:00Kristopher Johnsontag:undefinedvalue.com,2014-05-20:/zenburn-xamarin-studio.html<p>I've been playing around with <a href="http://xamarin.com">Xamarin</a>. Its IDE, Xamarin Studio, comes with a bunch of (IMHO) terrible color schemes, so I ported <a href="http://slinky.imukuppi.org/zenburnpage/">Zenburn</a> to the Xamarin format.</p>
<p>You can find my Zenburn for Xamarin theme here: https://gist.github.com/kristopherjohnson/784360f9676b59766678</p>
<p>To use it, download the <code>.json</code> file, open …</p><p>I've been playing around with <a href="http://xamarin.com">Xamarin</a>. Its IDE, Xamarin Studio, comes with a bunch of (IMHO) terrible color schemes, so I ported <a href="http://slinky.imukuppi.org/zenburnpage/">Zenburn</a> to the Xamarin format.</p>
<p>You can find my Zenburn for Xamarin theme here: https://gist.github.com/kristopherjohnson/784360f9676b59766678</p>
<p>To use it, download the <code>.json</code> file, open Xamarin Studio, go to <strong>Preferences > Syntax Highlighting</strong>, click the <strong>Add</strong> button, and select the file. </p>
<p>It's not a complete rendition of Zenburn. I've only defined the colors that are used in C# and F# code, and haven't bothered with all of the subtle color distinctions between different varieties of syntax elements.</p>2014 Publix Georgia Marathon2014-03-24T10:32:06-04:002014-03-24T10:32:06-04:00Kristopher Johnsontag:undefinedvalue.com,2014-03-24:/2014-publix-georgia-marathon.html<p><img style="float:right; margin-left:1em;" src="http://d3o87qckky4fk8.cloudfront.net/kris_marathon_medal.png" width="300" height="399" alt="Kris with Medal" ></p>
<p>I finished a freaking marathon!</p>
<p>I ran the <a href="http://www.usroadsports.com/Signature/Georgia/">2014 Publix Georgia Marathon</a> in Atlanta on March 23. I got up at 4:00 AM, drove to the <a href="http://www.itsmarta.com">MARTA</a> train station, and took the train to Centennial Park, where the race started and finished.</p>
<p>The weather was good for running a …</p><p><img style="float:right; margin-left:1em;" src="http://d3o87qckky4fk8.cloudfront.net/kris_marathon_medal.png" width="300" height="399" alt="Kris with Medal" ></p>
<p>I finished a freaking marathon!</p>
<p>I ran the <a href="http://www.usroadsports.com/Signature/Georgia/">2014 Publix Georgia Marathon</a> in Atlanta on March 23. I got up at 4:00 AM, drove to the <a href="http://www.itsmarta.com">MARTA</a> train station, and took the train to Centennial Park, where the race started and finished.</p>
<p>The weather was good for running a marathon. Temperatures were in the 50's, and the sky was covered with clouds. The forecast called for rain, but aside from a few sprinkles halfway through the race, we stayed dry.</p>
<p>I have previously run two half marathons with times around 2:10, so I was expecting my marathon time to be somewhere between four-and-a-half and five hours.</p>
<p>That was wildly optimistic.</p>
<p>The Georgia Marathon <a href="http://www.usroadsports.com/Signature/Georgia/PDF/2014_PGM_CourseMap.pdf">course</a> is very hilly. The course I run when training has more hills than the average marathon course, I think, but it didn’t prepare me for this course.</p>
<p>For the first ten miles, I kept to my goal pace of ten and a half minutes per mile. After mile 12, I had to walk up most of the uphill stretches, and there were a <em>lot</em> of uphill stretches. After mile 21, I couldn’t even run on the downhill stretches anymore. My legs would cramp whenever I tried to run. I wasn’t even sure I’d be able to walk those last five miles.</p>
<p>My final time was 5:41, about an hour longer than I expected. I think I did everything right in terms of preparation, nutrition, hydration, and pacing. I just hadn’t built up the endurance to run 26 miles of hills.</p>
<p>But hey, I finished a freaking marathon.</p>
<p>My FitBit says I took over 54,000 steps (including steps walking to/from the marathon). I suspect that will be my daily step record for a while.</p>
<p>On mile 20, some spectators were handing out cups with a brownish liquid. I assumed it was iced tea, but was delighted to find that it was cold beer. I’d like to nominate those people for the Presidential Medal of Freedom.</p>
<p>Action photos available here: <a href="https://www.facebook.com/media/set/?set=a.10152303791522320.1073741825.552697319&type=1&l=8dc4bea3f3">Facebook photo album</a></p>Setting Up ArcGIS Runtime SDK for iOS for Xcode Bot2014-02-08T16:48:55-05:002014-02-08T16:48:55-05:00Kristopher Johnsontag:undefinedvalue.com,2014-02-08:/setting-arcgis-runtime-sdk-ios-xcode-bot.html<p>I am working on an iPad app that uses the <a href="https://developers.arcgis.com/ios/">ArcGIS Runtime SDK for iOS</a>. Esri provides an easy-to-use installer and <a href="https://developers.arcgis.com/ios/info/install.htm">instructions</a> for setting up the SDK so that it can be used with Xcode.</p>
<p>The installer puts the SDK files in the user’s home directory, and the setup …</p><p>I am working on an iPad app that uses the <a href="https://developers.arcgis.com/ios/">ArcGIS Runtime SDK for iOS</a>. Esri provides an easy-to-use installer and <a href="https://developers.arcgis.com/ios/info/install.htm">instructions</a> for setting up the SDK so that it can be used with Xcode.</p>
<p>The installer puts the SDK files in the user’s home directory, and the setup guide recommends using paths like <code>$(HOME)/Library/SDKs/ArcGIS/iOS/</code> in Xcode projects to find the framework’s headers and libraries. This works fine as long as one is using Xcode as a user who has installed the SDK. However, it causes problems if one wants to set up an <a href="https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/xcode_guide-continuous_integration/ConfigureBots/ConfigureBots.html">Xcode bot</a> to automatically build and test the app. The Xcode service that runs bots executes under the <code>_teamsserver</code> user account, and by default that user’s home directory does not exist, and you can’t log in as that user to run the SDK installer. So attempts to create a bot to build the app just result in “ArcGIS.h not found” compilation errors.</p>
<p>After I bit of Googling and experimentation, I came up with a solution:</p>
<ol>
<li>Install the SDK in my user account (or another normal account), following Esri’s installer and instructions.</li>
<li>Make my <code>~/Library</code> directory world-readable:<br><code>chmod 755 ~/Library</code></li>
<li>Create the <code>/var/teamsserver</code> home directory for the <code>_teamsserver</code> user:<br><code>sudo mkdir /var/teamsserver</code></li>
<li>Create the <code>/var/teamsserver/Library</code> directory for the <code>_teamsserver</code> user:<br><code>sudo mkdir /var/teamsserver/Library</code></li>
<li>Set <code>_teamsserver</code> as the owner of the directories:<br><code>sudo chown -R _teamsserver:_teamsserver /var/teamsserver/</code></li>
<li>Set permissions on the directories:<br><code>sudo chmod -R 770 /var/teamsserver/</code></li>
<li>Create a symbolic link to the SDK installation:<br><code>sudo ln -s ~/Library/SDKs /var/teamsserver/Library/SDKs</code></li>
</ol>
<p>This works. It could be argued that it would be better to set up the symbolic links in some shared location (<code>/Library</code>, <code>/usr/local</code>, etc.) and then use a full path in the Xcode project to find them, but I prefer this solution because it doesn’t require any extra steps for developers who don’t need to set up a bot.</p>472014-01-19T19:17:58-05:002014-01-19T19:17:58-05:00Kristopher Johnsontag:undefinedvalue.com,2014-01-19:/47.html<p>Another annual State of the Kris message:</p>
<p>The biggest change for my family this year was moving from our little mountain cabin in Dahlonega to a house in Cumming. This puts us closer to the schools where Pebble teaches and where Bailey learns. Instead of spending two hours commuting per …</p><p>Another annual State of the Kris message:</p>
<p>The biggest change for my family this year was moving from our little mountain cabin in Dahlonega to a house in Cumming. This puts us closer to the schools where Pebble teaches and where Bailey learns. Instead of spending two hours commuting per day, we now spend about 30 minutes.</p>
<p>I used to joke that we should just move to exit 14, since that’s where we spent our weekends shopping. Now we’re here.</p>
<p>Our five dogs love the big fenced-in wooded backyard. They get to chase deer once in a while. (I don't know what a Yorkshire Terrier would do if it caught a deer, but I'm not worried about that happening.)</p>
<p>I ran two half marathons this year, and I’m preparing to run a marathon in March. I’ll probably also try to run the Atlanta Marathon in October. After that, I suspect I will be finished with marathons, but will keep running twenty or thirty miles per week. I enjoy running, and like being in better shape than I have been for the past couple decades.</p>
<p>Pebble is now working at North Forsyth High School, teaching courses in forensics and agricultural science. She seems less stressed-out than she did while working at the middle school.</p>
<p>Bailey is really enjoying the marching band. His band went to a competition in New York, and they took third place. The band will be going to London in December.</p>
<p>I usually make a list of goals for each year. However, I rarely consult that list after making it, so I’m not going to bother this year. I hope to run two marathons, and I hope to learn to play piano, but I really just want to enjoy life in our new home.</p>Garmin Forerunner 110 Blank Screen2014-01-14T15:16:47-05:002014-01-14T15:16:47-05:00Kristopher Johnsontag:undefinedvalue.com,2014-01-14:/garmin-forerunner-110-blank-screen.html<p>I recently purchased a <a href="http://www.amazon.com/Garmin-Forerunner-GPS-Enabled-Sport-Monitor/dp/B003JTLKIA/ref=sr_1_1?s=electronics&ie=UTF8&qid=1389711829&sr=1-1&keywords=garmin+forerunner+110+red">Garmin Forerunner 110</a> GPS watch to track my runs. I've been happy with it, but today when I plugged it into my computer after a run, the watch screen flashed "Saving activity" and then went blank. The watch was dead after that.</p>
<p>It had been fully …</p><p>I recently purchased a <a href="http://www.amazon.com/Garmin-Forerunner-GPS-Enabled-Sport-Monitor/dp/B003JTLKIA/ref=sr_1_1?s=electronics&ie=UTF8&qid=1389711829&sr=1-1&keywords=garmin+forerunner+110+red">Garmin Forerunner 110</a> GPS watch to track my runs. I've been happy with it, but today when I plugged it into my computer after a run, the watch screen flashed "Saving activity" and then went blank. The watch was dead after that.</p>
<p>It had been fully charged before my run, so I was pretty sure it wasn't just out of juice. The instructions provided with the watch weren't helpful.</p>
<p>This blank-screen issue appears to be a common problem. The <a href="https://forums.garmin.com/showthread.php?10639-Forerunner-110-Blank-Screen-(Dead)">Forerunner 110 Blank Screen (Dead)</a> thread in the Garmin forums has over 100 entries. You can browse that thread for details, but here is a summary of the suggested fixes:</p>
<ul>
<li>Hold down the Light, Start/Stop, and Lap/Reset buttons for about 30 seconds. (This worked for me.)</li>
<li>Hold down all four buttons for about 7 seconds. (Didn't work for me.)</li>
<li>Hold down the Light button for about 6 seconds. (Didn't work for me.)</li>
<li>Hold down the "Power" button while plugging it into the computer. (My watch doesn't have a button labeled "Power". I tried this with the Light button, and with the Page/Menu button, but neither worked.)</li>
<li>Plug it into a different USB port. (Didn't work for me.)</li>
<li>Just leave it plugged in for a few days, and eventually it will spring back to life.</li>
<li>Rub the contacts with a pencil eraser.</li>
<li>Attach the USB clip, but don't attach the clip to a USB port, and hold the Light button for 6 seconds.</li>
<li>Check whether there is a <a href="http://connect.garmin.com/firmware">firmware update</a> available.</li>
<li>Try a <a href="http://support.garmin.com/support/searchSupport/case.faces?caseId=%7B0439fb90-a761-11e0-d01c-000000000000%7D">master reset</a>.</li>
<li>Contact Garmin Customer Support.</li>
<li>Take the watch back to where you bought it for replacement/refund.</li>
</ul>
<p>After I got it working, i checked for a <a href="http://connect.garmin.com/firmware">firmware update</a>, and indeed there was a new firmware version available for the Forerunner 110. This is in the release notes:</p>
<blockquote>
<p>Changes from version 2.60 to version 2.70:</p>
<p>Fixed an issue where the device could display a blank screen while connected to the charger.</p>
</blockquote>
<p>I installed the update. I'll have to wait to see whether the problem recurs.</p>
<p><strong>Update 2014-01-23:</strong> The screen went blank again. Hold down the Light, Start/Stop, and Lap/Reset buttons got it working.</p>
<p><strong>Update 2014-03-09:</strong> Blank again, and the above remedies weren't working. The recharging display did appear when I connect it to USB, but it wouldn't do anything when disconnected. I let it sit disconnected for a few hours, and it sprung to life.</p>The Running Thing2014-01-01T20:38:04-05:002014-01-01T20:38:04-05:00Kristopher Johnsontag:undefinedvalue.com,2014-01-01:/running-thing.html<p>A year ago, I was proud of <a href="https://undefinedvalue.com/2013/01/01/couch-potato-running-10k-few-months">running a 10K</a>. Since then, I've run two half marathons, and I am currently preparing to run the <a href="http://www.georgiamarathon.com/">2014 Publix Georgia Marathon</a> in March.</p>
<p>Here are some things I've learned along the way:</p>
<ul>
<li>Having a training plan is important. Just going out and …</li></ul><p>A year ago, I was proud of <a href="https://undefinedvalue.com/2013/01/01/couch-potato-running-10k-few-months">running a 10K</a>. Since then, I've run two half marathons, and I am currently preparing to run the <a href="http://www.georgiamarathon.com/">2014 Publix Georgia Marathon</a> in March.</p>
<p>Here are some things I've learned along the way:</p>
<ul>
<li>Having a training plan is important. Just going out and running a few miles when you feel like it doesn't get you anywhere. You need to steadily build up your mileage. I can personally recommend the <a href="http://www.coolrunning.com/engine/2/2_3/181.shtml">Couch-to-5K</a> plan for beginners, and I am currently in week 7 of <a href="http://www.halhigdon.com/training/51137/Marathon-Novice-1-Training-Program">Hal Higdon's novice marathon training program</a>, which is working well so far. Put your workouts on your calendar, and treat them like any other important appointment.</li>
<li>Rest is important. All the training plans have rest days and "easy weeks" for good reasons. Don't push yourself too far too fast, or you will just hurt yourself.</li>
<li>Stretching is important. I used to roll my eyes at the people who seemed to spend more time with their stretching rituals than actually running, but after trying it myself, I found it was easier to run and I was less sore afterwards.</li>
<li>Shoes are important. When my runs started getting into the five- and six-mile range, I was getting shin splints and other pains. Then I bought some new shoes, and those problems went away. My current favorite shoe is the Asics GEL-Cumulus 15, but you need to try on shoes yourself to find what works for you. I have two pairs of shoes, which I alternate (so that each pair gets 48 hours to dry out and re-spongify between uses), and I replace them after 200-300 miles.</li>
<li>Gadgets are fun. The geek in me likes having instrumentation. Using a <a href="http://www.fitbit.com/">FitBit</a> or a GPS-tracking device to record runs was helpful in sticking to my training plan, because even when I didn't feel like running, I wanted my numbers to be right. I used a couple of smartphone-based run-tracker apps early in my experience, but stopped using them when I got tired of carrying a phone-sized device when running (particularly an Android phone-sized device). I now have a Garmin Forerunner 110 watch that records my time, distance, and heart rate for each run.</li>
<li>Races are fun. You may have to do all or most of your running by yourself, but entering a real race lets you share the experience with hundreds or thousands of other people. It's a festive atmosphere, and everyone is encouraging and supportive.</li>
</ul>
<p>Finally, almost anyone can do this. I'm almost 47 years old, and have led a sedentary lifestyle for the last 30 years, but I will be running my first marathon in a few months. Obviously, if you have serious health problems or physical limitations, you might not be able to do it, but if you're reasonably healthy, it's just a matter of putting in the time.</p>Setting Up Windows, My Way2013-12-22T19:25:36-05:002013-12-22T19:25:36-05:00Kristopher Johnsontag:undefinedvalue.com,2013-12-22:/setting-windows-my-way.html<p>This is a companion piece to my <a href="https://undefinedvalue.com/2013/08/30/setting-new-mac-my-way">Setting Up a New Mac, My Way</a> entry. Thankfully, I haven't had to use Windows much in the past couple of years, but when I do have to set up a Windows machine for some task that requires it, I want to have …</p><p>This is a companion piece to my <a href="https://undefinedvalue.com/2013/08/30/setting-new-mac-my-way">Setting Up a New Mac, My Way</a> entry. Thankfully, I haven't had to use Windows much in the past couple of years, but when I do have to set up a Windows machine for some task that requires it, I want to have a list of things to do to minimize the pain.</p>
<p>(This is a work in progress. It will evolve every time I go through the setup process, and every time I have to work around some annoying Windows limitation.)</p>
<ol>
<li>Install/reinstall Windows. (The remaining steps assume the version is Windows 8.1 Pro x64, and it is running in VMWare on a Mac.)</li>
<li>In the Windows 8 start screen, remove everything except Desktop.</li>
<li>In Desktop, right-click the taskbar, choose Properties, select the Navigation tab, and check the <em>When I sign in or close all apps on a screen, go to the desktop instead of Start</em> box and the <em>Show the Apps view automatically when I go to Start</em> button.</li>
<li>In Settings (or Control Panel or wherever Microsoft puts these things this year) make these changes:</li>
<li>Ensure the Location is <em>United States</em> and <em>English (United States)</em> is the preferred language.</li>
<li>Enable automatic Windows updates.</li>
<li>Change the desktop background to a solid color.</li>
<li>Enable these desktop icons:<ul>
<li>Computer</li>
<li>Network</li>
<li>Recycle Bin</li>
</ul>
</li>
<li>Disable the screen saver and <em>Turn off the display</em> power option</li>
<li>Uncheck <em>Hide extensions for known file types</em></li>
<li>Turn these Windows features on:<ul>
<li>Internet Information Services (leave IIS 6 Management Compatibility off)</li>
<li>Telnet Client</li>
</ul>
</li>
<li>Install these applications:</li>
<li><a href="http://chrome.google.com/">Google Chrome</a><ul>
<li>On first run, choose the <em>Relaunch Chrome on the desktop</em> menu item to get it as a window on the desktop</li>
</ul>
</li>
<li><a href="https://www.dropbox.com/install">Dropbox</a></li>
<li><a href="https://agilebits.com/onepassword/win">1Password</a></li>
<li><a href="http://www.sublimetext.com/3">Sublime Text 3</a></li>
<li><a href="http://www.vim.org/download.php">Vim</a></li>
<li><a href="http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html">PuTTY</a></li>
<li><a href="http://git-scm.com/download/win">Git</a></li>
<li><a href="http://sourcetreeapp.com/">Sourcetree</a></li>
<li><a href="http://www.scootersoftware.com/download.php">Beyond Compare</a></li>
<li><a href="http://www.python.org/getit">Python 2.x</a> and <a href="http://sourceforge.net/projects/numpy/files/">NumPy</a><ul>
<li>Note: Unless you have a good reason, get the 32-bit version, not the 64-bit version. (It can be difficult to get 64-bit versions of third-party modules.)</li>
</ul>
</li>
<li><a href="http://nodejs.org/">Node</a></li>
<li>Start Powershell</li>
<li>Pin Powershell to the taskbar</li>
<li>Type this command to create the profile directory and file:<br><code>new-item -path $profile -type file -force</code></li>
<li>Type this command to copy my profile to this computer:<br><code>cp ~/Dropbox/windows/powershell_profile.ps1 $profile</code></li>
<li>Run Powershell as Administrator and run this command:<br><code>set-executionpolicy -executionpolicy remotesigned</code></li>
<li>Restart Powershell (with the new profile)</li>
<li>... (to be continued) ...</li>
</ol>The Easiest Programming Bug I Spent Way Too Much Time Trying to Solve2013-11-12T21:02:46-05:002013-11-12T21:02:46-05:00Kristopher Johnsontag:undefinedvalue.com,2013-11-12:/easiest-programming-bug-i-spent-way-too-much-time-trying-solve.html<p>I ran across a question on Quora: <a href="http://www.quora.com/Programming-Languages/Whats-the-easiest-programming-bug-you-spent-way-too-much-time-trying-to-solve">What's the easiest programming bug you spent way too much time trying to solve?</a>.</p>
<p>Here is my answer to that question. Some time during my first few years of programming, I wrote code similar to this little snippet and expected it to print …</p><p>I ran across a question on Quora: <a href="http://www.quora.com/Programming-Languages/Whats-the-easiest-programming-bug-you-spent-way-too-much-time-trying-to-solve">What's the easiest programming bug you spent way too much time trying to solve?</a>.</p>
<p>Here is my answer to that question. Some time during my first few years of programming, I wrote code similar to this little snippet and expected it to print "FOO", but instead it prints "None of the above". It is written in C, but should be comprehensible to anyone who knows a language with similar syntax (C++, Java, JavaScript, C#, etc.). It took me about a half day of staring and single-stepping in a debugger to figure out what was wrong. When I looked at the disassembly listing, I was convinced I'd found a bug in the C compiler. Maybe you can figure it out faster than I could.</p>
<div class="highlight"><pre><span></span><code><span class="c1">#include <stdio.h></span><span class="w"></span>
<span class="nb nb-Type">int</span><span class="w"> </span><span class="n">main</span><span class="p">(</span><span class="nb nb-Type">int</span><span class="w"> </span><span class="n">argc</span><span class="p">,</span><span class="w"> </span><span class="nb">char</span><span class="w"> </span><span class="o">*</span><span class="n">argv</span><span class="p">[])</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="nb nb-Type">int</span><span class="w"> </span><span class="n">FOO</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="nb nb-Type">int</span><span class="w"> </span><span class="n">BAR</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="nb nb-Type">int</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">FOO</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">switch</span><span class="w"> </span><span class="p">(</span><span class="n">n</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">FOO</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="n">printf</span><span class="p">(</span><span class="s2">"FOO"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">BAR</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="n">printf</span><span class="p">(</span><span class="s2">"BAR"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">default</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="n">printf</span><span class="p">(</span><span class="s2">"None of the above"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>My HTC One Review2013-11-02T15:08:33-04:002013-11-02T15:08:33-04:00Kristopher Johnsontag:undefinedvalue.com,2013-11-02:/my-htc-one-review.html<p>I've been an iPhone user for the past five years, except for a <a href="https://undefinedvalue.com/2011/09/11/my-visit-android-land">few weeks</a> in 2011 when I used an Android phone to see how good it was. At that time, my evaluation was that Android was a second-rate knock-off of iOS.</p>
<p>But, with the releases of Android 4 …</p><p>I've been an iPhone user for the past five years, except for a <a href="https://undefinedvalue.com/2011/09/11/my-visit-android-land">few weeks</a> in 2011 when I used an Android phone to see how good it was. At that time, my evaluation was that Android was a second-rate knock-off of iOS.</p>
<p>But, with the releases of Android 4.0 "Ice Cream Sandwich" and 4.1 "Jelly Bean", Android got a lot less ugly, and hardware is finally fast enough that it doesn't feel so slow. Because I make about half my income from Android app development, I decided it was time to give Android another try as my personal phone. I vowed to put the iPhone away, buy a top-of-the-line Android phone, and use it for at least 12 months. My goal was to immerse myself in the Android ecosystem and learn how "regular people" use their Android phones.</p>
<p>When I made this purchase in July, the two top-tier Android phones were the Samsung Galaxy S4 and the HTC One. I've hated the two Samsung devices I've owned (Samsung Galaxy S and a Samsung Galaxy Tab 2 7.0), so it was an easy choice.</p>
<p>I won't re-hash the things that all the other HTC One reviews say. These are my personal impressions, and what I do and don't like about the HTC One may not match your preferences.</p>
<!--break-->
<h2>It's Too Big</h2>
<p>Among iOS developers, the "huge Android phone" has been a running joke. Android OEMs have kept pushing the sizes of their screens to four inches, five inches, or more in a weird race, while Apple has stuck to its concept of a small thin device. I didn't want a big phone, but there are a lot of people who do, so I bought a big phone to get a better understanding of the trade-offs.</p>
<p>I thought I would eventually get used to it, but four months later, it's still too big. It's hard to pull it out of my pocket, it's hard to reach the power button to turn it on, it's hard to reach all the places on the screen with my thumb. I find I have to use two hands to really use it, whereas my iPhone was usually a one-handed device.</p>
<p>One of my most valuable Android apps is <a href="https://play.google.com/store/apps/details?id=com.plexnor.gravityscreenoffpro">Gravity Screen Pro</a>. What it does is automatically turn the phone off when I put it in my pocket, and turn it back on when I take it out of my pocket. This is a big deal, because it is difficult to reach the power button.</p>
<p>Sometimes it is nice to have that large screen, especially when browsing the web or reading. But I have a half-dozen other devices around the house that are even better for browsing the web or reading.</p>
<p>HTC recently introduced the <a href="http://www.htc.com/www/smartphones/htc-one-mini/">HTC One mini</a>, which would be a better size for me. Unfortunately, in addition to being a small phone, it is also a cheap phone, without the performance of the One.</p>
<h2>My Phone's Camera is Terrible</h2>
<p>Most of the early reviews of the HTC One praised the low-light capabilities of its camera. So I was surprised when I tried to take some low-light pictures and they looked like everything was in a cloud of purple haze.</p>
<p>Apparently, <a href="http://www.androidauthority.com/htc-one-camera-purple-tint-268232/">some HTC One cameras have this problem</a>, and others don't. HTC will not replace the phone if it has this problem. They claim a forthcoming software update will fix it.</p>
<p>So for now, I just have to live with a crummy camera.</p>
<h2>Carrier-based Updates are Terrible</h2>
<p>In July, Google released Android 4.3. In early October, HTC and AT&T finally released new firmware that included Android 4.3. This was supposed to automatically download to my phone, but after a few weeks, it hadn't.</p>
<p>I contacted AT&T about the problem. They were unhelpful, sending me the same set of instructions four times, then finally telling me to contact HTC.</p>
<p>HTC apologized for my difficulties, and sent me instructions for downloading an update package and installing it on the phone over USB. Unfortunately, their update procedure only works with a Windows computer, which I don't have. They apologized for the inconvenience.</p>
<p>I was eventually able to get the update installed by extracting the ROM image from the update package and using Android developer tools to flash it to the phone from my Mac, using instructions found on various Android hacker sites.</p>
<p>The lesson: Don't buy a phone that makes you dependent upon the carrier and manufacturer to provide software updates. From now on, the only Android phones I buy will be Nexus or Google Play editions, which get timely OS updates directly from Google.</p>
<h2>Android</h2>
<p>Before Android 4.0, Android was ugly, slow, and clunky. Anyone who says otherwise is an idiot.</p>
<p>But Android 4.0 eliminated a lot of the ugliness, and subsequent versions have addressed the slowness and clunkiness. Android is still not as attractive nor pleasant to use as iOS, but it is no longer embarrassingly bad in comparison.</p>
<p>The Google Play store has a much better selection of apps than it did a couple of years ago. Android developers are making a lot of good apps with well-designed user experiences. However, the best Android apps are still not as good as the best iOS apps.</p>
<p>I do like the openness of Android. I have grown dependent on some of the utilities that do things that wouldn't be allowed in iOS apps. For example, the <a href="https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm">Tasker</a> app can automate many activities. I have it set up so that my phone disables the unlock code when it sees my home wi-fi network, and then re-enables it when I leave. I have apps that automatically do things based upon whether I am in my car, or what time of day it is.</p>
<p>I've played around with alternate lockscreen and homescreen apps. I've gone back to the standard ones, but having these choices is nice.</p>
<p>I haven't rooted my phone or installed any custom ROMs. My goal is to use the phone like "regular people" do, and most people don't do that. (Then again, most "regular people" with Android phones don't install many apps or use the web browser.)</p>
<p>It seems to me that Android is getting better faster than iOS is. When my twelve-month enforced-Android-usage period is over, I think I'll probably stay with Android, unless Apple provides some amazing stuff in iOS 8.</p>
<h2>Final Thoughts</h2>
<p>The HTC One is a good product. It's a little big, but it works well.</p>
<p>It's difficult to say whether I'd rather have this HTC One or a new iPhone. The HTC One seems more interesting, but that may be due to its newness. The iPhone is a great product, but it really hasn't changed much since its introduction, except for getting thinner and faster. The current crop of Android phones is <em>a lot</em> better than last year's Android phones, but that's because last year's Android phones were terrible.</p>
<p>The Nexus 5 was released this week. If I were to buy an Android phone today, that's probably what I'd get, but I'm hearing bad things about the camera.</p>Trying Out CyanogenMod on a Samsung Galaxy Tab 2 7.02013-09-29T04:35:59-04:002013-09-29T04:35:59-04:00Kristopher Johnsontag:undefinedvalue.com,2013-09-29:/trying-out-cyanogenmod-samsung-galaxy-tab-2-70.html<p>Last year, I bought a <a href="http://en.wikipedia.org/wiki/Samsung_Galaxy_Tab_2_7.0">Samsung Galaxy Tab 2 7.0</a> for use as an Android development/testing device. I didn't expect it to be good, and it wasn't. It was slow, clunky, and ugly in comparison to the iPad, just like every other Android tablet. But it was cheap …</p><p>Last year, I bought a <a href="http://en.wikipedia.org/wiki/Samsung_Galaxy_Tab_2_7.0">Samsung Galaxy Tab 2 7.0</a> for use as an Android development/testing device. I didn't expect it to be good, and it wasn't. It was slow, clunky, and ugly in comparison to the iPad, just like every other Android tablet. But it was cheap, and I only used it to test apps I was developing, so it didn't bother me much.</p>
<p>Earlier this year, it finally got an upgrade to Jelly Bean, which improved the slow/clunky aspects, but it still wasn't good. I bought a new Nexus 7 in July (which is an awesome Android tablet), so I went to <a href="http://www.gazelle.com">gazelle.com</a> to see what they would give me for a used Galaxy Tab 2 7.0. Their offer: $16.</p>
<p>So, it went into the old-devices drawer. I kept it only because I might need it in an emergency if my Nexus 7 died at the worst possible time.</p>
<p>When all the <a href="http://www.cyanogenmod.org/blog/a_new_chapter">news</a> and <a href="https://plus.google.com/106978520009932034644/posts/L8FJkrcahPs">drama</a> about <a href="http://www.cyanogenmod.org/about">CyanogenMod</a> (CM) broke last week, I decided I would give CyanogenMod a try. I was curious, and I had a device available to "sacrifice".</p>
<p>And you know what? That crummy old Galaxy Tab 2 7.0 is actually a pretty nice tablet with CM installed. I now have an Android 4.2.2 Jelly Bean tablet without all the Samsung TouchWiz crapware. It's not as good as the Nexus 7, but it no longer feels like junk, and it might stay out of that drawer for a while. </p>
<p>If you have an Android device in your junk drawer (or a Kindle Fire, or a NOOK), you might want to give CM a try. Read on if you're interested.</p>
<!--break-->
<h2>Installation Pains</h2>
<p>Installation wasn't easy, and is not recommended for non-technical users. If you've never flashed new firmware nor installed Linux on a computer, you may find it daunting. I expected problems, and was 50% certain I'd end up with a bricked device, but as I wrote above, this device was just one step away from the trash can anyway.</p>
<p>I followed the instructions on this page:</p>
<ul>
<li>http://wiki.cyanogenmod.org/w/Install_CM_for_p3110</li>
</ul>
<p>I installed the latest stable release, <a href="http://get.cm/get/jenkins/42539/cm-10.1.3-p3110.zip">cm-10.1.3-p3110</a> along with the corresponding <a href="http://goo.im/gapps/gapps-jb-20130812-signed.zip">Google Apps</a> package.</p>
<p>I ran into three problems during the process. I'll describe the problems and their solutions here in case anyone else hits the same issues and runs across this page in a search.</p>
<h3>Unable to Install ClockworkMod</h3>
<p>Before installing CM, one must install a custom <a href="http://www.androidcentral.com/what-recovery-android-z">recovery</a> partition that can be used to install CM. The suggested recovery is <a href="http://forum.xda-developers.com/wiki/ClockworkMod_Recovery">ClockworkMod</a>, and installation is described in that <em>How to Install CyanogenMod</em> document I was following. However, after following the steps in the document and then rebooting, booting into recovery resulted in me still having the standard default recovery, not ClockworkMod.</p>
<p>I went through the steps a couple more times, and ended up with the same result. "Maybe I'm just not smart enough for CyanogenMod," I thought.</p>
<p>I Googled around. Eventually I stumbled on a forum posting that suggested that the stock ROM might be restoring the original recovery partition, and the trick was to make sure you boot into recovery before that happened. I don't know exactly what I did, but followed the steps one more time, taking care to ensure that I booted into recovery immediately after the downloader said it was finished, and it worked. I was in ClockworkMod!</p>
<h3>Unable to Install zip from sdcard</h3>
<p>Once in ClockworkMod recovery, I was supposed to be able to choose the <em>Install zip from sdcard</em> menu item and then select the <code>cm-10.1.3-p3110.zip</code> file that I had earlier copied to the device's <code>/sdcard</code> directory. But when I selected that command, the file wasn't visible in the resulting list.</p>
<p>I rebooted into the normal device OS, and verified that <code>cm-10.1.3-p3110.zip</code> was indeed in the <code>/sdcard</code> directory. Booted into recovery again, and again, unable to select that file.</p>
<p>So, I chose the <em>Install from sideload</em> command instead, and then did <code>adb sideload cm-10.1.3-p3110.zip</code> from my Mac connected via USB. That worked. I then did the same thing with the Google Apps package.</p>
<p>Subsequent research indicates that the zip files can be found by ClockworkMod in the <code>/sdcard/0/</code> directory, rather than in <code>/sdcard/</code>. This has something to do with support for multiple users.</p>
<h3>Keyboard Not Functional</h3>
<p>After sideloading the CM and Google Apps packages, I rebooted the device, and CyanogenMod appeared on the screen. Success!</p>
<p>Well, not really. It asked me whether I wanted to set up a CyanogenMod account, and I clicked the button, and then it brought up the wi-fi selection screen, and I selected my wi-fi, and then it asked me for a password.</p>
<p>But no keyboard was displayed. How does one enter a wi-fi password without a keyboard?</p>
<p>So I just skipped through all the initial setup stuff. After I got to the Android home screen, every few seconds I got an error alert saying "Unfortunately, Android keyboard (AOSP) has stopped", indicating that the standard Android keyboard process was crashing repeatedly. That explained why no keyboard was displayed when I was prompted for my wi-fi password.</p>
<p>My first thought was to install <a href="http://www.swiftkey.net">SwiftKey</a>, my preferred Android keyboard. But how does one download something from Google Play if one can't enter the wi-fi password?</p>
<p>So, more Googling. The most popular suggested fix for this problem was to go into Settings > Apps and clear data for the Android Keyboard and Dictionary Provider apps. I tried this, and it didn't help.</p>
<p>Another popular suggestion was to try an older version of the Google Apps package. It didn't make sense to me that the Google Apps package would affect the standard Android keyboard, but I checked for older compatible Google Apps packages anyway.</p>
<p>And this is how I discovered I had installed the wrong Google Apps package. I had installed the package designed for CM 4.2, but I had CM 4.1.</p>
<p>So I rebooted back into recovery and installed the correct Google Apps package via sideload. For good measure, I also cleared the user data, the Dalvik cache and every other thing that seemed clearable or resettable in ClockworkMod.</p>
<p>I rebooted, and the keyboard worked.</p>
<h2>The Result</h2>
<p>I expected CyanogenMod to be weird, but it isn't. It generally looks and works just like my Nexus 7 does. I can access my Google contacts, calendar, and mail. I can download and run my purchased apps from the Google Play store (although there are a couple that are marked "incompatible with your device"). I can read my Kindle books. I can access all my Dropbox stuff.</p>
<p>I do see a few extra menu items in Settings, and there are probably a few magic features I haven't noticed yet, but if you are accustomed to pure Android as delivered in the Nexus devices, you'll find it familiar.</p>
<h2>The Community</h2>
<p>As with any large open-source project, the community of developers and users are the only source of help. The CM community is a lot like the Linux community: there are a few smart helpful people, a few users sincerely offering and seeking assistance, and a lot of arrogant assholes who want to make everyone feel stupid.</p>
<p>Don't be part of that last group.</p>Setting Up a New Mac, My Way2013-08-30T18:02:30-04:002013-08-30T18:02:30-04:00Kristopher Johnsontag:undefinedvalue.com,2013-08-30:/setting-new-mac-my-way.html<p>Over the past couple of weeks, I've set up a few Mac OS X machines to do development of iOS and Android apps. Doing this used to be an all-day chore, but things like app stores, iCloud, and Dropbox have streamlined the process a lot.</p>
<p>(I could streamline the process …</p><p>Over the past couple of weeks, I've set up a few Mac OS X machines to do development of iOS and Android apps. Doing this used to be an all-day chore, but things like app stores, iCloud, and Dropbox have streamlined the process a lot.</p>
<p>(I could streamline the process even more by cloning an existing drive or virtual machine, but I'd rather install everything from scratch to avoid the presence of old cruft.)</p>
<p>As a reminder to myself, and to help out anyone else who needs to do this, here is my procedure for setting up an OS X machine <a href="https://undefinedvalue.com/2012/06/15/my-setup">the way I like it</a>:</p>
<ol>
<li>Install/re-install OS X.</li>
<li>During the OS X setup process, use the same login account name and password that I use on other computers, and provide the Apple IDs for iCloud and iTunes (which are different, in my case).</li>
<li>Open System Preferences and do the following:</li>
<li>In the <em>General</em> panel, set <em>Sidebar icon size</em> to <em>Small</em> and <em>Show scroll bars</em> to <em>Always</em>.</li>
<li>In the <em>Mission Control</em> panel, uncheck the <em>Automatically rearrange Spaces based on most recent use</em> box.</li>
<li>In the <em>Mouse</em> and <em>Trackpad</em> panels, set all speeds to two ticks less than the maximums, and enable all the gestures.</li>
<li>In the <em>Keyboard</em> panel, set <em>Key Repeat</em> and <em>Delay Until Repeat</em> all the way to the right, and check the <em>Use F1, F2, etc. keys as standard function keys</em> box</li>
<li>In the <em>Keyboard</em> panel, go to the <em>Shortcuts</em> tab, select <em>Services</em>, and then enable the <em>New Terminal at Folder</em> service.</li>
<li>In the <em>iCloud</em> panel, enable everything.</li>
<li>In the <em>Sharing</em> panel, set the <em>Computer Name</em> to something unique (not "Kristopher's computer") and enable <em>Remote Management</em>, <em>Remote Login</em>, and <em>File Sharing</em>.</li>
<li>Set up <em>Time Machine</em></li>
<li>If this is a virtual machine, go to the <em>Desktop & Screen Saver</em> panel and turn off the screen saver, and go to the <em>Energy Saver</em> panel and set the sleep sliders to <em>Never</em>.</li>
<li>Use the <em>Software Update...</em> menu item to install any system updates that are available, and reboot if necessary.</li>
<li>If this is a virtual machine, install VMWare Tools or Parallels Tools.</li>
<li>Download and install these packages (using serial numbers and licenses stored in 1Password):</li>
<li><a href="https://www.dropbox.com/gs">Dropbox</a> (and wait for everything to sync before continuing)</li>
<li><a href="https://agilebits.com/onepassword">1Password</a></li>
<li><a href="http://smilesoftware.com/TextExpander/index.html">TextExpander</a></li>
<li><a href="http://www.keyboardmaestro.com/">Keyboard Maestro</a></li>
<li><a href="http://www.google.com/chrome">Chrome</a></li>
<li><a href="http://brettterpstra.com/projects/nvalt/">nvALT</a></li>
<li><a href="http://www.sublimetext.com">Sublime Text</a></li>
<li><a href="http://www.jetbrains.com/objc/download/">AppCode</a></li>
<li><a href="http://www.sourcetreeapp.com">SourceTree</a></li>
<li><a href="http://www.omnigroup.com/omnioutliner">OmniOutliner</a></li>
<li><a href="http://www.omnigroup.com/omnipresence">OmniPresence</a></li>
<li><a href="http://fletcherpenney.net/multimarkdown/">MultiMarkdown</a></li>
<li><a href="http://brettterpstra.com/projects/markdown-service-tools/">Markdown Service Tools</a> (copy the <code>.workflow</code> files to <code>~/Library/Services</code>)</li>
<li><a href="http://www.tug.org/mactex/index.html">MacTeX</a></li>
<li><a href="https://code.google.com/p/pandoc/downloads/list">Pandoc</a></li>
<li><a href="http://www.openoffice.org/download/index.html">OpenOffice</a> and/or <a href="http://www.libreoffice.org/download">LibreOffice</a> (depending on mood)</li>
<li><a href="http://sourceforge.net/projects/sourcecodepro.adobe/files/">Source Code Pro font</a></li>
<li><a href="http://dejavu-fonts.org/wiki/Download">Deja Vu fonts</a></li>
<li><a href="http://www.fantascienza.net/leonardo/ar/inconsolatag/inconsolata-g_font.zip">Inconsolata-g font</a></li>
<li><a href="http://input.fontbureau.com/download/">Input font</a></li>
<li><a href="http://www.crashplan.com">CrashPlan</a></li>
<li><a href="http://www.macbartender.com">Bartender</a></li>
<li><a href="http://www.noodlesoft.com/hazel.php">Hazel</a></li>
<li><a href="http://www.kaleidoscopeapp.com">Kaleidoscope</a></li>
<li><a href="http://www.utorrent.com">µTorrent</a></li>
<li><a href="http://xquartz.macosforge.org/">XQuartz</a></li>
<li><a href="http://freemind.sourceforge.net/wiki/index.php/Download">FreeMind</a></li>
<li>Open the App Store app and install these applications (skipping any that are not needed):</li>
<li>Xcode</li>
<li>CodeRunner</li>
<li>OS X Server</li>
<li>Moom</li>
<li>PopClip</li>
<li>Alfred</li>
<li>Pages</li>
<li>Soulver</li>
<li>Evernote</li>
<li>Sketch</li>
<li>Skitch</li>
<li>Pixelmator</li>
<li>MultiMarkdown Composer</li>
<li>Open Xcode, accept the license agreement and download simulators and documentation. On a Terminal command line, execute <code>xcode-select --install</code> to install the command-line tools.</li>
<li>Install <a href="http://brew.sh">Homebrew</a></li>
<li>Open the Terminal application and run <code>java</code>. Download and install the JDK when prompted.</li>
<li>Download and install the <a href="http://developer.android.com/sdk/index.html">ADT Bundle</a>. <em>(Note: This is old; now Android Studio is the thing to download and install.)</em></li>
<li>After installation, launch the Eclipse application. Choose the <em>Android SDK Manager</em> menu item, and install/update everything in these subtrees:<ul>
<li>Tools</li>
<li>Android 4.3 (or whatever the newest API level is)</li>
<li>Extras</li>
</ul>
</li>
<li>Choose <em>Help > Install New Software...</em>. Click the <em>Add...</em> button. Add this repository and install the Eclipse Color Theme plugin:<ul>
<li>Name: Eclipse Color Theme Update Site</li>
<li>Location: <code>http://eclipse-color-theme.github.io/update/</code></li>
</ul>
</li>
<li>Download and install latest the HAXM driver from <a href="https://software.intel.com/en-us/android/articles/intel-hardware-accelerated-execution-manager">https://software.intel.com/en-us/android/articles/intel-hardware-accelerated-execution-manager</a>. (If that link is broken, go to <a href="https://software.intel.com/en-us/android/">https://software.intel.com/en-us/android/</a> and look for a HAXM download link.)</li>
<li>Set up <code>~/.bashrc</code> to run my shared scripts that are in <code>~/Dropbox/bin</code>.</li>
<li>Execute this in Terminal: <code>chflags nohidden ~/Library</code></li>
<li>Set up ssh keys for <a href="https://confluence.atlassian.com/pages/viewpage.action?pageId=270827678">Bitbucket</a> and <a href="https://help.github.com/articles/generating-ssh-keys">GitHub</a>.</li>
</ol>
<p>Then to verify everything is ready to go, I use Git to grab the source code for an iOS app, and build it and run it, and then do the same for an Android app.</p>
<p>(For my Windows setup, see <a href="https://undefinedvalue.com/2013/12/22/setting-windows-my-way">Setting Up Windows, My Way</a>.)</p>Transferring Ringtones from iTunes to an HTC One2013-08-25T13:21:46-04:002013-08-25T13:21:46-04:00Kristopher Johnsontag:undefinedvalue.com,2013-08-25:/transferring-ringtones-itunes-htc-one.html<p>I had some ringtones purchased from iTunes that I wanted to put onto my new HTC One. Googling for instructions found many hits, but many of those pages were a few years old, so the instructions didn't work anymore, or they didn't work with the HTC One, or were six-minute-long …</p><p>I had some ringtones purchased from iTunes that I wanted to put onto my new HTC One. Googling for instructions found many hits, but many of those pages were a few years old, so the instructions didn't work anymore, or they didn't work with the HTC One, or were six-minute-long YouTube videos. So, I'm writing up some simple instructions that worked on my HTC One. (They will probably work with other Android phones, but I'm not promising anything.)</p>
<p>First, on your computer with iTunes, find the ringtones you want to transfer. These will be files with a <code>.m4r</code> extension. On my Mac, they were in the directory <code>/Users/kdj/Music/iTunes/iTunes Music/Tones</code>.</p>
<p>Next, you need some way to get the files onto your device's <code>/sdcard/Ringtones</code> directory. If you have a Windows computer, I think you can just plug the phone into the computer and it will be mounted as a USB drive, so you can just drag and drop the files. If you have a Mac, you can either use the <a href="http://www.android.com/filetransfer/">Android File Transfer</a> application, or use something like <a href="https://www.dropbox.com/">Dropbox</a>.</p>
<p>Finally, you need to change the filename extensions of the files in <code>/sdcard/Ringtones</code> from <code>.m4r</code> to <code>.m4a</code>.</p>
<p>Then, on the device you can go to Settings > Sound > Ringtone, and you should see your ringtones in the list.</p>Solving "Symbol not found: _Perl_Gthr_key_ptr" When Running git-svn on Certain Unnamed Operating System Beta Versions2013-08-21T16:32:29-04:002013-08-21T16:32:29-04:00Kristopher Johnsontag:undefinedvalue.com,2013-08-21:/solving-symbol-not-found-perlgthrkeyptr-when-running-git-svn-certain-unnamed-operating-sy.html<p>Let's say that you are using a beta version of a new operating system that you can't name because it is covered by a non-disclosure agreement, and you have also installed the newest version of its development tools, which are also covered by NDA, and when you try to run …</p><p>Let's say that you are using a beta version of a new operating system that you can't name because it is covered by a non-disclosure agreement, and you have also installed the newest version of its development tools, which are also covered by NDA, and when you try to run the <code>git svn</code> command, you get this output:</p>
<div class="highlight"><pre><span></span><code><span class="n">dyld</span><span class="o">:</span><span class="w"> </span><span class="n">lazy</span><span class="w"> </span><span class="n">symbol</span><span class="w"> </span><span class="n">binding</span><span class="w"> </span><span class="n">failed</span><span class="o">:</span><span class="w"> </span><span class="n">Symbol</span><span class="w"> </span><span class="n">not</span><span class="w"> </span><span class="n">found</span><span class="o">:</span><span class="w"> </span><span class="n">_Perl_Gthr_key_ptr</span><span class="w"></span>
<span class="w"> </span><span class="n">Referenced</span><span class="w"> </span><span class="n">from</span><span class="o">:</span><span class="w"> </span><span class="sr">/usr/../Library/Perl/5.12/darwin-thread-multi-2level/auto/SVN/_Core/</span><span class="n">_Core</span><span class="o">.</span><span class="na">bundle</span><span class="w"></span>
<span class="w"> </span><span class="n">Expected</span><span class="w"> </span><span class="k">in</span><span class="o">:</span><span class="w"> </span><span class="n">flat</span><span class="w"> </span><span class="kd">namespace</span><span class="w"></span>
<span class="n">dyld</span><span class="o">:</span><span class="w"> </span><span class="n">Symbol</span><span class="w"> </span><span class="n">not</span><span class="w"> </span><span class="n">found</span><span class="o">:</span><span class="w"> </span><span class="n">_Perl_Gthr_key_ptr</span><span class="w"></span>
<span class="w"> </span><span class="n">Referenced</span><span class="w"> </span><span class="n">from</span><span class="o">:</span><span class="w"> </span><span class="sr">/usr/../Library/Perl/5.12/darwin-thread-multi-2level/auto/SVN/_Core/</span><span class="n">_Core</span><span class="o">.</span><span class="na">bundle</span><span class="w"></span>
<span class="w"> </span><span class="n">Expected</span><span class="w"> </span><span class="k">in</span><span class="o">:</span><span class="w"> </span><span class="n">flat</span><span class="w"> </span><span class="kd">namespace</span><span class="w"></span>
<span class="n">error</span><span class="o">:</span><span class="w"> </span><span class="n">git</span><span class="o">-</span><span class="n">svn</span><span class="w"> </span><span class="n">died</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">signal</span><span class="w"> </span><span class="mi">5</span><span class="w"></span>
</code></pre></div>
<p>Apparently, the problem is that <code>git-svn</code> is implemented in Perl, and there is something wrong with the Perl configuration used when you run <code>/usr/bin/git</code>.</p>
<p>What do you do?</p>
<p>It turns out you can fix this by putting the Git executables provided by the new development tools at the head of your PATH, by executing this command (or adding it to <code>.bashrc</code>):</p>
<div class="highlight"><pre><span></span><code><span class="k">export</span><span class="w"> </span><span class="n">PATH</span><span class="o">=</span><span class="s2">"/Applications/XXXXX.app/Contents/Developer/usr/libexec/git-core"</span><span class="p">:</span><span class="o">$</span><span class="n">PATH</span><span class="w"></span>
</code></pre></div>
<p>where <code>XXXXX.app</code> is the unnamed development tool.</p>
<p>Alternatively, you can add all the command-line tools to your PATH like this:</p>
<div class="highlight"><pre><span></span><code><span class="k">export</span><span class="w"> </span><span class="n">PATH</span><span class="o">=</span><span class="err">”</span><span class="o">/</span><span class="n">Applications</span><span class="o">/</span><span class="n">XXXXX</span><span class="o">.</span><span class="n">app</span><span class="o">/</span><span class="n">Contents</span><span class="o">/</span><span class="n">Developer</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="err">”</span><span class="p">:</span><span class="o">$</span><span class="n">PATH</span><span class="w"></span>
</code></pre></div>
<p>Credit to <a href="http://vandadnp.wordpress.com/2012/04/06/git-from-command-line-after-installing-xcode-on-os-x-lion/">Vandad Nahavandipoor</a> for the hint.</p>
<p>Another option would be to use the <a href="https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/xcrun.1.html">xcrun</a> utility to run <code>git</code>. You can do this:</p>
<div class="highlight"><pre><span></span><code>xcrun git svn blah-blah-blah
</code></pre></div>
<p>or put this into your <code>.bashrc</code> so that you don't have to remember to type <code>xcrun</code>:</p>
<div class="highlight"><pre><span></span><code>alias git='xcrun git'
</code></pre></div>
<p>If you are the kind of person who thinks its a good idea to replace system files with symlinks, you might try symlinking <code>/usr/bin/git</code> to <code>/Applications/XXXXX.app/Contents/Developer/usr/libexec/git-core/git</code>. However, that alone doesn't work, because Git will still run the <code>git-svn</code> executable from its default location, <code>/Library/Developer/CommandLineTools/usr/libexec/git-core</code>, and you will still have the <code>_Perl_Gthr_key_ptr</code> problem. So you also need to symlink the default location to <code>/Applications/XXXXX.app/Contents/Developer/usr/libexec/git-core/</code>, or set the <code>GIT_EXEC_PATH</code> environment variable.</p>Customizing Android Action Bar for Edit Mode2013-07-25T13:06:38-04:002013-07-25T13:06:38-04:00Kristopher Johnsontag:undefinedvalue.com,2013-07-25:/customizing-android-action-bar-edit-mode.html<p>I spent a <em>long</em> time trying to get a <a href="http://developer.android.com/design/patterns/actionbar.html">contextual action bar (CAB)</a> working for an editing mode in an Android app I'm developing. My goal was to have a CAB appear whenever the user started changing field values on a screen, and the user would tap the <em>Done</em> button …</p><p>I spent a <em>long</em> time trying to get a <a href="http://developer.android.com/design/patterns/actionbar.html">contextual action bar (CAB)</a> working for an editing mode in an Android app I'm developing. My goal was to have a CAB appear whenever the user started changing field values on a screen, and the user would tap the <em>Done</em> button when complete. The CAB would also show a "Revert Changes" button allowing the user to undo whatever they did.</p>
<p>This initially seemed like the easiest way to indicate to the user that they had made changes and needed to explicitly save them, but Android's default implementation of CAB has some drawbacks:</p>
<ul>
<li>The standard button is titled "Done". I would prefer it to be "Save", but there is no easy-to-use <code>setTitle()</code> method available.</li>
<li>If user hits the <em>BACK</em> button, the CAB disappears, and there is no straightforward way for the app to determine whether the CAB disappeared because the user hit Done or because they hit <em>BACK</em>, or any way to intercept the processing of the <em>BACK</em> button while a CAB is displayed.</li>
<li>I have to write code to restore the CAB state on a <a href="http://developer.android.com/guide/topics/resources/runtime-changes.html">configuration change</a></li>
</ul>
<p>While browsing around the web trying to find examples of workarounds for these issues, I ran across a blog post explaining why using a CAB for this scenario won't work, even if I did fix the aforementioned problems:</p>
<p><a href="http://dazcorp.blogspot.com/2013/04/edit-mode-and-why-using-contextual.html">Edit Mode and why using a Contextual ActionBar is a bad idea</a></p>
<p>(Short version: If user double-taps or long-presses an edit field, it will pop up its own text-selection CAB which would blow away my CAB and eventually lead to a <code>NullPointerException</code>.)</p>
<p>So, I'm not using a CAB, but will instead customize the look of the non-contextual action bar as suggested in that blog post.</p>Removing a Broken Lightning Connector Plug from an iPad or iPhone2013-06-29T18:20:32-04:002013-06-29T18:20:32-04:00Kristopher Johnsontag:undefinedvalue.com,2013-06-29:/removing-broken-lightning-connector-plug-ipad-or-iphone.html<p>Newer models of the iPad, iPhone, and iPod touch use the <a href="http://en.wikipedia.org/wiki/Lightning_(connector)">Lightning connector</a> instead of the old 30-pin dock connectors. The Lightning connector is smaller and you don't have to worry about inserting it upside-down, so on the whole this is a good change. However, the connector plug is not …</p><p>Newer models of the iPad, iPhone, and iPod touch use the <a href="http://en.wikipedia.org/wiki/Lightning_(connector)">Lightning connector</a> instead of the old 30-pin dock connectors. The Lightning connector is smaller and you don't have to worry about inserting it upside-down, so on the whole this is a good change. However, the connector plug is not as sturdy as the old plug, and my wife somehow broke the plug off inside her iPad.</p>
<p>It is pretty easy to pull a broken plug out:</p>
<ol>
<li>Get a small screwdriver (small enough that the head will fit in the Lightning port)</li>
<li>Put a small drop of super glue on the tip of the screwdriver</li>
<li>Insert the screwdriver into the socket and hold it against the broken plug. Be careful not to touch the sides of the socket with the super glue.</li>
<li>Wait about 30 seconds for the glue to set.</li>
<li>Pull it out smoothly. The Lightning plug isn't held with much force, so this should be easy.</li>
</ol>
<p>Unfortunately, there is no way to repair the cable, so you'll have to go to your friendly neighborhood Apple Store and give them twenty bucks for a new one.</p>Android SDK Tools 22.0.1 Considered Harmful2013-05-29T23:43:08-04:002013-05-29T23:43:08-04:00Kristopher Johnsontag:undefinedvalue.com,2013-05-29:/android-sdk-tools-2201-considered-harmful.html<p>After finishing up some work on an iOS app today, it was time to go make equivalent changes to the Android port of that app. "I'll just update my Android SDK before I get to work," I said (to myself). I opened the Android SDK Manager and let it update …</p><p>After finishing up some work on an iOS app today, it was time to go make equivalent changes to the Android port of that app. "I'll just update my Android SDK before I get to work," I said (to myself). I opened the Android SDK Manager and let it update these SDK packages to these versions:</p>
<ul>
<li>Android SDK Tools: 22.0.1</li>
<li>Android SDK Platform-tools: 17</li>
<li>Android SDK Build-tools: 17</li>
</ul>
<p>Then I let Eclipse update the ADT. Then I got to work. I opened my Android app project and tried to run it. I found I couldn't make a working debug build or a release build, nor could I create a signed APK to install on a device.</p>
<p>After a few hours of hair pulling, I got everything working. Here is what I learned. I hope it helps someone.</p>
<!--break-->
<h2>Unable to resolve superclass</h2>
<p>My first problem was that, even though I could create an APK, when I tried to run it on a device I would see a bunch of "unable to resolve superclass" errors in the Logcat console. It looked like some of the JARs I was using (<code>android-support-v4.jar</code>, <code>gcm.jar</code>, and some third-party libraries) weren't being included in the APK.</p>
<p>I eventually found the answer here: http://stackoverflow.com/questions/16583786/android-sdk-tools-revision-22-issue</p>
<p>Most of my app's code is in an Android library project, and then there is an application project that uses the library. Apparently revision 22 of the SDK breaks the way that application projects bring in JARs from a library project. To fix this, do the following on both the library project and the application project that uses the library:</p>
<ol>
<li>Right-click the project in Eclipse's Package Explorer and choose Properties.</li>
<li>Choose <strong>Java Build Path</strong></li>
<li>Choose <strong>Order and Export</strong></li>
<li>Check the boxes next to <strong>Android Private Libraries</strong> and <strong>Android Dependencies</strong></li>
</ol>
<p>After making these changes, do a clean and then rebuild.</p>
<p>With this change, I was able to build an APK that installed and ran on an Android device.</p>
<p>This is apparently a bug with ADT 22. See https://code.google.com/p/android/issues/detail?id=55304</p>
<h2>INSTALL_PARSE_FAILED_NO_CERTIFICATES</h2>
<p>After getting the release build working, I tried making a debug build to run in the debugger. I figured it would just work, but I saw this error in the console:</p>
<div class="highlight"><pre><span></span><code>Installation error: INSTALL_PARSE_FAILED_NO_CERTIFICATES
</code></pre></div>
<p>I verified that I had a <code>debug.keystore</code> file, which is what should hold the certificates for a debug build. Sure enough, it existed, and hasn't been changed in a couple of years. I hadn't changed anything about my project or my <code>debug.keystore</code>, but it looked like something about code signing has changed with the SDK.</p>
<p>I decided to delete my <code>debug.keystore</code> file and let the SDK automatically recreate it. That solved the problem.</p>UIColor Category for Specifying Packed RGB Values2013-05-19T00:36:53-04:002013-05-19T00:36:53-04:00Kristopher Johnsontag:undefinedvalue.com,2013-05-19:/uicolor-category-specifying-packed-rgb-values.html<p>iOS's <a href="https://developer.apple.com/library/ios/#documentation/uikit/reference/UIColor_Class/Reference/Reference.html">UIColor</a> class makes it pretty easy to specify a color using red, green, blue (RGB) and alpha components:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">// set pale yellow color</span>
<span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">textColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">colorWithRed</span><span class="o">:</span><span class="mf">1.0</span><span class="w"></span>
<span class="w"> </span><span class="nl">green</span><span class="p">:</span><span class="mf">1.0</span><span class="w"></span>
<span class="w"> </span><span class="nl">blue</span><span class="p">:</span><span class="mf">0.5</span><span class="w"></span>
<span class="w"> </span><span class="nl">alpha</span><span class="p">:</span><span class="mf">1.0</span><span class="p">];</span><span class="w"></span>
</code></pre></div>
<p>However, as with many Cocoa API's, it's pretty verbose. Web developers would …</p><p>iOS's <a href="https://developer.apple.com/library/ios/#documentation/uikit/reference/UIColor_Class/Reference/Reference.html">UIColor</a> class makes it pretty easy to specify a color using red, green, blue (RGB) and alpha components:</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">// set pale yellow color</span>
<span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">textColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">colorWithRed</span><span class="o">:</span><span class="mf">1.0</span><span class="w"></span>
<span class="w"> </span><span class="nl">green</span><span class="p">:</span><span class="mf">1.0</span><span class="w"></span>
<span class="w"> </span><span class="nl">blue</span><span class="p">:</span><span class="mf">0.5</span><span class="w"></span>
<span class="w"> </span><span class="nl">alpha</span><span class="p">:</span><span class="mf">1.0</span><span class="p">];</span><span class="w"></span>
</code></pre></div>
<p>However, as with many Cocoa API's, it's pretty verbose. Web developers would specify that color using the hexcode shorthand <code>#ffff80</code>, and many graphics editing tools would generate a hexcode value like that rather than values in the range 0.0–1.0.</p>
<p>So I made a simple category on UIColor that lets one write stuff like this:</p>
<div class="highlight"><pre><span></span><code><span class="cp">#include</span><span class="w"> </span><span class="cpf">"UIColor+KDJPackedRGB.h"</span><span class="cp"></span>
<span class="c1">// ...</span>
<span class="c1">// set pale yellow color</span>
<span class="n">label</span><span class="p">.</span><span class="n">textColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">colorWithRGB24</span><span class="o">:</span><span class="mh">0xffff80</span><span class="p">];</span><span class="w"></span>
</code></pre></div>
<p>See <a href="https://gist.github.com/kristopherjohnson/5606209">https://gist.github.com/kristopherjohnson/5606209</a></p>Death, Numbers, and Risk2013-04-30T01:18:30-04:002013-04-30T01:18:30-04:00Kristopher Johnsontag:undefinedvalue.com,2013-04-30:/death-numbers-and-risk.html<p>Here are some numbers that many people don't know, or don't want to think about:</p>
<p>There are approximately 6.9 billion people in the world. On average,</p>
<ul>
<li>about 55 million people die each year,</li>
<li>about 1.05 million people die each week,</li>
<li>about 151,000 people die each day,</li>
<li>about …</li></ul><p>Here are some numbers that many people don't know, or don't want to think about:</p>
<p>There are approximately 6.9 billion people in the world. On average,</p>
<ul>
<li>about 55 million people die each year,</li>
<li>about 1.05 million people die each week,</li>
<li>about 151,000 people die each day,</li>
<li>about 6280 people die each hour, and</li>
<li>about 105 people die each minute.</li>
</ul>
<p>Source: <a href="http://www.wolframalpha.com/input/?i=annual+deaths">Wolfram Alpha</a></p>
<p>There are approximately 309 million people in the US. On average,</p>
<ul>
<li>about 2.5 million people die in the US each year,</li>
<li>about 48,000 people die in the US die each week,</li>
<li>about 6860 people die in the US each day,</li>
<li>about 286 people die in the US each hour, and</li>
<li>more than 4 people die in the US each minute</li>
</ul>
<p>Source: <a href="http://www.wolframalpha.com/input/?i=united+states+annual+deaths">Wolfram Alpha</a></p>
<p>In round numbers, that's <em>over 150,000 people dying every day</em>, and <em>almost 7,000 people dying in the US every day</em>.</p>
<p>Many of these deaths are those of elderly people passing away in their sleep. Many are unmourned. But many are <em>tragedies</em> in the sense that a person has died too young, and grieving people are left behind.</p>
<p>So, when the news presents reports of people dying, ask yourself: Why is the news reporting these deaths, and not the 150,000 other deaths that happened today around the world, or the 7,000 other deaths that happened today in the US?</p>
<p>Is it because the reported deaths are more important than all the others, or is it because somebody found a way to make <em>dramatic stories</em> out of these particular deaths? Are the reported deaths indicative of larger patterns or important trends, or are they interesting because they happened in a public place and there is video available from several angles?</p>
<p>In fact, your chance of being killed by a terrorist or a mass shooter or an airplane crash or flesh-eating bacteria or a meteor or anything else reported by the media is almost equal to zero. These events are reported on the evening news <em>because they are so rare</em>.</p>
<p>So be careful about letting what you see on the news control your fears.</p>
<p>It is silly to worry about terrorists and mass shooters if you smoke cigarettes, eat a lot of fast food, or use your mobile phone while driving. The latter activities could kill you; the former just don't happen often enough for any reasonable person to worry about them.</p>
<p>You might want to turn your home into an armed fortress to protect your family from dangerous people. Before you do that, you should ensure that your family is eating healthy meals, getting lots of exercise, and getting regular medical checkups. You are more likely to save somebody's life by learning CPR and First Aid techniques than by learning martial arts or small-arms tactics. Make sure all the smoke detectors have fresh batteries before you worry about installing a high-tech security system.</p>
<p>When someone asks you to pray for the victims of some tragic event, ask why you should pray just for them, and not for the one million people who died in other ways that week, or for the million that died the previous week, or for the million who will die the following week.</p>
<p>When somebody tells you that thousands of people are killed every year by some disease or government policy or widespread moral failing, and insists that drastic measures are justified to prevent those deaths, think about the 2.5 million Americans who die of other causes every year. If someone claims that a new law or policy is worthwhile "even if it saves only one child", consider whether resources might be better devoted to policies that save hundreds, thousands, or millions of children instead of just one.</p>
<p>I'm not suggesting that we do nothing to try to prevent deaths, or that we should not care when strangers die. If you can save one person's life, you have done more good than most people will ever do. I'm just suggesting that you remember the bigger picture.</p>
<p>With billions of people in the world, it will always be easy to find instances of evil people doing horrible things, and of innocent people dying in tragic circumstances. But remember, while around 150,000 people die every day, a larger number are born, and billions of people just go on living.</p>
<p>Your chances of making it through the day are pretty good.</p>Home Depot Two-Year Replacement Plan Doesn't2013-04-13T23:09:28-04:002013-04-13T23:09:28-04:00Kristopher Johnsontag:undefinedvalue.com,2013-04-13:/home-depot-two-year-replacement-plan-doesnt.html<p>When I bought a Yard Machines lawn mower from Home Depot last year, I paid for a 2-year repair/replacement plan. I'm pretty sure the plan was presented to me with language like "If it stops working, you just bring it back and we'll replace it." I've had some bad …</p><p>When I bought a Yard Machines lawn mower from Home Depot last year, I paid for a 2-year repair/replacement plan. I'm pretty sure the plan was presented to me with language like "If it stops working, you just bring it back and we'll replace it." I've had some bad luck with lawn equipment, and the price wasn't high, so it seemed like a good deal.</p>
<p>This year, the mower won't start. So I try to get Home Depot to repair or replace it. Home Depot's response is that the product is covered by a manufacturer's warranty, so I must get service from the manufacturer. The website helpfully tells me that I can contact the manufacturer by calling "$MFR_PHONE$". (That's verbatim.)</p>
<p>So Home Depot's repair/replacement plan was literally worthless. Lesson learned.</p>
<p>I contact the Yard Machines warranty service. They say that their warranty doesn't cover the engine. The engine is covered by the engine's manufacturer (Briggs & Stratton).</p>
<p>Yard Machines provides a warranty that doesn't cover the engine? Isn't a lawn mower just an engine attached to a blade and wheels? It's as if a warranty for a computer didn't cover any of the electronics.</p>
<p>So I go to the Briggs & Stratton website. It says I can get warranty service by an Authorized Briggs & Stratton Dealer in my area. I enter my zip code. Results: No dealers found.</p>
<p>And so now I have to wonder how many more hours I want to spend trying to get this thing fixed. Probably easier to either hire a lawn-mowing service, figure out how to fix it myself, or buy a new mower. But not from Home Depot.</p>
<p>To sum up, here are recent additions to my Shit List:</p>
<ul>
<li>Home Depot</li>
<li>Yard Machines</li>
<li>Briggs & Stratton</li>
</ul>Finding the Answers2013-04-12T16:44:43-04:002013-04-12T16:44:43-04:00Kristopher Johnsontag:undefinedvalue.com,2013-04-12:/finding-answers.html<p><a href="http://blog.stackoverflow.com/2013/03/podcast-45-keeping-it-sharp/">Episode #45</a> of the Stack Exchange podcast featured <a href="http://ericlippert.com">Eric Lippert</a>, who got some great advice from his first manager at Microsoft:</p>
<blockquote>
<p>I want you to be a recognized industry expert on something. ... But don't pick something that's too big. ... Find a source of questions, answer every question that you know …</p></blockquote><p><a href="http://blog.stackoverflow.com/2013/03/podcast-45-keeping-it-sharp/">Episode #45</a> of the Stack Exchange podcast featured <a href="http://ericlippert.com">Eric Lippert</a>, who got some great advice from his first manager at Microsoft:</p>
<blockquote>
<p>I want you to be a recognized industry expert on something. ... But don't pick something that's too big. ... Find a source of questions, answer every question that you know the answer to that is in that domain, and if there is a question that you not know the answer to, make it your business to find out.</p>
</blockquote>
<p>This is why Eric Lippert <a href="http://stackoverflow.com/users/88656/eric-lippert">answers so many Stack Overflow questions</a>. </p>
<p>This is why <em>everyone</em> should be answering more questions and sharing their expertise. It makes you better at what you do.</p>iOS and Android Icon Sizes2013-04-05T20:39:24-04:002013-04-05T20:39:24-04:00Kristopher Johnsontag:undefinedvalue.com,2013-04-05:/ios-and-android-icon-sizes.html<p>Every once in a while, I have to tell a graphic designer all the sizes needed for iOS and Android icons. So I'm putting together a summary here for easy reference.</p>
<h2>iOS</h2>
<p>For more details on requirements and guidelines for iOS app icons, see <a href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/IconMatrix.html#//apple_ref/doc/uid/TP40006556-CH27-SW1">iOS Human Interface Guidelines: Icons and …</a></p><p>Every once in a while, I have to tell a graphic designer all the sizes needed for iOS and Android icons. So I'm putting together a summary here for easy reference.</p>
<h2>iOS</h2>
<p>For more details on requirements and guidelines for iOS app icons, see <a href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/IconMatrix.html#//apple_ref/doc/uid/TP40006556-CH27-SW1">iOS Human Interface Guidelines: Icons and Image Sizes</a> and <a href="https://developer.apple.com/library/ios/qa/qa1686/_index.html">Technical Q&A QA1686: App Icons on iPad and iPhone</a>.</p>
<p>All icons must be in PNG format with 24-bit color.</p>
<h3>App Icons</h3>
<p>For an app for iOS 7 and later, we need icon image files in these sizes:</p>
<ul>
<li>1024 x 1024</li>
<li>512 x 512</li>
<li>228 x 228</li>
<li>180 x 180</li>
<li>152 x 152</li>
<li>120 x 120</li>
<li>87 x 87</li>
<li>80 x 80</li>
<li>76 x 76</li>
<li>58 x 58</li>
<li>40 x 40</li>
<li>29 x 29</li>
<li>144 x 144 (if supporting iOS 6.1 or earlier)</li>
<li>114 x 114 (if supporting iOS 6.1 or earlier)</li>
<li>100 x 100 (if supporting iOS 6.1 or earlier)</li>
<li>72 x 72 (if supporting iOS 6.1 or earlier)</li>
<li>57 x 57 (if supporting iOS 6.1 or earlier)</li>
<li>50 x 50 (if supporting iOS 6.1 or earlier)</li>
</ul>
<p>iOS icons are opaque. Note that iOS will automatically round the corners and add the glossy shine effect when it displays the icon. You may want to pre-render the shine effect if you want more control over how it looks.</p>
<h3>Toolbar, Navigation Bar, and Tab Bar Icons</h3>
<ul>
<li>Use pure white and transparent regions</li>
<li>Do not include a drop shadow</li>
<li>Use anti-aliasing</li>
</ul>
<p>For toolbar and navigation bar icons, create images with these sizes:</p>
<ul>
<li>22 x 22</li>
<li>44 x 44 (high resolution)</li>
</ul>
<p>For tab bar icons, create images with these sizes:</p>
<ul>
<li>25 x 25</li>
<li>50 x 50 (high-resolution)</li>
</ul>
<p>For each toolbar, navigation bar, or tab bar icon, you may provide a single image, which iOS will treat as a template to generate unselected and selected appearances, or you may provide two images: one for the unselected appearance and another for the selected appearance. </p>
<p>Note that the sizes given for toolbar, navigation bar, and tab bar icons here are approximate. Images may be slightly larger or slightly smaller than these sizes. Give all icons in a bar a similar visual weight.</p>
<h3>Apple Watch Icons</h3>
<p>If the iOS app includes an Apple Watch app, the following icons are needed:</p>
<h3>Notification Center Icons</h3>
<ul>
<li>29 x 29 (38mm watch)</li>
<li>36 x 36 (42mm watch)</li>
</ul>
<h3>Long Look Notification Icons</h3>
<ul>
<li>80 x 80 (38mm watch)</li>
<li>88 x 88 (42mm watch)</li>
</ul>
<h3>Home Screen Icon and Short Look Icon</h3>
<ul>
<li>172 x 172 (38mm watch)</li>
<li>196 x 196 (42mm watch)</li>
</ul>
<h3>Menu Icons</h3>
<ul>
<li>70 x 70, with content size 46 x 46 (38mm watch)</li>
<li>80 x 80, with content size 54 x 54 (42mm watch)</li>
</ul>
<h3>Watch Companion Icons</h3>
<ul>
<li>58 x 58</li>
<li>87 x 87</li>
</ul>
<h2>Android</h2>
<h3>App Icons</h3>
<p>For an Android app launcher icon, we need PNG image files in these sizes:</p>
<ul>
<li>512 x 512 (Google Play)</li>
<li>144 x 144 (xxhdpi)</li>
<li>96 x 96 (xhdpi)</li>
<li>72 x 72 (hdpi)</li>
<li>48 x 48 (mdpi)</li>
</ul>
<p>Note that Android app icons don't have to be square: the alpha channel can be used to create transparent areas, so an icon should have a distinct silhouette.</p>
<p>If the app generates notifications, then we need a 24 x 24 icon image. Notification icons must be entirely white except for transparent regions.</p>
<p>For more details on requirements and guidelines for Android app icons, see <a href="http://developer.android.com/guide/practices/ui_guidelines/icon_design_launcher.html">Launcher Icons</a> and <a href="http://developer.android.com/design/style/iconography.html">Iconography</a></p>
<h3>Action Bar Icons</h3>
<ul>
<li>96 x 96 (xxhdpi)</li>
<li>64 x 64 (xhdpi)</li>
<li>48 x 48 (hdpi)</li>
<li>32 x 32 (mdpi)</li>
</ul>
<h3>Small/Contextual Icons</h3>
<ul>
<li>48 x 48 (xxhdpi)</li>
<li>32 x 32 (xhdpi)</li>
<li>24 x 24 (hdpi)</li>
<li>16 x 16 (mdpi)</li>
</ul>
<h3>Notification Icons</h3>
<ul>
<li>72 x 72 (xxhdpi)</li>
<li>48 x 48 (xhdpi)</li>
<li>36 x 36 (hdpi)</li>
<li>24 x 24 (mdpi)</li>
</ul>REST Client Testing for Google Chrome2013-04-04T21:59:15-04:002013-04-04T21:59:15-04:00Kristopher Johnsontag:undefinedvalue.com,2013-04-04:/rest-client-testing-google-chrome.html<p>After years of using <a href="http://curl.haxx.se">curl</a> to test web interfaces, like cavemen did, I've finally started using more sophisticated tools.</p>
<p>There are a couple of applications for Google Chrome that I like:</p>
<ul>
<li><a href="https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm">Postman</a></li>
<li><a href="https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo?hl=en">Advanced Rest Client</a></li>
</ul>
<p>They have similar feature sets. Postman is prettier and a bit easier to use, but …</p><p>After years of using <a href="http://curl.haxx.se">curl</a> to test web interfaces, like cavemen did, I've finally started using more sophisticated tools.</p>
<p>There are a couple of applications for Google Chrome that I like:</p>
<ul>
<li><a href="https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm">Postman</a></li>
<li><a href="https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo?hl=en">Advanced Rest Client</a></li>
</ul>
<p>They have similar feature sets. Postman is prettier and a bit easier to use, but Advanced Rest Client makes it easier to manipulate the response data.</p>Half Marathon Denied2013-03-24T06:34:31-04:002013-03-24T06:34:31-04:00Kristopher Johnsontag:undefinedvalue.com,2013-03-24:/half-marathon-denied.html<p>I was supposed to run the <a href="http://berryhalfmarathon.com">Berry Half Marathon</a> today. This would have been my first half marathon, and I've been training for the past two months to run it.</p>
<p>Unfortunately, the event was cancelled due to lightning and poor course conditions after several hours of thunderstorms. So all I …</p><p>I was supposed to run the <a href="http://berryhalfmarathon.com">Berry Half Marathon</a> today. This would have been my first half marathon, and I've been training for the past two months to run it.</p>
<p>Unfortunately, the event was cancelled due to lightning and poor course conditions after several hours of thunderstorms. So all I got out of it was a very expensive t-shirt.</p>
<!--break-->
<p>It was a stormy two-and-a-half-hour drive each way. I awoke around 4 AM. I arrived at Berry College before 7 AM, prepared for a start time of 8 AM, but that was pushed back to 9 AM due to the weather. Then it was delayed to 9:30, then to 10:00, but at 9:45 they announced the race was cancelled. I got back home at around 1:00 PM.</p>
<p>Even though I had not run, my body was sore because I had been standing out in the rain for a few hours and drove home with cold wet socks and shoes.</p>
<p>I took a nap, then decided to go to the park and run 13.1 miles all by myself, because dammit, I was gonna run a half marathon today and nobody was gonna stop me. I only made it six miles before calling it quits.</p>
<p>I really want to get a half marathon behind me. I am getting bored with running, so I want to get that goal achieved so that I can move on to some new interest. I won't quit running entirely, but I would rather focus on improving my 10K times than on doing longer runs.</p>
<p>I found a <a href="http://www.active.com/running/blairsville-ga/northeast-georgia-run-2013">half marathon event in Blairsville</a> that will run two weeks from now, so I'm planning to enter that. However, that is the day after our fifth wedding anniversary, so I won't run if it would interfere with an appropriate celebration of that milestone. Maybe we can rent a cabin up in Blairsville for a couple of days.</p>
<p>Lessons I learned:</p>
<ul>
<li>Don't register for a running event too far in advance. I registered for this event back in January, having set it as my goal for training, but it has been obvious for the past week that the weather would be terrible. It's cheaper to register farther in advance, but I'd rather pay more to know what the weather will be.</li>
<li>Don't register for a running event that is more than an hour-and-a-half away.</li>
<li>I need better rain gear. I have a couple of water-resistant jackets, but they are not waterproof, so they get soaked through after 30 minutes or so. I want something light but waterproof.</li>
<li>Always take an extra pair of dry socks, an extra pair of shoes, and a <a href="http://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker's_Guide_to_the_Galaxy#Knowing_where_one.27s_towel_is">towel</a>.</li>
<li><a href="http://www.active.com/running/Articles/How_to_Taper_for_Your_Road_Race">Tapering</a> for two weeks before the race doesn't seem to have helped me prepare. I ran 12 miles three weeks ago, but then 9 miles and 8 miles the past two weekends, and now I have trouble running more than six miles. In the future, I'll keep my long runs long, and take it easy for only 3-4 days before the race.</li>
</ul>A Web Page for Reformatting JSON Text2013-03-02T22:57:23-05:002013-03-02T22:57:23-05:00Kristopher Johnsontag:undefinedvalue.com,2013-03-02:/web-page-reformatting-json-text.html<p>After I published my <a href="https://undefinedvalue.com/2013/03/01/os-x-automator-service-reformatting-json-text">OS X Automator Service for Reformatting JSON Text</a>, one commenter said that he always uses <a href="http://jsonformat.com/">http://jsonformat.com/</a>. I have used that, but I hate the output it produces. Its output is more readable than JSON with all the whitespace stripped out, but not by much …</p><p>After I published my <a href="https://undefinedvalue.com/2013/03/01/os-x-automator-service-reformatting-json-text">OS X Automator Service for Reformatting JSON Text</a>, one commenter said that he always uses <a href="http://jsonformat.com/">http://jsonformat.com/</a>. I have used that, but I hate the output it produces. Its output is more readable than JSON with all the whitespace stripped out, but not by much.</p>
<p>It also seemed a little silly for such a web page to require a back-end service to do the work. I figured I could whip up a simple web page that wouldn't require any server round-trips to do its work, and that produced reformatted output that wouldn't make me cry.</p>
<p>Here it is: <a href="http://s3.amazonaws.com/undefinedvalue/formatjson.html">JSON Reformatter</a></p>
<p>You can look at the source here: <a href="https://gist.github.com/kristopherjohnson/5073681">https://gist.github.com/kristopherjohnson/5073681</a></p>
<p>Enjoy!</p>
<hr>
<p><strong>Update 2014-05-29:</strong> New version, implemented using AngularJS: <a href="https://undefinedvalue.com/2014/05/28/web-page-reformatting-json-text-using-angularjs">https://undefinedvalue.com/2014/05/28/web-page-reformatting-json-text-using-angularjs</a></p>An OS X Automator Service for Reformatting JSON Text2013-03-01T16:35:11-05:002013-03-01T16:35:11-05:00Kristopher Johnsontag:undefinedvalue.com,2013-03-01:/os-x-automator-service-reformatting-json-text.html<p><strong>Note: This is an old post. I now use jq (<a href="http://stedolan.github.io/jq/">http://stedolan.github.io/jq/</a>) instead of the <code>formatjson</code> Node script described below, and I recommend that you do too.</strong></p>
<p>I had some JSON files that were not indented consistently. I edit these files by hand, so I wanted a …</p><p><strong>Note: This is an old post. I now use jq (<a href="http://stedolan.github.io/jq/">http://stedolan.github.io/jq/</a>) instead of the <code>formatjson</code> Node script described below, and I recommend that you do too.</strong></p>
<p>I had some JSON files that were not indented consistently. I edit these files by hand, so I wanted a way to easily reformat them.</p>
<p>My text editor has a reformatting command, but I really hate what it produces, so I decided to make my own JSON reformatting service for OS X that I could use in any application.</p>
<!--break-->
<p>First, I wrote a <a href="http://nodejs.org">Node.js</a> script to do the reformatting:</p>
<script src="https://gist.github.com/kristopherjohnson/5065599.js"></script>
<p>(Also available as <a href="http://gist.github.com/kristopherjohnson/5153772">Literate CoffeeScript</a>).</p>
<p>I saved that file as <code>~/bin/formatjson</code>, and made it executable with <code>chmod +x ~/bin/formatjson</code>.</p>
<p>Then I used Automator to create a service:</p>
<ol>
<li>Launch Automator</li>
<li>Create a new Automator document of type <em>Service</em></li>
<li>Set it to "Service receives selected <strong>text</strong> in <strong>any application</strong>", and check the <strong>Output replaces selection text</strong> box.</li>
<li>Drag a <em>Run Shell Script</em> action into the workflow, and enter the following command:</li>
<li><code>PATH=$PATH:~/bin:/usr/local/bin formatjson</code></li>
<li>Save the service with the name "Reformat JSON Text".</li>
</ol>
<p>(Note: In step 4, if your <code>node</code> executable is not in <code>/usr/local/bin</code>, then substitute the appropriate directory. Also, if you saved <code>formatjson</code> to a directory other than <code>~/bin</code>, substitute the appropriate directory for that.)</p>
<p><img src="https://s3.amazonaws.com/undefinedvalue/ReformatJSONTextService.png" height="413" width="600" alt="Automator screenshot"/></p>
<p>Now, whenever I want to reformat some JSON in a text editor, I just select it, right-click, and choose <strong>Services > Reformat JSON Text</strong>.</p>
<p>And when I want to reformat things from the command line, this works:</p>
<div class="highlight"><pre><span></span><code>formatjson <ugly.json >pretty.json
</code></pre></div>
<p>If you'd rather have a web page that does this, see <a href="https://undefinedvalue.com/2013/03/02/web-page-reformatting-json-text">A Web Page for Reformatting JSON Text</a>.</p>Git GUI Clients for OS X2013-02-28T13:55:54-05:002013-02-28T13:55:54-05:00Kristopher Johnsontag:undefinedvalue.com,2013-02-28:/git-gui-clients-os-x.html<p>I have recently evaluated a few <a href="http://git-scm.com">Git</a> GUI apps for OS X. Here are my impressions.</p>
<!--break-->
<h2>SourceTree</h2>
<p><a href="http://www.sourcetreeapp.com">http://www.sourcetreeapp.com</a></p>
<p>Pros:</p>
<ul>
<li>Free</li>
<li>Supports Mercurial repositories</li>
<li>Supports Subversion repositories (via git-svn)</li>
<li>Supports <a href="https://github.com/nvie/gitflow">git-flow</a></li>
<li>Information-rich display layout (but some say it is too complicated)</li>
<li>Also available for Windows</li>
</ul>
<p>Cons:</p>
<ul>
<li>Least attractive …</li></ul><p>I have recently evaluated a few <a href="http://git-scm.com">Git</a> GUI apps for OS X. Here are my impressions.</p>
<!--break-->
<h2>SourceTree</h2>
<p><a href="http://www.sourcetreeapp.com">http://www.sourcetreeapp.com</a></p>
<p>Pros:</p>
<ul>
<li>Free</li>
<li>Supports Mercurial repositories</li>
<li>Supports Subversion repositories (via git-svn)</li>
<li>Supports <a href="https://github.com/nvie/gitflow">git-flow</a></li>
<li>Information-rich display layout (but some say it is too complicated)</li>
<li>Also available for Windows</li>
</ul>
<p>Cons:</p>
<ul>
<li>Least attractive</li>
</ul>
<p>I have been using SourceTree for the past year, and while I do remember some confusion when I first used it, it feels very easy to use now. I like its support of Subversion and of git-flow, and I think it has the most useful screen layout. However, it is ugly.</p>
<h2>Tower</h2>
<p><a href="http://www.git-tower.com">http://www.git-tower.com</a></p>
<p>Pros:</p>
<ul>
<li>Visually attractive</li>
</ul>
<p>Cons:</p>
<ul>
<li>Not free, most expensive ($59)</li>
<li>Not available from Mac App Store (If I have to pay for a Mac app, that's where I want to buy it.)</li>
<li>Inefficient UI (too much empty space, too many clicks required to perform common operations)</li>
<li>Only shows one repository at a time</li>
</ul>
<p>Tower is the prettiest of the bunch, but feels inefficient, and is a lot more expensive than the other options. When the 30-day trial expires, I will delete it.</p>
<h2>Gitbox</h2>
<p><a href="http://gitboxapp.com">http://gitboxapp.com</a></p>
<p>Pros:</p>
<ul>
<li>Minimalistic (in a good way)</li>
</ul>
<p>Cons:</p>
<ul>
<li>Not free (but only $15)</li>
<li>Minimalistic (in a bad way)</li>
<li>No integrated diff-viewer. Viewing differences requires launching an external tool.</li>
</ul>
<p>Gitbox is focused on making it easy to commit changes with a few keystrokes. It is not very good for browsing through changes, due to the need to use an external app to view changes. This app isn't appealing to me. I don't need a GUI to quickly make commits: I use the command line for that. I like having a GUI to make it easy for me to review changes before I commit or push, and Gitbox doesn't help me with that.</p>
<h2>GitX-dev</h2>
<p><a href="https://github.com/rowanj/gitx">https://github.com/rowanj/gitx</a></p>
<p>Pros:</p>
<ul>
<li>Free</li>
<li>Open-source</li>
</ul>
<p>Cons:</p>
<ul>
<li>Clumsy staging workflow</li>
</ul>
<p>This feels a lot like SourceTree, but is not as polished. It is nice that it's open-source/free-as-in-speech, but if all one cares about is free-as-in-beer, SourceTree is the better free option.</p>
<h2>GitHub for Mac</h2>
<p><a href="http://mac.github.com">http://mac.github.com</a></p>
<p>Pros:</p>
<ul>
<li>Free</li>
<li>Nice integration with GitHub.com website</li>
<li>Attractive easy-to-use UI</li>
</ul>
<p>Cons:</p>
<ul>
<li>Feels dumbed-down</li>
</ul>
<p>I would recommend this to someone who only uses Git in association with GitHub. It is easy to use and is a great match for the workflow promoted by GitHub. But as a general-purpose Git client, it seems to be missing some features (or maybe I just didn't try hard enough to find them).</p>
<h2>Closing Thoughts</h2>
<p>I typically work alone. Sometimes I work with one other developer. I don't have to worry much about merging or keeping track of lots of commits by other developers. So, what I want from a Git client may be different from what others need.</p>
<p>I use the command line for anything "complicated". I don't trust the GUIs to do what I expect. All Git GUIs should provide some sort of log that indicates exactly what git operations they have performed.</p>
<p>It looks like I will stick with SourceTree, but I will force myself to use the others once in a while. SourceTree feels right to me, but I wonder whether that is due more to familiarity than to actual goodness.</p>
<p>I will probably recommend GitHub for Mac to any newbies who ask.</p>iOS UI Automation Cheatsheet2013-02-22T16:00:46-05:002013-02-22T16:00:46-05:00Kristopher Johnsontag:undefinedvalue.com,2013-02-22:/ios-ui-automation-cheatsheet.html<p>I have just learned about Apple's UI Automation testing framework. Unfortunately, I don't have an iOS project to work on at the moment, so I am probably going to forget all about it. This is my cheatsheet. It may not help you at all.</p>
<h2>Tutorials</h2>
<ul>
<li><a href="http://blog.manbolo.com/2012/04/08/ios-automated-tests-with-uiautomation">http://blog.manbolo.com/2012 …</a></li></ul><p>I have just learned about Apple's UI Automation testing framework. Unfortunately, I don't have an iOS project to work on at the moment, so I am probably going to forget all about it. This is my cheatsheet. It may not help you at all.</p>
<h2>Tutorials</h2>
<ul>
<li><a href="http://blog.manbolo.com/2012/04/08/ios-automated-tests-with-uiautomation">http://blog.manbolo.com/2012/04/08/ios-automated-tests-with-uiautomation</a></li>
<li><a href="http://alexvollmer.com/posts/2010/07/03/working-with-uiautomation/">http://alexvollmer.com/posts/2010/07/03/working-with-uiautomation/</a></li>
<li><a href="https://developer.apple.com/videos/wwdc/2010/">WWDC 2010 Session 306 - Automating User Interface Testing with Instruments</a></li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a href="http://developer.apple.com/library/ios/#documentation/DeveloperTools/Reference/UIAutomationRef/_index.html">UI Automation JavaScript Reference</a></li>
<li><a href="http://developer.apple.com/library/ios/#documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/UsingtheAutomationInstrument/UsingtheAutomationInstrument.html">Instruments User Guide: Automating UI Testing</a></li>
</ul>
<h2>Tips</h2>
<ul>
<li>Use the Tuneup library: <a href="https://github.com/alexvollmer/tuneup_js">https://github.com/alexvollmer/tuneup_js</a></li>
<li>Assign an <code>accessibilityIdentifier</code> to each UI element.</li>
<li>Set <code>UIATarget.onAlert</code> to handle externally generated alerts.</li>
<li>Use <code>UIAElement.logElementTree()</code> to figure out how to navigate the visual hierarchy.</li>
<li>If a value doesn't change when expected, try adding <code>UIATarget.delay(1);</code></li>
</ul>Amazon Marketplace 5-Star Rating Extortion2013-02-06T19:11:56-05:002013-02-06T19:11:56-05:00Kristopher Johnsontag:undefinedvalue.com,2013-02-06:/amazon-marketplace-5-star-rating-extortion.html<p>I recently bought my wife a coat from an Amazon Marketplace seller. We are happy with the coat, but I was troubled when I saw this notice bundled with it:</p>
<blockquote>
<p>Leave a 5-Star, Positive Review ~ Receive a Free One Year Warranty!*</p>
<p>Dear Valued Customer,</p>
<p>If you are COMPLETELY SATISFIED with …</p></blockquote><p>I recently bought my wife a coat from an Amazon Marketplace seller. We are happy with the coat, but I was troubled when I saw this notice bundled with it:</p>
<blockquote>
<p>Leave a 5-Star, Positive Review ~ Receive a Free One Year Warranty!*</p>
<p>Dear Valued Customer,</p>
<p>If you are COMPLETELY SATISFIED with this purchase, please RATE us 5 STARS and write a POSITIVE Customer Review on amazon.com within the next 7 days. This will activate your FREE ONE YEAR WARRANTY* and give you PRIORITY customer service.</p>
<p>[...]</p>
<p>*FREE ONE YEAR WARRANTY covers defects in material and workmanship. Merchandise may be returned for repair, replacement or store credit at company's sole discretion. To qualify for this warranty, customer must have left a 5-star rating and a positive customer review on amazon.com within 7 days of receipt of merchandise."</p>
</blockquote>
<p>That's right: they offer a warranty that covers defects, but only if you help them sell their products to other unsuspecting customers. It's little wonder that they have an average 4.9-star rating.</p>
<p>This kind of gaming of rating and review systems really bugs me, so I contacted Amazon customer support. They told me that this is indeed a violation of their Marketplace policies, and that sellers who do this kind of thing can be banned from Amazon.</p>
<p>So, if you ever see something like this, go to http://www.amazon.com/gp/help/reports, select "Report a violation of our rules" and provide the details. Amazon will investigate and take appropriate action.</p>
<p>Also, leave the seller a one-star review. They deserve it.</p>"Takers"2013-01-28T15:16:14-05:002013-01-28T15:16:14-05:00Kristopher Johnsontag:undefinedvalue.com,2013-01-28:/takers.html<p>There is one hot-button issue that will always draw me into a rant: the assertion that anyone who draws a paycheck from the government is a "taker" rather than a "maker".</p>
<p>My wife is a science teacher in a public school. She worked her way through college and obtained a …</p><p>There is one hot-button issue that will always draw me into a rant: the assertion that anyone who draws a paycheck from the government is a "taker" rather than a "maker".</p>
<p>My wife is a science teacher in a public school. She worked her way through college and obtained a Masters degree and a Ph.D. She could have taken a position in university academia, but decided she could do more good in a classroom with students.</p>
<p>She works in one of the wealthiest districts of one of the wealthiest counties in the state. She teaches the children of dentists, lawyers, and accountants who spend their weekends on the golf course complaining about being crushed by taxes and arguing about which model of BMW to buy this year.</p>
<p>She always works over 60 hours per week. She works harder than anyone I've ever seen in private industry.</p>
<p>Her pay has been cut by about 25% over the past five years. A big chunk of what income she has left goes toward buying supplies that the school system and parents won't provide.</p>
<p>She's a <em>taker</em>. She's a leech sucking society's blood. She should stop teaching science to children and go do something <em>useful</em>, right?</p>DirecTV Customer Service Sucks, and They Are Watching Me2013-01-25T03:08:31-05:002013-01-25T03:08:31-05:00Kristopher Johnsontag:undefinedvalue.com,2013-01-25:/directv-customer-service-sucks-and-they-are-watching-me.html<p>This past weekend, two of our DirecTV satellite receivers failed. The failures were unrelated: one stopped working after the <a href="http://en.wikipedia.org/wiki/Magic_smoke">magic smoke</a> leaked out of the vents; the other kept rebooting itself and scanning its disk, without ever actually starting up.</p>
<p>My wife called DirecTV and told them about the problems …</p><p>This past weekend, two of our DirecTV satellite receivers failed. The failures were unrelated: one stopped working after the <a href="http://en.wikipedia.org/wiki/Magic_smoke">magic smoke</a> leaked out of the vents; the other kept rebooting itself and scanning its disk, without ever actually starting up.</p>
<p>My wife called DirecTV and told them about the problems. The customer service rep agreed to send a replacement for the receiver that was smoking. However, we would have to let the other receiver keep trying to scan/repair its disk for 48 hours before they would declare it dead and replace it.</p>
<p>A couple of days went by, and we received a replacement receiver. We hooked it up and called DirecTV to activate it, and that went fine.</p>
<p>So far, I'd call this reasonable service. Then we asked about the other failed receiver, which was still not working after 48 hours, and that's when things changed.</p>
<!--break-->
<h3>Special Circumstances</h3>
<p>The rep who helped us activate the replacement receiver for Old Smokey looked at what he could do about the other receiver. It turned out that, because we have had multiple receiver failures and replacements, he had to pass us over to a <em>special case manager</em>. The special case manager was, of course, not available at that time, so we had to agree to let him call us the following day.</p>
<p>The special case manager called at dinner time. I spent 45 minutes on the phone with him. Most of that time consisted of the following questions, asked repeatedly, with a few minutes of on-hold music once in a while:</p>
<ul>
<li>What are you doing with your receivers that causes them to fail?</li>
<li>How many receivers do you have?</li>
<li>Why don't your receivers' serial numbers match our records?</li>
<li>It would be <em>very unfortunate</em> if we had to charge you a lost-equipment fee for the receivers you cannot account for. I'm <em>trying</em> to help you. Are you <em>sure</em> you don't have any other receivers you haven't told me about?</li>
</ul>
<p>After a few rounds of questioning, the manager told me he wasn't going to honor my request to replace the bad-disk-drive receiver, because they had <em>already</em> replaced it, according to their records. And I didn't admit to having a receiver that their records said I did have. What had happened to that receiver?</p>
<p>It was obvious to me that when we made the original call about the two broken receivers, the rep recorded that they were sending a replacement for the bad-disk receiver instead of the smoking receiver.</p>
<p>So according to their records, I was supposed to still have a receiver I sent back, and not supposed to have a receiver they sent me. In the manager's mind, that meant there were <em>two receivers</em> unaccounted for. He kept asking the same questions in different ways, clearly convinced that I was lying or withholding information.</p>
<p>My wife and I have been paying DirecTV over a hundred dollars per month for several years. Do they really think we did that as a scam to steal one of their receivers?</p>
<h3>You Are Free to Go...But Don't Leave Town</h3>
<p>After 40 minutes of interrogation, he finally said, "Oh, I see what must have happened. That representative must have recorded the wrong ID. Let me fix that for you. Thank you for your patience."</p>
<p>Maybe he gave up on proving my guilt. Maybe it took him 40 minutes to accept that someone could have made a mistake.</p>
<p>At the end, he made it sound like I should be grateful that he was able to straighten things out for me.</p>
<p>And he let me know he'd be calling again to check up on me. If any more mysterious receiver failures happen, he'll give us his personal attention.</p>
<p>I look forward to the day when we can cancel DirecTV and get all our TV programs via the Internet. I think DirecTV knows how close we all are to that point, and they are milking us for all they can while they still have a business.</p>462013-01-17T13:43:20-05:002013-01-17T13:43:20-05:00Kristopher Johnsontag:undefinedvalue.com,2013-01-17:/46.html<p>Another year behind me. Not a bad year.</p>
<p>I picked up a new client this year, and reduced my time spent working with the client I've had for the past couple of years. The new client provides opportunities for more interesting work. I've been able to do more mobile and …</p><p>Another year behind me. Not a bad year.</p>
<p>I picked up a new client this year, and reduced my time spent working with the client I've had for the past couple of years. The new client provides opportunities for more interesting work. I've been able to do more mobile and web development, and will be doing some embedded-systems development soon. I may be able to put Windows/.NET development behind me for a while. I was able to give myself a nice bump in my hourly rate.</p>
<p>Family and dogs are doing fine. There is nothing very interesting to report, but I'm happy.</p>
<p>We're planning to visit relatives in North Dakota this summer. I haven't been back there for a while (over ten years, I think), so it will be nice to see everybody.</p>
<p>I think I'm healthier than I was a year ago. I <a href="https://undefinedvalue.com/2013/01/01/couch-potato-running-10k-few-months">started running</a> in August, and ran a 10K on New Year's Day. I have started training to run a half-marathon at the end of March. I've lost a few pounds and a few waistline inches. If I can lose 20 pounds this year, I'll be in good shape.</p>
<p>Goals for the coming year:</p>
<ul>
<li>Write in my journal every day.</li>
<li>Run a half marathon, and the Peachtree Road Race 10K. (Maybe: run Atlanta Marathon this fall.)</li>
<li>Practice guitar at least six days per week. My wife bought me a nice Fender Telecaster for Christmas/birthday, which has been a good motivator.</li>
<li>Learn to play keyboard. My brother bought me a keyboard for Christmas/birthday, so I have no excuses not to learn to play.</li>
<li>Look at this list of goals once in a while.</li>
</ul>From Couch Potato to Running a 10K in a Few Months2013-01-01T20:46:31-05:002013-01-01T20:46:31-05:00Kristopher Johnsontag:undefinedvalue.com,2013-01-01:/couch-potato-running-10k-few-months.html<p>In August, I started running after a few years of sitting on the couch. I ran the <a href="https://undefinedvalue.com/2007/07/04/peachtree-road-race-2007">Peachtree Road Race</a> back in 2007, then stopped running completely shortly thereafter. My post-40 physical decline has been distressing, so I decided I needed to get back into doing something active.</p>
<p>Just as …</p><p>In August, I started running after a few years of sitting on the couch. I ran the <a href="https://undefinedvalue.com/2007/07/04/peachtree-road-race-2007">Peachtree Road Race</a> back in 2007, then stopped running completely shortly thereafter. My post-40 physical decline has been distressing, so I decided I needed to get back into doing something active.</p>
<p>Just as I started thinking "Maybe I should start running again," I ran across a mention of the <a href="http://www.coolrunning.com/engine/2/2_3/181.shtml">Couch-to-5K</a> running plan in a blog. The core idea is that the plan <em>very gradually</em> builds up your endurance. Too many people decide "I need to start running," then go out and run a couple of miles, and then wake up in pain the following morning and decide they will never run again. C25K starts you off alternating between 60 seconds of jogging and 90 seconds of walking the first week, then 90 seconds of jogging and two minutes of walking the second week, then keeps bumping up the jogging time and reducing the walking time until you are running for 30 minutes straight.</p>
<p>The first couple of weeks of C25K seemed really easy, and I was tempted to skip forward a couple of weeks, but I'm glad I didn't. I think its gradual nature is the key to its success. Every other time I've gotten into running, I've had problems with pain in my right ankle, but that didn't happen this time.</p>
<p>There is an <a href="http://www.active.com/mobile/c25k">iPhone app</a> that will tell you when to start running and when to start walking, so it appealed to both the geeky and lazy aspects of my personality.</p>
<p>I finished C25K in October, and was surprised at how easy it had been. So I decided to follow up with the <a href="http://www.active.com/mobile/10k">5K-to-10K</a> program, which also has an iPhone app.</p>
<p>I expected going from 5K to 10K to be easier than going from 0 to 5K, but it wasn't. The first few weeks were easy, but once I got up to about five miles, it was tough to keep going. But I did it, and a few weeks ago I did my first 60-minute continuous run.</p>
<p>Unfortunately, this coincided with the holiday season, so while I lost 10 pounds between August and December, I put five pounds back on during December. I'll have to work on that.</p>
<p>Today, New Year's Day, I ran the <a href="http://atlantatrackclub.org/events/view/2013-pt-solutions-resolution-run">2013 PT Solutions Resolution Run 10K</a>, organized by the Atlanta Track Club. It was 45 degrees and raining, so it wasn't pleasant, but I did finish, running the whole 6.2 miles except for brief walks at the water stations. My time was one hour and three minutes, which is not great, but I'm happy with it.</p>
<p>Next, on to the <a href="http://www.berryhalfmarathon.com/">half marathon</a>!</p>Embracing Customization and Automation2012-12-09T23:00:59-05:002012-12-09T23:00:59-05:00Kristopher Johnsontag:undefinedvalue.com,2012-12-09:/embracing-customization-and-automation.html<p>During the first decade-or-so of my career (the 90's), I had to use a lot of different computers. I worked on half a dozen different operating systems, and during the course of a day it was common to use four or five different computers, only one or two of which …</p><p>During the first decade-or-so of my career (the 90's), I had to use a lot of different computers. I worked on half a dozen different operating systems, and during the course of a day it was common to use four or five different computers, only one or two of which were in my cubicle. I'd spend weeks at customer sites. I often sat side-by-side with other developers to work through issues. Every few weeks, a new machine would appear or another machine would disappear.</p>
<p>This led me to decide that I should never treat any computer as "mine", no matter how often I used it or how little anyone else used it. So I didn't spend time customizing any of the system or application settings to suit me. I didn't mess with the keyboard bindings. I didn't write a lot of scripts or macros. I tried to become as adept as possible at using the "stock configuration" of every tool. The extent of my customization was copying a <code>.emacs</code> file from machine to machine.</p>
<p>This habit was reinforced when I got into Windows development, and found that I needed to re-install Windows from scratch every couple of months to keep things running well, and that I had to help non-technical users figure out how to fix their machines. Sticking to the stock configuration and standard applications seemed to be the only way to stay sane.</p>
<p>I often rolled my eyes when I'd hear someone talk about how hyperproductive they were with their Dvorak keyboard layout and customized desktop shell and macro recorders. "Great", I'd think, "but can you also be productive with the computer in the neighboring cubicle, or the computer at your parents' house?" I was proud that I could sit down in front of any computer, and not need to worry about how to work without All My Stuff with me. As the Buddhists say, attachment is the cause of suffering, so let go of your attachments.</p>
<p>Over the past couple of years, I've finally started to relax this stock-configuration-only policy. A few things have changed:</p>
<ul>
<li>I do practically all my work with my own personally owned laptop. I always have it with me, at every work location and at my home. Even when I have to do something with another computer, I often do so by connecting to it from my laptop. So my machine really is "mine", and I rarely touch another.</li>
<li>Dropbox, iCloud, GitHub, and similar services make it easy to keep All My Stuff in sync between machines, and to set up a brand-new machine the way I want it.</li>
<li>I'm no longer stuck with using Windows most of the time. Mac OS X is a lot easier to customize and automate, due to its UNIX underpinnings. (Yes, seriously.)</li>
</ul>
<p>Here are the things I've found most useful:</p>
<ul>
<li><a href="http://www.keyboardmaestro.com/main/">Keyboard Maestro</a>: This thing is awesome. Key rebinding was the customization I put off the longest, due to concerns about the need to memorize a bunch of new keyboard shortcuts and the need to find keys that were not already used by my applications, but now that I've dived in, I like it.</li>
<li><a href="http://www.alfredapp.com">Alfred</a>: a nice app launcher. I've also tried QuickSilver and LaunchBar, but decided I prefer the simplicity of Alfred</li>
<li><a href="http://smilesoftware.com/TextExpander/index.html">TextExpander</a></li>
<li>Automator and the Services menu: While not as useful as they could be, due to Apple's on-again-off-again interest in supporting and promoting them, they are great for hooking little scripts into my workflow.</li>
<li>Safari Extensions: If you know a bit about JavaScript and the DOM, you can automate a lot of common web activities, and even customize website behavior.</li>
</ul>
<p>Here are things that I've experimented with, and decided to avoid:</p>
<ul>
<li>AppleScript. I use it when I have to, but it is the <strong>worst scripting language ever created</strong>. Whenever I can use bash, Python, or JavaScript instead, I do.</li>
<li>Application-specific macros. Some applications provide their own mechanisms for replaying sequences of keystrokes or triggering scripts. I use these when they make sense, but it is generally better to let Keyboard Maestro, TextExpander, or the Services menu take care of them system-wide.</li>
<li>Speech recognition: The Mac has long had a speech recognition system that lets you do a lot of things by just telling it to do so. However, this has never been as useful as, for example, Siri on the iPhone.</li>
<li>Doing everything suggested by Merlin Mann, Brett Terpstra, Dr. Drang, and MacSparky. Those are all smart people who figure out some cool stuff, but it's easy to get bogged down trying to use it all. It's better to examine your own workflow and improve it than it is to try to adopt someone else's workflow. (It also makes me feel stupid when it takes me longer to <em>read</em> that stuff than it takes them to <em>write</em> it.)</li>
</ul>
<p>The process of customizing my computer has changed the way I use it. Whenever I do the same sequence of steps more than a couple of times, I stop and think about whether there is a way to make it faster or easier. Often the answer is not to customize the machine further, but to change my own work habits.</p>Node.js Cheatsheet2012-11-20T15:54:01-05:002012-11-20T15:54:01-05:00Kristopher Johnsontag:undefinedvalue.com,2012-11-20:/nodejs-cheatsheet.html<p>I am learning about <a href="http://nodejs.org">node.js</a>. This is my cheatsheet. It may not be useful to you at all.</p>
<p>Also see <a href="https://undefinedvalue.com/2011/06/16/my-javascript-cheatsheet">My JavaScript Cheatsheet</a>.</p>
<!--break-->
<h2>Documentation</h2>
<ul>
<li>API Docs: <a href="http://nodejs.org/api/">http://nodejs.org/api/</a></li>
<li>Debugger: <a href="http://nodejs.org/api/debugger.html">http://nodejs.org/api/debugger.html</a></li>
<li>Cluster: <a href="http://nodejs.org/docs/latest/api/cluster.html">http://nodejs.org/docs/latest/api/cluster.html</a></li>
<li><em>Up and Running …</em></li></ul><p>I am learning about <a href="http://nodejs.org">node.js</a>. This is my cheatsheet. It may not be useful to you at all.</p>
<p>Also see <a href="https://undefinedvalue.com/2011/06/16/my-javascript-cheatsheet">My JavaScript Cheatsheet</a>.</p>
<!--break-->
<h2>Documentation</h2>
<ul>
<li>API Docs: <a href="http://nodejs.org/api/">http://nodejs.org/api/</a></li>
<li>Debugger: <a href="http://nodejs.org/api/debugger.html">http://nodejs.org/api/debugger.html</a></li>
<li>Cluster: <a href="http://nodejs.org/docs/latest/api/cluster.html">http://nodejs.org/docs/latest/api/cluster.html</a></li>
<li><em>Up and Running with Node.js</em>: <a href="http://ofps.oreilly.com/titles/9781449398583/">http://ofps.oreilly.com/titles/9781449398583/</a></li>
<li><em>Async JavaScript: Build More Responsive Apps with Less Code</em> by Trevor Burnham: <a href="http://pragprog.com/book/tbajs/async-javascript">http://pragprog.com/book/tbajs/async-javascript</a></li>
<li><a href="http://stephensugden.com/middleware_guide/">A short guide to Connect Middleware</a></li>
</ul>
<h2>Installation</h2>
<ul>
<li>Downloadable installers: http://nodejs.org/download/</li>
<li>Installing via various package managers: https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager</li>
<li>Source code: https://github.com/joyent/node</li>
</ul>
<h2>Debugging</h2>
<ul>
<li><a href="http://nodejs.org/api/debugger.html">http://nodejs.org/api/debugger.html</a></li>
<li><a href="https://github.com/dannycoates/node-inspector">https://github.com/dannycoates/node-inspector</a></li>
</ul>
<h2>Utilities</h2>
<ul>
<li><a href="https://npmjs.org">npm</a> - package manager for Node</li>
<li><a href="https://github.com/isaacs/node-supervisor">supervisor</a> - automatically restarts Node-based server when files change</li>
<li><a href="https://github.com/remy/nodemon">nodemon</a> - automatically restarts Node-based server when files change</li>
<li><a href="https://github.com/nodejitsu/forever">forever</a> - ensures that a script runs continuously</li>
<li><a href="http://jfromaniello.github.com/winser/">Winser</a> - runs Node as a Windows service, using <a href="http://nssm.cc">nssm</a></li>
<li><a href="http://jashkenas.github.com/docco/">docco</a> and <a href="https://github.com/mbrevoort/docco-husky">docco-husky</a> - documentation generator</li>
<li><a href="https://github.com/dannycoates/node-inspector">node-inspector</a> - browser-based debugging interface</li>
</ul>
<h2>Modules</h2>
<p>These libraries are especially useful (to me, for what I have done):</p>
<ul>
<li><a href="http://expressjs.com">Express</a> - web application framework</li>
<li><a href="http://www.senchalabs.org/connect/">Connect</a> - middleware framework</li>
<li><a href="http://jade-lang.com">Jade</a> - template engine</li>
<li><a href="http://socket.io">Socket.IO</a> - support for WebSockets and other connection mechanisms</li>
<li><a href="https://github.com/mikeal/request">Request</a> - simplified HTTP requests</li>
<li><a href="https://github.com/Leonidas-from-XIV/node-xml2js">xml2js</a> - simple XML-to-JavaScript converter</li>
<li><a href="https://github.com/flatiron/nconf">nconf</a> - configuration files</li>
<li><a href="http://pdfkit.org">PDFKit</a> - PDF generation</li>
<li><a href="http://backbonejs.org">Backbone</a> - client-side data models, views, UI binding</li>
<li><a href="http://underscorejs.org/">Underscore</a> - functional programming helper library</li>
<li><a href="https://github.com/caolan/async">Async.js</a> - asynchronous JavaScript</li>
<li><a href="https://github.com/kriskowal/q">Q.js</a> - asynchronous promises</li>
<li><a href="http://visionmedia.github.com/mocha/">Mocha</a> - testing framework</li>
<li><a href="http://github.com/visionmedia/should.js">should.js</a> - BDD-style assertion library</li>
<li><a href="http://chaijs.com/">Chai</a> - BDD- and TDD-style assertion library</li>
</ul>
<p>See https://npmjs.org for a complete list of all available modules.</p>
<h2>Creating a New Web App with the Express Framework</h2>
<p>If you don't already have the Express framework installed, do this:</p>
<div class="highlight"><pre><span></span><code>npm install -g express
</code></pre></div>
<p>To create a new app, do this:</p>
<div class="highlight"><pre><span></span><code>express --sessions MyNewApp
cd MyNewApp
npm install
</code></pre></div>
<p>To run the app:</p>
<div class="highlight"><pre><span></span><code>cd MyNewApp
node app
</code></pre></div>
<p>By default, the app will listen for requests on port 3000, so you can browse to http://locahost:3000/ to view the app. Modify <code>app.js</code> or set the <code>PORT</code> environment variable to use a different port.</p>
<h2>Snippets</h2>
<script src="https://gist.github.com/4118742.js?file=nodejs_snippets.js"></script>Havenjark Color Theme for Xcode 42012-10-30T11:59:34-04:002012-10-30T11:59:34-04:00Kristopher Johnsontag:undefinedvalue.com,2012-10-30:/havenjark-color-theme-xcode-4.html<p>I've been experimenting with low-contrast color themes in my source-code editors. For a while, I thought I had settled on <a href="http://slinky.imukuppi.org/zenburnpage/">Zenburn</a>. However, I recently ran across <a href="http://eclipsecolorthemes.org/?view=theme&id=25">Havenjark</a> in the Eclipse Color Themes plugin, and I decided it is perfection.</p>
<p>The only problem was that, while I could find Havenjark theme …</p><p>I've been experimenting with low-contrast color themes in my source-code editors. For a while, I thought I had settled on <a href="http://slinky.imukuppi.org/zenburnpage/">Zenburn</a>. However, I recently ran across <a href="http://eclipsecolorthemes.org/?view=theme&id=25">Havenjark</a> in the Eclipse Color Themes plugin, and I decided it is perfection.</p>
<p>The only problem was that, while I could find Havenjark theme files for Eclipse and Textmate/Sublime Text 2, I could not find one for Xcode. So I converted the Eclipse color theme to Xcode 4's color theme format by hand.</p>
<p>If you'd like to try Havenjark in Xcode 4 yourself, download <a href="https://github.com/kristopherjohnson/havenjark/raw/master/Havenjark.dvtcolortheme">Havenjark.dvtcolortheme</a> and copy it to your <code>~/Library/Developer/Xcode/UserData/FontAndColorThemes/</code> directory. Then, in Xcode, go to <em>Preferences</em> -> <em>Fonts & Colors</em> and select it.</p>
<p>My theme uses the Bitstream Vera Sans Mono font, which you can download for free from various locations on the Internet, or you can just change the font to something of your liking.</p>
<p>I'm not going to bother converting this to Visual Studio's color-theme format. There is no point in trying to make Windows development look nice.</p>KJFlashlight2012-10-02T14:54:30-04:002012-10-02T14:54:30-04:00Kristopher Johnsontag:undefinedvalue.com,2012-10-02:/kjflashlight.html<p>I got fed up with the complexities of the various iPhone flashlight apps I've tried, so I whipped up my own dead-simple flashlight app.</p>
<p>It has three buttons that control the LED on the back of the phone:</p>
<ul>
<li>On</li>
<li>Off</li>
<li>Flash (once per second)</li>
</ul>
<p>That's it.</p>
<p>My hope is that …</p><p>I got fed up with the complexities of the various iPhone flashlight apps I've tried, so I whipped up my own dead-simple flashlight app.</p>
<p>It has three buttons that control the LED on the back of the phone:</p>
<ul>
<li>On</li>
<li>Off</li>
<li>Flash (once per second)</li>
</ul>
<p>That's it.</p>
<p>My hope is that keeping it as simple as possible means it will load quickly and stay resident in memory, so I can have light instantly whenever I want it. This is a lot more important to me than all the controls and "features" provided by other flashlight apps.</p>
<p>You may want to build your own dead-simple flashlight app, or maybe you want to see some code that controls the LED light. The source code is on GitHub: https://github.com/kristopherjohnson/KJFlashlight</p>Apple Product Names2012-08-29T11:29:00-04:002012-08-29T11:29:00-04:00Kristopher Johnsontag:undefinedvalue.com,2012-08-29:/apple-product-names.html<p><strong>Correct:</strong> iPod, iPhone, iPad, iTunes, Mac, MacBook, Apple</p>
<p><strong>Incorrect:</strong> IPOD, Iphone, I-Phone, IFone, i-PAD, EyePad, Ipadd, I-Toons, MAC, MAC Book, McIntosh, APPLE, AAPL</p>
<p>(Don't be an idiot. This isn't complicated.)</p>Re-attaching an iMac Stand After VESA Mount Use2012-08-19T21:48:38-04:002012-08-19T21:48:38-04:00Kristopher Johnsontag:undefinedvalue.com,2012-08-19:/re-attaching-imac-stand-after-vesa-mount-use.html<p>We recently purchased a new iMac to replace a old iMac. The old iMac was attached to a wall mount using Apple's <a href="http://store.apple.com/us/product/MD179ZM/A">VESA Mount Adapter</a>, so I had to remove the old iMac from that adapter and attach the new iMac to it. That went fine, but then I had …</p><p>We recently purchased a new iMac to replace a old iMac. The old iMac was attached to a wall mount using Apple's <a href="http://store.apple.com/us/product/MD179ZM/A">VESA Mount Adapter</a>, so I had to remove the old iMac from that adapter and attach the new iMac to it. That went fine, but then I had problems re-connecting the original stand to the old iMac. What follows is what I learned, and I hope it helps others who have the same problem.</p>
<p>When you connect an iMac to a VESA adapter, you first have to remove the iMac's stand. This procedure is described in the VESA adapter manual, and it is not difficult. Unfortunately, that manual does not tell you how to reattach the stand to the iMac when you no longer want it mounted to a wall, and simply following the manual in reverse doesn't work.</p>
<p>The complication is that there is a latch inside an iMac that keeps the stand at a range of angles that hides the screws. The VESA adapter kit comes with a little credit-card-sized plastic "access card" that you slide into the stand slot to release the latch and "lock" it into the position needed to remove the screws and connect the VESA adapter. But when you take the VESA adapter off and reconnect the stand, it is not obvious how to unlock that latch.</p>
<p>This was one time I really wish the back of the iMac was transparent. It would have been nice to see the latch to figure out how to unlock it.</p>
<p>After some head-scratching and Googling and trial and error, I got the latch unlocked. Here's how to do it:</p>
<ol>
<li>Lay the iMac face down on a table.</li>
<li>Re-attach the iMac stand to the flange using the 8 screws. Note that you will need to position the stand such that its bottom hangs off the table.</li>
<li>Push down slightly on the stand to expose the latch, and insert the access card at a low angle (that is, don't insert it by pressing directly toward the table, but try to slide it toward the top of the iMac).</li>
<li>When you feel the card moving the latch, lift on the stand and it should go into the proper position. If not, repeat the previous step at a slightly different angle.</li>
</ol>
<p>To give credit where it's due, here are the online forum postings that I found most helpful:</p>
<ul>
<li>http://forums.macrumors.com/showthread.php?t=502077</li>
<li>https://discussions.apple.com/message/17965614#17965614</li>
</ul>Downloading an Image from Gravatar Using the Command Line2012-08-13T20:28:08-04:002012-08-13T20:28:08-04:00Kristopher Johnsontag:undefinedvalue.com,2012-08-13:/downloading-image-gravatar-using-command-line.html<p>I am signed up for the <a href="join.app.net">app.net</a> alpha, and wanted to upload my avatar picture. Unfortunately, I can't find my avatar picture anywhere on my computer.</p>
<p><a href="http://gravatar.com">Gravatar</a> is a free service that allows you to save your avatar (picture) for use by multiple websites. Gravatar has a copy of …</p><p>I am signed up for the <a href="join.app.net">app.net</a> alpha, and wanted to upload my avatar picture. Unfortunately, I can't find my avatar picture anywhere on my computer.</p>
<p><a href="http://gravatar.com">Gravatar</a> is a free service that allows you to save your avatar (picture) for use by multiple websites. Gravatar has a copy of the avatar I wanted, so I hoped I could just download it via the same user interface you use to upload images. It turns out that you can't do that: the user interface lets you upload pictures, but does not provide a download option. So I had to figure out how to retrieve my image the way that a Gravatar web client would.</p>
<p>According to the <a href="http://en.gravatar.com/site/implement/images/">Gravatar documentation</a>, one can get a image for a user by doing an HTTP GET with a URL of this form:</p>
<div class="highlight"><pre><span></span><code>http://www.gravatar.com/avatar/HASH
</code></pre></div>
<p>where <code>HASH</code> is the MD5 hash of the user's email address. You can calculate this using the <a href="https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man1/md5.1.html">md5</a> command-line utility.</p>
<p>So, here is the complete command line that will calculate my MD5 hash and the retrieve the image using the <a href="https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man1/curl.1.html">curl</a> utility:</p>
<script src="https://gist.github.com/3343852.js?file=gravatar.sh"></script>Job Title: Architect2012-07-31T14:36:34-04:002012-07-31T14:36:34-04:00Kristopher Johnsontag:undefinedvalue.com,2012-07-31:/job-title-architect.html<p><em>What follows is from an actual job opportunity that was presented to me. It has been edited for brevity and to remove identifying information. I also cleaned up typos and broken grammar (recruiters apparently never read these things before sending them to candidates).</em></p>
<p><strong>Job Title:</strong> Architect</p>
<p><strong>Responsibilities:</strong></p>
<ul>
<li>Drive Solution Architectures …</li></ul><p><em>What follows is from an actual job opportunity that was presented to me. It has been edited for brevity and to remove identifying information. I also cleaned up typos and broken grammar (recruiters apparently never read these things before sending them to candidates).</em></p>
<p><strong>Job Title:</strong> Architect</p>
<p><strong>Responsibilities:</strong></p>
<ul>
<li>Drive Solution Architectures for high performance and very highly scalable platforms.</li>
<li>Collaborate with key Telecommunication Architecture on definition of delivery of solution.</li>
<li>Participate in Joint Application Design/Requirements sessions</li>
<li>Create and present PowerPoints to leadership describing architectural approaches and solutions</li>
</ul>
<p><strong>Seeking Enterprise Architecture related type candidates. Not seeking a candidate with developer, project manager, or solution architect experience.</strong></p>
<p><strong>Skills & Experience Needed:</strong></p>
<ul>
<li><strong>UML 2</strong> 3-5 years Required</li>
<li><strong>Visio</strong> 5 years Required</li>
<li><strong>MS Word</strong> 5 years Required</li>
<li><strong>Agile Methodology</strong> Intermediate Desired</li>
<li><strong>OAuth</strong> Intermediate Desired</li>
<li><strong>Cloud computing</strong> Intermediate Desired</li>
</ul>
<p><em>Note what is not required of an Architect: Any knowledge of how to actually produce working software. All you need to know is how to use Visio and PowerPoint.</em></p>Distributing a Custom iOS B2B App2012-07-12T23:22:47-04:002012-07-12T23:22:47-04:00Kristopher Johnsontag:undefinedvalue.com,2012-07-12:/distributing-custom-ios-b2b-app.html<p>I recently went through the process of distributing a free <a href="https://developer.apple.com/programs/volume/b2b/">custom B2B app</a> in the App Store. I hit a few snags, and I found very little information about the process, or help in online forums, and Apple Support assistance was very slow. So I'm documenting what I learned here …</p><p>I recently went through the process of distributing a free <a href="https://developer.apple.com/programs/volume/b2b/">custom B2B app</a> in the App Store. I hit a few snags, and I found very little information about the process, or help in online forums, and Apple Support assistance was very slow. So I'm documenting what I learned here. I hope it helps someone.</p>
<h2>Overview</h2>
<p>A <em>custom B2B app</em> is an iOS app that is distributed through the App Store, but which is only available for purchase by a specific set of customers. It lets you take advantage of the App Store infrastructure while still restricting distribution of the app. Thus, it can be an attractive alternative to using the iOS Enterprise Developer Program or ad-hoc deployment to distribute in-house apps.</p>
<p>There are some significant restrictions:</p>
<ul>
<li>The customers must be members of Apple's <a href="http://www.apple.com/business/vpp/">Volume Purchase Program</a> (VPP). To enroll in the program, the customer must have a DUNS number. To purchase apps, the customer must use the VPP portal rather than going through the usual App Store mechanisms. When the customer purchases apps through the VPP, they receive redemption codes when they can then distribute to employees for installation on devices.</li>
<li>The VPP is currently only available in the US. (It is "coming soon" to Australia, Canada, France, Germany, Italy, Japan, New Zealand, Spain, and United Kingdom.)</li>
<li>Even though the apps are targeted at the needs of a specific set of customers, they still must go through Apple's review process, which takes time and could result in a rejection.</li>
</ul>
<p>There used to be another restriction: B2B apps had to have a minimum price of $9.99, and Apple would take a 30% commission. This may not seem like a significant restriction, but many potential customers would balk at paying a per-download price for a custom app after paying up-front development costs. However, Apple recently changed the rules to allow custom B2B apps to be distributed for free.</p>
<p>The <a href="https://developer.apple.com/videos/wwdc/2012/">WWDC 2012 session video</a> "Building and Distributing Custom B2B Apps for IOS" is worth watching if you are interested in distributing a B2B app. (Note that you'll need to be a registered iOs developer to access this video.)</p>
<h2>Prerequisites</h2>
<p>To distribute a custom B2B app, one must be a member of the <a href="https://developer.apple.com/programs/ios/">iOS Developer Program</a>.</p>
<p>In addition, one must agree to the iOS Paid Applications agreement, and must provide banking and tax information to Apple. This is true <em>even if your custom B2B apps will be free.</em> (I don't know why this is true, but I suspect it has something to do with the original requirement that B2B apps have a minimum price.)</p>
<h2>Submission and Distribution</h2>
<p>In general, submitting a B2B app to the App Store works just like submitting a for-the-public app. You first create the new app in iTunes Connect, then you use Xcode to upload the binary, then you wait for Apple to approve it. Here is what is different:</p>
<ul>
<li>You will need the Apple ID associated with each customer's VPP account.</li>
<li>When creating the app in iTunes Connect, on the page where you set the Availability Date and Price Tier, there is a <em>Custom B2B App</em> checkbox. Check it, and you will be able to enter the customers' Apple IDs.</li>
<li>When the app is approved for sale, your customers purchase redemption codes from the VPP portal. It will not be available in the App Store app or iTunes.</li>
</ul>
<p>Note that you cannot convert a regular app to a B2B app, or vice versa. Once it is created one way, you can't change it. For a B2B app, you can add Apple IDs for additional customers after creation.</p>
<h2>My Story</h2>
<p>What follows is my own personal history of trying to get this stuff set up, and the snags we hit ("we" meaning myself, my colleagues and customers).</p>
<p>The background is that we had initially deployed an app to a customer's employees using ad-hoc distribution to a set of about 20 devices. The customer decided to roll the app out to a few hundred more employees, so we looked at the options, and when Apple announced that VPP apps no longer had a minimum-price restriction, we decided to go down that path.</p>
<p>My initial attempt to add a new free custom B2B app in iTunes Connect failed. The B2B option was not available on the screen where it was supposed to be. We eventually guessed that we'd need to accept the iOS Paid Applications agreement (even though the app was free). After doing that, then the B2B option was available. I submitted the app, and waited.</p>
<p>After about a week, the status went from "Waiting for Review" to "in Review", but we immediately received a message stating that the app would require additional review time. I don't know whether all B2B apps require additional review, or if there was something about our app that raised questions, or if it was just because it went into review on the Fourth of July.</p>
<p>About five days later, the status changed to "Ready for Sale". Mission accomplished, right? No. The customer reported that they could not see the app in their VPP portal. We found information online stating that once an app was accepted, it might take 24 hours before it would be visible. So we waited 24 hours. Customer still couldn't see it.</p>
<p>After a few days of exchanging email with Apple Support, we finally heard that we needed to supply banking and tax information to Apple (even though the app is free). So we did that, and then the customer could see the app in their VPP portal. Woo-hoo! Now we're done, right?</p>
<p>No. When the customer attempted to "purchase" the free app, they got an error message saying "An unknown error occurred". So we send more email to Apple Support. Eventually somebody at Apple gives me a phone call, and tells me everything is set up correctly in iTunes Connect and the App Store, so there is nothing more we needed to do, and that the customer needed to contact VPP support to resolve their purchasing issue.</p>
<p>We told the customer they would need to contact VPP support. The customer reported dissatisfaction with VPP support (the word "sucks" was used). The customer was also annoyed to discover that their <a href="http://en.wikipedia.org/wiki/Mobile_device_management">MDM</a> solution, MaaS360, supports purchase of App Store apps, but does not support purchase of custom B2B apps through the VPP. Thus, when they download redemption codes from the VPP, they have to manually mail them out to employees, rather than relying on the MDM to automatically update the managed devices.</p>
<p>So, the end result is that the customer didn't love this solution, but they can live with it. If we consider the B2B route with any future customers, we will definitely have some discussions about the pros and cons.</p>Entity Framework Cheatsheet2012-07-11T14:46:54-04:002012-07-11T14:46:54-04:00Kristopher Johnsontag:undefinedvalue.com,2012-07-11:/entity-framework-cheatsheet.html<p>This is my cheatsheet for using Microsoft's <a href="http://en.wikipedia.org/wiki/ADO.NET_Entity_Framework">Entity Framework</a> API.</p>
<p>(The existence of this page should not be taken as an endorsement of Entity Framework. It's not something you should learn more about if you don't have to. It's just another complicated <a href="http://en.wikipedia.org/wiki/Object-relational_mapping">ORM</a> framework with its own quirks and annoyances …</p><p>This is my cheatsheet for using Microsoft's <a href="http://en.wikipedia.org/wiki/ADO.NET_Entity_Framework">Entity Framework</a> API.</p>
<p>(The existence of this page should not be taken as an endorsement of Entity Framework. It's not something you should learn more about if you don't have to. It's just another complicated <a href="http://en.wikipedia.org/wiki/Object-relational_mapping">ORM</a> framework with its own quirks and annoyances.)</p>
<!--break-->
<h2>Documentation</h2>
<ul>
<li>MSDN: http://msdn.microsoft.com/en-us/library/bb399572</li>
<li>Managing Connections and Transactions: http://msdn.microsoft.com/en-us/library/bb896325</li>
<li>Performance Considerations: http://msdn.microsoft.com/en-us/library/cc853327.aspx</li>
<li>Troubleshooting Connection Strings: http://blogs.teamb.com/craigstuntz/2010/08/13/38628/</li>
</ul>
<h2>Code Snippets</h2>
<script src="https://gist.github.com/3090796.js?file=entityframeworksnippets.cs"></script>What Siri Is Good For2012-07-06T14:11:53-04:002012-07-06T14:11:53-04:00Kristopher Johnsontag:undefinedvalue.com,2012-07-06:/what-siri-good.html<p>A <a href="http://www.washingtonpost.com/business/apples-siri-doesnt-work-very-well-research-report-finds/2012/06/29/gJQArtpPBW_story.html">recent study</a> indicates that Apple's <a href="http://www.apple.com/iphone/features/siri-faq.html">Siri</a> doesn't perform very well when searching for information. I think most people who use Siri would agree, but "searching" is only one thing that Siri does.</p>
<p>I don't use Siri much for general question-asking. ("Siri, is it raining?") Instead, I use Siri as …</p><p>A <a href="http://www.washingtonpost.com/business/apples-siri-doesnt-work-very-well-research-report-finds/2012/06/29/gJQArtpPBW_story.html">recent study</a> indicates that Apple's <a href="http://www.apple.com/iphone/features/siri-faq.html">Siri</a> doesn't perform very well when searching for information. I think most people who use Siri would agree, but "searching" is only one thing that Siri does.</p>
<p>I don't use Siri much for general question-asking. ("Siri, is it raining?") Instead, I use Siri as a quick way to perform operations on my phone. For example:</p>
<ul>
<li>"Add eggs to my groceries list"</li>
<li>"Text my wife I'm on my way home."</li>
<li>"What is 345 times 843?"</li>
<li>"Set timer for five minutes."</li>
<li>"Set alarm for 6 AM"</li>
<li>"When I get home, remind me to take the trash out."</li>
<li>"Give me directions to Mall of Georgia."</li>
</ul>
<p>Siri works almost perfectly for these types of requests. I can do all of these things without unlocking my phone, typing anything, or navigating through user interfaces, saving time and minimizing interruption of whatever I'm doing.</p>
<p>There was a period a few months ago when Siri was offline/unavailable a lot, but it is pretty reliable now.</p>
<p>Apple hasn't done a good job in educating people about Siri's capabilities. Anyone who tries to do what the celebrities do in the TV commercials will be disappointed. Instead, I'd recommend reading something like <a href="http://www.amazon.com/Talking-Siri-Intelligent-Assistant-ebook/dp/B007KORAIE/ref=sr_1_sc_1?s=digital-text&ie=UTF8&qid=1341583494"><em>Talking to Siri: Learning the Language of Apple's Intelligent Assistant</em></a> which can give you all the necessary instructions and tips.</p>
<p>Siri is far from perfect, but it is a really nice feature, and every other phone and computer maker is adding similar features. I'd recommend learning how to use the features effectively, rather than dismissing them due to their imperfections and limitations. Or you can just keep typing everything, like an old person.</p>HTML Man pages2012-06-25T02:31:25-04:002012-06-25T02:31:25-04:00Kristopher Johnsontag:undefinedvalue.com,2012-06-25:/html-man-pages.html<p>If you've used UNIX-based systems, you're probably aware of <a href="http://en.wikipedia.org/wiki/Man_page">man pages</a>. And you know that they suck.</p>
<p>To make them a little less sucky, I wrote a little shell script called <code>hman</code> which displays a man page as HTML in the browser, rather than forcing you to use <code>less</code>. Here …</p><p>If you've used UNIX-based systems, you're probably aware of <a href="http://en.wikipedia.org/wiki/Man_page">man pages</a>. And you know that they suck.</p>
<p>To make them a little less sucky, I wrote a little shell script called <code>hman</code> which displays a man page as HTML in the browser, rather than forcing you to use <code>less</code>. Here it is, if you'd like such a thing. (You'll need to install <code>man2html</code> via <a href="http://mxcl.github.com/homebrew/">Homebrew</a>).</p>
<script src="https://gist.github.com/2983879.js"> </script>
<p>So, with this, you can run <code>hman bash</code> and get something halfway usable.</p>Bash Scripting Cheatsheet2012-06-23T15:53:11-04:002012-06-23T15:53:11-04:00Kristopher Johnsontag:undefinedvalue.com,2012-06-23:/bash-scripting-cheatsheet.html<p>These are the half-dozen-or-so things I need to re-learn whenever I have to write a <a href="http://en.wikipedia.org/wiki/Bash_(Unix_shell)">Bash</a> script.</p>
<p>(And yes, I do know that the cool kids use <a href="http://en.wikipedia.org/wiki/Z_shell">zsh</a> or <a href="http://ridiculousfish.com/shell/">fish</a> instead of bash. You don't need to point that out to me.)</p>
<h2>Good Online Resources</h2>
<ul>
<li><a href="http://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO.html">BASH Programming - Introduction HOW-TO</a></li>
<li><a href="http://tldp.org/LDP/abs/html/index.html">Advanced …</a></li></ul><p>These are the half-dozen-or-so things I need to re-learn whenever I have to write a <a href="http://en.wikipedia.org/wiki/Bash_(Unix_shell)">Bash</a> script.</p>
<p>(And yes, I do know that the cool kids use <a href="http://en.wikipedia.org/wiki/Z_shell">zsh</a> or <a href="http://ridiculousfish.com/shell/">fish</a> instead of bash. You don't need to point that out to me.)</p>
<h2>Good Online Resources</h2>
<ul>
<li><a href="http://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO.html">BASH Programming - Introduction HOW-TO</a></li>
<li><a href="http://tldp.org/LDP/abs/html/index.html">Advanced Bash Scripting Guide</a></li>
<li><a href="http://linux.die.net/man/1/bash">bash(1) Linux man page</a></li>
</ul>
<h2>Snippets</h2>
<script src="https://gist.github.com/2998722.js?file=bash_snippets.sh"></script>My Setup2012-06-15T22:50:14-04:002012-06-15T22:50:14-04:00Kristopher Johnsontag:undefinedvalue.com,2012-06-15:/my-setup.html<p>I'm a fan of <a href="http://usesthis.com">The Setup</a>, a website containing interviews with people about the tools they use to do their jobs. It is just "<a href="http://wiki.43folders.com/index.php/Productivity_pr0n">productivity porn</a>", but I find it interesting.</p>
<p>As a tribute, here is my own "interview" for The Setup, using the standard four questions. I don't expect …</p><p>I'm a fan of <a href="http://usesthis.com">The Setup</a>, a website containing interviews with people about the tools they use to do their jobs. It is just "<a href="http://wiki.43folders.com/index.php/Productivity_pr0n">productivity porn</a>", but I find it interesting.</p>
<p>As a tribute, here is my own "interview" for The Setup, using the standard four questions. I don't expect anyone to find this interesting, but writing it up helps me think about what I'm using and why, and it will come in handy the next time I have to set up a new computer.</p>
<!--break-->
<h2>Who are you, and what do you do?</h2>
<p>I'm Kris Johnson. I <a href="http://careers.stackoverflow.com/kristopherjohnson">develop software</a> for Windows, UNIX, iOS, and Android. I currently work as an independent contractor. I work from home most of the time, but travel to clients' facilities as needed.</p>
<h2>What hardware are you using?</h2>
<p>I have a late-2011 15-inch <a href="http://www.apple.com/macbook-pro/">MacBook Pro</a>, with 16 GB RAM and a 750 GB hard drive. I use a <a href="http://www.apple.com/magictrackpad/">Magic Trackpad</a>. In my home office, I have a 24-inch monitor and a <a href="http://www.daskeyboard.com/model-s-professional-for-mac/">Das Keyboard for Mac</a>. On the road, I carry an <a href="http://store.apple.com/us/product/MC184LL/B">Apple Wireless Keyboard</a>.</p>
<p>I have an iPhone 4S and a third-generation wi-fi-only iPad. I sometimes use an <a href="http://adonit.net/product/jot/">Adonit Jot</a> stylus with the iPad. I tether my iPad to the phone when necessary, but it is rare that I am using my iPad somewhere that doesn't have wi-fi.</p>
<p>For testing iOS apps, I have my old iPhone 3G, iPhone 4, 4th-generation iPod Touch, and original iPad. For testing Android apps, I have a Samsung Galaxy S Captivate phone and a Samsung Galaxy Tab 2 7.0.</p>
<p>I find external conversations and music to be very distracting, so when I'm working I usually wear some <a href="http://www.hearos.com/ear-plug-hearos">Hearos earplugs</a> or sound-isolating earphones playing <a href="http://en.wikipedia.org/wiki/Colors_of_noise#Pink_noise">pink noise</a>.</p>
<p>In bed at night, I read on a <a href="http://en.wikipedia.org/wiki/Kindle_Fire">Kindle Fire</a>.</p>
<p>I have a Targus computer bag that I lug everything around in. </p>
<p>We have a Saeco super-automatic espresso machine. I try to limit myself to ten shots of espresso per day.</p>
<h2>And what software?</h2>
<p>These are my most-useful Mac OS X applications:</p>
<ul>
<li><a href="https://developer.apple.com/xcode/">Xcode</a> for iOS development</li>
<li><a href="http://eclipse.org/">Eclipse</a> and the <a href="http://developer.android.com/sdk/eclipse-adt.html">ADT Plugin</a> for Android development</li>
<li><a href="http://www.sublimetext.com/">Sublime Text 2</a> for web development and general text editing</li>
<li><a href="http://www.parallels.com/products/desktop/">Parallels Desktop</a> for running Windows</li>
<li><a href="http://kapeli.com/dash/">Dash</a> for developer reference documentation</li>
<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> for git (but I tend to use the git command-line tools more often than this app)</li>
<li><a href="http://www.alfredapp.com/">Alfred</a> for quick launching</li>
<li><a href="http://adium.im/">Adium</a> for chat</li>
<li><a href="http://www.apple.com/safari/">Safari</a> for web browsing</li>
<li><a href="http://www.acqualia.com/soulver/">Soulver</a> for calculations</li>
<li><a href="http://www.apple.com/iwork/pages/">Pages</a> for word processing</li>
<li>Microsoft Office for documents that I have to share with non-Mac people</li>
<li><a href="http://brettterpstra.com/project/nvalt/">nvALT</a> for personal notes</li>
<li><a href="http://manytricks.com/moom/">Moom</a> for window management</li>
<li><a href="http://smilesoftware.com/TextExpander/">TextExpander</a></li>
<li><a href="http://www.noodlesoft.com/hazel.php">Hazel</a> for automatically clearing out my Downloads folder once in a while</li>
</ul>
<p>These are my most-used Windows applications:</p>
<ul>
<li>Visual Studio 2010 Professional</li>
<li><a href="http://www.sublimetext.com/">Sublime Text</a> (yes, it's cross-platform)</li>
<li><a href="http://the.earth.li/~sgtatham/putty/">PuTTY</a> for Telnet/SSH</li>
<li><a href="http://www.wireshark.org/">Wireshark</a> for network-related code debugging</li>
<li><a href="http://www.autohotkey.com/download/">AutoHotKey</a> to remap Windows keys and mouse gestures to work more like Mac OS X</li>
</ul>
<p>I use the <a href="http://slinky.imukuppi.org/zenburn/">Zenburn</a> color scheme and <a href="http://ftp.gnome.org/pub/GNOME/sources/ttf-bitstream-vera/1.10/">DejaVu Sans Mono</a> font in all my source-code editors and terminal windows.</p>
<p>I have <a href="https://www.dropbox.com/">Dropbox</a> and <a href="https://agilebits.com/downloads">1Password</a> installed everywhere I can install them. </p>
<p>I write everything in <a href="http://daringfireball.net/projects/markdown/">Markdown</a> format when I can, because so many of the tools I use support it.</p>
<p>This blog and other webby things are hosted on an <a href="http://aws.amazon.com/ec2/">Amazon EC2</a> micro instance running Ubuntu. I put as much of my code as I can on <a href="http://www.github.com/">GitHub</a> and <a href="http://www.bitbucket.org">BitBucket</a> so that I don't have to worry about getting to it.</p>
<h2>What would be your dream setup?</h2>
<p>I'd love to have one of the new MacBook Pros with Retina display, but I need to wait until I can afford to buy an SSD that is large enough to hold Mac OS X and all my Windows virtual machines.</p>
<p>I'd like to have a desk that can automatically move between standing and sitting height.</p>
<p>I'd like to have my own helicopter for commuting between my home and clients' facilities.</p>
<p>I wish I still had my Macintosh SE.</p>20 Years Ago2012-06-05T00:22:21-04:002012-06-05T00:22:21-04:00Kristopher Johnsontag:undefinedvalue.com,2012-06-05:/20-years-ago.html<p>It was 20 years ago this month that I graduated from college and got my first professional programming job. It has me reminiscing about what the computer programming profession was like before the Internet.</p>
<!--break-->
<p>I had used the Internet in college. However, back then we didn't have web browsers. We …</p><p>It was 20 years ago this month that I graduated from college and got my first professional programming job. It has me reminiscing about what the computer programming profession was like before the Internet.</p>
<!--break-->
<p>I had used the Internet in college. However, back then we didn't have web browsers. We had <a href="http://en.wikipedia.org/wiki/File_Transfer_Protocol">FTP</a>, <a href="http://en.wikipedia.org/wiki/Gopher_(protocol)">Gopher</a>, and <a href="http://en.wikipedia.org/wiki/Usenet">Usenet newsgroups</a>. So all that I really did with the Internet was download some software and engage in stupid arguments.</p>
<p>Back then the only way to be on the Internet was to be at a university or working for a company that did work for the Department of Defense. So when I graduated, that was the end of the Internet for me, until a few years later when <a href="http://en.wikipedia.org/wiki/GEnie">GEnie</a>, <a href="http://en.wikipedia.org/wiki/CompuServe">CompuServe</a>,) and <a href="http://en.wikipedia.org/wiki/AOL">America Online</a> started provding Internet access to the masses.</p>
<p>Here's what a programmer's life was like back then:</p>
<p>My job search consisted of looking at classified ads in newspapers. I'd send a printed resumé and cover letter to each local company that was hiring programmers. The interviewing process was very similar to what it is today, except that there was no way to tell someone to look at your Stack Overflow or Github acttivity to get a feel for how well you could write code.</p>
<p>I got a couple of job offers, and took the one that seemed more interesting. Of course, back then it was hard to get a feel for how good an employer any company could be, due to lack of message boards or other ways to chat with current and former employees of that company.</p>
<p>When I started work, I had a <a href="http://terminals.classiccmp.org/wiki/index.php/DEC_VT340">DEC VT340</a> display in my cubicle. This was connected via a local area network (<a href="http://en.wikipedia.org/wiki/10BASE2">10BASE2</a>, I believe) to the terminal server connected to a <a href="http://en.wikipedia.org/wiki/VMScluster">VAXcluster</a>. The DEC VT340 was a character-based terminal, supporting 80x24 and 132x24 resolutions. It could even display multiple colors!</p>
<p>We wrote software for the following platforms:</p>
<ul>
<li><a href="http://en.wikipedia.org/wiki/SunOS">SunOS</a> (later to be renamed "SOLARIS")</li>
<li><a href="http://en.wikipedia.org/wiki/UNIX_System_V">AT&T System V</a> UNIX</li>
<li><a href="http://en.wikipedia.org/wiki/OpenVMS">VMS</a></li>
<li><a href="http://en.wikipedia.org/wiki/OS/2">OS/2</a> 1.3</li>
</ul>
<p>Notice the lack of a notable operating system in that list? We didn't touch Windows until NT 4.0 was released. Our users had to use VT100-style terminals for most of our apps. A few "graphical" apps ran on OS/2 with a Matrox graphics card that displayed 1280x1024 images (and cost a few thousand dollars).</p>
<p>I had a <a href="http://en.wikipedia.org/wiki/Macintosh_SE">Macintosh SE</a> at home, but <em>nobody</em> in the professional world used Macs, and Apple was expected to go out of business any day.</p>
<p>Everything we wrote had to be portable between all those operating systems. Everything was written in C. Some of the compilers supported the new <a href="http://en.wikipedia.org/wiki/ANSI_C">ANSI C</a> standard, but some did not, so most of the code was old K&R-style C. I learned a lot about writing portable code back then, and today I still feel a shudder whenever I have to use a proprietary API.</p>
<p>I settled on <a href="http://en.wikipedia.org/wiki/Emacs">Emacs</a> as my editor back then. It was awesome because on a 80x24 display you could <em>display two files side by side</em>, and you could show <em>a terminal window and debugger window at the same time</em>. (I would never recommend that anyone learn Emacs today, but back then, it was the best way to work.)</p>
<p>A full build of our application from scratch took almost 24 hours. Obviously, you can't get much done with a 24-hour build-and-run cycle, so we would coordinate among ourselves to make whatever changes were needed, then schedule a recompilation of the minimum set of source files, and if they all compiled then we would relink the application. Even this minimal effort often took almost an hour, so everyone was very careful about making changes.</p>
<p>I had done a little C++ in college, and was hoping to use that again, but the company had done a big project in C++, and had so many problems with the compilers that they swore never to use C++ again. (We would eventually use C++ a few years later when the time came to develop Presentation Manager applications for OS/2.)</p>
<p>Before the Internet, "online documentation" meant <a href="http://en.wikipedia.org/wiki/Man_page">man pages</a>. Try reading a few man pages on an 80x24 display, and imagine that being your only online source of operating system information.</p>
<p>Thankfully, we also had complete sets of the hardcopy documentation. One entire wall of the office contained the then-current VMS documentation. Another wall contained the previous-version VMS documentation. A third wall had the UNIX documentation. </p>
<p>Back then, the O'Reilly books about UNIX utilities were the most valuable things you had in your cube. You could also rely on your memory, because as complex as these operating systems were for the time, their APIs only consisted of a few dozen functions. It was actually possible to memorize the entire platform API (try that with Java or .NET).</p>
<p>Delivering a software release to a client meant sending them a <a href="http://en.wikipedia.org/wiki/DECtape">DECtape</a>, either through the mail, or when a fix was urgently needed, by overnight courier. Because remote access to the sites was often impossible, or impractical due to slow modem speeds, we'd often need to physically visit sites to install new software or upgrade existing software.</p>
<p>We didn't have email. We wrote memos, and had a lot more meetings that people do these days. An administrative assistant took the mail cart around each day to deliver the memos and mail. We generated many, many binders full of technical documentation. Some of that documentation was created by technical writers using <a href="http://en.wikipedia.org/wiki/WordPerfect">WordPerfect</a>, but a most of it was plain ASCII text and simple <a href="http://en.wikipedia.org/wiki/ASCII_art">ASCII art</a>.</p>
<p>Back then, I knew everything, and I was going to change the world. It was a wonderful feeling. I consider myself fortunate to have learned programming before Windows and Java ruined everything. But now I have a computer in my pocket that is more powerful and more useful than the million-dollar mainframe I wrote code for back then, so I wouldn't want to go back.</p>Want to Develop iOS Apps? Learn Objective-C.2012-05-06T14:41:42-04:002012-05-06T14:41:42-04:00Kristopher Johnsontag:undefinedvalue.com,2012-05-06:/want-develop-ios-apps-learn-objective-c.html<p>As an iOS software developer, I am often asked whether "we" (a team I'm working with, or someone I'm advising) should avoid using Objective-C and instead use a higher-level or easier-to-learn programming language. In general, my answer is "No". The rest of this post explains why.</p>
<!--break-->
<h2>The Problem</h2>
<p>A bit …</p><p>As an iOS software developer, I am often asked whether "we" (a team I'm working with, or someone I'm advising) should avoid using Objective-C and instead use a higher-level or easier-to-learn programming language. In general, my answer is "No". The rest of this post explains why.</p>
<!--break-->
<h2>The Problem</h2>
<p>A bit of background: Apple provides a development tool called "<a href="http://en.wikipedia.org/wiki/Xcode">Xcode</a>" which is what they want developers to use to write iOS apps. Programs are written using a programming language called "Objective-C", which enhances C with Smalltalk-style object-oriented message-passing features. Developers also use a tool called "Interface Builder" to visually lay out user-interface elements. Finally, there is a vast collection of libraries known as the "<a href="https://developer.apple.com/technologies/ios/cocoa-touch.html">Cocoa Touch frameworks</a>" which provide the programming interfaces to all the cool stuff that iPhones and iPads can do. </p>
<p>While many iOS developers enjoy the nature of Objective-C, many others think it is a terrible language for application development. Writing code in Objective-C seems very complicated to people accustomed to writing applications in C♯, Java, Ruby, Python, Javascript, and other more modern languages. Many would-be iOS developers don't know C and don't want to learn it. Objective-C developers have to think too much about memory management and other low-level aspects of their applications. Apple is the only company that uses Objective-C, so those who would like to reuse code across different platforms are frustrated due to its non-portability. The Xcode environment has many detractors, and even its fans admit that its bugs and quirks can be maddening.</p>
<p>I am not going to defend Objective-C or Xcode. Many iOS developers would like to use other programming languages and tools.</p>
<h2>The Alternatives</h2>
<p>The alternatives to Objective-C and Xcode can be grouped into two categories. One category is the set of tools that allow developers to write their apps in other programming languages, using some sort of bridge to the Cocoa Touch frameworks, which are then compiled down to native code just like Objective-C is. Examples of this category are <a href="http://xamarin.com/monotouch">MonoTouch</a> (for C♯) and <a href="http://www.rubymotion.com/">RubyMotion</a> (for Ruby).</p>
<p>The other category is the set of tools that allow developers to use HTML/CSS/Javascript to write webapp-style code, which is then packaged as something that will run as a native app. These tools provide some means to access the raw Cocoa Touch frameworks if desired, but the real purpose is to shield developers from them. Examples of these are <a href="http://phonegap.com/">PhoneGap</a> and <a href="http://www.appcelerator.com/">Titanium Appcelerator</a>. </p>
<h2>My Advice</h2>
<p>For most developers, my advice is to stay away from these things. Go ahead and evaluate them, and maybe one of them is a good fit for your particular set of circumstances, but if you don't know that you have a good reason to use them, just bite the bullet and learn Objective-C.</p>
<p>Here's why: Developing a good iOS app requires use of the Cocoa Touch frameworks. Learning how to use those frameworks is what is difficult about iOS development. Learning Objective-C should take a competent programmer only a day or two, but learning the frameworks is a never-ending struggle. Apple's documentation and samples for the frameworks use Objective-C. Most of the third-party libraries and other components you will want to use are in Objective-C. The smart people who answer iOS development questions in online forums use Objective-C.</p>
<p>if you are using another programming language to interface with all this Objective-C stuff, you end up with these little walls you have to hop over a lot. I'd prefer not to build those walls. <a href="http://merbist.com/2012/05/04/macruby-on-ios-rubymotion-review/">The RubyMotion review by Matt Aimonetti</a> describes how difficult it can be, especially for beginners.</p>
<p>The only exception to this rule is if you can create a good webapp-style app using PhoneGap or Titanium Appcelerator. The big benefit of this approach is that you can reuse or share HTML/CSS/Javascript code across platforms, so when your iOS app is done you can release versions for Android, Windows Phone, and other platforms with a minimal amount of additional work.</p>
<p>But when you take this approach, you will end up with a webappish app, not an app that takes advantage of what makes iOS special (or what makes any other platform special). Maybe that's the right solution for you, but you'll have to accept that some users will react by saying "Eww, this feels like a webapp".</p>
<h2>The Right Tool for the Job</h2>
<p>In general, it's a good idea to use whatever tools and APIs are officially supported by your vendor. If you are doing iOS development, that means you should use Objective-C and Xcode. If you are doing Android development, use Java and Eclipse with the Android Development Toolkit. If you are doing Windows development, use C♯ and Visual Studio. Going with other solutions might have benefits, but whenever you choose an unsupported development environment, you are taking a big risk. At any time, the vendor might make changes to its APIs or toolchain that break all tools you rely upon.</p>
<p>Using the right tool for the job is a good idea. Using anything other than Objective-C to develop iOS apps results in more work and more risk. Just go ahead and learn it. You might even like it.</p>Sorting Entries in a PList by Key2012-04-28T16:22:11-04:002012-04-28T16:22:11-04:00Kristopher Johnsontag:undefinedvalue.com,2012-04-28:/sorting-entries-plist-key.html<p>My iOS applications use <a href="http://en.wikipedia.org/wiki/Property_list">property list</a> (plist) files to specify configuration parameters and other stuff. I was trying to do some comparison and merging of these plists, but was tripped up because the keys were in different order in different files.</p>
<p>So I whipped up a little Python script to …</p><p>My iOS applications use <a href="http://en.wikipedia.org/wiki/Property_list">property list</a> (plist) files to specify configuration parameters and other stuff. I was trying to do some comparison and merging of these plists, but was tripped up because the keys were in different order in different files.</p>
<p>So I whipped up a little Python script to sort the keys in the plists and write them in a canonical format. If you would be interested in such a thing, it's as easy as this:</p>
<script src="https://gist.github.com/2519997.js?file=sortplist.py"></script>Cold Science Fiction2012-04-18T23:30:55-04:002012-04-18T23:30:55-04:00Kristopher Johnsontag:undefinedvalue.com,2012-04-18:/cold-science-fiction.html<p>I read a lot of science fiction (SF) in my adolescent and teenage years, but I got bored with it in college, and stopped reading it. Actually, I didn't get bored, I got <em>annoyed</em> with it. At the time, I couldn't describe exactly what it was that was annoying me …</p><p>I read a lot of science fiction (SF) in my adolescent and teenage years, but I got bored with it in college, and stopped reading it. Actually, I didn't get bored, I got <em>annoyed</em> with it. At the time, I couldn't describe exactly what it was that was annoying me, but I felt that each SF novel I read was following a formula that had stopped entertaining me, and that SF had become a waste of my time.</p>
<p>In the past few years, I've started reading SF again. I've read new stories by new authors, and tried to catch up on some of the SF classics I should have read back in the 80's. A friend loaned me his copy of Robert Heinlein's <a href="http://en.wikipedia.org/wiki/The_Moon_Is_a_Harsh_Mistress"><em>The Moon is a Harsh Mistress</em></a>. I looked forward to reading it, as I had heard it was one of the best SF novels ever written.</p>
<p>I hated it. This novel reminded me of everything that made me stop reading science fiction.</p>
<p>Lots of smart people do enjoy this book, so I spent some time thinking about what they see in it, and what it is that I find so repulsive.</p>
<!--break-->
<p>Similar to the distinction between <a href="http://en.wikipedia.org/wiki/Hard_science_fiction">"hard" and "soft" SF</a>, I see a distinction between "cold" and "warm" SF. These are the attributes of "cold" SF:</p>
<ul>
<li>The plot is a set of intellectual puzzles. There is one big overarching puzzle ("How do we destroy the alien fleet?", "How can we get back to Earth", "Where did this strange artifact come from?"), and a series of little puzzles that the characters have to solve before solving the big puzzle.</li>
<li>The central characters exhibit no emotion. All behavior is logical.</li>
<li>The central characters have no goals other than solving the puzzles. The author may spend a whole chapter explaining how a character fixes the spaceship's FTL drive, but won't spend a paragraph telling us what that character's hopes or dreams are.</li>
<li>The characters don't have realistic conversations. They deliver lectures to one another. They may rarely express friendship and affection for one another, but those expressions are very formal and limited.</li>
<li>The characters have no doubts or fears. (That wouldn't be logical.) They might make mistakes, but when they do it is due to lack of information, not poor judgment.</li>
<li>There is no significant conflict between the main characters. They share the same goal, they cooperate easily, and they get along with one another. (Except when there is one character who secretly has another agenda. When the surprise is revealed, it is quickly dealt with.)</li>
<li>Ethical and moral questions have simple logical answers. If violence is necessary, it doesn't bother the characters. They kill for logical reasons, without hesitation or remorse. If a friend is lost, those remaining just move on after a quick tip-of-the-hat.</li>
</ul>
<p>"Cold" SF is not necessarily bad. Stanley Kubrick's <em>2001: A Space Odyssey</em> is a prime example of "cold", in which sterile environments and spare dialogue build tension. And I do like "cold" short stories, like Asimov's robot stories. I understand the appeal.</p>
<p>Unfortunately for me, "cold" long-form SF is usually unsatisfying. Since the characters don't act like real people, I don't care about them or whether they solve their puzzles, and I don't care to slog through long books about them.</p>
<p>So obviously, I enjoy "warm" SF more than "cold". I like characters with irrational desires, doubts, and fears. I like characters who screw up and have to live with the consequences. I like characters who make me laugh. I like characters who disagree with one another. I like characters who can't find all the answers.</p>
<p>While I didn't enjoy reading <em>The Moon is a Harsh Mistress</em>, it did teach me something about myself. In that way, it was a good read.</p>KJSimpleBinding2012-03-22T10:15:47-04:002012-03-22T10:15:47-04:00Kristopher Johnsontag:undefinedvalue.com,2012-03-22:/kjsimplebinding.html<p>Mac OS X provides a pretty nice <a href="http://en.wikipedia.org/wiki/UI_data_binding">data-binding</a> technology for developers, called <a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaBindings/CocoaBindings.html">Cocoa bindings</a>. Unfortunately, the Cocoa bindings mechanism is not available to iOS developers, so iOS developers have to spend a lot of time writing code to keep user-interface elements and data in sync.</p>
<p>However, while Cocoa Bindings is …</p><p>Mac OS X provides a pretty nice <a href="http://en.wikipedia.org/wiki/UI_data_binding">data-binding</a> technology for developers, called <a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaBindings/CocoaBindings.html">Cocoa bindings</a>. Unfortunately, the Cocoa bindings mechanism is not available to iOS developers, so iOS developers have to spend a lot of time writing code to keep user-interface elements and data in sync.</p>
<p>However, while Cocoa Bindings is not available on iOS, the underlying <a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueCoding/Articles/KeyValueCoding.html">key-value coding</a> (KVC) and <a href="https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html">key-value observing</a> (KVO) mechanisms are, so it is straightforward to implement your own poor man's data-binding mechanism and eliminate some of the drudgery.</p>
<p>I have done just that. My <a href="http://github.com/kristopherjohnson/KJSimpleBinding/blob/master/README.markdown"><code>KJSimpleBinding</code></a> library is available on <a href="http://github.com/kristopherjohnson/KJSimpleBinding">GitHub</a>. I hope it is useful to someone, and I hope I have time to make it less simple.</p>
<p>I'm not the only one to try this. Here are a few similar projects I found on GitHub:</p>
<ul>
<li>http://github.com/dewind/KeyPathBindings</li>
<li>http://github.com/jonsterling/Observe</li>
<li>http://github.com/mruegenberg/objc-simple-bindings</li>
<li>http://github.com/zeasy/EasyBinding</li>
</ul>KJGridLayout2012-03-15T10:40:19-04:002012-03-15T10:40:19-04:00Kristopher Johnsontag:undefinedvalue.com,2012-03-15:/kjgridlayout.html<p>Xcode's Interface Builder is a pretty good user-interface layout tool, especially for simple situations. However, it is not the best tool for every job. Sometimes you have to write code to dynamically create user interface elements or to move them around as the view is resized.</p>
<p>When you do this …</p><p>Xcode's Interface Builder is a pretty good user-interface layout tool, especially for simple situations. However, it is not the best tool for every job. Sometimes you have to write code to dynamically create user interface elements or to move them around as the view is resized.</p>
<p>When you do this, you find that iOS doesn't help you much beyond some rudimentary autoresizing options. While iOS 5 does provide some autolayout features, they are limited, and they don't help at all if you need to support earlier versions of iOS.</p>
<p>I had a need to do a <a href="http://en.wikipedia.org/wiki/Grid_(page_layout)">grid-based layout</a> in an iOS app. I hoped to find a grid-layout component like one would find in <a href="http://developer.android.com/reference/android/widget/GridLayout.html">Android</a> or <a href="http://msdn.microsoft.com/en-us/library/system.windows.controls.grid.aspx">WPF</a>, but there is no such thing built into iOS.</p>
<p>I also couldn't find any third-party implementations. I found a few posts and samples for making a grid-like <code>UITableView</code>, but I wanted a way to lay out things in a grid in a plain-old <code>UIView</code>.</p>
<p>So I decided to write my own grid-layout thingees for iOS.</p>
<p>The results are the <code>KJGridLayout</code> class and the <code>KJGridLayoutView</code> class, which you can find on <a href="http://github.com/kristopherjohnson/KJGridLayout">GitHub</a>. Check out the <a href="http://github.com/kristopherjohnson/KJGridLayout/blob/master/README.markdown">README</a> and feel free to use them yourself. I hope someone finds this stuff useful.</p>MacBooks and Caps Lock and Control2012-03-08T23:46:44-05:002012-03-08T23:46:44-05:00Kristopher Johnsontag:undefinedvalue.com,2012-03-08:/macbooks-and-caps-lock-and-control.html<p>I learned to touch-type over 30 years ago, on an IBM Selectric typewriter. I'm a fast and accurate typist, compared to most programmers. I've always considered typing to be a basic skill that all programmers should take seriously. What goes on in your head is more important than how fast …</p><p>I learned to touch-type over 30 years ago, on an IBM Selectric typewriter. I'm a fast and accurate typist, compared to most programmers. I've always considered typing to be a basic skill that all programmers should take seriously. What goes on in your head is more important than how fast you can type, but the more efficient you are in getting your thoughts into the computer, the better you are going to be at your job.</p>
<p>I've been dismayed at one "feature" of my MacBook: to prevent accidental triggering of the Caps Lock key by incompetent typists, Apple makes it necessary to hold down the Caps Lock key for an extra fraction of a second. If you just tap it quickly, it does nothing. See http://support.apple.com/kb/HT1192 for Apple's explanation. They have also baked this behavior into some of their keyboards: see http://support.apple.com/kb/TS1578.</p>
<p>While I'm sure that many people welcome this feature, I do not. I type quickly and confidently, and my fingers hit the Caps Lock key at the appropriate times without me consciously thinking about it. I hit and release it so quickly that my MacBook ignores it. So when I type "UNIX is awesome!", "Party in the USA!" or "New York, NY", I see "unix is awesome!", "Party in the usa!" or "New York, ny" on the screen.</p>
<p>There is no easy way to disable this feature. When I complain, most people respond "Big deal. We hardly ever use Caps Lock." Well, I do. I've been using it for 30 years, and it has always worked fine. Until Apple decided It Should Just Not Work.</p>
<p>So, I looked into what I could do.</p>
<!--break-->
<p>The short answer is "Nothing". I have found a few mentions of kernel extensions and keyboard driver hacks, but this behavior seems to be embedded so deeply within OS X that there is no safe and reliable way to turn it off.</p>
<p>The cool kids all remap their Caps Lock key to work as the Control key. The reasoning is that programmers have to use the Control key a lot, and nobody has typed anything in all caps since 1968, so there is no need for a Caps Lock key. I have resisted doing any remapping of my keyboards, because I often have to use computers and keyboards that are not mine, and I didn't want to have to remember which keyboards are remapped and which are not. But since the Caps Lock key is useless to me, I decided to go ahead and give it a try.</p>
<p>This is easy to do on OS X: just go to <em>System Preferences</em> -> <em>Keyboard</em> and click the <em>Modifier Keys...</em> button, and then you can change the Caps Lock key to work as Control.</p>
<p>But then I don't have a Caps Lock key anymore, and I'd like to have one when I need to type "DANGER WILL ROBINSON! DANGER! DANGER! DANGER". Actually, joking aside, typing stuff in all caps comes up a lot in programming, and I've never understood why any programmers say they never need Caps Lock. Do they really hold down Shift while typing something like this?</p>
<div class="highlight"><pre><span></span><code><span class="nv">int</span><span class="w"> </span><span class="nv">fd</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">creat</span><span class="ss">(</span><span class="s2">"file.dat"</span>,<span class="w"> </span><span class="nv">O_RDWR</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nv">O_CREAT</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nv">O_DSYNC</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nv">O_RSYNC</span><span class="ss">)</span><span class="c1">;</span><span class="w"></span>
<span class="k">if</span><span class="w"> </span><span class="ss">(</span><span class="nv">fd</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="o">-</span><span class="mi">1</span><span class="ss">)</span><span class="w"> </span>{<span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="ss">(</span><span class="nv">errno</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nv">EEXIST</span><span class="ss">)</span><span class="w"> </span>{<span class="w"></span>
<span class="w"> </span><span class="cm">/* ... */</span><span class="w"></span>
</code></pre></div>
<p>I wanted to map some other key to Caps Lock for those times when I needed it. Some people swap their Caps Lock with Control, but I knew that wouldn't work for me: my fingers expect Control to be where it is, and I frequently use Control along with Command and/or Option, and hitting all those at once is awkward if the only Control key is where the Caps Lock key usually is.</p>
<p>What I really wanted to do was set things up so that double-tapping the Shift key would toggle Caps Lock mode, as it does on the virtual keyboards on iOS devices. I couldn't find a way to do that. So I decided I'd like to be able to hit Fn+Tab to trigger Caps Lock.</p>
<p>It turns out that can be done with the <a href="http://pqrs.org/macosx/keyremap4macbook/">KeyRemap4MacBook</a> utility. It's a little hard to configure (especially if you aren't comfortable editing XML files), but I got it set up. If you want to play at home, here's what I did:</p>
<ol>
<li>Download and install <a href="http://pqrs.org/macosx/keyremap4macbook/">KeyRemap4MacBook</a>. You'll have to restart at the end of the installation.</li>
<li>Open the <strong>System Preferences</strong> -> <strong>KeyRemap4MacBook</strong> preference pane.</li>
<li>Select the <strong>Misc & Uninstall</strong> tab, and click the <strong>Open private.xml</strong> button. This will open a Finder window in the folder where the <code>private.xml</code> configuration file is located.</li>
<li>Open <code>private.xml</code> in your favorite text editor (use TextEdit if you don't have a favorite text editor). It will be blank. Paste the following into it (which can also be found at https://gist.github.com/2002690 ):</li>
</ol>
<pre>
<?xml version="1.0"?>
<root>
<item>
<name>Private Settings</name>
<item>
<name>Fn+Tab to CapsLock</name>
<identifier>private.fn_tab_to_capslock</identifier>
<autogen>--KeyToKey-- KeyCode::TAB, ModifierFlag::FN, KeyCode::CAPSLOCK</autogen>
</item>
</item>
</root>
</pre>
<p>Save the file, then go back to the <strong>System Preferences -> KeyRemap4MacBook</strong> preference pane and do this:</p>
<ol>
<li>Select the <strong>Change Key</strong> tab.</li>
<li>Click the <strong>ReloadXML</strong> button to load your configuration file.</li>
<li>A <strong>Private Settings</strong> item should appear in the list in the preference pane. Expand the item, and check the <strong>Fn+Tab to CapsLock</strong> box.</li>
</ol>
<p>Now, you should be able to press Fn+Tab, and you'll see the Caps Lock key light up.</p>
<p>I'm going to try this setup for a while, but I suspect I won't get used to it. Maybe I'll have to tweak it more, or maybe I'll just give up and learn to slow down for the Caps Lock key.</p>
<p>I've ordered a <a href="http://www.daskeyboard.com/model-s-professional-for-mac/">Das Keyboard for Mac</a>. Maybe that will be the ultimate solution to my problem.</p>My Python Cheatsheet2012-02-27T20:25:44-05:002012-02-27T20:25:44-05:00Kristopher Johnsontag:undefinedvalue.com,2012-02-27:/my-python-cheatsheet.html<p>Here's another programming-language cheatsheet. It's been a couple of years since I've done any Python programming, and now I'm taking the online <a href="http://www.udacity.com/">CS373: Programming a Robotic Car</a> course, which uses Python for quizzes and homework assignments, so I have to get back up to speed.</p>
<p>As always, this is the …</p><p>Here's another programming-language cheatsheet. It's been a couple of years since I've done any Python programming, and now I'm taking the online <a href="http://www.udacity.com/">CS373: Programming a Robotic Car</a> course, which uses Python for quizzes and homework assignments, so I have to get back up to speed.</p>
<p>As always, this is the information I've found useful in reacquainting myself with a programming language. It may not help you at all.</p>
<!--break-->
<h2>Links</h2>
<ul>
<li><a href="http://docs.python.org/tutorial/">The Python Tutorial</a></li>
<li><a href="http://docs.python.org/library/">The Python Standard Library</a></li>
<li><a href="http://docs.python.org/library/functions.html">Built-in Functions</a></li>
<li><a href="http://docs.python.org/reference/">The Python Language Reference</a></li>
</ul>
<h2>Snippets</h2>
<script src="https://gist.github.com/3047911.js?file=snippets.py"></script>My Android Development Cheatsheet2012-02-24T00:10:44-05:002012-02-24T00:10:44-05:00Kristopher Johnsontag:undefinedvalue.com,2012-02-24:/my-android-development-cheatsheet.html<p>If I had my druthers, I'd spend all my time developing mobile apps. I've always been fascinated with pocket-sized computers, and have owned many through the years. Unfortunately, for most of my life such devices have been little more than toys, and so I've had to focus my expertise on …</p><p>If I had my druthers, I'd spend all my time developing mobile apps. I've always been fascinated with pocket-sized computers, and have owned many through the years. Unfortunately, for most of my life such devices have been little more than toys, and so I've had to focus my expertise on writing code for "real computers".</p>
<p>This is true even now, during the explosion of smartphone and tablet usage. I'm one of those dinosaurs who knows how to use C, C++, MFC, ATL, CORBA, UNIX, and other ancient magic, so there are sometimes a few months of old-school development between mobile-development gigs. I write iOS stuff for fun, so I keep those skills sharp, but Android is something I touch only when I'm being paid to do so. Thus, I have to find a way to quickly get back up to speed when the Android work does come.</p>
<p>This is my little refresher for when I arrive back in Android-land. It may not help you at all.</p>
<p><strong>WARNING:</strong> The below was written back when I wrote apps that targeted Android 2.x. A lot of it is obsolete with the 4.x API. Someday maybe I'll update this.</p>
<!--break-->
<h2>References</h2>
<ul>
<li><a href="http://developer.android.com/index.html">Android Development Home</a></li>
<li><a href="http://developer.android.com/guide/index.html">Android Dev Guide</a></li>
<li><a href="http://developer.android.com/design/index.html">Android Design</a></li>
<li><a href="http://developer.android.com/reference/packages.html">Android API Reference</a></li>
<li><a href="http://support.google.com/googleplay/android-developer/">Google Play (formerly Android Market)</a></li>
</ul>
<h2>Eclipse</h2>
<ul>
<li><a href="http://eclipse.org/downloads/">Downloading Eclipse</a> (get the package for Java developers)</li>
<li>Note: A new Mac won't have Java the runtime installed. You will be prompted to download and install it when you try to run Eclipse the first time.</li>
<li><a href="http://developer.android.com/sdk/index.html">Downloading and Installing the SDK</a></li>
<li>Use Ctrl+Space for "Content Assist" (autocomplete)</li>
<li>Search -> File... is the only decent multi-file search command. And that's where the multi-file Replace button is hidden.</li>
<li>Code templates, key bindings, and other settings are saved per-workspace. They can be exported and imported into other workspaces.</li>
</ul>
<h2>Android SDK</h2>
<ul>
<li>Do <code>android update project --path .</code> to prepare a directory for command-line builds. This will create <code>local.properties</code>. To make a release build, do <code>ant all clean release</code>. </li>
<li><a href="http://developer.android.com/guide/publishing/app-signing.html">App Signing</a>. To share a <code>debug.keystore</code> between developers, create it like this: <code>keytool -genkey -keypass android -keystore debug.keystore -alias androiddebugkey -storepass android -validity 1000 -dname "CN=Android Debug,O=Android,C=US"</code></li>
</ul>
<h2>Compatibility Library</h2>
<p>A <a href="http://developer.android.com/sdk/compatibility-library.html">compatibility library</a> is available to provide newer API features to older API levels.</p>
<h2>Application Resources</h2>
<p>See http://developer.android.com/guide/topics/resources/index.html</p>
<h2>Styles and Themes</h2>
<p>Application-specific styles are defined in XML files in the <code>res/values</code> folder of the project. Syntax for style definitions is described here: http://developer.android.com/guide/topics/resources/style-resource.html</p>
<p>The standard Android style and theme definitions can be found in the <code>platforms/android-N/data/res/values</code> folder in the Android SDK (where <em>N</em> is the platform version).</p>
<p>All available style properties are listed here: http://developer.android.com/reference/android/R.attr.html</p>
<p>All available styles are listed here: http://developer.android.com/reference/android/R.style.html</p>
<p>For more on Styles and Themes: http://developer.android.com/guide/topics/ui/themes.html</p>
<h2>Assets</h2>
<p>These tools help generate assets for Android apps:</p>
<ul>
<li>Android Asset Studio: <a href="http://android-ui-utils.googlecode.com/hg/asset-studio/dist/index.html">http://android-ui-utils.googlecode.com/hg/asset-studio/dist/index.html</a></li>
<li>ActoinBar Style Generator: <a href="http://actionbarstylegenerator.com/">http://actionbarstylegenerator.com/</a></li>
<li>Android Holo Colors: <a href="http://android-holo-colors.com">http://android-holo-colors.com</a></li>
</ul>
<h2>Activity Lifecycle</h2>
<p>Activities exist in one of three states:</p>
<ul>
<li>Resumed (or Running): activity is in foreground and has user focus</li>
<li>Paused: another activity is in the foreground and has focus, but this activity is still visible</li>
<li>Stopped: activity is completely obscured, but still maintains state</li>
</ul>
<p>Lifecycle callbacks:</p>
<ul>
<li><code>onCreate()</code> - followed by <code>onStart()</code></li>
<li><code>onRestart()</code> - followed by <code>onStart()</code></li>
<li><code>onStart()</code><ul>
<li><code>onResume()</code> - followed by <code>onPause()</code></li>
<li><code>onPause()</code> - followed by <code>onResume()</code> or <code>onStop()</code></li>
</ul>
</li>
<li><code>onStop()</code> - followed by <code>onDestroy()</code> or <code>onRestart()</code></li>
<li><code>onDestroy()</code> - final call</li>
</ul>
<p>Activity is killable after <code>onPause()</code>, <code>onStop()</code>, or <code>onDestroy()</code>. (Only <code>onPause()</code> is "guaranteed" to be called.)</p>
<p>When Activity A starts Activity B:</p>
<ol>
<li>A's <code>onPause()</code> method executes</li>
<li>B's <code>onCreate()</code>, <code>onStart()</code>, and <code>onResume()</code> methods execute.</li>
<li>If A is no longer visible, its <code>onStop()</code> method executes</li>
</ol>
<p><code>onSaveInstanceState(Bundle)</code> will be called if an activity may be killed. If called, it will be called before <code>onStop()</code>, but there are no guarantees about whether it will occur before or after <code>onPause()</code>. Bundle will be passed to <code>onCreate(Bundle)</code> and <code>onRestoreInstanceState(Bundle)</code> if activity is recreated. By default, this mechanism saves/restores states of all views in the hierarchy, as long as each widget has an ID.</p>
<h2>Fragments and Loaders</h2>
<p>See <a href="http://developer.android.com/guide/topics/fundamentals/fragments.html">Fragments</a> and <a href="http://developer.android.com/guide/topics/fundamentals/loaders.html">Loaders</a>. Also see http://android-developers.blogspot.com/2011/02/android-30-fragments-api.html.</p>
<p>The compatibility library has support for fragments and loaders. Need to derive class from <a href="http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html">FragmentActivity</a> and use <code>getSupportFragmentManager()</code> and <code>getSupportLoaderManager()</code>.</p>
<h2>Menus</h2>
<p>For basic implementation of menu triggered by MENU button or action bar, override <code>onCreateOptionsMenu()</code> and <code>onOptionsItemSelected()</code>.</p>
<p>To change options menu after creation, override <code>onPrepareOptionsMenu()</code>. On Android 3.0+, need to call <code>invalidateOptionsMenu()</code> to force system to call <code>onPrepareOptionsMenu()</code>.</p>
<p>To create context menu for a view, call <code>registerForContextMenu()</code>, then implement <code>onCreateContextMenu()</code> and <code>onContextItemSelected()</code>.</p>
<p>More about menus here: http://developer.android.com/guide/topics/ui/menus.html</p>
<h2>Dialogs</h2>
<p>Override <code>onCreateDialog(int)</code> and maybe <code>onPrepareDialog(int, Dialog)</code>. Use the <code>AlertDialog</code>, <code>ProgressDialog</code>, <code>DatePickerDialog</code>, or <code>TimePickerDialog</code> classes, or create custom dialog.</p>
<p>Call <code>showDialog(int)</code> to display the dialog. Call Dialog's <code>dismiss()</code> or activity's <code>dismissDialog(int)</code> to close it. Implement <code>DialogInterface.OnDismissListener</code> and/or <code>DialogInterface.OnCancelListener</code> for notification.</p>
<p>Consider using new fragments API for dialogs.</p>
<h2>ListView and ListActivity</h2>
<ul>
<li><a href="http://developer.android.com/reference/android/app/ListActivity.html">ListActivity</a></li>
<li><a href="http://developer.android.com/reference/android/widget/ListView.html">ListView</a></li>
<li><a href="http://developer.android.com/reference/android/widget/ListAdapter.html">ListAdapter</a></li>
<li><a href="http://developer.android.com/resources/tutorials/views/hello-listview.html">Hello, ListView tutorial</a></li>
</ul>
<p><a href="http://developer.android.com/reference/android/R.layout.html">R.layout</a> lists standard list item layouts.</p>
<p><a href="http://github.com/commonsguy/cwac-merge">CWAC MergeAdapter</a> makes it easy to bind list items to different kinds of data.</p>
<p>Call adapter's <code>notifyDataSetChanged()</code> method when data changes, to force views and observers to update.</p>
<h2>Tabs</h2>
<p>See the <a href="http://developer.android.com/resources/tutorials/views/hello-tabwidget.html">Tab Layout tutorial</a></p>
<p>Note that newer Android apps should use the ActionBar API rather than old-style tabs.</p>Measuring Elapsed Time in C# Methods2012-02-10T01:12:02-05:002012-02-10T01:12:02-05:00Kristopher Johnsontag:undefinedvalue.com,2012-02-10:/measuring-elapsed-time-c-methods.html<p>When determining why some damned thing in my .NET programs is taking so damned long, it is useful to be able to look at the elapsed time for various sections of code. The straightforward way to do this is to create an instance of <code>System.Diagnostics.Stopwatch</code>, start it, do …</p><p>When determining why some damned thing in my .NET programs is taking so damned long, it is useful to be able to look at the elapsed time for various sections of code. The straightforward way to do this is to create an instance of <code>System.Diagnostics.Stopwatch</code>, start it, do the thing, then stop the <code>Stopwatch</code> and print out the elapsed time.</p>
<p>But it gets tedious to keep adding those <code>var stopwatch = new Stopwatch(); stopwatch.Start();</code> and <code>stopwatch.Stop(); Print(stopwatch.ElapsedMilliseconds);</code> lines all over the place, and it also makes the code less readable, so I made a little class to simplify things.</p>
<!--break-->
<p>Here is the class:</p>
<div class="highlight"><pre><span></span><code>public static class Timed
{
/// <span class="nt"><summary></span>
/// Execute action then invoke reporting action with elapsed time
/// <span class="nt"></summary></span>
/// <span class="nt"><param</span> <span class="na">name=</span><span class="s">"timedAction"</span><span class="nt">></span>action to be executed and elapsed time measured<span class="nt"></param></span>
/// <span class="nt"><param</span> <span class="na">name=</span><span class="s">"reportAction"</span><span class="nt">></span>action to be executed with the elapsed number of milliseconds passed as a parameter<span class="nt"></param></span>
/// <span class="nt"><returns></span>number of milliseconds elapsed during timedAction<span class="nt"></returns></span>
public static long Execute(Action timedAction, Action<span class="nt"><long></span> reportAction)
{
var stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
timedAction();
stopwatch.Stop();
var elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
if (reportAction != null)
{
reportAction(elapsedMilliseconds);
}
return elapsedMilliseconds;
}
/// <span class="nt"><summary></span>
/// Invoke specified action and return number of milliseconds elapsed
/// <span class="nt"></summary></span>
/// <span class="nt"><param</span> <span class="na">name=</span><span class="s">"timedAction"</span><span class="nt">></span>timed action to be executed<span class="nt"></param></span>
/// <span class="nt"><returns></span>number of milliseconds elapsed<span class="nt"></returns></span>
public static long Execute(Action timedAction)
{
return Timed.Execute(timedAction, null);
}
}
</code></pre></div>
<p>So, for example, if one wanted to measure the time used by the loop in this method:</p>
<div class="highlight"><pre><span></span><code><span class="n">public</span><span class="w"> </span><span class="nb nb-Type">void</span><span class="w"> </span><span class="n">BlurpAllFrables</span><span class="p">()</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">foreach</span><span class="w"> </span><span class="p">(</span><span class="k">var</span><span class="w"> </span><span class="n">frable</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">GetFrables</span><span class="p">())</span><span class="w"></span>
<span class="w"> </span><span class="n">frable</span><span class="o">.</span><span class="n">Blurp</span><span class="p">();</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>one could do this:</p>
<div class="highlight"><pre><span></span><code><span class="n">public</span><span class="w"> </span><span class="nb nb-Type">void</span><span class="w"> </span><span class="n">BlurpAllFrables</span><span class="p">()</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">Timed</span><span class="o">.</span><span class="n">Execute</span><span class="p">(()</span><span class="w"> </span><span class="o">=></span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">foreach</span><span class="w"> </span><span class="p">(</span><span class="k">var</span><span class="w"> </span><span class="n">frable</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">GetFrables</span><span class="p">())</span><span class="w"></span>
<span class="w"> </span><span class="n">frable</span><span class="o">.</span><span class="n">Blurp</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="n">long</span><span class="w"> </span><span class="n">millis</span><span class="p">)</span><span class="w"> </span><span class="o">=></span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">Trace</span><span class="o">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="nb nb-Type">String</span><span class="o">.</span><span class="n">Format</span><span class="p">(</span><span class="s2">"Blurping frables took {0} ms"</span><span class="p">,</span><span class="w"> </span><span class="n">millis</span><span class="o">.</span><span class="n">ToString</span><span class="p">()));</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>or one could do this:</p>
<div class="highlight"><pre><span></span><code><span class="n">public</span><span class="w"> </span><span class="nb nb-Type">void</span><span class="w"> </span><span class="n">BlurpAllFrables</span><span class="p">()</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">var</span><span class="w"> </span><span class="n">millis</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Timed</span><span class="o">.</span><span class="n">Execute</span><span class="p">(()</span><span class="w"> </span><span class="o">=></span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">foreach</span><span class="w"> </span><span class="p">(</span><span class="k">var</span><span class="w"> </span><span class="n">frable</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">GetFrables</span><span class="p">())</span><span class="w"></span>
<span class="w"> </span><span class="n">frable</span><span class="o">.</span><span class="n">Blurp</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="w"> </span><span class="n">Trace</span><span class="o">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="nb nb-Type">String</span><span class="o">.</span><span class="n">Format</span><span class="p">(</span><span class="s2">"Blurping frables took {0} ms"</span><span class="p">,</span><span class="w"> </span><span class="n">millis</span><span class="o">.</span><span class="n">ToString</span><span class="p">()));</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>(Or one could use an actual profiler.)</p>452012-01-17T02:06:22-05:002012-01-17T02:06:22-05:00Kristopher Johnsontag:undefinedvalue.com,2012-01-17:/45.html<p>It's another one of those "5" years. I'm now halfway to 90. 90 sounds old, but once upon a time, 45 sounded old too.</p>
<p>The year has not been easy. We lost my maternal grandmother, <a href="http://nerofuneralhome.net/fh/obituaries/obituary.cfm?o_id=1238522&fh_id=13570">Ann Woods</a>, in August. I have many good memories of time spent with her up …</p><p>It's another one of those "5" years. I'm now halfway to 90. 90 sounds old, but once upon a time, 45 sounded old too.</p>
<p>The year has not been easy. We lost my maternal grandmother, <a href="http://nerofuneralhome.net/fh/obituaries/obituary.cfm?o_id=1238522&fh_id=13570">Ann Woods</a>, in August. I have many good memories of time spent with her up in North Dakota.</p>
<p>My brother announced that he and his wife were getting a divorce. Again, not happy news, but we have to accept it. The end-of-year family get-togethers have seemed incomplete without my sister-in-law.</p>
<p>However, even with those events, this year has been a pretty good one for me.</p>
<p>Professionally, things have been better than usual. I developed Real Professional mobile apps for <a href="http://itunes.apple.com/us/app/udot-traffic/id477093147?mt=8">iPhone/iPad</a> and <a href="https://market.android.com/details?id=com.transcore.android.commuterLink&hl=en">Android</a>. These apps have over 30,000 users, and they have been well received. Most applications I've developed during my career had only a few dozen users, so this larger reach gives me a sense that I can have some sort of effect on the world.</p>
<p>I've wanted to make iPhone and iPad software since I was 12 years old. Unfortunately, iPhones and iPads didn't exist back then. I'm glad that the devices are finally here, and that they pay me to work with them.</p>
<p>One resolution I've made for the coming year is to spend at least one hour per day working on personal programming projects. I gave up programming "for fun" a few years ago when work was burning me out, but now my interest has been rekindled.</p>
<p>I started meditating almost-daily about 12 months ago. For this, I credit 5by5's <a href="http://5by5.tv/person/dan-benjamin">Dan Benjamin</a>, who mentioned his <a href="http://hivelogic.com/articles/an-introduction-to-mindfulness-meditation/">meditation practice</a> on several podcasts. I'm no zen master, but I do feel less stress and it's easier to ignore all the little problems in my life. I only have a mental breakdown two or three times a day now.</p>
<p>The optometrist prescribed progressive lenses for me this year. I don't like having old-people glasses, but I'm glad that my corrected eyesight is better. I wish I'd gotten these years ago.</p>
<p>Life with my wife and stepson keeps getting better. Bailey has matured and calmed down a lot over the past year—we actually see him smile once in a while! Pebble hasn't had to spend any time in the hospital this year, which makes us both feel a lot better. Our five dogs are all healthy and happy. Our little cabin in the foothills is still the nicest place I've ever been.</p>
<p>I don't like getting older, but as long as the years keep getting better, I won't complain about it.</p>Creating .NET Remoting IPC Channels2011-12-14T14:36:55-05:002011-12-14T14:36:55-05:00Kristopher Johnsontag:undefinedvalue.com,2011-12-14:/creating-net-remoting-ipc-channels.html<p>Yet another C# code snippet. I'm developing a service and an accompanying UI that always run on the same physical box, and it was suggested that I implement the communication between them using .NET Remoting and the <em>IPC</em> channel type, which is a supposedly-easy way to get processes on the …</p><p>Yet another C# code snippet. I'm developing a service and an accompanying UI that always run on the same physical box, and it was suggested that I implement the communication between them using .NET Remoting and the <em>IPC</em> channel type, which is a supposedly-easy way to get processes on the same machine to talk to one another.</p>
<p>Of course, it wasn't easy, because if you simply create and register an <code>IpcChannel</code> with default parameters, you get security-related exceptions when you try to do anything with it. You have to delve through documentation and online forums to figure out what underdocumented magic is required to get the stuff to actually work. </p>
<p>There were two obstacles I had to overcome:</p>
<ul>
<li>By default, user-defined types will not be deserialized, to prevent deserialization-based attacks by malicious clients. To disable this "feature", one must set the <code>TypeFilterLevel</code> to <code>Full</code>.</li>
<li>My service runs as the LocalSystem user, whereas the client application runs in the logged-in user's security context. By default, the user's account would not be able open the IPC port that the service creates. The fix to this is to set the channel's <code>authorizedGroup</code> to the name of a user group that is allowed to open the port.</li>
</ul>
<p>So, as usual, the resulting code looks simple, but it took a couple of hours to figure out what had to be written.</p>
<!--break-->
<p>Here's the code, which is intended to work on .NET Framework 2.0 and higher on Windows XP and newer operating systems:</p>
<script src="https://gist.github.com/3047562.js?file=IpcRemotingUtil.cs"></script>
<p>On the server side, I use it like this:</p>
<div class="highlight"><pre><span></span><code><span class="k">var</span><span class="w"> </span><span class="n">channel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">IpcRemotingUtil</span><span class="o">.</span><span class="n">CreateIpcChannel</span><span class="p">(</span><span class="n">MyServer</span><span class="o">.</span><span class="n">IpcPortName</span><span class="p">);</span><span class="w"></span>
<span class="n">ChannelServices</span><span class="o">.</span><span class="n">RegisterChannel</span><span class="p">(</span><span class="n">channel</span><span class="p">,</span><span class="w"> </span><span class="bp">true</span><span class="p">);</span><span class="w"></span>
<span class="k">var</span><span class="w"> </span><span class="n">entry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new</span><span class="w"> </span><span class="n">WellKnownServiceTypeEntry</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="nb">typeof</span><span class="p">(</span><span class="n">MyRemoteObject</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="s2">"MyRemoteObject.rem"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">WellKnownObjectMode</span><span class="o">.</span><span class="n">Singleton</span><span class="p">);</span><span class="w"></span>
<span class="n">RemotingConfiguration</span><span class="o">.</span><span class="n">RegisterWellKnownServiceType</span><span class="p">(</span><span class="n">entry</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>And on the client side, I use it like this:</p>
<div class="highlight"><pre><span></span><code><span class="k">var</span><span class="w"> </span><span class="n">channel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">IpcRemotingUtil</span><span class="o">.</span><span class="n">CreateIpcChannelWithUniquePortName</span><span class="p">();</span><span class="w"></span>
<span class="n">ChannelServices</span><span class="o">.</span><span class="n">RegisterChannel</span><span class="p">(</span><span class="n">channel</span><span class="p">,</span><span class="w"> </span><span class="bp">true</span><span class="p">);</span><span class="w"></span>
<span class="k">var</span><span class="w"> </span><span class="n">objectUri</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb nb-Type">String</span><span class="o">.</span><span class="n">Format</span><span class="p">(</span><span class="s2">"ipc://{0}/MyRemoteObject.rem"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">MyServer</span><span class="o">.</span><span class="n">IpcPortName</span><span class="p">);</span><span class="w"></span>
<span class="k">var</span><span class="w"> </span><span class="n">remoteObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">MyRemoteObject</span><span class="p">)</span><span class="n">Activator</span><span class="o">.</span><span class="n">GetObject</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="nb">typeof</span><span class="p">(</span><span class="n">MyRemoteObject</span><span class="p">),</span><span class="w"> </span><span class="n">objectUri</span><span class="p">);</span><span class="w"></span>
<span class="n">try</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">remoteObject</span><span class="o">.</span><span class="n">FooBarAndBaz</span><span class="p">();</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="n">catch</span><span class="w"> </span><span class="p">(</span><span class="n">Exception</span><span class="w"> </span><span class="n">ex</span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">aaaiiiyyyeee</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Note that both sides use <code>IpcChannel</code> so that the server can invoke callbacks in the client. If you don't need callbacks, you could use <code>IpcServerChannel</code> and <code>IpcClientChannel</code> instead of <code>IpcChannel</code>. If you do that, you need to make sure you set the <code>TypeFilterLevel</code> and <code>authorizedGroup</code> on the <code>IpcServerChannel</code> as is done above for the <code>IpcChannel</code>.</p>Configuring WebDAV and Digest Authentication for Ubuntu2011-12-08T22:42:40-05:002011-12-08T22:42:40-05:00Kristopher Johnsontag:undefinedvalue.com,2011-12-08:/configuring-webdav-and-digest-authentication-ubuntu.html<p>I'm looking at using <a href="http://wpkg.org/">WPKG</a> as a mechanism for distributing software updates to client workstations. WPKG appears to be a pretty nice system, but it has one big downside: one has to set up a WebDAV-enabled server if the updates are to be pulled from the Internet instead of from …</p><p>I'm looking at using <a href="http://wpkg.org/">WPKG</a> as a mechanism for distributing software updates to client workstations. WPKG appears to be a pretty nice system, but it has one big downside: one has to set up a WebDAV-enabled server if the updates are to be pulled from the Internet instead of from a local shared directory. So I've spent a few hours learning the intricacies of setting up WebDAV on <a href="https://undefinedvalue.com/2010/11/12/setting-drupal-ubuntu-1010-ec2">my Ubuntu-based Internet server</a> and accessing it from Windows machines. Here's what I learned.</p>
<!--break-->
<p>I started by following the steps here: <a href="http://www.howtoforge.com/how-to-set-up-webdav-with-apache2-on-ubuntu-9.04">How To Set Up WebDAV With Apache2 On Ubuntu 9.04</a>.</p>
<p>That seemed to get me working: I could access the <code>webdav</code> directory without any problem using web browsers, Mac OS X, or Linux. However, it didn't work when I tried to connect from Windows, which is unfortunate, because Windows is the one platform where I need it to work.</p>
<p>After a couple of hours of Googling and hair-pulling, I discovered that the problem is that Windows Vista and Windows 7 don't support Basic HTTP authentication for WebDAV. One needs to either use Digest authentication, or <a href="http://support.microsoft.com/kb/841215">make registry changes</a> to enable Basic authentication.</p>
<p>I didn't want to force registry changes on all the client machines, and Digest authentication is The Right Thing anyway, so I changed my Apache configuration to do it.</p>
<p>Here are the new lines that ended up in my <code>/etc/apache2/sites-available/default</code> file:</p>
<div class="highlight"><pre><span></span><code> <span class="nt"><Directory</span> <span class="err">/var/www/webdav</span><span class="nt">/></span>
Options Indexes MultiViews
AllowOverride None
Order allow,deny
allow from all
<span class="nt"></Directory></span>
Alias /webdav /var/www/webdav
<span class="nt"><Location</span> <span class="err">/webdav</span><span class="nt">/></span>
DAV On
AuthType Digest
AuthName "webdav"
AuthDigestDomain /webdav/
AuthDigestProvider file
AuthUserFile /var/www/webdav/.digest_passwd.dav
Require valid-user
<span class="nt"></Location></span>
</code></pre></div>
<p>I had to enable the <code>auth_digest</code> module:</p>
<div class="highlight"><pre><span></span><code>sudo a2enmod auth_digest
</code></pre></div>
<p>Then I had to create the digest authentication file, adding the user <code>test</code>:</p>
<div class="highlight"><pre><span></span><code><span class="n">sudo</span><span class="w"> </span><span class="n">htdigest</span><span class="w"> </span><span class="o">-</span><span class="n">c</span><span class="w"> </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">www</span><span class="o">/</span><span class="n">webdav</span><span class="o">/.</span><span class="n">digest_passwd</span><span class="o">.</span><span class="n">dav</span><span class="w"> </span><span class="n">webdav</span><span class="w"> </span><span class="n">test</span><span class="w"></span>
<span class="n">sudo</span><span class="w"> </span><span class="n">chown</span><span class="w"> </span><span class="n">root</span><span class="p">:</span><span class="n">www</span><span class="o">-</span><span class="n">data</span><span class="w"> </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">www</span><span class="o">/</span><span class="n">webdav</span><span class="o">/.</span><span class="n">digest_passwd</span><span class="o">.</span><span class="n">dav</span><span class="w"></span>
<span class="n">sudo</span><span class="w"> </span><span class="n">chmod</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">www</span><span class="o">/</span><span class="n">webdav</span><span class="o">/.</span><span class="n">digest_passwd</span><span class="o">.</span><span class="n">dav</span><span class="w"></span>
</code></pre></div>
<p>With all those changes made, I did this</p>
<div class="highlight"><pre><span></span><code>sudo /etc/init.d/apache2 restart
</code></pre></div>
<p>And then I verified that I could do the following from my Windows box:</p>
<div class="highlight"><pre><span></span><code>net use \\example.com\webdav MYPASSWORD /user:test
dir \\example.com\webdav
</code></pre></div>
<p>With that done, I then set up WPKG and everything was easy and smooth!</p>
<p>(Not really, but the problems weren't due to WebDAV.)</p>Deserializing Objects from XML in C#2011-11-22T15:22:03-05:002011-11-22T15:22:03-05:00Kristopher Johnsontag:undefinedvalue.com,2011-11-22:/deserializing-objects-xml-c.html<p>Here's another C# code snippet that takes me way too much time to recreate by just reading the documentation. </p>
<p>This is a simple example of a class that can be serialized to/from XML. In this case the "ServerConfig" XML string can contain a list of servers, looking like this …</p><p>Here's another C# code snippet that takes me way too much time to recreate by just reading the documentation. </p>
<p>This is a simple example of a class that can be serialized to/from XML. In this case the "ServerConfig" XML string can contain a list of servers, looking like this:</p>
<div class="highlight"><pre><span></span><code><span class="nt"><ServerConfig</span> <span class="na">loggingEnabled=</span><span class="s">"1"</span><span class="nt">></span>
<span class="nt"><Servers></span>
<span class="nt"><Server</span> <span class="na">host=</span><span class="s">"test1.example.com"</span> <span class="na">port=</span><span class="s">"9999"</span> <span class="nt">/></span>
<span class="nt"><Server</span> <span class="na">host=</span><span class="s">"test2.example.com"</span> <span class="na">port=</span><span class="s">"8888"</span> <span class="nt">/></span>
<span class="nt"></Servers></span>
<span class="nt"></ServerConfig></span>
</code></pre></div>
<p>The client code can just do "<code>var serverConfig = ServerConfig.FromXmlString(s);</code>" to deserialize it into a <code>ServerConfig</code> object.</p>
<script src="https://gist.github.com/3227483.js?file=XmlDeserializationExample.cs"></script>
<p>(The method that would serialize a <code>ServerConfig</code> to an XML string is left as an exercise for the reader. I rarely need to do that.)</p>Pretty-formatting XML in C#2011-11-17T22:13:28-05:002011-11-17T22:13:28-05:00Kristopher Johnsontag:undefinedvalue.com,2011-11-17:/pretty-formatting-xml-c.html<p>I had a need to convert an XML string to a nice, indented format. It was a little more complicated than I expected, so I'm posting this snippet here where I can find it again when I need it.</p>
<div class="highlight"><pre><span></span><code><span class="n">using</span><span class="w"> </span><span class="n">System</span><span class="p">;</span><span class="w"></span>
<span class="n">using</span><span class="w"> </span><span class="n">System</span><span class="o">.</span><span class="n">Text</span><span class="p">;</span><span class="w"></span>
<span class="n">using</span><span class="w"> </span><span class="n">System</span><span class="o">.</span><span class="n">Xml</span><span class="p">;</span><span class="w"></span>
<span class="n">using</span><span class="w"> </span><span class="n">System</span><span class="o">.</span><span class="n">Xml …</span></code></pre></div><p>I had a need to convert an XML string to a nice, indented format. It was a little more complicated than I expected, so I'm posting this snippet here where I can find it again when I need it.</p>
<div class="highlight"><pre><span></span><code><span class="n">using</span><span class="w"> </span><span class="n">System</span><span class="p">;</span><span class="w"></span>
<span class="n">using</span><span class="w"> </span><span class="n">System</span><span class="o">.</span><span class="n">Text</span><span class="p">;</span><span class="w"></span>
<span class="n">using</span><span class="w"> </span><span class="n">System</span><span class="o">.</span><span class="n">Xml</span><span class="p">;</span><span class="w"></span>
<span class="n">using</span><span class="w"> </span><span class="n">System</span><span class="o">.</span><span class="n">Xml</span><span class="o">.</span><span class="n">Linq</span><span class="p">;</span><span class="w"></span>
<span class="k">static</span><span class="w"> </span><span class="n">string</span><span class="w"> </span><span class="n">PrettyXml</span><span class="p">(</span><span class="n">string</span><span class="w"> </span><span class="n">xml</span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">var</span><span class="w"> </span><span class="n">stringBuilder</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new</span><span class="w"> </span><span class="n">StringBuilder</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="k">var</span><span class="w"> </span><span class="n">element</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">XElement</span><span class="o">.</span><span class="n">Parse</span><span class="p">(</span><span class="n">xml</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">var</span><span class="w"> </span><span class="n">settings</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new</span><span class="w"> </span><span class="n">XmlWriterSettings</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">settings</span><span class="o">.</span><span class="n">OmitXmlDeclaration</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">true</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">settings</span><span class="o">.</span><span class="n">Indent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">true</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">settings</span><span class="o">.</span><span class="n">NewLineOnAttributes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">true</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">using</span><span class="w"> </span><span class="p">(</span><span class="k">var</span><span class="w"> </span><span class="n">xmlWriter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">XmlWriter</span><span class="o">.</span><span class="n">Create</span><span class="p">(</span><span class="n">stringBuilder</span><span class="p">,</span><span class="w"> </span><span class="n">settings</span><span class="p">))</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">element</span><span class="o">.</span><span class="n">Save</span><span class="p">(</span><span class="n">xmlWriter</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">stringBuilder</span><span class="o">.</span><span class="n">ToString</span><span class="p">();</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Note that this method can throw exceptions for a variety of reasons.</p>Code Reuse Is Not Lazy2011-11-01T23:16:16-04:002011-11-01T23:16:16-04:00Kristopher Johnsontag:undefinedvalue.com,2011-11-01:/code-reuse-not-lazy.html<p><a href="http://www.stackoverflow.com/">Stack Overflow</a> is an amazing resource for computer programmers. I've asked a lot of questions there, and answered a lot of questions there, and I've found the answers to lots and lots of questions that someone else has already asked. Participating in Stack Overflow has been great.</p>
<p>However, there have …</p><p><a href="http://www.stackoverflow.com/">Stack Overflow</a> is an amazing resource for computer programmers. I've asked a lot of questions there, and answered a lot of questions there, and I've found the answers to lots and lots of questions that someone else has already asked. Participating in Stack Overflow has been great.</p>
<p>However, there have been a few cases where I've asked a question in this form:</p>
<blockquote>
<p>I need some code that will [...]. Can someone point me to some existing code that does this?</p>
</blockquote>
<p>And then I have received responses and comments like these:</p>
<blockquote>
<p>We aren't here to do your homework for you.</p>
<p>You can't expect help without doing a little bit of work on your own.</p>
<p>You mean "Plez sir send me the codez?"</p>
</blockquote>
<p>I don't understand why my questions provoke these responses. It is possible that I am asking the question in a way that the readers interpret as demanding or rude. But I also wonder if some people think that looking for existing code to solve a problem is a dishonorable thing to do, and Real Programmers write everything they need from scratch.</p>
<p>What if I had asked this:</p>
<blockquote>
<p>I am new to C. I need to format some printed output in my C program. What's the easiest way to do this?</p>
</blockquote>
<p>I hope someone would respond with</p>
<blockquote>
<p>There is a standard function called "printf()" that will do what you want.</p>
</blockquote>
<p>and not</p>
<blockquote>
<p>Why can't you just figure out how to write your own formatting function It's the best way to learn C. Or are you just too lazy to spend time learning things?</p>
</blockquote>
<p>Appropriate reuse of already written and debugged code is the mark of a professional, and time spent in research of existing implementations of algorithms you need is time well invested. If you want to write the millionth from-scratch implementation of Quicksort in your favorite programming language, go right ahead, but those of us who have real work to do are going to look for a library or a code snippet.</p>Steve Jobs2011-10-10T14:08:24-04:002011-10-10T14:08:24-04:00Kristopher Johnsontag:undefinedvalue.com,2011-10-10:/steve-jobs.html<p>It's OK to have heroes, right?</p>
<p>I didn't think I had any heroes, but when Steve Jobs passed away last week, it hit me harder than expected. I didn't cry, I didn't go into a funk, but I was sad and felt like I'd lost something.</p>
<p>We idealize our heroes …</p><p>It's OK to have heroes, right?</p>
<p>I didn't think I had any heroes, but when Steve Jobs passed away last week, it hit me harder than expected. I didn't cry, I didn't go into a funk, but I was sad and felt like I'd lost something.</p>
<p>We idealize our heroes. We ignore their flaws and the fact that they are just regular people, and instead create simple character sketches based on a few noble aspects of their personalities and deeds. For example, Albert Einstein was a very smart, yet very warm and kind person, Abraham Lincoln freed the slaves, and Thomas Jefferson wrote the Declaration of Independence. We reduce a hero to a simple sentence, knowing that it is an incomplete picture, but focusing on what it is that makes the hero important to us.</p>
<p>Steve Jobs inspired people to make awesome things.</p>
<p>To me he represents the drive to make things better. But "better" wasn't good enough; he insisted that they be <em>great</em>. Sure, he exaggerated the greatness of Apple's products, and didn't give credit to the sources of many of the ideas that went into those products, but he did everything he could to make them the best they could be, and to give them a little bit of magic.</p>
<p>He didn't make things easy for software developers, or for hardware engineers, or for component manufacturers, or for IT managers. He simply did not care about their problems. His goal was to bring the amazing power of computers to normal people. He wanted normal people to have machines that they could use to create things and to enjoy themselves, without expecting them to become programmers and hardware techs.</p>
<p>A lot of business-type people wish that they were Steve Jobs. Not me. I would not have wanted to <em>be</em> Steve Jobs, but I would have loved to <em>work for</em> Steve Jobs. Even if I was the recipient of his famously colorful criticism, I'd know that I was getting advice from someone who expected me to do great things.</p>
<p>I missed out on the Apple II era (having an Atari 800 instead), so my first Apple product was a <a href="http://en.wikipedia.org/wiki/Macintosh_SE">Macintosh SE</a>. I loved that cute, beautiful little computer. I learned Pascal, C, and Motorola 68000 assembly programming on that computer. I drew pictures, I played games, I stuck it in the back seat of my car when going from place to place.</p>
<p>But you know what it is about that Mac that really sticks out in my memory? If you opened the case, instead of finding just metal and wires, you found engraved signatures of Steve Jobs and other members of the Macintosh team. They had created something special, and they were very proud of their work. That inspired me to be proud of the things I make, and to make them as beautiful and magical as I am able.</p>
<p>Sometimes I forget to do those things. I need Steve Jobs around to occasionally remind me.</p>
<p>It's OK to have heroes, right?</p>My Visit to Android-land2011-09-11T23:50:52-04:002011-09-11T23:50:52-04:00Kristopher Johnsontag:undefinedvalue.com,2011-09-11:/my-visit-android-land.html<p>I've been a happy iPhone and iPad user for a while, but I'm currently involved in developing Android applications, so I decided to buy an Android phone to use for a few weeks. It's important to understand the user-interface conventions and user expectations for whatever platform you are developing for …</p><p>I've been a happy iPhone and iPad user for a while, but I'm currently involved in developing Android applications, so I decided to buy an Android phone to use for a few weeks. It's important to understand the user-interface conventions and user expectations for whatever platform you are developing for, and I figured that the only way to learn those things would be to take the plunge and immerse myself in Android for a while.</p>
<p>The following is a self-indulgent review of what I've found. If you are an Android fan, you will probably summarize it as "Surprise! Apple fanboy doesn't like Android," and you can just stop reading here.</p>
<!--break-->
<h2>Context</h2>
<p>I'll quickly run down my background and biases, so people can understand some of my expectations and reactions.</p>
<p>I got my first Apple computer, a Macintosh SE, in 1987. I immediately felt that Apple understood how computers should look and work. My enthusiasm for Mac OS waned in the late 1990's as Apple stuck with their rickety tinkertoy OS, and I became a GNU/Linux user for a few years. I bought an iMac G5 in 2004 and my enthusiasm for the Apple way was restored.</p>
<p>However, while my personal computers have mostly been Macs, 90% of my income for the past 19 years has come from developing Windows applications, and most of the rest has come from developing UNIX applications, so I'm not ignorant of how other systems work. For what it's worth, I think Windows 7 is about even with Mac OS X 10.6 and 10.7 in terms of providing a good user experience. I stick with Macs because the hardware looks and feels better than the low-margin Wintel hardware that's on the market, and I don't mind paying more for something nice. I also like having a home computer that is nothing like my work computer.</p>
<p>I've long been a fan of mobile computing. When I was a kid, I wanted an iPad, but nobody made an iPad until 2010. So in the meantime I've had an Apple Newton, a few Palm OS devices, a few Windows CE/Pocket PC/Windows Mobile devices, and a couple of never-officially-released products (anybody remember the <a href="http://tuxmobil.org/pda_linux_agenda.html">Agenda VR</a>?). The iPhone was the first pocket-sized computer that did not completely suck.</p>
<p>While I am a fan of Apple's products, I don't like many of its policies toward consumers and developers, and I've wished for high-quality alternatives. I <a href="https://undefinedvalue.com/2009/11/19/switching-away-apple">looked at Android phones back in 2009</a>, hoping that I'd like them enough to switch away from Apple's ecosystem. Unfortunately, <a href="https://undefinedvalue.com/2009/11/25/android-maybe-not">I was disappointed</a> with the quality of Android phones and software, and it seemed clear to me that nobody was going to get rich developing software for a system that was marketed as a low-cost alternative to Apple's products. iPhone users are willing to pay for software, and Android users expect everything to be free, so it seemed obvious that if I wanted to make a living as a mobile-software developer, the iPhone market was the place to be.</p>
<p>I've been doing some iOS development, and I am now involved in porting iOS apps over to Android. As stated above, I think it's important that developers and designers deeply understand the conventions of the platforms they are developing for, and so I decided to buy an Android phone and put my iPhone away for a while.</p>
<h2>The Hardware</h2>
<p>I had two primary criteria for choosing an Android phone:</p>
<ul>
<li>I wanted a "typical" Android phone. That is, I wanted one with display resolution, CPU speed, RAM, and features that would be close to what a majority of Android users would be using. So I didn't want the newest, most feature-packed phone, nor an old clunker.</li>
<li>I wanted a phone that would work on the AT&T network, so that I could just swap the SIM from my iPhone into it, rather than paying for another data plan or relying on wi-fi.</li>
</ul>
<p>The result is that I bought a used Samsung Captivate (Galaxy S) via eBay. The Galaxy S is last year's model, but so is the iPhone 4, so I think it's fair to compare them. I know there are better Android phones out there, so please don't write me to tell me that your Nexus S or Atrix have none of the problems I mention in this review. I don't care.</p>
<p>Here are some things I like about the Captivate:</p>
<ul>
<li>The display is bright and crisp (not as nice as the iPhone 4 retina display, but pretty close).</li>
<li>The built-in speaker seems to be a little bit louder than the iPhone's. (I often listen to podcasts using the built-in speaker, and the iPhone's low volume has always bugged me.)</li>
<li>I haven't had a dropped call yet. (But I only have about two phone calls per week, so the sample is small.)</li>
</ul>
<p>Here are some things I don't like about the Captivate:</p>
<ul>
<li>It's too big, and the buttons are awkwardly placed. I'm sure some people like the large display size, but I find it difficult to hold it and operate it with one hand. Even with two hands, it doesn't feel right. (Your hands may vary.)</li>
<li>It's hard to tell which way is up. The iPhone has this problem too, but the big home button on the iPhone is easier to find when it's in your pocket or when it's dark.</li>
<li>It feels plasticky and flimsy. I have dropped it a couple of times, and it didn't break, so It's not fragile, but it does feel like a kid's toy.</li>
<li>It is loaded with a bunch of AT&T crapware.</li>
<li>AT&T has disabled installation of apps from "unknown sources", so the Android Market is the only place one can get apps. One can't get apps from the Amazon Appstore for Android or from developers who post free apps online. (There are various workarounds for this, but it's annoying.)</li>
<li>The hardware buttons (Menu, Home, Back, and Search) are not actual buttons, but are just little touch-sensitive areas at the bottom of the phone's case. I'd prefer something I can feel.</li>
<li>Battery life is terrible. If I just leave it in my pocket all day, then it's fine, but if I use it as often as I use my iPhone, I have to recharge it a couple of times per day.</li>
</ul>
<p>I would have liked to try an Android tablet as well, but the app I'm porting is a phone app, not a tablet app, so I couldn't justify the purchase. Also, I keep hearing that <em>next year's</em> Android tablets will be really awesome, so I'll keep waiting until that year comes.</p>
<h2>The Software</h2>
<p>I hate to have to say what every other iPhone user says about Android, but here it is anyway: Android software is not as attractive and does not work as smoothly as iPhone software does. This is true both of built-in applications and third-party applications. Of course, the best Android apps look better than the worst iPhone apps, but as a rule, Android just isn't as good in the UI department. Default fonts look terrible, and are often way too big or way too small. Scrolling isn't smooth.</p>
<p>It reminds me of using Remote Desktop Connection or VNC to access a remote computer. Everything works, but there's always a slight delay and loss of resolution that takes away the illusion of direct manipulation.</p>
<p>As someone who develops both iOS and Android software, I can understand why this happens. Almost by default, iOS apps look nice. If you just throw a few of the standard UI gizmos into an app, without any additional customization or tweaking, the app will look pretty good. In contrast, Android's standard UI gizmos do not look good. Any really nice Android app you see probably has a lot of highly customized UI code. And if you do get it to look good on one model of Android phone, chances are that it will not look as good on all the other models of phone due to differences in screen sizes, resolutions, aspect ratios, graphics chipset, color schemes, fonts, etc., etc., etc.</p>
<p>The text editing experience is really bad. Unlike iOS, there is no magnifying loupe that appears when you want to finely position the cursor, so trying to place the cursor can be frustrating. Copy/paste is awkward. Many text-editing apps don't have an Undo feature. ("Who the hell does a lot of text editing on their phone," you ask? I do.)</p>
<p>There are some things I really like about Android:</p>
<ul>
<li>Alternative input methods are welcome. The Swype keyboard is awesome. I really wish Apple would license this.</li>
<li>Home-screen widgets are cool. (On the other hand, Live Wallpapers are a demo that got out of hand.)</li>
<li>The notifications system is nice. (Apple will be providing a similar feature in iOS 5.)</li>
<li>I haven't really needed "true multitasking", but it is nice that all Android apps are capable of downloading updated content in the background.</li>
<li>Google Maps navigation is nice.</li>
</ul>
<p>Much has been written about how horrible the Android Market is in comparison to the App Store, but I didn't find it to be a problem. Yes, there is a lot of crap in there, and it's frustrating when someone tells you "You need application X", and you search for "Application X" in the Market, and there are 20 matches. However, the App Store has a lot of crap in it too, and everyone knows that the way to find good software is by word of mouth and reviews from trusted sources, not by searching the store.</p>
<p>I easily found Android replacements for most of the iOS apps I depend upon. The only exception is the Omni Group's OmniFocus app, which is not available for Android. Fortunately, I can still run OmniFocus on my iPad and MacBook, so missing it on my phone is an annoyance rather then a deal-breaker.</p>
<p>While choice is good, it also has its downsides. Out of the box, my Captivate had five or six different apps that could play music, and I didn't know which one I should use. I'd prefer that there be exactly one standard built-in music player, or SMS app, or app launcher. It's great to have alternatives, but don't force a new user to make choices that they are unqualified to make.</p>
<p>iTunes is one of the worst applications in the world, but I do like that there is a standard way to sync an iOS device with my laptop. I miss that with Android. (Yes, I know about DoubleTwist.)</p>
<p>I haven't rooted the phone. I also haven't jailbroken my iPhone. Ten years ago when I was trying out a new Linux distro every week, I would have loved the chance to try a bunch of different ROMs and OS customizations in my phone. Nowadays, I just don't have time for that. If the product isn't useful out of the box, I don't want it.</p>
<h2>Closing Thoughts</h2>
<p>I used the Captivate as my primary phone for about five weeks. I was very happy to go back to the iPhone, but Android really isn't that bad. If Apple ever did anything that really pissed me off, switching to Android wouldn't be a great hardship.</p>
<p>I would still recommend an iPhone over an Android phone for a anyone who is not a geek. A big difference between being an iOS user and being an Android user is the degree of customization that is needed. You can buy an iPhone and use it as-is and be happy. In contrast, buying an Android phone is just a starting point—you'll need to try a lot of different apps and tweaks before it works the way you want it to work. But if you are a geeky control-freak, then Android does offer more opportunity to get a phone that works exactly the way you want.</p>
<p>The "open" nature of Android doesn't impress me. It's nice that one can download the source code and tweak it, but there are limits to the openness. Google doesn't release new versions of the software until it is shipping on new devices, and the whole development process is quite secretive. Handset manufacturers and carriers add their own restrictions on top of whatever Google provides, so even if Android were truly open, you're still at the mercy of the OEMs and carriers. I will never be able to get an OS upgrade for this year-old Captivate. I'd rather be subject to Apple's control than AT&T's.</p>
<p>My biggest beef with Android is that the whole thing just feels like a second-rate knockoff of the iPhone and iPad, and I've never liked using second-rate products. Palm and Microsoft have been trying some interesting things with their mobile platforms, but the folks at Google are simply engaged in mimicry. If Google can't innovate, then I hope they will steal some good ideas from those other platforms rather than just following in Apple's footsteps. If they are going to just keep copying Apple, I wish they'd do a better job of it.</p>
<p>I disagree with those who claim that Apple is evil and Google is good. Apple wants me to pay them lots of money for stuff I like. Google wants to give me a cheap operating system so that it can sell my data and attention to advertisers. I prefer Apple's offer.</p>KJMenuTableViewController - iOS Menus Made Easy2011-07-31T21:22:17-04:002011-07-31T21:22:17-04:00Kristopher Johnsontag:undefinedvalue.com,2011-07-31:/kjmenutableviewcontroller-ios-menus-made-easy.html<p>I have a new open-source library on Github for use by iOS developers: <a href="https://github.com/kristopherjohnson/KJMenuTableViewController">KJMenuTableViewController</a>.</p>
<p>KJMenuTableViewController is an Xcode project that contains set of classes that simplifies the
creation of "menus" in iOS applications using <code>UITableViewController</code>.</p>
<p>The <code>UITableViewController</code> class is a generic mechanism for presenting a scrollable list of
rows of …</p><p>I have a new open-source library on Github for use by iOS developers: <a href="https://github.com/kristopherjohnson/KJMenuTableViewController">KJMenuTableViewController</a>.</p>
<p>KJMenuTableViewController is an Xcode project that contains set of classes that simplifies the
creation of "menus" in iOS applications using <code>UITableViewController</code>.</p>
<p>The <code>UITableViewController</code> class is a generic mechanism for presenting a scrollable list of
rows of items. It is powerful and extensible, but it can be a chore to present a simple
list of button-like objects that react when tapped. One must provide implementations of several
methods of the <code>UITableViewDataSource</code> and <code>UITableViewDelegate</code> classes, each of which will
probably have a <code>case</code> statement to handle each of the individual items. It's not difficult, but it is
tedious and error-prone.</p>
<p>The KJMenuTableViewController classes simplify this usage case. One simply defines a subclass of
<code>KJMenuTableViewController</code> and overrides the <code>viewDidLoad</code> method to create sections and row items.
<code>KJMenuTableViewController</code> implements the table view delegate and data source methods to
appropriately display the sections and rows, and will take action when a row is tapped.</p>
<p>The code to be executed when an item is tapped are written as a block.
When the block is invoked, a <code>KJMenuItemInvocation</code> structure is passed to it. This structure
contains pointers to the menu item, cell, and controller, so there is no reason for the block
to retain any of these objects itself. (Beware of retain cycles if the block <em>does</em> reference
the menu item, cell, or controller.)</p>
<!--break-->
<h2>Usage</h2>
<p>Someday, this will be a proper library, but right now the library is distributed as a demo
application that contains reusable classes.</p>
<p>To make use of the reusable classes, copy the following source files from the demo project into
your own application:</p>
<ul>
<li><code>KJMenuTableViewController.h</code> and <code>KJMenuTableViewController.m</code></li>
<li><code>KJMenuSection.h</code> and <code>KJMenuSection.m</code></li>
<li><code>KJMenuItem.h</code> and <code>KJMenuItem.m</code></li>
</ul>
<h2>Example</h2>
<p>In this example snippet, the controller is a subclass of <code>KJMenuTableViewController</code>, which is itself
a subclass of <code>UITableViewController</code>. In the <code>viewDidLoad</code> method, we add a first section with
two items, each of which displays its text in an alert box, and a second section that has
an item that pushes a new controller onto the navigation stack.</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">viewDidLoad</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">viewDidLoad</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Add first section</span>
<span class="w"> </span><span class="n">KJMenuSection</span><span class="w"> </span><span class="o">*</span><span class="n">section</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">KJMenuSection</span><span class="w"> </span><span class="n">sectionWithHeaderTitle</span><span class="o">:</span><span class="s">@"First Section"</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">section</span><span class="p">.</span><span class="n">footerTitle</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">@"Select item above to display alert"</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">addSection</span><span class="o">:</span><span class="n">section</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">KJMenuItem</span><span class="w"> </span><span class="o">*</span><span class="n">item</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">item</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">KJMenuItem</span><span class="w"> </span><span class="n">itemWithTitle</span><span class="o">:</span><span class="s">@"First"</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">item</span><span class="p">.</span><span class="n">detailText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">@"This is the first item"</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">item</span><span class="p">.</span><span class="n">block</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">^</span><span class="p">(</span><span class="n">KJMenuItemInvocation</span><span class="w"> </span><span class="n">inv</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">RootViewController</span><span class="w"> </span><span class="o">*</span><span class="n">controller</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">RootViewController</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="n">inv</span><span class="p">.</span><span class="n">controller</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="n">title</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">inv</span><span class="p">.</span><span class="n">item</span><span class="p">.</span><span class="n">titleText</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="n">message</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">inv</span><span class="p">.</span><span class="n">item</span><span class="p">.</span><span class="n">detailText</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">controller</span><span class="w"> </span><span class="n">displayAlertWithTitle</span><span class="o">:</span><span class="n">title</span><span class="w"> </span><span class="n">message</span><span class="o">:</span><span class="n">message</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">section</span><span class="w"> </span><span class="n">addItem</span><span class="o">:</span><span class="n">item</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">item</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">KJMenuItem</span><span class="w"> </span><span class="n">itemWithTitle</span><span class="o">:</span><span class="s">@"Second"</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">item</span><span class="p">.</span><span class="n">detailText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">@"This is the second item"</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">item</span><span class="p">.</span><span class="n">block</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">^</span><span class="p">(</span><span class="n">KJMenuItemInvocation</span><span class="w"> </span><span class="n">inv</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">RootViewController</span><span class="w"> </span><span class="o">*</span><span class="n">controller</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">RootViewController</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="n">inv</span><span class="p">.</span><span class="n">controller</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="n">title</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">inv</span><span class="p">.</span><span class="n">item</span><span class="p">.</span><span class="n">titleText</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="n">message</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">inv</span><span class="p">.</span><span class="n">item</span><span class="p">.</span><span class="n">detailText</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">controller</span><span class="w"> </span><span class="n">displayAlertWithTitle</span><span class="o">:</span><span class="n">title</span><span class="w"> </span><span class="n">message</span><span class="o">:</span><span class="n">message</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">section</span><span class="w"> </span><span class="n">addItem</span><span class="o">:</span><span class="n">item</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Add second section</span>
<span class="w"> </span><span class="n">section</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">KJMenuSection</span><span class="w"> </span><span class="n">sectionWithHeaderTitle</span><span class="o">:</span><span class="s">@"Second Section"</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">addSection</span><span class="o">:</span><span class="n">section</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">item</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">KJMenuItem</span><span class="w"> </span><span class="n">itemWithTitle</span><span class="o">:</span><span class="s">@"Push view"</span><span class="w"></span>
<span class="w"> </span><span class="nl">accessoryType</span><span class="p">:</span><span class="n">UITableViewCellAccessoryDisclosureIndicator</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">item</span><span class="p">.</span><span class="n">autoDeselectAfterSelect</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">NO</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">item</span><span class="p">.</span><span class="n">block</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">^</span><span class="p">(</span><span class="n">KJMenuItemInvocation</span><span class="w"> </span><span class="n">inv</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">MyViewController</span><span class="w"> </span><span class="o">*</span><span class="n">subcontroller</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="n">MyViewController</span><span class="w"> </span><span class="n">alloc</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="nl">initWithNibName</span><span class="p">:</span><span class="s">@"MyViewController"</span><span class="w"> </span><span class="n">bundle</span><span class="o">:</span><span class="nb">nil</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">inv</span><span class="p">.</span><span class="n">controller</span><span class="p">.</span><span class="n">navigationController</span><span class="w"> </span><span class="n">pushViewController</span><span class="o">:</span><span class="n">subcontroller</span><span class="w"> </span><span class="n">animated</span><span class="o">:</span><span class="nb">YES</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">section</span><span class="w"> </span><span class="n">addItem</span><span class="o">:</span><span class="n">item</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>For a complete example, see the demo application's <a href="https://github.com/kristopherjohnson/KJMenuTableViewController/blob/master/KJMenuTableViewController/RootViewController.m">RootViewController.m</a>.</p>
<h2>Future Directions</h2>
<p>The following features are planned:</p>
<ul>
<li>Add convenience methods to reduce the verbosity of setting up a menu.</li>
<li>Make it possible to add/remove menu items and change their attributes after menu has already been displayed. (As-is, you need to call the table view's <code>reloadData</code> method if you change anything after <code>viewDidLoad</code>.)</li>
<li>Provide a mechanism so that only one item within a section has a checkmark, and when user selects another item the originally checked item is unchecked.</li>
<li>Provide the ability to define a menu hierarchy that is handled by a single view controller.</li>
<li>Add support for compilation with ARC enabled</li>
</ul>Beware the Lure of the iOS UIWebView2011-07-26T00:32:03-04:002011-07-26T00:32:03-04:00Kristopher Johnsontag:undefinedvalue.com,2011-07-26:/beware-lure-ios-uiwebview.html<p>Apple's iOS SDK provides a class, <a href="http://developer.apple.com/library/ios/#documentation/uikit/reference/UIWebView_Class/Reference/Reference.html">UIWebView</a>, that provides a simple way to display HTML content in an iOS application. Many apps use UIWebView to display web pages, online help, and other formatted content.</p>
<p>For the basic purpose of displaying HTML content, it works pretty well. However, as it appears …</p><p>Apple's iOS SDK provides a class, <a href="http://developer.apple.com/library/ios/#documentation/uikit/reference/UIWebView_Class/Reference/Reference.html">UIWebView</a>, that provides a simple way to display HTML content in an iOS application. Many apps use UIWebView to display web pages, online help, and other formatted content.</p>
<p>For the basic purpose of displaying HTML content, it works pretty well. However, as it appears to just be a wrapper around WebKit, one might be tempted to use it to try to implement a full-fledged web browser embedded in an application. <em>Don't do this!</em> UIWebView has many limitations that make it unsuitable for this purpose:</p>
<ul>
<li>Many commonly used JavaScript functions, such as <code>alert()</code> and <code>window.open()</code>, don't work at all or only work in limited ways in a UIWebView. So many of the web sites one would try to visit do not work in a UIWebView.</li>
<li>There are limited hooks for customizing the behavior. There are a few delegate methods that notify your app when the web view starts loading or finishes loading, but you can't detect many of the events you'd really want to detect.</li>
<li>UIWebView does not send the same browser identification info that Mobile Safari does, so some servers will treat it as an unknown browser and return limited content.</li>
</ul>
<p>Of course, some intrepid developers have found ingenious ways to work around some of these limitations. If you really want to try it, or if you are curious about what kinds of hackery are needed to use UIWebView as a web browser, check out these links:</p>
<ul>
<li><a href="http://www.icab.de/blog/2009/07/27/webkit-on-the-iphone-part-1/">WebKit on the iPhone, part 1</a> and <a href="http://www.icab.de/blog/2009/08/05/webkit-on-the-iphone-part-2/">part 2</a></li>
<li><a href="http://www.codingventures.com/2008/12/using-uiwebview-to-render-svg-files/">7 tips for using UIWebView</a></li>
</ul>
<p>The lesson I learned (after several hours of banging my head against the wall) is to pass web URLs over to Safari, rather than trying to display them in a UIWebView within my app. It's just not the right tool for this job.</p>
<hr>
<p><strong>Update (2011/10/1):</strong> My original post included this bullet point:</p>
<ul>
<li>You can't control authentication. UIWebView can open an HTTPS connection, but if the server-side certificate is self-signed, there is no way to get it to ignore the certificate, and so it just fails with an error message.</li>
</ul>
<p>I've been informed that this is not entirely true. It is possible to somehow "preconnect" to the server with an NSConnection, deal with authentication, and then pass the credentials to the UIWebView. More information is available here: http://stackoverflow.com/questions/11573164/uiwebview-to-view-self-signed-websites-no-private-api-not-nsurlconnection-i/15074358#15074358</p>My JavaScript Cheatsheet2011-06-16T18:49:43-04:002011-06-16T18:49:43-04:00Kristopher Johnsontag:undefinedvalue.com,2011-06-16:/my-javascript-cheatsheet.html<p>A couple of times per year, I have to work on something that requires me to write some <a href="http://en.wikipedia.org/wiki/JavaScript">JavaScript</a>. When I do, I have to reacquaint myself with the language by skimming through <a href="https://undefinedvalue.com/2009/11/29/javascript-good-parts"><em>JavaScript: The Good Parts</em></a> and finding some good online reference documentation.</p>
<p>In an effort to reduce the …</p><p>A couple of times per year, I have to work on something that requires me to write some <a href="http://en.wikipedia.org/wiki/JavaScript">JavaScript</a>. When I do, I have to reacquaint myself with the language by skimming through <a href="https://undefinedvalue.com/2009/11/29/javascript-good-parts"><em>JavaScript: The Good Parts</em></a> and finding some good online reference documentation.</p>
<p>In an effort to reduce the time needed to do this next time, I'm recording the little things that I ran across that I didn't remember or wished I could have found faster. So this is my own personal refresher for JavaScript. It may not help you at all.</p>
<p>Also see my <a href="https://undefinedvalue.com/2012/11/20/nodejs-cheatsheet">Node.js Cheatsheet</a>.</p>
<h2>Reference Links</h2>
<ul>
<li><a href="http://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf">ECMAScript Language Specification, 3rd edition (PDF)</a></li>
<li><a href="http://docs.webplatform.org/wiki/javascript">webplatform.org: JavaScript</a></li>
<li>Mozilla Developer Network</li>
<li><a href="https://developer.mozilla.org/en/JavaScript/Reference">JavaScript Reference</a></li>
<li><a href="https://developer.mozilla.org/en/JavaScript/Guide">JavaScript Guide</a></li>
<li><a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array">Array</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Boolean">Boolean</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date">Date</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error">Error</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function">Function</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math">Math</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Number">Number</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object">Object</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/RegExp">RegExp</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String">String</a></li>
<li>Douglas Crockford: <a href="http://javascript.crockford.com/code.html">Code Conventions</a>, <a href="http://javascript.crockford.com/private.html">Private Members</a></li>
<li><a href="http://addyosmani.com/resources/essentialjsdesignpatterns/book/">Essential JavaScript Patterns for Beginners</a></li>
<li><a href="http://docs.jquery.com/Main_Page">jQuery Docs</a>, <a href="http://docs.jquery.com/Tutorials:Getting_Started_with_jQuery">Getting Started with jQuery</a>, <a href="https://undefinedvalue.com/2009/12/06/quick-and-dirty-guide-qunit">Quick and Dirty Guide to QUnit</a></li>
<li><a href="http://msdn.microsoft.com/en-us/library/bb397536.aspx">Microsoft Ajax Library Client Reference</a></li>
<li><a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/">DOM Level 1</a></li>
<li><a href="http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html">DOM Level 2 Core</a>, <a href="http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html">DOM Level 2 Events</a>, <a href="http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/html.html">DOM Level 2 HTML</a></li>
<li><a href="http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html">DOM Level 3 Core</a>, <a href="http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html">DOM Level 3 XPath</a></li>
<li><a href="http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/WebKitDOMRef/index.html#//apple_ref/doc/uid/TP40006089">WebKit DOM Reference</a>, <a href="http://developer.apple.com/library/safari/#documentation/AppleApplications/Conceptual/SafariJSProgTopics/WebKitJavaScript.html#//apple_ref/doc/uid/TP40001483">WebKit DOM Programming Topics</a>, <a href="http://developer.apple.com/library/safari/#documentation/ScriptingAutomation/Conceptual/JSCodingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40006088">Apple JavaScript Coding Guidelines</a></li>
<li><a href="https://developer.mozilla.org/en/Gecko_DOM_Reference">Gecko DOM Reference</a></li>
<li><a href="http://www.w3.org/TR/XMLHttpRequest/">XMLHttpRequest</a></li>
<li><a href="http://dev.w3.org/geo/api/spec-source.html">Geolocation API Specification</a></li>
</ul>
<!--break-->
<h2>Gotchas</h2>
<p>Generally, use <tt>===</tt> and <tt>!==</tt> rather than <tt>==</tt> and <tt>!=</tt> (which coerce arguments before comparison).</p>
<p>Numeric values are really IEEE floating-point, even when they look like integers.</p>
<p><tt>NaN</tt> is not equal to itself. Use <tt>isNaN()</tt></p>
<p>Semicolons are often optional, but interpreter may insert semicolons in unexpected places, so it is best to use semicolons and to put opening braces on the same line as the keyword.</p>
<p>If <code>var</code> declaration is missing, then a variable is global. The scope of a <code>var</code> is the entire function in which it is declared (blocks do not create a new scope)</p>
<p>Note difference between <code>substr(startIndex, charCount)</code> and <code>substring(startIndex, endIndex)</code>.</p>
<p>Avoid using <tt>/<em> </em>/</tt> style comments, as regular expressions can contain similar character sequences.</p>
<h2>Special Values and Constants</h2>
<pre>
null, undefined, false, true, NaN, Infinity
</pre>
<p>Values that are considered to be "false" in conditionals: <tt>undefined, null, false, '', 0, NaN</tt></p>
<p><tt>typeof obj</tt> returns one of these values: <tt>'undefined', 'object', 'boolean', 'number', 'string', 'function'</tt>. If the operand is an array or <tt>null</tt>, then the result is <tt>'object'</tt>. If the operand is a regexp, the result may be either <tt>'object'</tt> or <tt>'function'</tt>.</p>
<h2>Standard Methods</h2>
<ul>
<li>array.<strong>concat</strong>(item...)</li>
<li>array.<strong>join</strong>(separator)</li>
<li>array.<strong>pop</strong>()</li>
<li>array.<strong>push</strong>(item...)</li>
<li>array.<strong>reverse</strong>()</li>
<li>array.<strong>shift</strong>()</li>
<li>array.<strong>slice</strong>(start, end)</li>
<li>array.<strong>sort</strong>(comparefn)</li>
<li>array.<strong>splice</strong>(start, deleteCount, item...)</li>
<li>array.<strong>unshift</strong>(item...)</li>
<li>function.<strong>apply</strong>(thisArg, argArray)</li>
<li>number.<strong>toExponential</strong>(fractionDigits)</li>
<li>number.<strong>toFixed</strong>(fractionDigits)</li>
<li>number.<strong>toPrecision</strong>(precision)</li>
<li>number.<strong>toString</strong>(radix)</li>
<li>object.<strong>hasOwnProperty</strong>(name)</li>
<li>regexp.<strong>exec</strong>(string)</li>
<li>regexp.<strong>test</strong>(string)</li>
<li>string.<strong>charAt</strong>(pos)</li>
<li>string.<strong>charCodeAt</strong>(pos)</li>
<li>string.<strong>concat</strong>(string...)</li>
<li>string.<strong>indexOf</strong>(searchString, position)</li>
<li>string.<strong>lastIndexOf</strong>(searchString, position)</li>
<li>string.<strong>localeCompare</strong>(that)</li>
<li>string.<strong>match</strong>(regexp)</li>
<li>string.<strong>replace</strong>(searchValue, replaceValue)</li>
<li>string.<strong>search</strong>(regexp)</li>
<li>string.<strong>slice</strong>(start, end)</li>
<li>string.<strong>split</strong>(separator, limit)</li>
<li>string.<strong>substring</strong>(start, end)</li>
<li>string.<strong>toLocaleLowerCase</strong>()</li>
<li>string.<strong>toLocaleUpperCase</strong>()</li>
<li>string.<strong>toLowerCase</strong>()</li>
<li>string.<strong>toUpperCase</strong>()</li>
<li>String.<strong>fromCharCode</strong>(char...)</li>
</ul>
<h2>Syntax Examples and Snippets</h2>
<script src="https://gist.github.com/4046616.js?file=cheatsheet.js"></script>(268242) Pebble2011-05-23T00:12:11-04:002011-05-23T00:12:11-04:00Kristopher Johnsontag:undefinedvalue.com,2011-05-23:/268242-pebble.html<p>My wife has had a <a href="http://en.wikipedia.org/wiki/Minor_planet">minor planet</a> named after her. This is the official naming citation, written by the planet's discoverer, Jim Bedient:</p>
<blockquote>
(268242) Pebble = 2005 JW1
Discovered 2005 May 4 by J. Bedient at Haleakala-Faulkes Telescope North.
Pebble Johnson (b. 19xx) is an innovative teacher of middle-school science and …</blockquote><p>My wife has had a <a href="http://en.wikipedia.org/wiki/Minor_planet">minor planet</a> named after her. This is the official naming citation, written by the planet's discoverer, Jim Bedient:</p>
<blockquote>
(268242) Pebble = 2005 JW1
Discovered 2005 May 4 by J. Bedient at Haleakala-Faulkes Telescope North.
Pebble Johnson (b. 19xx) is an innovative teacher of middle-school science and technology in Forsyth County, Georgia. She uses astronomy to heighten interest and excitement in the physical sciences among her students.
</blockquote>
<p>The naming citation was published in MPC # 75106:
http://www.minorplanetcenter.net/iau/ECS/MPCArchive/2011/MPC_20110517.pdf</p>
<p>Jim also wrote the following in an email::</p>
<blockquote>
On May 4, 2005, I was observing with the 2.2m Faulkes Telescope-North on Haleakala, Maui. While observing an asteroid on the NEO list, I observed another object in the background. I performed astrometric observations on succeeding nights, and it received the provisional designation 2005 JW1. After following it for several years, its orbit has been accurately determined, and a few months ago it received its permanent designation, (268242). Under International Astronomical Union rules, this made it eligible for naming, and of the privileges of discovering a minor planet is naming it. A couple of months ago I submitted a name proposal to the IAU's Working Group for Small Body Nomenclature. Last week I was informed that the proposed name had been approved, and it is hereafter known as (268242) Pebble, in honor of my good friend Dr. Pebble Johnson.
...
With the boom in minor planet discoveries in the last decade, due the advent of more sophisticated techniques, particularly larger telescopes and electronic detectors, naming citations are required to be considerably briefer than they once were allowed to be. I really couldn't wax poetic about meeting Pebble ten years ago at Dr. Karen Meech's TOPS workshops in Hawaii, how she became a very dear friend, how I helped with her Ph.D thesis, and all the things, small and large, that go into forming a strong friendship. Placing her name in perpetuity on this small piece of solar system real estate is just an insignificant monument to a person with a very large heart. I am honored to know her and to be able to make this gesture.
</blockquote>Do We Still Need Programmers?2011-04-12T12:16:47-04:002011-04-12T12:16:47-04:00Kristopher Johnsontag:undefinedvalue.com,2011-04-12:/do-we-still-need-programmers.html<p>When reading descriptions of how software is produced, I often wonder what role programmers play. Programmers used to be the people who made software, but now a lot of other people are involved and claim credit for doing the work.</p>
<p>There has to be an "architect" who guides the overall …</p><p>When reading descriptions of how software is produced, I often wonder what role programmers play. Programmers used to be the people who made software, but now a lot of other people are involved and claim credit for doing the work.</p>
<p>There has to be an "architect" who guides the overall structure of the system. Often architects are former programmers, but they are far too important to write any code anymore. </p>
<p>Then there is the "user experience" designer, who decides how the software should interact with its users. We used to call this "user interface design", but the UX people have redefined UI design to be the monkeywork of laying out controls and fields on forms. Programmers can't be trusted with any important design decisions. </p>
<p>There is the database administrator (DBA), who ensures that the programmers can't screw up the database schema. </p>
<p>There are testers who tell the programmers whether they have done their jobs well enough. </p>
<p>With all these other people making the important decisions, what does a programmer do? Apparently programmers are glorified typists who transcribe specifications written by architects and UX designers into code that is verified by testers.</p>
<p>Of course, that's not how things are.</p>
<p>While there are teams and organizations that operate as described above, a lot of software is created by solitary programmers. The boss says "We need feature X," and a programmer then designs the user experience, updates the database schema, implements the new functionality, tests it, creates some new icons in Photoshop, adds a couple of pages to the user manual, and deploys the new software to the customer. </p>
<p>Somehow a programmer is able to do this without help from all those people who are too important to write code.</p>
<p>Those other people aren't useless. Software is certainly better when a good architect and good DBA maintain its structure, and when a talented UX designer makes it easy to use, and when good testers find the problems. But software is still made by programmers. Those other people don't make software—they make wishes.</p>UTF-82011-04-06T14:48:23-04:002011-04-06T14:48:23-04:00Kristopher Johnsontag:undefinedvalue.com,2011-04-06:/utf-8.html<p>Matt Gallagher's <a href="http://cocoawithlove.com/2011/04/user-interface-strings-in-cocoa.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+CocoaWithLove+%28Cocoa+with+Love%29">"User interface strings in Cocoa"</a> post is good for its overall purpose (telling people how to use <code>NSLocalizedString()</code>), but I especially like this little embedded rant:</p>
<blockquote>
<p><strong>A quick swipe at almost everybody:</strong> UTF-8 has been around since 1993 and Unicode 2.0 since 1996; if you have created …</p></blockquote><p>Matt Gallagher's <a href="http://cocoawithlove.com/2011/04/user-interface-strings-in-cocoa.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+CocoaWithLove+%28Cocoa+with+Love%29">"User interface strings in Cocoa"</a> post is good for its overall purpose (telling people how to use <code>NSLocalizedString()</code>), but I especially like this little embedded rant:</p>
<blockquote>
<p><strong>A quick swipe at almost everybody:</strong> UTF-8 has been around since 1993 and Unicode 2.0 since 1996; if you have created any 8-bit character content since 1996 in anything other than UTF-8, then I hate you.<br><br>I weep to think of the years of programmer time that are still wasted attempting to support non-Unicode formats without characters getting garbled because people are still creating content using ancient encodings without useful identifiers to indicate what nonsense encoding they're using (or worse, people creating content that explicitly uses the wrong encoding for an encoding-specific text field).<br><br>MacRoman? Atrocious. Big-5? I hope you want to see garbage output. Windows Latin? You suck. If you're creating new content using anything other than UTF-8, UTF-16 or UTF-32 then you should be forced to serve prison time with whatever idiot monkey decided that UTF-16 should be allowed little-endian and big-endian variants instead of a single authoritative encoding.</p>
</blockquote>
<p>Yeah, seriously. If you call yourself a programmer, but you don't understand what all this Unicode, UTF-8, and UTF-16 business is about, please read Joel Spolsky's <a href="http://www.joelonsoftware.com/articles/Unicode.html">The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)</a>.</p>The Apple Store Needs a Checkout Counter2011-03-31T09:49:18-04:002011-03-31T09:49:18-04:00Kristopher Johnsontag:undefinedvalue.com,2011-03-31:/apple-store-needs-checkout-counter.html<p>The Apple Store is a great place to go if you want to play with new Apple products or get help at the Genius Bar. But if you go there to <em>buy something</em>, the experience is confusing and humiliating.</p>
<p>Everyone knows how the process of buying something at a store …</p><p>The Apple Store is a great place to go if you want to play with new Apple products or get help at the Genius Bar. But if you go there to <em>buy something</em>, the experience is confusing and humiliating.</p>
<p>Everyone knows how the process of buying something at a store is supposed to work:</p>
<ol>
<li>You pick up the product(s) you want to buy.</li>
<li>You go to the end of the line at the checkout counter.</li>
<li>When it's your turn, you step up to the counter, transact your business, and go on your way.</li>
</ol>
<p>In contrast, here is how things work at the Apple Store:</p>
<ol>
<li>You pick up the product(s) you want to buy.</li>
<li>You stand there like a child who has lost his mommy, looking around for a friendly Apple associate who can help you.</li>
<li>As the Apple associates are always busy helping other customers, you pick one and stand nearby, hoping to catch their attention when they are finished with the current customer.</li>
<li>Eventually, after helping the current customer, and maybe a few others who are also standing nearby, the associate asks how they can help you. You say you want to buy the things you have been holding. The associate says, "OK, I'll find someone to help you", and goes to look for one of the associates who has one of the magic credit-card-reading devices. You stand there looking lost again for a while.</li>
<li>Eventually, a person with a magic device arrives to let you make your purchase. You fumble around, juggling products between your armpits, your crotch, and under your chin while you present each item for scanning. Then you drop all the items while you dig out your wallet and credit card.</li>
<li>While you pick up the items you have dropped, the person swipes your card, then hands the little device back to you so you can check some boxes and sign your name. Again, your items have to go into your armpits and onto the floor.</li>
<li>While you pick up your items again, the person goes to one corner of the store to get your printed receipt, then to the opposite corner of the store to get you a bag, then back to you. You help the associate put your items into the bag, and then you go on your way.</li>
</ol>
<p>Wouldn't this all be easier if there was a checkout counter?</p>
<p>Sometimes the old ways are best.</p>What I've Learned about iOS Development2011-03-29T03:43:00-04:002011-03-29T03:43:00-04:00Kristopher Johnsontag:undefinedvalue.com,2011-03-29:/what-ive-learned-about-ios-development.html<p>I've been playing around with development for Mac OS X and iOS for a few years. I've had a pretty good grasp of how Cocoa and UIKit worked, and I've written some simple apps, but for the past month I've been working on my first <em>Real iOS Application</em>. I've had …</p><p>I've been playing around with development for Mac OS X and iOS for a few years. I've had a pretty good grasp of how Cocoa and UIKit worked, and I've written some simple apps, but for the past month I've been working on my first <em>Real iOS Application</em>. I've had to solve some problems that were easily ignored when writing little apps for fun. What follows is a randomly ordered collection of some of the little techniques and tips I didn't know before, which may be useful for other Cocoa newbies.</p>
<!--break-->
<p>(More to come as I learn from more mistakes.)</p>
<h2>NSDictionary is Incredibly Useful</h2>
<p>As a long-time C++ programmer, whenever I have some sort of "object" with a bunch of "attributes" my instinct is to define a <code>class</code> or <code>struct</code> with a bunch of instance members. However, when using Objective-C, it is often easier and more useful to just throw all that stuff into an <code>NSDictionary</code> (or <code>NSMutableDictionary</code>), for the following reasons:</p>
<ul>
<li>You don't have to write code to save/load the data to/from files or <code>NSUserDefaults</code>. As long as you can live with <code>NSDictionary</code>'s serialization format, you get persistence for free.</li>
<li>Many system classes and APIs already know how to work with dictionaries in interesting ways.</li>
<li>It's easier than defining an Objective-C class with properties. I know some Objective-C fans will disagree, but the <code>@property</code> mechanism sucks. If you use an <code>NSDictionary</code>, then you can worry less about retain counts, <code>dealloc</code>, and other such things.</li>
<li>It lets you write JavaScript-like or Python-like code, if you're into that sort of thing.</li>
</ul>
<p>The downside of using <code>NSDictionary</code> is that it may be less efficient than using a custom class or a <code>struct</code>. Use your brain.</p>
<h2>Use NSValue to Box Pointers and Primitives</h2>
<p>If you follow the above advice about using <code>NSDictionary</code>, you'll run into a couple of wrinkles with using it as a general-purpose attribute container:</p>
<ul>
<li>Keys and values have to be objects, not primitive types.</li>
<li>The keys have to conform to the <code>NSCopying</code> protocol.</li>
<li>The dictionary will retain values, which may introduce undesirable retain cycles.</li>
</ul>
<p>The workaround for these issues is to use the <code>NSValue</code> class and its subclasses, which can box most interesting values as objects that can be used as dictionary keys and values.</p>
<p><code>+[NSValue valueWithNonretainedObject:]</code> is how you put objects into your collections without bumping the retain counts.</p>
<h2>Use objc_setAssociatedObject() and objc_getAssociatedObject() to Extend Unsubclassable Classes</h2>
<p>The Apple frameworks contain some classes which cannot be subclassed (or cannot be <em>easily</em> subclassed). If you just want to add some methods, then you can just define a category. In cases where you wish you could make a subclass to add a property or two, the workaround is to use <code>objc_setAssociatedObject()</code> and <code>objc_getAssociatedObject()</code>.</p>
<h2>Don't Support Old iOS Versions If You Don't Absolutely Have To</h2>
<p>I'm lucky in that I am developing an in-house application, not to be sold in the App Store, so I was able to decree that I would only support devices running iOS 4.2 and higher. This lets me take advantage of useful stuff that isn't available in pre-4.0 versions of iOS without cluttering the code with a lot of checks for feature support. It's nice to use blocks and regular expressions and other "new" iOS features without worrying about it.</p>
<p>Even if I was developing for the App Store, I'd be tempted to require 4.0. People who haven't bought a new phone for a few years or who don't keep the OS up to date aren't going to pay for that app you're developing, so why do anything for them?</p>
<h2>Don't Mix Objective-C and C++ Unnecessarily</h2>
<p>For a while, I was enamored with "Objective-C++", which is what you get when you change your <code>.m</code> suffixes to <code>.mm</code> and put C++ code into your Objective-C code. I thought this would be nifty, because I could use cool stuff from C++ (templates, typesafe container classes, smart pointers, RAII, ...).</p>
<p>I did this for a while, but found that every time I decided to rewrite the C++ parts in plain-old-idiomatic Objective-C, the result was cleaner. So I don't do it any more.</p>
<p>I think use of Objective-C++ is best limited to cases where you need to integrate some existing C++ code or libraries into your app, or when the guts of the app are all written in C++ and you need to interface with Objective-C-based UI classes. Don't use it to "improve" Objective-C. Objective-C is already a clumsy combination of C and Smalltalk. Dumping a third language into the mix doesn't make it better.</p>
<h2>Use the Static Analyzer</h2>
<p>Objective-C's manual memory management sucks, but I have yet to see a memory leak in my app that wasn't caught by the static analyzer. Run that Analyze command once in a while.</p>
<h2>You Don't Have to Use Interface Builder</h2>
<p>I spent a long time struggling with figuring out how to do complicated things in Interface Builder (like laying out a tab bar with a set of navigation controllers, each with toolbar items in their button bars and ...). After all, this is how one creates GUIs on other platforms.</p>
<p>IB can be helpful, particularly when laying out a form-style view, but in many cases it is easier to create your views and hook up the events by writing code.</p>
<p>I'm not saying "Don't use Interface Builder," or "Interface Builder is for pussies." I'm just saying that if you know how to do it all in code, it will often be easier to get what you want.</p>
<h2>Use UIWebView for Read-Only Views</h2>
<p>For a view that simply displays information, it is often easier to write some simple HTML and CSS and display it in a UIWebView than it is to lay out a bunch of UIKit components. It also gives your non-Objective-C-knowing colleagues a way to help you out (but don't let them use Arial).</p>
<h2>Use Xcode 4</h2>
<p>At the time of this writing, Xcode 4 is new (released just a few weeks ago), and lots of people are having problems with it. Those people recommend sticking with Xcode 3.x until the problems get worked out.</p>
<p>If you run into those problems, or if you are an old-timer who fears change, then by all means, use Xcode 3. But if you can use Xcode 4, then I heartily recommend it. The improved autocompletion alone is worth the risk of crashes and hiccups.</p>
<p>Xcode 4 is pretty rough for a released Apple product, but iIt really isn't that bad. When I first started using, it seemed like it was crashing "all the time." I started recording each crash, and found that over the course of three weeks it crashed, on average, only about once per day. The crashes are annoying, but the effect on my productivity was neglible. YMMV.</p>
<h2>Use Ingredients</h2>
<p>The Xcode documentation viewer sucks. Install <a href="http://fileability.net/ingredients/">Ingredients</a>. It's free. Assign a keyboard shortcut to the <em>Look up in Ingredients</em> item in the Services menu.</p>
<h2>Use TBXML for XML Parsing</h2>
<p>I experimented with a few XML parsers, and settled on <a href="http://www.tbxml.co.uk/TBXML/TBXML_Free.html">TBXML</a> as my preferred parser. It's got the best combination of performance and ease of use.</p>
<p>However, it is not necessarily the best for all purposes. See <a href="http://www.raywenderlich.com/553/how-to-chose-the-best-xml-parser-for-your-iphone-project">How to Choose the Best XML Parser for Your iPhone Project</a> for a survey of available XML parsers.</p>App Idea: Prose Translation Assistant2011-03-07T02:03:21-05:002011-03-07T02:03:21-05:00Kristopher Johnsontag:undefinedvalue.com,2011-03-07:/app-idea-prose-translation-assistant.html<p>(This is just an <em>idea</em>. As I explain my post about <a href="https://undefinedvalue.com/2011/02/26/why-i-loved-social-network">Why I Loved The Social Network</a>, I think ideas are cheap, so if you want to "steal" this idea and make the app, I heartily support you.)</p>
<p>A friend has started a personal project to translate the works of …</p><p>(This is just an <em>idea</em>. As I explain my post about <a href="https://undefinedvalue.com/2011/02/26/why-i-loved-social-network">Why I Loved The Social Network</a>, I think ideas are cheap, so if you want to "steal" this idea and make the app, I heartily support you.)</p>
<p>A friend has started a personal project to translate the works of Jules Verne from the original French into English, as he is dissatisfied with the existing English translations. He is going about it pretty much the way I would: he has a browser window open with the original French text, a browser window with Google Translate, and a text editor where he is writing the English translation. He also has a French-English dictionary on hand.</p>
<p>I wondered whether there might be some software available that is specifically designed for this purpose. Some Googling finds plenty of applications to assist in translation, but all the ones I found are designed to help translate conversations, e-mails, or other such things. I couldn't find anything designed for assisting with translating a novel, play, or other such work, where the translation needs to be written by a human in an accurate-but-artful way.</p>
<p>It got me thinking about how I would design an application to assist with this process, making it less necessary to switch between various applications and documents.</p>
<!--break-->
<p>The basic presentation in my mind is a table with three columns: the first column contains the original text, the second column contains the text translated into the target language, and the third column is intended to hold notes. There would be a button and simple keyboard shortcut for showing/hiding the third column.</p>
<p>There would be one paragraph per row. So, the main window would look something like this:</p>
<table border="1">
<tr>
<th> P# </th>
<th>French</th>
<th>English</th>
<th>Notes</th>
</tr>
<tr>
<td>1</td>
<td>
L'année 1866 fut marquée par un événement bizarre, un phénomène inexpliqué et inexplicable que personne n'a sans doute oublié. Sans parler des rumeurs qui agitaient les populations des ports et surexcitaient l'esprit public à l'intérieur des continents les gens de mer furent particulièrement émus. Les négociants, armateurs, capitaines de navires, skippers et masters de l'Europe et de l'Amérique, officiers des marines militaires de tous pays, et, après eux, les gouvernements des divers États des deux continents, se préoccupèrent de ce fait au plus haut point.
</td>
<td>
The year 1866 was marked by a bizarre development, an unexplained and downright inexplicable phenomenon that surely no one has forgotten. Without getting into those rumors that upset civilians in the seaports and deranged the public mind even far inland, it must be said that professional seamen were especially alarmed. Traders, shipowners, captains of vessels, skippers, and master mariners from Europe and America, naval officers from every country, and at their heels the various national governments on these two continents, were all extremely disturbed by the business.
</td>
<td>
Does the English word "downright" in the first sentence capture the correct meaning?
</td>
</tr>
<td>2</td>
<td>
En effet, depuis quelque temps, plusieurs navires s'étaient rencontrés sur mer avec « une chose énorme » un objet long, fusiforme, parfois phosphorescent, infiniment plus vaste et plus rapide qu'une baleine.
</td>
<td>
In essence, over a period of time several ships had encountered "an enormous thing" at sea, a long spindle–shaped object, sometimes giving off a phosphorescent glow, infinitely bigger and faster than any whale.
</td>
<td>
</td>
</tr>
<tr>
<td>3</td>
<td>
Les faits relatifs à cette apparition, consignés aux divers livres de bord, s'accordaient assez exactement sur la structure de l'objet ou de l'être en question, la vitesse inouïe de ses mouvements, la puissance surprenante de sa locomotion, la vie particulière dont il semblait doué. Si c'était un cétacé, il surpassait en volume tous ceux que la science avait classés jusqu'alors. Ni Cuvier, ni Lacépède, ni M. Dumeril, ni M. de Quatrefages n'eussent admis l'existence d'un tel monstre — à moins de l'avoir vu, ce qui s'appelle vu de leurs propres yeux de savants.
</td>
<td>
The facts concerning this apparition, as recorded in various logbooks, agreed pretty accurately the structure of the object or creature in question, the unprecedented speed of its movements, the surprising power of locomotion, life which he seemed particularly gifted. If it was a cetacean, it surpassed in size all that science had previously ordered. Neither Cuvier, Lacepede neither nor Mr. Dumeril nor M. de Quatre had not admitted the existence of such a monster - at least to have seen, which is called saw with their own eyes of scholars.
</td>
<td>
(Automatic translation by Google Translate.)
</td>
</tr>
</table>
<p>There would then be a set of floating auxiliary windows that provide assistance. For example, a Google Translate window would appear which displays translated text. By default, it would display a translation of the current paragraph, but if one or more words were selected, then it would display the translation for only the selected word/phrase/sentence. There would also be a Babelfish window, a window with a dictionary for the target language, and windows for any other sources of translation assistance.</p>
<p>When you create a new translation project, you would import the original-language text, and could also then import a translation, or could allow the application to automatically generate an initial translation using Google Translate or another service.</p>
<p>I am assuming that <em>paragraph</em> is the best chunk size for this kind of work. I think a sentence is too short, as it is likely one would want to split or combine sentences when writing a nicely flowing translation. A chapter is too long to be an atomic unit, but there should be a way to create some sort of outline view to show and hide larger groups of paragraphs. (Are there written languages that don't have a concept analogous to a <em>paragraph</em>?)</p>
<p>Each paragraph can be marked with a color to indicate its status: <em>unedited initial translation</em>, <em>edited but incomplete</em>, <em>needs review</em>, or <em>finished</em>.</p>
<p>There needs to be an easy way to share the project with others, and merge others' work into your own. (Maybe one more column for each additional author?)</p>
<p>I'm not sure whether this should be a desktop app or a web app. A web app would make it easier to share work between people and let people review it, so that's probably the way to go.</p>
<p>Anyway, that's the idea. It's too much work for me to try to tackle, but maybe somebody else wants to try, or can point me to existing examples of this type of product.</p>Why I Loved The Social Network2011-02-27T01:41:21-05:002011-02-27T01:41:21-05:00Kristopher Johnsontag:undefinedvalue.com,2011-02-27:/why-i-loved-social-network.html<p>I'm writing this the night before the Oscars, but that is not why I'm writing. I only saw three of the films nominated for Best Picture: <em>The King's Speech</em>, <em>True Grit</em>, and <em>The Social Network</em>. While I enjoyed <em>The King's Speech</em> and <em>True Grit</em>,, I haven't thought about them since …</p><p>I'm writing this the night before the Oscars, but that is not why I'm writing. I only saw three of the films nominated for Best Picture: <em>The King's Speech</em>, <em>True Grit</em>, and <em>The Social Network</em>. While I enjoyed <em>The King's Speech</em> and <em>True Grit</em>,, I haven't thought about them since I saw them. In contrast, I still think about <em>The Social Network</em> every day.</p>
<p>For some, <em>The Social Network</em> is just a story about how an arrogant jerk became a billionaire by screwing over his friends and business associates. I didn't see it that way. To me, it is a story about the nature of creativity and invention.</p>
<!--break-->
<p>I'll say up front that this entire discussion is about the story and characters in the movie, which don't necessarily match the real-life story. When I talk about <em>Mark Zuckerberg</em>, I'm talking about the character in the movie. I don't care how true the movie is.</p>
<p>The movie does a good job of presenting all sides of the story. To the Winklevosses, the story is that they had a great idea, and Mark Zuckerberg stole it and made a lot of money that was rightfully theirs. In contrast, Zuckerberg doesn't think he stole anything: the Winklevosses had a stupid idea, and he had a better idea, and he was wildly successful in executing that idea.</p>
<p>That's the key to the movie: the difference between having an <em>idea</em>, and actually <em>creating</em> something. I don't think it matters who had the idea. I think the person who creates something deserves almost all the credit for it, regardless of where the idea came from.</p>
<p>Say you're at Starbucks one day, and you overhear a barista saying to another that there ought to be an easier way to control the production of froth in a latte. You go home, spend a few months inventing some new froth-production device, and then you sell that device to Starbucks for a million dollars. How much do you owe that barista you overheard?</p>
<p>My answer: nothing. Everyone has ideas all the time. Lots of people independently have the same ideas. Ideas have little value on their own. What matters is <em>doing something</em> with the ideas you have.</p>
<p>My favorite blurb about this principle is by the author Neil Gaiman, in his essay <a href="http://www.neilgaiman.com/p/Cool_Stuff/Essays/Essays_By_Neil/Where_do_you_get_your_ideas%3F">"Where do you get your ideas?"</a>:</p>
<blockquote>
<p>Every published writer has had it - the people who come up to you and tell you that they've Got An Idea. And boy, is it a Doozy. It's such a Doozy that they want to Cut You In On It. The proposal is always the same - they'll tell you the Idea (the hard bit), you write it down and turn it into a novel (the easy bit), the two of you can split the money fifty-fifty. </p>
</blockquote>
<p>The idea is not the hard bit. The hard bit is making the thing, and then improving the thing, and then throwing the thing away and starting over, and then showing it to people, and being rejected, and keeping at it until the thing is right, even when everyone is telling you that you are doing it all wrong. And then when the thing is right, you don't relax; you start on your next thing. It is better to have lots of pretty good ideas than to have one Great Idea.</p>
<p>I'm a computer programmer, and I felt a lot more sympathy for the Zuckerberg character than most people do. He's a smart guy who made a lot of money by writing some software, and he didn't let others get in the way. He has some problems dealing with people, but his personality defects are similar to my own. When I walked out of that movie, Mark Zuckerberg was my hero.</p>
<p>He's not a perfect hero. He misled the Winklevosses, and he betrayed his best friend. I am neither excusing nor ignoring Zuckerberg's dark side, but there are relevant lessons here too. First, if you can't make your thing yourself, and you are counting on someone else to make the thing for you, you are in a precarious position. Second, be careful about who you let help you.</p>
<p><em>The Social Network</em> sticks in my brain because it keeps reminding me of the importance of <em>making stuff</em>. It's important to daydream, to exchange ideas with others, to learn and hone your craft, and to organize your life to give you the time and energy to work, but what's really important is that <em>you have to work</em>. You don't get points just for being smart.</p>Setting Up for Use of Microsoft Symbol Server2011-02-23T22:13:30-05:002011-02-23T22:13:30-05:00Kristopher Johnsontag:undefinedvalue.com,2011-02-23:/setting-use-microsoft-symbol-server.html<p>When debugging native Win32 code, it is useful to have the debug symbols for all of Microsoft's DLLs. The easiest way to set this up is to just set an environment variable before starting Visual Studio (or other Microsoft debugging tools):</p>
<div class="highlight"><pre><span></span><code><span class="n">set</span><span class="w"> </span><span class="n">_NT_SYMBOL_PATH</span><span class="o">=</span><span class="n">srv</span><span class="o">*</span><span class="n">c</span><span class="p">:</span>\<span class="n">symbols</span><span class="o">*</span><span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">msdl</span><span class="o">.</span><span class="n">microsoft</span><span class="o">.</span><span class="n">com …</span></code></pre></div><p>When debugging native Win32 code, it is useful to have the debug symbols for all of Microsoft's DLLs. The easiest way to set this up is to just set an environment variable before starting Visual Studio (or other Microsoft debugging tools):</p>
<div class="highlight"><pre><span></span><code><span class="n">set</span><span class="w"> </span><span class="n">_NT_SYMBOL_PATH</span><span class="o">=</span><span class="n">srv</span><span class="o">*</span><span class="n">c</span><span class="p">:</span>\<span class="n">symbols</span><span class="o">*</span><span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">msdl</span><span class="o">.</span><span class="n">microsoft</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">download</span><span class="o">/</span><span class="n">symbols</span><span class="w"></span>
</code></pre></div>
<p>The first time you run the debugger after setting this, it will take some time to start as it downloads symbol files from the Internet into your local symbol cache, but it will be faster after that. Whenever you update your system with patches or service packs, the new symbols will automatically be downloaded the next time you debug.</p>
<p>For more information, see these pages:</p>
<ul>
<li>http://support.microsoft.com/kb/311503</li>
<li><a href="http://msdn.microsoft.com/en-us/library/ee416588(VS.85).aspx">http://msdn.microsoft.com/en-us/library/ee416588(VS.85).aspx</a></li>
</ul>
<p>(This post is really for my own benefit. I have trouble finding this information whenever I set up a new development workstation, so I'm putting it somewhere I'll know to look.)</p>Visual Studio 2008 Code Snippet for Inserting a String.Format() call2011-02-20T00:57:51-05:002011-02-20T00:57:51-05:00Kristopher Johnsontag:undefinedvalue.com,2011-02-20:/visual-studio-2008-code-snippet-inserting-stringformat-call.html<p>Over the past year I've been (re-)learning how to use Visual Studio 2008. I did a lot of work in the 90's and early 00's with Visual Studios 5, 6, and 2003, but then I had a few years away from Windows development, and I've had limited experience with …</p><p>Over the past year I've been (re-)learning how to use Visual Studio 2008. I did a lot of work in the 90's and early 00's with Visual Studios 5, 6, and 2003, but then I had a few years away from Windows development, and I've had limited experience with .NET development. So I'm constantly discovering "new things" about Visual Studio that my coworkers already know.</p>
<p>One nifty feature of Visual Studio is "code snippets", which are basically little bits of code that can be quickly inserted into a source file using Intellisense. The inserted code includes placeholders that can be replaced with whatever you need. For example, if you are typing away in a C# source file and type the keyword <code>for</code>, you'll see the Intellsense window pop up with "for" as the selected item, and if you hit the Tab key a couple of times a complete <code>for</code> statement with braces, a loop variable, and everything will be inserted. You can then hit Tab to move to the loop variable name (in case you don't like <code>i</code>) and again to go into the loop body.</p>
<p>(Other IDEs have similar features, calling them <em>abbreviations</em>, <em>macros</em>, etc. You don't need to tell me that Visual Studio isn't magically unique.)</p>
<p>Visual Studio has a whole bunch of these snippets built in, but you can also define your own by writing an XML file and saving it where Visual Studio can find it. For example, here is a snippet I wrote to quickly insert <code>String.Format()</code> expressions:</p>
<div class="highlight"><pre><span></span><code><span class="cp"><?xml version="1.0" encoding="utf-8" ?></span>
<span class="nt"><CodeSnippets</span> <span class="na">xmlns=</span><span class="s">"http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"</span><span class="nt">></span>
<span class="nt"><CodeSnippet</span> <span class="na">Format=</span><span class="s">"1.0.0"</span><span class="nt">></span>
<span class="nt"><Header></span>
<span class="nt"><Title></span>String.Format<span class="nt"></Title></span>
<span class="nt"><Description></span>Creates a String.Format() call<span class="nt"></Description></span>
<span class="nt"><Author></span>Kristopher Johnson<span class="nt"></Author></span>
<span class="nt"><SnippetTypes></span>
<span class="nt"><SnippetType></span>Expansion<span class="nt"></SnippetType></span>
<span class="nt"></SnippetTypes></span>
<span class="nt"><Shortcut></span>sf<span class="nt"></Shortcut></span>
<span class="nt"></Header></span>
<span class="nt"><Snippet></span>
<span class="nt"><Declarations></span>
<span class="nt"><Literal></span>
<span class="nt"><ID></span>format<span class="nt"></ID></span>
<span class="nt"><ToolTip></span>Replace with format string<span class="nt"></ToolTip></span>
<span class="nt"><Default></span>format<span class="nt"></Default></span>
<span class="nt"></Literal></span>
<span class="nt"><Literal></span>
<span class="nt"><ID></span>arguments<span class="nt"></ID></span>
<span class="nt"><ToolTip></span>Replace with arguments<span class="nt"></ToolTip></span>
<span class="nt"><Default></span>arguments<span class="nt"></Default></span>
<span class="nt"></Literal></span>
<span class="nt"></Declarations></span>
<span class="nt"><Code</span> <span class="na">Language=</span><span class="s">"CSharp"</span><span class="nt">></span>
<span class="cp"><![CDATA[String.Format("$format$", $arguments$)$end$]]></span>
<span class="nt"></Code></span>
<span class="nt"></Snippet></span>
<span class="nt"></CodeSnippet></span>
<span class="nt"></CodeSnippets></span>
</code></pre></div>
<p>Just save this as a file called <code>StringFormat.snippet</code> in your <code>Documents\Visual Studio 2008\Code Snippets\Visual C#\My Code Snippets</code> folder. (You can specify another location using the <em>Code Snippets Manager</em> in the <em>Tools</em> menu.) Then, when editing a C# source file, if you type <code>sf</code> and hit Tab a couple of times, this will magically appear in your code:</p>
<p><code>String.Format("</code> <em>format</em> <code>",</code> <em>arguments</em> <code>)</code></p>
<p>The <em>format</em> placeholder will be selected, so you can replace it with your format string, then hit Tab and the <em>arguments</em> placeholder will be selected, so you can replace it. You can keep hitting Tab to go back and forth between the placeholders. When finished, hit Return and the cursor will go to the end of the line.</p>
<p>Alas, snippets only work for C#, Visual Basic, and XML editing. Most of my work is in C++, so I have to keep writing code the old-fashioned way there (or use macros).</p>
<p>Note that I have been talking about Visual Studio 2008. "Why aren't you using Visual Studio 2010?" some will ask. Shut up.</p>
<p>For a more comprehensive tutorial on creating code snippets, see <a href="http://www.switchonthecode.com/tutorials/csharp-tutorial-visual-studio-code-snippets">Switch On The Code: C# Tutorial - Visual Studio Code Snippets</a>.</p>AppleScript for Bulk Conversion of PowerPoint Documents to Keynote2011-02-18T01:47:43-05:002011-02-18T01:47:43-05:00Kristopher Johnsontag:undefinedvalue.com,2011-02-18:/applescript-bulk-conversion-powerpoint-documents-keynote.html<p>The instructor in one of my MBA-prerequisite classes distributed a set of PowerPoint presentations as course notes. I want to review these on my iPad, so I needed to convert them to Keynote.</p>
<p>This is pretty easy to do manually on a Mac: Just right-click the PPT file, select <strong>Open …</strong></p><p>The instructor in one of my MBA-prerequisite classes distributed a set of PowerPoint presentations as course notes. I want to review these on my iPad, so I needed to convert them to Keynote.</p>
<p>This is pretty easy to do manually on a Mac: Just right-click the PPT file, select <strong>Open With... -> Keynote</strong> from the menu (which reads the PowerPoint file into Keynote), then <strong>File -> Save As...</strong> to store it where you want it. However, I am a programmer, so I would rather spend a few hours figuring out how to write a program to do a repetitive task than simply spend the five minutes needed to do it by hand.</p>
<p>At first I tried using Apple's Automator utility, which is supposed to make stuff like this easy, but I couldn't figure it out. So, I took the plunge into AppleScript.</p>
<p>As with every foray I make into AppleScriptLand, I was frustrated, annoyed, saddened, and exhausted by the experience. But I did succeed (if two hours spent futzing with AppleScript can possibly be called a <em>success</em>).</p>
<p>So, if you have need for a utility for converting a bunch of PowerPoint files to Keynote, open up the AppleScript Editor and copy and paste this script into the window:</p>
<div class="highlight"><pre><span></span><code><span class="nv">on</span><span class="w"> </span><span class="nv">open</span><span class="w"> </span><span class="nv">droppedFiles</span><span class="w"></span>
<span class="w"> </span><span class="nv">set</span><span class="w"> </span><span class="nv">theDestinationFolder</span><span class="w"> </span><span class="nv">to</span><span class="w"> </span><span class="ss">(</span><span class="nv">choose</span><span class="w"> </span><span class="nv">folder</span><span class="w"> </span><span class="nv">with</span><span class="w"> </span><span class="nv">prompt</span><span class="w"> </span><span class="s2">"Choose destination folder"</span><span class="ss">)</span><span class="w"> </span><span class="nv">as</span><span class="w"> </span><span class="nv">Unicode</span><span class="w"> </span><span class="nv">text</span><span class="w"></span>
<span class="w"> </span><span class="nv">repeat</span><span class="w"> </span><span class="nv">with</span><span class="w"> </span><span class="nv">theFile</span><span class="w"> </span><span class="nv">in</span><span class="w"> </span><span class="nv">droppedFiles</span><span class="w"></span>
<span class="w"> </span><span class="nv">tell</span><span class="w"> </span><span class="nv">application</span><span class="w"> </span><span class="s2">"Keynote"</span><span class="w"></span>
<span class="w"> </span><span class="nv">open</span><span class="w"> </span><span class="nv">theFile</span><span class="w"></span>
<span class="w"> </span><span class="nv">set</span><span class="w"> </span><span class="nv">theSlideshow</span><span class="w"> </span><span class="nv">to</span><span class="w"> </span><span class="nv">slideshow</span><span class="w"> </span><span class="mi">1</span><span class="w"></span>
<span class="w"> </span><span class="nv">set</span><span class="w"> </span><span class="nv">theDestinationPath</span><span class="w"> </span><span class="nv">to</span><span class="w"> </span><span class="nv">theDestinationFolder</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="ss">(</span><span class="nv">name</span><span class="w"> </span><span class="nv">of</span><span class="w"> </span><span class="nv">theSlideshow</span><span class="ss">)</span><span class="w"></span>
<span class="w"> </span><span class="nv">save</span><span class="w"> </span><span class="nv">theSlideshow</span><span class="w"> </span><span class="nv">in</span><span class="w"> </span><span class="nv">theDestinationPath</span><span class="w"></span>
<span class="w"> </span><span class="nv">close</span><span class="w"> </span><span class="nv">theSlideshow</span><span class="w"></span>
<span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="nv">tell</span><span class="w"></span>
<span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="nv">repeat</span><span class="w"></span>
<span class="k">end</span><span class="w"> </span><span class="nv">open</span><span class="w"></span>
</code></pre></div>
<p>Click the <strong>Compile</strong> button, and assuming you see no errors, choose <strong>File -> Save</strong>, set the File Format to <strong>Application</strong>, and save it to the desired place with the desired name.</p>
<p>Then, to convert files, just select them in the Finder and drag them onto the application icon. You will be prompted for a destination folder, then the script will do its thing.</p>
<p>The script looks pretty simple and straightforward, right? Well, AppleScript is a language that is easy to read, but very, very difficult to write. Every programming language that tries to "look like plain English" is a nightmare to use, because like English, the rules are illogical, arbitrary, and self-contradictory. Every application, like Keynote, has its own commands and object types, and the documentation is poor, so you end up doing a lot of experimentation and hair-pulling. The hardest part of this particular script was figuring out that I needed to add the <code>as Unicode text</code> conversion in order to produce a valid file path.</p>
<p>(This article is based upon my question and answer in the <a href="http://apple.stackexchange.com/questions/8317/applescript-or-automator-workflow-for-bulk-converting-powerpoint-presentations-to">Ask Different</a> Q&A forum.)</p>Air Display vs. MaxiVista2011-01-28T23:19:24-05:002011-01-28T23:19:24-05:00Kristopher Johnsontag:undefinedvalue.com,2011-01-28:/air-display-vs-maxivista.html<p>Most of the time, I work at the Windows 7 computer in my home office with a dual-monitor setup. A lot of non-geeks have never used a dual-monitor setup: you will just have to trust me when I tell you that, for a programmer or any other person who needs …</p><p>Most of the time, I work at the Windows 7 computer in my home office with a dual-monitor setup. A lot of non-geeks have never used a dual-monitor setup: you will just have to trust me when I tell you that, for a programmer or any other person who needs to look at a lot of information all at once, it is much more productive than a single-monitor setup. It's not a luxury—it's a necessity. </p>
<p>When I have to leave the home office and go into the office office, I take a Windows laptop. This is less productive, due both to the small screen size and the fact that I have only one screen. So I decided to try out a couple of apps that allow one to use an iPad as a second monitor.</p>
<!--break-->
<p>I tried <a href="http://avatron.com/apps/air-display/">Air Display</a> and <a href="http://www.maxivista.com/ipad_monitor.htm">MaxiVista</a>. Both apps have been available for a while, but Air Display originally only supported Mac, while MaxiVista only supported Windows, so there was no choice to be made. However, Air Display now supports both Mac and Windows, so I wanted to try both apps to see which provided the better experience on Windows.</p>
<p>Both apps require a wi-fi connection between iPad and computer Both apps require that you download a free program to install and run on the computer side. Both apps are currently selling for $9.99 in the App Store. </p>
<p>MaxiVista was the first app I tried. Setup on my Windows 7 laptop was easy, and It Just Worked. I started the app on the iPad, then started the app on the laptop, and the laptop immediately found the iPad on the wi-fi network. I was a little disappointed with the speed of display updates on the iPad monitor, but it was usable. </p>
<p>Then I tried Air Display. The setup was a little more difficult. After installing the Air Display application on the laptop and rebooting, it started automatically but could not locate my iPad. After trying a few troubleshooting measures, I eventually figured out that it had to be Run as Administrator. After I worked that out, it ran fine.</p>
<p>Air Display provides a better display experience. While MaxiVista feels like running VNC over an Internet connection, Air Display feels like running with a directly connected monitor on a ten-year-old computer. </p>
<p>Air Display lets you use the touchscreen like a mouse. MaxiVista doesn't have that feature (but they say it is coming).</p>
<p>The fact that Air Display works on both Windows and Macs is nice. The "Windows laptop" I've been talking about is actually a MacBook Pro with Boot Camp, so it's nice to be able to use the iPad as a second monitor with whichever OS I have running.</p>
<p>So, to sum up, Air Display is the clear winner for me. It's faster and more capable, for the same price. Even if MaxiVista was free, I'd gladly pay the ten bucks for Air Display.</p>
<hr>
<p><em>Update:</em> Note that someone from MaxiVista has responded with a comment describing configuration settings that could improve its performance. (I haven't tried them myself.)</p>442011-01-15T22:49:56-05:002011-01-15T22:49:56-05:00Kristopher Johnsontag:undefinedvalue.com,2011-01-15:/44.html<p>Another year past. Highlights of the past year:</p>
<p>We've acquired two more dogs this year: "Blitz," a Miniature Schnauzer, and "Lucy," one of a litter of puppies from our Yorkies Boo and Tweezer. This brings the total to five. We gave away four puppies from two litters this year. One …</p><p>Another year past. Highlights of the past year:</p>
<p>We've acquired two more dogs this year: "Blitz," a Miniature Schnauzer, and "Lucy," one of a litter of puppies from our Yorkies Boo and Tweezer. This brings the total to five. We gave away four puppies from two litters this year. One puppy, "Riley," ended up with my parents, and the other three were taken by Bailey's soccer coach. </p>
<p>Stopped working for Scientific Games as a contractor, and returned to TransCore as a contractor. </p>
<p>My wife Pebble went through a couple of surgeries in the space of a few weeks this summer, so that was tough on her, but she's doing well now. </p>
<p>Bsiley started middle school. He has taken up the oboe. I'm happy to hear him practicing it. It's the first thing he's taken any interest in other than videogames, and it's nice to seem him excited about it. </p>
<p>I've gained back ten of the thirty pounds that I lost in 2009. So I'm working on that again. </p>
<p>I don't know what's in store for me this year. I may be offered permanent employment with TransCore. I may start an MBA program in the fall. I'm sure there will be surprises and setbacks. </p>
<p>Anyway, enough for now. It's time for the hourly dog-poop removal. </p>To MBA, or Not to MBA, That Is the Question2011-01-10T05:53:47-05:002011-01-10T05:53:47-05:00Kristopher Johnsontag:undefinedvalue.com,2011-01-10:/mba-or-not-mba-question.html<p>I've starting taking some business classes that are prerequisites for starting an MBA program in the fall. So you may be seeing a lot more business-school-related musings than computer-programming-related musings for a while.</p>
<!--break-->
<h1>Why Go Back to School?</h1>
<p>I haven't made the final decision about whether I am actually going …</p><p>I've starting taking some business classes that are prerequisites for starting an MBA program in the fall. So you may be seeing a lot more business-school-related musings than computer-programming-related musings for a while.</p>
<!--break-->
<h1>Why Go Back to School?</h1>
<p>I haven't made the final decision about whether I am actually going to start the MBA program. It will be expensive, it will take up a lot of my time, and I don't really know why I'm doing it.</p>
<p>For a few years, I've wanted to go back to school. I didn't take college very seriously the first time around, and I've had this romantic notion that if I returned, I could have the wonderfully educational and enlightening <em>college experience</em> that our high-school guidance counselors told us about. I know it wouldn't really be that way, particularly since I'm old and can't be a full-time student, but the notion still appeals to me.</p>
<p>I considered going back to get an M.S. and Ph.D. in Computer Science, but not for very long. I did well in my computer-science studies while pursuing my B.S., but the academic side of things never appealed to me as much as practical skills.</p>
<p>I considered law school, as it seems like it would include a lot of activities I like: reading detailed technical documents, applying abstract principles to concrete situations, etc. But that didn't feel right either.</p>
<p>Which brings me to Business School. I have never really understood how business works. I write my little programs and collect my paychecks, but that has been the extent to which I've understood about the forces that shape my career. I had a brief stint as a manager several years ago, and while I found it stressful and unpleasant—I eventually quit and took a year off from work—I found a lot of it exhilarating. I found that I really enjoyed talking to customers and being involved in the company's higher-level decision making. However, I felt completely incompetent.</p>
<p>Looking back, I wasn't as bad as it as I thought, and really should not have expected myself to do any better in my first asssignment. But I'm the kind of person who wants to feel <em>prepared</em>. So before I do such a thing again, I want some training.</p>
<p>I started looking into MBA programs, and eventually found one that fit well. Classes are held in the evenings in a nearby city, and the school offers some accelerated courses to teach non-business-majors the stuff that they should have already learned in business school.</p>
<h1>Am I Giving Up Programming to Become a Manager?</h1>
<p>I'm not one of those programmers who wants to give up coding to become a manager. Developing software is something I do well, and so I'm not looking to replace my programming skillset with a managerial skillset. Rather, I'm hoping that this new knowledge will supplement what I already know, making me a software developer who doesn't look down at his shoes during business meetings, and who is willing to give useful answers to executives rather than a lot of <em>it depends</em> answers.</p>
<p>Maybe I'll start my own company someday. Maybe I'll rise up through the ranks of a corporation. Maybe I'll design and develop the software that business people have always wished they wanted. I don't know, and I don't think I need to know yet.</p>
<h1>But Why Go to School, When I Could Just Be a Manager?</h1>
<p>Of course, there are plenty of arguments against getting an MBA. Many people say that it is a waste of time, and the best way to learn about business is just to dive in and start a business or start managing people. But, you see, I <em>tried</em> that, and I know I'm not going to pick things up that way. I need a foundation. Formality gives me confidence. When I feel like I know a little bit, then I'll be ready for the sink-or-swim educational method.</p>
<p>I've started my first prerequisite course, and the first two chapters of the book have already given me some insights as to what I did wrong and how stupid my expectations were. Maybe these business people actually know some things worth learning.</p>
<p>This is exciting.</p>Now Running on Amazon EC22010-11-19T23:13:19-05:002010-11-19T23:13:19-05:00Kristopher Johnsontag:undefinedvalue.com,2010-11-19:/now-running-amazon-ec2.html<p>This blog is now running in the <a href="http://aws.amazon.com/ec2/">Amazon Elastic Compute Cloud (EC2)</a>. I hope that nobody notices any difference.</p>
<!--break-->
<p>Migrating the blog over to the new server was relatively straightforward, using Drupal's <a href="http://drupal.org/project/backup_migrate">Backup and Migrate</a> module. After <a href="/2010/11/12/setting-drupal-ubuntu-1010-ec2">getting Drupal set up on the new server</a>, I just copied all my …</p><p>This blog is now running in the <a href="http://aws.amazon.com/ec2/">Amazon Elastic Compute Cloud (EC2)</a>. I hope that nobody notices any difference.</p>
<!--break-->
<p>Migrating the blog over to the new server was relatively straightforward, using Drupal's <a href="http://drupal.org/project/backup_migrate">Backup and Migrate</a> module. After <a href="/2010/11/12/setting-drupal-ubuntu-1010-ec2">getting Drupal set up on the new server</a>, I just copied all my themes, modules, and files from the old server to the new and then did a restore of the database.</p>
<p>I am using a <a href="http://aws.amazon.com/ec2/instance-types/">t1.micro</a> EC2 instance, which is the cheapest and least-powerful EC2 instance. So far, it seems to be handling the load fine, but I'll need to see how it works over the next few weeks. If all goes well, I'm going to try moving all my other web sites to the same server. None of my sites gets more than a few hundred hits per day, so if I can handle them all with one cheap easy-to-manage server, I'll be very happy. But if I have to upgrade to a beefier instance, all it takes is a couple of minutes and a few dollars.</p>
<p>Time will tell how bad being dependent on Amazon is in comparison to being dependent on the web hosting services I've used. I suspect it will be better.</p>
<p>I'm switching to <a href="http://www.google.com/apps/intl/en/group/index.html">Google Apps</a> for my e-mail and other such services. Configuring and managing your own e-mail servers these days is a colossal waste of time unless you have some very special requirements.</p>
<h2>A Few Weeks Later...</h2>
<p>Amazon AWS charges for November: $2.88. $2.40 of that was for having an unattached elastic IP address for 240 hours, and $0.17 was for running a Small (rather than Micro) instance for a couple of hours, so I expect my costs going forward to be lower.</p>
<p>AWS charges for December: $0.52.</p>
<p>AWS charges for January: $0.52.</p>
<p>(In contrast, my previous hosting plans cost me a total of $20 per month.) </p>
<p>Google's webmaster tools indicate that my blog pages are now downloading in about 150 ms, as opposed to around 400 ms with my old provider. So performance seems to have improved quite a bit.</p>Setting Up Drupal 6 on Ubuntu 10.10 on EC22010-11-12T22:14:23-05:002010-11-12T22:14:23-05:00Kristopher Johnsontag:undefinedvalue.com,2010-11-12:/setting-drupal-6-ubuntu-1010-ec2.html<p>For several years, I've been using pretty-cheap web hosting services for my blog, my <a href="http://capablehands.net">corporate website</a>, and other webby things. However, I'm pretty sure that it would be even cheaper to use <a href="http://aws.amazon.com/ec2/">Amazon EC2</a>, especially as they now offer <a href="http://aws.amazon.com/free/">free usage</a> for a year. I also like the ease with …</p><p>For several years, I've been using pretty-cheap web hosting services for my blog, my <a href="http://capablehands.net">corporate website</a>, and other webby things. However, I'm pretty sure that it would be even cheaper to use <a href="http://aws.amazon.com/ec2/">Amazon EC2</a>, especially as they now offer <a href="http://aws.amazon.com/free/">free usage</a> for a year. I also like the ease with which one can scale EC2 servers up or down, and run temporary instances for a few cents per hour.</p>
<p>Of course, this means I have to figure out how to move everything from where it is to a new EC2 instance. Most of the stuff I care about is managed in <a href="http://drupal.org/">Drupal</a>, so step one is figuring out how to set up Drupal on an EC2 host.</p>
<p>I decided to go with <a href="http://www.ubuntu.com">Ubuntu</a> as my OS, because I'm a long-time Debian user and my brushes with Ubuntu have been positive. A little research showed that they had an easy-to-install <code>drupal6</code> package and a few other packages that I plan to use in my plans for world conquest.</p>
<p>But no matter how easy/straightforward things look, they are always a little bit complicated. Here are my notes for setting things up, which may be helpful to others, and will probably be helpful to me whenever I end up redoing this.</p>
<p>I assume the reader has basic knowledge of how to connect to servers via SSH, knows a little bit about setting up Apache and Drupal, and is comfortable using a text editor to modify configuration files.</p>
<!--break-->
<h3>Launch Ubuntu 10.10 Instance on EC2</h3>
<p>(For more details and help, check out the <a href="https://help.ubuntu.com/community/EC2StartersGuide">EC2StartersGuide</a> in Ubuntu Community Documentation.)</p>
<ol>
<li>If you don't already have an Amazon Web Services account, go to http://aws.amazon.com/ and click the Sign Up Now button. After you give them all your info (including credit card info), you may need to wait for authorization to use the services. (I had to wait about 12 hours.) Then create and download your private keys and certificates. (Note: in the rest of this document, I'll use the symbol <code>$EC2_PKEY</code> to mean the full path and name of your stored private key file.)</li>
<li>Sign in to the <a href="https://console.aws.amazon.com/s3/home">AWS Management Console</a> (which, unfortunately, requires Adobe Flash Player, so you can't do this on an iPad).</li>
<li>Click the <em>Amazon EC2</em> tab, and click the <em>Launch Instance</em> button</li>
<li>Choose a suitable AMI. I went with ami-508c7839, which is a 32-bit EBS-based Ubuntu 10.10 instance that runs in the US-East availability zone. For lists of other available Ubuntu AMIs, see the <a href="https://help.ubuntu.com/community/EC2StartersGuide">EC2StartersGuide</a> and the <a href="http://alestic.com/">Alestic</a> site.</li>
<li>On the <em>Instance Details</em> page of the wizard, set these details:</li>
<li>Number of instances: 1</li>
<li>Instance type: Micro (required for Free Tier)</li>
<li>Availability Zone: No preference</li>
<li>Launch Instances</li>
<li>For <em>Advanced Instance Options</em>, accept the defaults.</li>
<li>Fill in something for the Name tag. You will eventually have a list of instances, so make this descriptive.</li>
<li>Choose a new key pair or create a new one.</li>
<li>Choose an existing security group or create a new security group. It is <strong>very important</strong> that you set up a security group that allows access via the SSH port, and you probably want the HTTP port open also.</li>
<li>Assuming everything looks good, click the <em>Launch Instance</em> button.</li>
<li>Return to the <em>Amazon EC2</em> tab in the AWS Management Console. You should see your new instance with a status of "Pending". Wait for it to become "Running".</li>
<li>Select your new instance in the top pane. In the bottom pane, you will see a bunch of info. Select the contents of the <em>Public DNS</em> field. It will look something like this: <code>ec2-XX-XX-XX-XXX.compute-1.amazonaws.com</code>. This is the hostname you will use to connect. In the rest of this guide, I'll denote this address as <code>$EC2_HOST</code>in the rest of this article.</li>
<li>Verify that you can connect to the new instance. If you have Mac OS X, Linux, or another UNIXy box, you can run SSH like this: <code>ssh -i $EC2_PKEY ubuntu@$EC2_HOST</code>. If you are on Windows, then figure out how to use <a href="http://www.chiark.greenend.org.uk/~sgtatham/putty/">PuTTY</a>. If all is well, you will be greeted with a prompt like this: <code>ubuntu@ip-XX-XX-XXX-XXX:~$</code>.</li>
</ol>
<h3>Set Up DNS</h3>
<p>You <em>could</em> use that Public DNS to connect to your server forever, but you will probably want a real domain name. So, head on over to your favorite registrar and register a new domain, or change an existing domain to point to your new server.</p>
<p>Amazon recommends using a CNAME (alias) record that points to your EC2 instance's public DNS. However, I've found that the public DNS can change for no apparent reason, so if you expect people to connect to your site, you should use Amazon's Elastic IP service to assign a static IP address to your server. (Note that after assigning the static IP, the public DNS for your server will change to match the static IP, so be sure to refresh your EC2 dashboard instance display after setting up the Elastic IP to get the final DNS or IP address.)</p>
<p>I'll use the symbol $MY_HOSTNAME in the remainder of this document to denote the domain name that you want to use. (In my case, $MY_HOSTNAME is "happyspacelab.com".)</p>
<h3>Install Drupal and Related Packages</h3>
<ol>
<li>Go back to your SSH command prompt on the EC2 box (reconnecting if necessary).</li>
<li>Update the package index with this command: <code>sudo apt-get update</code></li>
<li>Upgrade existing packages with this command: <code>sudo apt-get upgrade</code> (Answer "Yes" when asked whether to continue.)</li>
<li>Install Drupal and all dependencies (Apache, MySQL, PHP, etc.) with this command: <code>sudo apt-get install drupal6</code></li>
<li>You will be prompted for various passwords and names. Write down whatever you enter.</li>
<li>Mail Configuration: No configuration (unless you really want to do this now)</li>
<li>Database type: mysql</li>
<li>Default choices for everything else</li>
<li>Restart Apache with this command: <code>sudo /etc/init.d/apache2 restart</code></li>
<li>Verify that Apache is working and all the firewalls have port 80 open by visiting your site with a web browser. You can use <code>http://$EC2_HOST</code> or <code>http://$MY_HOSTNAME</code> (assuming enough time has passed for DNS propagation). You should see a web page that says "It Works!".</li>
<li>Visit the Drupal installation page: <code>http://$EC2_HOST/drupal6/install.php</code> (and then follow normal Drupal installation procedures)</li>
<li>Click the link to set up in English</li>
<li>Fill in the fields and click <em>Send and continue</em><ul>
<li>Note: You will not be able to enable Clean URLs at this time. Don't worry; we'll get to that.</li>
</ul>
</li>
<li>If you have not set up email on the server, you'll get a warning that mail could not be sent. Ignore.
At this point, you should have a usable Drupal installation, accessible at <code>http://$EC2_HOST/drupal6/</code> or at <code>http://$MY_HOSTNAME/drupal6</code>. Go into the Administration screens and configure the site as desired, or wait until you have finished subsequent steps.</li>
</ol>
<h3>Set Up Virtual Host</h3>
<p>The <code>/drupal6/</code> part of the URL is ugly, so we'll set up virtual hosting so that the Drupal content is available at <code>http://$MY_HOSTNAME/</code>.</p>
<ol>
<li>Go back to your SSH command prompt on the EC2 box (reconnecting if necessary).</li>
<li><code>cd /etc/apache2/sites-available</code></li>
<li>Create a new file named $MY_HOSTNAME (substituting your actual hostname), with these contents (and remember to start your editor with <code>sudo</code>):
<VirtualHost <em>:80>
ServerAdmin webmaster@$MY_HOSTNAME.com
ServerName $MY_HOSTNAME
ServerAlias $MY_HOSTNAME </em>.$MY_HOSTNAME.com
DocumentRoot /usr/share/drupal6
RewriteEngine On
RewriteOptions inherit
</VirtualHost></li>
<li><code>sudo a2ensite $MY_HOSTNAME</code></li>
<li><code>sudo a2enmod rewrite</code></li>
<li>Open the file <code>/usr/share/drupal6/.htaccess</code> with a text editor (remember <code>sudo</code>), and uncomment the line that says
RewriteBase /</li>
<li><code>sudo /etc/init.d/apache2 restart</code></li>
<li>Visit <code>http://$MY_HOSTNAME/</code> with a web browser. You should see the Drupal front page, rather than the "It Works!" page.</li>
</ol>
<h3>Enable Clean URLs</h3>
<p>With the virtual hosting stuff set up, you can now enable Clean URLs in Drupal. Log in to the Drupal page as administrator, go to Administrator > Site Configuration > Clean URLs, select <em>Enable</em> and click <em>Save configuration</em>.</p>
<h3>Set Up Memcached</h3>
<p>Amazon will be charging us for EBS I/O requests (we only get one million free I/O requests per month with the Free Tier), so let's set up Drupal to use <a href="http://memcached.org/">memcached</a> to reduce the amount of I/O and improve performance.</p>
<p>This is a simple setup, using a single memcached instance running on the local machine. For more complicated situations, refer to the <a href="http://drupal.org/project/memcache">Drupal memcache module documentation</a>.</p>
<ol>
<li><code>sudo apt-get install memcached php5-memcached</code></li>
<li>In the file <code>/etc/php5/apache2/conf.d/memcached.ini</code>, add this line: <code>memcache.hash_strategy="consistent"</code></li>
<li>Download and unarchive the <a href="http://drupal.org/project/memcache">Memcache API and Integration</a> module in <code>/usr/share/drupal6/sites/all/modules</code></li>
<li>In your site's <code>settings.php</code>, add the snippet provided below to set <code>cache_inc</code> and <code>memcache_key_prefix</code>.</li>
<li><code>sudo /etc/init.d/apache2 reload</code></li>
<li>Go to Administer > Site building > Modules and enable the Memcache Admin module</li>
<li>Go to Administer > Site configuration > Memcache and enable "Show memcache statistics at the bottom of each page". After this is enabled, you can scroll down to the bottom of the page to verify that memcache is working and that things are in the cache. (You can then disable this if you find the display annoying.)</li>
</ol>
<p>Here is the snippet to be added/modified in <code>settings.php</code>. In place of <code>some_unique_prefix</code>, substitute your site's name or some other value that is unique for your site.</p>
<div class="highlight"><pre><span></span><code>$conf = array(
'cache_inc' => '/usr/share/drupal6/sites/all/modules/memcache/memcache.inc',
'memcache_key_prefix' => 'some_unique_prefix',
);
</code></pre></div>
<h3>Set up Alternative PHP Cache</h3>
<p>As a further measure to reduce the number of I/O requests and improve performance, we'll install the <a href="http://www.php.net/manual/en/intro.apc.php">Alternative PHP Cache</a> (APC).</p>
<ol>
<li><code>sudo apt-get install php-apc</code></li>
<li>Add the following line to <code>/etc/php5/conf.d/apc.ini</code></li>
</ol>
<pre>
apc.stat = 0
</pre>
<p>You can monitor the performance of APC by installing the <code>apc.php</code> script:</p>
<ol>
<li><code>sudo gunzip /usr/share/doc/php-apc/apc.php.gz</code></li>
<li><code>sudo ln -s /usr/share/doc/php-apc/apc.php /usr/share/drupal6/apc.php</code></li>
<li>In a web browser, go to <code>http://$MY_HOSTNAME/apc.php</code>. You should see stats for APC.</li>
</ol>
<h3>Performance Adjustments</h3>
<ol>
<li>Go to Administer > Site configuration > Performance</li>
<li>Set <em>Caching Mode</em> to "Normal"</li>
<li>Set <em>Page compression</em> to "Disabled" (Apache is already configured to compress stuff, so there is no need to have Drupal do it.)</li>
<li>Set <em>Block cache</em>, <em>Optimize CSS files</em>, and <em>Optimize JavaScript files</em> to "Enabled"</li>
<li>Click the <em>Save configuration</em> button</li>
</ol>
<h3>Set Up Drupal Multisite</h3>
<p>If you are cheap enough to go with free hosting, you'll probably want to go ahead and put multiple Drupal sites, each with their own doman name, on this same host. This is fairly easy to do. Here are a bunch of documents describing how to do it: http://drupal.org/node/43816</p>
<p>However, here are quick-and-dirty instructions:</p>
<ol>
<li>For each site, create a file in <code>/etc/apache2/sites-available</code> like you did in the <strong>Set Up Virtual Host</strong> section, above. Substitute the appropriate host name in each new file.</li>
<li><code>cd /usr/share/drupal6/sites</code></li>
<li>Copy the <code>default</code> site to a directory named $MY_HOSTNAME: <code>sudo cp -a default $MY_HOSTNAME</code></li>
<li>For each site, you need to create another database. Here's the easy way to do that:</li>
<li>Run this command: <code>sudo dpkg-reconfigure drupal6</code></li>
<li>Answer the prompts as follows:<ol>
<li>Reinstall database: Yes</li>
<li>Database type: mysql</li>
<li>Connection method: Unix socket</li>
<li>Name of the database's administrative user: root</li>
<li>Password: [root's MySQL password, as you specified when MySQL was installed]</li>
<li>Username for drupal6: [enter a site-specific user name to own the new database, e.g., "myhostname2"]</li>
<li>Database name: [enter a site-specific database name, e.g., "myhostname2"] </li>
</ol>
</li>
<li>The new configuration is in the <code>/usr/share/drupal6/sites/default</code> directory. Copy that to host-specific directory: <code>sudo cp -a default myhostname2</code></li>
<li>The <code>sites\default</code> directory that you just copied has a <code>files</code> directory which is a symlink to <code>/var/lib/drupal6/files</code>. If you don't want all your Drupal instances to put their files in the same place, then do the following:<ol>
<li><code>sudo rm myhostname2/files</code></li>
<li><code>sudo mkdir myhostname2/files</code></li>
<li><code>sudo chown myhostname2/files</code></li>
<li><code>sudo chgrp www-data myhostname2/files</code></li>
<li><code>sudo chmod 750 myhostname2/files</code></li>
</ol>
</li>
<li><code>sudo /etc/init.d/apache2 restart</code></li>
<li>Visit <code>http://myhostname/install.php</code> to complete the installation process for this new Drupal instance.</li>
<li>Repeat the site-specific steps for setting up memcached (update settings.php, enable the module, etc.), and repeat the Performance Adjustments steps</li>
</ol>
<p>For more details on this method of setting up multisite on Ubuntu, see http://drupal.org/node/138889</p>
<p>After setting up a few sites this way, I found that the daily Drupal cron job would run for some of my sites, but not for others. The cron job is triggered by the file <code>/etc/cron.d/drupal6</code>, which calls <code>/usr/share/drupal6/scripts.cron.sh</code>, but I never figured out exactly why it was working for some sites and other others.</p>
<p>I was able to get the cron job running for all sites by adding a <code>baseurl.php</code> file to each of the <code>/usr/share/drupal6/sites</code> subdirectories. <code>/usr/share/drupal6/scripts.cron.sh</code> looks at this file, and at <code>settings.php</code>, to determine the appropriate URL for invoking the script that site. The contents of each file should look like this:</p>
<div class="highlight"><pre><span></span><code><span class="o">$</span><span class="nt">base_url</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'http://example.com'</span><span class="o">;</span><span class="w"></span>
</code></pre></div>
<p>Substitute the appropriate host name for <code>example.com</code>. Note that the URL should <em>not</em> have a slash at the end.</p>
<h3>Cleaning Up</h3>
<p>So, you've followed all the instructions above, and you've got a working Drupal server. Congratulations!</p>
<p>Remember that Amazon is going to be charging you for this instance that you've set up. Even if you stop the instance, Amazon still charges you by the hour. So, if you aren't really ready to use this instance, you'll want to terminate it via the AWS Management Console.</p>
<p>Note that "terminating" really means "deleting". After terminating the instance, you won't be able to restart it. It's gone forever, unless you take steps to save the image in S3.</p>
<h3>Odds and Ends</h3>
<ul>
<li>The Drupal <a href="http://drupal.org/project/backup_migrate">Backup and Migrate</a> module makes it easy to move Drupal data from your old site to your new site.</li>
<li>If you don't want to set up your system as a mail server, but do want SMTP to work so that Drupal can send you mail, look at the <a href="http://drupal.org/project/smtp">SMTP Authentication</a> module.</li>
<li><a href="http://duplicity.nongnu.org/">Duplicity</a> is a nifty tool for making incremental backups to Amazon S3 (or other destinations). See http://www.problogdesign.com/how-to/automatic-amazon-s3-backups-on-ubuntu-debian/.</li>
</ul>
<h3>To-Do</h3>
<p>There are a few little things I want to figure out when I have time. If anyone already knows the answers, I'd appreciate learning about them.</p>
<ul>
<li>The Ubuntu AMI is a 15 GB boot image, but I am using less than 2 GB of it. The Amazon Free Tier only allows 10 GB of EBS storage, so I've thought about shrinking the boot image. (On the other hand, 5 GB of excess storage only costs fifty cents per month, so why worry?)</li>
<li>I want to automatically back up my volume and my MySQL database to S3 periodically.</li>
</ul>
<h3>References</h3>
<p>Here are some links I traversed while figuring this stuff out:</p>
<ul>
<li>https://help.ubuntu.com/community/EC2StartersGuide</li>
<li>http://drupal.org/node/138889</li>
<li>http://groups.drupal.org/node/8004</li>
<li>http://awebfactory.com.ar/node/275</li>
<li>http://www.sunsetlakesoftware.com/2010/09/15/how-run-drupal-amazon-ec2-using-new-micro-instance</li>
<li>http://www.control-escape.com/web/configuring-apache2-debian.html</li>
<li>http://drupal.org/project/memcache</li>
<li>http://fplanque.com/dev/linux/install-apc-php-cache-debian-lenny</li>
<li>http://www.imminentweb.com/technologies/tune-apc-improve-php-performance</li>
<li>http://2bits.com/articles/high-php-execution-times-drupal-and-tuning-apc-includeonce-performance.html</li>
</ul>Find-My-iPhone Success Story2010-11-11T02:24:23-05:002010-11-11T02:24:23-05:00Kristopher Johnsontag:undefinedvalue.com,2010-11-11:/find-my-iphone-success-story.html<p>My wife, stepson, and I carpooled today. I picked them up at school and we made the 45-minute drive back home. As we parked the car, my wife noticed that she didn't have her iPhone.</p>
<!--break-->
<p>She initially thought she had left it back in the teachers' lounge, so she called …</p><p>My wife, stepson, and I carpooled today. I picked them up at school and we made the 45-minute drive back home. As we parked the car, my wife noticed that she didn't have her iPhone.</p>
<!--break-->
<p>She initially thought she had left it back in the teachers' lounge, so she called some coworkers to ask them to check. None were still at school, so one of her friends drove to school to look for it. It wasn't there.</p>
<p>So, my wife fired up MobileMe and went to <a href="http://www.apple.com/mobileme/features/find-my-iphone.html">Find My iPhone</a>. It indicated that the phone was on Georgia SR 400 (a multilane freeway), in the northbound lanes between exits 14 and 15. She realized that she must have left the phone on the roof of the car while she was getting in, and it fell off when we were on the freeway.</p>
<p>The good news was that it was reporting its location, so we knew approximately where it was and that it was still functional. We hopped back in the car to go retrieve it.</p>
<p>I didn't know how accurate the reported location would be. I suspected we'd be walking up and down the freeway in the dark and beating the bushes on the side of the road to find it. But she spotted it immediately, a few feet to the right of the edge of the freeway, within a few dozen yards of where it was supposed to be.</p>
<p>We pulled over and went to pick it up. We could tell from several yards away that the glass was cracked, but it turned out that only the back glass of the iPhone 4 was cracked. The front glass was fine, and everything was fully functional. (A little Googling indicates that Apple will replace the back glass for $29.) The bumper was a little mangled, but apparently it did its job.</p>
<p>So, all in all, this was a good outcome. We got lucky in that the iPhone didn't break when it hit the pavement, that it didn't land where a car would run it over, and that it didn't skitter off into the brush, but if we hadn't had MobileMe, we wouldn't have ever known where to look for it.</p>
<p>I sometimes think that MobileMe is overpriced, and think about cancelling it, but I'll remember this incident whenever I think that.</p>
<p><em>Epilogue:</em> My wife called the friendly neighborhood AT&T store (where she bought the phone) and asked about fixing the broken back glass. They directed her to a cellphone repair service which quoted $130 to fix it. I called the Apple Store, and they confirmed that they would fix it for $29. So, once again, AT&T proves itself to be the worst part of the iPhone customer experience.</p>
<p>And in the end, Apple replaced the back glass at no charge.</p>
<p>As of iOS 4.2.1, released 2010/11/22, Find My Phone is now a free service. You don't need to pay for MobileMe to get it anymore.</p>PowerShell and Unicode2010-07-21T12:01:58-04:002010-07-21T12:01:58-04:00Kristopher Johnsontag:undefinedvalue.com,2010-07-21:/powershell-and-unicode.html<p>After being away from the Windows developer world for a few years, I have been pleased to find some of the nice things that Microsoft has given us. Visual Studio has some really nice refactoring capabilities. The Windows 7 user experience rivals OS X. And as an alternative to the …</p><p>After being away from the Windows developer world for a few years, I have been pleased to find some of the nice things that Microsoft has given us. Visual Studio has some really nice refactoring capabilities. The Windows 7 user experience rivals OS X. And as an alternative to the venerable <code>cmd.exe</code>, we now have a much better command-line shell: <a href="http://en.wikipedia.org/wiki/Windows_PowerShell">PowerShell</a>.</p>
<p>What I like most about PowerShell is that it feels more like a UNIX shell. It supports a lot of UNIXy commands (<code>ls</code>, <code>echo</code>, <code>cat</code>). It lets you use either forward slashes or backslashes in paths This is good for someone like me who can never remember what OS I'm using when I start typing a command.</p>
<p>But of course, Microsoft can't give us something new without throwing in some surprisingly inappropriate behavior.</p>
<!--break-->
<p>A couple of days ago, I needed to create a patch for a Subversion repository, and so I typed the typical command to do so (which works fine in UNIX shells and with <code>cmd.exe</code>):</p>
<div class="highlight"><pre><span></span><code>svn diff > my_patch.diff
</code></pre></div>
<p>I then looked at my patch to verify that it looked good:</p>
<div class="highlight"><pre><span></span><code>cat my_patch.diff | more
</code></pre></div>
<p>Everything looked fine. However, when I later tried to apply the patch to another Subversion workspace:</p>
<div class="highlight"><pre><span></span><code>patch -p0 -i my_patch.diff
</code></pre></div>
<p>I got errors. I opened up <code>my_patch.diff</code> in Vim, and realized it was a UTF-16-encoded file.</p>
<p>Neither <code>svn</code> nor <code>patch</code> know how to deal with Unicode. How did this happen?</p>
<p>After wasting an hour trying various <code>svn</code> command-line options and diff utilities, I finally stumbled onto the answer. It turns out that, in PowerShell, <code>svn diff > my_patch.diff</code> is equivalent to this command:</p>
<div class="highlight"><pre><span></span><code>svn diff | out-file my_patch.diff
</code></pre></div>
<p>and (get this), the <code>out-file</code> cmdlet <strong>encodes its output as UTF-16 by default</strong>, regardless of what the input encoding was.</p>
<p>This default behavior makes sense for <code>out-file</code>, but it is counter-intuitive that the <code>></code> redirection operator would take ASCII and convert it to Unicode.</p>
<p>To make PowerShell do the right thing, you have to do this:</p>
<div class="highlight"><pre><span></span><code>svn diff | out-file -encoding ascii my_patch.diff
</code></pre></div>
<p>Grrr.</p>Workflow for Remote CVS, Local Git2010-07-02T13:31:32-04:002010-07-02T13:31:32-04:00Kristopher Johnsontag:undefinedvalue.com,2010-07-02:/workflow-remote-cvs-local-git.html<p>One of my clients uses a <a href="http://ximbiot.com/cvs/">CVS</a> repository for all its source code. People recognize that there are better options available than CVS, but it's been cranking along fine for 15 years, and they see no compelling reason to change.</p>
<p>However, I really like being able to commit incremental changes …</p><p>One of my clients uses a <a href="http://ximbiot.com/cvs/">CVS</a> repository for all its source code. People recognize that there are better options available than CVS, but it's been cranking along fine for 15 years, and they see no compelling reason to change.</p>
<p>However, I really like being able to commit incremental changes often in my own personal branches, and while not connected to the company network (I work from home). So I've been checking out files from the CVS repository, using <a href="http://git-scm.com/">Git</a> locally to manage modifications, and then periodically committing those changes back to the remote CVS repository.</p>
<p>I figured I'd write up what I'm doing, in case others want to try the same thing, or others can tell me a better way to do what I'm doing. I'm still a bit of a Git newbie, so if I'm doing something stupid, please let me know.</p>
<!--break-->
<p>I'm assuming the reader has a basic understanding of CVS and Git. If not, see the <a href="http://ximbiot.com/cvs/manual/">CVS manual</a> and/or the <a href="http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html">Git tutorial</a>. I predominantly work on Windows, but I use <a href="http://en.wikipedia.org/wiki/Windows_PowerShell">PowerShell</a>, so there is no difference between the commands I use on Windows and those I use on Mac OS X or Linux.</p>
<p>In the examples, I'll use a few variables:</p>
<div class="highlight"><pre><span></span><code><span class="k">USER</span><span class="o">=</span><span class="n">myusername</span><span class="w"></span>
<span class="n">CVSROOT</span><span class="o">=</span><span class="err">:</span><span class="nl">pserver</span><span class="p">:</span><span class="err">$</span><span class="k">USER</span><span class="nv">@cvsserver</span><span class="err">:</span><span class="mi">2401</span><span class="o">/</span><span class="n">cvsrepository</span><span class="w"></span>
<span class="n">PROJECT</span><span class="o">=</span><span class="n">myprojectname</span><span class="w"></span>
<span class="n">DROPBOX</span><span class="o">=</span><span class="n">mydropboxfolder</span><span class="w"></span>
</code></pre></div>
<p>($DROPBOX refers to my <a href="https://www.dropbox.com/referrals/NTE0Mzc3MDY5?src=global0">Dropbox</a> directory, where I keep all sorts of little files that I want synched to all my computers. If you haven't looked at Dropbox, do.)</p>
<p>First, we need to get the CVS repository. Git does have a <a href="http://www.kernel.org/pub/software/scm/git/docs/git-cvsimport.html">git-cvsimport</a> command that I could use to suck all the CVS stuff into a local Git repository, but the CVS repository is huge, so that would be very slow, and frankly, I don't really trust <code>git-cvsimport</code>. So I just do what I would normally do to get stuff from CVS:</p>
<div class="highlight"><pre><span></span><code>cvs login
cd ~/work
cvs checkout $PROJECT
</code></pre></div>
<p>Next, I set up the local Git repository:</p>
<div class="highlight"><pre><span></span><code>cd ~/work/$PROJECT
git init
</code></pre></div>
<p>The working directory contains a lot of files that I don't want or need to track with Git, so I've got a standard <code>.gitignore</code> file that looks like this:</p>
<div class="highlight"><pre><span></span><code><span class="nf">CVS</span><span class="w"></span>
<span class="err">.</span><span class="c1">#*</span>
<span class="na">.hg</span><span class="w"></span>
<span class="na">.hgignore</span><span class="w"></span>
<span class="nf">bin</span><span class="w"></span>
<span class="nf">obj</span><span class="w"></span>
<span class="nf">Debug</span><span class="w"></span>
<span class="nf">Release</span><span class="w"></span>
<span class="nf">TestResults</span><span class="w"></span>
<span class="err">*</span><span class="na">.obj</span><span class="w"></span>
<span class="err">*</span><span class="na">.suo</span><span class="w"></span>
<span class="err">*</span><span class="na">.ncb</span><span class="w"></span>
<span class="err">*</span><span class="na">.user</span><span class="w"></span>
<span class="err">*</span><span class="na">.tli</span><span class="w"></span>
<span class="err">*</span><span class="na">.tlh</span><span class="w"></span>
<span class="err">*</span><span class="na">.idb</span><span class="w"></span>
<span class="err">*</span><span class="na">.pdb</span><span class="w"></span>
<span class="nf">build</span><span class="w"></span>
<span class="err">*</span><span class="na">.pbxuser</span><span class="w"></span>
<span class="err">*</span><span class="na">.perspectivev3</span><span class="w"></span>
<span class="na">.DS_Store</span><span class="w"></span>
<span class="nf">xcuserdata</span><span class="w"></span>
<span class="err">*</span><span class="na">.old</span><span class="w"></span>
<span class="err">*</span><span class="na">.log</span><span class="w"></span>
<span class="err">*</span><span class="na">.out</span><span class="w"></span>
<span class="err">*</span><span class="na">.cache</span><span class="w"></span>
</code></pre></div>
<p>I just copy it from my Dropbox:</p>
<div class="highlight"><pre><span></span><code>cp $DROPBOX/.gitignore .
</code></pre></div>
<p>Now, I'm ready to import everything into the Git repository:</p>
<div class="highlight"><pre><span></span><code>git add .
git commit -m "Initial commit"
</code></pre></div>
<p>I keep a local tag <code>cvssync</code> that indicates the last time that the Git and CVS repositories matched.</p>
<div class="highlight"><pre><span></span><code>git tag cvssync
</code></pre></div>
<p>Now I'm ready to do some work. I always want the <code>master</code> Git branch to match CVS, so I create a topic branch where I do my work:</p>
<div class="highlight"><pre><span></span><code>git checkout -b develop
</code></pre></div>
<p>After very efficiently and productively adding all the error-free code needed to implement whatever I'm implementing, I'm ready to commit on my <code>develop</code> branch.</p>
<div class="highlight"><pre><span></span><code>git commit -am "Implemented the whosey-whatsit"
</code></pre></div>
<p>I tend to commit changes frequently. If I go more than an hour or so without a commit, I get worried. The great thing about frequent commits is that it is easy to undo things. The bad thing about frequent commits is that your commit history has lots and lots of entries, but as nobody is going to see that history but me, I don't worry about that.</p>
<p>Now my boss calls and asks when the thing is going to be ready, and I tell him I'll check it into CVS right away. </p>
<p>First, I quit Visual Studio or whatever editor(s) I'm using, because the next few operations will cause the contents of files to change, and IDE's often don't handle that gracefully. This step also ensures that I don't forget to save all my changed files.</p>
<p>I have to merge the <code>develop</code> branch back into the <code>master</code> branch:</p>
<div class="highlight"><pre><span></span><code>git checkout master
git merge develop
</code></pre></div>
<p>Then, I pull in whatever changes others have added to CVS</p>
<div class="highlight"><pre><span></span><code>cvs update -d
git commit -am "Sync with CVS"
</code></pre></div>
<p>(I skip the <code>git commit</code> here if there were no changes from CVS.)</p>
<p>I check what I've changed since the <code>cvssync</code> tag, to remind myself and to verify it is right:</p>
<div class="highlight"><pre><span></span><code>git log cvssync..
cvs diff
</code></pre></div>
<p>Finally, I can push my changes to CVS:</p>
<div class="highlight"><pre><span></span><code>cvs commit -m "Implemented the whosey-whatsit"
</code></pre></div>
<p>My employer likes to put the <code>$Id$</code> tag into source files, so after a <code>cvs commit</code>, any committed files are going to have new identifiers, so I need to commit those changes to my <code>master</code> branch</p>
<div class="highlight"><pre><span></span><code>git commit -am "Sync with CVS"
</code></pre></div>
<p>I update my <code>cvssync</code> tag:</p>
<div class="highlight"><pre><span></span><code>git tag -f cvssync
</code></pre></div>
<p>If, while I'm doing my work, others check things into CVS, and I need those changes, here is what I generally do (after quitting Visual Studio):</p>
<div class="highlight"><pre><span></span><code>git commit -am "Check in work branch"
git checkout master
cvs update -d
git commit -am "Pull from CVS"
git checkout develop
git rebase master
</code></pre></div>
<p>It may seem like a lot of work, but it's saved me a couple of times already. I have some aliases and scripts that automate some of the steps, so my actual workflow is not as verbose as what I've written here.</p>Core Animation Performance Tips2010-05-18T10:36:44-04:002010-05-18T10:36:44-04:00Kristopher Johnsontag:undefinedvalue.com,2010-05-18:/core-animation-performance-tips.html<p>In my copious free time, I've been working on a videogame for the iPad. Friends and family may interject here that it seems like I'm <em>always</em> working on a videogame in my free time, but I've never actually finished one. This time is different. Really.</p>
<p>All of my personal projects …</p><p>In my copious free time, I've been working on a videogame for the iPad. Friends and family may interject here that it seems like I'm <em>always</em> working on a videogame in my free time, but I've never actually finished one. This time is different. Really.</p>
<p>All of my personal projects are intended primarily to be interesting and fun for me. I gave myself a couple of technical constraints to keep things challenging:</p>
<ul>
<li>All the code is well-factored idiomatic Objective-C. Unlike a lot of iPhone/iPad game programmers, I'm not writing all the guts in low-level C or C++ and then sprinkling a minimal amount of Cocoa on top to interface with the OS.</li>
<li>I'm using Core Animation as my "engine", rather than the OpenGL ES API or an off-the-shelf gaming engine. (Note: My game only needs a couple dozen sprites.)</li>
</ul>
<p>So far, things have worked out well. I was worried that using Objective-C and Core Animation might lead to performance issues on the iPad, but that hasn't been the case. I have run into a couple of issues with Core Animation that were pretty easy to fix.</p>
<!--break-->
<h3>Layer Creation Is Expensive</h3>
<p>My game's "sprites" are just Core Animation layers. When I initially implemented the game, I was creating layers and adding them to the view as needed, and then deleting them when they were no longer needed. This turned out to cause problems; a few frames would get dropped whenever a layer was created.</p>
<p>The fix for this was to create a pool of layers and add them all to the view at startup, and reuse those layers as needed. Unused layers get their <code>hidden</code> property set false.</p>
<h3>Watch Out for Misaligned Images</h3>
<p>If you run an app with Instruments with the Core Animation tool, it has an option to highlight "misaligned images." I couldn't find a lot of information on this, but apparently if you give layers positions that are not perfectly aligned to pixels, Core Animation does some anti-aliasing when rendering those layers, which degrades performance.</p>
<p>The easy fix is to just round all positions to whole pixels, via something like this:</p>
<div class="highlight"><pre><span></span><code><span class="nt">-</span><span class="w"> </span><span class="o">(</span><span class="nt">void</span><span class="o">)</span><span class="w"> </span><span class="nt">setSpritePosition</span><span class="o">:(</span><span class="nt">CGPoint</span><span class="o">)</span><span class="nt">position</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="err">CGPoint</span><span class="w"> </span><span class="err">alignedPosition</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="err">alignedPosition.x</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">floorf(0.5f</span><span class="w"> </span><span class="err">+</span><span class="w"> </span><span class="err">position.x)</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="err">alignedPosition.y</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">floorf(0.5f</span><span class="w"> </span><span class="err">+</span><span class="w"> </span><span class="err">position.y)</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="err">sprite.position</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">alignedPosition</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>This got rid of most of my misaligned images, but I do still have some sprites that get rotated or scaled via a transform, and such images are always misaligned, unless they are rotated by some multiple of 90 degrees. Thankfully, I only have a few such sprites.</p>My iPad Review2010-05-16T14:30:36-04:002010-05-16T14:30:36-04:00Kristopher Johnsontag:undefinedvalue.com,2010-05-16:/my-ipad-review.html<p>So, I've had an iPad for about a month and a half. Here are my impressions:</p>
<p>Overall, it's really nice. It fills the need for a little Internet-connected device that lets me watch video, read books, read online news, and browse the web. I used to keep my old 13-inch …</p><p>So, I've had an iPad for about a month and a half. Here are my impressions:</p>
<p>Overall, it's really nice. It fills the need for a little Internet-connected device that lets me watch video, read books, read online news, and browse the web. I used to keep my old 13-inch MacBook next to the couch for these purposes, but that MacBook is now in a closet. I've also put away my Sony Reader that I kept on the nightstand.</p>
<!--break-->
<p>I'm using it at work a lot more than I expected. My workstation has two monitors, but I find it handy to have programming documentation or technical books open on my iPad for reference. It essentially gives me a third monitor.</p>
<p>We take a lot of long car trips, and my wife really likes reading and web browsing with her 3G iPad in the passenger seat. It's a better experience than her MacBook Air.</p>
<p>The iPad is not a replacement for a laptop. When I need to do a lot of typing, or switch back and forth between applications, then I put down the iPad and pick up my laptop. But picking up a laptop is starting to seem like a chore. The iPad is not heavy, it's not hot, it doesn't have a whirring fan, it doesn't require me to assume an uncomfortable posture.</p>
<p>If you want a "computer," don't get an iPad: you'll be disappointed. Think of it more as a portable videogame console, or a Kindle on steroids. Saying that it is just a big iPhone is essentially correct, although many of the people who say that seemingly have no idea how useful a big iPhone could be. </p>
<p>The iPad is heavier than I expected. This is usually not a problem, but when playing any of those accelerometer-based games for an extended period, my arms get tired. On the plus side, I guess this gives me some exercise that I've been missing.</p>
<p>I use the Amazon Kindle app for iPad more than I use iBooks. I like the fact that with Kindle, I'm not locked in to Apple-only platforms. iBooks is prettier than the Kindle app, but the Kindle app is good enough. I need to stress here that I am talking about the Kindle <em>application</em> for <em>iPad</em>, not a Kindle device, which looks laughably archaic next to an iPad.</p>
<p>My only real complaint about the iPad is lack of multitasking, but that should be addressed in a few months with a new iPhone OS revision. (I have the iPhone OS 4 beta on my phone, and really, really miss the new features when using my iPad.)</p>
<p>In the month-and-a-half I've had the iPad, there has only been one instance where I went to a web page that didn't work due to lack of Flash. So that's not really a problem.</p>
<p>I've got the <a href="http://store.apple.com/us/product/MC361ZM/A">Apple iPad case</a>. It is functional, but it is ugly.</p>
<p>My iPad has survived one fall from a countertop to the floor, without any ill effects.</p>
<p>I hope that some nice Android or Palm tablets appear soon. Competition and choice would be good, but right now, there is really no competitor to the iPad. It does what it does better than anything else can.</p>
<p>As a software developer, I'm excited by the new user-interface expectations being ushered in by the iPad. This will finally get us away from the 1984 Macintosh-style WIMP interfaces that we've been stuck with for so long. I don't like a lot of Apple's policies, but they are leading the way here, and other vendors will copy the good stuff, so we will all benefit.</p>
<p>Some must-try apps:</p>
<ul>
<li><a href="http://itunes.apple.com/us/app/pandora-radio/id284035177?mt=8">Pandora Radio</a></li>
<li><a href="http://itunes.apple.com/us/app/netflix/id363590051?mt=8">Netflix</a></li>
<li><a href="http://itunes.apple.com/us/app/abc-player/id364191819?mt=8">ABC Player</a></li>
<li><a href="http://itunes.apple.com/us/app/the-guardian-eyewitness/id363993651?mt=8">The Guardian Eyewitness</a></li>
<li><a href="http://itunes.apple.com/us/app/nyt-editors-choice/id357066198?mt=8">NYT Editors' Choice</a></li>
<li><a href="http://itunes.apple.com/us/app/npr-for-ipad/id364183644?mt=8">NPR</a></li>
<li><a href="http://itunes.apple.com/us/app/goodreader-for-ipad/id363448914?mt=8">GoodReader</a></li>
<li><a href="http://itunes.apple.com/us/app/instapaper-pro/id288545208?mt=8">Instapaper Pro</a></li>
</ul>Looking for iPad Application Beta Testers2010-05-15T14:20:11-04:002010-05-15T14:20:11-04:00Kristopher Johnsontag:undefinedvalue.com,2010-05-15:/looking-ipad-application-beta-testers.html<p>Some time in the next few weeks, I hope to have an iPad game ready for submission to the App Store. I am looking for people to help me test the app.</p>
<!--break-->
<p>You need to have the following:</p>
<ul>
<li>An iPad</li>
<li>Somebody with whom to play the game (it's a two-player …</li></ul><p>Some time in the next few weeks, I hope to have an iPad game ready for submission to the App Store. I am looking for people to help me test the app.</p>
<!--break-->
<p>You need to have the following:</p>
<ul>
<li>An iPad</li>
<li>Somebody with whom to play the game (it's a two-player game, but you only need one iPad)</li>
</ul>
<p>To be a beta tester, you will have to give me your iPad's UDID (unique device identifier). To get that, follow these steps:</p>
<ol>
<li>Launch iTunes on your computer</li>
<li>Plug your iPad into the USB port and wait for iTunes to indicate it is connected</li>
<li>Select your iPad in the Devices list in iTunes and click the Summary tab</li>
<li>Click the word "Serial Number" next to the picture of your iPad. It will change to "Identifier (UDID)" and there will be a long string of letters and digits after it</li>
<li>From the Edit menu, choose the Copy command. (This will copy the letters and digits to the clipboard.)</li>
<li>Paste the UDID into an email to me, saying that you want to be a beta tester. If you don't know my email address, use the <a href="https://undefinedvalue.com/contact">Contact Me</a> form.</li>
</ol>Internet Emergency Checklist2010-03-30T21:06:02-04:002010-03-30T21:06:02-04:00Kristopher Johnsontag:undefinedvalue.com,2010-03-30:/internet-emergency-checklist.html<p>When the power goes out, nobody asks me how to make it work again. But if the Internet goes down for three seconds, everyone in the house is yelling at me, telling me I need to MAKE IT WORK! NOW!</p>
<!--break-->
<p>Most Internet problems are due to issues that have nothing …</p><p>When the power goes out, nobody asks me how to make it work again. But if the Internet goes down for three seconds, everyone in the house is yelling at me, telling me I need to MAKE IT WORK! NOW!</p>
<!--break-->
<p>Most Internet problems are due to issues that have nothing to do with me. I don't have any magical Internet-fixing powers. I just calmly go through a few steps until everything works. Most often, the steps I take don't really fix anything. The Internet generally fixes itself, without any help from me, but family members yell at me less if it looks like I'm taking action. (My grouchy "leave me alone; I'm working" demeanor helps too.)</p>
<p>I have a dream: I would like to watch an entire episode of <em>Lost</em> uninterrupted. I'm going to try to empower my family to "fix" these problems themselves, and I encourage others in my shoes to do the same.</p>
<p>In the spirit of the <a href="http://xkcd.com/627/">xkcd Tech Support Cheat Sheet</a>, here is the checklist that I plan to post next to each computer in the house. It's very simple; I don't expect anyone to check network settings, enter wi-fi passwords, or interpret blinking lights. It's really just a matter of turning things off and back on. Fill in the blanks as needed.</p>
<h2>Internet Emergency Checklist</h2>
<p>In the event that the Internet stops working, please take the following steps:</p>
<ol>
<li>Remain calm. It may seem like the end of the world, but remember that this has already happened 83 times this year, and somehow we survived.</li>
<li>Slowly count to 100, then try whatever you were doing again.</li>
<li>Close all the web browser windows you have open (Internet Explorer, Firefox, Safari, Mozilla). Also close any other programs you know are using the Internet (e-mail, online games, Twitter clients, etc.). Wait 30 seconds, then open your web browser to see if your home page appears.</li>
<li>Reboot your computer, and try again.</li>
<li>Check other computers in the house. If the Internet is working for them, but not on your computer, then turn off your computer, wait five minutes, turn it back on, wait five more minutes, and then try again.</li>
<li>Turn off the cable modem, the router, and your computer. The cable modem is the box that says <strong><em>_</em></strong><strong><em>_</em></strong><strong> on it. The router is the box that says <strong><em>_</em></strong><strong><em>_</em></strong></strong> on it. (If you can't find an on/off switch, just unplug the power cord.) Wait 60 seconds, then turn the cable modem back on. Wait 60 seconds, then turn the router back on. Wait 60 more seconds, then turn on your computer and try to do whatever you want to do.</li>
<li>If things still don't work, you can ask for help from the resident tech support expert. If help isn't available, call the Internet service provider's tech support at <strong><em>_</em></strong><strong><em>_</em></strong><strong>. When they ask for your phone number, don't give them your mobile phone number; give them the number associated with our account, which is <strong><em>_</em></strong><strong><em>_</em></strong></strong>.</li>
</ol>New Job, Old Job2010-03-26T13:31:54-04:002010-03-26T13:31:54-04:00Kristopher Johnsontag:undefinedvalue.com,2010-03-26:/new-job-old-job.html<p>For the last seven years, I've been working with a company that does gaming-related stuff (lotteries, casinos, race tracks). I worked for a few years as an employee, and later as a contractor. Like all jobs, it's had its ups and downs, but on the whole it was a good …</p><p>For the last seven years, I've been working with a company that does gaming-related stuff (lotteries, casinos, race tracks). I worked for a few years as an employee, and later as a contractor. Like all jobs, it's had its ups and downs, but on the whole it was a good experience.</p>
<p>I would have been happy to continue it, but a few weeks ago the company announced a "strategic partnership" with a European gaming company, with the intent of selling that other company's products in the US. That's probably good news for the company and its shareholders, but it's not good news for those of us who develop the products that are to be phased out.</p>
<p>It was pretty clear we would eventually be laid off, but it wasn't clear when that would happen in a few weeks, a few months, or a few years. I started putting out feelers, hoping I'd be able to jump before getting pushed.</p>
<!--break-->
<p>Lucky for me, a former boss was enthusiastic about re-hiring me, and so I'm now back at <a href="http://www.transcore.com/">TransCore</a>, my first employer. Right now, I'm working as a contractor, but the plan is to go permanent.</p>
<p>It's nice to see my former co-workers again. Their hair is a little grayer, but otherwise it doesn't seem much different from how it was when I left. They are still using the wiki I created. They are still using CVS (eeaagh!). The two guys who I hired are still working there. Code I wrote back in the early 1990's is still in production.</p>
<p>I'm looking forward to working again with a team of people I like, but another big plus to this job is that I'll be able to do most of it from home. I live in Dahlonega, GA, which is about fifty miles north of where any Atlanta-area programming jobs are, so this is a very good thing. My boss and other team members work a lot from home too, so I won't be the oddball guy who seems to never be around.</p>
<p>It's a Microsoft-heavy shop, so I'm having to re-learn a lot of the Windows development skills that I haven't used for a few years. Luckily (or unluckily), writing Win32 code with C++ hasn't changed much in the last decade, so I'm getting back up to speed in that area pretty quickly. I didn't do too much with .NET, but it looks like .NET has gotten a lot better during those years, so I'm looking forward to playing with that too.</p>
<p>I really hope this works out. I'm done with the "switch jobs every few years to get a wide range of experience" part of my career, and want to find a place where I can do good work and have influence over the company's direction. This company has smart people who care about the quality of their work, and managers who put value on experience and talent. That's exactly the kind of place where I'd like to stay.</p>Shiny iPhone Buttons Without Photoshop2010-02-27T12:16:43-05:002010-02-27T12:16:43-05:00Kristopher Johnsontag:undefinedvalue.com,2010-02-27:/shiny-iphone-buttons-without-photoshop.html<p><img style="float: right;" src="https://undefinedvalue.com/sites/undefinedvalue.com/files/GradientButtonsAppScreenshot.png" alt="Screenshot"></p>
<p>Newcomers to iPhone development are sometimes surprised at how ugly the standard button controls are. They quickly learn that they need a graphic artist to create a nice-looking button image in Photoshop and then attach that to the buttons. However, in this tutorial, I'll show how to create nice shiny …</p><p><img style="float: right;" src="https://undefinedvalue.com/sites/undefinedvalue.com/files/GradientButtonsAppScreenshot.png" alt="Screenshot"></p>
<p>Newcomers to iPhone development are sometimes surprised at how ugly the standard button controls are. They quickly learn that they need a graphic artist to create a nice-looking button image in Photoshop and then attach that to the buttons. However, in this tutorial, I'll show how to create nice shiny buttons in code, without any image files, by using a <code>CAGradientLayer</code>.</p>
<!--break-->
<p>(<em>Update (2010/5/20): Jeff LaMarche has posted code that is more capable than the code here, and I'd recommend taking a look at it. See his <a href="http://iphonedevelopment.blogspot.com/2010/05/improved-gradient-buttons.html">Improved Gradient Buttons</a> post.</em>)</p>
<p>(*Update (2014/11/20): Note that with iOS 7 and later, shiny curvy buttons are no longer typical in iOS user interfaces. The code described below should still work, but consider using the standard iOS button appearance instead.)</p>
<p>The source code can be downloaded here: <a href="http://bitbucket.org/KristopherJohnson/gradientbuttons/get/tip.zip">gradientbuttons-tip.zip</a>. It is also available on <a href="https://github.com/kristopherjohnson/gradientbuttons">GitHub</a>.</p>
<p>You can also follow along by looking at the source files in your web browser:</p>
<ul>
<li><a href="http://bitbucket.org/KristopherJohnson/gradientbuttons/src/tip/Classes/GradientButton.h">GradientButton.h</a></li>
<li><a href="http://bitbucket.org/KristopherJohnson/gradientbuttons/src/tip/Classes/GradientButton.m">GradientButton.m</a></li>
<li><a href="http://bitbucket.org/KristopherJohnson/gradientbuttons/src/tip/Classes/GradientButtonsViewController.h">GradientButtonsViewController.h</a></li>
<li><a href="http://bitbucket.org/KristopherJohnson/gradientbuttons/src/tip/Classes/GradientButtonsViewController.m">GradientButtonsViewController.m</a></li>
</ul>
<h3>Creating the Buttons in Interface Builder</h3>
<p><img style="float: left;" src="https://undefinedvalue.com/sites/undefinedvalue.com/files/GradientButtonsInterfaceBuilder.png" alt="Interface Builder screenshot"></p>
<p>We'll start by laying out a view containing buttons. In Xcode, create a new View-based iPhone application, and call it "GradientButtons" (or whatever you want). Double-click the <code>GradientButtonsViewController.xib</code> resource to open it in Interface Builder.</p>
<p>Drag a UIButton control from the library onto the view. Set its properties like this:</p>
<ul>
<li>Change the type to "Custom"</li>
<li>Set the title to "Gray"</li>
<li>Change the text color to white</li>
<li>Change the background color to gray</li>
</ul>
<p>If you want to get fancy, you can also change the font, set the shadow, and so on.</p>
<p>For purposes of this tutorial, I then created several copies of this button, and set each to have a different background color and title. You can skip that if you just want to see what a single button looks like.</p>
<p>Save the Interface Builder file, and then run the project in Xcode. It works, but where are those shiny buttons I promised?</p>
<h3>Adding the Shine Layer</h3>
<p>To give the buttons the glossy shine, we'll add a <code>CAGradientLayer</code> overlay to each button. The overlay will be partially transparent, so the underlying text and background color will show through.</p>
<p>Define a new class, <code>GradientButton</code>, derived from <code>UIButton</code>, like this:</p>
<div class="highlight"><pre><span></span><code><span class="c1">// GradientButton.h:</span>
<span class="cp">#import <UIKit/UIKit.h></span>
<span class="cp">#import <QuartzCore/QuartzCore.h></span>
<span class="k">@interface</span> <span class="nc">GradientButton</span> : <span class="bp">UIButton</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">CAGradientLayer</span><span class="w"> </span><span class="o">*</span><span class="n">shineLayer</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="bp">CALayer</span><span class="w"> </span><span class="o">*</span><span class="n">highlightLayer</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">@end</span><span class="w"></span>
</code></pre></div>
<p>The first thing we want to do is give the button rounded corners and a border. This is easily done by setting the button's root layer's <code>cornerRadius</code> and <code>masksToBounds</code> properties, and using a border color that is a transparent gray (so that the actual background color will show through).</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">initBorder</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">CALayer</span><span class="w"> </span><span class="o">*</span><span class="n">layer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">layer</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">layer</span><span class="p">.</span><span class="n">cornerRadius</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">8.0f</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">layer</span><span class="p">.</span><span class="n">masksToBounds</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">YES</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">layer</span><span class="p">.</span><span class="n">borderWidth</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">1.0f</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">layer</span><span class="p">.</span><span class="n">borderColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">colorWithWhite</span><span class="o">:</span><span class="mf">0.5f</span><span class="w"> </span><span class="n">alpha</span><span class="o">:</span><span class="mf">0.2f</span><span class="p">].</span><span class="n">CGColor</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Next, we'll create the shine layer. All we need to do is create a <code>CAGradientLayer</code>, set its frame to match the button's bounds, and set the <code>colors</code> and <code>locations</code> properties to arrays of colors and locations.</p>
<p>We set the gradient colors and locations like this:</p>
<ul>
<li>At the top, pure white with 40% opacity</li>
<li>At the middle, pure white with 20% opacity, giving us a nice shine on the top half of the button</li>
<li>Then, switch to a medium gray with 20% opacity, making the bottom half of the button darker than the top half</li>
<li>Finally, at the very bottom, go back to pure white with 40% opacity to give the appearance of a little inner glow</li>
</ul>
<p>Note: I came up with these parameters after a lot of trial and error, to make the shine look good against all the background colors in my sample app. You will probably want to tweak them for the specific colors and overall look of your application. Make it shinier, make it flatter, give things a warm orangish glow rather than white, whatever. It's your app; do what you want to do.</p>
<p>After setting those properties, we add the layer to the button's root layer.</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">addShineLayer</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">shineLayer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">CAGradientLayer</span><span class="w"> </span><span class="n">layer</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">shineLayer</span><span class="p">.</span><span class="n">frame</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">layer</span><span class="p">.</span><span class="n">bounds</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">shineLayer</span><span class="p">.</span><span class="n">colors</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">NSArray</span><span class="w"> </span><span class="n">arrayWithObjects</span><span class="o">:</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="kt">id</span><span class="p">)[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">colorWithWhite</span><span class="o">:</span><span class="mf">1.0f</span><span class="w"> </span><span class="n">alpha</span><span class="o">:</span><span class="mf">0.4f</span><span class="p">].</span><span class="n">CGColor</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="kt">id</span><span class="p">)[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">colorWithWhite</span><span class="o">:</span><span class="mf">1.0f</span><span class="w"> </span><span class="n">alpha</span><span class="o">:</span><span class="mf">0.2f</span><span class="p">].</span><span class="n">CGColor</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="kt">id</span><span class="p">)[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">colorWithWhite</span><span class="o">:</span><span class="mf">0.75f</span><span class="w"> </span><span class="n">alpha</span><span class="o">:</span><span class="mf">0.2f</span><span class="p">].</span><span class="n">CGColor</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="kt">id</span><span class="p">)[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">colorWithWhite</span><span class="o">:</span><span class="mf">0.4f</span><span class="w"> </span><span class="n">alpha</span><span class="o">:</span><span class="mf">0.2f</span><span class="p">].</span><span class="n">CGColor</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="kt">id</span><span class="p">)[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">colorWithWhite</span><span class="o">:</span><span class="mf">1.0f</span><span class="w"> </span><span class="n">alpha</span><span class="o">:</span><span class="mf">0.4f</span><span class="p">].</span><span class="n">CGColor</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nb">nil</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">shineLayer</span><span class="p">.</span><span class="n">locations</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">NSArray</span><span class="w"> </span><span class="n">arrayWithObjects</span><span class="o">:</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="bp">NSNumber</span><span class="w"> </span><span class="n">numberWithFloat</span><span class="o">:</span><span class="mf">0.0f</span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="bp">NSNumber</span><span class="w"> </span><span class="n">numberWithFloat</span><span class="o">:</span><span class="mf">0.5f</span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="bp">NSNumber</span><span class="w"> </span><span class="n">numberWithFloat</span><span class="o">:</span><span class="mf">0.5f</span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="bp">NSNumber</span><span class="w"> </span><span class="n">numberWithFloat</span><span class="o">:</span><span class="mf">0.8f</span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="bp">NSNumber</span><span class="w"> </span><span class="n">numberWithFloat</span><span class="o">:</span><span class="mf">1.0f</span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nb">nil</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">layer</span><span class="w"> </span><span class="n">addSublayer</span><span class="o">:</span><span class="n">shineLayer</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Note that, because we are adding the layer as a sublayer, we don't need to <code>retain</code> the <code>shineLayer</code> here.</p>
<p>With those methods written, now we just need to define an <code>awakeFromNib</code> method so that they will be called when the buttons are loaded from the NIB. I also override <code>initWithFrame:</code> so that my snazzy buttons can be created programmatically rather than being loaded from a NIB.</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">initLayers</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">initBorder</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">addShineLayer</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">awakeFromNib</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">initLayers</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithFrame:</span><span class="p">(</span><span class="n">CGRect</span><span class="p">)</span><span class="nv">frame</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">self</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">initWithFrame</span><span class="o">:</span><span class="n">frame</span><span class="p">])</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">initLayers</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">self</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>You'll need to add the <code>QuartzCore</code> framework to your project to get it to link.</p>
<p>The last thing we need to do is go back to Interface Builder and, for each button, go to the Identity inspector and change the class to <code>GradientButton</code>, then save.</p>
<p>With that done, build and run the app. Now we have pretty buttons. However, if you press a button, nothing seems to happen. We need to highlight the button when it is touched.</p>
<h3>Handling Touch Tracking</h3>
<p>Back when I told you to define the <code>GradientButton</code> class, you may have noticed the <code>highlightLayer</code> instance variable, and wondered what that was for. Well, here's the deal: when the user touches the button, we'll just show a layer that makes the button look highlighted, and when the user stops touching the button, we'll hide that layer. Simple!</p>
<p>So, we'll create a simple layer with a partially transparent gray color, set it to be hidden, and add it beneath the shine layer.</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">addHighlightLayer</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">highlightLayer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">CALayer</span><span class="w"> </span><span class="n">layer</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">highlightLayer</span><span class="p">.</span><span class="n">backgroundColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">colorWithRed</span><span class="o">:</span><span class="mf">0.25f</span><span class="w"></span>
<span class="w"> </span><span class="nl">green</span><span class="p">:</span><span class="mf">0.25f</span><span class="w"></span>
<span class="w"> </span><span class="nl">blue</span><span class="p">:</span><span class="mf">0.25f</span><span class="w"></span>
<span class="w"> </span><span class="nl">alpha</span><span class="p">:</span><span class="mf">0.75</span><span class="p">].</span><span class="n">CGColor</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">highlightLayer</span><span class="p">.</span><span class="n">frame</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">layer</span><span class="p">.</span><span class="n">bounds</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">highlightLayer</span><span class="p">.</span><span class="n">hidden</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">YES</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">layer</span><span class="w"> </span><span class="n">insertSublayer</span><span class="o">:</span><span class="n">highlightLayer</span><span class="w"> </span><span class="n">below</span><span class="o">:</span><span class="n">shineLayer</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>If you don't like gray, change the color as needed.</p>
<p>Then, we override the <code>setHighlighted:</code> method so that the highlight layer is displayed whenever the button is touched (or any other time its <code>highlighted</code> property would be set true):</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">setHighlighted:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">highlight</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">highlightLayer</span><span class="p">.</span><span class="n">hidden</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">!</span><span class="n">highlight</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">setHighlighted</span><span class="o">:</span><span class="n">highlight</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Note that because <code>hidden</code> is an animatable property, the highlight will fade in and fade out smoothly during a transition.</p>
<p>Finally, we need to change our <code>initLayers</code> method so that it calls <code>addHighlightLayer</code>:</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">initLayers</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">initBorder</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">addShineLayer</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">addHighlightLayer</span><span class="p">];</span><span class="w"> </span><span class="c1">// added line</span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Run the app, and the buttons should light up when you touch them.</p>
<h3>But, Do the Buttons Actually Work?</h3>
<p>Just to verify that our snazzy <code>GradientButton</code> class hasn't somehow broken the basic button functionality, add an event-handler method to the <code>GradientButtonsViewController</code> class.</p>
<div class="highlight"><pre><span></span><code><span class="c1">// GradientButtonsViewController.h:</span>
<span class="cp">#import <UIKit/UIKit.h></span>
<span class="k">@interface</span> <span class="nc">GradientButtonsViewController</span> : <span class="bp">UIViewController</span> <span class="p">{</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="kt">IBAction</span><span class="p">)</span><span class="nf">colorButtonClicked:</span><span class="p">(</span><span class="bp">UIButton</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">sender</span><span class="p">;</span><span class="w"></span>
<span class="k">@end</span><span class="w"></span>
</code></pre></div>
<p>The event handler will just set the view's background color to match the color of whatever button was clicked.</p>
<div class="highlight"><pre><span></span><code><span class="c1">// GradientButtonsViewController.m:</span>
<span class="cp">#import "GradientButtonsViewController.h"</span>
<span class="k">@implementation</span> <span class="nc">GradientButtonsViewController</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="kt">IBAction</span><span class="p">)</span><span class="nf">colorButtonClicked:</span><span class="p">(</span><span class="bp">UIButton</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">sender</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Set view's background to match button color</span>
<span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">backgroundColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sender</span><span class="p">.</span><span class="n">backgroundColor</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">@end</span><span class="w"></span>
</code></pre></div>
<p>Go back to Interface Builder and wire up the buttons to the controller's <code>colorButtonClicked:</code> handler.</p>
<p>Run the app, and you should see the background change whenever you click a button.</p>
<h3>Exercises for the Reader</h3>
<p>That's basically it, but you can extend these ideas in a lot of ways. For example:</p>
<ul>
<li>When the <code>enabled</code> property of the button gets set to false, you can "dim" the button by changing its opacity.</li>
<li>Use another layer to handle the button becoming <code>selected</code>.</li>
<li>Instead of using static layers, add some animation. For example, your shiny button could pulsate while idle, and grow slightly when touched.</li>
</ul>JacksOrBetter for iPhone Updated2010-02-17T01:10:27-05:002010-02-17T01:10:27-05:00Kristopher Johnsontag:undefinedvalue.com,2010-02-17:/jacksorbetter-iphone-updated.html<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/jb-screenshot-small.png" alt="JacksOrBetter Screenshot" style="float: right;"></p>
<p>Version 1.1 of JacksOrBetter for the iPhone and iPod Touch is now available, and it's still free. If you have an iPhone or iPod Touch, you can get it from <a href="http://itunes.apple.com/us/app/jacksorbetter-video-poker/id290542821?mt=8">the App Store</a>.</p>
<p>This version has a lot of cosmetic improvements over version 1.0. Version 1.0 was …</p><p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/jb-screenshot-small.png" alt="JacksOrBetter Screenshot" style="float: right;"></p>
<p>Version 1.1 of JacksOrBetter for the iPhone and iPod Touch is now available, and it's still free. If you have an iPhone or iPod Touch, you can get it from <a href="http://itunes.apple.com/us/app/jacksorbetter-video-poker/id290542821?mt=8">the App Store</a>.</p>
<p>This version has a lot of cosmetic improvements over version 1.0. Version 1.0 was, frankly, a little embarrassing, and I'm glad I finally got around to fixing it.</p>
<p>I created JacksOrBetter back in September 2008, when the App Store had only existed for a couple of months, and the iPhone App Store Gold Rush was on. The news was full of stories of iPhone developers who were making tens of thousands or hundreds of thousands of dollars from simple games. I wanted in on that action.</p>
<p>JacksOrBetter wasn't intended to make me a fortune; it was my first learning-how-to-write-an-iPhone-app exercise. I figured I'd whip it out, learn what I needed to learn to make and sell an iPhone app, and then I'd get to work on the apps I <em>really</em> wanted to write. (Those other apps are still in the conceptual stage, a year and a half later.)</p>
<p>At that time, there were only a few hundred apps in the App Store, and many were little better than JacksOrBetter. I initially charged $1.99 for it, and after a couple of weeks dropped the price to $0.99. I made about $280 over the next few months. Initially I was selling a dozen or so copies per day, but sales eventually fell to only a few per week, so I decided to make it free.</p>
<p>Since then, I've seen a lot of really nice iPhone apps, and I've learned a lot more about how to make them, so I finally decided to sit down and make JacksOrBetter what it should have been.</p>
<p>So now, it has nicer looking cards, a nicer background, and some cool animations of cards being dealt, discarded, and cleared at the end.</p>
<p>It's still pretty simple, but it no longer has that first-app stink.</p>
<p>There are a few more changes I want to make, but I've got this idea for an iPad app...</p>iPhone Sample Code: Tiles2010-02-06T04:28:21-05:002010-02-06T04:28:21-05:00Kristopher Johnsontag:undefinedvalue.com,2010-02-06:/iphone-sample-code-tiles.html<p>As an exercise in using the <a href="http://en.wikipedia.org/wiki/Core_Animation">Core Animation</a> API, I've implemented a little iPhone app that reproduces the behavior of the iPhone home screen's icon reorganization interface. (You know, dragging the wiggly icons around.) You can download my sample code to see how it works. Some descriptions of the highlights …</p><p>As an exercise in using the <a href="http://en.wikipedia.org/wiki/Core_Animation">Core Animation</a> API, I've implemented a little iPhone app that reproduces the behavior of the iPhone home screen's icon reorganization interface. (You know, dragging the wiggly icons around.) You can download my sample code to see how it works. Some descriptions of the highlights follow below.</p>
<!--break-->
<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/Tiles_Screenshot.png" alt="Screenshot" style="float: right;"></p>
<p>The source code and Xcode project can be downloaded here: <a href="https://github.com/kristopherjohnson/tilessample/zipball/master">tilessample</a>.</p>
<p>Source is also available on <a href="https://github.com/kristopherjohnson/tilessample">GitHub</a>.</p>
<p>The primary classes to look at are <a href="http://github.com/kristopherjohnson/tilessample/blob/master/Classes/TilesViewController.m"><code>TilesViewController</code></a> and <a href="http://github.com/kristopherjohnson/tilessample/blob/master/Classes/Tile.m"><code>Tile</code></a>. The view controller implements all of the "logic" of the application, while the <code>Tile</code> class has the animations.</p>
<p>An instance of <code>Tile</code> represents one of the icons, and is derived from <code>CAGradientLayer</code>. The gradient layer properties get set to provide a gloss effect for the tiles. <code>Tile</code> also provides a few animations, initiated by calling these methods:</p>
<ul>
<li><code>appearDraggable</code>: Changes the tile to be partially transparent, and makes it slightly bigger. This is invoked when the user touches a tile.</li>
<li><code>appearNormal</code>: Reverses the effects of <code>appearDraggable</code>. This is invoked when the tile is released.</li>
<li><code>startWiggling</code>: Starts a tile "wiggling", as in the iPhone home screen while in reorganization mode.</li>
<li><code>stopWiggling</code>: Stops the wiggling effect</li>
</ul>
<p>The <code>TilesViewController</code> class is pretty straightforward. When the user touches a the screen, the <code>touchesBegan</code> method determines which tile was touched, calls its <code>appearDraggable</code> method, and calls other tiles' <code>startWiggling</code> methods.</p>
<p>As the user drags the tile around the screen, the <code>touchesMoved</code> method moves the dragged tile, and moves the other tiles as needed to provide an open space for it. Core Animation takes care of all the zooming around of the icons.</p>
<p>When the user lets go of the tile, the <code>touchesEnded</code> method drops it in place and removes all the animations.</p>
<p>Things I learned from this project:</p>
<ul>
<li>Turning on the <code>masksToBounds</code> property for layers slows things down quite noticeably.</li>
<li>When hit-testing layers, you have to use a layer's presentation layer, not a model layer itself.</li>
<li><code>CAGradientLayer</code> is easy to use.</li>
</ul>
<p>Here are some things I don't understand. (Maybe some smart person can explain.)</p>
<ul>
<li>When hit-testing to see which layer was touched, I had to do both <code>[touch locationInView:view]</code> and <code>[view convertPoint:location toView:nil]</code>. However, when handling touch-moves, I only have to use <code>[touch locationInView:view]</code>. I don't understand why the coordinate systems are (apparently) different.</li>
</ul>
<hr>
<p><strong>Update: 2012-04-16:</strong> Changed the layer-initialization code so that it draws in high resolution on a Retina display. Updated the links above to point to GitHub. The tilessample repository on BitBucket is officially abandoned.</p>Thoughts on the iPad2010-01-29T23:55:57-05:002010-01-29T23:55:57-05:00Kristopher Johnsontag:undefinedvalue.com,2010-01-29:/thoughts-ipad.html<p>I like the iPad. A few friends and acquaintances accuse me of being stupid and easily fascinated by sparkly objects. Rather than have the same argument over and over again, I'm writing all my thoughts and predictions here. I will speak no further on the subject until I actually own …</p><p>I like the iPad. A few friends and acquaintances accuse me of being stupid and easily fascinated by sparkly objects. Rather than have the same argument over and over again, I'm writing all my thoughts and predictions here. I will speak no further on the subject until I actually own an iPad.</p>
<!--break-->
<p>Whenever I am on my couch at home, I have a laptop computer with me. I browse the web while my wife watches her girly TV shows. I pay bills. I deal with email. I go to IMDB to figure out where I've seen that actor before. I get directions to wherever we're going. I write blog posts.</p>
<p>Maybe there is something wrong with me, but I've grown dependent on having that electronic link with the world at arm's reach. Even in bed, I often have my laptop with me, so that I can read or watch a video when insomnia strikes.</p>
<p>But the laptop is not ideal for the job. I don't like having a hot, heavy, vibrating hunk of metal in my lap. I don't like the fan noise. I don't like the need to open it when I want it and close it when putting it aside. I don't like backing up the humongous hard drive. A trackpad is not a good substitute for a mouse. I don't like the need to memorize all the shortcut keys to be productive without a mouse. I don't like it when the dogs walk over the keyboard and mess things up.</p>
<p>More and more, I reach for my iPhone instead of my laptop, but the iPhone's tiny display has obvious limitations.</p>
<p>So, an iPad would fill a real need <em>for me</em> (using a First World definition of <em>need</em>). It looks like a tool that will let me browse the web, read, watch video, etc., without all the unnecessary clunkiness of a laptop. Believe it or not, I wanted something like this before Lord Jobs told me I wanted it.</p>
<p>If you don' t have that need, fine. I'm not trying to sell you anything. I'm just explaining why <strong>I</strong> would like to have an iPad, or something like it. (And there is currently nothing else like it.) </p>
<p>I believe a lot of other people will want one too. There are people like me who want the couch/bed experience. Anyone who has tried to use a laptop on an airplane will appreciate how much better the iPad would be for that situation. There will be people who want to take something to school, the library, or the coffee shop without lugging a laptop case around. The iPad will be a much better notebook than a "notebook computer".</p>
<p>For many people, an iPad will not be a good product. If you do a lot of writing and editing of text, iPad probably won't be a great thing. If you want to quickly switch among multiple applications, iPad probably won't be a great thing. If you need Microsoft Office or Photoshop or some other specific desktop application, iPad probably won't be a great thing. If you don't have a few hundred dollars of disposable income, iPad probably won't be a great thing. But that's OK—nobody is requiring you to replace your laptop with an iPad.</p>
<p>An iPad won't do everything a laptop does. It's not <em>supposed</em> to do everything a laptop does. The relationship between iPad and laptop is similar to the relationship between a microwave oven and a set of pots, pans, and cooking utensils. The pots, pans, and cooking utensils can produce more satisfying meals than the microwave can, but often you just don't want to drag all that stuff out and clean up afterward. A microwave is a good complement to a well-equipped kitchen.</p>
<p>Remember back when all of our moms and grandparents suddenly started buying computers? It was because they wanted to use the web and e-mail. That's still all that most of them do. They don't care about multi-tasking or writing their own software or enterprise integration. They shouldn't have had to learn the differences between left-click and right-click and double-click and control-click and shift-click. The iPad is what they needed back then. The iPad is what the original Mac was, thirty years later.</p>
<p>There is a lot I don't like about the iPad (What? The ultimate fanboy has criticisms?). Apple's locked-down control of the platform is definitely a problem. The iPad doesn't support a lot of functionality that it obviously should (multitasking, use of network printers, and so on), and Apple's policies prevent third-party developers from providing such functionality. The media DRM can be frustrating, but I think the content providers deserve more of the blame for that than Apple does. Objective-C is a relic from the 1980's, and Apple's development tools haven't really improved since they were NeXT's development tools.</p>
<p>It doesn't run Flash, but that doesn't bother me. While I do think it would be a more useful device if it did, and users should be able to install whatever crap software they'd like, I think websites that require Flash are like websites that require Internet Explorer 6. If Apple is hastening the day when we all laugh at such sites, than I applaud them.</p>
<p>It doesn't have a camera. So what? A computer needs a camera like a fish needs a bicycle.</p>
<p>Regarding multi-tasking: I actually like the idea of having a single app running at a time. That fits my own preferences for using my laptop; I often use the "Hide Others" menu item to minimize distractions. It annoys me when I have to open Activity Monitor to figure out which app is using 200% CPU. However, it would be really nice if Pandora or instant messengers could run in the background. If Apple won't give us full multitasking, I hope they will at least provide some sort of support for third-party background tasks in the near future.</p>
<p>Now on to some predictions. I do think Apple has created a new product category. Notice that every new smartphone looks a lot like an iPhone? The same is going to happen in this market segment. I think you'll see the netbook and e-reader markets shrink, and more vendors will provide things that look like the iPad.</p>
<p>I hope a strong competitor emerges, because we need choice. Apple is first out of the gate, and as with the iPhone, it will take a while before anyone else produces anything with the level of polish needed to be considered an alternative. The surprisingly low price is going to give Apple a pretty big user base pretty quickly.</p>
<p>The strong competitor won't be a Windows-based machine. Microsoft still thinks everything, from the smallest smartphone to the biggest enterprise server, should have a desktop-style Windows UI, with all of its complexity and anachronisms. Apple was very smart to base the iPad on the iPhone UI, rather than on Mac OS X UI. The consistency of Windows- and Mac OS-style UIs was a great thing 20 years ago, when people were first learning about personal computers, but experience with smartphones and videogame systems shows that people can pick up different UIs quickly, without need for a mouse, windows, menus, and other such things. UIs can and should be designed specifically for each different type of appliance, but Microsoft is going to keep giving us the Windows 95 UI everywhere. So we're going to see a lot of tablet-pad knockoffs that run Windows 7 Basic, and they will all suck.</p>
<p>Putting a desktop-style OS on iPad-style hardware is not a solution. Cool as the iPad hardware is, it's the OS that makes it what it is. If your OS is designed for a keyboard and mouse, then you are not competing with the iPad.</p>
<p>Maybe someone will make a good Android-based alternative, or maybe Google's Chrome OS on iPad-like hardware will work. Maybe someone will make Ubuntu touch-friendly. I'm doubtful, but we'll see.</p>
<p>Some say that the iPhone and iPad are the first steps on a path to a world where we will all be limited to using locked-down narrow-use devices, and nobody will have access to an open platform. I can't see this happening. Buying an iPad doesn't require you to turn your laptop in to the authorities. You can continue to do everything you could do before you had an iPad. There will always be Google, Microsoft, Linux, and/or other alternatives available if Apple does to its computers what it has done with its mobile devices. And more of our applications and data are going to live in The Cloud rather than on our devices, so it won't matter what rules the devices follow.</p>
<p>Ten years from now, I think the computers we use will look a lot more like the iPad than like the Macbook or a Wintel laptop, both in terms of hardware and software. Apple may not dominate the industry then, but the heritage will be obvious.</p>
<p>You can hop on the bandwagon now, or wait and hop on later. I don't care what you do. I'm not ashamed of being an early hopper.</p>432010-01-17T01:46:13-05:002010-01-17T01:46:13-05:00Kristopher Johnsontag:undefinedvalue.com,2010-01-17:/43.html<p>Another year older. I'm solidly in my forties now. It's not too bad.</p>
<p>We've had a few additions to the family in the past year: we bought two more Yorkshire Terriers, named <em>Boo</em> and <em>Tweezer</em>. About a week and half ago, Boo gave birth to a puppy whom we've named …</p><p>Another year older. I'm solidly in my forties now. It's not too bad.</p>
<p>We've had a few additions to the family in the past year: we bought two more Yorkshire Terriers, named <em>Boo</em> and <em>Tweezer</em>. About a week and half ago, Boo gave birth to a puppy whom we've named <em>Sparky</em>. He hasn't opened his eyes yet, but he's growing fast. So we now have four dogs, which is more than we really want, but I doubt we'll be able to part with any of them.</p>
<p>I've lost thirty pounds this year. I'd like to lose another twenty, but I'm glad I haven't regained what I've lost. As a result of the weight loss and diet, I no longer need medication to control hypertension or cholesterol. So I'm healthier than I was last year, and I hope that trend continues.</p>
<p>My wife decided to put a patio in the backyard this summer. The project grew a little beyond its initial scope, but the result is that we have a really nice backyard now. I like to go out there and read on weekends. It makes our little house feel a little bigger. I still really like it in <a href="http://en.wikipedia.org/wiki/Dahlonega,_Georgia">Dahlonega</a>.</p>
<p>I notice in my "42" entry that I said my career was in a rut, and it still is. However, I will have some opportunity to learn new things at work this year, so while it's still not what I want, at least it won't be completely boring.</p>
<p>Usually when I write my birthday blog post, I can review the last year's worth of blog postings to remind me of the things I did during the year. Unfortunately, this year I decided to limit my blog postings to programming- and technology-related topics, so I don't have a record of the really interesting things that have happened. But I do know that I'm happy, and that's all that really matters.</p>Objectified: Great Documentary2010-01-16T02:45:00-05:002010-01-16T02:45:00-05:00Kristopher Johnsontag:undefinedvalue.com,2010-01-16:/objectified-great-documentary.html<p>I just finished watching <em><a href="http://www.objectifiedfilm.com/">Objectified</a></em>, and can wholeheartedly recommend it to audiences of all ages.</p>
<p><em>Objectified</em> is about industrial designers. Those are the people who design all the stuff we buy. Look around you: that desk you're sitting at was <em>designed</em> by <em>somebody</em>. The mouse and keyboard were designed by …</p><p>I just finished watching <em><a href="http://www.objectifiedfilm.com/">Objectified</a></em>, and can wholeheartedly recommend it to audiences of all ages.</p>
<p><em>Objectified</em> is about industrial designers. Those are the people who design all the stuff we buy. Look around you: that desk you're sitting at was <em>designed</em> by <em>somebody</em>. The mouse and keyboard were designed by somebody. That lamp was designed by somebody. The chair you're sitting in was designed by somebody.</p>
<p>We often think that consumerism and mass production takes the human element out of life, but <em>Objectified</em> will make you look at all those little works of art that surround us. Sure, there may be ten million copies of each thing, made by people in sweatshops, but somewhere there was a person who thought about it and made a lot of decisions. What shape should it be? What color should it be? What texture should it have? Should the edges be sharp or rounded? What should it be made of? How will it be manufactured? How much will people pay for it? What should the packaging look like? How can it be disposed of?</p>
<p><em>Objectified</em> presents interviews with the people who do that. It may be a little geeky (how many people really care?), but it will give you a new appreciation for all that stuff you buy.</p>
<p>I rented it from iTunes. Next I'm going to watch <em><a href="http://www.helveticafilm.com/">Helvetica</a></em>, which was directed by the same guy.</p>JacksOrBetter as a Web App2010-01-02T14:38:01-05:002010-01-02T14:38:01-05:00Kristopher Johnsontag:undefinedvalue.com,2010-01-02:/jacksorbetter-web-app.html<p>A little over a year ago, I created my first iPhone app, <a href="https://undefinedvalue.com/2008/09/07/jacksorbetter-iphone-and-ipod-touch">JacksOrBetter</a>. As an exercise in learning CSS, JavaScript, and jQuery, I've created a web-based version of JacksOrBetter.</p>
<ul>
<li>http://jacksorbetterpoker.appspot.com/</li>
</ul>
<!--break-->
<p>It should work on any decent web browser (that is, one that has good implementations of JavaScript …</p><p>A little over a year ago, I created my first iPhone app, <a href="https://undefinedvalue.com/2008/09/07/jacksorbetter-iphone-and-ipod-touch">JacksOrBetter</a>. As an exercise in learning CSS, JavaScript, and jQuery, I've created a web-based version of JacksOrBetter.</p>
<ul>
<li>http://jacksorbetterpoker.appspot.com/</li>
</ul>
<!--break-->
<p>It should work on any decent web browser (that is, one that has good implementations of JavaScript and CSS2). It uses a few WebKit-specific features, so it looks a little better on Safari and Google Chrome than it does on Firefox and Internet Explorer. It doesn't require Flash or Java; it's just plain-old HTML, CSS, and JavaScript.</p>
<p>If you'd rather control it with the keyboard then by clicking buttons, you can use the space bar to deal/draw, and you can use the numeric keys 1, 2, 3, 4, and 5 to specify which cards to hold.</p>
<p>Implementing a jacks-or-better poker game is my standard exercise for learning a new programming language or development platform. It is simple, without being trivial. The user interface can be very simple or very complex, depending upon my mood.</p>
<p>While this app is basically functional now, there are still a lot of features and refinements to be added. For example, I need to show a payout table. The animations could also use improvement.</p>
<p>While this app works fine in a desktop browser, I'm really targeting it at the iPhone and similar platforms. Right now, it is a bit sluggish on iPhone. I'm going to start playing with <a href="http://www.jqtouch.com/">jQTouch</a> to give it more of a native-app feel.</p>
<p>One nifty feature that does work on the iPhone is that, after initial loading from the web, it can be run while offline.</p>
<p>I'd be interested to hear how well it works on Android and Palm Pre. It works on the emulators, but I don't have actual devices to test. Please let me know.</p>Bruce Schneier on Aviation Security2009-12-29T23:21:20-05:002009-12-29T23:21:20-05:00Kristopher Johnsontag:undefinedvalue.com,2009-12-29:/bruce-schneier-aviation-security.html<p>Computer security expert <a href="http://en.wikipedia.org/wiki/Bruce_Schneier">Bruce Schneier</a> has a really nice opinion piece on CNN:</p>
<ul>
<li><a href="http://www.cnn.com/2009/OPINION/12/29/schneier.air.travel.security.theater/index.html">Is aviation security mostly for show?</a></li>
</ul>
<p>One could wonder whether a computer-security expert is qualified to write about aviation security or national security, but what he says makes a lot of sense.</p>
<blockquote>
<p>Despite fearful rhetoric to the …</p></blockquote><p>Computer security expert <a href="http://en.wikipedia.org/wiki/Bruce_Schneier">Bruce Schneier</a> has a really nice opinion piece on CNN:</p>
<ul>
<li><a href="http://www.cnn.com/2009/OPINION/12/29/schneier.air.travel.security.theater/index.html">Is aviation security mostly for show?</a></li>
</ul>
<p>One could wonder whether a computer-security expert is qualified to write about aviation security or national security, but what he says makes a lot of sense.</p>
<blockquote>
<p>Despite fearful rhetoric to the contrary, terrorism is not a transcendent threat. A terrorist attack cannot possibly destroy a country's way of life; it's only our reaction to that attack that can do that kind of damage. The more we undermine our own laws, the more we convert our buildings into fortresses, the more we reduce the freedoms and liberties at the foundation of our societies, the more we're doing the terrorists' job for them.</p>
</blockquote>Ten New Year's Resolutions for Everyone2009-12-29T20:08:20-05:002009-12-29T20:08:20-05:00Kristopher Johnsontag:undefinedvalue.com,2009-12-29:/ten-new-years-resolutions-everyone.html<p>Being the arrogant lummox that I am, I've taken it upon myself to make a list of ten New Year's resolutions for everyone to follow.</p>
<ol>
<li>Be a better parent. Make time to spend with children (even if you have no children of your own).</li>
<li>Be a better spouse. Are you …</li></ol><p>Being the arrogant lummox that I am, I've taken it upon myself to make a list of ten New Year's resolutions for everyone to follow.</p>
<ol>
<li>Be a better parent. Make time to spend with children (even if you have no children of your own).</li>
<li>Be a better spouse. Are you the partner you promised you would be?</li>
<li>Be a better friend. Your life isn't all about you. Reach out to others.</li>
<li>Be a better citizen. Educate yourself about what's going on in your city, in your nation, and in the world. Ask questions. Respect those who disagree with you.</li>
<li>Take care of your health. Prepare healthy meals, get some exercise, take your prescribed medications, get a physical exam and vaccinations.</li>
<li>Put more into your work, but don't let your work take anything out of you.</li>
<li>Correspond with people. This doesn't mean forwarding e-mail jokes, posting your status on a web page, or ranting about what some politician has done. You need to write about meaningful things, and carefully read what others have written.</li>
<li>Examine your beliefs. How did you get them? What are they based on? Do they still make sense to you? Can you imagine what it would be like to hold different beliefs? Talk to someone who does have different beliefs.</li>
<li>Get rid of what you don't need, literally and metaphorically.</li>
<li>Relax more. You really do have the time.</li>
</ol>
<p>Now you'll have an answer when annoying people ask you what your resolutions are.</p>Detecting Bullshit on the Internet2009-12-29T18:10:23-05:002009-12-29T18:10:23-05:00Kristopher Johnsontag:undefinedvalue.com,2009-12-29:/detecting-bullshit-internet.html<p>It's amazing what people will believe. At least once a week, I'm forwarded a piece of information from a seemingly reasonable friend or family member that seems ridiculous. <em>Obama is a Muslim!</em> <em>The Department of Homeland Security is setting up death camps!</em> <em>Eat whatever you want and still lose weight …</em></p><p>It's amazing what people will believe. At least once a week, I'm forwarded a piece of information from a seemingly reasonable friend or family member that seems ridiculous. <em>Obama is a Muslim!</em> <em>The Department of Homeland Security is setting up death camps!</em> <em>Eat whatever you want and still lose weight!</em> <em>Cancer cured by prayer!</em> Etc., etc., etc.</p>
<p>It is usually easy to debunk such claims:</p>
<ul>
<li>Has the story been reported or repeated by any reputable news sources? If not, be skeptical.</li>
<li>Try Googling the first sentence or two of the story. This often brings up pages that demonstrate the story to be a hoax.</li>
<li>Try searching websites like http://snopes.com/, http://www.factcheck.org/, and http://skeptoid.com/ that have smart people who investigate suspicious claims.</li>
</ul>
<p>For some stories, the tiniest bits of critical thinking and research and should quickly lead to the conclusion that they are bogus. But it amazes me that people accept these stories without even <em>considering</em> that they may be untrue. Their "bullshit detectors" just don't work. They accept any negative story about people they don't like, and any positive story about people they do like. They reject mainstream media and other reputable sources in favor of quacks, cranks, and conspiracy theorists.</p>
<p>It is very easy to accept stories without question if they fit your existing beliefs, but we must always be on guard against such acceptance.</p>
<blockquote>
<p>The first principle is that you must not fool yourself — and you are the easiest person to fool.
— <a href="http://en.wikipedia.org/wiki/Richard_Feynman">Richard Feynman</a></p>
</blockquote>
<p>Carl Sagan, in his book <em>The Demon-Haunted World</em>, presented what he called his "<a href="http://www.xenu.net/archive/baloney_detection.html">Baloney Detection Kit</a>". It's a simple set of guidelines for testing the believability of assertions and arguments. If you haven't read it, please do, and apply it both to new stories and to your existing beliefs.</p>
<p>And, please, stop sending me this crap.</p>Are Web Apps the new BASIC?2009-12-22T02:38:05-05:002009-12-22T02:38:05-05:00Kristopher Johnsontag:undefinedvalue.com,2009-12-22:/are-web-apps-new-basic.html<p>I often wonder how kids today get into programming. When I was a kid, and got my first computer, I spent a lot of time typing in the programs from David Ahl's <em><a href="http://www.atariarchives.org/basicgames/">BASIC Computer Games</a></em>. After typing in the code for the games and playing them a bit, I'd start …</p><p>I often wonder how kids today get into programming. When I was a kid, and got my first computer, I spent a lot of time typing in the programs from David Ahl's <em><a href="http://www.atariarchives.org/basicgames/">BASIC Computer Games</a></em>. After typing in the code for the games and playing them a bit, I'd start changing them. Eventually, I got into 6502 assembly language and higher-level programming languages, but that early exposure to a bunch of simple BASIC programs is what got me started as a coder.</p>
<p>Today, kids are surrounded by programmable computers, but few of them learn how to write code. Most kids are no more interested in programming a computer than they would be in rebuilding a car's engine. I don't see a lot of simple projects like those in Ahl's book that they can play with and start hacking on. How does a kid get started?</p>
<p>There are programming environments that are intended to teach kids programming (<a href="http://scratch.mit.edu/">Scratch</a>, <a href="http://www.alice.org/">Alice</a>, etc.). But use of those is not widespread, and I question their usefulness. All the good programmers I know started with something primitive, like BASIC, Fortran, Perl, or command.com. I wonder if somebody can really learn how computers work by working with fancy 3D "authoring environments" like Alice and Squeak.</p>
<p>It's occurred to me that web applications might be one way to get into programming. Web browsers are as ubiquitous as BASIC was in the 80's. It's relatively easy for a kid to set up a web site with Drupal or some other off-the-shelf CMS, and then start customizing it. All they need is Notepad and an Internet connection. They learn a little CSS here, a little JavaScript there, and pretty soon they'd rather be coding than playing games. </p>
<p>Is there a web-app equivalent to <em>BASIC Computer Games</em>? If not, there should be.</p>Playing Catch-Up2009-12-21T15:12:21-05:002009-12-21T15:12:21-05:00Kristopher Johnsontag:undefinedvalue.com,2009-12-21:/playing-catch.html<p>Early in my career, I prided myself on my ability to absorb a lot of information and stay up-to-date on programming tools and techniques. I learned C++ before any of my colleagues did, and knew more about its intricacies than they did. I implemented ActiveX control containers before most people …</p><p>Early in my career, I prided myself on my ability to absorb a lot of information and stay up-to-date on programming tools and techniques. I learned C++ before any of my colleagues did, and knew more about its intricacies than they did. I implemented ActiveX control containers before most people knew what "ActiveX" or "COM" was. I dug into CORBA. I played around with Java 1.0.</p>
<p>In short, I liked being an expert on new stuff. I enjoyed being on the bleeding edge. I liked being able to answer everyone's questions.</p>
<p>However, now I find myself on the other side of things. I'm bringing myself up to speed on some relatively "old" stuff: Java, JavaScript, and Cascading Style Sheets (CSS). It sometimes feels like I'm the only person who doesn't already know this stuff, and I'm not comfortable being the newbie.</p>
<p>There are benefits to jumping on the bandwagon late. For example, JavaScript actually works now, and most people have browsers with decent implementations of DOM and CSS. A lot of that J2EE silliness has quietly faded away. I can take advantage of lots of libraries written by experienced people, and there are plenty of people who can answer my questions.</p>
<p>Still, I'm nagged by the feeling that I'm <em>behind</em>, and won't ever be able to be one of the foremost experts or significant contributors to the communities. Maybe someday I'll be mature enough to accept that, but I'm not there yet.</p>Are Web Developers Real Programmers?2009-12-09T13:23:01-05:002009-12-09T13:23:01-05:00Kristopher Johnsontag:undefinedvalue.com,2009-12-09:/are-web-developers-real-programmers.html<p>The <a href="https://undefinedvalue.com/2009/11/25/native-apps-vs-web-apps">native-vs.-webapp hubbub</a> from a couple of weeks ago got me thinking about why "web developers" are looked down upon by "real programmers."</p>
<!--break-->
<p>I'll admit that I've held that prejudice myself. I looked at client-side web development back in the mid 1990's and decided that it wasn't for me …</p><p>The <a href="https://undefinedvalue.com/2009/11/25/native-apps-vs-web-apps">native-vs.-webapp hubbub</a> from a couple of weeks ago got me thinking about why "web developers" are looked down upon by "real programmers."</p>
<!--break-->
<p>I'll admit that I've held that prejudice myself. I looked at client-side web development back in the mid 1990's and decided that it wasn't for me. The web browsers of the time were primitive, and every one acted differently. The situation was a big mess.</p>
<p>I learned to be a programmer back in the days when, if you read the programming language manual, the operating system manual, and the library manual, then you could actually write code and expect it to work the first time you ran it. The machine was your servant. If the code you wrote didn't work, it was probably because you had screwed up.</p>
<p>In contrast, programming for web browsers requires a lot of experimentation, guesswork, vendor-specific APIs, and hacky workarounds. The code one wrote usually didn't work the first time, no matter how smart and careful the author was. The browser held a lot of secrets, and thus was the master.</p>
<p>How could any <em>real programmer</em> get involved in technology that mostly didn't work, and had no formal specifications nor complete documentation? This led to my belief that all the people writing scripts for browsers and calling them "applications" were merely programmer-wanna-bes. They may have been able to hack things together, but they didn't have that <em>real programmer</em> mindset.</p>
<p>Obviously, things have changed a lot since then. Google Maps was the web application that made a lot of us wake up and notice that browsers weren't just HTML displayers any more. AJAX took hold as an "enterprise technology." Still, a lot of us old-timers continued to consider JavaScript to be a toy language, and the many incompatibilities between browsers meant it still could not be considered a stable platform for serious programming. And any user interface platform that doesn't have a standard graphics API is just pathetic.</p>
<p>Then again, to be completely honest, I was just too lazy to learn more about it, and I was making my living by writing native applications. It was all too easy to just dismiss it as something not worth wasting time on.</p>
<p>Now that I've spent a couple of weeks with JavaScript and related stuff, I've changed my mind. One can be a real programmer with JavaScript. It's actually a pretty cool little language, incorporating aspects of Scheme and Self. What makes JavaScript programming a mess is cross-browser incompatibility and the fact that most JavaScript is just cut-and-pasted by people who don't know what they are doing.</p>
<p>Browsers have come a long way too. It used to be that you could create a sophisticated UI only by implementing a Java applet (yuck) or using Flash (yuck). <a href="http://en.wikipedia.org/wiki/HTML5">HTML5</a> is finally making it possible for web developers to <em>draw things on the screen</em> (!!!) without resorting to such madness. Web browsers now provide a platform similar to what personal computers provided twenty years ago. It's taken a while, but there are no more excuses for dismissing the web browser as a primitive toy.</p>
<p>It's now pretty easy for someone to create a slick website by installing Drupal, Joomla, Zope, or something similar, and then customizing it for a client's needs. However, the people who do that are not <em>real programmers</em>.</p>
<p>What distinguishes a <em>real programmer</em> from all the rest of the people who can create a web site?</p>
<ul>
<li>Can you write your own HTML and CSS, or do you just use prepackaged themes?</li>
<li>Do you write your own JavaScript code, or do you just cut-and-paste snippets you find via Google?</li>
<li>Can you read and write JavaScript, or do you have to rely on some higher-level abstraction (Google Web Toolkit, Cappucino, etc.)?</li>
<li>When something doesn't work, can you debug and fix it, or do you just have to throw it away and try something else?</li>
<li>Do you refer to the official specs for HTML, CSS, and JavaScript, or do you just make a lot of guesses?</li>
<li>Do you try to write portable code and adhere to standards, or are you happy with whatever works for 80% of your users?</li>
<li>Do you design, develop, and test your code in modular fashion, or just throw the whole mess together?</li>
<li>Do you adhere to coding standards and try to write code that is readable and maintainable?</li>
<li>Do you <em>care</em> about the code you write?</li>
</ul>
<p>In short, can you create your own software components, or can you only glue together components created by others? If you're a creator, then you're a <em>real programmer</em>.</p>Quick-and-Dirty Guide to QUnit2009-12-06T22:07:52-05:002009-12-06T22:07:52-05:00Kristopher Johnsontag:undefinedvalue.com,2009-12-06:/quick-and-dirty-guide-qunit.html<p>I'm playing around with <a href="http://en.wikipedia.org/wiki/Javascript">JavaScript</a> in my spare time, and have started creating a web app. As I usually do, particularly when learning something new, I am using a <a href="http://en.wikipedia.org/wiki/Test-driven_development">test-driven development</a> approach. I looked at a few JavaScript unit-testing frameworks, and decided to go with <a href="http://docs.jquery.com/QUnit">QUnit</a>, the testing framework used …</p><p>I'm playing around with <a href="http://en.wikipedia.org/wiki/Javascript">JavaScript</a> in my spare time, and have started creating a web app. As I usually do, particularly when learning something new, I am using a <a href="http://en.wikipedia.org/wiki/Test-driven_development">test-driven development</a> approach. I looked at a few JavaScript unit-testing frameworks, and decided to go with <a href="http://docs.jquery.com/QUnit">QUnit</a>, the testing framework used by the jQuery project.</p>
<p>QUnit isn't too hard to set up or use, but my unfamiliarity with JavaScript, jQuery, and related things meant it took a little more work than it should have. A few out-of-date QUnit tutorials on the web made things worse. So, here is a quick-and-dirty QUnit tutorial that might be helpful for others who are the same boat that I was in.</p>
<!--break-->
<h3>Download Library Files</h3>
<p>First, we need to download jQuery and the two files that make up QUnit. Here are the links:</p>
<ul>
<li><a href="http://jquery.com/">jquery.js</a> (Click the big <strong>Download</strong> button on the right side of that page.)</li>
<li><a href="http://github.com/jquery/qunit/raw/master/qunit/qunit.js">qunit.js</a></li>
<li><a href="http://github.com/jquery/qunit/raw/master/qunit/qunit.css">qunit.css</a></li>
</ul>
<p><strong>Note:</strong> When you download jQuery, you'll end up with a file called <code>jquery-1.3.2.min.js</code>, or something like that. I rename it to <code>jquery.js</code> to keep things simple.</p>
<p>We'll put all three files into the directory where the JavaScript code is going to be. Of course, in real life you can put them wherever you want.</p>
<h3>Production Code to be Tested</h3>
<p>Next, we will need some JavaScript code to test. Some poor confused souls put their JavaScript code and tests in their HTML files, but we're going to do things The Right Way and put the production code and the test code into their own files.</p>
<p>Here is our production module, <code>calculator.js</code>, which provides sophisticated mathematical functionality:</p>
<div class="highlight"><pre><span></span><code><span class="o">//</span><span class="w"> </span><span class="n">calculator</span><span class="o">.</span><span class="n">js</span><span class="w"></span>
<span class="k">var</span><span class="w"> </span><span class="n">Calculator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">this</span><span class="o">.</span><span class="n">add</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">b</span><span class="p">;</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="n">this</span><span class="o">.</span><span class="n">subtract</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">b</span><span class="p">;</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="n">this</span><span class="o">.</span><span class="n">multiply</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">b</span><span class="p">;</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="n">this</span><span class="o">.</span><span class="n">divide</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">b</span><span class="p">;</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="p">};</span><span class="w"></span>
</code></pre></div>
<h3>Test Cases</h3>
<p>Here are our tests, in <code>calculator_tests.js</code>:</p>
<div class="highlight"><pre><span></span><code><span class="o">//</span><span class="w"> </span><span class="n">calculator_tests</span><span class="o">.</span><span class="n">js</span><span class="w"></span>
<span class="n">Calculator</span><span class="o">.</span><span class="n">runTests</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">test</span><span class="p">(</span><span class="s2">"add"</span><span class="p">,</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">var</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new</span><span class="w"> </span><span class="n">Calculator</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">equals</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">),</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="s2">"1 + 1"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">equals</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">),</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="s2">"2 + 2"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="w"> </span><span class="n">test</span><span class="p">(</span><span class="s2">"subtract"</span><span class="p">,</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">var</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new</span><span class="w"> </span><span class="n">Calculator</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">equals</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">subtract</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">),</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s2">"1 - 1"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">equals</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">subtract</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">),</span><span class="w"> </span><span class="mi">99</span><span class="p">,</span><span class="w"> </span><span class="s2">"100 - 1"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="w"> </span><span class="n">test</span><span class="p">(</span><span class="s2">"multiply"</span><span class="p">,</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">var</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new</span><span class="w"> </span><span class="n">Calculator</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">equals</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">multiply</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">),</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s2">"1 * 1"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">equals</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">multiply</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">),</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="s2">"2 * 2"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">equals</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">multiply</span><span class="p">(</span><span class="mi">17</span><span class="p">,</span><span class="w"> </span><span class="mi">23</span><span class="p">),</span><span class="w"> </span><span class="mi">391</span><span class="p">,</span><span class="w"> </span><span class="s2">"17 * 23"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="w"> </span><span class="n">test</span><span class="p">(</span><span class="s2">"divide"</span><span class="p">,</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">var</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new</span><span class="w"> </span><span class="n">Calculator</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">equals</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">divide</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">),</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s2">"1 / 1"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">equals</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">divide</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">),</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="s2">"8 / 2"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">equals</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">divide</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">),</span><span class="w"> </span><span class="n">Infinity</span><span class="p">,</span><span class="w"> </span><span class="s2">"1 / 0"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="p">};</span><span class="w"></span>
</code></pre></div>
<p>Here, I've made my test function a member of the module I'm testing, but you can define it however you want.</p>
<p>See the <a href="http://docs.jquery.com/QUnit#API_documentation">QUnit API documentation</a> for info about the test functions. All you need to know to understand the above is that <code>test(name, function)</code> creates a test case, and <code>equals(actual, expected, message)</code> checks equivalence of results.</p>
<h3>HTML Test Runner</h3>
<p>Finally, we need an HTML file, <code>calculator_tests.html</code>, which loads all the modules, runs the tests, and displays the results in a web browser:</p>
<div class="highlight"><pre><span></span><code><span class="nt"><html></span>
<span class="nt"><head></span>
<span class="cm"><!-- Load jQuery and QUnit --></span>
<span class="nt"><script</span> <span class="na">src=</span><span class="s">"jquery.js"</span><span class="nt">></script></span>
<span class="nt"><script</span> <span class="na">src=</span><span class="s">"qunit.js"</span><span class="nt">></script></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"qunit.css"</span> <span class="na">type=</span><span class="s">"text/css"</span> <span class="na">media=</span><span class="s">"screen"</span> <span class="nt">/></span>
<span class="cm"><!-- Load modules to be tested --></span>
<span class="nt"><script</span> <span class="na">src=</span><span class="s">"calculator.js"</span><span class="nt">></script></span>
<span class="nt"><script</span> <span class="na">src=</span><span class="s">"calculator_tests.js"</span><span class="nt">></script></span>
<span class="cm"><!-- This jQuery fragment calls Calculator.runTests() after the document loads --></span>
<span class="nt"><script></span>
$(document).ready(function(){
Calculator.runTests();
});
<span class="nt"></script></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="cm"><!-- QUnit will put the results in the elements here --></span>
<span class="nt"><h1</span> <span class="na">id=</span><span class="s">"qunit-header"</span><span class="nt">></span>Calculator Tests<span class="nt"></h1></span>
<span class="nt"><h2</span> <span class="na">id=</span><span class="s">"qunit-banner"</span><span class="nt">></h2></span>
<span class="nt"><h2</span> <span class="na">id=</span><span class="s">"qunit-userAgent"</span><span class="nt">></h2></span>
<span class="nt"><ol</span> <span class="na">id=</span><span class="s">"qunit-tests"</span><span class="nt">></ol></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div>
<p>With all those files in place, just open <code>calculator_tests.html</code> in a web browser, and it will show the results. Successful tests will be green, and failed tests will be red. You can click the individual tests to get detailed lists of the assertions.</p>
<p>So, that's it. Read the QUnit docs, and start writing some tests.</p>Stack Overflow Careers2009-12-06T14:38:08-05:002009-12-06T14:38:08-05:00Kristopher Johnsontag:undefinedvalue.com,2009-12-06:/stack-overflow-careers.html<p>Finding a good job, or finding good employees, has always sucked. The guys at <a href="http://stackoverflow.com">Stack Overflow</a> are trying to make it suck a little less with their new service: <a href="http://careers.stackoverflow.com/">Stack Overflow Careers</a>.</p>
<p>What differentiates Stack Overflow Careers from all the other job-board web sites is that candidates' CV's are linked …</p><p>Finding a good job, or finding good employees, has always sucked. The guys at <a href="http://stackoverflow.com">Stack Overflow</a> are trying to make it suck a little less with their new service: <a href="http://careers.stackoverflow.com/">Stack Overflow Careers</a>.</p>
<p>What differentiates Stack Overflow Careers from all the other job-board web sites is that candidates' CV's are linked to their Stack Overflow accounts, so an employer can get a feel for how smart a person is by checking their SO reputation and reading their questions and answers. They are also trying to avoid a lot of the annoyances that plague other job-search sites. It is also hoped that the association with Stack Overflow will attract the best candidates and employers.</p>
<p>I've set up my CV: http://careers.stackoverflow.com/kristopherjohnson. The process was pretty easy.</p>
<p>It costs $29 to enter your CV into the employee-search database, and employers have to pay a pretty steep price to search it. I think that's a good thing; it keeps things serious. (Note: You can create a public CV for free, but it won't show up in employer's searches.)</p>
<p>I'm not actively looking for a job, but if anyone wants to pay me huge gobs of money to play with cool stuf, I'm definitely available.</p>Not Quite NaNoWriMo Review2009-12-01T10:38:23-05:002009-12-01T10:38:23-05:00Kristopher Johnsontag:undefinedvalue.com,2009-12-01:/not-quite-nanowrimo-review.html<p>Last month, I <a href="https://undefinedvalue.com/2009/11/01/not-quite-nanowrimo">committed to do a lot of writing</a>. Let's see how I did.</p>
<p>I was going to write one blog entry per day. I actually did 22 entries during the 30-day month of November. Not 100%, but not too bad.</p>
<p>My other commitment was to get a new …</p><p>Last month, I <a href="https://undefinedvalue.com/2009/11/01/not-quite-nanowrimo">committed to do a lot of writing</a>. Let's see how I did.</p>
<p>I was going to write one blog entry per day. I actually did 22 entries during the 30-day month of November. Not 100%, but not too bad.</p>
<p>My other commitment was to get a new iPhone app into the App Store. That didn't work out. I did work on an app, but as it progressed, I came to the conclusion that it wasn't worth finishing. So instead of spending that time working on an app nobody would use, I spent time learning about JavaScript.</p>
<p>So, I didn't really meet either commitment, but I'm OK with that. I don't mind letting myself down.</p>Switched from Firefox to Safari2009-11-30T13:31:45-05:002009-11-30T13:31:45-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-30:/switched-firefox-safari.html<p>For the past week, I've been using <a href="http://www.apple.com/safari/">Safari</a> instead of <a href="http://www.mozilla.com/firefox/">Firefox</a>, and I've decided to make the change permanent.</p>
<p>Why? Firefox is just too slow. Remember the good old days when Firefox was the leaner, faster, less-bloated alternative to Netscape Navigator? Those days are in the past. Firefox now takes …</p><p>For the past week, I've been using <a href="http://www.apple.com/safari/">Safari</a> instead of <a href="http://www.mozilla.com/firefox/">Firefox</a>, and I've decided to make the change permanent.</p>
<p>Why? Firefox is just too slow. Remember the good old days when Firefox was the leaner, faster, less-bloated alternative to Netscape Navigator? Those days are in the past. Firefox now takes over ten seconds to show a window after I launch it, whereas Safari is up immediately. Safari is also faster at rendering pages, and at just about everything else. It takes up a fraction of the memory of Firefox, and has not yet started using 100% CPU for no reason at all, as Firefox tends to do.</p>
<p>The thing that Firefox has that other browsers can't match is the huge set of add-ons (extensions, themes, etc.). While many of these are really cool, there are really only two that I consider must-haves:</p>
<ul>
<li><a href="http://adblockplus.org/en/">Adblock Plus</a></li>
<li><a href="https://addons.mozilla.org/en-US/firefox/addon/469">PasswordMaker</a></li>
</ul>
<p>The lack of Adblock Plus is noticeable right away. Web sites that I visit frequently are suddenly awash in ads. It's very distracting. There are a couple of Safari extensions that supposedly work like Adblock Plus on Firefox, but they don't work as well. Most Safari users seem to be happy with simply blocking Flash ads with <a href="http://rentzsch.github.com/clicktoflash/">ClickToFlash</a>, but I'm not satisfied with that. I've just started a trial of <a href="http://glimmerblocker.org/">GlimmerBlocker</a>, which seems pretty good so far.</p>
<p>PasswordMaker is a nifty little Firefox extension that automatically fills in password fields with passwords that are unique to each site, generated by hashing a master password with the site's name. This is great, because I haven't had to remember any passwords with Firefox, but it's bad in that I now have to teach Safari all those passwords, and I have to do a lot of manual copying and pasting.</p>
<p>With my new foray into web technologies, maybe I'll devote some time to trying to get these essential Firefox extensions ported over to Safari.</p>
<p>Aside from the missing add-ons, I also miss Firefox's <a href="http://www.linuxjournal.com/node/1005889">keyword search</a> functionality. There is a Safari extension, <a href="http://alexstaubo.github.com/keywurl/">Keywurl</a>, that supposedly provides this functionality, but it is not yet compatible with Snow Leopard. (If you search hard enough, however, you can find some unofficial 64-bit builds.)</p>
<p>Unfortunately, Safari does not actually have a supported plug-in/extension mechanism, so most of these extensions are hacks that can crash Safari or which suddenly stop working whenever there is a system update. It's a shame that so many products today are not designed to be extensible or user-programmable.</p>
<p>Aside from those little missing features, I'm happy with Safari. (For now, anyway.)</p>JavaScript: The Good Parts2009-11-29T21:57:34-05:002009-11-29T21:57:34-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-29:/javascript-good-parts.html<p>As a result of the recent <a href="https://undefinedvalue.com/2009/11/25/native-apps-vs-web-apps">hubbub about web apps</a>, I decided to get myself up to speed on JavaScript and CSS. Knowing <a href="http://www.crockford.com/javascript/">Douglas Crockford's</a> reputation as the JavaScript guru, I read his book, entitled <em><a href="http://oreilly.com/catalog/9780596517748">JavaScript: The Good Parts</a></em>.</p>
<p>It's a good book. The basic idea is that while JavaScript …</p><p>As a result of the recent <a href="https://undefinedvalue.com/2009/11/25/native-apps-vs-web-apps">hubbub about web apps</a>, I decided to get myself up to speed on JavaScript and CSS. Knowing <a href="http://www.crockford.com/javascript/">Douglas Crockford's</a> reputation as the JavaScript guru, I read his book, entitled <em><a href="http://oreilly.com/catalog/9780596517748">JavaScript: The Good Parts</a></em>.</p>
<p>It's a good book. The basic idea is that while JavaScript is actually a pretty cool little programming language, it has a lot of features that are best not used, and it has many outright flaws, so Crockford presents a recommended subset of the language.</p>
<p>The most valuable parts of the book are appendices A and B, entitled "Awful Parts" and "Bad Parts", respectively. These appendices list the gotchas of JavaScript and present rationale for leaving certain constructs out of the recommended subset.</p>
<p>My only gripe with the book is that, while it is presented as an introductory book, it seems to assume some previous knowledge and experience with JavaScript. I got a bit lost in some parts, particularly those regarding prototypes, the <code>new</code> keyword, and how <code>this</code> gets bound in various situations. (I was able to eventually figure these things out with a bit of Googling.) It also assumes some experience with functional programming, which is OK with me, but which will probably confuse a lot of introductory readers.</p>
<p>So, while I can enthusiastically recommend the book, I think I'd recommend it as a <em>second</em> JavaScript book.</p>The Houben Case and Facilitated Communication2009-11-26T10:08:20-05:002009-11-26T10:08:20-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-26:/houben-case-and-facilitated-communication.html<p>The media has been reporting that a Belgian man who has been in a coma for 23 years is now able to communicate thanks to "assistance" from an aide who holds his hand while he types on a keyboard (or actually, while <em>they</em> type on a keyboard).</p>
<p>Here are a …</p><p>The media has been reporting that a Belgian man who has been in a coma for 23 years is now able to communicate thanks to "assistance" from an aide who holds his hand while he types on a keyboard (or actually, while <em>they</em> type on a keyboard).</p>
<p>Here are a couple of good articles about what seems fishy about these claims, and why "facilitated communication" is not taken seriously by most of the scientific community:</p>
<ul>
<li>Wired.com: <a href="http://www.wired.com/wiredscience/2009/11/houben-communication/">Reborn Coma Man's Words May Be Bogus</a></li>
<li>Science-based Medicine: <a href="http://www.sciencebasedmedicine.org/?p=2838">Man in Coma 23 Years – Is He Really Conscious?</a></li>
</ul>
<p>And, finally, there is <a href="http://www.deredactie.be/cm/vrtnieuws/mediatheek/programmas/terzake/2.7992/2.7993/1.640381">video of Houben and his "assistant" typing while Houben's eyes are closed</a>. It's amazing that a man so disabled is able to type with one finger with his eyes closed. I can't do it. I wonder how well he would do if the assistant's eyes were closed.</p>iPhone Video Output2009-11-26T09:30:19-05:002009-11-26T09:30:19-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-26:/iphone-video-output.html<p>A common problem for iPhone developers is demonstrating the apps they've developed to others. Showing it on an actual device only works well if you are showing it to one or two people. Showing it in the iPhone Simulator running on a Mac takes a lot of setup, and may …</p><p>A common problem for iPhone developers is demonstrating the apps they've developed to others. Showing it on an actual device only works well if you are showing it to one or two people. Showing it in the iPhone Simulator running on a Mac takes a lot of setup, and may not work if the app depends on things you can't do in the simulator.</p>
<p>Fortunately, <a href="http://www.theevilboss.com/">The Evil Boss</a> shows us how to <a href="http://www.theevilboss.com/2009/10/iphone-video-output.html">enable video output</a> for an iPhone application, so you can show it running on a TV screen.</p>Native Apps vs. Web Apps2009-11-26T00:08:18-05:002009-11-26T00:08:18-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-26:/native-apps-vs-web-apps.html<p>This week, there was a lot of chatter in the blogosphere about the prospect of writing <a href="http://www.apple.com/webapps/whatarewebapps.html">web apps for the iPhone</a> instead of developing native apps.</p>
<!--break-->
<p>The furor started with Peter-Paul Koch's profanity-laden rant entitled <a href="http://www.quirksmode.org/blog/archives/2009/11/apple_is_not_ev.html">"Apple is not evil. iPhone developers are stupid."</a>. In this post, PPK asserts that most …</p><p>This week, there was a lot of chatter in the blogosphere about the prospect of writing <a href="http://www.apple.com/webapps/whatarewebapps.html">web apps for the iPhone</a> instead of developing native apps.</p>
<!--break-->
<p>The furor started with Peter-Paul Koch's profanity-laden rant entitled <a href="http://www.quirksmode.org/blog/archives/2009/11/apple_is_not_ev.html">"Apple is not evil. iPhone developers are stupid."</a>. In this post, PPK asserts that most iPhone applications could easily have been implemented as web applications rather than as native apps that have to be downloaded from the App Store, but iPhone developers are just too stupid to take advantage of web technologies.</p>
<p>PPK obviously has a chip on his shoulder over the fact that web developers are not given the same respect as "real programmers", but he did raise some good points. There are a lot of simple form-based iPhone apps that could easily have been done as web apps, thus avoiding entanglements with Apple's review process and also providing compatibility with platforms other than the iPhone.</p>
<p>However, PPK was also wrong on several points. He overestimated the capabilities of the iPhone's mobile browser; many of the things he thought could be done in-browser really can't be (yet). He also completely ignored two major benefits of native apps: they look nicer and developers have an easy way to get paid for making them. Cocoa developers have <a href="http://farukat.es/journal/2009/11/347-iphone-developers-arent-stupid-ppk">many good reasons</a> for choosing the Cocoa Touch framework over web technologies, even in cases where a web app could compare well to a native app.</p>
<p>PPK has backpedaled a bit, and his follow-up posts have been a little more reasonable and balanced than the original. But other iPhone developers have jumped on the bandwagon, pointing out that maybe too many Cocoa developers really are just too lazy and arrogant to <a href="http://developer.apple.com/safari/">learn enough about HTML, CSS, and JavaScript to make web apps for iPhone</a>.</p>
<p>A healthy iPhone web-app industry would lead to interoperability with Android and other mobile platforms, and would loosen Apple's stranglehold on its platform. Unfortunately, there is no App Store for iPhone web apps, so they can be hard to find. Apple does have a <a href="http://www.apple.com/webapps/">web apps page</a> with a lot of links to web apps, but most iPhone users have no idea that it exists. </p>
<p>It's a bit ironic, because in the early days of the iPhone, Apple was telling developers that web apps were the way to go, and there was no need for a native SDK. At that time, most developers scoffed. Developers wanted to create high-performance games and apps that looked as nice as the built-in apps (Mail, Contacts, Google Maps, etc), and those things could not be done as web apps. And there is a lot of truth to the idea that "real programmers" look down on web development as an activity for those not smart enough to learn C, and see web apps as toys.</p>
<p>However, thanks to PPK's nudge, a few influential iPhone developers have started talking about web technologies. As a proof-of-concept, there is the game <a href="http://mrgan.tumblr.com/post/257187093/pie-guy">Pie Guy</a> by Neven Mrgan, which is a web application but which acts a lot like a native app.</p>
<p>Other iPhone developers have started talking about the <a href="http://cappuccino.org/">Cappucino Web Framework</a>, an open-source framework that allows one to use an API that is very similar to the Mac Cocoa frameworks. In this way, Cocoa developers can write iPhone web apps without having to learn too much (an approach which I don't like, but which has some merit).</p>
<p>I myself have wanted to spend more time learning the ins and outs of JavaScript, CSS, and the DOM, but I've never had a "real project" that forced me to do so. Maybe it's time to find one.</p>Android: Maybe Not2009-11-25T06:01:49-05:002009-11-25T06:01:49-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-25:/android-maybe-not.html<p>A couple of days ago, I said I was considering <a href="https://undefinedvalue.com/2009/11/19/switching-away-apple">giving up on iPhone development and trying Android instead</a>. Here are my further thoughts along those lines.</p>
<!--break-->
<p>First, I downloaded the Android SDK, did a little reading, and went through the <em>Hello, world!</em> tutorial to bring up an app in …</p><p>A couple of days ago, I said I was considering <a href="https://undefinedvalue.com/2009/11/19/switching-away-apple">giving up on iPhone development and trying Android instead</a>. Here are my further thoughts along those lines.</p>
<!--break-->
<p>First, I downloaded the Android SDK, did a little reading, and went through the <em>Hello, world!</em> tutorial to bring up an app in the Android simulator. I didn't go too far beyond that. The Android SDK is all based on Java and XML, which are two things I don't like much, but on the whole it looked like a reasonable set of tools and software frameworks.</p>
<p>Next, I went to Best Buy to play with a <a href="http://www.engadget.com/2009/10/30/motorola-droid-review/">DROID</a> phone. They had a nonfunctional "dummy" phone on display, and the dummy phone was terrible: it was flimsy and cheap, and the keyboard was too stiff to be usable. I was about to just give up right there. But, while my wife ooh'ed over a Sony Reader Touch, I asked a salesperson if they had any actual DROID phones, and when told that they did, I asked to try it out.</p>
<p>I don't have much to add to all the other DROID reviews you can find on the net. It looks really nice. It feels really nice. It doesn't have the smooth curves of the iPhone, but the hardware is definitely impressive. The software didn't look as nice as the iPhone, but it looked usable.</p>
<p>So, with those two basic "Would Android use and development suck?" questions tentatively answered <em>No</em>, I started doing some investigation of the Android software market. And that is where I stopped.</p>
<p>For a complete description of everything wrong with the Android software market, see AppleInsider's "<a href="http://www.appleinsider.com/articles/09/11/21/inside_googles_android_and_apples_iphone_os_as_software_markets.html">Inside Google's Android and Apple's iPhone OS as software markets</a>". Here's a summary:</p>
<ul>
<li>Android users don't buy as many apps as iPhone and iPod touch users do.</li>
<li>Android phones don't have much storage space available for installation of apps.</li>
<li>The apps in the Android Market are what you would expect for an open-source platform: functional-but-ugly stuff written by hobbyists, no commercial-grade productivity apps or name-brand games.</li>
<li>The variety of Android devices and software versions make it difficult to create apps that work on all phones, or even on large subsets of phones. (This is similar to the problems of Windows Mobile development.)</li>
<li>Google is doing little to promote the Android Market to users or to encourage serious developers to put apps there.</li>
</ul>
<p>There is a negative feedback loop at play. Nobody wants to buy Android apps, because nobody makes good Android apps, and nobody wants to make good apps, because nobody buys them.</p>
<p>The bottom line may be that people who are willing to spend money on their mobile devices are going to buy the Apple products. The people who buy Android phones are going to be members of one of these three camps:</p>
<ul>
<li>those who just want a functional phone, and don't care about the OS or third-party apps</li>
<li>those who would really like an iPhone, but can't afford one</li>
<li>those who would really like an iPhone, but have philosophical objections to giving money to Apple</li>
</ul>
<p>In short, Android users aren't the kind of people who spend money on nice apps. So they aren't going to get any.</p>
<p>The most optimistic spin I could put on this situation was "Well, if there are no good apps there, then if I make the first awesome Android app, then every Android user will buy it and I'll make lots of money." But that doesn't seem reasonable.</p>
<p>Another optimistic spin is "Well, there won't be any consumer apps, but there may be a lot of custom app development. For example, give all your salespeople Android phones with your company's CRM app installed." That may be true, but I don't want to get into that business.</p>
<p>So, I'm going to stick with iPhone development for a while.</p>
<p>Note that I am not saying that Android is going to "fail." I think device makers will be dropping Windows Mobile, Symbian, and other OSes in favor of Android, so we'll be seeing a lot of Android devices. But I don't see an iPhone App Store-style success. There will be apps for Android, but they won't be the reason to buy an Android phone. Better to be in the Android ringtone business, I think.</p>Deal on Photoshop Elements 82009-11-25T00:41:09-05:002009-11-25T00:41:09-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-25:/deal-photoshop-elements-8.html<p>If you're one of the people (like me) who needs to occasionally do some image editing, but who doesn't want to spend a few hundred bucks on Photoshop, you may want to check out Photoshop Elements 8.</p>
<p>Adobe has a deal on it right now: $20 off the normal price …</p><p>If you're one of the people (like me) who needs to occasionally do some image editing, but who doesn't want to spend a few hundred bucks on Photoshop, you may want to check out Photoshop Elements 8.</p>
<p>Adobe has a deal on it right now: $20 off the normal price, and a $20 rebate, so that the final price is about $60. See <a href="http://www.adobe.com/products/photoshopelmac">the Adobe store</a>. Deal ends 11/30/2009.</p>Switching Away from Apple?2009-11-19T11:44:32-05:002009-11-19T11:44:32-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-19:/switching-away-apple.html<p>I've enjoyed using my Apple products, but I'm considering a switch. I find myself drawn back to Windows on my desktop, and to an Android mobile device.</p>
<!--break-->
<p>There was a time that the Macintosh was clearly superior to the competition in every way except price. But that was a while …</p><p>I've enjoyed using my Apple products, but I'm considering a switch. I find myself drawn back to Windows on my desktop, and to an Android mobile device.</p>
<!--break-->
<p>There was a time that the Macintosh was clearly superior to the competition in every way except price. But that was a while ago. Now, the Mac is still pretty nice, but choosing Mac over Windows is a matter of preference rather than necessity. Ironically, Windows is now the operating system for the everyman, where everything just works, while Mac OS X is the one that requires a lot of technical expertise and hackish workarounds to get anything done.</p>
<p>The iPhone created a new product category, and is still the clear leader of that category. If I was just a consumer, I'd stick with the iPhone. But I'm a software developer, and developing software for the iPhone isn't the joy I'd hoped it would be. The tools are archaic and quirky, and with 100,000 apps in the App Store, it is practically impossible to get anyone to notice your work. Apple just doesn't need me, and doesn't act like it wants me.</p>
<p>So, I find myself drawn back to Windows on my desktop, and to an Android mobile device.</p>
<p>It's been a few years since I've done any serious Windows development, and I need to brush up on those skills. I want to buy a copy of Windows 7 and Visual Studio whatever-the-current-version-is and play with it. Microsoft's products have a lot of flaws, but their development tools have always been top-notch, and I need to see what they've done recently. (In contrast, the Mac's development tools are basically 20 years old, but are still not quite <em>finished</em>.)</p>
<p>Mobile device development has always been a favorite pastime of mine. I've enjoyed learning my way around the iPhone API and tools, but I feel like I know it all. Diving into Android development should be fun, and will help me brush up on my Java skills. And unlike Apple, maybe Android needs me.</p>
<p>So, I find myself drawn back to Windows on my desktop, and to an Android mobile device.</p>
<p>I don't hate Apple products, and I'm not bashing them. I just don't find them interesting anymore.</p>
<p>Switching back to Windows on the desktop should be pretty easy, since I can just run it in VMWare on my Macbook for a while, and then Boot Camp it if necessary.</p>
<p>The switch from iPhone to Android would take a lot more commitment, so I'm going to go slow with that. I'll start by playing with the Android SDK, and I'll try to find a store with a Droid or other top-of-the-line Android device. I suspect I'll end up keeping my iPhone for real-world use, but I'll have an Android device for development purposes.</p>
<p>I'll still definitely be getting an Apple tablet, though.</p>Google Wave2009-11-18T14:17:00-05:002009-11-18T14:17:00-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-18:/google-wave.html<p>Thanks to a former co-worker, I got an invite for <a href="http://wave.google.com/">Google Wave</a>.</p>
<p>The biggest problem right now with Google Wave is finding other people to interact with. Chances are that few of your family, friends, or co-workers have access to Wave yet. I've set up a public wave for readers …</p><p>Thanks to a former co-worker, I got an invite for <a href="http://wave.google.com/">Google Wave</a>.</p>
<p>The biggest problem right now with Google Wave is finding other people to interact with. Chances are that few of your family, friends, or co-workers have access to Wave yet. I've set up a public wave for readers of this blog. If you are a waver, feel free to play with it:</p>
<ul>
<li><a href="https://wave.google.com/wave/#restored:wave:googlewave.com!w%252Bl7GtSGxjA">Undefined Value wave</a></li>
</ul>
<p>(If that link doesn't work, then try entering "with:public undefined value" in the Google Wave search box.)</p>Sony PRS-500 Upgrade2009-11-16T12:38:54-05:002009-11-16T12:38:54-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-16:/sony-prs-500-upgrade.html<p>As mentioned in a <a href="https://undefinedvalue.com/2009/10/09/little-service-converts-files-epub-format">recent post</a>, my wife bought me a Sony PRS-500 Reader, and I like it a lot. I've just found out about an upgrade offer: PRS-500 owners can <a href="http://www.sonystyle.com/webapp/wcs/stores/servlet/CategoryDisplay?catalogId=10551&storeId=10151&langId=-1&categoryId=8198552921644683012&N=4294953907">trade in their readers for discount on a Reader Pocket or Reader Touch</a>. The offer expires April 10, 2010 …</p><p>As mentioned in a <a href="https://undefinedvalue.com/2009/10/09/little-service-converts-files-epub-format">recent post</a>, my wife bought me a Sony PRS-500 Reader, and I like it a lot. I've just found out about an upgrade offer: PRS-500 owners can <a href="http://www.sonystyle.com/webapp/wcs/stores/servlet/CategoryDisplay?catalogId=10551&storeId=10151&langId=-1&categoryId=8198552921644683012&N=4294953907">trade in their readers for discount on a Reader Pocket or Reader Touch</a>. The offer expires April 10, 2010.</p>
<p>(Bummer. Turns out that I have a PRS-505, not a PRS-500, so I'm not eligible for the upgrade. So I guess I'll just have to wait for the Apple tablet.)</p>The Go Programming Language2009-11-14T22:07:48-05:002009-11-14T22:07:48-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-14:/go-programming-language.html<p>There's a new programming language out there: <a href="http://golang.org/">Go</a>. There are lots of ways to describe it, but basically it's got Python-esque syntax and C++-esque performance. It's statically typed, but is designed to feel more like a dynamic language. It has garbage collection.</p>
<p>Based on its pedigree, I expect this …</p><p>There's a new programming language out there: <a href="http://golang.org/">Go</a>. There are lots of ways to describe it, but basically it's got Python-esque syntax and C++-esque performance. It's statically typed, but is designed to feel more like a dynamic language. It has garbage collection.</p>
<p>Based on its pedigree, I expect this to be a lot more successful than the various other languages intended to be successors to C++. Play with it while it's still cool, before it starts to suck.</p>
<p>Sorry, Windows programmers, but only Linux and Mac OS X are supported for now. But I'm sure Windows programmers won't mind; to them, "new programming language" means "C# 4.0".</p>iPhone OS Filename Case Sensitivity2009-11-12T00:51:59-05:002009-11-12T00:51:59-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-12:/iphone-os-filename-case-sensitivity.html<p>I hit a little snag while adding a feature to an iPhone app today. I added this code to load a logo to be displayed in a view:</p>
<div class="highlight"><pre><span></span><code><span class="bp">UIImage</span><span class="w"> </span><span class="o">*</span><span class="n">logoImage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIImage</span><span class="w"> </span><span class="n">imageNamed</span><span class="o">:</span><span class="s">@"Icon.png"</span><span class="p">];</span><span class="w"></span>
</code></pre></div>
<p>This worked fine in the iPhone Simulator, so I thought I was done. I loaded the …</p><p>I hit a little snag while adding a feature to an iPhone app today. I added this code to load a logo to be displayed in a view:</p>
<div class="highlight"><pre><span></span><code><span class="bp">UIImage</span><span class="w"> </span><span class="o">*</span><span class="n">logoImage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIImage</span><span class="w"> </span><span class="n">imageNamed</span><span class="o">:</span><span class="s">@"Icon.png"</span><span class="p">];</span><span class="w"></span>
</code></pre></div>
<p>This worked fine in the iPhone Simulator, so I thought I was done. I loaded the app onto my iPod Touch, and it didn't work. Running in the debugger, I discovered that <code>logoImage</code> was being set to <code>nil</code>.</p>
<p>Why would this not work on a device, when it ran fine in the simulator?</p>
<p>It turns out that the iPhone OS filesystem is case-sensitive, while the Mac OS X filesystem is not case-sensitive. The actual name of the image file was <code>icon.png</code>, with a lowercase-<em>i</em>, so it didn't match on iPhone OS.</p>
<p>No big deal, but it's another reminder that you always need to test on an actual device before considering an iPhone development task <em>done</em>.</p>Introducing Kids to Programming2009-11-11T00:28:52-05:002009-11-11T00:28:52-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-11:/introducing-kids-programming.html<p>No time for a real blog entry today, but instead of nothing, here are some links to really cool toys that kids (and curious adults) can play with to learn about programming and multimedia:</p>
<ul>
<li><a href="http://scratch.mit.edu/">Scratch</a></li>
<li><a href="http://www.alice.org/">Alice</a></li>
</ul>Automator Service: Copy Current UTC Timestamp to Clipboard2009-11-10T00:12:31-05:002009-11-10T00:12:31-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-10:/automator-service-copy-current-utc-timestamp-clipboard.html<p>Yeah, I know, you're probably getting sick of these Automator services. But I really do create a new one of these practically every day to make my life a little easier, and maybe some of these will be useful to others.</p>
<p>This one puts a UTC timestamp on the clipboard …</p><p>Yeah, I know, you're probably getting sick of these Automator services. But I really do create a new one of these practically every day to make my life a little easier, and maybe some of these will be useful to others.</p>
<p>This one puts a UTC timestamp on the clipboard. The timestamp is an <a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>-format string like "2009-11-09T13:14:03Z". If you'd like a different format, type "man date" in Terminal to see how to change the output format of the <code>date</code> command in the shell script below.</p>
<!--break-->
<h2>How to Use It</h2>
<p>Choose <strong>Copy Current UTC Timestamp to Clipboard</strong> from the Services menu, then paste it into wherever you need it.</p>
<h2>How to Make it</h2>
<ol>
<li>Start up Automator, and create a new Service which takes <strong>no input</strong> in <strong>any application</strong></li>
<li>Add a <strong>Run Shell Script</strong> action, with shell <strong>/bin/bash</strong> and which passes input <strong>to stdin</strong>. Replace the shell script text with this:<pre>
<br>/bin/date -u '+%FT%TZ'<br></pre></li>
<li>Add a <strong>Copy to Clipboard</strong> action.</li>
<li>Choose <strong>File -> Save</strong> and name it "Copy Current UTC Timestamp to Clipboard"</li>
</ol>
<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/Copy_UTC_Timestamp_to_Clipboard.png" alt="Screenshot"></p>Authenticating with Google App Engine2009-11-09T01:01:56-05:002009-11-09T01:01:56-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-09:/authenticating-google-app-engine.html<p>I've finally got around to doing some work on that iPhone application that I've <a href="https://undefinedvalue.com/2009/11/01/not-quite-nanowrimo">committed to finishing this month</a>.</p>
<p>On days I do a lot of work on the app, I don't feel obligated to work too hard on the blog, but I will post a little something about whatever …</p><p>I've finally got around to doing some work on that iPhone application that I've <a href="https://undefinedvalue.com/2009/11/01/not-quite-nanowrimo">committed to finishing this month</a>.</p>
<p>On days I do a lot of work on the app, I don't feel obligated to work too hard on the blog, but I will post a little something about whatever I worked on. Today, I got my iPhone app to connect to a <a href="http://code.google.com/appengine/docs/whatisgoogleappengine.html">Google App Engine</a>-based web site.</p>
<p>Without giving away too much, I'll just say that the iPhone app and the web site work together to provide a service to iPhone users. I put the web site together in a couple of weekends. I decided to use Google Accounts for authentication, meaning that to log into my web site, either via a web browser or via the iPhone app, a user has to provide a Google account ID and password.</p>
<p>If you do things this way, the server-side authentication stuff is easy. However, writing the client side is not easy, because the mechanism for authenticating with a Google account and connecting to a Google App Engine web site is not well documented. Luckily, I found a <a href="http://stackoverflow.com/questions/471898/google-app-engine-with-clientlogin-interface-for-objective-c">Stack Overflow question and answer</a> that provided all the clues I needed to get my iPhone app working.</p>
<p>After using Google's API for implementing the web site, I'm growing increasingly frustrated with the Cocoa APIs. What takes two lines of code on the web side takes dozens of lines of code on the client side. Simple operations like connecting to a URL, downloading data, and storing it to a local database requires a lot of boilerplate code on the Cocoa side. It's not difficult, but it's incredibly verbose.</p>
<p>I should note that I'm using the Python API for Google App Engine. If I was using the Java API, then I'm sure it would be as grotesque as the Objective-C stuff.</p>How Does One Become a Good Programmer?2009-11-07T18:03:52-05:002009-11-07T18:03:52-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-07:/how-does-one-become-good-programmer.html<p>This is a quote I like:</p>
<blockquote>
<p>The really good programmers spend a lot of time programming. I haven't seen very good programmers who don't spend a lot of time programming. If I don't program for two or three days, I need to do it. And you get better at it …</p></blockquote><p>This is a quote I like:</p>
<blockquote>
<p>The really good programmers spend a lot of time programming. I haven't seen very good programmers who don't spend a lot of time programming. If I don't program for two or three days, I need to do it. And you get better at it—you get quicker at it. The side effect of writing all this other stuff is that when you get to doing ordinary problems, you can do them very quickly.</p>
</blockquote>
<p>That's from Joe Armstrong, creator of <a href="http://en.wikipedia.org/wiki/Erlang_%28programming_language%29">Erlang</a>, in Peter Seibel's <em><a href="http://www.codersatwork.com/">Coders at Work</a></em> (which, by the way, every programmer should read).</p>
<p>This jibes with what I've seen over the years. Really good programmers don't treat coding as a nine-to-five job. It's something they want to do whether they are being paid for it or not.</p>
<p>A very easy way to weed out bad candidates when interviewing is to ask them about their personal programming projects. If they don't have any, then I'm pretty sure I don't want to work with them.</p>
<p>In this respect, programming is little different from other creative pursuits. You become a good writer by writing a lot; you become a good sculptor by sculpting a lot; you become a good musician by playing music a lot.</p>
<p>So go out there and write some code. That's what I'm about to do.</p>Automator Service: Scale Images by 75%2009-11-06T17:17:14-05:002009-11-06T17:17:14-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-06:/automator-service-scale-images-75.html<p>Here is yet another Snow Leopard Automator service: This one takes an image file and scales it down to 75% of its original size. This is great for screenshots, as all the text is still readable at that size, but you can put it in a web page or document …</p><p>Here is yet another Snow Leopard Automator service: This one takes an image file and scales it down to 75% of its original size. This is great for screenshots, as all the text is still readable at that size, but you can put it in a web page or document without filling the screen.</p>
<!--break-->
<h2>How to Use It</h2>
<ol>
<li>Select one or more image files in the Finder.</li>
<li>From the Services menu, choose <strong>Scale Images by 75%</strong></li>
<li>Look for the scaled images on the desktop.</li>
</ol>
<p>For example, to create the Automator screenshot you see at the bottom of this article, I used Shift-Command-4 to select an area of the screen. The screenshot image file was put on my desktop. Then I right-clicked the image file and chose <strong>Scale Images by 75%</strong>, which created a new image file next to the original.</p>
<h2>How to Create It</h2>
<p>In Automator, create a new Service that receives selected <strong>image files</strong> in <strong>Finder</strong>.</p>
<p>Add these actions:</p>
<ol>
<li><strong>Copy Finder Items</strong> (Note: you can skip this if you would rather replace the original image file.)</li>
<li><strong>Scale Images</strong> with <strong>By Percentage</strong> set to 75.</li>
</ol>
<p>Save the service as "Scale Images by 75%".</p>
<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/Scale_Images_by_75_percent.png" alt="Picture"></p>Automator Service: Copy File Paths to Clipboard2009-11-05T09:12:31-05:002009-11-05T09:12:31-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-05:/automator-service-copy-file-paths-clipboard.html<p>Here's another Snow Leopard Automator-based service: It takes the Finder selection and puts the file paths on the clipboard, for easy pasting into command lines or scripts.</p>
<!--break-->
<h2>How to Use It</h2>
<ol>
<li>In the Finder, select one or more files or folders.</li>
<li>From the Services menu, select <strong>Copy Files Paths to …</strong></li></ol><p>Here's another Snow Leopard Automator-based service: It takes the Finder selection and puts the file paths on the clipboard, for easy pasting into command lines or scripts.</p>
<!--break-->
<h2>How to Use It</h2>
<ol>
<li>In the Finder, select one or more files or folders.</li>
<li>From the Services menu, select <strong>Copy Files Paths to Clipboard</strong></li>
<li>Go somewhere and select <strong>Edit -> Paste</strong> to put the full path(s) to the file(s) there.</li>
</ol>
<p>For example, if I go to my home folder, select the "Documents" and "Downloads" folders, and then invoke the service, this is what ends up on the clipboard:</p>
<div class="highlight"><pre><span></span><code><span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="n">kdj</span><span class="o">/</span><span class="n">Documents</span><span class="w"></span>
<span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="n">kdj</span><span class="o">/</span><span class="n">Downloads</span><span class="w"></span>
</code></pre></div>
<h2>How to Make It</h2>
<p>Open Automator, create a new Service, and set it to receive selected files or folders in the Finder.</p>
<p>Add these actions:</p>
<ol>
<li><strong>Run Shell Script</strong>, with shell <code>/bin/bash</code>, passing input <strong>as arguments</strong>, with this script:<pre><code>
for f in "$@"
do
echo "$f"
done
</code></pre></li>
<li><strong>Copy to Clipboard</strong></li>
</ol>
<p>Save it, and name it "Copy File Paths to Clipboard".</p>
<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/Copy_File_Paths_to_Clipboard.png" alt="Picture"></p>Mac Software for Software Developers2009-11-05T01:22:23-05:002009-11-05T01:22:23-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-05:/mac-software-software-developers.html<p>A fellow developer who is getting his first Mac asked me what software he should get. Here is a list of Mac software that I, as a software developer, find useful.</p>
<!--break-->
<p>First, let's look at what apps come in the box, at no extra charge:</p>
<ul>
<li>Basic contacts, calendar, and email …</li></ul><p>A fellow developer who is getting his first Mac asked me what software he should get. Here is a list of Mac software that I, as a software developer, find useful.</p>
<!--break-->
<p>First, let's look at what apps come in the box, at no extra charge:</p>
<ul>
<li>Basic contacts, calendar, and email: Address Book, iCal, Mail</li>
<li>Word processing and text editing: TextEdit (handles text, RTF, and Microsoft Word DOC formats)</li>
<li>Automation and scripting: Automator, AppleScript Editor, bash, Python</li>
<li>Scientific and programmer's calculator: Calculator</li>
<li>Dictionary and thesaurus: Dictionary</li>
<li>Music and sound recording and editing: GarageBand</li>
<li>Desktop search: Spotlight</li>
<li>Instant messaging and video conferencing: iChat</li>
<li>Basic image processing: iPhoto</li>
<li>PDF reader and image file viewer: Preview</li>
<li>Webkit-based web browser: Safari</li>
<li>Backups: Time Machine</li>
<li>X Window System: X11</li>
<li>Command Prompt, telnet, ssh: Terminal</li>
</ul>
<p>For basic Mac development, Xcode is the standard tool. For iPhone development, Xcode is the <em>required</em> tool. It includes a bunch of other tools (Interface Builder, iPhone Simulator, Instruments, Shark). Download it from <a href="http://developer.apple.com/iphone/">Apple</a>.</p>
<p>If you like something lower level, remember that Mac OS X is UNIX at its heart, so all the typical UNIX and GNU tools are there (<code>gcc</code>, <code>vi</code>, <code>make</code>, etc.).</p>
<p>Some other development-related tools that you might find useful are these:</p>
<ul>
<li>API documentation browser: <a href="http://homepage.mac.com/aglee/downloads/appkido.html">AppKiDo</a></li>
<li>iPhone screenshots and screencasts: <a href="http://blog.atebits.com/2009/03/not-your-average-iphone-screencast/">SimFinger</a></li>
<li>Hex editor: <a href="http://www.ridiculousfish.com/hexfiend/">Hex Fiend</a></li>
<li>Automatic installation of thousands of UNIX utilities: <a href="http://www.macports.org/">MacPorts</a></li>
<li>Scientific and programmers calculator: <a href="http://projectswithlove.com/magicnumbermachine/">Magic Number Machine</a></li>
<li>Apache, MySQL, PHP: <a href="http://www.mamp.info/en/index.html">MAMP</a></li>
<li>Java development: Eclipse or NetBeans</li>
<li>Nirvana: <a href="https://undefinedvalue.com/2009/09/12/building-emacs-source-mac-os-x">Emacs</a></li>
</ul>
<p>Here are the other pieces of software that you should consider:</p>
<ul>
<li>Web browser: <a href="http://www.mozilla.com">Firefox</a></li>
<li>Instant Messaging: <a href="http://adium.im/">Adium</a></li>
<li>Graphics Editor: <a href="http://www.adobe.com/products/photoshopelmac/">Adobe Photoshop Elements</a>, the <a href="http://www.gimp.org/macintosh/">Gimp</a>, or <a href="http://seashore.sourceforge.net/">Seashore</a></li>
<li>Sound editing: <a href="http://audacity.sourceforge.net/download/">Audacity</a></li>
<li>Pixel measurements: <a href="http://www.pascal.com/software/freeruler/">Free Ruler</a></li>
<li>"Productivity": <a href="http://www.openoffice.org/">OpenOffice</a>, <a href="http://www.apple.com/iwork/">iWork</a>, or (if your employer will pay for it) <a href="http://www.microsoft.com/mac/default.mspx">Microsoft Office for Mac</a></li>
<li>Outliner: <a href="http://www.omnigroup.com/applications/omnioutliner/">OmniOutliner</a></li>
<li>Virtual machine for running Windows: <a href="http://www.vmware.com/products/fusion/">VMWare Fusion</a> or <a href="http://www.parallels.com/products/desktop/">Parallels</a></li>
</ul>The "Easy Part" vs. the "Hard Part" of Software Development2009-11-04T00:58:57-05:002009-11-04T00:58:57-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-04:/easy-part-vs-hard-part-software-development.html<p>In his essay "<a href="http://www.neilgaiman.com/p/Cool_Stuff/Essays/Essays_By_Neil/Where_do_you_get_your_ideas%3F">Where Do You Get Your Ideas?</a>," Neil Gaiman relates a common situation faced by authors:</p>
<blockquote>
<p>Every published writer has had it - the people who come up to you and tell you that they've Got An Idea. And boy, is it a Doozy. It's such a Doozy that …</p></blockquote><p>In his essay "<a href="http://www.neilgaiman.com/p/Cool_Stuff/Essays/Essays_By_Neil/Where_do_you_get_your_ideas%3F">Where Do You Get Your Ideas?</a>," Neil Gaiman relates a common situation faced by authors:</p>
<blockquote>
<p>Every published writer has had it - the people who come up to you and tell you that they've Got An Idea. And boy, is it a Doozy. It's such a Doozy that they want to Cut You In On It. The proposal is always the same - they'll tell you the Idea (the hard bit), you write it down and turn it into a novel (the easy bit), the two of you can split the money fifty-fifty.</p>
</blockquote>
<p>Computer programmers often find themselves in similar situations. Some well-meaning person says they have a Great Idea for an application. They'll tell the programmer the idea, and then all the programmer has to do is write the code to implement it. The Idea Person will get the bulk of the credit, but the lucky programmer gets to ride those coattails. After all, coding must be easy: it's just a bunch of <em>stuff</em> somebody has to type. The idea, the design, the inspiration - that's the hard part, right?</p>
<p>Usually, no.</p>
<p>Don't get me wrong: a great idea is a great idea, and a talented application designer is certainly worth more than the average programmer. But most people just aren't that smart.</p>
<p>And even if the idea is amazing and the designer creates a brilliant user experience, the bulk of the work still falls on the programmers' shoulders. If you take all the man-hours spent designing the iPhone, and all the man-hours spent implementing the operating system, frameworks, and standard applications, and making it all work the way the designers intended, I'd bet the latter number would be at least ten times the former. Maybe twenty times.</p>
<p>Programming is just plain <em>hard</em>, in a way that related software-development activities are not. And it is <em>unforgiving</em>. If a programmer makes a mistake, the program crashes or gives incorrect results. In contrast, if the UI designer makes a mistake, the consequences are not so dire; it just means the application is not quite as good as it could be.</p>
<p>It's usually a programmer who has to fill in all the details of someone else's grand design. When it's 2:00 AM, and the architects are snuggled in their beds, and some program is crashing because nobody thought about how to handle a certain condition, it's a programmer who has to decide how to fix it. Programmers make dozens of these kinds of decisions every day, while the Big Picture guys talk about golf.</p>
<p>I don't intend to put programmers up on a pedestal. Developing good software also requires good designers, good artists, good technical writers, good testers, good managers, and a good user community. But, please, don't insult coders by treating them like a bunch of glorified typists. Programming requires as much imagination, inspiration, and perspiration as any other creative endeavour.</p>Easy Gradient Backgrounds for UITextViewCells2009-11-03T00:28:23-05:002009-11-03T00:28:23-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-03:/easy-gradient-backgrounds-uitextviewcells.html<p>When you create a table-view-based iPhone app, by default you get tables with plain white rows. But all the cool kids are making apps with 3D-ish gradient backgrounds. You want to make those kinds of apps too, right? This article explains how.</p>
<!--break-->
<h2>Overview</h2>
<p>Making table cells with custom backgrounds is …</p><p>When you create a table-view-based iPhone app, by default you get tables with plain white rows. But all the cool kids are making apps with 3D-ish gradient backgrounds. You want to make those kinds of apps too, right? This article explains how.</p>
<!--break-->
<h2>Overview</h2>
<p>Making table cells with custom backgrounds is a common thing for iPhone developers to want to do, and there is a lot of information around the net about how to do it. Why should you read this tutorial? Well, this tutorial is designed to make it as easy as possible: just copy some files to your project, make a couple of source code changes, and <em>bang!</em> instant gradient backgrounds. With easy stuff like this out of the way, you can concentrate on doing whatever is unique for your application.</p>
<p>Also, unlike some older tutorials, this one lets you use the built-in <code>UITableViewCell</code> <code>textLabel</code> and <code>detailTextLabel</code> properties, rather than creating your own custom cell from scratch. So it's easy to retrofit existing code.</p>
<p>To give credit where it's due, I got a lot of this information from the article <a href="http://maniacdev.com/2009/10/how-to-make-ultra-slick-gradient-uitableview-cells/">How To Make Ultra-Slick Gradient UITableView Cells</a> at manicadev.com, so go check that site out.</p>
<p>What we'll do is create a simple example project, and then show how to add the gradients.</p>
<h2>Creating the Example Project</h2>
<p>We'll start by creating a run-of-the-mill iPhone app that displays a table. These are the steps:</p>
<ol>
<li>In Xcode, choose the <strong>File -> New Project…</strong> menu item.</li>
<li>Choose <strong>iPhone OS -> Application</strong> in the left pane of the <strong>New Project</strong> dialog.</li>
<li>Select <strong>Navigation-based Application</strong>, leave <strong>Use Core Data for storage</strong> uncheck, and click <strong>Choose…</strong></li>
<li>Save the project as "GradientTableViewCellExample"</li>
</ol>
<p>Open the <code>RootViewController.m</code> source file, and replace the <code>tableView:numberOfRowsInSection:</code> and <code>tableView:cellForRowAtIndexPath:</code> methods with these bodies:</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="n">NSInteger</span><span class="p">)</span><span class="nf">tableView:</span><span class="p">(</span><span class="bp">UITableView</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">tableView</span><span class="w"></span>
<span class="w"> </span><span class="nl">numberOfRowsInSection</span><span class="p">:(</span><span class="n">NSInteger</span><span class="p">)</span><span class="nv">section</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">1000</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="bp">UITableViewCell</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nf">tableView:</span><span class="p">(</span><span class="bp">UITableView</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">tableView</span><span class="w"></span>
<span class="w"> </span><span class="nl">cellForRowAtIndexPath</span><span class="p">:(</span><span class="bp">NSIndexPath</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">indexPath</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">static</span><span class="w"> </span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="n">CellIdentifier</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">@"Cell"</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="bp">UITableViewCell</span><span class="w"> </span><span class="o">*</span><span class="n">cell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">tableView</span><span class="w"> </span><span class="n">dequeueReusableCellWithIdentifier</span><span class="o">:</span><span class="n">CellIdentifier</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">cell</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">nil</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">cell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="bp">UITableViewCell</span><span class="w"> </span><span class="n">alloc</span><span class="p">]</span><span class="w"> </span><span class="n">initWithStyle</span><span class="o">:</span><span class="n">UITableViewCellStyleSubtitle</span><span class="w"></span>
<span class="w"> </span><span class="nl">reuseIdentifier</span><span class="p">:</span><span class="n">CellIdentifier</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">cell</span><span class="w"> </span><span class="n">autorelease</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">rowIndex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">indexPath</span><span class="p">.</span><span class="n">row</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">cell</span><span class="p">.</span><span class="n">textLabel</span><span class="p">.</span><span class="n">text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">NSString</span><span class="w"> </span><span class="n">stringWithFormat</span><span class="o">:</span><span class="s">@"Row %d"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">rowIndex</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">cell</span><span class="p">.</span><span class="n">detailTextLabel</span><span class="p">.</span><span class="n">text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">NSString</span><span class="w"> </span><span class="n">stringWithFormat</span><span class="o">:</span><span class="s">@"This is the detail text for row %d"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">rowIndex</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">cell</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Build and run the project, and you'll see this:</p>
<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/NoGradient.png" alt="App with no gradient"></p>
<p>Not bad, but it would look more interesting if, instead of the flat white table cells, we had three-dimensional-looking gradient backgrounds on those cells.</p>
<h2>Adding the Gradient Background Image</h2>
<p>First, we'll need a background image. You can look at the <a href="http://maniacdev.com/2009/10/how-to-make-ultra-slick-gradient-uitableview-cells/">How To Make Ultra-Slick Gradient UITableView Cells</a> article to see how to make one of these from scratch, using Adobe Photoshop Elements, but here's an image you can steal directly from this page:</p>
<p><a href="https://undefinedvalue.com/sites/undefinedvalue.com/files/CellGradientBackground.png"><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/CellGradientBackground.png" alt="CellGradientBackground.png"></a></p>
<p>Add this image to your project, or create your own background image if you're so-inclined.</p>
<h2>Adding the GradientTableViewCell class</h2>
<p>Grab the source files <a href="https://undefinedvalue.com/sites/undefinedvalue.com/files/GradientTableViewCell.h">GradientTableViewCell.h</a> and <a href="https://undefinedvalue.com/sites/undefinedvalue.com/files/GradientTableViewCell.m">GradientTableViewCell.m</a>, and add them to your project.</p>
<p><code>GradientTableViewCell</code> overrides two methods of <code>UITableViewCell</code>. First, we override the <code>initWithStyle:reuseIdentifier:</code> method so that it adds the gradient background image, stretching it to fill the cell.</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithStyle:</span><span class="p">(</span><span class="n">UITableViewCellStyle</span><span class="p">)</span><span class="nv">style</span><span class="w"> </span><span class="nf">reuseIdentifier:</span><span class="p">(</span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">reuseIdentifier</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">self</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">initWithStyle</span><span class="o">:</span><span class="n">style</span><span class="w"> </span><span class="n">reuseIdentifier</span><span class="o">:</span><span class="n">reuseIdentifier</span><span class="p">])</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">UIImage</span><span class="w"> </span><span class="o">*</span><span class="n">image</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIImage</span><span class="w"> </span><span class="n">imageNamed</span><span class="o">:</span><span class="s">@"CellGradientBackground.png"</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="bp">UIImageView</span><span class="w"> </span><span class="o">*</span><span class="n">imageView</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="bp">UIImageView</span><span class="w"> </span><span class="n">alloc</span><span class="p">]</span><span class="w"> </span><span class="n">initWithImage</span><span class="o">:</span><span class="n">image</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">imageView</span><span class="p">.</span><span class="n">contentMode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">UIViewContentModeScaleToFill</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">backgroundView</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">imageView</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">imageView</span><span class="w"> </span><span class="k">release</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">self</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Then, we have to override the <code>setSelected:animated:</code> method. The inherited method sets the cell's subviews' background colors, but we want the subviews to have transparent backgrounds so that the gradient background shows through:</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">setSelected:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">selected</span><span class="w"> </span><span class="nf">animated:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">animated</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">setSelected</span><span class="o">:</span><span class="n">selected</span><span class="w"> </span><span class="n">animated</span><span class="o">:</span><span class="n">animated</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="bp">UIView</span><span class="w"> </span><span class="o">*</span><span class="n">view</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nb">self</span><span class="p">.</span><span class="n">contentView</span><span class="p">.</span><span class="n">subviews</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">backgroundColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">clearColor</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<h2>Using the GradientTableViewCell class</h2>
<p>Now, we just have to change our <code>RootViewController.m</code> file so that it will use <code>GradientTableViewCell</code> instead of a plain old <code>UITableViewCell</code>.</p>
<p>Add an <code>#import</code> at the top:</p>
<div class="highlight"><pre><span></span><code><span class="c1">#import "GradientTableViewCell.h"</span>
</code></pre></div>
<p>Then, change this line in <code>tableView:cellForRowAtIndexPath:</code></p>
<div class="highlight"><pre><span></span><code><span class="n">cell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="bp">UITableViewCell</span><span class="w"> </span><span class="n">alloc</span><span class="p">]</span><span class="w"> </span><span class="n">initWithStyle</span><span class="o">:</span><span class="n">UITableViewCellStyleSubtitle</span><span class="w"></span>
<span class="w"> </span><span class="nl">reuseIdentifier</span><span class="p">:</span><span class="n">CellIdentifier</span><span class="p">];</span><span class="w"></span>
</code></pre></div>
<p>to this:</p>
<div class="highlight"><pre><span></span><code><span class="n">cell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="n">GradientTableViewCell</span><span class="w"> </span><span class="n">alloc</span><span class="p">]</span><span class="w"> </span><span class="n">initWithStyle</span><span class="o">:</span><span class="n">UITableViewCellStyleSubtitle</span><span class="w"></span>
<span class="w"> </span><span class="nl">reuseIdentifier</span><span class="p">:</span><span class="n">CellIdentifier</span><span class="p">];</span><span class="w"></span>
</code></pre></div>
<p>Build and run the project, and you should get this:</p>
<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/WithGradient.png" alt="With gradient"></p>
<p>See, easy!</p>
<p>Note that the gradient image I provide is pretty subtle, so the effect may not show up well in your web browser. Look at in on an actual iPhone, and experiment with different background images to get the effect you want.</p>"Speak Count of Words on Clipboard" Automator Service2009-11-01T23:43:35-05:002009-11-01T23:43:35-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-01:/speak-count-words-clipboard-automator-service.html<p>As part of my <a href="https://undefinedvalue.com/2009/11/01/not-quite-nanowrimo">"write a blog entry every day during November"</a> commitment, I considered imposing a minimum word limit for each entry. I've decided against that, because I don't want to feel pressure to add filler, but before deciding that, I created an Automator service that would help me …</p><p>As part of my <a href="https://undefinedvalue.com/2009/11/01/not-quite-nanowrimo">"write a blog entry every day during November"</a> commitment, I considered imposing a minimum word limit for each entry. I've decided against that, because I don't want to feel pressure to add filler, but before deciding that, I created an Automator service that would help me to count words.</p>
<!--break-->
<h2>How to Use the Service</h2>
<p>To use it, just select some text, hit Command-C to copy it to the clipboard, and then choose <strong>Speak Count of Words on Clipboard</strong> from the application's <strong>Services</strong> menu.</p>
<h2>How to Create It</h2>
<p>Here's how to create the service (using Mac OS X 10.6 Snow Leopard, of course):</p>
<ol>
<li>Start Automator, and choose "Service" as the workflow template.</li>
<li>Choose "no input" and "any application" in the "Service receives . . ." section at the top.</li>
<li>Drag a <strong>Get Contents of Clipboard</strong> action from the left column to the workflow pane.</li>
<li>Drag a <strong>Run Shell Script</strong> action from the left column to the workflow pane, beneath the <strong>Get Contents of Clipboard</strong> action.</li>
<li>In the <strong>Run Shell Script</strong> text area, replace "cat" with this:<pre>
echo "The selection contains `wc -w` words."</pre></li>
<li>Drag a <strong>Speak Text</strong> action from the left column into the workflow area, beneath the <strong>Run Shell Script</strong> action.</li>
<li>Choose the <strong>File -> Save</strong> menu item, and save it as "Speak Count of Words on Clipboard".</li>
</ol>
<p>Here's how it should look:</p>
<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/Speak_Count_of_Words_on_Clipboard_service.png" alt="Picture"></p>
<p>If you think you are going to use this a lot, go to the Services Preferences and assign a keyboard shortcut.</p>Not Quite NaNoWriMo2009-11-01T23:28:33-05:002009-11-01T23:28:33-05:00Kristopher Johnsontag:undefinedvalue.com,2009-11-01:/not-quite-nanowrimo.html<div style="float: right; margin-left: 10px; margin-bottom: 10px;"><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/iStock_000002585519XSmall.jpg" alt="Picture"></div>
<p>I've often dreamed of participating in <a href="http://www.nanowrimo.org/eng/whatisnano">NaNoWriMo</a>, the National Novel Writing Month, in which people pledge to write a novel during the month of November. Unfortunately, I'm not really a novel-writing guy. I could maybe write a short story or two, but I'm just not enough of a writer to …</p><div style="float: right; margin-left: 10px; margin-bottom: 10px;"><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/iStock_000002585519XSmall.jpg" alt="Picture"></div>
<p>I've often dreamed of participating in <a href="http://www.nanowrimo.org/eng/whatisnano">NaNoWriMo</a>, the National Novel Writing Month, in which people pledge to write a novel during the month of November. Unfortunately, I'm not really a novel-writing guy. I could maybe write a short story or two, but I'm just not enough of a writer to generate 2,000 words of fiction per day.</p>
<p>So, instead of doing that, I'm going to commit to do these things during November:</p>
<ol>
<li>Publish a blog article every day.</li>
<li>Get a new iPhone application into the App Store.</li>
</ol>
<p>This article does not count as a daily blog entry, as it is really more of a meta-entry. The real blog entries need to be programming-related or computer-related.</p>
<p>This is why you'll be seeing a lot of blog entries from me this month. Please feel free to call me bad names if I skip a day.</p>Getting Into iPhone Development2009-10-27T13:46:41-04:002009-10-27T13:46:41-04:00Kristopher Johnsontag:undefinedvalue.com,2009-10-27:/getting-iphone-development.html<p>I know some people who are interested in getting into iPhone application development. There is a lot of advice out there. Here's my advice for experienced developers who start out knowing nothing about iPhone or Cocoa development:</p>
<ul>
<li>If you don't know the C programming language, learn it.</li>
<li>Read Matt Gemmell's …</li></ul><p>I know some people who are interested in getting into iPhone application development. There is a lot of advice out there. Here's my advice for experienced developers who start out knowing nothing about iPhone or Cocoa development:</p>
<ul>
<li>If you don't know the C programming language, learn it.</li>
<li>Read Matt Gemmell's "<a href="http://mattgemmell.com/2009/07/14/iphone-development-emergency-guide">iPhone Development Emergency Guide</a>".</li>
<li>Buy and read <em><a href="http://pragprog.com/titles/amiphd/iphone-sdk-development">iPhone SDK Development</a></em> by Bill Dudney and Chris Adamson. Also buy and watch the Xcode- and iPhone-related screencasts available from that web page.</li>
</ul>
<p>After that, you'll be able to delve into the other things you need in Apple's documentation. I'd recommend learning all you can about <a href="https://undefinedvalue.com/2009/07/04/review-core-animation-mac-os%C2%A0x-and-iphone-bill%C2%A0dudney">Core Animation</a> and Core Data, but it depends a lot on exactly what kinds of apps you want to write.</p>
<p>Finally, write lots of little iPhone apps before you start on that big important app you really want to write. The language and tools are a little strange at first, but the more you use them, the better they feel.</p>A Little Service That Converts Files to EPUB Format2009-10-09T13:26:19-04:002009-10-09T13:26:19-04:00Kristopher Johnsontag:undefinedvalue.com,2009-10-09:/little-service-converts-files-epub-format.html<!--break-->
<p>My wife recently gave me a nice gift: a <a href="http://en.wikipedia.org/wiki/Sony_Reader">Sony Reader</a> device. She'd been watching me read books using the Kindle for iPhone app, and felt sorry for me because I had to read from such a small screen. The Reader is really nice. (Thanks, honey.)</p>
<p>Now I'm learning all …</p><!--break-->
<p>My wife recently gave me a nice gift: a <a href="http://en.wikipedia.org/wiki/Sony_Reader">Sony Reader</a> device. She'd been watching me read books using the Kindle for iPhone app, and felt sorry for me because I had to read from such a small screen. The Reader is really nice. (Thanks, honey.)</p>
<p>Now I'm learning all about how to get content onto the device. The Sony eBookLibrary software that you use to copy files to the device is pretty bad (worse than iTunes, if you can believe that), so I'm using it as little as possible. I ran across <a href="http://calibre.kovidgoyal.net/">calibre</a>, a nice open-source tool for managing various e-reader devices. Unfortunately, it can't handle the DRM'ed files that one downloads from the Sony eBookstore, and after transferring books to the device, the device often crashes and has to reboot itself.</p>
<p>So, my compromise for now is to use calibre's <code>ebook-convert</code> command-line utility to convert files to EPUB format, and then use Sony eBookLibrary to transfer those to the device. To make this easier, I've created a service, using Automator, that will invoke <code>ebook-convert</code> on files selected in the Finder.</p>
<p>If you want such a thing for yourself, here's how to make it:</p>
<h2>Install the calibre command-line tools</h2>
<ol>
<li>Download <a href="http://calibre.kovidgoyal.net/download">calibre</a> and install it.</li>
<li>Start calibre, and click the <strong>Preferences</strong> toolbar button.</li>
<li>Select Advanced in the list on the left, and click the <strong>Install command-line tools</strong> button.</li>
</ol>
<h2>Create the service</h2>
<p>(Note: This requires Mac OS X 10.6 Snow Leopard or newer.)</p>
<ol>
<li>Start the Automator application.</li>
<li>When prompted to choose a workflow template, select <strong>Service</strong> and click <strong>Choose</strong>.</li>
<li>In the <strong>Service receives selected</strong> list, choose <strong>documents</strong>.</li>
<li>In the <strong>in</strong> list, choose <strong>Finder</strong>.</li>
<li>Drag a <strong>Run shell script</strong> action from the list into the workflow area.</li>
<li>Leave the <strong>Shell:</strong> option set to <strong>/bin/bash</strong></li>
<li>For the <strong>Pass input:</strong> option, choose <strong>as arguments</strong></li>
<li>Change the script text to this:</li>
</ol>
<pre>
for f in "$@"
do
/usr/bin/ebook-convert "$f" "$f.epub" --output-profile=sony
done
</pre>
<ol>
<li>Choose the <strong>File -> Save</strong> menu item, and give the service a name like "Convert to EPUB"</li>
</ol>
<p>To test your new service, go to the Finder and select a text, RTF, PDF, or other such file, and then choose the <strong>File -> Services -> Convert to EPUB</strong> menu item. Note that conversion may take a while, depending upon how large the input file is.</p>
<p>I am a newbie at this. If there are better ways, I'd love to hear about them.</p>Building Emacs from Source for Mac OS X2009-09-12T22:29:05-04:002009-09-12T22:29:05-04:00Kristopher Johnsontag:undefinedvalue.com,2009-09-12:/building-emacs-source-mac-os-x.html<p>There are a few binary Emacs packages for OS X floating around out there, but I always build it myself from the sources. This usually results in an Emacs that works the way I expect, rather than the way some "helpful" distributor thinks it ought to work.</p>
<p>I'll assume you …</p><p>There are a few binary Emacs packages for OS X floating around out there, but I always build it myself from the sources. This usually results in an Emacs that works the way I expect, rather than the way some "helpful" distributor thinks it ought to work.</p>
<p>I'll assume you have the developer tools and <code>bzr</code> installed, and know how to open Terminal and type some commands. Here are the commands you need to type:</p>
<blockquote>
<p>bzr init-repo --2a emacs/</p>
<p>cd emacs</p>
<p>bzr branch bzr://bzr.savannah.gnu.org/emacs/trunk/</p>
<p>cd trunk</p>
<p>./configure --with-ns</p>
<p>make install</p>
</blockquote>
<p>When this is complete, you'll end up with <code>Emacs.app</code> in the <code>nextstep</code> subdirectory. You can run <code>Emacs.app</code> from there, or copy it to your Applications directory.</p>
<hr>
<p><strong>Update 2010/10/29:</strong> Discovered that the Emacs team now uses Bazaar (<code>bzr</code>) rather than CVS. Updated the instructions accordingly, following advice from http://www.emacswiki.org/emacs/EmacsForMacOS and http://www.emacswiki.org/emacs/BzrForEmacsDevs. Also, found what appears to be a faithful binary distribution at http://emacsformacosx.com/.</p>The iTunes UI Sucks2009-09-08T11:13:41-04:002009-09-08T11:13:41-04:00Kristopher Johnsontag:undefinedvalue.com,2009-09-08:/itunes-ui-sucks.html<p>I like most of Apple's products, but iTunes is a very dark corner of the Mac universe.</p>
<p>For example, here is what you have to do to download updates to your iPhone apps:</p>
<ol>
<li>Click the <em>Applications</em> link in the upper-left corner of the navigation pane.</li>
<li>Move the mouse over to …</li></ol><p>I like most of Apple's products, but iTunes is a very dark corner of the Mac universe.</p>
<p>For example, here is what you have to do to download updates to your iPhone apps:</p>
<ol>
<li>Click the <em>Applications</em> link in the upper-left corner of the navigation pane.</li>
<li>Move the mouse over to the lower-right corner and click the tiny, tiny <em>Check for updates</em> button.</li>
<li>Move the mouse to the upper-right corner and click the <em>Download All Free Updates</em> button.</li>
</ol>
<p>Three steps, requiring moving the mouse pointer all the way across the screen between each. There is no automatic-update feature.</p>
<p>There is probably some way to write an AppleScript to automate this, but <a href="http://www.grumpytico.com/?p=66">AppleScript sucks too</a>.</p>
<p>Do a web search for "iTunes sucks", and you'll find a lot more examples.</p>
<p>To paraphrase <a href="http://www.research.att.com/~bs/bs_faq.html">Bjarne Stroustrup</a>: There is software everyone complains about, and software nobody uses. One could chalk up the iTunes hate to the simple fact that so many people are forced to use it whether they like it or not. But iTunes really does suck. I wish Apple would do a complete re-design of its UI, and make it act like an Apple product.</p>We Loves the Preciousss2009-09-02T11:41:21-04:002009-09-02T11:41:21-04:00Kristopher Johnsontag:undefinedvalue.com,2009-09-02:/we-loves-preciousss.html<p>It's not always easy to be an Apple fanboy: read "<a href="http://www.kungfugrippe.com/post/177715198/confessional">In Nomine Jobs, et Woz, et Spiritus Schiller</a>" by Merlin Mann</p>
<p>I've installed Snow Leopard on my old 13-inch white Macbook (which I don't use for anything important). I've had no problems with it, but I'm going to wait a …</p><p>It's not always easy to be an Apple fanboy: read "<a href="http://www.kungfugrippe.com/post/177715198/confessional">In Nomine Jobs, et Woz, et Spiritus Schiller</a>" by Merlin Mann</p>
<p>I've installed Snow Leopard on my old 13-inch white Macbook (which I don't use for anything important). I've had no problems with it, but I'm going to wait a month or two before upgrading the Macs that I rely on. I want to wait until Apple releases a patch or two, and I need to let some <a href="http://bit.ly/1pVXk5">dinosaurs</a> catch up.</p>
<p>Snow Leopard is a nice upgrade which is definitely worth the thirty bucks, but for most users, it doesn't provide any benefits that justify the pain of being an early adopter.</p>
<p><strong>UPDATE:</strong> Have installed Snow Leopard on my work laptop. No problems, except that my HAL 9000 screensaver doesn't work anymore.</p>Menubar Countdown 1.2 Works with Snow Leopard2009-09-02T02:49:28-04:002009-09-02T02:49:28-04:00Kristopher Johnsontag:undefinedvalue.com,2009-09-02:/menubar-countdown-12-works-snow-leopard.html<p><a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a>, my Mac OS X countdown timer application, has been tested with 10.6 Snow Leopard, and it seems to work just fine.</p>
<p>(But if anyone finds otherwise, please let me know.)</p>MINI Cooper S Convertible Review2009-08-30T22:47:08-04:002009-08-30T22:47:08-04:00Kristopher Johnsontag:undefinedvalue.com,2009-08-30:/mini-cooper-s-convertible-review.html<p>My wife and I bought a <a href="http://www.miniusa.com/mini-cooper-conv-top-features.html#/learn/FACTS_FEATURES_SPECS/Top_Features-m">MINI Cooper S convertible</a> almost a year ago. These are my thoughts about it:</p>
<p>The MINI Cooper convertible is the Apple Macintosh of cars. It's very expensive in comparison to other models with similar performance and features, and it has a lot of annoying …</p><p>My wife and I bought a <a href="http://www.miniusa.com/mini-cooper-conv-top-features.html#/learn/FACTS_FEATURES_SPECS/Top_Features-m">MINI Cooper S convertible</a> almost a year ago. These are my thoughts about it:</p>
<p>The MINI Cooper convertible is the Apple Macintosh of cars. It's very expensive in comparison to other models with similar performance and features, and it has a lot of annoying flaws, but people who own them absolutely love them. It is the only car I've owned that has <em>delighted</em> me.</p>
<p>First, I'll note that I am not a "car guy." I won't say anything about horsepower, torque, zero-to-60, how it handles on a race course, or how it compares to any of the bazillion other cars on the road.</p>
<p>I'll start with what I don't like. It's small. Two adults fit in the front seats fine, but the rear seat is basically unusable. The trunk is almost unusable as well. We've had to cut shopping trips short because we knew we were carrying more shopping bags in our hands than would fit in the car. A full-size suitcase won't fit in the trunk, so think "duffel bags" if you want to do any traveling.</p>
<p>While it's small, you don't get a lot of the advantages of a small car. We only get about 26 MPG, in mostly highway driving. It's short nose-to-tail, but is about as wide as a typical car, so you can't squeeze into those parking spaces that other cars have to pass by. The turning radius is a lot wider than you would expect.</p>
<p>With the convertible top up, visibility is terrible. You can't see much out the rear window, due to the roll-bar thingees on top of the rear seats, and the blind spots in the rear quarters are huge. I often have to put my chin down on the steering wheel to see traffic signals, due to the low ceiling.</p>
<p>The GPS system sucks. </p>
<p>But with all those problems, I'm still thrilled with it.</p>
<p>Despite its small size and tiny engine, it feels more like a mid-size car. It's very stable, and takes corners well. The automatic transmission works pretty well in most circumstances, and switching into the semi-automatic mode is great when driving on twisty mountain roads like the one that goes up to my house.</p>
<p>And it has this magical aura that makes people smile. People come up to me in parking lots and ask about it. Other MINI drivers wave at me on the road. </p>
<p>So, if you just want practical inexpensive transportation, stay away from the MINI. But if you want to be happy when you're driving, give it a try.</p>
<p>P.S. Don't buy a MINI or any other car from Global Imports BMW in Atlanta. <a href="http://kristopherjohnson.blogspot.com/2008/12/letter-to-global-imports-bmw.html">Those guys suck.</a></p>Remoting.Corba2009-08-26T10:04:00-04:002009-08-26T10:04:00-04:00Kristopher Johnsontag:undefinedvalue.com,2009-08-26:/remotingcorba.html<p>[Note: The Remoting.Corba project is "dead", and has not been active for several years. If you need .NET-CORBA interoperability, I recommend checking out <a href="http://iiop-net.sourceforge.net">IIOP.NET</a>. Archived Remoting.Corba documentation is still available via <a href="http://tinyurl.com/remoting-corba">The Wayback Machine</a>.]</p>
<p><a href="http://remoting-corba.sourceforge.net">Remoting.Corba</a> is an open-source .NET library that provides interoperability between the .NET …</p><p>[Note: The Remoting.Corba project is "dead", and has not been active for several years. If you need .NET-CORBA interoperability, I recommend checking out <a href="http://iiop-net.sourceforge.net">IIOP.NET</a>. Archived Remoting.Corba documentation is still available via <a href="http://tinyurl.com/remoting-corba">The Wayback Machine</a>.]</p>
<p><a href="http://remoting-corba.sourceforge.net">Remoting.Corba</a> is an open-source .NET library that provides interoperability between the .NET Framework and CORBA servers/clients, using the .NET Remoting architecture.
<p>
I started this project to learn more about .NET Remoting and to apply my knowledge of CORBA. My basic goal was to be able to write .NET programs that interacted with CORBA-based applications using typical .NET Remoting code. For example, if a CORBA server supported this IDL:
<p>
<pre>
// CORBA IDL
interface Adder {
long add_longs(in long arg1, in long arg2);
double add_doubles(in double arg1, in double arg2);
};</pre>
<p>
then one could write a C# program that invoked the server, without need for an ORB, using code something like this:
<p>
<pre>
// standard .NET Remoting stuff
using System;
using System.IO;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Messaging;
// Remoting.Corba
using Remoting.Corba.Channels.Iiop;
namespace MyExample
{
// define .NET interface mapping for IDL interface
interface Adder {
int add_longs(int arg1, int arg2);
double add_doubles(double arg1, double arg2);
};
class App {
// entry point
static void Main(string[] args)
{
try
{
// register IIOP channel with Remoting
ChannelServices.RegisterChannel(new IiopClientChannel());
// Use a standard CORBA stringified object reference for the server
string ior = "corbaloc:iiop:localhost:9999/Adder";
// create the proxy to the server object
Adder server = (Adder) Activator.GetObject(typeof(Adder), ior);
// execute some methods on the remote object
Console.Out.WriteLine("1 + 2 = {0}", server.add_longs(1, 2));
Console.Out.WriteLine("1.0 + 2.0 = {0}", server.add_doubles(1.0, 2.0));
}
catch (Exception ex)
{
Console.Error.WriteLine("Exception: " + ex.ToString());
}
}
}
}</pre>
<p>
The IiopClientChannel class from the R.C library takes care of generating and interpreting the CORBA IIOP (Internet Inter-ORB Protocol) messages and pumping them through the .NET runtime. There is also an IiopServerChannel class that can be used to implement CORBA servers using .NET Remoting.
<p>
It was pretty cool when it worked. The .NET Framework's Remoting architecture is very open, allowing programmers to plug in their own network protocols and messaging formats. I used some custom attributes to provide information needed by the CORBA engine that could not be gleaned via data types and reflection mechanisms.
<p>
Unfortunately, while the .NET Remoting architecture is open, it is not well documented. If all you want to do is send a SOAP message via some protocol other than HTTP (for example, via UDP, via message queues, via e-mail, etc.), then it is pretty easy to plug your own stuff in. But if you are doing anything more complicated, then you will quickly discover that the only way to figure out what's going on inside .NET is to look at the <a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=3A1C93FA-7462-47D0-8E56-8DD34C6292F0&displaylang=en">Rotor</a> source code.
<p>
The project was valuable to me in that I learned a lot about .NET and C#. It was also gratifying to see other people using it and expressing interest in it. I was especially excited when it was mentioned in Don Box's Spoutlet, and when Miguel Icaza started playing around with it in Mono. It even got a few pages devoted to it in a <a href="http://www.amazon.com/exec/obidos/tg/detail/-/0735619220/qid=1069133497/sr=1-1/ref=sr_1_1/104-4292430-8931121?v=glance&s=books">Microsoft Press book</a>!
<p>
I wrote this code during a period when I was not working any paying jobs, so I was always hoping I would find somebody willing to pay me to add features. There were a few nibbles, but users were generally able to add features themselves instead of hiring me to do so. I talked to some people at Microsoft and Inprise about potential deals, but nothing came of it.
<p>
I have put a few projects on SourceForge, but this was the first one where I got any significant contributions from other developers. Michael Sawczyn created an IDL compiler. Other developers did some bug fixing, refactoring, and feature additions. It never reached the "critical mass" needed to keep it going after I lost interest, but I think it was close.
<p>
I worked on R.C for a few months. I lost interest in it due to several factors:
<ul>
<li>No paying customers.</li>
<li>No real need to use it myself.</li>
<li>All the low-hanging fruit was gone; further development would have involved a lot of very technical work with little tangible benefit.</li>
<li>The appearance of commercial CORBA ORBs for .NET from Borland and others.</li>
<li>Limitations of the .NET framework</li>
</ul>
<p>
We hit limitations of the .NET framework when trying to figure out how to map remote object references back to objects in the same process. The only way to do it was to use reflection to invoke private methods of internal .NET classes. That prospect triggered enough of an "Ewww!" response that I decided further work was only going to make me feel worse.
<p>
I used a <a href="http://web.archive.org/web/20041213054715/kristopherjohnson.net/cgi-bin/rc/wiki.pl?Remoting.Corba_Wiki">wiki</a> to distribute the "documentation" for the project. I liked how the wiki turned out, but there were few contributions to it from anyone except myself.
<p>
So what did I get out of Remoting.Corba? Obviously, I learned about .NET Remoting and some more about the internals of CORBA. I got practice writing C# networking code. I made some contacts in the industry. I wish I could have used R.C on a real project, but that might have taken the fun out of it.Changing Background Color and Section Header Text Color in a Grouped-style UITableView2009-08-26T00:10:40-04:002009-08-26T00:10:40-04:00Kristopher Johnsontag:undefinedvalue.com,2009-08-26:/changing-background-color-and-section-header-text-color-grouped-style-uitableview.html<p>While working on an iPhone application, I decided I wanted to change the colors of the background and section headers of a <code>UITableView</code> with the <code>UITableViewStyleGrouped</code> style. It took a lot more work than I expected, so I'm sharing what I learned with anyone else who needs to do this …</p><p>While working on an iPhone application, I decided I wanted to change the colors of the background and section headers of a <code>UITableView</code> with the <code>UITableViewStyleGrouped</code> style. It took a lot more work than I expected, so I'm sharing what I learned with anyone else who needs to do this.</p>
<p>To review: when you create a table view with the grouped style, each section of the table shows up as a rounded rectangle, section titles are displayed as dark gray text between the rectangles, and the background is gray. I wanted the background to be a pale pastel color, so I looked into how to change that.</p>
<p>Of course, I first looked at the options in Interface Builder. A table view has a background color that can be set in IB, but setting that didn't accomplish anything.</p>
<p>With a bit of Googling, I learned that the way to give a table view a background color is to set the background color to <code>[UIColor clearColor]</code>, and let the color (or image) of whatever is behind the table view show through. So, I set my window's <code>backgroundColor</code> to the color I wanted, and then added this to my table view's controller class:</p>
<div class="highlight"><pre><span></span><code><span class="cp">#define TableViewTag 8888</span>
<span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">viewDidLoad</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">viewDidLoad</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Make table view's background transparent to allow window background to be visible</span>
<span class="w"> </span><span class="bp">UITableView</span><span class="w"> </span><span class="o">*</span><span class="n">tableView</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="bp">UITableView</span><span class="w"> </span><span class="o">*</span><span class="p">)[</span><span class="nb">self</span><span class="p">.</span><span class="n">view</span><span class="w"> </span><span class="n">viewWithTag</span><span class="o">:</span><span class="n">TableViewTag</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">tableView</span><span class="p">.</span><span class="n">backgroundColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">clearColor</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>In Interface Builder, I set the tag of the table view to 8888. Another way to do this would be to have an outlet for the table view, but I didn't want to do that in this particular case.</p>
<p>So, that gave the table the background color I wanted, but I then noticed that the dark-gray section titles didn't look good against that color. I started looking for some sort of "<code>sectionHeaderTextColor</code>" property on the table view, but of course, there was no such thing.</p>
<p>After more Googling, I concluded that there was no way to just set the color. What you have to do is provide your own custom section header view containing a text label. With that, you can set whatever colors you want. So, I added these implementations of <code>UITableViewDataSource</code> protocol methods to my table view controller:</p>
<div class="highlight"><pre><span></span><code><span class="cp">#define SectionHeaderHeight 40</span>
<span class="p">-</span> <span class="p">(</span><span class="n">CGFloat</span><span class="p">)</span><span class="nf">tableView:</span><span class="p">(</span><span class="bp">UITableView</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">tableView</span><span class="w"> </span><span class="nf">heightForHeaderInSection:</span><span class="p">(</span><span class="n">NSInteger</span><span class="p">)</span><span class="nv">section</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">([</span><span class="nb">self</span><span class="w"> </span><span class="n">tableView</span><span class="o">:</span><span class="n">tableView</span><span class="w"> </span><span class="n">titleForHeaderInSection</span><span class="o">:</span><span class="n">section</span><span class="p">]</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nb">nil</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">SectionHeaderHeight</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// If no section header title, no section header needed</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="bp">UIView</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nf">tableView:</span><span class="p">(</span><span class="bp">UITableView</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">tableView</span><span class="w"> </span><span class="nf">viewForHeaderInSection:</span><span class="p">(</span><span class="n">NSInteger</span><span class="p">)</span><span class="nv">section</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="n">sectionTitle</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">tableView</span><span class="o">:</span><span class="n">tableView</span><span class="w"> </span><span class="n">titleForHeaderInSection</span><span class="o">:</span><span class="n">section</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">sectionTitle</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">nil</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">nil</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Create label with section title</span>
<span class="w"> </span><span class="bp">UILabel</span><span class="w"> </span><span class="o">*</span><span class="n">label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[[</span><span class="bp">UILabel</span><span class="w"> </span><span class="n">alloc</span><span class="p">]</span><span class="w"> </span><span class="n">init</span><span class="p">]</span><span class="w"> </span><span class="n">autorelease</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">frame</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">CGRectMake</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="mi">300</span><span class="p">,</span><span class="w"> </span><span class="mi">30</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">backgroundColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">clearColor</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">textColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">colorWithHue</span><span class="o">:</span><span class="p">(</span><span class="mf">136.0</span><span class="o">/</span><span class="mf">360.0</span><span class="p">)</span><span class="w"> </span><span class="c1">// Slightly bluish green</span>
<span class="w"> </span><span class="nl">saturation</span><span class="p">:</span><span class="mf">1.0</span><span class="w"></span>
<span class="w"> </span><span class="nl">brightness</span><span class="p">:</span><span class="mf">0.60</span><span class="w"></span>
<span class="w"> </span><span class="nl">alpha</span><span class="p">:</span><span class="mf">1.0</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">shadowColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIColor</span><span class="w"> </span><span class="n">whiteColor</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">shadowOffset</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">CGSizeMake</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span><span class="w"> </span><span class="mf">1.0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">font</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="bp">UIFont</span><span class="w"> </span><span class="n">boldSystemFontOfSize</span><span class="o">:</span><span class="mi">16</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sectionTitle</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Create header view and add label as a subview</span>
<span class="w"> </span><span class="bp">UIView</span><span class="w"> </span><span class="o">*</span><span class="n">view</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="bp">UIView</span><span class="w"> </span><span class="n">alloc</span><span class="p">]</span><span class="w"> </span><span class="n">initWithFrame</span><span class="o">:</span><span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">320</span><span class="p">,</span><span class="w"> </span><span class="n">SectionHeaderHeight</span><span class="p">)];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">view</span><span class="w"> </span><span class="n">autorelease</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">view</span><span class="w"> </span><span class="n">addSubview</span><span class="o">:</span><span class="n">label</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">view</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>I also added a suitable implementation of <code>tableView:titleForHeaderInSection:</code>, and everything worked.</p>
<p>If there is a simpler way to do this, I'd love to hear about it.</p>Netgear ReadyNAS Duo 2000 Setup and Review2009-08-20T00:40:10-04:002009-08-20T00:40:10-04:00Kristopher Johnsontag:undefinedvalue.com,2009-08-20:/netgear-readynas-duo-2000-setup-and-review.html<p>I have previously written about my <a href="https://undefinedvalue.com/2007/10/26/backups">backup strategy</a>. I've never really worried about backups too much. In the 30 years I've been using computers, I've never lost a hard drive.</p>
<p>...until last weekend. My wife's MacBook Air was displaying some funny behavior, so I ran Disk Utility on it. Disk …</p><p>I have previously written about my <a href="https://undefinedvalue.com/2007/10/26/backups">backup strategy</a>. I've never really worried about backups too much. In the 30 years I've been using computers, I've never lost a hard drive.</p>
<p>...until last weekend. My wife's MacBook Air was displaying some funny behavior, so I ran Disk Utility on it. Disk Utility said the drive had problems, so I clicked <em>Repair Disk</em>, and it would not boot thereafter. I called Apple, and their expert told me I'd have to erase the drive and reinstall the operating system. We had no backups for that machine. My wife was not happy to lose everything. And of course, it is <em>my</em> fault she had no backups. (I've told her about Time Machine, but she didn't believe it could really be that easy to set up, so she never did.)</p>
<p>So, better late than never, I decided to get some network storage and start backing up everything to it. A friend was happy with his Netgear ReadyNAS, so I ordered a Netgear ReadyNAS Duo 2000 from Amazon, along with two 1-TB Western Digital Caviar Green hard drives. Total price came out to about $400.</p>
<p>The ReadyNAS has two drive bays. Most models come with a drive included, but I bought the "bare" one that has no preinstalled drives, assuming that it would be cheaper to buy my own drives. Upon reading the manuals, I immediately hit a problem: the manuals explain how to add a second hard drive to a ReadyNAS that comes with a single drive, but nothing about what to do if you have an empty ReadyNAS.</p>
<p>Hoping for the best, I installed the two Caviar drives in the NAS, plugged it into the network, plugged it into power, and hit the power button. It turned on, but the slowly blinking LED didn't give me a warm fuzzy feeling.</p>
<p>I installed the RAIDar software, which one uses to manage the device, and it told me I had bad disks.</p>
<p>After about half an hour of Googling, registering the product, registering for the Netgear forums, and registering for the ReadyNAS forums (each of which required separate sign-up forms and e-mail confirmations), I finally found a current <a href="http://www.readynas.com/forum/faq.php">ReadyNAS FAQ</a> (after hitting a few old FAQs that were created before my model existed). There was nothing specific about my situation, but I decided I would try the "factory reset". That worked: RAIDar enabled its Setup button, and I was able to initialize everything.</p>
<p>I was initially worried about the fan noise. When the ReadyNAS boots up, the fans are incredibly loud for something that small. After 30 seconds or so, the noise drops a little, but it was still really loud. A Google search for "readynas loud fan" indicated that lots of users have replaced their ReadyNAS fans, due to the noise. But after the drives got formatted, the fan noise dropped to a barely detectable level. It's not as quiet as my Macs, but it's not loud enough to be annoying.</p>
<p>It was very easy to set up <a href="http://www.readynas.com/?p=1097">Time Machine with the ReadyNAS</a>. (First thing I did was back up my wife's Air, of course.)</p>
<p>The ReadyNAS has a lot of other features that I am not yet using. $400 for a backup solution seems a little pricey, when one can buy a 1-TB drive and enclosure for about $100, but having storage available on the network means we're more likely to actually do backups. We'll see how things go.</p>
<p>A complete description of all the features can be found here: http://www.readynas.com/?p=177</p>When Bailey Grows Up...2009-08-05T22:57:39-04:002009-08-05T22:57:39-04:00Kristopher Johnsontag:undefinedvalue.com,2009-08-05:/when-bailey-grows.html<p>On the drive home today, my ten-year-old stepson Bailey told me what he is going to have when he grows up:</p>
<ul>
<li>20-30 billion dollars</li>
<li>A really big house, with a fence and guard dogs</li>
<li>Two Ferraris, and a Corvette</li>
<li>Hundreds of kittens. (He'll have to hire people to take care …</li></ul><p>On the drive home today, my ten-year-old stepson Bailey told me what he is going to have when he grows up:</p>
<ul>
<li>20-30 billion dollars</li>
<li>A really big house, with a fence and guard dogs</li>
<li>Two Ferraris, and a Corvette</li>
<li>Hundreds of kittens. (He'll have to hire people to take care of the kittens, because he'll be too busy with guard-dog training.)</li>
<li>An aquarium with rare fish</li>
<li>Security dudes</li>
<li>Snipers hidden in the dark</li>
<li>Motion detectors that will deliver an electric shock to anyone who tries to hurt any of the security dudes</li>
<li>A special button, carried in his back pocket, that will instantly drop an anvil on any attackers. The anvil won't hurt them too much; it will just knock them through the floor into a jail cell.</li>
<li>A locked room containing gold bricks. The only people with keys to this room will be Bailey, his best friend, and the security dudes. The security dudes get paid twenty dollars per week, and two gold bricks per month.</li>
</ul>
<p>Sounds like a plan. I hope he'll let my wife and me stay somewhere on the grounds, preferably in a rent-free beach house like Thomas Magnum.</p>
<p>He also asked me if he could get rich by making toothpaste. I wish him luck.</p>Mac App Guide Review of Menubar Countdown2009-07-30T14:45:27-04:002009-07-30T14:45:27-04:00Kristopher Johnsontag:undefinedvalue.com,2009-07-30:/mac-app-guide-review-menubar-countdown.html<p>The Mac App Guide video podcast has reviewed my <a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a> application. The review includes a demonstration of how to use it.</p>
<p>See <a href="http://www.macappguide.com/2009/07/29/mag-23-handy-countdown-timer-freeware-for-mac-os-x/">MAG 23: Handy Countdown Timer (Freeware) for Mac OS X</a>.</p>Lunar Lander2009-07-21T03:24:42-04:002009-07-21T03:24:42-04:00Kristopher Johnsontag:undefinedvalue.com,2009-07-21:/lunar-lander.html<p>Whenever I'm asked how I got interested in computers, I relate this story:</p>
<p>When I was about ten years old, my father took me to an IBM open house. Dad worked for IBM during its heyday, when it was the biggest computer company in the world and would seemingly control …</p><p>Whenever I'm asked how I got interested in computers, I relate this story:</p>
<p>When I was about ten years old, my father took me to an IBM open house. Dad worked for IBM during its heyday, when it was the biggest computer company in the world and would seemingly control the computer industry forever. Employees were treated very well, with lots of company-sponsored picnics, dinners, holiday parties, and so forth. This open house was a celebration of the opening of yet another new IBM office in the Atlanta area.</p>
<p>I was a smart kid, but had never played with computers. This was before TRS-80's and other home computers were available. I think we may have had some Sears-marketed Pong game at home, but otherwise my experience with computers consisted of what I saw in sci-fi TV shows and movies.</p>
<p>While walking around the office, Dad showed me and the rest of my family the IBM systems. I was bored: all I saw were a bunch of glass-walled rooms containing green-screen terminals with huge printers connected to them, and guides bragged about how many pages of text could fit in their memories and how fast the tractor-feed printers could print reports and paychecks.</p>
<p>Then, almost as an afterthought, we went into this little room where a couple of bearded guys were sitting at a small terminal. I immediately sensed that these guys were not like the other IBM'ers. I now know these guys were <em>programmers</em>, but back then I just knew that even though these guys wore ties, they weren't businessmen. These guys were different, and cool.</p>
<p>They asked me if I wanted to play a game on their computer.</p>
<p>The game was Lunar Lander. It was a purely text-based game, without any graphics or joystick controllers or other videogamey stuff. It told me how high above the lunar surface I was, and what my rate of descent was, and asked me how much fuel I wanted to burn. (See <a href="http://technologizer.com/2009/07/19/lunar-lander/">40 Years of Lunar Lander</a> for examples of what this experience was like.)</p>
<p>I was unable to successfully land, even after several tries, but I was suddenly and permanently obsessed with computers. I had no idea how they worked, but I knew that the computer contained an imaginary universe where a lunar lander was descending toward the moon. I figured that if I learned how computers worked, I could create some imaginary universes of my own.</p>
<p>So, I started asking Dad about how computers worked, and he started borrowing manuals from the system engineers and bringing them home to me. Then I got an Atari 800 computer for Christmas one year.</p>
<p>Since then, there have not been very many days that I have not been playing around with imaginary universes. I owe it all to Lunar Lander and supportive parents.</p>
<p>Thank you, Mom and Dad, and also thank you to the nameless, bearded guys who invited me to join their game.</p>
<p><strong>Update (2019-09-21):</strong> I tracked down the original source code to this game, and ported it to C and Rust. See <a href="lunar-for-c-and-rust.html">LUNAR for C and Rust</a></p>iPhone Camp Atlanta 20092009-07-19T14:22:16-04:002009-07-19T14:22:16-04:00Kristopher Johnsontag:undefinedvalue.com,2009-07-19:/iphone-camp-atlanta-2009.html<p>Yesterday, I attended <a href="http://iphonecampatlanta.org/">iPhone Camp Atlanta 2009</a>, a conference for iPhone developers and others interested in iPhone development (business/marketing people). At the same time and in the same venue, <a href="http://www.mobilecampatlanta.org/">Mobile Camp Atlanta 2009</a> was taking place, targeting developers for Blackberry, Android, Palm Pre, and Windows Mobile.</p>
<p>The conference was …</p><p>Yesterday, I attended <a href="http://iphonecampatlanta.org/">iPhone Camp Atlanta 2009</a>, a conference for iPhone developers and others interested in iPhone development (business/marketing people). At the same time and in the same venue, <a href="http://www.mobilecampatlanta.org/">Mobile Camp Atlanta 2009</a> was taking place, targeting developers for Blackberry, Android, Palm Pre, and Windows Mobile.</p>
<p>The conference was organized as a "BarCamp-style event." I had no idea what that meant. It means that on the day of the conference, attendees who want to give presentations sign up and give them. There were five presentation rooms, and nine half-hour time slots, which meant that 45 sessions could have taken place. The actual number of sessions was about half that, because there just weren't that many people who came prepared to give presentations. </p>
<p>Each session was scheduled to be 30 minutes long. In reality, each session was only about 20 minutes, due to in-between-session movement and other logistics. I would have liked to have seen some longer sessions with more detail, but the short-session format did keep things from dragging.</p>
<p>I enjoyed the sessions I attended. I sat in on a few iPhone development sessions, related to use of Google Toolkit for iPhone, XML parsing, and Core Data. I also sat in on a couple of business-oriented sessions.</p>
<p>As with most conferences, the real reason to attend is not to learn material in the sessions, but to network with other people. Unfortunately, I'm not a great networker, so I wasn't able to take advantage of that aspect.</p>
<p>The organizers did a great job. I'll go again next year. Maybe I'll even work up the courage to give a presentation myself.</p>Adding a Custom View to an NSStatusItem2009-07-07T11:48:30-04:002009-07-07T11:48:30-04:00Kristopher Johnsontag:undefinedvalue.com,2009-07-07:/adding-custom-view-nsstatusitem.html<p>My <a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a> application uses an <a href="http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Classes/NSStatusItem_Class/Reference/Reference.html">NSStatusItem</a> to display itself in the menu bar. I recently had to add a custom view to that status item, and thought I'd share what I learned about the process here.</p>
<!--break-->
<p>A little background: a <em>status item</em> is one of those little thingees you …</p><p>My <a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a> application uses an <a href="http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Classes/NSStatusItem_Class/Reference/Reference.html">NSStatusItem</a> to display itself in the menu bar. I recently had to add a custom view to that status item, and thought I'd share what I learned about the process here.</p>
<!--break-->
<p>A little background: a <em>status item</em> is one of those little thingees you see on the right side of the Mac OS X menu bar, such as the clock, the Spotlight icon, the sound volume control, and so forth. Programmatically, you create a status item by doing this:</p>
<div class="highlight"><pre><span></span><code><span class="n">NSStatusBar</span><span class="w"> </span><span class="o">*</span><span class="n">statusBar</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">NSStatusBar</span><span class="w"> </span><span class="n">systemStatusBar</span><span class="p">];</span><span class="w"></span>
<span class="n">NSStatusItem</span><span class="w"> </span><span class="n">statusItem</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">statusBar</span><span class="w"> </span><span class="n">statusItemWithLength</span><span class="o">:</span><span class="n">NSVariableStatusItemLength</span><span class="p">];</span><span class="w"></span>
<span class="p">[</span><span class="n">statusItem</span><span class="w"> </span><span class="k">retain</span><span class="p">];</span><span class="w"></span>
</code></pre></div>
<p>Once you've created a status item, you can do these things with it:</p>
<ul>
<li>call the <code>setTitle:</code> method to display a string in the menu bar (or call <code>setAttributedTitle</code> to display an attributed string)</li>
<li>call the <code>setMenu:</code> method so that a menu is displayed when the status item is clicked</li>
<li>call the <code>setHighlightMode:</code> method with <code>YES</code> to cause the title to be highlighted appropriately when a menu is displayed</li>
<li>call the <code>setTooltip:</code> method to set a tooltip to be displayed when the mouse hovers over the status item</li>
</ul>
<p>There are other things you can do with a status item, but the above describes the features that Menubar Countdown uses. When the timer is running, the application simply calls <code>setTitle:</code> once per second to display 00:25:00, 00:24:59, 00:24:58, and so on down to 00:00:00. The user can click the item to display a menu that controls the application.</p>
<p>While the <code>setTitle</code> and <code>setImage</code> methods give many developers all the display functionality they need, NSStatusItem also offers the ability to set a custom view that displays in the menu bar. I decided I wanted to add some animation, so I needed a view. The problem is that if you set a custom view, you are responsible for all the drawing and event handling; NSStatusItem no longer does any of that for you.</p>
<p>After a couple of hours of Googling and experimentation, I wound up with something that worked.</p>
<p>I decided I wanted a view class with two properties: <code>statusItem</code>, which would be a pointer to the <code>NSStatusItem</code> associated with the view, and <code>title</code>, which would be the string displayed in the menu bar. The <code>title</code> attribute would make it easy to update the app, as I could just change every instance of <code>[statusItem setTitle:...]</code> to <code>[statusItemView setTitle:...]</code>.</p>
<p>Here is my class declaration:</p>
<div class="highlight"><pre><span></span><code><span class="k">@interface</span> <span class="nc">StatusItemView</span> : <span class="nc">NSView</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">NSStatusItem</span><span class="w"> </span><span class="o">*</span><span class="n">statusItem</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="n">title</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kt">BOOL</span><span class="w"> </span><span class="n">isMenuVisible</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">@property</span><span class="w"> </span><span class="p">(</span><span class="k">retain</span><span class="p">,</span><span class="w"> </span><span class="k">nonatomic</span><span class="p">)</span><span class="w"> </span><span class="n">NSStatusItem</span><span class="w"> </span><span class="o">*</span><span class="n">statusItem</span><span class="p">;</span><span class="w"></span>
<span class="k">@property</span><span class="w"> </span><span class="p">(</span><span class="k">retain</span><span class="p">,</span><span class="w"> </span><span class="k">nonatomic</span><span class="p">)</span><span class="w"> </span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="n">title</span><span class="p">;</span><span class="w"></span>
<span class="k">@end</span><span class="w"></span>
</code></pre></div>
<p>(The purpose of the <code>isMenuVisible</code> instance variable will be explained later.)</p>
<p>The initialization and deallocation methods are straightforward:</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithFrame:</span><span class="p">(</span><span class="n">NSRect</span><span class="p">)</span><span class="nv">frame</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nb">self</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">initWithFrame</span><span class="o">:</span><span class="n">frame</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">self</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">statusItem</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">nil</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">title</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">@""</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">isMenuVisible</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">NO</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">self</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">dealloc</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">statusItem</span><span class="w"> </span><span class="k">release</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">title</span><span class="w"> </span><span class="k">release</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">super</span><span class="w"> </span><span class="n">dealloc</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>The <code>statusItem</code> property can be simply synthesized:</p>
<div class="highlight"><pre><span></span><code><span class="nv">@synthesize</span><span class="w"> </span><span class="n">statusItem</span><span class="p">;</span><span class="w"></span>
</code></pre></div>
<p>Next, we'll tackle menu handling. When our view is clicked, we want to display the menu, and while the menu is displayed, we want our item to be displayed in a highlighted state. <code>NSStatusItem</code> provides the method <code>popUpStatusItemMenu:</code> to display the menu in the right place under the menu bar. To deal with highlighting, we have an instance variable <code>isMenuVisible</code> that will be set to <code>YES</code> whenever the menu is being displayed, and <code>NO</code> when it is not. We can use the <a href="http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Classes/nsmenu_Class/Reference/Reference.html">NSMenu</a> delegate <code>menuWillOpen:</code> and <code>menuDidClose</code> methods to be notified when the menu is shown or closed.</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">mouseDown:</span><span class="p">(</span><span class="bp">NSEvent</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">event</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[[</span><span class="nb">self</span><span class="w"> </span><span class="n">menu</span><span class="p">]</span><span class="w"> </span><span class="n">setDelegate</span><span class="o">:</span><span class="nb">self</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">statusItem</span><span class="w"> </span><span class="n">popUpStatusItemMenu</span><span class="o">:</span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">menu</span><span class="p">]];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">setNeedsDisplay</span><span class="o">:</span><span class="nb">YES</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">rightMouseDown:</span><span class="p">(</span><span class="bp">NSEvent</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">event</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Treat right-click just like left-click</span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">mouseDown</span><span class="o">:</span><span class="n">event</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">menuWillOpen:</span><span class="p">(</span><span class="n">NSMenu</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">menu</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">isMenuVisible</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">YES</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">setNeedsDisplay</span><span class="o">:</span><span class="nb">YES</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">menuDidClose:</span><span class="p">(</span><span class="n">NSMenu</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">menu</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">isMenuVisible</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">NO</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">menu</span><span class="w"> </span><span class="n">setDelegate</span><span class="o">:</span><span class="nb">nil</span><span class="p">];</span><span class="w"> </span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">setNeedsDisplay</span><span class="o">:</span><span class="nb">YES</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>The <code>title</code> property affects the display, so we can't just synthesize it. When the <code>title</code> is set, we need to determine the bounding rectangle of the text, and then set the status item's <code>length</code> so that it can contain the text. Setting the status item's length also updates the custom view's bounds.</p>
<p>After some experimentation and zooming, I figured out that when a normal <code>NSStatusItem</code> draws its title, there is a horizontal margin of six pixels between the side of the item's display and the text, and a margin of three pixels between the bottom of the display and the text. We need those values to calculate status item size:</p>
<div class="highlight"><pre><span></span><code>#define StatusItemViewPaddingWidth 6
#define StatusItemViewPaddingHeight 3
</code></pre></div>
<p>To determine the text's bounding rectangle, we can call <code>NSString</code>'s <code>boundingRectWithSize:options:attributes:</code> method, but to do that, we need to construct an <em>attributes</em> dictionary that describes the font and other aspects of drawing. We'll use the same attributes dictionary in our <code>drawRect:</code> method, so when we construct it we'll include the desired foreground drawing color in the attributes.</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="n">NSColor</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nf">titleForegroundColor</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">isMenuVisible</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">[</span><span class="n">NSColor</span><span class="w"> </span><span class="n">whiteColor</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">[</span><span class="n">NSColor</span><span class="w"> </span><span class="n">blackColor</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span>
<span class="p">}</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="bp">NSDictionary</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nf">titleAttributes</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Use default menu bar font size</span>
<span class="w"> </span><span class="n">NSFont</span><span class="w"> </span><span class="o">*</span><span class="n">font</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">NSFont</span><span class="w"> </span><span class="n">menuBarFontOfSize</span><span class="o">:</span><span class="mi">0</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">NSColor</span><span class="w"> </span><span class="o">*</span><span class="n">foregroundColor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">titleForegroundColor</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">[</span><span class="bp">NSDictionary</span><span class="w"> </span><span class="n">dictionaryWithObjectsAndKeys</span><span class="o">:</span><span class="w"></span>
<span class="w"> </span><span class="n">font</span><span class="p">,</span><span class="w"> </span><span class="n">NSFontAttributeName</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">foregroundColor</span><span class="p">,</span><span class="w"> </span><span class="n">NSForegroundColorAttributeName</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nb">nil</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="n">NSRect</span><span class="p">)</span><span class="nf">titleBoundingRect</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">[</span><span class="n">title</span><span class="w"> </span><span class="n">boundingRectWithSize</span><span class="o">:</span><span class="n">NSMakeSize</span><span class="p">(</span><span class="mf">1e100</span><span class="p">,</span><span class="w"> </span><span class="mf">1e100</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nl">options</span><span class="p">:</span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="nl">attributes</span><span class="p">:[</span><span class="nb">self</span><span class="w"> </span><span class="n">titleAttributes</span><span class="p">]];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>With the <code>titleBoundingRect</code> method defined, we can implement the <code>title</code> property:</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">setTitle:</span><span class="p">(</span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nv">newTitle</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">[</span><span class="n">title</span><span class="w"> </span><span class="n">isEqual</span><span class="o">:</span><span class="n">newTitle</span><span class="p">])</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">newTitle</span><span class="w"> </span><span class="k">retain</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">title</span><span class="w"> </span><span class="k">release</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="n">title</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">newTitle</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Update status item size (which will also update this view's bounds)</span>
<span class="w"> </span><span class="n">NSRect</span><span class="w"> </span><span class="n">titleBounds</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">titleBoundingRect</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">newWidth</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">titleBounds</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="mi">2</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">StatusItemViewPaddingWidth</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">statusItem</span><span class="w"> </span><span class="n">setLength</span><span class="o">:</span><span class="n">newWidth</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">setNeedsDisplay</span><span class="o">:</span><span class="nb">YES</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">-</span> <span class="p">(</span><span class="bp">NSString</span><span class="w"> </span><span class="o">*</span><span class="p">)</span><span class="nf">title</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">title</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>And finally, we can implement <code>drawRect:</code>. <code>NSStatusItem</code> provides a method <code>drawStatusBarBackgroundInRect:withHighlight:</code> that will draw the appropriate background for a status item, so we just call that and then draw the title at the correct position.</p>
<div class="highlight"><pre><span></span><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">drawRect:</span><span class="p">(</span><span class="n">NSRect</span><span class="p">)</span><span class="nv">rect</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Draw status bar background, highlighted if menu is showing</span>
<span class="w"> </span><span class="p">[</span><span class="n">statusItem</span><span class="w"> </span><span class="n">drawStatusBarBackgroundInRect</span><span class="o">:</span><span class="p">[</span><span class="nb">self</span><span class="w"> </span><span class="n">bounds</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="nl">withHighlight</span><span class="p">:</span><span class="n">isMenuVisible</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Draw title string</span>
<span class="w"> </span><span class="n">NSPoint</span><span class="w"> </span><span class="n">origin</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">NSMakePoint</span><span class="p">(</span><span class="n">StatusItemViewPaddingWidth</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">StatusItemViewPaddingHeight</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="n">title</span><span class="w"> </span><span class="n">drawAtPoint</span><span class="o">:</span><span class="n">origin</span><span class="w"></span>
<span class="w"> </span><span class="nl">withAttributes</span><span class="p">:[</span><span class="nb">self</span><span class="w"> </span><span class="n">titleAttributes</span><span class="p">]];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>That's it for the view class. Here is the code in my application controller that sets it all up:</p>
<div class="highlight"><pre><span></span><code><span class="n">statusItem</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="n">NSStatusBar</span><span class="w"> </span><span class="n">systemStatusBar</span><span class="p">]</span><span class="w"> </span><span class="n">statusItemWithLength</span><span class="o">:</span><span class="n">NSVariableStatusItemLength</span><span class="p">];</span><span class="w"></span>
<span class="p">[</span><span class="n">statusItem</span><span class="w"> </span><span class="k">retain</span><span class="p">];</span><span class="w"></span>
<span class="n">statusItemView</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="n">StatusItemView</span><span class="w"> </span><span class="n">alloc</span><span class="p">]</span><span class="w"> </span><span class="n">init</span><span class="p">];</span><span class="w"></span>
<span class="p">[</span><span class="n">statusItemView</span><span class="w"> </span><span class="k">retain</span><span class="p">];</span><span class="w"></span>
<span class="n">statusItemView</span><span class="p">.</span><span class="n">statusItem</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">statusItem</span><span class="p">;</span><span class="w"></span>
<span class="p">[</span><span class="n">statusItemView</span><span class="w"> </span><span class="n">setMenu</span><span class="o">:</span><span class="n">menu</span><span class="p">];</span><span class="w"></span>
<span class="p">[</span><span class="n">statusItemView</span><span class="w"> </span><span class="n">setToolTip</span><span class="o">:</span><span class="n">NSLocalizedString</span><span class="p">(</span><span class="s">@"Menubar Countdown"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s">@"Status Item Tooltip"</span><span class="p">)];</span><span class="w"></span>
<span class="p">[</span><span class="n">statusItem</span><span class="w"> </span><span class="n">setView</span><span class="o">:</span><span class="n">statusItemView</span><span class="p">];</span><span class="w"></span>
<span class="p">[</span><span class="n">statusItemView</span><span class="w"> </span><span class="n">setTitle</span><span class="o">:</span><span class="s">@"00:00:00"</span><span class="p">];</span><span class="w"></span>
</code></pre></div>
<p>(There is a minor gotcha here: You have to call the status item's <code>setView:</code> before calling the view's <code>setTitle:</code>, because the view needs to call the status item's <code>setLength:</code> method. I could have fixed that, but decided it wasn't worth the effort.)</p>
<p>Here, we are creating the view programmatically. I tried creating it in Interface Builder and setting all the connections that way, but the result was a view with funky <code>bounds</code> and <code>frame</code> properties that didn't display itself the right way. Google found some other people with the same problem, and while some had found solutions, I didn't understand them. (Maybe someone can explain it to me.)</p>
<p>And so, after a couple of hours of work, I had a custom status item view that makes the status item look just like it did before I had the custom view. Progress!</p>
<p>Of course, I didn't leave the view that way. I called <code>setWantsLayer:</code> on the view and started doing Core Animation stuff. Maybe I'll write about that next time.</p>Anti-pattern: The Overly Generic Interface2009-07-04T14:43:25-04:002009-07-04T14:43:25-04:00Kristopher Johnsontag:undefinedvalue.com,2009-07-04:/anti-pattern-overly-generic-interface.html<p>While learning about <a href="http://en.wikipedia.org/wiki/Core_Animation">Core Animation</a>, I was disappointed to find that it is plagued by the anti-pattern that I call the <em>Overly Generic Interface</em>.</p>
<!--break-->
<p>The Overly Generic Interface is an interface that provides a very small set of functions, but each function takes parameters that allow one to do a …</p><p>While learning about <a href="http://en.wikipedia.org/wiki/Core_Animation">Core Animation</a>, I was disappointed to find that it is plagued by the anti-pattern that I call the <em>Overly Generic Interface</em>.</p>
<!--break-->
<p>The Overly Generic Interface is an interface that provides a very small set of functions, but each function takes parameters that allow one to do a lot of different things. For example, in C++ an overly generic interface would look like this:</p>
<div class="highlight"><pre><span></span><code><span class="o">//</span><span class="w"> </span><span class="n">A</span><span class="w"> </span><span class="n">Value</span><span class="w"> </span><span class="n">object</span><span class="w"> </span><span class="n">can</span><span class="w"> </span><span class="n">hold</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">string</span><span class="p">,</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="n">integer</span><span class="p">,</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="nb nb-Type">float</span><span class="p">,</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="n">object</span><span class="w"> </span><span class="n">pointer</span><span class="p">,</span><span class="w"> </span><span class="n">etc</span><span class="o">.</span><span class="w"></span>
<span class="k">class</span><span class="w"> </span><span class="n">Value</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="o">...</span><span class="w"></span>
<span class="p">};</span><span class="w"></span>
<span class="k">class</span><span class="w"> </span><span class="nb nb-Type">Object</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="n">public</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Return</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">named</span><span class="w"> </span><span class="n">property</span><span class="w"></span>
<span class="w"> </span><span class="n">Value</span><span class="w"> </span><span class="n">getProperty</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">string</span><span class="o">&</span><span class="w"> </span><span class="n">propertyName</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Set</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">named</span><span class="w"> </span><span class="n">property</span><span class="w"></span>
<span class="w"> </span><span class="nb nb-Type">void</span><span class="w"> </span><span class="n">setProperty</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">string</span><span class="o">&</span><span class="w"> </span><span class="n">propertyName</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="n">Value</span><span class="o">&</span><span class="w"> </span><span class="n">newValue</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Set</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">values</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">multiple</span><span class="w"> </span><span class="n">named</span><span class="w"> </span><span class="n">properties</span><span class="w"> </span><span class="n">with</span><span class="w"> </span><span class="n">one</span><span class="w"> </span><span class="n">call</span><span class="w"></span>
<span class="w"> </span><span class="nb nb-Type">void</span><span class="w"> </span><span class="n">setProperties</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">map</span><span class="o"><</span><span class="n">string</span><span class="p">,</span><span class="w"> </span><span class="n">Value</span><span class="o">></span><span class="w"> </span><span class="n">properties</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Invoke</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">named</span><span class="w"> </span><span class="n">operation</span><span class="w"> </span><span class="n">with</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">parameter</span><span class="w"> </span><span class="n">list</span><span class="w"></span>
<span class="w"> </span><span class="nb nb-Type">void</span><span class="w"> </span><span class="n">performOperation</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">string</span><span class="o">&</span><span class="w"> </span><span class="n">operationName</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="n">list</span><span class="o"><</span><span class="n">Value</span><span class="o">>&</span><span class="w"> </span><span class="n">parameters</span><span class="p">);</span><span class="w"></span>
<span class="p">};</span><span class="w"></span>
</code></pre></div>
<p>And application code that uses the interface looks something like this:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="n">Person</span><span class="p">:</span><span class="w"> </span><span class="n">public</span><span class="w"> </span><span class="nb nb-Type">Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="k">enum</span><span class="w"> </span><span class="n">Gender</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Female</span><span class="p">,</span><span class="w"> </span><span class="n">Male</span><span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="o">//</span><span class="w"> </span><span class="n">Initialize</span><span class="w"> </span><span class="n">husband</span><span class="p">,</span><span class="w"> </span><span class="n">using</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">set</span><span class="o">-</span><span class="n">one</span><span class="o">-</span><span class="n">property</span><span class="o">-</span><span class="n">at</span><span class="o">-</span><span class="n">a</span><span class="o">-</span><span class="n">time</span><span class="w"> </span><span class="n">interface</span><span class="w"></span>
<span class="n">Person</span><span class="w"> </span><span class="n">husband</span><span class="p">;</span><span class="w"></span>
<span class="n">husband</span><span class="o">.</span><span class="n">setProperty</span><span class="p">(</span><span class="s2">"firstName"</span><span class="p">,</span><span class="w"> </span><span class="n">ValueFromString</span><span class="p">(</span><span class="s2">"Kristopher"</span><span class="p">));</span><span class="w"></span>
<span class="n">husband</span><span class="o">.</span><span class="n">setProperty</span><span class="p">(</span><span class="s2">"lastName"</span><span class="p">,</span><span class="w"> </span><span class="n">ValueFromString</span><span class="p">(</span><span class="s2">"Johnson"</span><span class="p">));</span><span class="w"></span>
<span class="n">husband</span><span class="o">.</span><span class="n">setProperty</span><span class="p">(</span><span class="s2">"gender"</span><span class="p">,</span><span class="w"> </span><span class="n">ValueFromInt</span><span class="p">(</span><span class="n">Male</span><span class="p">));</span><span class="w"></span>
<span class="o">//</span><span class="w"> </span><span class="n">Initialize</span><span class="w"> </span><span class="n">wife</span><span class="p">,</span><span class="w"> </span><span class="n">using</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">set</span><span class="o">-</span><span class="n">all</span><span class="o">-</span><span class="n">properties</span><span class="o">-</span><span class="ow">in</span><span class="o">-</span><span class="n">one</span><span class="o">-</span><span class="n">operation</span><span class="w"> </span><span class="n">interface</span><span class="w"></span>
<span class="n">Person</span><span class="w"> </span><span class="n">wife</span><span class="p">;</span><span class="w"></span>
<span class="n">map</span><span class="o"><</span><span class="n">string</span><span class="p">,</span><span class="w"> </span><span class="n">Value</span><span class="o">></span><span class="w"> </span><span class="n">wifeProperties</span><span class="w"></span>
<span class="n">wifeProperties</span><span class="p">[</span><span class="s2">"firstName"</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ValueFromString</span><span class="p">(</span><span class="s2">"Pebble"</span><span class="p">);</span><span class="w"></span>
<span class="n">wifeProperties</span><span class="p">[</span><span class="s2">"lastName"</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ValueFromString</span><span class="p">(</span><span class="s2">"Johnson"</span><span class="p">);</span><span class="w"></span>
<span class="n">wifeProperties</span><span class="p">[</span><span class="s2">"Gender"</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ValueFromInt</span><span class="p">(</span><span class="n">Female</span><span class="p">);</span><span class="w"></span>
<span class="n">wife</span><span class="o">.</span><span class="n">setProperties</span><span class="p">(</span><span class="n">wifeProperties</span><span class="p">);</span><span class="w"></span>
<span class="o">//</span><span class="w"> </span><span class="n">Invoke</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">recordMarriage</span><span class="w"> </span><span class="n">operation</span><span class="w"></span>
<span class="n">list</span><span class="w"> </span><span class="n">params</span><span class="p">;</span><span class="w"></span>
<span class="n">params</span><span class="o">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">ValueFromPointer</span><span class="p">(</span><span class="o">&</span><span class="n">husband</span><span class="p">));</span><span class="w"></span>
<span class="n">params</span><span class="o">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">ValueFromPointer</span><span class="p">(</span><span class="o">&</span><span class="n">wife</span><span class="p">));</span><span class="w"></span>
<span class="n">recorder</span><span class="o">.</span><span class="n">performOperation</span><span class="p">(</span><span class="s2">"recordMarriage"</span><span class="p">,</span><span class="w"> </span><span class="n">params</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>The supposed benefits (as far as I can tell) of a very generic interface are these:</p>
<ul>
<li>Changes to the interface are not needed as new properties and operations are added.</li>
<li>If the set of properties and operations is huge, you don't need a huge class declaration.</li>
<li>The set of available properties and operations can be different for different instances of a class.</li>
<li>Property names and operation names can be "magic" things with powerful behavior. (For example, maybe the above interface could let you specify something like <code>wifesName = husband.getProperty("findWife#name")</code>.)</li>
<li>It makes it easy to create tools that bind things to other things, via names and string values.</li>
<li>There can be performance benefits for the “set multiple properties in a single call” operation.</li>
<li>There can be performance benefits if it maps directly to a lower-level interface (a device driver, for example).</li>
</ul>
<p>However, I hate such interfaces, for these reasons:</p>
<ul>
<li>The code is ugly and hard to read.</li>
<li>The compiler can't check whether you’ve correctly spelled all the property names or whether you have passed the right type of data for each one. (For example, did you notice that I capitalized the name of the <code>gender</code> property inconsistently in the above example? If not, how would you have found that bug?)</li>
<li>The debugger can't easily show you the values of properties in an object, because they are not exposed as simple instance variables.</li>
<li>Somewhere external to code, you need to maintain documentation about what all the valid names and values are. And you need to constantly refer to that external documentation, because your IDE won't be able to help you with auto-completion and other time-saving features.</li>
<li>Magic is not always helpful. The magic names often have a syntax that is different from that of the “host language,” so programmers essentially have another language to learn.</li>
</ul>
<p>As an illustration of the first objection, I think the following would be a lot more readable and easier to maintain than the above:</p>
<div class="highlight"><pre><span></span><code>Person husband;
husband.setFirstName("Kristopher");
husband.setLastName("Johnson");
husband.setGender(Male);
Person wife;
wife.setFirstName("Pebble");
wife.setLastName("Johnson");
wife.setGender(Female);
recorder.recordMarriage(husband, wife);
</code></pre></div>
<p>Note that the <code>Person</code> class here could be derived from <code>Object</code>, like this:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="n">Person</span><span class="p">:</span><span class="w"> </span><span class="n">public</span><span class="w"> </span><span class="nb nb-Type">Object</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="n">public</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="n">string</span><span class="w"> </span><span class="n">getFirstName</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="nb nb-Type">void</span><span class="w"> </span><span class="n">setFirstName</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">string</span><span class="o">&</span><span class="w"> </span><span class="n">newValue</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">string</span><span class="w"> </span><span class="n">getLastName</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="nb nb-Type">void</span><span class="w"> </span><span class="n">setLastName</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">string</span><span class="o">&</span><span class="w"> </span><span class="n">newValue</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">so</span><span class="w"> </span><span class="n">on</span><span class="p">,</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">all</span><span class="w"> </span><span class="n">properties</span><span class="w"></span>
<span class="p">};</span><span class="w"></span>
</code></pre></div>
<p>and in this case you would have both the generic <code>Object</code> interface and the specific <code>Person</code> interfaces available. I have no objection to that.</p>
<p>A generic interface is often useful (e.g., <a href="http://developer.apple.com/documentation/Cocoa/Reference/CocoaBindingsRef/CocoaBindingsRef.html">Cocoa Bindings</a>). My objection is to those API’s that <em>only</em> provide the generic interface. In Core Animation, practically everything is done by creating <code>NSDictionary</code> objects, populating them with obscure keys and objects, and then passing them to the API. There are a few higher-level interfaces available, but they don't provide access to all of Core Animation's functionality, so using the low-level overly generic interface is a necessity.</p>
<p>Names are important, and therefore names should be explicitly declared. Otherwise, they are hidden from the compiler, from the IDE, from the debugger, and from programmers who read class declarations. Hidden names and magic names cause problems.</p>
<p>When confronted with an Overly Generic Interface, I almost always end up writing some sort of wrapper around it. It would be nice if I didn't have to do so.</p>Review: Core Animation for Mac OS X and the iPhone, by Bill Dudney2009-07-04T11:54:17-04:002009-07-04T11:54:17-04:00Kristopher Johnsontag:undefinedvalue.com,2009-07-04:/review-core-animation-mac-os x-and-iphone-bill dudney.html<p>Either I’m stupid, or Apple’s developer documentation sucks. Whenever I try to enter a new area of Cocoa development, I am presented with simplistic tutorials and detailed reference information, with little in between to bridge the gap between newbie and expert. Often, the only way to learn an …</p><p>Either I’m stupid, or Apple’s developer documentation sucks. Whenever I try to enter a new area of Cocoa development, I am presented with simplistic tutorials and detailed reference information, with little in between to bridge the gap between newbie and expert. Often, the only way to learn an API is to hack on the example programs until enlightenment occurs, or do a lot of Googling.</p>
<p>This is exactly what happened with <a href="http://en.wikipedia.org/wiki/Core_Animation">Core Animation</a>. Most OS X developers don't need to know much about Core Animation, as it is a relatively new feature of OS X, and Mac users don't expect fancy animations in every application's UI. However, on iPhone, users do expect things to bounce and zip around the screen, so an iPhone developer really needs to know this stuff.</p>
<p>I started reading Apple’s <em>Core Animation Programming Guide</em> several times, and each time, I got lost in Chapter 2. Skipping forward to subsequent chapters didn't get me anywhere. I was presented with architectural diagrams and abstract discussions of geometry and transforms and layers and timing and so forth, but there was nothing concrete that I could grasp.</p>
<p>Bill Dudney’s <a href="http://www.pragprog.com/titles/bdcora/core-animation-for-mac-os-x-and-the-iphone"><em>Core Animation for Mac OS X and the iPhone</em></a> filled in the gaps perfectly. It doesn't contain much information that is not in Apple's docs, but it presents it in a way that makes sense to me. Dudney leads you by the hand through actual working code that does cool stuff. After reading this book, I now understand what Apple was trying to tell me in the <em>Core Animation Programming Guide</em>.</p>
<p>I read the PDF-formatted version. I also tried reading the epub-formatted version using Stanza on the iPhone, but that didn't work so well: the diagrams and code examples aren't readable. So I recommend sticking to the PDF or paper formats.</p>
<p>My only gripe is that I'd like to see more iPhone-specific information. The majority of the book covers Core Animation on OS X, and then there is a single chapter at the end about the iPhone. I would prefer to have an iPhone-centric book with an oh-by-the-way-this-works-on-OS-X-too chapter. But that's a minor quibble; I think I understand the material well enough now that I can learn the iPhone-specific aspects of Core Animation on my own.</p>iPhone Temperature Warning2009-06-29T16:59:24-04:002009-06-29T16:59:24-04:00Kristopher Johnsontag:undefinedvalue.com,2009-06-29:/iphone-temperature-warning.html<p>In case you're curious: here's what you'll see if you rescue your iPhone from a hot car:</p>
<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/temperaturewarning.png" alt="iPhone Temperature Warning Image" /></p>
<p><em>Appel d'urgence</em> indeed!</p>How Not to Refactor a Function That Has Too Many Parameters2009-06-28T16:13:31-04:002009-06-28T16:13:31-04:00Kristopher Johnsontag:undefinedvalue.com,2009-06-28:/how-not-refactor-function-has-too-many-parameters.html<p>Most programmers know that having functions with too many parameters can be confusing. However, fixing such problems requires some intelligence. A programmer once saw some code like this:</p>
<div class="highlight"><pre><span></span><code>SetObjectParams(obj, foo, bar, baz, quux, xyzzy, abra, cadabra, hocus, pocus, presto, shazam);
</code></pre></div>
<p>Finding a stylistic rule somewhere that said a function …</p><p>Most programmers know that having functions with too many parameters can be confusing. However, fixing such problems requires some intelligence. A programmer once saw some code like this:</p>
<div class="highlight"><pre><span></span><code>SetObjectParams(obj, foo, bar, baz, quux, xyzzy, abra, cadabra, hocus, pocus, presto, shazam);
</code></pre></div>
<p>Finding a stylistic rule somewhere that said a function should have no more than five parameters, the programmer “refactored” it to this:</p>
<div class="highlight"><pre><span></span><code>SetObjectParams1(obj, foo, bar, baz, quux);
SetObjectParams2(obj, xyzzy, abra, cadabra, hocus);
SetObjectParams3(obj, pocus, presto, shazam);
</code></pre></div>
<p>No, that's not how one resolves this problem. You fix this problem by defining functions that each do something simple, give each function a name that describes what it does, and let them take however many parameters make sense.</p>Menubar Countdown 1.2 Released2009-06-23T01:32:26-04:002009-06-23T01:32:26-04:00Kristopher Johnsontag:undefinedvalue.com,2009-06-23:/menubar-countdown-12-released.html<p><a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a> is a simple countdown timer that displays itself on the right side of the Mac OS X menu bar.</p>
<p>Version 1.2 has these new features:</p>
<ul>
<li>Supports <a href="http://growl.info/">Growl</a> notifications. The Announcement text specified in the Start dialog will be displayed in the Growl notification window.</li>
<li>New application icon …</li></ul><p><a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a> is a simple countdown timer that displays itself on the right side of the Mac OS X menu bar.</p>
<p>Version 1.2 has these new features:</p>
<ul>
<li>Supports <a href="http://growl.info/">Growl</a> notifications. The Announcement text specified in the Start dialog will be displayed in the Growl notification window.</li>
<li>New application icon</li>
<li>Adds option to hide seconds in the menu bar</li>
<li>Shows start-timer dialog when application launches (behavior can be disabled in Settings dialog)</li>
<li>Command-X, Command-C, Command-V, and Command-A now work in the text fields in the settings dialog</li>
<li>Command-R is now a shortcut key for the Restart Countdown... button in the alert window</li>
</ul>
<p>To download the application or get more information, see the <a href="http://capablehands.net/menubarcountdown">Menubar Countdown product page</a>.</p>New Menubar Countdown Icon2009-06-15T13:02:29-04:002009-06-15T13:02:29-04:00Kristopher Johnsontag:undefinedvalue.com,2009-06-15:/new-menubar-countdown-icon.html<p>When I released <a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a> 1.1, I whipped up an application icon by simply taking a screenshot of the menu bar and cropping it. The application icon looked like this:</p>
<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/MenuTimerIcon256x256.png" alt="Old Menubar Countdown Icon" /></p>
<p>Fugly, eh? Not only is it ugly, but the narrow shape makes it hard to recognize as an icon …</p><p>When I released <a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a> 1.1, I whipped up an application icon by simply taking a screenshot of the menu bar and cropping it. The application icon looked like this:</p>
<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/MenuTimerIcon256x256.png" alt="Old Menubar Countdown Icon" /></p>
<p>Fugly, eh? Not only is it ugly, but the narrow shape makes it hard to recognize as an icon, and makes it hard to click.</p>
<p>For version 1.2, I wanted a better icon. I thought an hourglass would be a good symbol, so after a few hours of looking at images on the web and dinking around in Photoshop Elements, I wound up with this:</p>
<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/HourglassIcon256x256.png" alt="Proposed Menubar Countdown Icon" /></p>
<p>I'm was very happy with it. Unfortunately, the hourglass image is based upon a photo I licensed from iStockphoto. iStockphoto considers an application icon to be the same as a logo or trademark, and such usage is expressly prohibited by their licenses. I'm not a lawyer, but I assume this is to prevent someone from putting a stock image into a logo and then suing all the other users of that image for trademark infringement.</p>
<p>So, I again went to the web, to look for public-domain images I could use. I eventually wound up with this:</p>
<p><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/NewHourglassIcon256x256.png" alt="New Menubar Countdown Icon" /></p>
<p>I don't like it as much as the last one, but I've decided I've spent too much time on my application icon, and not enough time on making the application better, so this will be the icon for version 1.2. Maybe I'll revisit it for the next version.</p>
<p>I remember the good old days when a Macintosh application icon was a 32x32 black-and-white bitmap. Those were pretty easy for a non-artist like me to create, using simple tools like <a href="http://en.wikipedia.org/wiki/Resedit">ResEdit</a>. Now, this stuff is better left in the hands of professional designers, but Menubar Countdown is free, so I trust users will be satisfied with my amateurish effort.</p>
<p>If there are any real designers out there who would like to give me a better icon, I'd definitely appreciate it.</p>ESATInformer = FAIL2009-06-11T16:11:54-04:002009-06-11T16:11:54-04:00Kristopher Johnsontag:undefinedvalue.com,2009-06-11:/esatinformer-fail.html<p>One of my clients, whose e-mail system I have to use, uses a product called ESATInformer to filter spam. I don't understand exactly what the benefit of this system is.</p>
<p>When the system receives suspected spam, it keeps that spam message on the server, but it sends me an e-mail …</p><p>One of my clients, whose e-mail system I have to use, uses a product called ESATInformer to filter spam. I don't understand exactly what the benefit of this system is.</p>
<p>When the system receives suspected spam, it keeps that spam message on the server, but it sends me an e-mail saying "We've received a message that might be spam. What do you want to do with it?" So my inbox still gets filled with unwanted e-mail about suspicious e-mail.</p>
<p>If I want to read the suspicious messages (and I usually do), then I have to send an e-mail request back to the system, which will then send them to me. It would be a whole lot easier if they would just let the suspicious messages through in the first place.</p>
<p>If your spam-filtering system is more annoying than spam is, then you've really screwed up.</p>How to Learn Programming, in Two Easy Steps2009-06-10T12:03:05-04:002009-06-10T12:03:05-04:00Kristopher Johnsontag:undefinedvalue.com,2009-06-10:/how-learn-programming-two-easy-steps.html<p>I am sometimes asked by non-programmers how they can get into programming. They want to know what programming language they should learn, which programming tools to use, and so on. These are hard questions to answer. It's like asking "I'd like to be a musician. Which instruments and which pieces …</p><p>I am sometimes asked by non-programmers how they can get into programming. They want to know what programming language they should learn, which programming tools to use, and so on. These are hard questions to answer. It's like asking "I'd like to be a musician. Which instruments and which pieces of music should I learn?"</p>
<p>Here is my advice:</p>
<ol>
<li>Think about an application you'd like to see developed.</li>
<li>Learn what you need to know to develop that app, and develop it.</li>
</ol>
<p><em>Programming</em> is a big field. Choosing a concrete goal narrows it to something manageable. Once you know exactly what you want to make, it is a lot easier to decide upon a programming language and tools.</p>
<p>Professional programmers don't "learn how to program" and then just apply that knowledge for the rest of their careers. Every day, we learn new things: programming languages, tools, libraries, operating systems, and so on. We pick it up as we go, learning what we need to know as it's needed. If you want to be a programmer, you need to learn how to do the same.</p>OmniFocus Review2009-05-30T14:21:46-04:002009-05-30T14:21:46-04:00Kristopher Johnsontag:undefinedvalue.com,2009-05-30:/omnifocus-review.html<p>I've written before about my <a href="http://kristopherjohnson.blogspot.com/2008/12/ultimate-to-do-list-application-for.html">Ultimate To-Do List Application for iPhone</a>. I was pretty happy with what I wound up with, but it still wasn't helping me much. Writing down your desired actions is only one part of <a href="http://www.davidco.com">Getting Things Done</a> (GTD); you also need to organize and process things …</p><p>I've written before about my <a href="http://kristopherjohnson.blogspot.com/2008/12/ultimate-to-do-list-application-for.html">Ultimate To-Do List Application for iPhone</a>. I was pretty happy with what I wound up with, but it still wasn't helping me much. Writing down your desired actions is only one part of <a href="http://www.davidco.com">Getting Things Done</a> (GTD); you also need to organize and process things, and a simple list of lists wasn't helping me do that.</p>
<p>So, I've decided to give <a href="http://www.omnigroup.com/applications/omnifocus/">OmniFocus</a> a try. So far, I'm very impressed. It truly is the ultimate to-do list application for iPhone.</p>
<p>I won't go over the basics of what it does or how to use it. Watch these videos if you want a taste of its functionality:</p>
<ul>
<li><a href="http://www.screencastsonline.com/index_files/SCO0178-omnifocusbasics.php">OmniFocus Basics</a></li>
<li><a href="http://www.screencastsonline.com/index_files/SCO0180-omnifocuspt2.php">Advanced OmniFocus and iPhone Client</a></li>
</ul>
<p>What makes OmniFocus better than a simple to-do list is that it organizes next actions in two dimensions: by project and by context. A project is some list of actions. For example, if I want to take my wife out for dinner and music this weekend, I might create this project:</p>
<ul>
<li>Take Pebble out</li>
<li>Find out what bands are playing this weekend</li>
<li>Check with Pebble to see which band is her favorite</li>
<li>Buy tickets</li>
<li>Make dinner reservations</li>
<li>Get directions to restaurant</li>
<li>Get directions to venue</li>
<li>Call babysitter</li>
</ul>
<p>That's a lot to do, and this is only one of dozens of projects that are active at any time. But what makes all this stuff manageable is assigning the actions to contexts:</p>
<ul>
<li>Online</li>
<li>Find out what bands are playing this weekend</li>
<li>Buy tickets</li>
<li>Get directions to restaurant</li>
<li>Get directions to venue</li>
<li>Phone</li>
<li>Check with Pebble to see which band is her favorite</li>
<li>Make dinner reservations</li>
<li>Call babysitter</li>
</ul>
<p>So, the next time I'm at my computer and bring up OmniFocus, I'll see "Find out what bands are playing this weekend" as the next available action for this project, and I'll (I hope) take care of it. Then the next time I open OmniFocus on my phone, I'll see "Check with Pebble..." and take care of that.</p>
<p>You plan using projects, but you execute using contexts. Contexts are the key to actually getting things done, rather than just leaving those actions languishing in dozens of lists you never look at.</p>
<p>The killer feature of OmniFocus is the synchronization between the desktop application and the iPhone application. I like that I can do all my planning at my desktop, then when I get in my car, I can quickly bring up the Errands list on my iPhone to see what I can pick up on the way to wherever I'm going. Or while in a meeting, I can quickly enter items into the phone and then process them later at my desk.</p>
<p>The downside of synchronization is that it is sometimes slow. My iPhone sometimes takes several minutes to complete a synchronization through MobileMe. You can use the app while it is synching, so the synchronization delay is more of a minor annoyance than a major source of frustration.</p>
<p>OmniFocus is designed to support the GTD system, and while it is adaptable to other productivity systems, adherents of other systems may not like it. For example, it does not give a way to prioritize tasks in the way that Franklin-Covey devotees would like.</p>
<p>It is important to understand what OmniFocus does not do. It is not a calendar or scheduling application (use iCal or Entourage for that). It is a personal organization system; it is not suitable for managing teams. It is not a project-tracking system. It is not a communication tool. It is just a tool for keeping track of all the things you want to do, and helping you do them with minimal fuss.</p>
<p>OmniFocus is powerful, but also pretty complicated. You definitely need to watch the tutorial videos and read the <em>Getting Things Done</em> book to figure out what the hell this thing does. The effort pays off.</p>
<p>OmniFocus is a little pricey ($100 to get it for both desktop and iPhone), but I'm very happy with it.</p>
<p>(Note: This review is based upon version 1.6.1 of the OmniFocus desktop app, and version 1.2.3 of the iPhone app.)</p>The Skeptics' Guide to the Universe2009-05-26T12:44:57-04:002009-05-26T12:44:57-04:00Kristopher Johnsontag:undefinedvalue.com,2009-05-26:/skeptics-guide-universe.html<p><a href='http://www.theskepticsguide.org' target='_blank'><img src='http://www.theskepticsguide.org/images/banner/sgu_200x200_cruise.jpg'></a></p>Twitter Is About Content, Not Contact2009-05-20T12:37:23-04:002009-05-20T12:37:23-04:00Kristopher Johnsontag:undefinedvalue.com,2009-05-20:/twitter-about-content-not-contact.html<div style="float: right; margin-left: 10px; margin-bottom: 10px;"><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/iStock_000001485226XSmall.jpg" alt="Toy Bird"></div>
<p><a href="http://twitter.com">Twitter</a> is getting a lot of attention from the mainstream media. A lot of people are trying it out, but they don't understand what they are supposed to do with it, they don't see any value in it, and they give up quickly.</p>
<p>Many people fall into the trap of …</p><div style="float: right; margin-left: 10px; margin-bottom: 10px;"><img src="https://undefinedvalue.com/sites/undefinedvalue.com/files/iStock_000001485226XSmall.jpg" alt="Toy Bird"></div>
<p><a href="http://twitter.com">Twitter</a> is getting a lot of attention from the mainstream media. A lot of people are trying it out, but they don't understand what they are supposed to do with it, they don't see any value in it, and they give up quickly.</p>
<p>Many people fall into the trap of thinking of Twitter as a "social network," and so they just follow their real-world friends and people they would like to meet. Unless you have exceptionally interesting friends, this is not going to give you a good Twitter experience. Steve Lawson explains it better than I can: <a href="http://www.stevelawson.net/wordpress/2009/03/twitter-sucks-so-change-your-friends/">Twitter sucks, so change your friends</a>.</p>
<p>Don't think of Twitter as being like Facebook, or like an instant messenger. Think of it more like YouTube. It is a place where you go to consume content, 140 characters at a time.</p>
<p>Be very selective about who you follow. Don't just follow your friends or celebrities. Follow good writers, especially those skilled at brevity. Comedians and comedy writers are often great tweeters, as jokes fit well in this format. However, don't count on your favorite comedian being a good twitterer: many use Twitter only to plug their appearances or DVDs.</p>
<p>Note that if you follow too many people, other Twitter users will assume you are a spammer or other not-really-serious user, and will ignore you. Being selective is an important part of finding the right "community" within Twitter.</p>
<p>Many Twitter users complain because nobody is following them. Don't expect people to follow you, unless you create really interesting tweets. Nobody cares what you had for breakfast or how long your flight has been delayed. And don't expect celebrities to respond to your messages: they have so many followers that they can't possibly pay attention to what the followers say to them.</p>
<p>For suggestions about how to recognize and create good tweets, see this Washington Post article: <a href="http://www.washingtonpost.com/wp-dyn/content/article/2009/05/18/AR2009051802870.html">Short(est) Stories: The Art of Twitterature Means Making 140 Characters Count</a>.</p>
<p>If you have a group of friends or co-workers who keep in touch via Twitter, that's great. But for most of us, Twitter is best used as a read-only medium. Leave the writing to the people who have something to say, and otherwise just enjoy it.</p>A Brief, Incomplete, and Mostly Wrong History of Programming Languages2009-05-08T17:13:36-04:002009-05-08T17:13:36-04:00Kristopher Johnsontag:undefinedvalue.com,2009-05-08:/brief-incomplete-and-mostly-wrong-history-programming-languages.html<p>This is great: <a href="http://james-iry.blogspot.com/2009/05/brief-incomplete-and-mostly-wrong.html">A Brief, Incomplete, and Mostly Wrong History of Programming Languages</a></p>Stack Overflow Moderator Voting Now Open2009-05-06T11:26:16-04:002009-05-06T11:26:16-04:00Kristopher Johnsontag:undefinedvalue.com,2009-05-06:/stack-overflow-moderator-voting-now-open.html<p>If you are a <a href="http://stackoverflow.com">Stack Overflow</a> user, you should be aware that <a href="http://blog.stackoverflow.com/2009/05/stack-overflow-moderator-voting-now-open/">voting is open</a> for a new moderator.</p>
<p>I personally support <a href="http://stackoverflow.com/users/3333/paul-tomblin">Paul Tomblin</a>, who is an all-around cool guy, but I don't think any of the candidates would be a wrong choice.</p>The Undefined Value To-Do List2009-05-05T11:30:46-04:002009-05-05T11:30:46-04:00Kristopher Johnsontag:undefinedvalue.com,2009-05-05:/undefined-value-do-list.html<p>I'm happy with how the new Undefined Value site compares with the old Blogger-based site. However, I still have some things to do. I'm a compulsive list-maker, so I'll put my list here.</p>
<ul>
<li>Update Drupal</li>
<li>Add syntax coloring for code examples</li>
<li>Fix the <code><pre></code> style so that scrollbars are present …</li></ul><p>I'm happy with how the new Undefined Value site compares with the old Blogger-based site. However, I still have some things to do. I'm a compulsive list-maker, so I'll put my list here.</p>
<ul>
<li>Update Drupal</li>
<li>Add syntax coloring for code examples</li>
<li>Fix the <code><pre></code> style so that scrollbars are present when needed</li>
<li>Move old blog content to new blog</li>
<li>Move Undefined Value to VPS</li>
</ul>The Bottom-Feeders of Software Distribution2009-05-03T10:29:48-04:002009-05-03T10:29:48-04:00Kristopher Johnsontag:undefinedvalue.com,2009-05-03:/bottom-feeders-software-distribution.html<p>When I released my <a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a> app, I posted it to two places: <a href="http://www.macupdate.com/info.php/id/31091/menubar-countdown">MacUpdate</a> and <a href="http://www.apple.com/downloads/macosx/productivity_tools/menubarcountdown.html">Apple Downloads</a>.</p>
<p>It has been interesting to see the number of other Mac software download sites that now have Menubar Countdown available. A few have done something of value: for example, <a href="http://mac.softpedia.com/get/Utilities/Menubar-Countdown.shtml">Softpedia</a> created their own …</p><p>When I released my <a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a> app, I posted it to two places: <a href="http://www.macupdate.com/info.php/id/31091/menubar-countdown">MacUpdate</a> and <a href="http://www.apple.com/downloads/macosx/productivity_tools/menubarcountdown.html">Apple Downloads</a>.</p>
<p>It has been interesting to see the number of other Mac software download sites that now have Menubar Countdown available. A few have done something of value: for example, <a href="http://mac.softpedia.com/get/Utilities/Menubar-Countdown.shtml">Softpedia</a> created their own <a href="http://mac.softpedia.com/progScreenshots/Menubar-Countdown-Screenshot-54783.html">screenshots</a>.</p>
<p>However, most of the other sites have simply copied-and-pasted the text from MacUpdate or Apple, and put their own advertising and branding around it. This strikes me as being pretty scummy.</p>
<p>Menubar Countdown is freeware, licensed under the GPL, and I have no complaint about them redistributing the app itself. What bothers me is that they copy the descriptions and other text verbatim (which is probably a copyright violation). They don't review the app. They don't promote it. They don't make it easier for users to find it. They do nothing of value; they simply siphon away attention to make a tiny profit for themselves.</p>
<p>Some might say that these other sites are somehow "helping" me by copying my work and pasting it all over the Internet without my consent. I'll refer those people to Merlin Mann's <a href="http://www.43folders.com/2009/04/10/free-me">blog post</a> on that subject.</p>Enabling FastCGI for PHP on Ubuntu2009-05-02T08:44:35-04:002009-05-02T08:44:35-04:00Kristopher Johnsontag:undefinedvalue.com,2009-05-02:/enabling-fastcgi-php-ubuntu.html<p>I'm setting up a <a href="http://en.wikipedia.org/wiki/Virtual_private_server">virtual private server</a>. If all goes well, I'll be moving all my websites from their current shared-hosting arrangements to this VPS.</p>
<p>I started with a minimal Ubuntu 8.10 image and installed all the LAMP stuff. Things went smoothly until I decided to try to enable …</p><p>I'm setting up a <a href="http://en.wikipedia.org/wiki/Virtual_private_server">virtual private server</a>. If all goes well, I'll be moving all my websites from their current shared-hosting arrangements to this VPS.</p>
<p>I started with a minimal Ubuntu 8.10 image and installed all the LAMP stuff. Things went smoothly until I decided to try to enable FastCGI for Drupal. Googling for things like "ubuntu apache php fastcgi" results in zillions of links to suggested methods, all of which were very complicated and required digging through docs. I figured there had to be a simple way to do it.</p>
<p>After a few hours of research, I finally did stumble upon what I wanted. My problem was that I was googling for "ubuntu", when I should have been googling for "debian".</p>
<p>The info I needed was here: http://michiel.vanbaak.info/docsphp5fcgi.htm. Thank you, Michiel van Baak!</p>
<p>(To those of you saying "But you should really be using <a href="http://en.wikipedia.org/wiki/Nginx">nginx</a> instead of Apache," my response is "Yes, I know. Leave me alone.")</p>Server Fault Private Beta Begins2009-04-30T09:26:54-04:002009-04-30T09:26:54-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-30:/server-fault-private-beta-begins.html<p><a href="http://serverfault.com">Server Fault</a> is a new Q&A website for system administrators and IT professionals, brought to us by the folks who brought us <a href="http://stackoverflow.com/">Stack Overflow</a>.</p>
<p>It just started its private beta, which is open to any Stack Overflow user with a reputation score of 100 or higher, or anyone with …</p><p><a href="http://serverfault.com">Server Fault</a> is a new Q&A website for system administrators and IT professionals, brought to us by the folks who brought us <a href="http://stackoverflow.com/">Stack Overflow</a>.</p>
<p>It just started its private beta, which is open to any Stack Overflow user with a reputation score of 100 or higher, or anyone with an OpenID who submits a request. See the <a href="http://blog.stackoverflow.com/2009/04/server-fault-private-beta-begins/">Stack Overflow blog entry</a> for details.</p>
<p>I'm in the process of setting up an Ubuntu web server on a Xen VPS, so I expect to have a lot of questions to post on Server Fault. It arrived at the perfect time.</p>Menubar Countdown Suggestions2009-04-27T16:59:49-04:002009-04-27T16:59:49-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-27:/menubar-countdown-suggestions.html<p>Since releasing <a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a>, my little Mac OS X timer app, I've received some nice feedback and suggestions for improvement. I'm going to list them here, both so that I can find them later, and to let others leave their own suggestions.</p>
<ul>
<li>Repeat the alarm until the user acknowledges it …</li></ul><p>Since releasing <a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a>, my little Mac OS X timer app, I've received some nice feedback and suggestions for improvement. I'm going to list them here, both so that I can find them later, and to let others leave their own suggestions.</p>
<ul>
<li>Repeat the alarm until the user acknowledges it. (Currently, the alarm just goes off once.)</li>
<li>Support Growl notifications</li>
<li>Allow user to choose an alert sound other than the default system alert sound.</li>
<li>Provide option to log out and/or shutdown when timer expires.</li>
<li>Provide option to launch an application or an AppleScript when timer expires.</li>
<li>Add a <em>days</em> field.</li>
<li>Allow user to specify the output format (so that a user could, for example, have it display "4m, 3s" instead of 00:04:03.</li>
<li>One-click start: bypass the Settings dialog</li>
<li>Animated "alarm is going off" display</li>
<li>Allow user to specify an ending date and time, rather than a time interval</li>
<li>Provide option to not display seconds (continuous animation is distracting to some, and removing seconds would save some menubar real estate)</li>
</ul>
<p>I'm not going to implement very many of these, if any. What I like about Menubar Countdown is its simplicity, both in terms of user interface and in terms of implementation. It's only a couple hundred lines of code, and I'd like to keep it that way.</p>Lumpkin 400 Storage Rocks!2009-04-27T16:55:24-04:002009-04-27T16:55:24-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-27:/lumpkin-400-storage-rocks.html<p>I want to give a quick plug to <a href="http://www.lumpkin400storage.com">Lumpkin 400 Storage</a> in Dahlonega, Georgia. (My recommendation probably doesn't carry much weight, but maybe this will help their Google PageRank by some tiny amount.)</p>
<p>I showed up on Saturday afternoon, asking to rent a U-Haul. Todd Cannon, one of the owners …</p><p>I want to give a quick plug to <a href="http://www.lumpkin400storage.com">Lumpkin 400 Storage</a> in Dahlonega, Georgia. (My recommendation probably doesn't carry much weight, but maybe this will help their Google PageRank by some tiny amount.)</p>
<p>I showed up on Saturday afternoon, asking to rent a U-Haul. Todd Cannon, one of the owners, didn't have any trucks available, but asked me what I needed to move. I told him I had a broken riding lawnmower that I needed to take back to Home Depot.</p>
<p>Todd apolgized that he didn't have any trucks available (the end of the month is a busy time), but he'd have his trailer with him on Monday, so I should call him and he'd give me and a mower a ride. <em>No charge.</em> We got the mower moved this morning, and I forced him to take twenty bucks.</p>
<p>I would definitely recommend doing business with Lumpkin 400 Storage. Friendly people, awesome service.</p>Migrating Blog Posts2009-04-26T13:33:29-04:002009-04-26T13:33:29-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-26:/migrating-blog-posts.html<p>I looked around for an easy way to migrate all the content from my old blog to this new one. While some automated solutions for moving from Blogger to Drupal are available, I decided it would be better to do it all by hand. This gives me a chance to …</p><p>I looked around for an easy way to migrate all the content from my old blog to this new one. While some automated solutions for moving from Blogger to Drupal are available, I decided it would be better to do it all by hand. This gives me a chance to pick only the Best of Undefined Value, and to do some editing.</p>
<p>It's not really that hard. Just click the edit button for the old post, select all, copy, click my "Create Story" bookmark, paste.</p>
<p>The only tedious part is setting the date. The old site has dates in MM/DD/YYYY HH:MM:SS AM/PM format, whereas Drupal wants YYYY-MM-DD HH:MM:SS, so I can't just copy and paste.</p>
<p>Luckily, there's not much good stuff on the old site, so I hope to have the <strong>Monthly archive</strong> block on the left filled in a few weeks.</p>Fisher Bullet Space Pen: Almost Perfect2009-04-22T11:06:28-04:002009-04-22T11:06:28-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-22:/fisher-bullet-space-pen-almost-perfect.html<p>When I was a kid, while visiting the Kennedy Space Center my parents bought me a <a href="http://www.spacepen.com/matteblackshuttlespacepen.aspx">Fisher Space Pen</a>. At the time, I thought it was cool because it could write upside down, and because it had been designed for astronauts. I don't know where that first pen is now …</p><p>When I was a kid, while visiting the Kennedy Space Center my parents bought me a <a href="http://www.spacepen.com/matteblackshuttlespacepen.aspx">Fisher Space Pen</a>. At the time, I thought it was cool because it could write upside down, and because it had been designed for astronauts. I don't know where that first pen is now.</p>
<p>Over the past year, I've been eyeing the <a href="http://www.spacepen.com/bullet.aspx">"bullet" Space Pens</a> whenever I go into Office Depot. Spending twenty bucks on a ballpoint pen seemed like an unnecessary extravagance, so I held out for a long time. However, seeing the pen mentioned on <a href="http://www.43folders.com/2004/09/03/introducing-the-hipster-pda">43 Folders</a> pushed me over the edge, and I finally bought one, in chrome.</p>
<p>It's almost perfect. I love the size and shape: it easily goes into and out of a pocket. When you remove the cap and put it on the other end, the result is a full-size nicely weighted and balanced pen. It feels good in the hand. Holding it and looking at it just makes me smile, as it is such a nice example of elegant engineering.</p>
<p>The only thing I don't like about it is the ballpoint cartridge. I don't find myself in zero G or underwater very often, so I don't really need the whole pressurized-magic-ink thing. I find I have to press down a little harder than I'd like to get the ink to flow while writing. It's better than the typical 25¢ disposable ballpoint, but when I'm scribbling on an index card, I'd like smoother ink flow.</p>
<p>It's too bad they can't offer a terrestrial version of the Space Pen, with the same form factor but with gel ink. (Maybe I'll experiment with putting a Pilot G2 cartridge into my Space Pen.)</p>
<p>But that's really a minor quibble. The Bullet Space Pen is easily worth the twenty bucks, and I'll probably buy another as a backup. We astronauts value redundancy, you know.</p>
<p>By the way, whenever the topic of the Space Pen comes up, some know-it-all will note that the American space program spent millions of dollars developing a high-tech pen that writes in zero-G vacuum while the Russians decided to simply use pencils. The story is not true: see <a href="http://www.snopes.com/business/genius/spacepen.asp">Snopes.com</a> for the truth.</p>
<hr>
<p><strong>Update:</strong> A commenter provided a link to http://www.scarysharp.com/?p=441, which provides a solution for those who would like gel ink in their bullet pens.</p>
<p>I notice that I'm getting a lot of traffic from <a href="http://www.penaddict.com/2009/04/ink-links-3.html">The Pen Addict</a>. It's nice to know that others share my trivial obsessions.</p>Thanks for the Confirmation2009-04-21T15:19:13-04:002009-04-21T15:19:13-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-21:/thanks-confirmation.html<p>I thought the iPhone App Store's review policy was unnecessarily opaque, but Apple tops it with its <a href="http://www.apple.com/downloads/">Apple Downloads</a> submission process:</p>
<blockquote>
This email confirms we have received your submission. Apple reviews all submissions and reserves the right, at its discretion, to omit, edit, or reject submissions for inclusion on the …</blockquote><p>I thought the iPhone App Store's review policy was unnecessarily opaque, but Apple tops it with its <a href="http://www.apple.com/downloads/">Apple Downloads</a> submission process:</p>
<blockquote>
This email confirms we have received your submission. Apple reviews all submissions and reserves the right, at its discretion, to omit, edit, or reject submissions for inclusion on the Mac OS X Downloads website at:
<a href="http://www.apple.com/downloads/macosx/"><http://www.apple.com/downloads/macosx/>.</a>
This is the only notification you will receive. If you do not see your product listed on Mac OS X Downloads after 90 days, you may resubmit it for consideration.
</blockquote>
<p>89 days to go...</p>
<p><strong>Update:</strong> It only took one day. <em>Menubar Countdown</em> is now available at <<a href="http://www.apple.com/downloads/macosx/productivity_tools/menubarcountdown.html">http://www.apple.com/downloads/macosx/productivity_tools/menubarcountdown.html</a>>.</p>Menubar Countdown 1.1 for Mac OS X Released2009-04-21T03:38:43-04:002009-04-21T03:38:43-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-21:/menubar-countdown-11-mac-os-x-released.html<p>I've released an update to my <a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a> application.</p>
<p>See <a href="http://capablehands.net/story/menubarcountdown/announce/v1.1">http://capablehands.net/story/menubarcountdown/announce/v1.1</a> for details.</p>NutriSystem Review2009-04-18T16:57:33-04:002009-04-18T16:57:33-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-18:/nutrisystem-review.html<p>I've lost almost twenty pounds after a month-and-a-half on <a href="http://www.nutrisystem.com/">NutriSystem</a>. I'm not a paid shill. I figured I'd post my thoughts about it, in case others are considering it.</p>
<p>There are many, many diets and weight-loss programs out there. I'm not an expert on any of them; I'm just a …</p><p>I've lost almost twenty pounds after a month-and-a-half on <a href="http://www.nutrisystem.com/">NutriSystem</a>. I'm not a paid shill. I figured I'd post my thoughts about it, in case others are considering it.</p>
<p>There are many, many diets and weight-loss programs out there. I'm not an expert on any of them; I'm just a guy who wanted his pants to fit again. Here are the things that made me choose NutriSystem:</p>
<ul>
<li>It's easy: they send me food, and I eat it. I don't have to count calories, carbs, fat grams, etc. I don't have to spend time preparing special meals.</li>
<li>It's not a radical diet. The basic idea is to eat small portions, and avoid saturated fat and "bad carbs" (sugar, white bread, etc.). You supplement the food they send you with fruits, vegetables, and whole-grain breads and cereals. It's normal food.</li>
<li>No meetings or counseling required. I'm sure social support is great for a lot of people, but that kind of thing is a big negative for me.</li>
</ul>
<p>It works for me because it takes advantage of my laziness. I don't have to plan meals. I don't have to look at nutritional labels. I don't have to spend time cooking, other than popping something into the microwave for 90 seconds. All I have to do is pull my next meal out of a box every few hours.</p>
<p>Despite the fact that I'm eating only about a thousand calories a day, I rarely feel hungry. On the plan, you eat a meal six times per day, so even though the portions are small, you always feel like you've just eaten.</p>
<p>Some people complain about the cost (over three hundred dollars a month), but it's actually cheaper than the company cafeteria and restaurant fare I used to eat daily.</p>
<p>Some people simply can't stand the food. I'm not a picky eater, so I don't mind. With the extra fruit and vegetables, I'm actually eating much tastier food than I did when my meals came from restaurants. It may not be the best-tasting food in the world, but it's not bad, and I like the results.</p>
<p>I recently took a week-long vacation, and ate "normally" during that week (that is, I didn't eat the NutriSystem stuff). I didn't lose any weight that week, but I didn't gain any either, so I'm optimistic that I'm developing good eating habits as a result of the plan, and will be able to keep the weight off when I reach my goal.</p>
<p>I've lost about twenty pounds so far. I've got about thirty more pounds to lose to reach my goal weight. I'll let you know if NutriSystem gets me there.</p>Drupal Rocks2009-04-18T12:51:15-04:002009-04-18T12:51:15-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-18:/drupal-rocks.html<p>I first dabbled in web development back in the mid 1990's. Back in those dark times, you created web sites by writing raw HTML, or by writing C or Perl CGI scripts. You spent about 1% of your time creating interesting content and behavior, and 99% of your time dinking …</p><p>I first dabbled in web development back in the mid 1990's. Back in those dark times, you created web sites by writing raw HTML, or by writing C or Perl CGI scripts. You spent about 1% of your time creating interesting content and behavior, and 99% of your time dinking with site layout, page layout, link updates, database administration, and other "plumbing" issues. I decided web development wasn't for me.</p>
<p>As years have passed, I've dabbled with some of the techologies that were supposed to make web development better: ASP, JSP, PHP, ASP.NET, Rails, Django, etc. While these eliminated some of the tedium of development, they also added a lot of complexity. Those technologies give you tools for building a web site, but they don't give you a web site. You still spent 1% of your time on creativity and 99% of your time on plumbing. A "real web site" still required a team of programmers, designers, and editors to create and maintain.</p>
<p>I've set up a few wikis. Those are nice in that they are easy to set up, and then the users can create content. Wikis are awesome for providing a collaborative publishing environment. The downside is that they all look ugly; you wouldn't want to use them for general-public web sites.</p>
<p>So, I've stuck with writing raw HTML for my personal web sites. This caused my sites to suffer, because writing raw HTML for an entire site sucks, especially if you want to provide navigation links, multi-column layout, headers and footers, and other niceties. My <a href="http://capablehands.net">Capable Hands Technologies, Inc.</a> web site kept its "Under Construction" home page for two years, because it just didn't seem to be worth the effort to put something there.</p>
<p>But then a few months ago, a friend told me he had set up a site using <a href="http://drupal.org">Drupal</a>, and it was pretty cool. I finally tried it out a couple of weeks ago, and I am amazed.</p>
<p>All you do is unpack a tarball on a web site, create a MySQL database, fill in a few parameters (site name, logo, etc.), and <em>bingo!</em>, you have a real web site, with a nice layout, front page, navigation menus, articles, RSS feed, user accounts, multi-user blogs, forums, file uploads, and a zillion other things I haven't even looked at yet.</p>
<p>Drupal provides a great out-of-the-box experience, but there is a whole community providing additional modules (site features), themes (look-and-feel), and other stuff. So without writing any HTML or Perl, you can extensively customize your site with practically any feature you need.</p>
<p>The Drupal web site has a lot of documentation, but as with a lot of open-source projects, the documentation is not well organized, the tutorials are many-versions-old, and finding what you are looking for often takes forever. So I recommend that any new Drupal user read the book <a href="http://www.usingdrupal.com/"><em>Using Drupal</em></a> or something else like it.</p>
<p>The only downside to Drupal is that it is written in PHP, which is one of those programming languages which is wildly popular but which also sucks tremendously. But I understand that the Drupal codebase is one of the few examples of well-written, well-structured PHP, so I may tinker with the code a bit to learn some things.</p>
<p>Of course, Drupal is not the only "content management system" out there. WordPress or MovableType may meet your needs if you have a blog, or you may want something like Joomla or Zope instead of Drupal. The real lesson (for me) is that writing whole pages in HTML is something we don't need to do anymore.</p>
<p>Anyway, this old dog has learned some new tricks, and <em>Undefined Value</em> is now running on Drupal. As I learn more about Drupal, I'll be adding features. I apologize in advance for anything I screw up.</p>For Mac Users Who Use Firefox2009-04-17T20:57:07-04:002009-04-17T20:57:07-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-17:/mac-users-who-use-firefox.html<p>If you use Firefox on a Mac, you must change its theme to <a href="http://www.takebacktheweb.org/themes_3.html">GrApple Delicious (blue)</a> right now.</p>
<p>That is all.</p>The New Focus2009-04-16T12:53:45-04:002009-04-16T12:53:45-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-16:/new-focus.html<p>I've been thinking a lot about what I want <em>Undefined Value</em> to be. These are the reasons I have this blog:</p>
<ul>
<li>I enjoy writing and I like to practice.</li>
<li>I want to make contact with other people who care about the things I care about.</li>
<li>I want to be able …</li></ul><p>I've been thinking a lot about what I want <em>Undefined Value</em> to be. These are the reasons I have this blog:</p>
<ul>
<li>I enjoy writing and I like to practice.</li>
<li>I want to make contact with other people who care about the things I care about.</li>
<li>I want to be able to go back and review what I was thinking in the past.</li>
<li>For potential clients and employers, I want to have a collection of writings that demonstrate that I'm smart and that I take my work very seriously.</li>
<li>I want to exercise my rudimentary website design and administration skills.</li>
</ul>
<p>I don't expect to become a top blogger or generate ad revenue or become an influential thought leader. However, I do want people to read what I write, so I want to attract readers and keep the quality high.</p>
<p>To that end, I have decided to narrow the focus of this blog. Up to now, I think the audience of <em>Undefined Value</em> has consisted of these segments:</p>
<ol>
<li>Software developers</li>
<li>People who are not software developers, but who are interested in software and information technology</li>
<li>Private pilots</li>
<li>Friends and family</li>
</ol>
<p>Going forward, I'm going to concentrate on the first two groups. I haven't flown for a couple of years, so I don't have much to say to pilots anymore. The friends and family can be served via <a href="http://www.facebook.com/profile.php?id=552697319&ref=name">Facebook</a> or my <a href="http://kristopherjohnson.net/">personal website</a>.</p>
<p>That doesn't mean I won't occasionally post a picture of my dogs or re-tell a good fart joke I overheard. It just means that I'm going to focus on things that are of interest to "technology people."</p>
<p>I want to be both a teacher and a learner, so I'll be posting articles that share what I've learned but which also invite commentary.</p>
<p>Thanks for reading, and let me know what you want me to write about.</p>Undefined Value Takes a Flying Leap2009-04-15T11:29:10-04:002009-04-15T11:29:10-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-15:/undefined-value-takes-flying-leap.html<p>For the last five years, http://kristopherjohnson.blogspot.com/ has been the home of my blog, <em>Undefined Value</em>. Blogspot is a great free platform for bloggers to get their feet wet, but I've decided I want to have a "real blog" on a website I control, so <em>Undefined Value</em> now …</p><p>For the last five years, http://kristopherjohnson.blogspot.com/ has been the home of my blog, <em>Undefined Value</em>. Blogspot is a great free platform for bloggers to get their feet wet, but I've decided I want to have a "real blog" on a website I control, so <em>Undefined Value</em> now lives at https://undefinedvalue.com/.</p>
<p>I intend to get more serious about blogging. We'll see how that goes.</p>
<p>I'm going to try to get all the content from from the old blog moved over here. Again, we'll see how that goes.</p>
<p>Thanks to everyone who has read and commented on my blog over the years, and I hope you enjoy the new look. Feel free to leave feedback about the new site. I'm sure I've configured some things incorrectly, so if you have trouble navigating or leaving comments or doing anything else that you should be able to do on a "real blog," I really do want to know about it.</p>Menubar Countdown 1.0 for Mac OS X Released2009-04-06T21:52:00-04:002009-04-06T21:52:00-04:00Kristopher Johnsontag:undefinedvalue.com,2009-04-06:/menubar-countdown-10-mac-os-x-released.html<p>Lately, I've been experimenting with the <a href="http://www.pomodorotechnique.com/">Pomodoro Technique</a> for time management. The basic idea is that you work in focused 25-minute bursts, with short breaks between bursts. You are supposed to use a kitchen timer to avoid getting distracted by looking at the clock.
</p>
<p>
Of course, as a computer guy …</p><p>Lately, I've been experimenting with the <a href="http://www.pomodorotechnique.com/">Pomodoro Technique</a> for time management. The basic idea is that you work in focused 25-minute bursts, with short breaks between bursts. You are supposed to use a kitchen timer to avoid getting distracted by looking at the clock.
</p>
<p>
Of course, as a computer guy I'd like my timer to be on my computer. I looked around for a Mac application that would provide an unobtrusive 25-minute countdown timer, but I didn't find any that I liked. So I decided to write my own.
</p>
<p>
<a href="http://capablehands.net/menubarcountdown">Menubar Countdown</a> is the result of that effort. It displays a countdown timer on the right side of the menu bar. It has menu items that allow you the user to start, stop, or resume the timer.
</p>
<p>
There are three options for what you want to happen when the timer reaches 00:00:00:
</p>
<ul>
<li>Play the system alert sound (which I never notice).</li>
<li>Display an alert window (which is effective, but you may not like the abrupt interruption).</li>
<li>Speak. This is my favorite option. You can specify what you want the application to say.</li>
</ul>
<p>
It's free software, distributed under the terms of the <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public License</a>.
</p>
<p>
Source code is included. Other neophyte Cocoa programmers might find it useful as an example of using such classes as NSStatusBar, NSStatusItem and NSUserDefaultsController, or for measuring absolute time in a Mac application.
</p>
<p>
You can download the application from my snazzy new corporate web site: <a href="http://capablehands.net/menubarcountdown">Menubar Countdown product page</a>.
</p>All Work and No Play...2009-03-17T04:51:00-04:002009-03-17T04:51:00-04:00Kristopher Johnsontag:undefinedvalue.com,2009-03-17:/all-work-and-no-play.html<p>
I've been really hating my job for the last twelve months or so. I've wondered why. After all, I'm getting paid well to work on software. This is my dream job, isn't it? Why can't I enjoy it? And if I can't enjoy it, why can't I just be happy …</p><p>
I've been really hating my job for the last twelve months or so. I've wondered why. After all, I'm getting paid well to work on software. This is my dream job, isn't it? Why can't I enjoy it? And if I can't enjoy it, why can't I just be happy that I'm not unemployed or flipping burgers?
</p>
<p>
While sitting in the car dealership service lounge today, I watched a <a href="http://blog.ted.com/2009/03/stuart_brown_play.php">TEDTalk about the importance of play</a>. During that video, it hit me: I hate my job because I never get to play anymore.
</p>
<p>
When I say <em>play</em>, I don't mean playing ping-pong in the storeroom or having Nerf gunfights with my co-workers. <em>Play</em> is about curiosity, exploration, and imagination. That's what I've always liked about software development: the opportunity to explore different solutions, to try out new ideas, and to learn interesting things from other people.
</p>
<p>
For the past year, there has been none of that. My job has mostly been about fixing bugs. There have been a few new features to add, but they have all been uninteresting and uninspiring.
</p>
<p>
This may sound like whining, but lack of play really does take a toll on productivity. We have several smart people on the team, and yet we are way beyond schedule and over budget. I'm sure that a major reason for that is the fact that nobody is having any fun. Just putting your head down and charging forward may sound like an admirable way to handle an unpleasant situation, but it stifles creativity and makes it difficult to imagine better solutions. When there is no opportunity to be smart, everyone just gets more stupid.
</p>
<p>
Play should not be considered a luxury reserved for children. The mind <em>needs</em> play, just like it needs sleep. Without it, performance suffers.
</p>
<p>
I honestly can't remember the last time we did any brainstorming or drew things on whiteboards or talked about abstractions or joked about how to re-implement the whole system in some obscure programming language. All we do is write code, and contrary to what some people believe, writing code is the least valuable thing a developer does. It's the thinking behind the writing that makes us more than mere typists, and we aren't doing enough of that.
</p>
<p>
I don't know the solution to this problem. I just hope that after the current projects are finished, I'll remember how my job is supposed to feel.
</p>422009-01-18T06:40:00-05:002009-01-18T06:40:00-05:00Kristopher Johnsontag:undefinedvalue.com,2009-01-18:/42.html<div style="float: right; margin-left: 10px; margin-bottom: 10px;"><a href="http://flickr.com/photos/kristopherjohnson/3207835759/" title="photo sharing"><img src="http://farm4.static.flickr.com/3339/3207835759_fcd5ba59ec_m.jpg" alt="Picture of Kris's family" style="border: solid 2px #000000;" /></a><br /><span style="font-size: 0.9em; margin-top: 0px;"><a href="http://flickr.com/photos/kristopherjohnson/3207835759/">Christmas Card Picture</a><br />Originally uploaded by <a href="http://www.flickr.com/people/kristopherjohnson/">kristopherjohnson</a></span></div>
<p>
Another birthday has passed. It has been an eventful year: I got <a href="http://flickr.com/photos/kristopherjohnson/2791535441/">married</a>, and I've become part of a new family.
</p>
<p>
My wife <a href="http://flickr.com/photos/kristopherjohnson/3149067712/">Pebble</a> is the most amazing person I've ever met. She's smart. She's kind. She's giving. She's forgiving. Most of all …</p><div style="float: right; margin-left: 10px; margin-bottom: 10px;"><a href="http://flickr.com/photos/kristopherjohnson/3207835759/" title="photo sharing"><img src="http://farm4.static.flickr.com/3339/3207835759_fcd5ba59ec_m.jpg" alt="Picture of Kris's family" style="border: solid 2px #000000;" /></a><br /><span style="font-size: 0.9em; margin-top: 0px;"><a href="http://flickr.com/photos/kristopherjohnson/3207835759/">Christmas Card Picture</a><br />Originally uploaded by <a href="http://www.flickr.com/people/kristopherjohnson/">kristopherjohnson</a></span></div>
<p>
Another birthday has passed. It has been an eventful year: I got <a href="http://flickr.com/photos/kristopherjohnson/2791535441/">married</a>, and I've become part of a new family.
</p>
<p>
My wife <a href="http://flickr.com/photos/kristopherjohnson/3149067712/">Pebble</a> is the most amazing person I've ever met. She's smart. She's kind. She's giving. She's forgiving. Most of all, she's open in a way I thought nobody could be. Marriage and life aren't always wonderful, but she's a wonderful wife, and I know I've chosen the right partner. We grow closer every day, and I honestly can't remember what my life was like before I met her. (I'm pretty sure it sucked.)
</p>
<p>
For my birthday, she bought me the complete collection of <em>Planet of the Apes</em> movies on Blu-ray. I didn't even ask for it. How cool is that?
</p>
<p>
My stepson <a href="http://flickr.com/photos/kristopherjohnson/2915112920/">Bailey</a> is about to become ten years old. Like his mother, his emotions and thoughts are always right at the surface, so I always know where I stand with him. He is sometimes incredibly cute, and other times he's apparently possessed by Satan, but I know we'll always love one another. He loves his new grandma and grandpa (my parents), and enjoys spending time with his new cousins (my nieces).
</p>
<p>
We also got a puppy. He's a Yorkshire Terrier named <em><a href="http://flickr.com/photos/kristopherjohnson/2619799174/">Mouse</a></em>, and he thinks he owns the house. I believe there is nothing cuter than a Yorkie puppy.
</p>
<p>
Our little cabin in the woods is nice, but we all agree that we'd like more space, and we're all getting tired of driving two hours a day to get to and from our occupations, so we're probably going to be moving closer to Atlanta some time this year. But we're not in a hurry: we do like our little cabin in the woods.
</p>
<p>
My forties are shaping up to be a lot better than my twenties and thirties were. Life is good. (But I occasionally complain about it anyway.)
</p>
<p>
I've done more traveling in the past year than any other. My wife and I went to Waikiki for our honeymoon, and also visited Pensacola and the Grand Canyon. I went alone to Australia for three weeks on business, finally putting my passport to use.
</p>
<p>
Career-wise, I'm in a bit of a rut. I had hoped to do some job searching this year, but with the economy the way it is, I consider myself fortunate to have the contracting gig that I have. I'll try to acquire some new skills this year. Maybe next year...
</p>Saving a View as a Photo in an iPhone App2009-01-16T06:52:00-05:002009-01-16T06:52:00-05:00Kristopher Johnsontag:undefinedvalue.com,2009-01-16:/saving-view-photo-iphone-app.html<p>
For an iPhone app that I'm working on, I want to be able to save the screen image to the Photos album. My first attempt at this was complicated: I created a color space, a bitmap context, a <tt>CGImage</tt>, and finally a <tt>UIImage</tt>, copying and pasting most of the code …</p><p>
For an iPhone app that I'm working on, I want to be able to save the screen image to the Photos album. My first attempt at this was complicated: I created a color space, a bitmap context, a <tt>CGImage</tt>, and finally a <tt>UIImage</tt>, copying and pasting most of the code from the <em>Quartz 2D Programming Guide</em>. Unfortunately, it didn't work; I kept getting BAD_ACCESS signals when I called <tt>UIImageWriteToSavedPhotosAlbum()</tt>, even though it looked to me like everything was correct.
</p>
<p>
After Googling for a bit for known issues with UIImageWriteToSavedPhotosAlbum, I ran across a far easier solution to the problem. Here are the methods I ended up with:
</p>
<div style="overflow: scroll; border: 1px solid #666"><pre>
// Create an image for the view and save it to the Photos library
- (void)savePhotoOfView:(UIView *)view
{
UIGraphicsBeginImageContext(view.bounds.size);
[view drawRect:view.bounds];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(image,
self,
@selector(savedPhotoImage:didFinishSavingWithError:contextInfo:),
NULL);
}
// Called by UIImageWriteToSavedPhotosAlbum() when it completes
- (void) savedPhotoImage:(UIImage *)image
didFinishSavingWithError:(NSError *)error
contextInfo:(void *)contextInfo
{
NSString *message = @"This image has been saved to your Photos album";
if (error) {
message = [error localizedDescription];
}
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:message
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
</pre></div>
<p>
These just call the view's <tt>drawRect</tt> method to create an image, save the image to the Photos library, and then pop up an alert box to let the user know what happened.
</p>iPhone Development Fun2009-01-13T04:09:00-05:002009-01-13T04:09:00-05:00Kristopher Johnsontag:undefinedvalue.com,2009-01-13:/iphone-development-fun.html<p>
I am so thankful for the existence of the iPhone. Twenty years ago, if you'd told me I'd be holding a pocket-sized computer that had a high-resolution touchscreen, a wireless always-on connection to the Internet, and a library of Hollywood movies, I wouldn't have believed it. The fact that it …</p><p>
I am so thankful for the existence of the iPhone. Twenty years ago, if you'd told me I'd be holding a pocket-sized computer that had a high-resolution touchscreen, a wireless always-on connection to the Internet, and a library of Hollywood movies, I wouldn't have believed it. The fact that it can make phone calls is nice too, but I frankly don't care too much about that feature. All those idiots who keep asking "Why would anyone spend $500 on a <em>phone</em>?!?" are really missing the point.
</p>
<p>
(See <a href="http://www.thebestpageintheuniverse.net/c.cgi?u=iphone">The iPhone is a piece of shit, and so is your face</a> for an alternative opinion.)
</p>
<p>
Writing little apps for the iPhone is the only thing keeping me from giving up on software completely. (Well, OK, I guess the mortgage has something to do with that also.) At work, I'm starting my third year of maintaining a horrible soul-crushing legacy application, and it's hard for me to maintain the appropriate level of Give-a-Shit. Playing with the iPhone SDK helps.
</p>
<p>
I'm working on an application now that I hope to put in the App Store in a few weeks. No, it's not <a href="http://kristopherjohnson.blogspot.com/2008/12/to-do-list-application-update-1.html">my to-do list app</a>. I'd rather not write about it yet, because it is the kind of thing someone else could create during an uninterrupted weekend, but once it's done, I'll start the marketing.
</p>
<p>
My first app, <a href="http://kristopherjohnson.blogspot.com/2008/11/jacksorbetter-for-iphone-and-ipod-touch.html">JacksOrBetter</a>, has not been wildly successful. I've sold about 200 copies worldwide. That's not surprising, as it's a simple game, and there are a few dozen other casino-style card games available in the App Store, many of which are free. I didn't expect to make money with that game; I just wanted to go through the process of developing something and putting it up for sale.
</p>
<p>
I doubt my next app will bring in a windfall either, but that's fine with me. I'm having fun, and people are using software I've written. That's why I got into this business in the first place.
</p>New 15-inch MacBook Pro2009-01-09T04:48:00-05:002009-01-09T04:48:00-05:00Kristopher Johnsontag:undefinedvalue.com,2009-01-09:/new-15-inch-macbook-pro.html<p>
A few days ago, I bought a 15-inch MacBook Pro. For those of you keeping score, that brings the number of Macintoshes in our household to five. We also have two iPhones and several iPods. I wish the local Apple Store had some sort of customer loyalty program.
</p>
<p>
I bought …</p><p>
A few days ago, I bought a 15-inch MacBook Pro. For those of you keeping score, that brings the number of Macintoshes in our household to five. We also have two iPhones and several iPods. I wish the local Apple Store had some sort of customer loyalty program.
</p>
<p>
I bought this machine for work. (My wife will snicker at this statement, but it's true.) I've been toting a <a href="http://kristopherjohnson.blogspot.com/2006/11/toshiba-tecra-m7-tablet-pc.html">Toshiba tablet</a> with Windows XP to the office for the past couple of years, but with each passing month, I've hated it more. Most of my development work has been for Linux, and I haven't needed Windows much. So, I've decided I'll finally ditch PC's for good.
</p>
<p>
I will have VMWare Fusion running Windows, Linux, and FreeDOS. For a contract programming gig a couple years ago, I <a href="http://kristopherjohnson.blogspot.com/2006/10/my-next-windows-laptop.html">ran Windows on my 13-inch MacBook with Parallels</a>, and that worked pretty well, so I'm confident that a beefier Mac with VMWare can handle all my Windows needs from now on.
</p>
<p>
The new Pro is pretty. For the past few days, I've been preparing it for service by installing all the necessary software. On Monday, I'll take it to work, but I'll also take my old Toshiba for a few days until I'm sure the new machine can take its place.
</p>Interacting with Customers2008-12-19T05:12:00-05:002008-12-19T05:12:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-12-19:/interacting-customers.html<p>
Today I was delighted to discover that Jeff Atwood and Joel Spolsky discussed my recorded question on <a href="http://blog.stackoverflow.com/2008/12/podcast-34/">Episode 34 of the Stack Overflow podcast</a>. I now think there should be a Stack Overflow badge for people who have appeared on the podcast, either as questioners or as guests.
</p>
<p>
Here is …</p><p>
Today I was delighted to discover that Jeff Atwood and Joel Spolsky discussed my recorded question on <a href="http://blog.stackoverflow.com/2008/12/podcast-34/">Episode 34 of the Stack Overflow podcast</a>. I now think there should be a Stack Overflow badge for people who have appeared on the podcast, either as questioners or as guests.
</p>
<p>
Here is my question (without the <i>ums</i> and pauses):
</p>
<blockquote>
It is often said that the primary role of a software development manager is to insulate developers from the customers, users, executives, and other people involved with the project. However, I have often found that talking to customers and users gives me a lot of useful information that isn't in the specs about what the true requirements and goals of the project are. To what extent should developers interact with customers, users, and other stakeholders?
</blockquote>
<p>
Jeff and Joel had some good comments. What follows are my own thoughts about the topic.
</p>
<p>
When I started out as a developer, I was very introverted, content to just sit in my cubicle and interact with the world via e-mail. However, as I acquired more responsibility, I had to start talking to executives. Eventually, I became a development lead, and had to meet with actual customers.
</p>
<p>
I was terrified, but it was my job, so I did it. It turned out to be a lot easier than I expected. It was even pleasant. It turned out that customers weren't morons who wanted to torture software developers; they just wanted us to deliver software that suited their needs, which is exactly what I wanted too. I looked forward to the customer meetings, which always invigorated me and got me more excited about working on the software.
</p>
<p>
Based upon that experience, I've encouraged other developers to get involved with people outside the team, and I've often wished my employers would do more to get us out of our offices and into the users' facilities.
</p>
<p>
Of course, there are costs and risks associated with customer/user interaction. The time you spend with customers and users is time you aren't writing code, so if you do too much you lose productivity. Developers by nature are usually willing to do whatever it takes to solve problems, so there is the risk that a developer will commit to do something that shouldn't be done. If customers get too chummy with developers, they may try to go around management, which is bad for everyone. And finally, some developers have personality disorders that make them unsuitable for outside contact.
</p>
<p>
But still, I encourage all developers to try to get into positions where they can talk to customers and users. It makes software development a lot more fun.
</p>Australian Toilets2008-11-15T09:01:00-05:002008-11-15T09:01:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-11-15:/australian-toilets.html<p>
A surprising large number of people have asked me whether the toilets really do flush in the opposite direction in the Southern Hemisphere, due to the Coriolis Effect. Well, the toilets I've seen in Australia don't swirl at all; there is a short but powerful swoosh of water that lasts …</p><p>
A surprising large number of people have asked me whether the toilets really do flush in the opposite direction in the Southern Hemisphere, due to the Coriolis Effect. Well, the toilets I've seen in Australia don't swirl at all; there is a short but powerful swoosh of water that lasts only a second or two, with no rotation.
</p>
<p>
According to <a href="http://www.straightdope.com/columns/read/149/do-bathtubs-drain-counterclockwise-in-the-northern-hemisphere">The Straight Dope</a>, <a href="http://www.snopes.com/science/coriolis.asp">Snopes.com</a>, and other authorities, the whole swirling-the-opposite-way thing is a myth. Any rotation of water in a toilet, bathtub, sink, etc. is due to previous motion of the water, or direction of a water jet, or shape of the bowl, or other mundane reasons.
</p>
<p>
But it is true that if you let go of something, it falls up into the air, because we're upside down. I never would have guessed that was true.
</p>Expatriate2008-11-09T07:40:00-05:002008-11-09T07:40:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-11-09:/expatriate.html<p>
I've never been much of a traveler or a tourist. I don't get the thrill that others do from sightseeing or being away from home. But having said that, I've gotta say I'm a little disappointed with how not-foreign Adelaide, Australia is. I was hoping for a little adventure here …</p><p>
I've never been much of a traveler or a tourist. I don't get the thrill that others do from sightseeing or being away from home. But having said that, I've gotta say I'm a little disappointed with how not-foreign Adelaide, Australia is. I was hoping for a little adventure here on the opposite side of the world, but it's a lot like home. It's as adventurous as visiting Vancouver.
</p>
<p>
Of course, I'm not on vacation, and all I've seen of Australia so far is the office where I'm working, and some stuff within walking distance of the hotel. I'm hoping that next weekend I'll have a chance to see something outside the city.
</p>
<p>
I was flying over the Pacific on US Election Day, so I didn't get to vote or to watch what was going on. I caught a little coverage on CNN during my layover in Auckland. After arriving in Adelaide, it was straight to the office, but a couple of hours later, US networks started projecting the winner, and the Australians were all talking about it. All the Australians I've talked to are elated about the US election result. They don't understand the policy differences between the candidates or between the Democrat and Republican parties, but they are happy to see that America elected a black man to its highest office, and they are relieved that Bush's party will no longer be in power. I don't know what the media coverage is like back home, but over here it is overwhelmingly positive toward Obama, and there is hope that America will start being benevolent again.
</p>
<p>
My wife's been a trouper, complaining less than I expected about her new husband leaving her for three weeks on short notice. We're keeping in touch via Skype, instant messenger, text messages, and the occasional insanely-expensive phone call. Unfortunately, the timezone difference makes it difficult to keep in touch, as I'm sleeping during her day and vice versa. The only good time for a conversation during the week is when she's getting up at 5:30 AM her time and I've gotten back to the hotel at 9:00 PM my time.
</p>
<p>
Australia is a nice place, and the Australians are great people. But there's no place like home, and I look forward to getting back there.
</p>Flying to Australia2008-11-06T12:03:00-05:002008-11-06T12:03:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-11-06:/flying-australia.html<p>
Flying to Australia from the US is not a pleasant experience. It takes a long time. For me, it was a five-hour flight from Atlanta to San Francisco, then a fourteen-hour flight from San Francisco to Auckland, New Zealand, then another five-hour flight from Auckland to Adelaide, South Australia. Including …</p><p>
Flying to Australia from the US is not a pleasant experience. It takes a long time. For me, it was a five-hour flight from Atlanta to San Francisco, then a fourteen-hour flight from San Francisco to Auckland, New Zealand, then another five-hour flight from Auckland to Adelaide, South Australia. Including the layovers, the trip took over thirty hours. It will be the same on the way back, but of course, I'll be a lot happier when I reach my destination then.
</p>
<p>
It's expensive, too. My business-class round-trip ticket was $15,000.
</p>
<p>
Lucky for me, I flew Air New Zealand. This is very nice, because their Business Premier-class travel is about as good as you can get without buying your own personal aircraft. I was expecting a typical business-class seat, which gives you a few extra inches of legroom, but Air New Zealand gives each business-class passenger a very comfortable recliner, an ottoman, and in-flight entertainment system. With the push of a button, the seat folds down forward and joins the ottoman to form a bed, so it's actually possible to get some restful sleep.
</p>
<p>
My colleagues who flew United were very jealous.
</p>Stack Overflow2008-09-15T11:21:00-04:002008-09-15T11:21:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-09-15:/stack-overflow.html<p>
There is a new web site for software developers to ask questions and get answers: <a href="http://stackoverflow.com">Stack Overflow</a>.
</p>
<p>
I've been one of the site's beta testers for the past month, and I've been impressed with the quality of the information available on the site. Of course, there are plenty of a-holes …</p><p>
There is a new web site for software developers to ask questions and get answers: <a href="http://stackoverflow.com">Stack Overflow</a>.
</p>
<p>
I've been one of the site's beta testers for the past month, and I've been impressed with the quality of the information available on the site. Of course, there are plenty of a-holes there, but I think it will become one of the top go-to sites for developers.
</p>
<p>
What's cool about Stack Overflow:
<ul>
<li>It's free (unlike some other programmer-question-and-answer sites).</li>
<li>Simple question-and-answer format keeps things focused.</li>
<li>Voting lets the good information rise to the top, and the spam disappear.</li>
<li>Editing allows outdated information to be corrected.</li>
<li>Reputation system prevents newbies from screwing things up too much.</li>
</ul>
</p>
<p>
It is now open to the public. Please read <a href="http://stackoverflow.com/faq">the FAQ</a> before participating, and please don't screw it up.
</p>
<p>
Also take a look at <a href="http://www.joelonsoftware.com/items/2008/09/15.html">Joel Spolsky's explanation of how it should work</a>, and <a href="http://www.codinghorror.com/blog/archives/001169.html">Jeff Atwood's blog entry</a>.
</p>Troubleshooting the MacBook Air SuperDrive2008-09-11T05:15:00-04:002008-09-11T05:15:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-09-11:/troubleshooting-macbook-air-superdrive.html<p>
My wife has a <a href="http://www.apple.com/macbookair/">MacBook Air</a>. One of the things that makes it so light is that it doesn't have an internal SuperDrive (writable DVD/CD). Apple sells a special external SuperDrive designed to specifically work with the MacBook Air.
</p>
<p>
Unfortunately, we couldn't get the SuperDrive to work. Any disc …</p><p>
My wife has a <a href="http://www.apple.com/macbookair/">MacBook Air</a>. One of the things that makes it so light is that it doesn't have an internal SuperDrive (writable DVD/CD). Apple sells a special external SuperDrive designed to specifically work with the MacBook Air.
</p>
<p>
Unfortunately, we couldn't get the SuperDrive to work. Any disc we inserted would be ejected, without being recognized by the computer. After a few days of struggling with this, we decided we'd take it back to the local Apple Store.
</p>
<p>
We live far away from civilization, and a trip to the Apple Store requires D-Day-like preparation. So, the SuperDrive sat on the desk for a while. Then my wife figured out the problem:
</p>
<p>
It was upside-down.
</p>
<p>
Yep, we were trying to use the SuperDrive when it was <em>upside-down</em>.
</p>
<p>
You see, Apple products aren't designed like other computer manufacturer's products. Most products have a shiny logo on the top of the product, and the bottom looks like a piece of Soviet military hardware. The MacBook Air SuperDrive has a shiny silver side, and a shiny side with an Apple logo.
</p>
<p>
It never occurred to us that the logo-side would be the bottom. She turned it logo-side-down, and now it works fine.
</p>
<p>
We feel really smart for figuring that one out. At least we didn't have to be told by a Genius at the Apple Store.
</p>Favorite Podcasts2008-09-09T15:34:00-04:002008-09-09T15:34:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-09-09:/favorite-podcasts.html<p>
If you're like me, you might like the same podcasts I do. Here are my current favorites.
</p>
<ul>
<li><a href="http://youlooknicetoday.com/">You Look Nice Today: A Journal of Emotional Hygiene</a>: I find this hilarious, although I suspect most people won't get it at all. It's basically just three guys talking, alternating between being absurd …</li></ul><p>
If you're like me, you might like the same podcasts I do. Here are my current favorites.
</p>
<ul>
<li><a href="http://youlooknicetoday.com/">You Look Nice Today: A Journal of Emotional Hygiene</a>: I find this hilarious, although I suspect most people won't get it at all. It's basically just three guys talking, alternating between being absurd and being abusive toward one another. The cultural references are primarily geared toward white American males in their 30's and 40's who have never played team sports. This podcast is not safe for work.</li>
<li><a href="http://blog.stackoverflow.com/">Stack Overflow</a>: This is a series of conversations between Joel Spolsky (the <a href="http://joelonsoftware.com">Joel on Software</a> guy) and Jeff Atwood (the <a href="http://codinghorror.com">Coding Horror</a> guy). The conversations are centered around development of the new <a href="http://stackoverflow.com">stackoverflow.com</a> programmers' web site, but they follow a lot of diversions into general software development topics. If you're a software developer, you'll find this podcast very interesting. If you're not, you won't know what they're talking about.</li>
<li><a href="http://thisamericanlife.org/">This American Life</a>: I've <a href="http://kristopherjohnson.blogspot.com/2008/06/this-american-life.html">written about this before</a>.</li>
<li><a href="http://www.themoth.org/podcast">The Moth Podcast</a>: This podcast consists of people telling stories on stage. Most are pretty good. Some aren't.</li>
</ul>Night Shift2008-09-08T16:37:00-04:002008-09-08T16:37:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-09-08:/night-shift.html<p>
The team I'm working with is involved in installing a new system in <a href="http://en.wikipedia.org/wiki/South_australia">South Australia</a>. A few people are on site in <a href="http://en.wikipedia.org/wiki/Adelaide">Adelaide</a>, but most of us are still back here in Georgia, USA.
</p>
<p>
South Australia is thirteen-and-a-half hours ahead of US Eastern time. This has made it difficult to …</p><p>
The team I'm working with is involved in installing a new system in <a href="http://en.wikipedia.org/wiki/South_australia">South Australia</a>. A few people are on site in <a href="http://en.wikipedia.org/wiki/Adelaide">Adelaide</a>, but most of us are still back here in Georgia, USA.
</p>
<p>
South Australia is thirteen-and-a-half hours ahead of US Eastern time. This has made it difficult to provide support from home, as they work while we sleep and vice versa.
</p>
<p>
We came up with a solution: the people in the States will be working from 3:00 PM to 11:00 PM, which is 4:30 AM to 12:30 AM in Adelaide. The people in Australia will show up at 7:00 AM their time (which is the earliest they are allowed into the building), giving us a few hours of overlapping duty.
</p>
<p>
So, this week I get to see my wife and stepson for just a few minutes each morning (as they prepare for school/work) and each evening (when I get home around midnight). That stinks, but I guess it's better than traveling to Australia myself.
</p>
<p>
(I know some people will chime in and say "Oh, you've got to go to Australia! It's wonderful!" You people obviously aren't IT workers. If I go to Australia for this project, all I will get to see is the airport, the hotel, and the basement where they keep the computers. Sightseeing is for people in Business Development.)
</p>Secrets of Recruiters2008-09-08T16:37:00-04:002008-09-08T16:37:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-09-08:/secrets-recruiters.html<p>
When you are contacted by a recruiter, they will give you a description of the job they want you to take, but they won't tell you the name of the employer. This ensures that they will get their cut for their "assistance" if you eventually get the job.
</p>
<p>
Luckily, recruiters …</p><p>
When you are contacted by a recruiter, they will give you a description of the job they want you to take, but they won't tell you the name of the employer. This ensures that they will get their cut for their "assistance" if you eventually get the job.
</p>
<p>
Luckily, recruiters are as lazy as the rest of us. If you Google the description, you can usually find the job listing or company profile.
</p>
<p>
I don't do this to screw recruiters out of their take. If I am interested in the job, I'll go through the recruiter. I just do it to save time; 90% of the jobs offered via recruiters are not worth pursuing.
</p>JacksOrBetter for iPhone and iPod Touch2008-09-07T11:49:00-04:002008-09-07T11:49:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-09-07:/jacksorbetter-iphone-and-ipod-touch.html<div style="float: right; margin-left: 10px; margin-bottom: 10px;"><a href="http://www.flickr.com/photos/kristopherjohnson/2835235817/" title="photo sharing"><img src="http://farm4.static.flickr.com/3035/2835235817_c78cea5dc5_m.jpg" alt="Screenshot of video poker game" style="border: solid 2px #000000;" /></a><br /><span style="font-size: 0.9em; margin-top: 0px;"><a href="http://www.flickr.com/photos/kristopherjohnson/2835235817/">JacksOrBetter for iPhone</a><br />Originally uploaded by <a href="http://www.flickr.com/people/kristopherjohnson/">kristopherjohnson</a></span></div>
<p>
Several years back, I created <a href="http://kristopherjohnson.blogspot.com/2004/01/jacksorbetter.html">JacksOrBetter</a> for Palm OS. Now, I've created an updated version of that for iPhone and iPod Touch.
</p>
<p>
It is available for free. <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=290542821&mt=8">Click here to download from the App Store</a>.
</p>Know Your Programming Languages2008-08-21T00:10:00-04:002008-08-21T00:10:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-08-21:/know-your-programming-languages.html<p>
I am often amazed at how little some of my colleagues know or care about their craft. Something that constantly frustrates me is that people don't want to learn any more than they need to about the programming languages they use every day. Many programmers seem content to learn some …</p><p>
I am often amazed at how little some of my colleagues know or care about their craft. Something that constantly frustrates me is that people don't want to learn any more than they need to about the programming languages they use every day. Many programmers seem content to learn some pidgin sub-dialect, and stick with that. If they see a keyword or construct that they aren't familiar with, they'll complain that the code is "tricky."
</p>
<p>
What would you think of a civil engineer who shied away from calculus because it had "all those weird math symbols?"
</p>
<p>
I'm not suggesting that we all need to become "language lawyers." But if you make your living as a programmer, and claim to be a competent user of language X, then I think at a minimum you should know the following:
</p>
<ul>
<li>What are all the keywords of the language?</li>
<li>What are all the valid syntactic forms?</li>
<li>How are memory, files, and other operating system resources managed?</li>
<li>Where is the official language specification and library reference for the language?</li>
</ul>
<p>
The last one is the one that really gets me. Many programmers seem to have no idea that there is a "specification" or "standard" for any particular language. I still talk to people who think that Microsoft invented C++, and that if a program doesn't compile under VC6, it's not a valid C++ program.
</p>
<p>Programmers these days have it easy when it comes to obtaining specs. Newer languages like C#, Java, Python, Ruby, etc. all have their documentation available for free from the vendors' web sites. Older languages and platforms often have standards controlled by standards bodies that demand payment for specs, but even that shouldn't be a deterrent: the C++ standard is available from ISO for $30 (and why am I the only person I know who has a copy?).
</p>
<p>
Programming is hard enough even when you do know the language. If you don't, I don't see how you have a chance.
</p>First Contact2008-07-29T01:57:00-04:002008-07-29T01:57:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-07-29:/first-contact.html<p>
Today is the anniversary of the first communication between my wife and myself. How did we celebrate? By playing online games on our <i>two</i> Xbox 360 consoles; one in the family room, and one in the bedroom, shouting taunts at one another.
</p>
<p>
Some may think it's a little sad that …</p><p>
Today is the anniversary of the first communication between my wife and myself. How did we celebrate? By playing online games on our <i>two</i> Xbox 360 consoles; one in the family room, and one in the bedroom, shouting taunts at one another.
</p>
<p>
Some may think it's a little sad that we did this, but think about it this way: what is the likelihood that each of us would have found another person who would have thought this was a great way to spend an evening?
</p>
<p>
By the way, this is the first time we've played games like this. And my wife probably won't immediately recognize the Star Trek allusion in this post's title. She's not <i>that</i> geeky, which is a very good thing.
</p>Fairness2008-07-10T14:16:00-04:002008-07-10T14:16:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-07-10:/fairness.html<p>
This sentence made my day:
</p>
<blockquote>
As you know, fairness is a concept that was invented so that children and idiots could participate in arguments.
</blockquote>
<p>
from the <a href="http://dilbert.com/blog/entry/fairtaxes/">Scott Adams blog</a> (the Dilbert guy).
</p>Mouse2008-06-29T16:10:00-04:002008-06-29T16:10:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-06-29:/mouse.html<div style="float: right; margin-left: 10px; margin-bottom: 10px;"><a href="http://www.flickr.com/photos/kristopherjohnson/2619799174/" title="photo sharing"><img src="http://farm4.static.flickr.com/3233/2619799174_712e85f71d_m.jpg" alt="Picture of Mouse, a Yorkshire Terrier puppy" style="border: solid 2px #000000;" /></a><br /><span style="font-size: 0.9em; margin-top: 0px;"><a href="http://www.flickr.com/photos/kristopherjohnson/2619799174/">Mouse</a><br />Originally uploaded by <a href="http://www.flickr.com/people/kristopherjohnson/">kristopherjohnson</a></span></div>
<p>
This is Mouse, the newest member of our family. He's a twelve-week-old Yorkshire Terrier. My wife says I've been beaming with a smile ever since I first held him.
</p>
<p>
My wife and I have been talking about getting a dog for a while. We were …</p><div style="float: right; margin-left: 10px; margin-bottom: 10px;"><a href="http://www.flickr.com/photos/kristopherjohnson/2619799174/" title="photo sharing"><img src="http://farm4.static.flickr.com/3233/2619799174_712e85f71d_m.jpg" alt="Picture of Mouse, a Yorkshire Terrier puppy" style="border: solid 2px #000000;" /></a><br /><span style="font-size: 0.9em; margin-top: 0px;"><a href="http://www.flickr.com/photos/kristopherjohnson/2619799174/">Mouse</a><br />Originally uploaded by <a href="http://www.flickr.com/people/kristopherjohnson/">kristopherjohnson</a></span></div>
<p>
This is Mouse, the newest member of our family. He's a twelve-week-old Yorkshire Terrier. My wife says I've been beaming with a smile ever since I first held him.
</p>
<p>
My wife and I have been talking about getting a dog for a while. We were thinking about getting something like a Boxer. We decided we should wait before getting something so big.
</p>
<p>
Last week, my wife suggested she'd like to get a Yorkshire Terrier or a Miniature Schnauzer. As luck would have it, on Friday a friend needed to stop at a pet store to get a new leash for his dog, and so I saw some yorkies and a mini schnauzer. A smart worker at the store noticed my interest, and immediately put a yorkie in my hands. It was very cute, but I was able to give it back. I sent a text to my wife telling her I'd seen them, and she texted back that I should buy one immediately. I stayed strong, and didn't buy one.
</p>
<p>
The following day, we went to the store together. Before we went in, we decided we would look at the dogs, but would have lunch before committing to anything. We played with a yorkie (who would eventually come home with us) and a schnauzer. Pebble had wanted a schnauzer before going in, but the yorkie had a lot more personality. We told the salesman we would think about it.
</p>
<p>
On the way to lunch, it was obvious we had both already decided we wanted that yorkie. Pebble came up with the name "Mouse," and from that point he was <em>our dog</em>. She suggested we go back right away to pick him up, before anyone else got him, but I wanted to stick to the lunch-first plan.
</p>
<p>
To my shock and horror, when we returned to the store, another customer was playing with <em>our dog</em>! She was clearly very interested. I was tempted to immediately shout "We want him, we'll pay right now," but decided the wiser course was to wait and see what happened.
</p>
<p>
Thankfully, the other woman wanted to think about it, so as soon as the dog was back in the pen, I pounced and told the salesman we'd take him. The other woman was crestfallen, but you know, we saw him first.
</p>
<p>
It's great to have a pet again. My wife had cats when we met, but I have severe allergies to cats, so she had to choose between me and them. Luckily, she had family members willing to take the cats. I have mild allergic reactions to dogs, but yorkies are hypo-allergenic, and I haven't had any reactions to Mouse.
</p>The Grand Re-Passwording2008-06-14T11:38:00-04:002008-06-14T11:38:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-06-14:/grand-re-passwording.html<p>
I have a dirty little secret: I have used the same password for my web site accounts for a very long time. Yahoo!, Google, Hotmail, Amazon, all of them — same password, unchanged for years. I've always known this was a bad idea, but I didn't want the trouble of creating …</p><p>
I have a dirty little secret: I have used the same password for my web site accounts for a very long time. Yahoo!, Google, Hotmail, Amazon, all of them — same password, unchanged for years. I've always known this was a bad idea, but I didn't want the trouble of creating unique passwords and changing them periodically.
</p>
<p>
That has changed. After hearing the description of <a href="http://passwordmaker.org">PasswordMaker</a> on the <a href="http://blog.stackoverflow.com/">stackoverflow</a> podcast, I decided to finally take action. Unlike a lot of other "password manager" applications, PasswordMaker doesn't require you to store your passwords anywhere or synchronize them between machines. Instead, it uses a hash algorithm to generate a unique password for each site, but you don't have to store anything anywhere. It can be used with any web browser, on any platform.
</p>
<p>
So now I'm changing all my passwords on all the web sites I use. I may even change them periodically.
</p>This American Life2008-06-05T12:14:00-04:002008-06-05T12:14:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-06-05:/american-life.html<p>
<em><a href="http://thisamericanlife.org/">This American Life</a></em> has become my favorite podcast. Each week, they choose a theme and have three little stories about that theme. The "stories" are usually interviews or monologues. Occasionally they will do a single long story.
</p>
<p>
The host's voice annoyed me at first. After a few shows, though, I …</p><p>
<em><a href="http://thisamericanlife.org/">This American Life</a></em> has become my favorite podcast. Each week, they choose a theme and have three little stories about that theme. The "stories" are usually interviews or monologues. Occasionally they will do a single long story.
</p>
<p>
The host's voice annoyed me at first. After a few shows, though, I now look forward to hearing it.
</p>
<p>
There is also a TV series on Showtime. I like it, but prefer the audio podcast.
</p>
<p>
<em>This American Life</em> is produced by Chicago Public Radio. If you like the show as much as I do, send 'em a few bucks.
</p>Old Man2008-04-29T04:43:00-04:002008-04-29T04:43:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-04-29:/old-man.html<p>
We got an Xbox 360 this weekend. I went to the store to buy Guitar Hero III for it.
</p>
<p>
I came home with Guitar Hero III <em>for the Playstation 3!</em> We don't have a Playstation 3.
</p>
<p>
It's true: eventually, we all turn into our parents.
</p>Killbots Want Peace Too2008-04-22T16:17:00-04:002008-04-22T16:17:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-04-22:/killbots-want-peace-too.html<div style="float: left; margin-left: 10px; margin-bottom: 10px;"> <a href="http://www.flickr.com/photos/littleanimals/2429862923/" title="photo sharing"><img src="http://farm4.static.flickr.com/3051/2429862923_01ba069d5f_m.jpg" alt="Cartoon of scary looking robot saying Killbots want peace" style="border: solid 2px #000000;" /></a> <br /> <span style="font-size: 0.9em; margin-top: 0px;"> <a href="http://www.flickr.com/photos/littleanimals/2429862923/">killbots_want_peace</a> <br /> Originally uploaded by <a href="http://www.flickr.com/people/littleanimals/">darkpony</a> </span></div>
<p><br clear="all" /></p>The Wedding and Honeymoon2008-04-19T15:27:00-04:002008-04-19T15:27:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-04-19:/wedding-and-honeymoon.html<p>
As indicated in previous posts, I got married on April 5. Although it was obviously one of the most important events in my life, I haven't written much about it, due to lack of free time. So, here's all the stuff I should have written during the past few weeks …</p><p>
As indicated in previous posts, I got married on April 5. Although it was obviously one of the most important events in my life, I haven't written much about it, due to lack of free time. So, here's all the stuff I should have written during the past few weeks.
</p>
<h3>The Wedding</h3>
<p>
Pebble and I both wanted a simple wedding. Our first idea was to only invite immediate family. But then we figured we should also invite our neighbors. And if we were inviting the neighbors, we should also invite close friends. And if we were having that many guests, maybe we should have a band. So, we ended up with about 50 people attending the wedding.
</p>
<p>
The wedding and reception were at <a href="http://theoarhouse.com/">The Oar House</a>, a nice little restaurant that happens to be about a mile from our house. The restaurant's property borders the Chestatee River, so we hoped to have the ceremony on the river bank, followed by dinner reception.
</p>
<p>
Pebble is an ardent do-it-yourselfer, so we spent a lot of time at craft stores getting all the stuff needed for decorations, place settings, centerpieces, party favors, and so on. We also spent a lot of time putting the stuff together, but we were able to draft family members to help with that.
</p>
<p>
Then, it rained on the wedding day.
</p>
<p>
We were, of course, disappointed. Due to the rain, we had to abandon the idea of the riverside ceremony, and instead hold it inside the reception tent. We also had trouble getting good pictures in our wedding garb.
</p>
<p>
But, in hindsight, the rain wasn't all bad. It kept everybody inside the tent, which gave intimacy to the event. I enjoyed spending the time with good friends, some of whom I haven't seen in years.
</p>
<p>
We hired the band <a href="http://emeraldrose.com/">Emerald Rose</a> to perform. They hold a special place in our memories, as Pebble invited me to see them on our second date. Also, one of the band members is our next-door neighbor, and another band member is the husband of the woman who officiated, so we have a lot of connections.
</p>
<p>
As we left, Pebble and I both commented on how beautiful the whole thing was. I give her full credit for how wonderful everything looked, but we were also happy that our friends and family seemed to enjoy it as much as we did. The staff at the Oar House did a great job.
</p>
<h3>The Honeymoon</h3>
<p>
After the reception, we drove to a hotel near the airport. The next morning, we flew to Honolulu. It was a nine-hour flight. We had both forgotten our Bose noise-cancelling headphones, so we picked up two pairs of Sony noise-cancelling headphones at the airport before the flight. The headphones served us well, but we now have no idea where they are.
</p>
<p>
I was surprised at how big Honolulu was; I had always thought of it as a small resort town, but driving through it felt like driving through Los Angeles. We stayed at the <a href="http://www.outriggerwaikikihotel.com/">Outrigger Waikiki</a>, a hotel on Waikiki Beach. We didn't get all checked-in and unpacked until around 5 PM Honolulu time, which is 11 PM Atlanta time, so all we did that first day was go out to dinner at Cheeseburger in Paradise (which had great coconut shrimp) and head back to the hotel.
</p>
<h4>Kris Screws Up, Big-time</h4>
<p>
The second day (Monday), we decided to spend the morning at the beach. Pebble bought me a bodyboard and thought it would be fun to watch me drown myself while she sat on the beach. As I walked down to the water, Pebble said "Don't lose your wedding ring," and I said "It won't come off; it's too tight." I went out and played in the surf for a while, but didn't like the experience. There were a lot of rocks on the bottom, and I smashed a couple of my toes pretty good (they were purple for the rest of the trip). After getting a little sun, we headed back to the hotel.
</p>
<p>
As I laid down to take a nap, I reached for my wedding ring, and discovered it wasn't there! We looked around the hotel room, but I knew I hadn't intentionally taken it off. It was out in the ocean.
</p>
<p>
After apologizing to my wife profusely for several minutes, I got on the phone and called the jeweler to ask whether they could ship us a replacement ring. Unfortunately, their computers were down, so they couldn't look up the records that day.
</p>
<p>
Over the next few days, we did get an identical ring ordered and shipped to us in Hawaii. I hope our sacrifice to the Polynesian gods will bring good fortune.
</p>
<h4>Rain</h4>
<p>
After Monday, it rained every day. So we didn't spend much time at the beach or doing other touristy things. But we enjoyed ourselves anyway. We were newlyweds.
</p>
<p>
We spent a couple of days driving along the north shore of Oahu. It is a beautiful area, and not as densely populated as the Honolulu area. We saw whales, sea turtles, and a sea lion.
</p>
<p>
We spent some time with the Bedient family, friends of Pebble's who live in Honolulu. The two girls, aged 19 and 21, decided they wanted tatoos, so Pebble and I accompanied them to the tattoo parlor. They lobbied hard for me to get a tattoo as well - one suggestion was having "I lost my wedding ring" tattooed on my forehead - but I left still unmarked.
</p>
<h3>The Return</h3>
<p>
We really didn't want to come home. Really, really, really did not want to come home. It was a difficult adjustment to go from living like royalty to being normal people again.
</p>
<p>
We've discovered that the Sears wedding gift registry website sucks. First, some people had trouble placing orders through the site. Now, we've discovered that there is no way to find out who bought the gifts we've received. So, if you sent us a gift through Sears, but haven't received a thank-you note, we're sorry.
</p>Booleans Shouldn't Be Complicated2008-04-18T18:17:00-04:002008-04-18T18:17:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-04-18:/booleans-shouldnt-be-complicated.html<p><i>Warning: Geeky programmer content below.</i></p>
<p>
While learning a new codebase, I was a little disturbed when I saw this:
</p>
<pre>
enum IsVerifying {
IsVerifyingFalse,
IsVerifyingTrue
};
enum IsVerified {
IsVerifiedFalse,
IsVerifiedTrue
};
enum IsEnabled {
IsEnabledFalse,
IsEnabledTrue
};
enum IsActive {
IsActiveFalse,
IsActiveTrue
};
enum IsOnline {
IsOnlineFalse,
IsOnlineTrue
};
/* etc. (There are about a dozen more of these.) */
</pre>
<p>
And there …</p><p><i>Warning: Geeky programmer content below.</i></p>
<p>
While learning a new codebase, I was a little disturbed when I saw this:
</p>
<pre>
enum IsVerifying {
IsVerifyingFalse,
IsVerifyingTrue
};
enum IsVerified {
IsVerifiedFalse,
IsVerifiedTrue
};
enum IsEnabled {
IsEnabledFalse,
IsEnabledTrue
};
enum IsActive {
IsActiveFalse,
IsActiveTrue
};
enum IsOnline {
IsOnlineFalse,
IsOnlineTrue
};
/* etc. (There are about a dozen more of these.) */
</pre>
<p>
And there was a lot of verbose code for dealing with these types, such as
</p>
<pre>
if (Verified()) {
verified = IsVerifiedTrue;
}
else {
verified = IsVerifiedFalse;
}
if (Enabled()) {
enabled = IsEnabledTrue;
}
else {
enabled = IsEnabledFalse;
}
/* etc. */
</pre>
<p>
What's wrong with using plain-old-Boolean values <tt>false</tt> and <tt>true</tt>, or 0 and 1?
</p>
<p>
Well, after poking around the code more, I did find the reason that the original programmer did this. He has a lot of functions that take several flags as parameters, and something like this:
</p>
<pre>
SetStates(IsVerifyingFalse,
IsVerifiedTrue,
IsEnabledTrue,
IsActiveFalse,
IsOnlineFalse);
</pre>
<p>
is easier to understand than something like this:
</p>
<pre>
SetStates(false, true, true, false, false);
</pre>
<p>
But still, <i>yyeeaagghh</i> is the proper reaction to seeing something like this.
</p>Pebble and Kris Got Married2008-04-11T22:41:00-04:002008-04-11T22:41:00-04:00Kristopher Johnsontag:undefinedvalue.com,2008-04-11:/pebble-and-kris-got-married.html<p><a href="http://www.flickr.com/photos/kristopherjohnson/2791535441/" title="Our Wedding by kristopherjohnson, on Flickr"><img src="http://farm4.static.flickr.com/3132/2791535441_280819af64_m.jpg" width="240" height="192" alt="Our Wedding" style="float:right;" /></a></p>
<p>
April 5, 2008
</p>
<p>
It was beautiful, thanks to my lovely wife. Thank you to everyone who helped make this a special day.
</p>She Switched!2008-03-07T11:41:00-05:002008-03-07T11:41:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-03-07:/she-switched.html<p>
When I first started dating my fiancee, she made fun of my Macs. I told her I'd convince her to buy one herself someday. Her response: <i>NEVER!!!</i>
</p>
<p>
She kept that stance until she bought an iPhone. Then she started looking more closely at the Mac, saying stuff like "I really …</p><p>
When I first started dating my fiancee, she made fun of my Macs. I told her I'd convince her to buy one herself someday. Her response: <i>NEVER!!!</i>
</p>
<p>
She kept that stance until she bought an iPhone. Then she started looking more closely at the Mac, saying stuff like "I really like that interface," and "I like how it turns on instantly when you open it."
</p>
<p>
Then she saw that <a href="http://www.apple.com/macbookair/">MacBook Air</a> commercial - the one where the computer fits in a manila envelope. For the first few weeks, she said it was cool, but she didn't really need one. About a week ago, she started trying to convince me that it would be a practical purchase. (I didn't need convincing; she was really trying to convince herself.)
</p>
<p>
Yesterday, she bought a MacBook Air.
</p>
<p>
However, I can't really savor my victory. I'm too jealous of her new toy.
</p>Shrine of the Mall Ninja2008-02-29T11:54:00-05:002008-02-29T11:54:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-02-29:/shrine-mall-ninja.html<p>
I was crying with laughter while reading this: <a href="http://lonelymachines.org/mall-ninjas/">http://lonelymachines.org/mall-ninjas/</a>
</p>Proud Teacher2008-02-26T14:48:00-05:002008-02-26T14:48:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-02-26:/proud-teacher.html<p>
Today, a development team I've worked with solved an architectural problem by adding a <a href="http://en.wikipedia.org/wiki/Factory_method_pattern">factory method</a>. And they did it without any prompting from me!
</p>
<p>
They grow up so fast.
</p>Large Fonts2008-02-20T03:55:00-05:002008-02-20T03:55:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-02-20:/large-fonts.html<p>
A few days ago, I finally had to change my Windows machine's settings from "Normal" font size to "Large" font size. Normal-sized text was getting too hard to read.
</p>
<p>
It's certainly not the first sign of aging I've noticed, but this is the first time I've had to make an …</p><p>
A few days ago, I finally had to change my Windows machine's settings from "Normal" font size to "Large" font size. Normal-sized text was getting too hard to read.
</p>
<p>
It's certainly not the first sign of aging I've noticed, but this is the first time I've had to make an adjustment in my work environment to compensate. I wonder what will be next.
</p>Good-bye SCons, Hello CMake2008-02-20T03:34:00-05:002008-02-20T03:34:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-02-20:/good-bye-scons-hello-cmake.html<p>
Last year, I wrote of my <a href="http://kristopherjohnson.blogspot.com/2007/03/scons.html">initial impressions</a> of <a href="http://www.scons.org/">SCons</a> for controlling software builds. My initial impressions were positive, but even then I was wary of performance issues.
</p>
<p>
A few months later, I wrote about <a href="http://kristopherjohnson.blogspot.com/2007/07/improving-scons-performance-for-msvc8.html">a performance problem with SCons and MSVC</a>. I was able to hack SCons to make …</p><p>
Last year, I wrote of my <a href="http://kristopherjohnson.blogspot.com/2007/03/scons.html">initial impressions</a> of <a href="http://www.scons.org/">SCons</a> for controlling software builds. My initial impressions were positive, but even then I was wary of performance issues.
</p>
<p>
A few months later, I wrote about <a href="http://kristopherjohnson.blogspot.com/2007/07/improving-scons-performance-for-msvc8.html">a performance problem with SCons and MSVC</a>. I was able to hack SCons to make things a little better.
</p>
<p>
Things weren't bad for me, because I've been doing all my development on Linux, where SCons is pretty well-behaved. But the Windows developers hated it. SCons will build MSVC project files so that developers can edit and browse code through the IDE, but the builds are still controlled by SCons, and SCons was painfully slow on Windows. As the codebase grew, SCons got slower and slower.
</p>
<p>
The boss put up with it for a while, but he finally decided that enough was enough. I was ordered to find something better than SCons for our cross-platform builds.
</p>
<p>
We weren't the only people dissatisfied with SCons. The KDE team had tried SCons, found it lacking, then started their own Python-based build system based on SCons, which eventually became <a href="http://code.google.com/p/waf/">Waf</a>. I looked at Waf briefly, but the immaturity of the project and lack of documentation turned me off.
</p>
<p>
I read that the <a href="http://en.wikipedia.org/wiki/GNU_build_system">autotools</a> system was starting to provide better support for Windows, but I didn't think that solution would go over well with the team members and leaders who passionately hate things that are too UNIX-ish.
</p>
<p>
So, after reading that the KDE team finally settled on <a href="http://www.cmake.org">CMake</a>, I decided to give that a try.
</p>
<p>
I've spent the last couple of days translating build scripts from SCons into CMake. So far, I'm pretty pleased with the results.
</p>
<p>
<b>Pros of CMake over SCons:</b>
</p>
<ul>
<li>It generates real honest-to-goodness MSVC solution and project files that work as well as or better than those that Windows developers would create by hand. The CMake developers don't treat Windows developers as second-class citizens.</li>
<li>The default compiler settings in the generated MSVC files and Makefiles are remarkably sane.</li>
<li>It has lots of functionality built in. (In contrast, SCons often required lots of code to be written to do simple things.)</li>
<li>It provides a simple mechanism for handling unit tests.</li>
<li>Simpler support for hierarchical builds.</li>
<li>It has the feel of something that has been used for real-world work. (In contrast, SCons always felt like a grad student's summer project.)</li>
<li>I don't have to go take a coffee break every time I need to do a build.</li>
</ul>
<p>
<b>Cons:</b>
</p>
<ul>
<li>I don't like CMake's syntax. It's like they took the syntaxes of Make, Perl, Bourne shell, and BASIC, and mixed them all together. (Please, people, stop inventing your own application-specific scripting languages! Especially if you are going to invent one that sucks.)</li>
<li>Online documentation is poor. You have to buy a $50 book if you want to figure things out in a reasonable amount of time.</li>
<li>While it is cross-platform, you still have to write a lot of "IF( WINDOWS ) ... ELSE ..." code.</li>
<li>It has no built-in support for precompiled headers. (But then again, neither did SCons. As with SCons, you can use precompiled headers by writing some code.).</li>
</ul>
<p>
I'm happy with the switch to CMake, and I'm sure the boss will be too. But who knows; maybe next year I'll be writing yet another blog entry about the need to adopt a new build system.
</p>Buying an Engagement Ring2008-01-30T09:47:00-05:002008-01-30T09:47:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-01-30:/buying-engagement-ring.html<p>
I'm getting married. You may congratulate me.
</p>
<p>
I asked The Question, and she said yes. I know you're supposed to buy an engagement ring first, and present it as part of the proposal, but I wasn't really prepared when the conversation happened. So, the following day, we went out to …</p><p>
I'm getting married. You may congratulate me.
</p>
<p>
I asked The Question, and she said yes. I know you're supposed to buy an engagement ring first, and present it as part of the proposal, but I wasn't really prepared when the conversation happened. So, the following day, we went out to shop for rings.
</p>
<p>
After hitting a half-dozen jewelry stores, we selected a beautiful ring. Of course, I'm a guy, so all jewelry is just shiny metal and sparkly rocks to me, but I was surprised when the one she liked is the one that I probably would have bought if she wasn't along for the trip.
</p>
<p>
This was on Sunday. I put down a 20% deposit on the ring, planning to move more money into my checking account on Monday so I could pay the balance on Tuesday.
</p>
<p>
Paying the deposit took a while. The nice salesperson at the store spent several minutes trying to figure out the menus and commands in the computer system. Then, when she tried to validate my check by TeleCheck, she had to call them and stay on the line for about twenty minutes before they cleared it. She said they said that the holdup was due to my address recently changing. I received a receipt, and I told them I'd be back on Tuesday.
</p>
<p>
I made my checking-account deposit on Monday morning, and went back to the store around noon on Tuesday. The nice salesperson from Sunday wasn't working on Tuesday, so nice salesperson #2 took my receipt, and spent several minutes trying to figure out (a) what I was buying and (b) how much I owed. She eventually figured it out, and I wrote another check. She ran my check through TeleCheck, and the answer came back: <i>Declined</i>.
</p>
<p>
Nice salesperson #2 asked if I had recently made a deposit. I told her I had, but that the bank told me the funds would be available immediately. She suggested I visit the bank to clear things up. I asked repeatedly whether I could bring back a cashier's check or money order, but she wouldn't answer that question, instead repeatedly insisting that, because she had worked at a bank in the past, she knew that there <i>must</i> be some kind of hold on my account.
</p>
<p>
So I went to the bank. Nice bankerperson told me that all my funds are available, and there is no reason that my check should be declined.
</p>
<p>
So I went back to the jewelry store. Nice salesperson #2 is heading out to lunch, so I had to explain the history of this transaction to nice salesperson #3, who then spent several minutes studying my receipt and looking up computer records to figure out (a) what I was buying and (b) how much I owed. She ran my check through TeleCheck, and the answer came back: <i>Declined</i>.
</p>
<p>
As she was walking out the door, nice salesperson #2 suggested that #3 call TeleCheck, which she does. She's on the line for twenty minutes, but the only answer she gets is that I've been declined.
</p>
<p>
I asked whether I could just go to the bank and bring back a cashier's check, money order, or a briefcase full of cash. She asked whether I have a debit card with me.
</p>
<p>
Yes, I did have a debit card with me, but I was under the impression that it had a limit of $400 or something like that. She swiped it through her card reader, and boom, thousands of dollars were exchanged for a sparkly rock. Wow, technology is cool. So now I own the ring, but have to wait a few days for it to be resized.
</p>
<p>
It was easier to buy my last car than to buy this ring.
</p>
<p>
Lessons learned:
</p>
<ul>
<li>TeleCheck can bite me. I haven't used checks for retail items in a long time, but I thought a check would make sense for this large purchase. TeleCheck's validation system wasted a couple hours of my time. From now on, I'll just use credit cards for everything.</li>
<li>Jewelry store salespeople could use more training on their computer systems. All three of the salespeople I observed seemed to randomly try every menu item until they got results that seemed reasonable.</li>
<li>I'm never getting married ever again.</li>
</ul>
<p>
[UPDATE: My fiancee read this entry, made a face, and remarked, "There's no love in there at all, is there?"]
</p>412008-01-17T10:10:00-05:002008-01-17T10:10:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-01-17:/41.html<div style="float: right; margin-left: 10px; margin-bottom: 10px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/2201706884/" title="41 by kristopherjohnson, on Flickr"><img src="http://farm3.static.flickr.com/2274/2201706884_517daf7cd9_m.jpg" width="171" height="240" alt="41" /></a></div>
<p>
Another year has gone by. I didn't really set many goals for myself last year, so I'm not disappointed by any failures (although I am disappointed that I didn't set any goals). I lost 20 pounds and ran the Peachtree Road Race, but I've regained the 20 pounds.
</p>
<p>
The biggest …</p><div style="float: right; margin-left: 10px; margin-bottom: 10px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/2201706884/" title="41 by kristopherjohnson, on Flickr"><img src="http://farm3.static.flickr.com/2274/2201706884_517daf7cd9_m.jpg" width="171" height="240" alt="41" /></a></div>
<p>
Another year has gone by. I didn't really set many goals for myself last year, so I'm not disappointed by any failures (although I am disappointed that I didn't set any goals). I lost 20 pounds and ran the Peachtree Road Race, but I've regained the 20 pounds.
</p>
<p>
The biggest change in my life is that the Match.com thing worked out and I am now part of an "us." This is making me very happy. We live in a newly-purchased old cabin in the woods. I'm now spending my weekends doing yardwork and other handiwork, which is a big change from my old lifestyle. I now have access to a chainsaw and will soon have a tiller. At some point, I'm going to have to learn about wells so I can get rid of the sulfur smell in our water.
</p>
<p>
I never understood why all the guys with wives and girlfriends never have any free time to hang out with us unencumbered guys. Now I understand. There's not much "me time" anymore, but I don't miss it. (Love you, sweetie.)
</p>
<p>
I am practicing guitar whenever I have some free time. I hope to be able to play some actual music by this time next year. I also want to master the drums on Rock Band.
</p>
<p>
I'm going to start taking a self-portrait on each birthday. I hope my deterioration will be very slow. I haven't decided yet whether the beard is going to be a permanent part of my appearance.
</p>
<p>
[UPDATE: 2/4/2008: Beard is gone.]
</p>
<p>
[UPDATE: 2/26/2008: I miss my beard.]
</p>Death March2008-01-16T12:31:00-05:002008-01-16T12:31:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-01-16:/death-march.html<p>
The term "death march" is commonly used for software projects that are behind schedule, over budget, have no end in sight, and yet must be completed. There is a <a href="http://www.amazon.com/Death-March-2nd-Yourdon-Press/dp/013143635X/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1200486804&sr=8-1">book</a> with that title.
</p>
<p>
I've recently become involved in one of these projects. I should say "re-involved," because I was involved …</p><p>
The term "death march" is commonly used for software projects that are behind schedule, over budget, have no end in sight, and yet must be completed. There is a <a href="http://www.amazon.com/Death-March-2nd-Yourdon-Press/dp/013143635X/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1200486804&sr=8-1">book</a> with that title.
</p>
<p>
I've recently become involved in one of these projects. I should say "re-involved," because I was involved with the project a year and a half ago, and I quit. As an independent contractor now, I feel less pain than the employees do: I get paid by the hour and can set my own schedule, whereas they have to work unpaid overtime, and they are shackled to their desks.
</p>
<p>
Yesterday, one of the developers had to go to a doctor. The stress and long hours had led to high pulse rate and other signs of anxiety. The doctor told him to go home and get some rest, which he did.
</p>
<p>
We're all glad that nothing more serious happened. Managers express surprise that anyone would work himself so hard. I've only been involved with this for a few days, but here's what I've observed:
</p>
<ul>
<li>Requirements and UI design are still changing.</li>
<li>Work that was originally estimated to take three months has now been scheduled for completion in three weeks.</li>
<li>A manager stops by every hour to get a status update.</li>
<li>There are a couple of hour-long meetings every day. Plans are changed during each meeting.</li>
</ul>
<p>
Yeah, I can't imagine why anyone would feel pressure, with management being so "helpful."
</p>
<p>
He's young. I remember when I was younger. I tried to meet crazy deadlines. I took it personally when managers demanded faster progress. I blamed myself for everything that went wrong. I worked myself sick.
</p>
<p>
Now, with a couple of decades of seasoning, I know better. If you're involved in a death march, it is due to your managers' incompetence, not yours. Work at a sustainable pace, and remember that no matter how loudly the managers are screaming, there is no need to sacrifice yourself for the good of the company.
</p>Science, Evolution, and Creationism2008-01-05T14:39:00-05:002008-01-05T14:39:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-01-05:/science-evolution-and-creationism.html<p>
The National Academy of Sciences has issued a report that documents the methods of science and the overwhelming evidence in support of the theory of evolution. It also presents the arguments against the teaching of creationism and Intelligent Design in public-school science classes. However, it also clearly outlines the reasons …</p><p>
The National Academy of Sciences has issued a report that documents the methods of science and the overwhelming evidence in support of the theory of evolution. It also presents the arguments against the teaching of creationism and Intelligent Design in public-school science classes. However, it also clearly outlines the reasons that a belief in evolution is not incompatible with a belief in God or with other religious beliefs.
</p>
<p>
It is available for download (PDF) from <a href="http://www.nap.edu/catalog.php?record_id=11876">http://www.nap.edu/catalog.php?record_id=11876</a>.
</p>
<p>
I personally find it repugnant that some religious fundamentalists are trying to erode this country's science-education programs. They have convinced a large number of people that there is a large body of scientific evidence against evolution and that there is a debate in the scientific community about its validity. They use fallacious arguments to present Intelligent Design as a logical "scientifically based" alternative. They say that "good Christians" need to support inclusion of these teachings in our kids' science classes. They want us to embrace ignorance.
</p>
<p>Those people are dishonest and evil. The information in this book can help us fight them.
</p>Slower Traffic Keep Right2008-01-02T00:05:00-05:002008-01-02T00:05:00-05:00Kristopher Johnsontag:undefinedvalue.com,2008-01-02:/slower-traffic-keep-right.html<p>
<i>Having driven a few thousand miles over the holidays, I decided I'd like to present the following message to all the drivers of America:
</i>
</p>
<p>
If you are driving in the left lane on an interstate or other multi-lane highway, please check the following:
</p>
<ul>
<li>Are there cars right behind you, but …</li></ul><p>
<i>Having driven a few thousand miles over the holidays, I decided I'd like to present the following message to all the drivers of America:
</i>
</p>
<p>
If you are driving in the left lane on an interstate or other multi-lane highway, please check the following:
</p>
<ul>
<li>Are there cars right behind you, but no cars in front of you?</li>
<li>Are you driving well below the posted speed limit?</li>
<li>Are people passing you on your right side?</li>
<li>Have you been driving dead even with the car to your right?</li>
<li>Are you talking on the phone, eating, fixing your kids' hair, or engaging in any other activity that may distract you?</li>
<li>Is there no possibility that you will pass anyone, ever?</li>
</ul>
<p>
If you answer <i>Yes</i> to any of the above questions, then please get out of the left lane. You don't belong there.
</p>
<p>
And please, don't speed up when someone tries to pass you. Highway driving is not a NASCAR-sanctioned event, and you won't lose any Winston Cup points if you let somebody get in front of you.
</p>Two Years Later2007-11-27T14:08:00-05:002007-11-27T14:08:00-05:00Kristopher Johnsontag:undefinedvalue.com,2007-11-27:/two-years-later.html<div style="float: right; margin-left: 10px; margin-bottom: 10px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/504810290/" title="Airplane photo by kristopherjohnson, on Flickr"><img src="http://farm1.static.flickr.com/190/504810290_ea942143a5_m.jpg" width="240" height="160" alt="Airplane" /></a></div>
<p>
The two-year anniversary of my <a href="http://kristopherjohnson.blogspot.com/2005/11/private-pilot-checkride.html">private pilot checkride</a> went by recently. This has legal significance, as a pilot is not allowed to exercise the privileges of the certificate (license) unless one has had a checkride or a flight review in the past 24 months. So, as of the end of …</p><div style="float: right; margin-left: 10px; margin-bottom: 10px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/504810290/" title="Airplane photo by kristopherjohnson, on Flickr"><img src="http://farm1.static.flickr.com/190/504810290_ea942143a5_m.jpg" width="240" height="160" alt="Airplane" /></a></div>
<p>
The two-year anniversary of my <a href="http://kristopherjohnson.blogspot.com/2005/11/private-pilot-checkride.html">private pilot checkride</a> went by recently. This has legal significance, as a pilot is not allowed to exercise the privileges of the certificate (license) unless one has had a checkride or a flight review in the past 24 months. So, as of the end of this month, I can't fly again until I've had a flight review.
</p>
<p>
A flight review really isn't a big deal. You are required to spend one hour on the ground and one hour in the air with a flight instructor, who will determine whether you are still a safe pilot. However, since I haven't flown since January, I'll need a couple of refresher lessons before my review.
</p>
<p>
I also need to renew my aviation medical certification, due to the blood-pressure medication I've recently been prescribed. So that's another hurdle to negotiate before I can fly again.
</p>
<p>
Right now, I have no plans to go back up in the air. I have better things to do with my time and money. I hope that I will get back into the left seat someday, but for now, I'm content to gaze up at the sky and say "It sure would be a nice day to fly."
</p>Learning Jazz Guitar2007-11-16T11:49:00-05:002007-11-16T11:49:00-05:00Kristopher Johnsontag:undefinedvalue.com,2007-11-16:/learning-jazz-guitar.html<div style="float: right; margin-left: 10px; margin-bottom: 10px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/673071561/" title="Les Paul Bridge by kristopherjohnson, on Flickr"><img src="http://farm2.static.flickr.com/1268/673071561_52b8447cc5_m.jpg" width="240" height="172" alt="Les Paul Bridge" /></a></div>
<p>
After spending way too much time playing <i><a href="http://en.wikipedia.org/wiki/Guitar_hero">Guitar Hero</a></i>, it occurred to me that if I had spent the same amount of time practicing with my real guitars, I might be able to play by now.
</p>
<p>
I have collected a lot of books about rock guitar over the past 20 …</p><div style="float: right; margin-left: 10px; margin-bottom: 10px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/673071561/" title="Les Paul Bridge by kristopherjohnson, on Flickr"><img src="http://farm2.static.flickr.com/1268/673071561_52b8447cc5_m.jpg" width="240" height="172" alt="Les Paul Bridge" /></a></div>
<p>
After spending way too much time playing <i><a href="http://en.wikipedia.org/wiki/Guitar_hero">Guitar Hero</a></i>, it occurred to me that if I had spent the same amount of time practicing with my real guitars, I might be able to play by now.
</p>
<p>
I have collected a lot of books about rock guitar over the past 20 years, but I am getting more interested in jazz. Can anyone recommend some good basic/intermediate books or interactive courses for learning jazz guitar?
</p>
<p>
(I'm interested in jazz keyboard as well, so related recommendations would also be welcome.)
</p>Leopard Impressions2007-11-01T11:25:00-04:002007-11-01T11:25:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-11-01:/leopard-impressions.html<p>
Unlike the Windows world, where operating systems upgrades are sources of frustration and loathing, among Mac users upgrades are met with enthusiastic interest. I've been using <a href="http://www.apple.com/macosx">Leopard</a> (Mac OS X 10.5) for a few days now.
</p>
<p>
The performance improvements promised by Apple are real. Everything feels snappier. Spotlight is …</p><p>
Unlike the Windows world, where operating systems upgrades are sources of frustration and loathing, among Mac users upgrades are met with enthusiastic interest. I've been using <a href="http://www.apple.com/macosx">Leopard</a> (Mac OS X 10.5) for a few days now.
</p>
<p>
The performance improvements promised by Apple are real. Everything feels snappier. Spotlight is actually usable now.
</p>
<p>
My favorite new feature is Spaces. Some say "Big deal. It's just a virtual desktop manager. UNIX workstations have had those for years." True, but it is an improvement over the original Exposé feature. Unlike other virtual desktops, it is well-integrated into the rest of the UI. Dragging live windows between virtual workspaces is really cool.
</p>
<p>
Time Machine is pretty cool too. Again, some would say "Big deal. It's just a backup/restore application. I can do the same thing with rsync." What makes Time Machine special is its simplicity. You plug an external drive in, and the Mac asks "Do you want to back up your main drive to this drive?" If you answer "yes," then that's it: you now have automatic hourly, daily, and weekly backups. Unlike other backup systems, Time Machine keeps all these backups available, but conserves drive space by not making copies of files that have not changed from one backup to another.
</p>
<p>
Time Machine is one of those amazingly great things that seems obvious, now that somebody has done it. I expect Time Machine clones to appear for Windows and UNIX very soon.
</p>
<p>
I do have some complaints about Time Machine: the UI is a little hard to figure out the first time you see it (and there is no menu bar or online help available in the app), and my MacBook CPU usage goes to eleven for a couple of minutes every hour while it makes the backups. I may turn off the automatic backups and switch to manual backups (right-click the backup drive and choose "Backup Now").
</p>
<p>
The new version of the Safari web browser is a lot more usable than the previous version, but I went back to Firefox after a few days. Firefox has more "power-user" features than Safari does, and I can't live without them.
</p>
<p>
I have mixed feelings about the "Leopard look." On one hand, brushed metal has pretty much disappeared, so we can rejoice. But there are other things that, while they look cool, actually make it more difficult to see important information: translucent menus, the 3D Dock, subtle folder icons, too-dark windows, etc. But it's not as bad as Vista.
</p>
<p>
On the whole, it's a solid upgrade. In a way, it is a bit of a letdown, because it is really just a polishing of an already-good system. Of its touted "300+ new features," few are going to change the way one uses their Mac. I haven't found anything that makes me say "Wow!" but there are a lot of little new things that make me say "Hey, that's kinda neat."
</p>
<p>
For a good in-depth technical review of Leopard, see <a href="http://arstechnica.com/reviews/os/mac-os-x-10-5.ars/1">the Ars Technica review</a>. From that review, it looks like it is a great time to be a Mac developer—lots of cool new APIs and debugging aids. (Unfortunately, I'm still a Windows whore.)
</p>
<p>
To sum everything up: the upgrade was definitely worth $129 and a few hours of time. My only regret is that I didn't buy the "family pack" so that I could also upgrade my old iMac G5. Is it time to buy a new iMac?
</p>Leopard Upgrade2007-10-28T06:55:00-04:002007-10-28T06:55:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-10-28:/leopard-upgrade.html<p>
I've upgraded my MacBook from Tiger to Leopard. I hit a couple of snags along the way; maybe this will help someone else avoid the same issues.
</p>
<p>
1. When I first attempted to upgrade, the Installer wouldn't allow me to select my hard drive for the upgrade. I have been …</p><p>
I've upgraded my MacBook from Tiger to Leopard. I hit a couple of snags along the way; maybe this will help someone else avoid the same issues.
</p>
<p>
1. When I first attempted to upgrade, the Installer wouldn't allow me to select my hard drive for the upgrade. I have been using a copy of my original hard drive, and the copy was apparently not partitioned with "GUID Partition Table." It was booting fine under Tiger, but apparently there are new rules for Leopard. The installer offered to erase my drive for me and partition it correctly, but I didn't want to lose all my applications and data. The moral: when partitioning a new drive for use as a boot disk, click the <b>Options...</b> button in Disk Utility's <b>Partition</b> page and select "GUID Partition Table" (the default selection is "Apple Partition Table").
</p>
<p>
2. After installation, the drive started booting, but then just sat on a blank blue screen for a long time. This is apparently caused by an old version of Unsanity's Application Enhancer. To remove this software and let Leopard boot, follow the "Solution 2" instructions given here: <a href="http://docs.info.apple.com/article.html?artnum=306857">http://docs.info.apple.com/article.html?artnum=306857</a>.
</p>
<p>
3. My wireless Mighty Mouse didn't work at first. The Bluetooth icon didn't show up in the System Preferences, and the menu bar showed the icon but the menu said that Bluetooth was unavailable. After rebooting a couple times, Bluetooth magically reappeared and the mouse worked as before.
<p>
After getting over those little bumps, Leopard appears to be working fine, and it is worth noting that the serious problems would not have occurred if I was using the original Apple-installed hard drive and I had not installed any hacky software. So, can't blame Apple.
</p>Backups2007-10-26T11:32:00-04:002007-10-26T11:32:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-10-26:/backups.html<p>
I've never been good at keeping backups. Back in the good old days, when all my data fit on one floppy disk, I made copies of those, but the first time I had to back up a 20-MB (yes, <i>megabyte</i>) hard drive onto a stack of floppies, I gave up …</p><p>
I've never been good at keeping backups. Back in the good old days, when all my data fit on one floppy disk, I made copies of those, but the first time I had to back up a 20-MB (yes, <i>megabyte</i>) hard drive onto a stack of floppies, I gave up on backups. As my hard drives have grown, the thought of spending time making huge backups have become more daunting.
</p>
<p>
I've been lucky. I've never had a hard drive crash, or lost a laptop, or otherwise been unpleasantly surprised. I've never been taught a harsh lesson about the importance of backups. For important files, I've e-mailed copies to myself, taking advantage of the practically unlimited free storage space provided by Yahoo! Mail and GMail. However, if one of my hard drives ever died, it would take a very long time to re-install an OS and all my applications and settings.
</p>
<p>
I've always felt that I should be keeping backups, and with the upcoming <a href="http://www.apple.com/macosx/">Mac OS X Leopard</a> upgrade, I figured I should keep a backup of my Tiger installation in case Leopard turned out to be a lemon. A <a href="http://jwz.livejournal.com/801607.html">recent post by jwz</a> about backups prompted me to get serious. His suggestion is basically to buy some extra hard drives and an external enclosure, make copies of your hard drives, and use <a href="http://samba.anu.edu.au/rsync/">rsync</a> to periodically copy changes from your main drives to the backup copies. This gives you a bootable backup drive, so if your real drive ever dies, you just pop the backup drive into your computer, and you're back in business. jwz's advice is sound, and is easy to follow if you have a Mac or a Linux box. It's a little expensive to buy so many spare drives, but the convenience of having bootable backups is worth it to me.
</p>
<p>
Unfortunately, it is not as easy to back up a Windows machine. You can use rsync if you have <a href="http://www.cygwin.com">Cygwin</a> installed, but I wasn't sure that I would trust that to give me a bootable backup. So, my strategy for now is to use <a href="http://www.acronis.com/homecomputing/products/trueimage/">Acronis True Image</a> to make a backup copy of my drive, and then use <a href="http://www.microsoft.com/windowsxp/using/digitalphotography/prophoto/synctoy.mspx">Microsoft's SyncToy</a> to periodically copy new files from the laptop to the backup drive.
</p>
<p>
One benefit of this strategy is that it has been easy to upgrade my hard drives. My MacBook only had a 60-GB drive, which got full pretty quick; now it has a 160-GB drive with plenty of extra space. I also grew my Windows laptop drive from 120 GB to 160 GB.
</p>
<p>
I'll play around with the "Time Machine" feature of Leopard, but I'll probably keep relying on the simpler backup strategy instead of Apple's slick magic stuff.
</p>.NET Event Logging2007-09-20T19:49:00-04:002007-09-20T19:49:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-09-20:/net-event-logging.html<p>
(Nobody else will care about this. Move along. Nothing to see here now. Maybe I'll clean this up for public consumption later.)
</p>
<p>
After spending way too much time figuring out how to change the name of a custom event log my .NET-based service was writing messages to, I decided I …</p><p>
(Nobody else will care about this. Move along. Nothing to see here now. Maybe I'll clean this up for public consumption later.)
</p>
<p>
After spending way too much time figuring out how to change the name of a custom event log my .NET-based service was writing messages to, I decided I need to save the snippet of code that finally worked.
</p>
<p>
For more info about the confusing world of .NET's EventLog class, see <a href="http://www.informit.com/guides/printerfriendly.aspx?g=dotnet&seqNum=238">http://www.informit.com/guides/printerfriendly.aspx?g=dotnet&seqNum=238</a>.
</p>
<pre>
namespace BlahBlahBlah
{
class Log
{
// Lots of stuff left out here.
// ...
private static EventLog eventLog = null;
private static readonly string eventSource = "My Service";
private static readonly string eventLogName = "My Log";
private static readonly string eventLogMachineName = "."; // local
/// <summary>
/// Initializer for Log class
/// </summary>
static Log()
{
// Register this event source if necessary
try
{
bool needCreate = false;
if (EventLog.SourceExists(eventSource))
{
string logName = EventLog.LogNameFromSourceName(eventSource,
eventLogMachineName);
if (logName != eventLogName)
{
EventLog.DeleteEventSource(eventSource);
needCreate = true;
}
}
else
{
needCreate = true;
}
if (needCreate)
{
EventLog.CreateEventSource(eventSource, eventLogName);
}
}
catch
{
// Ignore failure
}
// Initialize our EventLog instance
try
{
eventLog = new EventLog(eventLogName, eventLogMachineName, eventSource);
// Ensure our event log has the "overwrite as needed" setting
eventLog.ModifyOverflowPolicy(OverflowAction.OverwriteAsNeeded,
eventLog.MinimumRetentionDays);
}
catch
{
// Ignore failure
}
}
// Lots more stuff left out
// ...
}
}
</pre>New Eyeglasses2007-09-20T01:11:00-04:002007-09-20T01:11:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-09-20:/new-eyeglasses.html<div style="float: right; margin-left: 10px; margin-bottom: 10px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/1410292554/" title="photo sharing"><img src="http://farm2.static.flickr.com/1425/1410292554_70ab78a73a_m.jpg" alt="Picture of Kris with new eyeglasses" style="border: solid 2px #000000;" /></a> <br /> <span style="font-size: 0.9em; margin-top: 0px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/1410292554/">New Eyeglasses</a> <br /> Originally uploaded by <a href="http://www.flickr.com/people/kristopherjohnson/">kristopherjohnson</a> </span></div>
<p>
I've worn contact lenses since I was a teenager. I've never liked eyeglasses: they're too heavy, too fragile, too dusty, and just plain uncomfortable. In contrast, contacts are lightweight, they rarely get dirty, they provide peripheral vision, and you don't have to wear ugly …</p><div style="float: right; margin-left: 10px; margin-bottom: 10px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/1410292554/" title="photo sharing"><img src="http://farm2.static.flickr.com/1425/1410292554_70ab78a73a_m.jpg" alt="Picture of Kris with new eyeglasses" style="border: solid 2px #000000;" /></a> <br /> <span style="font-size: 0.9em; margin-top: 0px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/1410292554/">New Eyeglasses</a> <br /> Originally uploaded by <a href="http://www.flickr.com/people/kristopherjohnson/">kristopherjohnson</a> </span></div>
<p>
I've worn contact lenses since I was a teenager. I've never liked eyeglasses: they're too heavy, too fragile, too dusty, and just plain uncomfortable. In contrast, contacts are lightweight, they rarely get dirty, they provide peripheral vision, and you don't have to wear ugly clip-on sunglasses over them. I've never understood why anyone would prefer glasses, other than because their eyes simply can't handle them.
</p>
<p>
But, a persuasive woman suggested she'd like to see me in glasses more often, so I decided to give glasses another try. My fifteen-year-old pair had very large lenses, which are great for visibility but they are heavy and unfashionable. I wore them only in those rare times when my eyes were irritated by allergies or other problems. I decided to try some new frames, and after some searching, settled on this Ray-Ban #6127 Bronze frame.
</p>
<p>
I figure I'll wear them for the rest of the week to see if I can get used to them. I expect to be back in my contacts next week, except for time spent with the persuasive woman.
</p>
<p>
UPDATE: I hate wearing them, but the persuasive woman likes them, so, well...
</p>Medication2007-09-18T06:18:00-04:002007-09-18T06:18:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-09-18:/medication.html<p>
Last week, I had my first complete physical exam in twenty years. I figured that, because I'm 40, I ought to get one. I was wondering what problems they might find.
</p>
<p>
EKG, heart sounds, lungs, reflexes, and everything were fine. However, my blood pressure was high. It was also high …</p><p>
Last week, I had my first complete physical exam in twenty years. I figured that, because I'm 40, I ought to get one. I was wondering what problems they might find.
</p>
<p>
EKG, heart sounds, lungs, reflexes, and everything were fine. However, my blood pressure was high. It was also high when I had my aviation exam, and both my parents have high blood pressure, so I finally had to accept the fact that I have a problem that probably can't be solved with rest, exercise, and a good diet. The doctor prescribed a daily dosage of blood pressure control medication.
</p>
<p>
<i>Medication</i>. In one sense, it's no big deal; lots of people are on medication. But, for the first time, I've been prescribed a daily dosage that I'll probably have to take for the rest of my life.
</p>
<p>
Does this make me officially middle-aged?
</p>
<p>
UPDATE: This means that if I want to fly again, I'll have to get cleared by the FAA medical staff again, which is a 4-to-6-week procedure. My medication is one that is approved by the FAA, but they review such things on a case-by-case basis.
</p>Mom vs. MSN2007-08-28T12:16:00-04:002007-08-28T12:16:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-08-28:/mom-vs-msn.html<p>
My Mom likes MSN.com. It's been her homepage for years, and I've accepted the fact that I won't be able to get her to switch to a better portal. However, every time Mom gets a new computer, she somehow gets tricked into signing up for MSN dial-up service for …</p><p>
My Mom likes MSN.com. It's been her homepage for years, and I've accepted the fact that I won't be able to get her to switch to a better portal. However, every time Mom gets a new computer, she somehow gets tricked into signing up for MSN dial-up service for $9.99/month, despite the fact that she already has a broadband Internet connection.
</p>
<p>
Mom's a smart woman, so I'm not sure why this keeps happening. I think the new computers' desktops are always cluttered with crapware icons, and as she clicks them all to see what they do, one of them offers to help her "get connected to the Internet," even though she's already connected to the Internet. Then an animated translucent butterfly pesters her until the clicks "OK."
</p>
<p>
I'm sure there are people who actually would find it helpful for a new Windows machine to assist them in setting up a dial-up account. But, it would be even more helpful if the MSN take-your-money wizard would detect whether a dial-up connection is really needed.
</p>
<p>
Next time I help set up a new computer, I'll be sure to delete anything that says "MSN" before turning it over to the owner.
</p>Python Server Start, Take 22007-08-11T07:00:00-04:002007-08-11T07:00:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-08-11:/python-server-start-take-2.html<p>
A couple of months ago, I posted <a href="http://kristopherjohnson.blogspot.com/2007/06/python-server-start.html">Python Server Start</a>, a simple template for starting implementation of a network server in Python. I got a comment from "dt" suggesting that what I really wanted to use was the standard Python <tt>SocketServer</tt> module.
</p>
<p>
Today, I had to write a "real server …</p><p>
A couple of months ago, I posted <a href="http://kristopherjohnson.blogspot.com/2007/06/python-server-start.html">Python Server Start</a>, a simple template for starting implementation of a network server in Python. I got a comment from "dt" suggesting that what I really wanted to use was the standard Python <tt>SocketServer</tt> module.
</p>
<p>
Today, I had to write a "real server" in Python, so I finally got around to looking into <tt>SocketServer</tt>. The documentation wasn't helpful, but the source code for the module was straightforward, so I figured things out pretty quickly.
</p>
<p>
After work, I decided to create a more generic version of what I'd done while I was on the clock. What follows is my new "starting point" for implementing a server process in Python. It's about 300 lines long, which is a bit large for a "Hello, world!" kind of program, but it has these nifty new features:
</p>
<ul>
<li>The server starts a daemon process, disconnected from the user's terminal, like it should.</li>
<li>The server writes to a log file</li>
<li>It implements a simple protocol between client and server. Basically, the client just sends its command-line arguments to the server, and the server processes the command and sends output back, which the client writes to standard output. (This protocol should, of course, be replaced with whatever protocol your real server has to handle; the template is just in place for testing and demonstration.)</li>
<li>It can work with TCP/IP sockets, or can use UNIX domain sockets (on platforms that support them).</li>
</ul>
<p>
I've only tried it on Mac OS X and Linux. It will need some work for Windows, but thankfully, I haven't had to do much Windows programming lately, so I'm not going to worry about it.
</p>
<p>
Making the necessary changes to use a base of <tt>ForkingTCPServer</tt>, <tt>ThreadingTCPServer</tt>, <tt>ThreadingUnixStreamServer</tt>, or other variations is left as an exercise for the reader.
</p>
<p>
I welcome any suggestions for improvement.
</p>
<pre>
#!/usr/bin/env python
"""Server start
This is a template for a Python-based server daemon derived from
SocketServer. Hack it up as needed.
This script implements both the server daemon and a command-line
client that can issue requests against it. The template client-server
protocol is very simple: the client simply sends the command-line
arguments to the server, and the server returns output which the
client writes to its standard output. Change the protocol as needed
for your purposes.
The template contains a few UNIXisms. Modification may be needed for
a Windows-based server.
References:
- Source for ServerSocket.py (standard Python module)
- Source for BaseHTTPServer.py (standard Python module)
- http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
"""
version = '1.0'
usage = """usage: %prog [options] command [arg...]
commands:
start start the server daemon
stop stop the server daemon
status return server daemon status
echo server echoes arguments
add A B return A+B
Example session:
%prog start # starts daemon
%prog status # print daemon's status
%prog add 15 8 # prints "15 + 8 = 23"
%prog stop # stops daemon"""
import SocketServer
import optparse
import os
import os.path
import resource
import socket
import sys
import tempfile
import time
# We can use either a TCPServer or a UnixStreamServer (assuming the OS
# supports UNIX domain sockets). We just need to define the
# appropriate ServerBase class and then customize a few things based
# upon which base we're using.
#ServerBase = SocketServer.TCPServer
ServerBase = SocketServer.UnixStreamServer
if ServerBase == SocketServer.TCPServer:
# TODO: replace with appropriate port number
server_address = ('', 54545)
elif ServerBase == SocketServer.UnixStreamServer:
# TODO: replace with appropriate socket file path
server_address = os.path.join(tempfile.gettempdir(), 'server_socket')
# Path to log file
# TODO: Change to appropriate path and name
server_log = os.path.join(tempfile.gettempdir(), 'server.log')
class RequestHandler(SocketServer.StreamRequestHandler):
"""Request handler
An instance of this class is created for each connection made
by a client. The Server class invokes the instance's
setup(), handle(), and finish() methods.
The template implementation here simply reads a single line from
the client, breaks that up into whitespace-delimited words, and
then uses the first word as the name of a "command." If there is
a method called "do_COMMAND", where COMMAND matches the
commmand name, then that method is invoked. Otherwise, an error
message is returned to the client.
"""
def handle(self):
"""Service a newly connected client.
The socket can be accessed as 'self.connection'. 'self.rfile'
can be used to read from the socket using a file interface,
and 'self.wfile' can be used to write to the socket using a
file interface.
When this method returns, the connection will be closed.
"""
# Read a single request from the input stream and process it.
# TODO: Change as needed for actual client-server protocol.
request = self.rfile.readline()
if request:
self.server.log('request %s: %s',
self.connection.getpeername(), request.rstrip())
try:
self.process_request(request)
except Exception, e:
self.server.log('exception: %s' % str(e))
self.wfile.write('Error: %s\n' % str(e))
else:
self.server.log('error: unable to read request')
self.wfile.write('Error: unable to read request')
def process_request(self, request):
"""Process a request.
This method is called by self.handle() for each request it
reads from the input stream.
This implementation simply breaks the request string into
words, and searches for a method named 'do_COMMAND',
where COMMAND is the first word. If found, that method is
invoked and remaining words are passed as arguments.
Otherwise, an error is returned to the client.
"""
words = request.split()
if len(words) == 0:
self.server.log('error: empty request')
self.wfile.write('Error: empty request\n')
return
command = words[0]
args = words[1:]
methodname = 'do_' + command
if not hasattr(self, methodname):
self.server.log('error: invalid command')
self.wfile.write('Error: "%s" is not a valid command\n' % command)
return
method = getattr(self, methodname)
method(*args)
def do_stop(self, *args):
"""Process a 'stop' command"""
self.wfile.write('Stopping server\n')
self.server.stop()
def do_echo(self, *args):
"""Process an 'echo' command"""
self.wfile.write(' '.join(args) + '\n')
def do_status(self, *args):
"""Process a 'status' command"""
self.wfile.write('Server Version: %s\n' % version)
self.wfile.write('Process ID: %d\n' % os.getpid())
self.wfile.write('Parent Process ID: %d\n' % os.getppid())
self.wfile.write('Server Socket: %s\n' % str(server_address))
self.wfile.write('Server Log: %s\n' % server_log)
def do_add(self, a, b):
"""Process an 'add' command"""
answer = int(a) + int(b)
self.wfile.write('%s + %s = %s\n' % (a, b, answer))
class Server(ServerBase):
"""Server implementation
"""
def __init__(self, server_address):
"""Constructor"""
self.__daemonize()
if ServerBase == SocketServer.UnixStreamServer:
# Delete the socket file if it already exists
if os.access(server_address, 0):
os.remove(server_address)
ServerBase.__init__(self, server_address, RequestHandler)
def log(self, format, *args):
"""Write a message to the server log file"""
try:
message = format % args
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
f = open(server_log, 'a+')
f.write('%s %s\n' % (timestamp, message))
f.close()
except Exception, e:
print str(e)
def serve_until_stopped(self):
"""Serve requests until self.stop() is called.
This is an alternative to BaseServer.serve_forever()
"""
self.log('started')
self.__stopped = False
while not self.__stopped:
self.handle_request()
self.log('stopped')
def stop(self):
"""Stop handling requests.
Calling this causes the server to drop out of
serve_until_stopped().
"""
self.__stopped = True
def __daemonize(self):
"""Create daemon process.
Based upon recipe provided at
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
"""
UMASK = 0
WORKDIR = '/'
MAXFD = 1024
if hasattr(os, 'devnull'):
REDIRECT_TO = os.devnull
else:
REDIRECT_TO = '/dev/null'
try :
if os.fork() != 0:
os._exit(0)
os.setsid()
if os.fork() != 0:
os._exit(0)
os.chdir(WORKDIR)
os.umask(UMASK)
except OSError, e:
self.log('exception: %s %s', e.strerror, e.errno)
raise Exception, "%s [%d]" % (e.strerror, e.errno)
except Exception, e:
self.log('exception: %s', str(e))
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
if maxfd == resource.RLIM_INFINITY:
maxfd = MAXFD
for fd in range(0, maxfd):
try:
os.close(fd)
except OSError:
pass
os.open(REDIRECT_TO, os.O_RDWR)
os.dup2(0, 1)
os.dup2(0, 2)
def run_server(options, args):
"""Run a server daemon in the current process."""
svr = Server(server_address)
svr.serve_until_stopped()
svr.server_close()
def do_request(options, args):
"""Send request to the server and process response."""
if ServerBase == SocketServer.UnixStreamServer:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
elif ServerBase == SocketServer.TCPServer:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Send request
# TODO: Change as needed for actual client-server protocol
s.connect(server_address)
s.sendall(' '.join(args) + '\n')
# Print response
# TODO: Change as needed for actual client-server protocol
sfile = s.makefile('rb')
line = sfile.readline()
while line:
print line,
line = sfile.readline()
#
# MAIN
#
if __name__ == '__main__':
optparser = optparse.OptionParser(usage=usage,
version=version)
(options, args) = optparser.parse_args()
if len(args) == 0:
optparser.print_help()
sys.exit(-1)
if args[0] == 'start':
run_server(options, args[1:])
else:
do_request(options, args)
</pre>Screwed E-Mail2007-08-03T23:18:00-04:002007-08-03T23:18:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-08-03:/screwed-e-mail.html<p>
I have my own domain that I've been using for e-mail for the past few years. Most of my e-mail goes there, and then gets forwarded to a Yahoo! mail account, which in turn gets downloaded to my Macbook. This gives me a couple of nice features: I can read …</p><p>
I have my own domain that I've been using for e-mail for the past few years. Most of my e-mail goes there, and then gets forwarded to a Yahoo! mail account, which in turn gets downloaded to my Macbook. This gives me a couple of nice features: I can read everything in the nice Mac Mail app when I'm at home, but I can also access everything via the web when I'm elsewhere.
</p>
<p>
Another nice feature was that I had the mail server configured such that <i>anything</i>@mydomain.com would get to me. This made it easy to essentially create new e-mail accounts whenever I needed a new one. For example, if Microsoft wants me to register for something, I'd give them "microsoft@mydomain.com" as my e-mail address. Since every website in the world wants my e-mail address, I figured that doing this would give me a way to create a different e-mail address for everybody who needs to contact me, making it easy to filter out stuff that I didn't want.
</p>
<p>
This worked great, until today. Today, my hosting provider, A2 Hosting, decided to disable the feature that automatically forwards everything to one place. Now, I need to create accounts or forwarders for every single address that I'd like to handle.
</p>
<p>
The problem is that I've been doing this for a long time, and I don't have a list of all the addresses I've used. Every business and every website I've interacted with in the past couple of years will no longer be able to contact me, unless I can remember them. So, now I'm not going to be getting a lot of e-mails that are important to me. I'm screwed.
</p>
<p>
The provider disabled this feature due to spam. It is too easy for spammers to just randomly generate e-mail addresses for every domain, and there is a cost to the provider for every e-mail they forward. I do sympathize, but it doesn't change the fact that I am now screwed by their policy change.
</p>
<p>
So, I think I need to find another hosting provider, so I can start getting e-mail again. Anybody out there happy with theirs?
</p>
<p>
Alternatively, I suppose I could write a script to find all the e-mail addresses for which I've received mail for the past couple of years. Switching providers sounds a lot simpler.
</p>New HBO Series2007-07-30T07:47:00-04:002007-07-30T07:47:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-07-30:/new-hbo-series.html<p>
HBO has a couple of new series that I predict will be cancelled at the end of their first season. Maybe if more people watch them, that won't happen, so I want to do my part to bring attention to them.
</p>
<p>
<em>John from Cincinnati</em> is weird, but has the best …</p><p>
HBO has a couple of new series that I predict will be cancelled at the end of their first season. Maybe if more people watch them, that won't happen, so I want to do my part to bring attention to them.
</p>
<p>
<em>John from Cincinnati</em> is weird, but has the best opening credits of any series ever. I'll watch anything that has Rebecca DeMornay in it, but this one seems special. I don't know what's going on, but I trust that David Milch (the genius behind <em>Deadwood</em>) will eventually deliver. I am determined to learn how to surf before I get too old.
</p>
<p>
<em>Flight of the Conchords</em> is incredibly funny. I like how the "music videos" spontaneously arise during each episode.
</p>
<p>
Please watch them. If you don't like them, then you and I have radically different worldviews.
</p>
<p></p>
<em>Update (2009/6/10): Well, I was right about John from Cincinnati: cancelled after one season. Flight of the Conchords lasted two seasons.</em>
</p></p>MacBook Display Problem2007-07-16T12:12:00-04:002007-07-16T12:12:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-07-16:/macbook-display-problem.html<div style="float: right; margin-left: 10px; margin-bottom: 10px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/825122256/" title="photo sharing"><img src="http://farm2.static.flickr.com/1435/825122256_f0dd95f25a_m.jpg" alt="Screenshot" style="border: solid 2px #000000;" /></a> <br /> <span style="font-size: 0.9em; margin-top: 0px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/825122256/">MacBook Display Problem</a> <br /> Originally uploaded by <a href="http://www.flickr.com/people/kristopherjohnson/">kristopherjohnson</a> </span></div>
<p>
A couple of weeks ago, my Macbook's display started "flickering." It seemed like I was getting some electromagnetic interference, so I didn't worry much at first. But, it got progressively worse, and now the screen is unreadable.
</p>
<p>
I took it in to the …</p><div style="float: right; margin-left: 10px; margin-bottom: 10px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/825122256/" title="photo sharing"><img src="http://farm2.static.flickr.com/1435/825122256_f0dd95f25a_m.jpg" alt="Screenshot" style="border: solid 2px #000000;" /></a> <br /> <span style="font-size: 0.9em; margin-top: 0px;"> <a href="http://www.flickr.com/photos/kristopherjohnson/825122256/">MacBook Display Problem</a> <br /> Originally uploaded by <a href="http://www.flickr.com/people/kristopherjohnson/">kristopherjohnson</a> </span></div>
<p>
A couple of weeks ago, my Macbook's display started "flickering." It seemed like I was getting some electromagnetic interference, so I didn't worry much at first. But, it got progressively worse, and now the screen is unreadable.
</p>
<p>
I took it in to the local Apple Genius Bar, and they took it in for repair. I should have it back in a week or less.
</p>
<p>
I'm glad I have a Genius Bar nearby, considering all the problems I've been having with recent Apple products. I've been using computers for almost thirty years, but have never needed repairs or service until I bought the iMac G5 and the Macbook.
</p>
<p>
<em>UPDATE 2007/07/19:</em> Got the Macbook back, and it's working fine now. According to the service form, the following parts were replaced:
</p>
<pre>
630-7691 PCB, MLB, 2.0GHZ DC, SMS/KIONI
607-0199 ASSY, HEATSINK, M42(1.0mmConnect
605-0994 SVC, TOP CASE W
646-0302 DSPL 13.3 GLOSSY AUO
</pre>Improving SCons Performance for MSVC82007-07-14T05:47:00-04:002007-07-14T05:47:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-07-14:/improving-scons-performance-msvc8.html<p>
The developers of <a href="http://www.scons.org">SCons</a> don't seem to be very interested in this, but I've found a way to dramatically speed up SCons builds for MSVC8 (Visual Studio 2005's C++ compiler).
</p>
<p><pp>
We've got a fairly big codebase with a few levels. It was taking over a minute to read all the …</pp></p><p>
The developers of <a href="http://www.scons.org">SCons</a> don't seem to be very interested in this, but I've found a way to dramatically speed up SCons builds for MSVC8 (Visual Studio 2005's C++ compiler).
</p>
<p><pp>
We've got a fairly big codebase with a few levels. It was taking over a minute to read all the SConstruct/SConscript files, even when there was nothing to do.
</p></p>
<p>
I ran the profiler, and found that the bulk of the time was in minidom.py and expatbuilder.py. This was surprising, because I didn't think SCons used XML.
<p>
Searching further, it turns out that for MSVC8, to determine library and include paths SCons opens a registry key which contains XML, and parses it. For our codebase, it was doing this about 300 times per build.
</p>
<p>
So, I hacked up my personal copy of SCons/Tool/msvc.py, and now instead of over a minute, it only takes 20 seconds. I don't consider this a "patch", because I don't really know much about SCons internals, and so this could be totally wrong, but maybe someone can figure out the right way to do what I have done and get it into CVS.
</p>
<p>
The idea is to cache the results of _get_msvc8_path, so that the XML parsing doesn't happen every time. I added a global variable to msvc.py, containing an empty dictionary:
</p>
<pre>
# START NEW CODE
# KDJ: cache results of _get_msvc8_path in a dictionary
cached_msvc8_path = {}
# END NEW CODE
</pre>
<p>
Then, I changed a few lines in get_msvc_path as follows:
</p>
<pre>
if version_num >= 8.0:
# ORIGINAL: return _get_msvc8_path(path, str(version_num), platform, suite)
# START NEW CODE
global cached_msvc8_path
if not cached_msvc8_path.has_key(path):
cached_msvc8_path[path] = _get_msvc8_path(path, str(version_num), platform, suite)
return cached_msvc8_path[path]
# END NEW CODE
elif version_num >= 7.0:
return _get_msvc7_path(path, str(version_num), platform)
</pre>Business Opportunities2007-07-10T03:32:00-04:002007-07-10T03:32:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-07-10:/business-opportunities.html<p>
Today I was presented with a business opportunity. I know a guy who knows a guy who needs some software written, but knows nothing about software, and I know nothing about how do do what he needs done.
</p>
<p>
Unfortunately, I am the kind of guy who, when presented with a …</p><p>
Today I was presented with a business opportunity. I know a guy who knows a guy who needs some software written, but knows nothing about software, and I know nothing about how do do what he needs done.
</p>
<p>
Unfortunately, I am the kind of guy who, when presented with a bunch of stuff he has no idea how to do, says "I have no idea how to do that stuff. You should talk to somebody else."
</p>
<p>
This is why I will never be rich. The really rich guys are the ones who say "Sure, we know how to do that," and then charge $150/hour until the client runs out of money.
</p>Kristopher Johnson Photography2007-07-06T00:58:00-04:002007-07-06T00:58:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-07-06:/kristopher-johnson-photography.html<p>
I've been made aware of "Kristopher Johnson Photography" (<a href="http://www.kristopherjohnsonphotography.com">http://www.kristopherjohnsonphotography.com</a>). I'm not that Kristopher Johnson, that's not my web site, and those aren't my pictures. (His are better than mine.)
</p>
<p>
My pictures are available on Flickr: <a href="http://www.flickr.com/photos/kristopherjohnson/">http://www.flickr.com/photos/kristopherjohnson/</a>
</p>Funny Pictures of Photographers2007-07-05T13:24:00-04:002007-07-05T13:24:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-07-05:/funny-pictures-photographers.html<p>These pictures may only be interesting to other photographers. A couple of these pictures are a little risqué, so beware of who might be looking over your shoulder.</p>
<p><a href="http://legko.be/index.php?option=com_content&task=view&id=6053&Itemid=1">http://legko.be/index.php?option=com_content&task=view&id=6053&Itemid=1</a></p>Peachtree Road Race 20072007-07-04T17:51:00-04:002007-07-04T17:51:00-04:00Kristopher Johnsontag:undefinedvalue.com,2007-07-04:/peachtree-road-race-2007.html<div style="float: right; margin-left: 10px; margin-bottom: 10px;">
<a href="http://www.flickr.com/photos/kristopherjohnson/717519883/" title="Photo Sharing"><img src="http://farm2.static.flickr.com/1086/717519883_2f6ab8c000_m.jpg" width="240" height="240" alt="Peachtree Road Race T-Shirt" /></a>
</div>
<p>
What kind of idiot gets up at 5:00 AM on a national holiday to go run six miles in the heat? I'm that kind of idiot, and today I joined about 55,000 other such idiots in the <a href="http://en.wikipedia.org/wiki/Peachtree_Road_Race">Peachtree Road Race</a>.
</p>
<p>
My goal was to run the full course …</p><div style="float: right; margin-left: 10px; margin-bottom: 10px;">
<a href="http://www.flickr.com/photos/kristopherjohnson/717519883/" title="Photo Sharing"><img src="http://farm2.static.flickr.com/1086/717519883_2f6ab8c000_m.jpg" width="240" height="240" alt="Peachtree Road Race T-Shirt" /></a>
</div>
<p>
What kind of idiot gets up at 5:00 AM on a national holiday to go run six miles in the heat? I'm that kind of idiot, and today I joined about 55,000 other such idiots in the <a href="http://en.wikipedia.org/wiki/Peachtree_Road_Race">Peachtree Road Race</a>.
</p>
<p>
My goal was to run the full course, without slowing to a walk. This goal turned out to be unrealistic. I ran the first three miles without a problem, but then I reached "Cardiac Hill," a long uphill stretch so-named for the obvious reason, and also because there is a hospital conveniently located next to it. About halfway up the hill, I started feeling dizzy, and I decided it was better to abandon the goal than to collapse.
</p>
<p>
After clearing the top of the hill, I set a new goal: run the remainder of the course. That didn't work either; after a half mile or so I again started feeling queasy.
</p>
<p>
So, I set yet another goal: walk to the five-mile marker, and then run the last 1.2-mile stretch to the finish. I accomplished that.
</p>
<p>
I didn't really run a 10K today. I ran a 5K and a 2K, with a 3K walk in between. My time from start to finish was 69 minutes, 28 seconds. I expect to do better next year.
</p>
<p>
I'm not disappointed in my performance. This was my first time. I don't have any steep hills in the area where I usually run, so I wasn't prepared for Cardiac Hill. The course is tricky, because the first half is mostly downhill, giving you a false sense of optimism, and then the second half is mostly uphill.
</p>
<p>
After finishing at Piedmont Park, it is a half-hour walk to get back to the MARTA train station. That's something they don't point out in the informational handouts. I would have rested for a while in the park before starting the walk if I'd known.
</p>
<p>
So anyway, now I have the "coveted Peachtree Road Race t-shirt." It makes it all worthwhile.
</p>
<p>
I was impressed with how well organized the event was. Getting 55,000 into the starting area, lining them up, giving them water and t-shirts, and getting them back home seems to me like a major undertaking, but I never felt crowded, delayed, or unsure of where I was supposed to go and what I was supposed to do. MARTA, the city police, the Atlanta Track Club, and other involved parties seem to really have their @#$% together for this event.
</p>
<p>
I didn't take my camera with me. There was one shot I really wanted to take: a wide-angle shot of hundreds of runners lined up at the banks of porta-potties, taken from above the parking lot. Maybe next year . . . .
</p>402007-01-14T15:54:00-05:002007-01-14T15:54:00-05:00Kristopher Johnsontag:undefinedvalue.com,2007-01-14:/40.html<p>A few weeks ago, I was talking to an older gentleman at a golf course, and I mentioned my 40th birthday was coming up.</p>
<p>He smiled and asked "It went by quick, didn't it?"</p>
<p>Yes, it did.</p>De Re Atari2006-09-04T22:29:00-04:002006-09-04T22:29:00-04:00Kristopher Johnsontag:undefinedvalue.com,2006-09-04:/de-re-atari.html<p>
People sometimes ask me where I learned programming. I learned long before going to college. Most of my formative years were spent reading books that are now available at <a href="http://www.atariarchives.org/">atariarchives.org</a>. </p>
<p>
It's a lot of fun looking through those books now. I remember holding the paper versions in my hands …</p><p>
People sometimes ask me where I learned programming. I learned long before going to college. Most of my formative years were spent reading books that are now available at <a href="http://www.atariarchives.org/">atariarchives.org</a>. </p>
<p>
It's a lot of fun looking through those books now. I remember holding the paper versions in my hands, and typing the programs into my Atari 800. </p>
<p>
My favorite among the books is <i><a href="http://www.atariarchives.org/dere/">De Re Atari: A Guide to Effective Programming</a></i> by Chris Crawford, Lane Winner, Jim Cox, Amy Chen, Jim Dunion, Kathleen Pitta, Bob Fraser, and Gus Makrea. I spent a lot of time studying this book, learning all the ways to do cool stuff with the Atari 8-bit hardware. It also contains a lot of advice about how to design software, and most of that advice is still applicable today. </p>
<p>
Those were the good old days, when developing software for personal computers required knowledge of hardware registers, assembly language, memory layouts, and so on. Today, we hide the details of the hardware as much as possible, but I think having experience with such low-level stuff is beneficial. If you're a young whippersnapper who has only used high-level APIs like DirectX, OpenGL, and Windows GDI to do graphics, I'd recommend paging though some of these old books to see how programmers used to do things. </p>
<p>
One last thing: Atari rules, Commodore sucks. </p>392006-01-20T11:39:00-05:002006-01-20T11:39:00-05:00Kristopher Johnsontag:undefinedvalue.com,2006-01-20:/39.html<p>I celebrated my 39th birthday this week, if the term "celebrate" can mean "sit alone at home and watch TV just like every other night." Because my birthday is in January, I do Birthday Resolutions instead of New Year's Resolutions. After all, that gives me a couple more weeks to …</p><p>I celebrated my 39th birthday this week, if the term "celebrate" can mean "sit alone at home and watch TV just like every other night." Because my birthday is in January, I do Birthday Resolutions instead of New Year's Resolutions. After all, that gives me a couple more weeks to figure out what it is I want to do with the coming year.</p>
<p>I've got one year to do all the things I want to do before I turn 40. But there's nothing I want to do. Yeah, I'd like to get into better shape, be smarter with my money, clean out my closets, find a better job, and the other typical stuff, but I know that none of these wants are engaging enough to hold my attention for a year.</p>
<p>So, I really don't have anything to write about, but I wanted to record this so that I can look at it next year and be comforted that I didn't fail to accomplish any of my goals.</p>
<p>I know some people will probably wish me a Happy Birthday, let me wish you all a Happy Birthday as well, whenever it is.</p>No2005-12-16T10:40:00-05:002005-12-16T10:40:00-05:00Kristopher Johnsontag:undefinedvalue.com,2005-12-16:/no.html<p><em>Kris, the production system isn't working since we installed the newest release. Can you tell us how to fix it?</em></p>
<p>Can you send me the log files?</p>
<p><em>No. They are too big.</em></p>
<p>Will you let me access the system myself to see what's going wrong?</p>
<p><em>No. Developers can't touch the …</em></p><p><em>Kris, the production system isn't working since we installed the newest release. Can you tell us how to fix it?</em></p>
<p>Can you send me the log files?</p>
<p><em>No. They are too big.</em></p>
<p>Will you let me access the system myself to see what's going wrong?</p>
<p><em>No. Developers can't touch the production system.</em></p>
<p>OK, can I access the test system to see if it exhibits the same behavior as the production system?</p>
<p><em>No. The test system has been dismantled.</em></p>
<p>Did you test the new release before putting it into production?</p>
<p><em>No. There wasn't time.</em></p>
<p>Did you install the server-side updates?</p>
<p><em>No. The IT manager won't allow installation of any server updates.</em></p>
<p>Other than installing the new version, did you keep everything else the same?</p>
<p><em>No, we changed a few other things too. Can't remember exactly what.</em></p>
<p>Are you sure this "new" problem never occurred with the previous release?</p>
<p><em>No. Maybe.</em></p>
<p>Can you roll back to the previous release?</p>
<p><em>No. We blew away the old data after converting it to the new format.</em></p>
<p><em>Well, Kris, can you tell us how to fix it?</em></p>My First AppleScripts2004-11-11T06:07:00-05:002004-11-11T06:07:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-11-11:/my-first-applescripts.html<p>OK, they aren't really my first AppleScripts. I used AppleScript a lot
back in the Mac OS 7 days, but unfortunately most of the applications I
used back then didn't support it very well. I was hoping things would be
better now, but they really aren't. Apple's applications have reasonable …</p><p>OK, they aren't really my first AppleScripts. I used AppleScript a lot
back in the Mac OS 7 days, but unfortunately most of the applications I
used back then didn't support it very well. I was hoping things would be
better now, but they really aren't. Apple's applications have reasonable
support, but many third parties' applications do not.</p>
<p>Case in point: Mozilla Firefox 1.0. Released this week, I read several
Mac users' comments that we finally have a good browser for Mac OS. On
Windows, I prefer Firefox to IE, but on Mac, it is not as good as
OmniWeb, and is not better than Safari. What really bugs me about
Firefox is the lack of support for basic Mac features, especially
AppleScript.</p>
<p>But anyway, I found a need for a simple AppleScript today. I use
<a href="http://www.shrook.com">Shrook</a> as my RSS reader. Shrook is nice, but is
still not very polished. I was trying to download Adam Curry's <a href="http://www.dailysourcecode.com">Daily Source Code</a> MP3 from the Shrook window,
but the "Download Linked File" item on the context menu had no effect.
So I opened the page in OmniWeb, but OmniWeb wanted to download the
whole file and then play it in an ugly embedded QuickTime viewer. What I
really wanted to do was get iTunes to stream it, but didn't want to go
through all the hoo-hah of opening the URL in iTunes.</p>
<p>I haven't used AppleScript for years, so I spent a long while looking at
various examples on the web and experimenting with different concepts. I
finally came up with a script that would let me copy the MP3 URL to the
clipboard, and then select "Open Clipboard Location in iTunes" from my
Script Menu. Here's the script, which I saved as "Open Clipboard
Location in iTunes.scpt" in <code>~/Library/Scripts</code>:</p>
<div class="highlight"><pre><span></span><code><span class="k">tell</span> <span class="nb">application</span> <span class="s2">"iTunes"</span>
<span class="k">set</span> <span class="nv">theLocation</span> <span class="k">to</span> <span class="p">(</span><span class="nb">the clipboard</span> <span class="k">as </span><span class="nc">text</span><span class="p">)</span>
<span class="nb">open location</span> <span class="nv">theLocation</span>
<span class="k">end</span> <span class="k">tell</span>
</code></pre></div>
<p>Very simple, like it should be.</p>
<p>After that little exercise, I decided I'd like a similar script that
would open a URL from the clipboard in a new tab in OmniWeb. OmniWeb has
the richest AppleScript support of all the Mac browsers I've seen.
Here's the script:</p>
<div class="highlight"><pre><span></span><code><span class="k">tell</span> <span class="nb">application</span> <span class="s2">"OmniWeb"</span>
<span class="nb">activate</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">count</span> <span class="nb">browsers</span><span class="p">)</span> <span class="o"><</span> <span class="mi">1</span> <span class="k">then</span>
<span class="nb">make</span> <span class="nb">new</span> <span class="nb">browser</span> <span class="nb">at</span> <span class="nb">front of</span> <span class="nb">browsers</span>
<span class="k">end</span> <span class="k">if</span>
<span class="k">set</span> <span class="nv">theUrl</span> <span class="k">to</span> <span class="p">(</span><span class="nb">the clipboard</span> <span class="k">as </span><span class="nc">text</span><span class="p">)</span>
<span class="k">tell</span> <span class="nb">front</span> <span class="nb">browser</span>
<span class="k">set</span> <span class="nv">theNewTab</span> <span class="k">to</span> <span class="p">(</span><span class="nb">make</span> <span class="nb">new</span> <span class="no">tab</span> <span class="nb">at</span> <span class="k">end</span> <span class="k">of</span> <span class="nv">tabs</span> <span class="nv">with</span> <span class="na">properties</span> <span class="p">{</span><span class="nv">address</span><span class="p">:</span><span class="nv">theUrl</span><span class="p">})</span>
<span class="k">set</span> <span class="na">active</span> <span class="no">tab</span> <span class="k">to</span> <span class="nv">theNewTab</span>
<span class="k">end</span> <span class="k">tell</span>
<span class="k">end</span> <span class="k">tell</span>
</code></pre></div>Name Taken2004-11-10T05:21:00-05:002004-11-10T05:21:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-11-10:/name-taken.html<p>It turns out that there is already an alarm clock utility named
"Rouser": <a href="http://www.pcmag.com/article2/0,1759,1654716,00.asp">http://www.pcmag.com/article2/0,1759,1654716,00.asp</a>.</p>
<p>So the search for a name for my alarm clock has to continue.</p>Looping a Helicopter2004-11-07T23:50:00-05:002004-11-07T23:50:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-11-07:/looping-a-helicopter.html<p>In the movie <em>Blue Thunder</em>, one of the plot devices used to build up
the mystique of Roy Scheider's character is his claim to have looped a
helicopter, which everyone else says is impossible. For years, I
believed looping a helicopter must be impossible (movies are all true,
aren't they …</p><p>In the movie <em>Blue Thunder</em>, one of the plot devices used to build up
the mystique of Roy Scheider's character is his claim to have looped a
helicopter, which everyone else says is impossible. For years, I
believed looping a helicopter must be impossible (movies are all true,
aren't they?), but it's not. When I got involved in RC helicopters, I
was amazed to see model helicopters doing loops, rolls, inverted flight,
and all sorts of other "impossible" maneuvers.</p>
<p>For an example, see <a href="http://www.augustoheli.com/videostuff/videoarchive/AlanSzabo/alan_vegasfunflydemo_2004.zip">this video</a>.
If you've never seen something like this before, you'll think you're
watching a special effects demo reel. Laws of physics are seemingly
violated, but it's all real. Full-size helicopters are capable of these
feats too, if they are rigged the right way. I suspect that when people
report seeing aircraft doing 90-degree turns over Area 51, they're not
seeing alien spacecraft: they're watching experimental helicopters.
Helicopter aerobatics make the most sophisticated airplane aerobatics
look boring.</p>
<p>Of course, I've wanted to do these maneuvers with my own RC helicopter.
The first helicopter I bought, the Ikarus Piccolo, was incapable of such
maneuvers. My current helicopter, the Hornet II, is capable. The
difference is that the Hornet features <em>collective pitch control</em>. The
thrust produced by a helicopter's rotor blades is dependent on two
things: the speed at which the rotor is turning, known as the <em>head
speed</em> and controlled by the throttle, and the angle of attack of the
blades with the air, known as the <em>blade pitch</em>. These throttle and
blade-pitch controls are known as the <em>collective</em>. As the head speed
increases, the lift produced by the blades increases, just like an
airplane's wing increases lift as its speed increases. Increasing the
angle of attack also increases lift. The Piccolo has fixed-pitch rotor
blades, meaning that the angle of attack never changes, so lift is
determined solely by the throttle. However, the Hornet can control both
the throttle and the pitch, providing for a greater degree of control
over lift. Collective pitch also provides quicker response, as changing
the pitch of blades can happen almost instantaneously, but speeding up
or slowing down the head speed takes some time.</p>
<p>To fly inverted (upside down), a helicopter pilot adjusts the blade
pitch so that it is opposite of normal blade pitch. This is called
<em>negative pitch</em>. Most real-world helicopters provide some degree of
negative pitch, which is needed for certain high-performance maneuvers
and for landing in high winds. However, real-world helicopters do not
provide enough negative pitch to hover or fly inverted. They could be
rigged for more negative pitch, but manufacturers do not want the
liability associated with certifying their helicopters for aerobatic
flight. In contrast, RC helicopters with collective pitch usually are
capable of inverted flight.</p>
<p>I first tried loops and rolls a few months ago, without much success. I
was able to roll successfully a few times, but I also crashed a few
times. My attempts to loop never worked out: I would end up with a messy
flip and recovery, or I would end up walking across the field to pick up
the pieces. The problem was that I didn't have enough <em>cyclic</em> power,
which is what controls left-right rolling and forward-backward pitch.
The helicopter just wouldn't turn over fast enough to get back to level
flight before hitting the ground.</p>
<p>I read of two potential fixes to this problem. The first fix was a
higher head speed. Higher head speeds increase the responsiveness of the
helicopter, but also make it more jumpy and difficult to control. The
second fix was addition of a Bell-Hiller mixer, which increases the
power of the cyclic, but also makes the helicopter more jumpy and
difficult to control. To smooth out the additional jumpiness, I added
weighted rotor blades, which stabilize the helicopter by increasing the
gyroscopic effect of the rotor.</p>
<p>I made these changes two months ago, but hadn't been able to try them
out. We had a few hurricanes blow through, which really screwed up our
weather for a few weeks. Then work ate up all my free time. Last week, I
finally tried it out. I was rusty, so I just did some simple hovering
and a few figure-eights. It was definitely a lot more responsive to the
controls, so much so that I had difficulty keeping it under control. i
did manage to keep it in the air for thirty minutes, and take it home
intact.</p>
<p>During the week, a made a few adjustments to my transmitter. I made the
cyclic control less sensitive and increased rudder sensitivity (I had
noticed difficulty getting the heli to turn sharply). I also did a lot
of practicing with the Reflex XTR simulator.</p>
<p>Today, I gave it another try. The air was calm when I packed up and got
in my car, but by the time I arrived at the flying site, there was a 10
MPH wind. Undeterred, I lifted off and did some easy flying for a while.
The wind was strong, but steady, so I figured I could try some crazy
stuff. I flew into the wind, went into a climb, applied negative pitch
as it went over the top of the loop, then leveled out. It wasn't pretty,
but it was a loop. I did a few more. None of them were pretty, and the
wind was too strong for me to know how they would look in steady wind,
but it was a lot easier than I expected. To tempt fate, I also did a
couple of rolls, and they were without incident.</p>
<p>So now I officially classify myself as an "intermediate" RC helicopter
pilot. Whenever we have a calm day, I'll try some inverted hovering,
which is what the Real Men do.</p>
<p>[UPDATE: Later in the day, I did my inverted hovering and a few more
loops and rolls. Unfortunately, I crashed during a simple right-side-up
hovering maneuver. I'm now waiting for $85 worth of replacement parts
before I can try again.]</p>Work, Stress, and Goofing Off2004-11-07T05:09:00-05:002004-11-07T05:09:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-11-07:/work-stress-and-goofing-off.html<p>Well, we have our software "finished", and it's a full week ahead of the
deadline. I expect smooth sailing for the next week or so, which will
finally give me some time to get back into a normal work schedule and to
focus on some longer-term goals instead of the …</p><p>Well, we have our software "finished", and it's a full week ahead of the
deadline. I expect smooth sailing for the next week or so, which will
finally give me some time to get back into a normal work schedule and to
focus on some longer-term goals instead of the crisis-of-the-hour.</p>
<p>The last couple of weeks have been very stressful and tiring. I've been
working long hours, and I've been working to solve many last-minute
problems. I haven't taken it all in stride: I've lost my temper a couple
of times, and I'm constantly thinking about just quitting without
notice. However, I haven't fallen apart, and I have been detached enough
to reflect a bit on how I react to stress.</p>
<p>I think I prefer to handle stress in a different way from most people.
Most people, it seems to me, try to handle stressful situations through
escapism. That is, they try to psychologically distance themselves from
the stress by distracting themselves with unrelated activities. They
play ping-pong, they go for a run during lunch, they watch DVDs, they do
anything they can to temporarily forget about the source of stress. This
is popularly known as "blowing off steam", and most people think it
works for them.</p>
<p>I am different. When confronted with a stressful situation, my reaction
is to focus intensely on whatever the problem is until it is solved.
Instead of distracting myself with other things, I want no distractions
whatsoever. By focusing on one particular problem, I can temporarily
forget about every other problem in my life.</p>
<p>Because of my intense desire to eliminate the problem, I see "blowing
off steam" as a waste of time. Every minute I spend doing something
other than my work is another minute that I have to worry about the
problem. I'm sure many people would see this as an unhealthy outlook,
but I just can't make myself ignore a serious problem.</p>
<p>I don't think there is anything wrong with my way of dealing with
stress, but it does seem to be incompatible with everybody else's way.
When things get rough, I want to eliminate every distraction. I want to
close my door. I want people to leave me alone. I don't want to talk to
anyone about matters not related to work. I want to go into work very
early and leave very late because I value the quiet of the early morning
and late evening hours. When I need to leave my office, I walk very
quickly and avoid eye contact with people because I have something to do
and I don't want anyone to interrupt me. I don't want to commiserate
with others in the same situation; I just want us all to do our jobs.</p>
<p>My desire for focus is so strong that it bothers me when people just
want to say "Hi, Kris". It's even worse when people start asking "What's
wrong?", "Why are you working so hard?", "Do you need any help?", or
suggesting that I need to chill out, go home early, or take a few days
off. They don't understand that locking myself in my office and working
actually makes me feel much better than I would if I was trying to blow
off steam the way that they do. They just think I'm wound up really
tight and that I'm about to snap.</p>
<p>Unfortunately, I don't know a polite way to ask people to let me do my
work in peace. Avoiding idle chit-chat is considered to be rude.
Responding with "no, I'm busy" every time I'm asked whether I want to go
to lunch, play a game, or go to an after-work party is leading my
friends and colleagues to think I don't like them anymore. Keeping my
office door shut seems to be the only socially acceptable way to prevent
people from interrupting me, but that makes me physically uncomfortable
due to the heat generated by all the equipment in my office.</p>
<p>A benefit of my way of handling stress is that I really don't have to
work very long hours. I have a couple of intense two-or-three-hour work
sessions a day (early morning and after hours), with some milder hours
in the middle of the day while other people are in the office and need
my attention. There is no sense in working really late, because my
intense focus causes my mind to turn to mush by the end of the day and
so I go home at 8 or 9 o'clock at the latest. In contrast, I see other
people working 12- or 14-hour days, with lots of ping-pong, video games,
and pizza breaks to keep them from losing their minds. For the same
reason, I never work more than a few hours over the weekend, no matter
how much work I think I have to have done by Monday morning. In
contrast, I know a few "relaxed" people who work seven days a week.</p>
<p>A took a year off from work a little while ago. During that time, I was
much happier, and I thought I had learned how to relax under any
circumstances. I now understand that when I have work to do, I just
can't relax. The best way for me to deal with stress is to work hard and
then go home when I'm done.</p>
<p>Some managers pride themselves on the fact that they've made their
offices "fun" places. That is, they let people goof off as much as they
want, as long as the work eventually gets done. For people unlike me, I
guess that's a good thing. But for me, a "fun" place to work would be
one where everybody focuses on work all the time at the office, but then
everybody goes home at 5 o'clock and never has to work on Saturday or
Sunday. Some people think work can be fun, but I think having one's work
finished is a lot more fun.</p>More of the Same2004-11-03T13:41:00-05:002004-11-03T13:41:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-11-03:/more-of-the-same.html<p>In <em>Citizen Kane</em>, there is a scene where Kane's newspaper staff is
deciding what headline to run on election day after Kane's run for the
governorship. Had Kane won, the headline would have been "KANE WINS!".
However, he didn't win, so the headline they use is "FRAUD AT POLLS!".</p>
<p>I …</p><p>In <em>Citizen Kane</em>, there is a scene where Kane's newspaper staff is
deciding what headline to run on election day after Kane's run for the
governorship. Had Kane won, the headline would have been "KANE WINS!".
However, he didn't win, so the headline they use is "FRAUD AT POLLS!".</p>
<p>I spent most of my time watching PBS's <em>NewsHour</em> coverage of the
election returns, but I jumped around to the other networks. When Ohio
was called for Bush by NBC, apparently sewing things up for Bush, the
commentators started admitting that they were surprised. Early exit
polls had shown a likely Kerry win. Most of the journalists thought it
they'd be projecting a Kerry win early in the evening. They told us none
of this until after Bush had apparently won. I suppose it's a good thing
that they didn't report on the what they thought the outcome would be
while it was going on, but it makes me wonder what else journalists know
that they won't tell us.</p>
<p>C-SPAN's <a href="http://network.ap.org/dynamic/files/specials/election_night_2004/us_map_govsenhouse/index.html?SITE=CSPANELN&SECTION=POLITICS&reload=true">election map</a> is pretty cool. I heard that on CNN.com, one can find the data on
election returns for each precinct/county. I'm not sure how "useful" any
of this data is, but political information junkies must be thrilled with
what they can get on the Internet these days.</p>
<p>My favorite election news story of the day was <a href="http://www.netcrucible.com/blog/PermaLink.aspx?guid=600db7be-39a9-4163-ac0c-0e8a8139dc3b">this one</a>.</p>Rouser 1.0 Release2004-11-03T04:58:00-05:002004-11-03T04:58:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-11-03:/rouser-10-release.html<p>I don't know if anyone is interested, but my simple Rouser alarm clock
application is available for download from here:
<a href="http://kristopherjohnson.net/download/Rouser-1.0.dmg">http://kristopherjohnson.net/download/Rouser-1.0.dmg</a>. Source code is
included, and it's all free under an MIT-style license.</p>
<p>It mostly works. The Print and Help commands don't work. OS …</p><p>I don't know if anyone is interested, but my simple Rouser alarm clock
application is available for download from here:
<a href="http://kristopherjohnson.net/download/Rouser-1.0.dmg">http://kristopherjohnson.net/download/Rouser-1.0.dmg</a>. Source code is
included, and it's all free under an MIT-style license.</p>
<p>It mostly works. The Print and Help commands don't work. OS X 10.3
(Panther) is required; I don't know what it will do (if anything) under
older OS X versions.</p>
<p>For the next version, I plan to provide the following:</p>
<ul>
<li>a better application icon</li>
<li>a help file</li>
<li>preferences/options settings</li>
<li>AppleScript support</li>
</ul>
<p>If you try it out, please let me know what you think.</p>My Voting Experience2004-11-03T00:57:00-05:002004-11-03T00:57:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-11-03:/my-voting-experience.html<p>Happy Voting Day, everyone!</p>
<p>I first went to my local polling place at 10:30 AM. The line was wrapped
around the parking lot, it looked like at least a two-hour wait, and
rain was likely. I was, in one sense, pleased to see such a huge
turnout, but I …</p><p>Happy Voting Day, everyone!</p>
<p>I first went to my local polling place at 10:30 AM. The line was wrapped
around the parking lot, it looked like at least a two-hour wait, and
rain was likely. I was, in one sense, pleased to see such a huge
turnout, but I also wondered why our local election officials hadn't
made better arrangements. I didn't want to wait for two hours in the
rain, and couldn't find a place to park, so I decided to return later.</p>
<p>At 2:30 PM, things were better, so I got into line. The polling location
was an elementary school cafetorium. As I walked through the line, I
looked at the decorations and other items on the walls. The theme was
"Rock and Roll", with paper cutouts of acoustic guitars and record
albums (how many elementary school kids have ever seen a vinyl record?).
The menu for the previous day's lunch included a "chix sandwich", which
made me wonder if the meat came from the same animal that is the source
of McNuggets.</p>
<p>During my 45 minutes or so in line, I thought a lot about the voting
process. My thoughts drifted toward cynicism. I thought about the
futility of voting in a state that will probably go 60% Bush. I thought
about the fact that in most of the local races, the incumbent was
running unopposed.</p>
<p>I did have a pleasant Democracy Moment: as I neared the end of the line,
the woman behind me said "Excuse me, this is my first time voting in
this country. Can you tell me where I go?" I suddenly felt very proud to
be welcoming a newcomer to the wonderful world of freedom and democracy.
(For all I know, she may have come from a country that is more free and
democratic than the USA, but I took my duties as Ambassador of American
Glory very seriously.) I wasn't sure exactly what we had to do, so I
explained that the procedures were different in every location, and if
we'd just go to the first table and do what the election workers said,
they would tell us what to do next. She thanked me as we walked forward
to the table to fill out our voter forms, and my cynicism was gone.</p>
<p>It returned when I was handed my "voting card", a smart card for the
paper-free touch-screen voting machines. The machines were easy to use,
and I have no reason to believe they are any less secure than the punch
cards we had in previous elections, but I left with the feeling that my
vote hadn't really been registered by the machine. Maybe it's just a
side effect of being a computer programmer, but I have absolutely no
trust in these things.</p>
<p>So now we'll have to sit back and see who wins the first vote count.
Then who wins the second vote count. Then the first lawsuit, and so on.
Several bloggers I read seem to think that things are leaning toward
Kerry, but they're pro-Kerry so I can't believe them.</p>
<p>I am left with good feelings about the day. Even with my cynicism, I was
impressed with the cheerfulness and courtesy of the other people in
line. There were no loudmouth idiots expounding simplistic views, or
people who have nothing to do but belittle the views of others. I think
there's hope for civility in America.</p>On Writing2004-11-01T15:35:00-05:002004-11-01T15:35:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-11-01:/on-writing.html<p>I like writing code. I like writing documentation. I like writing specs.
I like writing e-mail and letters. I like wikis. I like writing in my
blog. I like writing.</p>
<p>What do I like about writing? I like the process of organizing my
thoughts and seeing them on paper/screen …</p><p>I like writing code. I like writing documentation. I like writing specs.
I like writing e-mail and letters. I like wikis. I like writing in my
blog. I like writing.</p>
<p>What do I like about writing? I like the process of organizing my
thoughts and seeing them on paper/screen. I like continually revising
what I write; I can never make it perfect, but I can always make it
better. It's nice when other people read and comment on what I write,
but what I like about writing is personal and internal.</p>
<p>I've toyed with the idea of becoming a professional writer. In a way, I
already am, as a programmer is really a writer of a very specialized
kind of writing. However, I'd like to be a "real" writer: one who gets
articles and books published and spends most of his time writing English
prose rather than a mishmash of math and symbolic logic.</p>
<p>Unfortunately, I don't know how I can make a living at writing. I don't
know what I could write that I could get paid for. There have been times
in my career when I could have written really good technical books about
implementing ActiveX controls and containers with MFC and ATL, or about
developing CORBA applications. I never had the time to write those
books, and now those subjects are no longer interesting to anyone.</p>
<p>When I was developing
<a href="http://remoting-corba.sourceforge.net">Remoting.Corba</a>, one of my
dreams was that it would become popular enough to warrant publication of
a book. The content of the <a href="http://kristopherjohnson.net/cgi-bin/rc/wiki.pl?Remoting.Corba_Wiki">Remoting.Corba wiki</a>,
bulked up with a bunch of screenshots and appendices, would have made a
book that is at least as good as most of the books in the Computers
section of the bookstore. The project died, but that wiki still stands
as the most book-like thing I've written. I submitted a R.C article at
the request of a Microsoft employee for publication on the MSDN web
site, but interest in R.C waned before it was published.</p>
<p>I don't have the depth of knowledge to write good technical books about
current subjects, because I am now a primarily a manager and I don't get
to spend enough time delving into new stuff. Maybe there could be a .NET
book or a Cocoa book in my future, but right now I really don't have
anything to teach anyone about programming.</p>
<p>I've been told that I have a gift for explaining things to non-techies
without dumbing it down too much, so I've considered writing books or
articles targeted at people who have to deal with technology but don't
understand it. A subject area that I've considered is the interaction
between software developers, customers, and managers. My working title
is <em>Talking to Techies</em>. The work would be an attempt to explain to
non-techies how they can best get what they want when they need
assistance from software developers, network administrators, and
technical support personnel. Too often, I see communication between
these groups break down, as the non-techies don't accurately or
completely specify what they need, and the techies provide a useless
solution or no solution because they don't understand the needs or think
they are being asked to perform impossible tasks. A companion book could
be targeted at the techie audience who doesn't know how to explain
issues to non-techies.</p>
<p>I signed up for the <a href="http://www.nanowrimo.org">National Novel Writing Month</a> (NaNoWriMo) event. This is a "contest"
where participants must write a 50,000-word novel during the month of
November. Entries are not judged; anyone who writes a novel with 50,000
words is a winner. I wanted to do this, but now I don't think I will: I
don't have time, and I don't have a story in mind. Instead of trying to
write a novel, I'm currently reading Robert McKee's <em>Story</em> in the hope
that maybe I can develop an interesting story for next year's NaNoWriMo.</p>
<p>While I'm ruminating about all the books I'd like to write, I'll just
keep scribbling in this blog, sending too many e-mails to my co-workers,
and contributing to wikis. I hope that if I just keep writing, I'll
eventually find a book in there somewhere.</p>Microsoft Wireless IntelliMouse Explorer2004-10-31T16:21:00-05:002004-10-31T16:21:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-10-31:/microsoft-wireless-intellimouse-explorer.html<p>After using with the single-button mouse for a almost a month, I've
switched to the Microsoft Wireless IntelliMouse Explorer. I've concluded
that the single-button mouse is obsolete, and it is just arrogance and
stubbornness that cause Apple to stick with it.</p>
<p>I've used the Microsoft mice on Windows machines for …</p><p>After using with the single-button mouse for a almost a month, I've
switched to the Microsoft Wireless IntelliMouse Explorer. I've concluded
that the single-button mouse is obsolete, and it is just arrogance and
stubbornness that cause Apple to stick with it.</p>
<p>I've used the Microsoft mice on Windows machines for a while. Logitech
and Kensington have some acceptable models, but the various Microsoft
mouse products always feel best in my hand.</p>
<p>I initially looked at the Microsoft Bluetooth mouse, but it was $80 and
it wasn't clear whether it would work with my iMac's built-in Bluetooth
transceiver. I found the Wireless IntelliMouse Explorer marked down to
$30, so I snapped it up.</p>
<p>I have immediately noticed a difference in how easy the Mac is to use.
This is not simply a matter of familiarity with a two-button mouse. With
the new Macs' large screens and increasingly complex user interfaces,
selecting menu items from the pull-down menu at the top of the screen or
Control-clicking something is much more cumbersome than simply
right-clicking something. Similarly, it is much easier to scroll with a
wheel than it is to move the mouse all the way over to the scroll
arrows.</p>
<p>The IntelliMouse control panel allows a wider range of mouse tracking
speeds. I find Apple's mouse to be too slow, even at the highest
setting. Neither Microsoft nor Apple provides good acceleration
behavior, allowing fine control when the mouse is moving slowly but also
allowing quick movements across the screen. This was once a feature that
made the Mac more usable than Windows, but Apple forgot to include it in
Mac OS X.</p>
<p>Apple once had the best mouse for any platform. This is no longer true.
My Christmas wish is that Apple will develop a two-button scroll-wheel
Bluetooth mouse. Until that happens, Microsoft will keep yet another
foothold on my Mac.</p>Virtual PC 7 for Mac2004-10-30T19:25:00-04:002004-10-30T19:25:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-30:/virtual-pc-7-for-mac.html<p>I received Office 2004 Professional for Mac a couple of days ago. I
haven't felt like searching around for my Office 98 CDs (which I assume
will be needed for installation of this upgrade version), so I haven't
tried Office on the Mac yet. I was entertained by the packaging …</p><p>I received Office 2004 Professional for Mac a couple of days ago. I
haven't felt like searching around for my Office 98 CDs (which I assume
will be needed for installation of this upgrade version), so I haven't
tried Office on the Mac yet. I was entertained by the packaging, which
was designed to appeal to the dreamy stupid hippie-geeks that Microsoft
believes Mac owners to be.</p>
<p>I did install Virtual PC 7, which is in a separate package from the rest
of the Office install. Installation was easy, including automatic set up
of a Windows XP Service Pack 2 virtual machine. This was a nice
surprise: at work I've had to set up a few Virtual PC virtual machines
on Windows boxes, and I've found that doing a full installation of an
operating system on a virtual machine takes a very long time and can be
problematic when the OS installer doesn't automatically recognize the
virtual devices.</p>
<p>The first things I did after getting VPC installed were to try Windows
Update, and to try to activate Windows over the Internet. Neither
operation worked due to connection errors. I tried disabling the
firewalls, but that didn't help. (BTW, when VPC is running, you no
longer have access to the built-in Apple firewall.) What finally worked
was to change the VPC network settings from "Shared Networking" to
"Virtual Switch", which gives the virtual machine its own IP address.
There were warnings that Virtual Switch might not work with a wireless
network connection, but it is working fine for me and my AirPort
connection.</p>
<p>VPC seems to be well behaved. When it is idle (that is, when I'm not
"doing something" with the virtual machine), it uses between 2% and 12%
of my Mac's CPU. Even when it is busy, it doesn't bog down my other
running Mac apps.</p>
<p>There is a lot of integration between the virtual machine and the rest
of the Mac. For example, you can drag files between the Mac desktop and
the Windows desktop. Windows processes show up in the Mac's Activity
Monitor process list, and you can Quit or Force Quit the Windows
processes from the Mac. Windows things show up in the Dock. I'm not sure
whether I like these features. I supposed they are helpful for people
who need to use both Windows and Mac OS X to get things done, but I
worry that VPC might be making my Mac less stable and secure.</p>
<p>Unfortunately, Virtual PC is really, really slow on my iMac G5. I didn't
expect it to be speedy, but it is too slow to be useful for anything. So
when I want to run Windows apps from my Mac, I'll use Remote Desktop
Connection to access the real Windows PC that is in the other room. When
I have some free time, I'll try installing Fedora, Gentoo, or one of the
other Linux distros to see what kind of performance I can get from a
less-bloated OS.</p>Screwed by FileMerge2004-10-30T06:48:00-04:002004-10-30T06:48:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-30:/screwed-by-filemerge.html<p>While adding support for stored preferences to Rouser, I realized I
needed to add another outlet to my controller class so that it could
restore the setting of the on/off radio buttons. So I went into
Interface Builder, added the outlet, and told it to regenerate the class
files …</p><p>While adding support for stored preferences to Rouser, I realized I
needed to add another outlet to my controller class so that it could
restore the setting of the on/off radio buttons. So I went into
Interface Builder, added the outlet, and told it to regenerate the class
files. When prompted, I clicked the "Merge" button to indicate that I
wanted the new code merged into the existing files, instead of
overwriting the files.</p>
<p>The FileMerge application started, and the windows opened. As I've done
before, I just looked for "0 conflicts" and then did a Save of the
files. (I ignore all the shaded curvy lines and arrows on the FileMerge
display, because I don't understand what they mean.)</p>
<p>BZZZT! It didn't merge. It just overwrote my class files with a bunch of
empty declarations. Intuitive, forgiving interface, hah!</p>
<p>I commit to CVS pretty often, so this wasn't a disaster. I suppose I
really ought to read the docs for FileMerge one of these days. But why
doesn't this do the right thing by default? You know, like a Macintosh
would.</p>Rouser2004-10-29T12:40:00-04:002004-10-29T12:40:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-29:/rouser.html<p>My "Rouser" alarm clock application for Mac OS X is coming along nicely.
It is basically functional now, allowing me to turn the alarm on and off
and set the alarm time. When it is on, it displays a countdown (e.g.
"Alarm will ring in 2 hours, 13 minutes …</p><p>My "Rouser" alarm clock application for Mac OS X is coming along nicely.
It is basically functional now, allowing me to turn the alarm on and off
and set the alarm time. When it is on, it displays a countdown (e.g.
"Alarm will ring in 2 hours, 13 minutes, 12 seconds") that updates once
per second. When the alarm goes off, it repeatedly speaks the current
time ("The time is 12:49 PM.") for a minute or until the alarm is turned
off.</p>
<p>I am using the "Bells" voice to speak the time. This sounds like a
grandfather clock's chimes, emphasizing the clockiness of the
application. I couldn't find anything in Apple's current documentation
about the special delimiters one can use to control the voice (for
example, "[[slnc 1000]]" to pause for a second, or "[[rate -10]]" to
slow it down), but I was able to find plenty of unofficial information
around the web. Interestingly, the most informative sites were by Newton
enthusiasts. Once I knew what to look for, I did find some info on
<a href="http://developer.apple.com/documentation/mac/Sound/Sound-200.html">embedded speech commands</a>
in Apple's online <em>Inside Macintosh: Sound</em>.</p>
<p>There are just a few little things I want to add to Rouser before I can
call it "finished":</p>
<ul>
<li>an Options panel allowing the user to change the voice, to change what it says, or to play a sound instead of a voice</li>
<li>saving the state of the alarm clock to a plist file</li>
<li>displaying an "Are you sure you want to quit?" panel if user tries to quit while the alarm is on and counting down</li>
<li>a nifty application icon that does cool things in the Dock</li>
</ul>
<p>I currently have three pop-up menus to choose the time: the first has
the numbers 1-12, the second has the numbers 00, 05, 10, ... 55, and the
last has "AM" and "PM". This allows me to set the time fairly quickly,
without using the keyboard. If I still feel ambitious after "finishing"
the application, I'd like to implement pie-like menus for the numeric
fields.</p>
<p>So far, I'm pretty impressed with Interface Builder and the Cocoa API.
Everything I've wanted to do has been pretty simple to implement. If I
wasn't learning OS X programming while implementing Rouser, I think I
could have put the whole thing together in less than an hour.</p>
<p>The only gripe I have about Cocoa is that the date/time classes don't
seem to provide any easy way to extract the current hour, minute, and
second. So I had to fall back on the standard C <code>time()</code> and
<code>localtime()</code> functions, which aren't described in Xcode's
documentation. Apple clearly wasn't considering the needs of alarm-clock
developers - this is more undeniable evidence of Apple's irrational
anti-alarm-clock bias.</p>Living in Squalor2004-10-28T11:41:00-04:002004-10-28T11:41:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-28:/living-in-squalor.html<p>A few weeks ago, after watching the film <em>Supersize Me!</em>, I stopped
eating fast food. I was preparing my own meals, using fresh ingredients.
I felt better, lost some excess weight, and had more energy.
Unfortunately, with the long hours and stress at work recently, I've
returned to the old …</p><p>A few weeks ago, after watching the film <em>Supersize Me!</em>, I stopped
eating fast food. I was preparing my own meals, using fresh ingredients.
I felt better, lost some excess weight, and had more energy.
Unfortunately, with the long hours and stress at work recently, I've
returned to the old two-value-meals-a-day diet. I don't have time to
shop or cook, and that fast food tastes really, really good.</p>
<p>This means I now bring home a couple of fast-food bags per day, along
with all the excess packaging. My trash can at home is pretty small, so
I developed the habit of stacking the fast food bags neatly next to it.
Every few days (when I remember) I put all the trash in a bag and haul
it out to the dumpster.</p>
<p>It seemed like typical bachelor behavior. But this morning, while going
into the kitchen to make coffee (another habit I thought I'd broken), it
hit me: I've got <em>garbage piled up in my home</em>!</p>
<p>Ewww.</p>Two Weeks In2004-10-26T14:12:00-04:002004-10-26T14:12:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-26:/two-weeks-in.html<div class="highlight"><pre><span></span><code><span class="mi">10</span><span class="p">:</span><span class="mi">42</span><span class="w"> </span><span class="n">up</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="n">days</span><span class="p">,</span><span class="w"> </span><span class="mi">21</span><span class="p">:</span><span class="mi">46</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">users</span><span class="p">,</span><span class="w"> </span><span class="nb">load</span><span class="w"> </span><span class="n">averages</span><span class="p">:</span><span class="w"> </span><span class="mf">1.56</span><span class="w"> </span><span class="mf">1.41</span><span class="w"> </span><span class="mf">1.14</span><span class="w"></span>
</code></pre></div>
<p>The iMac has been pretty stable, after the first few days of
instability. I haven't had many application crashes (only OmniWeb is
prone to die) and haven't needed to reboot.</p>
<p>While the iMac …</p><div class="highlight"><pre><span></span><code><span class="mi">10</span><span class="p">:</span><span class="mi">42</span><span class="w"> </span><span class="n">up</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="n">days</span><span class="p">,</span><span class="w"> </span><span class="mi">21</span><span class="p">:</span><span class="mi">46</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">users</span><span class="p">,</span><span class="w"> </span><span class="nb">load</span><span class="w"> </span><span class="n">averages</span><span class="p">:</span><span class="w"> </span><span class="mf">1.56</span><span class="w"> </span><span class="mf">1.41</span><span class="w"> </span><span class="mf">1.14</span><span class="w"></span>
</code></pre></div>
<p>The iMac has been pretty stable, after the first few days of
instability. I haven't had many application crashes (only OmniWeb is
prone to die) and haven't needed to reboot.</p>
<p>While the iMac experience is generally good, I do have a few minor
complaints:</p>
<ul>
<li>It is not as quiet as I was led to believe. It's more quiet than a PC, but not so silent that I don't notice it.</li>
<li>I am disappointed with the smoothness of window resizing and other common operations. When resizing a window, a frame only updates itself a few times a second, leading to significant lag between where the mouse is and where the window corner is.</li>
<li>Xcode doesn't include any reference documentation for the standard C and C++ libraries. I find myself going to MSDN to look things up. I know Apple would prefer that I use the Core Framework and Cocoa APIs, but it would be nice to have better support for plain-old standard C/C++ programming.</li>
<li>Xcode's integrated CVS support sucks. It doesn't handle nib files. That's ridiculous. So I'm using the Terminal for CVS operations.</li>
<li>Many of Apple's applications don't follow the <em>Human Interface Guidelines</em>. This is nothing new, but it always bugs me to see applications that ignore the HIG without any reason other than "It looks cool this way". It bugs me when other software developers ignore the HIG, but Apple is the company with the most responsibility for promoting adherence, and is also the worst offender.</li>
<li>I'm surprised at how many things are "unfinished". Apple goes to great lengths to create cool stuff (like Exposé), but then they don't get little things working (like FTP support in the Finder). Until they get the little things right, I can't tell anyone that the Mac is better than Windows; it just looks prettier.</li>
</ul>Little Victories and Little Celebration2004-10-26T04:56:00-04:002004-10-26T04:56:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-26:/little-victories-and-little-celebration.html<p>We passed a significant milestone today at work: we finally got all the
hardware together and loaded the current software on it. And whadya
know: the damn thing actually works!</p>
<p>This should have made me feel good, but it really didn't. For one thing,
I was tired: it was after …</p><p>We passed a significant milestone today at work: we finally got all the
hardware together and loaded the current software on it. And whadya
know: the damn thing actually works!</p>
<p>This should have made me feel good, but it really didn't. For one thing,
I was tired: it was after 9PM when I finally got it all working. For
another, I was alone: everyone else had been gone for hours, so I
couldn't share the victory with anyone. I just sent the project manager
an e-mail, packed my bag, went home, and fell asleep on the couch like
any other evening.</p>
<p>Seeing it all put together and working, I wondered "so it took five
months to do <em>this</em>?" The system is conceptually very simple. What it
does, when it works, is almost trivial. What took a lot of time was
designing and implementing all the error handling for the situations
when it doesn't work. You can't see any of that complexity from the
outside; it's just a black box that does almost nothing, and that's
exactly how I saw it tonight.</p>
<p>Is there something wrong with me? I'm rarely proud of my work. I never
feel a sense of accomplishment. I focus more on what I didn't do, or did
poorly, than on what I did well. So work is just a series of
disappointments.</p>
<p>How can I turn this around? Is it the nature of the work, or just my own
nature? If it's my own nature, can I and should I change it?</p>Application Naming2004-10-25T14:03:00-04:002004-10-25T14:03:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-25:/application-naming.html<p>I've read a few chapters of some Cocoa programming books and
documentation, so I'm ready to start work on a simple Mac OS X alarm clock
application. The first step, which often takes me longer than the actual
implementation, is to come up with a cool name for it.</p>
<p>I …</p><p>I've read a few chapters of some Cocoa programming books and
documentation, so I'm ready to start work on a simple Mac OS X alarm clock
application. The first step, which often takes me longer than the actual
implementation, is to come up with a cool name for it.</p>
<p>I could just call it "Alarm Clock", but that would show a lack of
imagination. I did Google searches of some synonyms and related words,
like "Alarm Time", "Wake Up", "Alarmist", but all were already used by
someone else's alarm clock application. I also looked into some
alternate spellings, like "Alarm Clok", but again, somebody else had the
same idea.</p>
<p>I considered some platform-specific names, like "Cocoalarm", "Aqualarm",
and "Alarm X", but all those were already in use or they put me at risk
of legal action from Apple. (If those %\$#@ers at Apple hadn't removed
the good-old Alarm Clock desk accessory from Mac OS in the first place,
none of this would be necessary.)</p>
<p>"Strawberry Alarm Clock" is a cool name. But I think it's taken.</p>
<p>I've released some freeware under the fictitious business name
"Sleepless-Night Software". "Sleepless-Night Alarm Clock" does have a
certain ring to it, but would be too big for the application menu title.</p>
<p>So I'll keep thinking about names. I can't start the work until I've got
a good one.</p>
<p>[UPDATE: I've settled on the name "Rouser". Until I think of something
better.]</p>Sprinting to the Finish2004-10-20T05:05:00-04:002004-10-20T05:05:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-20:/sprinting-to-the-finish.html<p>I haven't written much in my blog about what I'm doing at work. This is
because I've wanted to avoid writing anything that could get me into
trouble with my managers, coworkers, or customers. However, I do want to
write a little about what's going on at work, because that …</p><p>I haven't written much in my blog about what I'm doing at work. This is
because I've wanted to avoid writing anything that could get me into
trouble with my managers, coworkers, or customers. However, I do want to
write a little about what's going on at work, because that is what I'm
spending most of my time thinking about. So I'll write, but I'll have to
leave a lot of the details out.</p>
<p>We're approaching the deadline (Nov. 15) for getting our system
installed and operational at the customer site. This is a hard deadline;
it absolutely cannot slip beyond that date. Software-wise, I think we're
OK. The problem is that we don't have many of the the custom hardware
components that will make up the production system. The software works
great with mock objects taking the place of the hardware, but until we
test the system with the actual hardware, we won't really know if
everything works.</p>
<p>Integration testing is starting tomorrow, so I'll be in the office this
evening putting the final touches on the test environment. Hardware
components will be showing up during the week, so I expect many long
hours of hooking new things up and figuring out why they aren't working.</p>
<p>It is too early to really know what we did right and what we should do
differently next time, but I've already started my lists:</p>
<p>What we did right:</p>
<ul>
<li>developed simulators for the non-existent hardware and host server</li>
<li>designed an architecture that made it easy to enable/disable various
features and to use simulators just like they are the real things</li>
<li>refactored and redesigned as we went along</li>
<li>as the deadline approaches, the managers have been willing to reduce
the scope</li>
<li>we haven't had to work overtime very much (although that may change
in the coming weeks)</li>
</ul>
<p>What we didn't do right:</p>
<ul>
<li>few automated tests</li>
<li>didn't find time for code reviews</li>
<li>haven't had any non-developers use the system</li>
<li>I couldn't figure out how to spend less time managing and more time
working with the code</li>
</ul>
<p>The code that we have developed for this project is supposed to become a
new framework to be used for developing similar systems. I already
expect a lot of resistance. We have a lot of decoupling between
different subsystems, which leads to more flexibility but also makes
things look very complicated in comparison to the older codebase. There
are some things that could be made simpler, so I hope to have some time
to clean those things up before presenting the new framework to others
in the software development group. I know they are going to hate it, but
I'm not going to worry about that.</p>
<p>There were a few weeks in the middle of the project when I got a little
depressed and burned out. We weren't getting the information we needed
on hardware, our project manager was re-assigned to another project, and
I felt that it was up to me to fix every problem myself. Thankfully, I
got past that by allowing myself to focus just on what I could do and to
stop worrying about what I couldn't do. Now, we've got a month to go,
and I am pretty optimistic about how things will turn out.</p>
<p>When it's over, I'll allow myself to finally take a vacation. I haven't
had any time off since I joined the company 15 months ago. Not taking
any vacation has probably been my biggest mistake.</p>
<p>After some time off, I'll have to do some heavy thinking about what I
want to do with my career. This recent project experience has given me
more insight into how the company works and how its management does
things. I have more data to consider as I decide whether this is where I
want to stay for a while, or whether it is time to move on to something
else. At the moment, "something else" is very attractive, but I don't
know how I'll feel after the project is behind me.</p>I Bought Microsoft Office for Mac2004-10-19T13:03:00-04:002004-10-19T13:03:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-19:/i-bought-microsoft-office-for-mac.html<p>I just ordered Microsoft Office Professional for the Mac. I feel a
little dirty.</p>
<p>I was hoping that the Mac could be a Microsoft-free haven for me. Then I
found that I needed Windows Media Player, so I got that. Then I needed
Microsoft Remote Desktop Connection, so I got …</p><p>I just ordered Microsoft Office Professional for the Mac. I feel a
little dirty.</p>
<p>I was hoping that the Mac could be a Microsoft-free haven for me. Then I
found that I needed Windows Media Player, so I got that. Then I needed
Microsoft Remote Desktop Connection, so I got that. Then I needed MSN
Messenger. But those were "free", so I consoled myself by thinking that
at least I wasn't giving any more money to Microsoft.</p>
<p>Unfortunately, I can't find a good substitute for Microsoft Office.
OpenOffice for Mac is uglier than any Windows application I've ever
seen. AppleWorks feels like a toy, and can't read Office file formats.
When I brought work home, I wanted to use the Mac, but couldn't.</p>
<p>What finally drew me in was the announcement that Virtual PC 7 would be
included in MS Office Pro. I don't really need to run Windows on my Mac,
but I would like to try out Linux distros and other operating systems.</p>
<p>The last Office version I bought was Office 98 for Windows. That
qualifies me for the upgrade price, so I'm getting Virtual PC and the
rest of Office for a little under $300. That's a pretty good deal in
the Windows world. And maybe it won't suck quite as much on a Mac.</p>The Brushed Metal Look2004-10-17T17:07:00-04:002004-10-17T17:07:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-17:/the-brushed-metal-look.html<p>I was going to blog about what I think about the mixture of the "brushed
metal" look and the more traditional Apple look in Mac OS X, but someone
else already wrote everything I wanted to say:
<a href="http://daringfireball.net/2004/10/brushedmetal">http://daringfireball.net/2004/10/brushedmetal</a>.</p>
<p>The Finder is especially annoying in that …</p><p>I was going to blog about what I think about the mixture of the "brushed
metal" look and the more traditional Apple look in Mac OS X, but someone
else already wrote everything I wanted to say:
<a href="http://daringfireball.net/2004/10/brushedmetal">http://daringfireball.net/2004/10/brushedmetal</a>.</p>
<p>The Finder is especially annoying in that it switches itself between
brushed metal and plain-old-window depending on whether you show or hide
the toolbar. With the Apple Human Interface Guidelines encouraging
developers to look to the Finder as an example of correct UI design,
it's not surprising that people can't agree on the appropriateness of
the brushed-metal look or on many other aspects of how a "real Macintosh
application" should work.</p>
<p>I don't understand why Apple wants its applications to look like a bunch
of overpriced components for audiophiles.</p>Sleepless-Night Wiki Moved to Quartus2004-10-17T06:00:00-04:002004-10-17T06:00:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-17:/sleepless-night-wiki-moved-to-quartus.html<p>A few years ago, I created the <a href="http://kristopherjohnson.net/wiki">Sleepless-Night Wiki</a> as a forum where <a href="http://www.quartus.net/products/forth/">Quartus
Forth</a> users could exchange information and code. I considered it to be pretty successful, as it
attracted several talented people who contributed code, tips, and
advice. It also attracted some people interested in related matters,
such …</p><p>A few years ago, I created the <a href="http://kristopherjohnson.net/wiki">Sleepless-Night Wiki</a> as a forum where <a href="http://www.quartus.net/products/forth/">Quartus
Forth</a> users could exchange information and code. I considered it to be pretty successful, as it
attracted several talented people who contributed code, tips, and
advice. It also attracted some people interested in related matters,
such as general Forth topics and Palm OS issues. Sleepless-Night was
based on the <a href="http://twiki.org">TWiki</a> software.</p>
<p>Earlier this year, the wiki went down when my evil hosting provider
moved all my files to a new differently-configured server. It stayed
down for several months, as I couldn't get FTP to work and every attempt
to use the "web control panel" provided by the hosting company made me
give up in frustration.</p>
<p>Getting the Mac prompted me to approach the problem again. This gave me
a chance to try out different Mac web browsers, FTP clients, and
Internet utilities. I got the wiki mostly working again, although I
didn't get around to re-implementing the authentication mechanism.</p>
<p>As most of the wiki content is related to Quartus Forth, and I don't do
anything with that tool anymore, I've transferred all the wiki content
over to Neal Bridges at Quartus. The new Quartus Wiki can be accessed at
<a href="http://quartus.net/wiki/">http://quartus.net/wiki/</a>. Everyone's old passwords should still work.</p>
<p>The wiki contains a lot of info on personal projects of mine. I'll
probably set up a Sleepless-Night Wiki Mark II as a repository for those
things. <a href="http://www.usemod.com/cgi-bin/wiki.pl">UseModWiki</a> is the wiki
software I like now; I used it to set up the <a href="http://kristopherjohnson.net/cgi-bin/rc/wiki.pl?Remoting.Corba_Wiki">Remoting.Corba Wiki</a>
and a few other private wikis. TWiki has a lot of features, but it was
always a little too complicated for users to figure out.</p>
<p>Unfortunately, there are bots out there that add wiki-spam to UseMod
wikis, so I'll probably have to make my wiki read-only for everyone but
myself.</p>iMac Memory Upgrade2004-10-17T05:39:00-04:002004-10-17T05:39:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-17:/imac-memory-upgrade.html<p>I added 1 GB of RAM to my iMac, bringing the total up to 1.5 GB. It is
much more responsive now. Applications launch faster; the Dock pops out
instantly when I put the mouse in the right place; windows resize
without all the jerking around; Exposé is smoother …</p><p>I added 1 GB of RAM to my iMac, bringing the total up to 1.5 GB. It is
much more responsive now. Applications launch faster; the Dock pops out
instantly when I put the mouse in the right place; windows resize
without all the jerking around; Exposé is smoother.</p>
<p>If I had it to do over again, I would have ordered the iMac with the
minimum RAM (256 MB), then bought 2 GB of RAM from someplace else.
Crucial will sell 2 GB of Mac RAM for $505. In contrast, Apple adds
$1,225 to the price tag if you want 2 GB out of the box.</p>
<p>Performing the upgrade was easy. I did some bad things to the screws
when opening up the Mac. I was expecting them to come out when I
unscrewed, but they are captive screws that stay in place when you turn
them. So I ended up unscrewing too far, and turned the Philips slot into
a conical pit. Don't make this mistake if you do this yourself.</p>The One-Button Mouse and Where to Put My Fingers2004-10-16T06:42:00-04:002004-10-16T06:42:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-16:/the-one-button-mouse-and-where-to-put-my-fingers.html<p>The Mac still ships with a one-button mouse. This made sense to me back
in the System-6-and-earlier days. Other operating systems had confusing
and inconsistent uses for their multiple mouse buttons, and Apple's
solution seemed to be the simple and elegant one. Apple's one-button
mouse was easier to use, and …</p><p>The Mac still ships with a one-button mouse. This made sense to me back
in the System-6-and-earlier days. Other operating systems had confusing
and inconsistent uses for their multiple mouse buttons, and Apple's
solution seemed to be the simple and elegant one. Apple's one-button
mouse was easier to use, and also of much better quality than the cheap
multi-button mice that were shipped with PCs back then.</p>
<p>Things are different now. Other operating systems introduced the
right-click context menu; Apple responded by using a Control-click. This
has become clumsy. The context menus have become the primary means of
controlling a lot of on-screen objects, yet Apple insists on this
two-handed method. It's too bad, because I really like the look and feel
of the Apple Bluetooth mouse; I just want another button (and maybe a
scroll wheel).</p>
<p>I know I could just take the easy way out and buy a third-party
multi-button mouse, but as an experiment I am going to stick with the
one-button mouse. Maybe I'll get used to it. Maybe I'll develop carpal
tunnel syndrome. I just want to see if maybe Apple's UI designers are on
to something. (Although I wouldn't be surprised if all Apple's UI gurus
have multi-button mice.)</p>
<p>All this use of the Command, Option, Control, and Shift keys along with
the mouse, as well as my increasing use of keyboard shortcuts to avoid
the mouse, has made me start thinking about exactly where I should put
my fingers on those keys. When I took typing class back in high school,
I learned to use my right thumb on the spacebar and my pinky on the
Shift key, but they didn't have these extra keys back then. I've been
sloppy, just hitting keys with whatever finger happened to be closest,
but I think I may be more efficient and less likely to suffer from RSI
if I can touch-type the modifiers like I do the rest of the keyboard.</p>
<p>Should I press Command with my thumb, my index finger, or my middle
finger? Should I press Control with my pinky, or with my ring finger?
When I use a shortcut like Command-Q, should hit both Command and Q with
my left thumb and middle finger (which is the habit I've developed), or
should I hit Command with my right hand and Q with my left (thus
avoiding a weird stretch of the hand)?</p>
<p>I've decided, for now, to try to consistently use my thumbs on the
Command keys, index finger on Option, and ring finger on Control or
Shift (and if I have to hit both Control and Shift, use index on Control
and ring on Shift). I am used to using my thumb for Command, so I think
this will be an easy habit to develop.</p>
<p>The harder challenge will be to break my habit of one-handed
"chording"for Cmd-Q, Cmd-Tab, Cmd-S, and other common shortcuts that are
possible with the left hand. When I use the Shift key, I have no problem
using two hands, so I hope I can develop the same habit for Command.</p>
<p>Maybe I <em>should</em> just buy a new mouse.</p>Emacs for OS X2004-10-13T22:58:00-04:002004-10-13T22:58:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-13:/emacs-for-os-x.html<p>One of the first things I wanted for my Mac was a good text editor.
TextEdit is okay for simple tasks, and I can use vi in a pinch, but I
really wanted something like Emacs.</p>
<p>So I started searching for Emacs/XEmacs for OS X. I found about a …</p><p>One of the first things I wanted for my Mac was a good text editor.
TextEdit is okay for simple tasks, and I can use vi in a pinch, but I
really wanted something like Emacs.</p>
<p>So I started searching for Emacs/XEmacs for OS X. I found about a dozen
different builds from different sources. Some ran on X11, some ran only
in the Terminal. Some were relatively new (Emacs 21); some were pretty
old (Emacs 19).</p>
<p>The best one I found is a Japanese-built <a href="http://home.att.ne.jp/alpha/z123/emacs-mac-e.html">Carbon Emacs
Package</a>. This one is
built from the current sources in the Emacs CVS repository, and doesn't
require an X11 server.</p>
<p>One problem with this version is that font support is bad. The default
font is a big Monaco font. I tried changing font faces, but always ended
up with something worse than the default (which is ugly). This package
is apparently designed for Japanese users, and the Roman font support
just isn't there. Every attempt to use the Set Font/Fontset command
results in a "Font not found" error, and attempts to use the
Customization screens result in something uglier than the default. I
decided that I can live with the default font.</p>
<p>There are some non-Japanese builds of Emacs with Carbon floating around,
so I'll give those a try. Eventually, I'll overcome my natural laziness
and figure out how to build it myself.</p>
<p>By default, the Command key is used as the Emacs Meta key. I prefer
using the Alt/Option key, so I added this to my .emacs file:</p>
<div class="highlight"><pre><span></span><code> (setq mac-command-key-is-meta nil)
</code></pre></div>AirPort Card Freak-Out2004-10-13T00:18:00-04:002004-10-13T00:18:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-13:/airport-card-freak-out.html<p>Last night, for no apparent reason, I suddenly lost the WiFi connection
on the iMac. I was unable to access the Internet or the other machines
on my home network. Every attempt to reconnect to the network gave me an
error message. My other laptop with WiFi was working fine …</p><p>Last night, for no apparent reason, I suddenly lost the WiFi connection
on the iMac. I was unable to access the Internet or the other machines
on my home network. Every attempt to reconnect to the network gave me an
error message. My other laptop with WiFi was working fine, so I assumed
it must be an iMac problem, but I tried re-booting the wireless router
anyway. No help.</p>
<p>So I re-booted the Mac, hoping that would fix it. Nope: still unable to
connect.</p>
<p>I tried re-booting a couple more times, along with various other acts of
desperation. What finally fixed it was a full shutdown and restart.
After that, everything was fine.</p>
<p>I'm finding that I have to reboot or shutdown-and-restart my iMac a lot
more often than I do with my Windows box. I'm hoping that trend will
change once I get through the initial shakedown period.</p>Alarm Clock2004-10-12T18:07:00-04:002004-10-12T18:07:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-12:/alarm-clock.html<p>One of my favorite desk accessories on the old Mac was the Alarm Clock.
Mac OS X doesn't have one. Neither does Windows. I miss it.</p>
<p>I don't know why it disappeared. I guess they figure the
appointment-setting features of iCal, Outlook, or Entourage eliminate
the need for a simple …</p><p>One of my favorite desk accessories on the old Mac was the Alarm Clock.
Mac OS X doesn't have one. Neither does Windows. I miss it.</p>
<p>I don't know why it disappeared. I guess they figure the
appointment-setting features of iCal, Outlook, or Entourage eliminate
the need for a simple alarm clock. But they don't: I often want my
computer to remind me when a TV show is coming on, or to check the oven
in ten minutes, or for other impromptu events, and I don't want to go
through the whole process of creating an appointment (specifying date,
time, length, location, who's attending, etc.).</p>
<p>I found a few alarm-clock utilities for OS X, but none of them do
exactly what I want, and the more useful ones are not free. So I may
need to write my own alarm clock application. This will be my chance to
learn to write a Cocoa app.</p>
<p>In the meantime, I'm just going to use the UNIX 'at' command from the
Terminal to trigger the speech synthesizer. I can use it like this:</p>
<div class="highlight"><pre><span></span><code><span class="nl">localhost</span><span class="p">:</span><span class="o">~</span><span class="w"> </span><span class="n">kdj</span><span class="err">$</span><span class="w"> </span><span class="k">at</span><span class="w"> </span><span class="mi">13</span><span class="err">:</span><span class="mi">50</span><span class="n">say</span><span class="w"> </span><span class="o">-</span><span class="n">v</span><span class="w"> </span><span class="n">Vicki</span><span class="w"> </span><span class="s1">'Kris, it is time to go back to work!'</span><span class="o">^</span><span class="n">D</span><span class="w"></span>
</code></pre></div>
<p>and then a soothing female voice will gently wake me from my afternoon
nap.</p>
<p>If you decide to use 'at' yourself, be sure to read the man page for at.
It is necessary to enable the 'atrun' command in /etc/crontab before it
will work.</p>iMac First Impressions2004-10-10T21:26:00-04:002004-10-10T21:26:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-10:/imac-first-impressions.html<p>My iMac arrived a few days ago. I took it out of the box, put batteries
into the Bluetooth keyboard and mouse, plugged the power cable in, and
turned it on. I was pleased to hear the good old Mac startup sound,
although it was startlingly loud.</p>
<p>Going through the …</p><p>My iMac arrived a few days ago. I took it out of the box, put batteries
into the Bluetooth keyboard and mouse, plugged the power cable in, and
turned it on. I was pleased to hear the good old Mac startup sound,
although it was startlingly loud.</p>
<p>Going through the initial startup screens was pretty easy. The only snag
was that I couldn't figure out how to enter the WEP key needed for the
AirPort card to connect to my wireless network. So I went without
network access at first, then I just disabled WEP on my router so that I
could test Internet access and download updates. Later, I found out that
it was necessary to enter a '$' in the AirPort password field before
typing the WEP key. Once I discovered this little tidbit, I was able to
restore WEP.</p>
<p>I last used a Mac in the System 7.5 days. It is interesting to note what
has changed, and what is the same. OS X has much more support for
keyboard navigation than the old Mac OS had. The top-of-screen menu bar
looks familiar, yet has standard menu items in different standard
locations.</p>
<p>The new Finder is very annoying in comparison to my memories of the
System 6 and 7.5 Finders. The windows and icons are too big to see
everything I'd like to see at one time. Too much valuable screen space
is used by the brushed-metal title bar and toolbars, and it seems to be
too difficult to do simple things. Is there a good reason that the
Finder will let you use Edit->Copy to copy files from place to place,
but won't let you use Cut and Paste to move them? The Finder's handling
of FTP servers and Windows shares really sucks. I've had the Finder lock
up, and it was unable to restart when I used Force Quit on it, so I had
to reboot. All in all, I think the Mac OS X Finder is the most
Microsoft-like of all the Mac's features.</p>
<p>I am getting used to the Dock. I found it a little confusing at first,
but now I prefer it to the Windows status bar mechanism. After playing
with a few configurations, I settled on auto-hide on the right side of
the screen: it is easier for me to flick the mouse to the right than
down to the bottom, and this puts the Trash down in the lower left
corner of the screen (as God intended). I would like to have a menu of
all open applications on the system menu, like 7.5 had, instead of
needing to use the Dock or Command-Tab to get to other apps.</p>
<p>Exposé is the coolest new feature. For those of you who haven't seen it,
it shrinks all your windows and tiles them so that you can see them all
at once and select the one you want to see. It is not just a
thumbnail-like mechanism: while shrunk, the windows are still updating
(you can see web animations, or terminal windows scrolling). I predict
Microsoft and other OS vendors will be providing similar mechanisms
soon.</p>
<p>None of the Mac web browsers are as nice as Internet Explorer on Windows
XP. OmniWeb and Safari are both OK, albeit a little less "snappy" than I
would like. Firefox is slow to render, and doesn't have a lot of the
standard file associations built in. I'm sticking with OmniWeb for now.</p>
<p>Here are some other applications that I've settled on:</p>
<ul>
<li>Adium X instant messenger client. (I also tried Fire and iChat, but
Adium's duck icon won me over.)</li>
<li>Transmit FTP client</li>
<li>Shrook RSS newsreader (Also tried NetNewsWire and Pulp Fiction, but
I found I prefer Shrook's column-based iTunes-like interface for the
wide iMac screen)</li>
</ul>
<p>As I have a couple of Windows machines at home, I spent a lot of time
with Mac OS X's support for Windows file and printer sharing. I am not
impressed. It works well when everything is set up correctly, but if I
reboot my Windows box while the iMac has a share open, or at other
random times, the Finder will completely lock up. I have since given up
on file sharing, and I now use FTP to transfer files between Mac and
Windows.</p>
<p>I was eventually able to get my iMac connected to my HP DeskJet 5550
printer that connected to my the Windows XP box, but fumbled around for
a while because the huge list of HP drivers in the Printer Setup dialog
doesn't include the 5550. I eventually decided to use the "dj450" driver
(which I vaguely remember from setting up CUPS on a Linux machine),
which seems to work although I get edges cut off on some printed pages.</p>
<p>I downloaded Microsoft's Remote Desktop Connection software for Mac. It
works very well. As my Windows box is in a different room from my iMac,
it is nice to be able to remotely control Windows when I need to find a
file to be transferred, or check what my password is for some web site,
and so on.</p>
<p>I keep hearing about how the new iMacs are completely silent. Mine is
not. It's not loud, and it's not annoying, but it is not silent. I
assume the sound is a fan, a disk drive, or both.</p>
<p>Despite the minor annoyances listed above, the iMac has quickly become
my primary machine. I only turn on the Windows box if I need to move an
old file over or to use the RC helicopter simulator (for which there is
no Mac equivalent). It's not that the Mac is better than Windows XP;
it's just different, and being able to escape from Microsoft when I
leave work is a nice thing.</p>Electronics and PIC Microcontrollers2004-10-10T21:22:00-04:002004-10-10T21:22:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-10-10:/electronics-and-pic-microcontrollers.html<p>Over the past few months, I've been trying to learn more about
electronics. My job involves a lot of devices and embedded software,
which piqued my interest. As with everything else I do, I want to
understand how it all works.</p>
<p>I had a couple of digital electronics courses in …</p><p>Over the past few months, I've been trying to learn more about
electronics. My job involves a lot of devices and embedded software,
which piqued my interest. As with everything else I do, I want to
understand how it all works.</p>
<p>I had a couple of digital electronics courses in college, so I know how
to design circuits with NAND gates, inverters, flip-flops, and so on.
But I've never known much about capacitors, coils, transistors,
amplifiers, oscillators, or other "analog" stuff, other than the
formulas needed to pass physics tests.</p>
<p>So I bought a couple of books on electronics, and bought one of those
Radio Shack "Electronics Learning Lab" thingees. I read the electronics
books, but they didn't do much for me. The first few chapters were just
refreshers of things I remembered from physics classes (V=IR and all
that stuff), and the later chapters were a little incomprehensible (too
many formulas, not much explanation of what the circuits actually do).</p>
<p>The Learning Lab has sat on the shelf. Going through its experiments
would have helped, but I never had time when I had interest, and never
had interest when I had time.</p>
<p>A couple of weeks ago I got into PIC microcontroller programming. My
current project involves some PIC firmware development, and while I
won't be directly involved in that, I wanted to learn enough about the
process that I could make some intelligent decisions about it. (I'm
supposedly directing this effort.)</p>
<p>I bought the "PICkit 1 Flash Starter Kit" from
<a href="http://www.microchip.com/">Microchip</a>. It includes a programmer/testing
board, a couple of PIC microcontrollers (a PIC12f675 and a PIC16F684),
and some tutorials and documentation. I also bought <em>Programming and
Customizing PICmicro Microcontollers</em> by Myke Predko. Using these and a
few little tidbits from the web, I've written the traditional "flashing
LED" program, and also a 7-segment LED controller program.</p>
<p>So far, I've written PIC code in assembly language. The PIC's
instruction set is very small, with only 35 instructions, so I picked it
up pretty quick. There are some differences between programming for PICs
and for other microprocessors, mostly related to lack of RAM, register
bank switching, and the separate code/data spaces, but it's not too hard
once you learn the techniques. It's nice to get back into this kind of
low-level programming.</p>
<p>When my programs get too complex for assembly, I'll probably give
<a href="http://www.rfc1149.net/devel/picforth">PicForth</a> a try. There are C
compilers and other languages for the PIC, but I'd prefer to stay as
low-level as I can. I notice that PicForth doesn't support the
controllers that I have, so I may have to play around with its code
generator a bit.</p>
<p>Wiring the PIC into circuits on the Learning Lab breadboard is helping
me get back into the electronics study. It's easier for me to write code
than to figure out how to wire together a bunch of resistors,
capacitors, and transistors, but once I've got the circuit working I can
play with replacing some of the PIC functions with electronic
components, and vice versa.</p>
<p>So now I have something to do while my helicopter batteries are
recharging.</p>Milestones Reached2004-09-11T16:58:00-04:002004-09-11T16:58:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-09-11:/milestones-reached.html<p>We've reached our deadline, and spent our entire budget. So I'd like to
congratulate everyone on the successful completion of the project.</p>
<p>Unfortunately, we aren't finished. I estimate that we have another 4-6
weeks of work to do, which means we really probably have 8-12 weeks of
work to do …</p><p>We've reached our deadline, and spent our entire budget. So I'd like to
congratulate everyone on the successful completion of the project.</p>
<p>Unfortunately, we aren't finished. I estimate that we have another 4-6
weeks of work to do, which means we really probably have 8-12 weeks of
work to do.</p>
<p>Let the recriminations begin!</p>I Bought an iMac2004-09-09T01:46:00-04:002004-09-09T01:46:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-09-09:/i-bought-an-imac.html<p>I was an early Macintosh user. I bought my first Mac in college
(actually, my parents bought it), around 1987. It was a Mac SE, with 1MB
of RAM and a 20 MB hard drive. A few years later, I got a IIsi, and a
few years after that, I …</p><p>I was an early Macintosh user. I bought my first Mac in college
(actually, my parents bought it), around 1987. It was a Mac SE, with 1MB
of RAM and a 20 MB hard drive. A few years later, I got a IIsi, and a
few years after that, I bought one of the initial PowerMacs.</p>
<p>Like the average Mac user, I couldn't understand why anyone would settle
for an MS-DOS-based PC. The Mac was clearly superior, in every way.</p>
<p>For me, that attitude started changing soon after Windows 95 appeared.
Flawed as it was, Windows 95 was an operating system with pre-emptive
multi-threading and protected memory spaces. Mac OS 7, while sporting a
better user interface, had an obsolete kernel, and Apple wasn't doing
anything about it. I stuck with the Mac for a while, but was no longer
sure of its superiority. When I started using NT 4 heavily at work, my
disenchantment with Mac OS grew. I finally gave up on the Mac in the
late 90's, buying a PC and putting Windows 98 on it.</p>
<p>So, for the past few years, I've been a Windows guy. I've watched
Apple's ups and downs. I was glad when they ditched their old OS in
favor of the NeXT-derived stuff, but by that time I had amassed enough
Windows software and experience that there was no strong reason to
switch back. I had lost all the emotional attachment that the original
Mac had engendered.</p>
<p>But last week, I saw the new iMac, and was immediately interested. It is
so simple and elegant. So I ordered one of the 20" models. It should be
here in early October.</p>
<p>This is the first time a computer has been an "impulse buy". I bought it
because it was cool, not because I have any real need for it. I'm still
not sure what I'll do with it. I don't plan to write software for it. I
probably won't play any games on it. I plan to just stick it on a small
desk in my living room, and use it to read e-mail or browse the web, and
maybe do some digital photo or music editing.</p>
<p>My iMac will just be a little computing sanctuary where Microsoft can't
touch me.</p>Hobbies2004-09-05T22:25:00-04:002004-09-05T22:25:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-09-05:/hobbies.html<p>For most of my adult life, I didn't really have any "hobbies". I spent
most of my free time watching TV, reading books, hanging out with
friends, or improving my programming skills. I didn't consider any of
these activities to be hobbies, and always wondered if I was missing out …</p><p>For most of my adult life, I didn't really have any "hobbies". I spent
most of my free time watching TV, reading books, hanging out with
friends, or improving my programming skills. I didn't consider any of
these activities to be hobbies, and always wondered if I was missing out
on the fun others had with their hobbies.</p>
<p>A couple of years ago, when I quit my job to take time off work, I made
an effort to develop some hobbies. I took sailing lessons, and joined
the local sailing club. I got into digital photography, taking pictures
of my young nieces and messing around with the pictures in Photoshop. I
tried creating music using Cakewalk's SONAR digital audio production
product.</p>
<p>I didn't really stick with any of those hobbies, but they were fun and
led me to try some other things:</p>
<ul>
<li>home-brew beer</li>
<li>self-taught piano lessons</li>
<li>juggling</li>
<li>RC helicopters</li>
<li>electronics</li>
</ul>
<p>I came pretty close to getting into aquariums a couple of weeks ago. I
was going to create a mini-reef marine aquarium, one of the most
difficult to maintain. After reading a couple of books, I finally
decided against it. The helicopter hobby is pretty expensive, and this
marine aquarium thing looked like it could be even worse. So I've
decided to put it off until I buy a house or until I give up the
helicopters.</p>
<p>One concern about my choice of hobbies is that none of them involve
other people. Sure, I "chat" with other hobbyists online who share these
hobbies, but I never actually interact with anyone. I need to find
something that interests me that will get me into groups of people.</p>
<p>All these hobbies are really cutting into my TV-watching time. And one
of these days, I may need to re-classify house-cleaning as a hobby so
that I can find time to do that.</p>Web Site Reconfiguration2004-07-04T13:57:00-04:002004-07-04T13:57:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-07-04:/web-site-reconfiguration.html<p>My web hosting provider was bought out by another provider. That new
provider has now "upgraded" my old account by moving it to their
servers. So now all my CGI scripts aren't working, and I can't use FTP
or SSH to fix them. Grrr.</p>
<p>Thank you iQuest Hosting.</p>iPod mini2004-05-16T16:06:00-04:002004-05-16T16:06:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-05-16:/ipod-mini.html<p>I bought an iPod mini yesterday. It felt good to buy an Apple product
again, after giving up on the Macintosh a few years back.</p>
<p>I've started exercising (This time I'll stick with it! Really!), and so
I wanted a music player to help pass the time and to keep …</p><p>I bought an iPod mini yesterday. It felt good to buy an Apple product
again, after giving up on the Macintosh a few years back.</p>
<p>I've started exercising (This time I'll stick with it! Really!), and so
I wanted a music player to help pass the time and to keep my mind off my
aching muscles.</p>
<p>I've given up on cassette and CD players, due to the hassle of lugging
all those tapes and discs around. I've tried using Windows Media Player
on my Pocket PC, but the low amount of memory on a SD memory card
limited me to just a couple dozen songs. I've looked at MP3 players, but
was underwhelmed. The mini is $250, which seemed a bit high for a music
player, but I decided that the benefits are worth the price. Once I
decided to spend the money, I considered getting one of the full-size
iPods, which cost more but have lots more space. I decided to get the
mini due to the more attractive weight and form factor; price wasn't
really a consideration.</p>
<p>I have a couple of complaints. One is that there is a brief pause
between tracks. I listen to a lot of albums that don't have pauses
between songs (Pink Floyd, for example), and so it is annoying to have
that brief stutter between songs when listening on the iPod. My other
complaint is that iTunes will only import one CD at a time. I've got two
CD drives connected to my PC, and it would be nice if it could rip two
CDs simultaneously as I build up my library.</p>
<p>The Apple Music Store is a nice feature. I bought a few audiobooks
(Steve Martin, George Carlin, and a couple others) and also tried out
Anton Fig's album. I tried buying a John Coltrane album, but got an
error message; I'll have to try that one again later.</p>New Projects2004-05-14T04:57:00-04:002004-05-14T04:57:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-05-14:/new-projects.html<p>I remember, when I was young and foolish, that I was always excited at
the beginning of a new project. I saw the opportunity to create
something new and perfect. I expected to learn new things with new
friends. I was always optimistic.</p>
<p>Now things are much different. When a …</p><p>I remember, when I was young and foolish, that I was always excited at
the beginning of a new project. I saw the opportunity to create
something new and perfect. I expected to learn new things with new
friends. I was always optimistic.</p>
<p>Now things are much different. When a new project starts, I notice the
lack of focus and of a unified vision. I see the counter-productive
agendas of the stakeholders. I wonder if the people I'm working with
know what they are doing. I see people setting one another up to take
the fall when things go badly.</p>
<p>Am I too jaded and cynical, or just realistic? I don't know. But I do
know that I dread new projects. I don't think it is fear of the unknown
causing my dread; I think it is recognition of bad things I've seen
before and expectation that history will repeat itself.</p>
<p>Ignorance is bliss.</p>What to Do Differently This Time2004-05-11T01:47:00-04:002004-05-11T01:47:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-05-11:/what-to-do-differently-this-time.html<p>Now that I am going to be a "lead" again, I'm reviewing what I've done
wrong in the past. My last stint as a manager was the worst professional
experience of my life, and I don't want to repeat it.</p>
<p>One good thing about my new project is that I'm …</p><p>Now that I am going to be a "lead" again, I'm reviewing what I've done
wrong in the past. My last stint as a manager was the worst professional
experience of my life, and I don't want to repeat it.</p>
<p>One good thing about my new project is that I'm getting in at the
beginning. I'll understand the history better and I'll have some
influence on the overall plan. Also, we don't yet have requirements, a
budget, or deadline. I'm sure budgets and deadlines will be imposed soon
enough, but this time I'll know I should quit instead of accepting an
impossible task.</p>
<p>I've started by writing a set of use cases and learning all I can about
the prototypes and the envisioned solution technologies. The use cases
have been helpful in determining what I don't understand. This is one of
those products where everything is easy when stuff works, but there are
lots of ways it won't work, so the use cases give me a place to explore
all the various kinds of error processing we will have to provide.</p>
<p>Our organization has a terrible "process" for software development, and
so of course everyone is insisting that this is the project where we can
start to do some things right. That sounds nice, but I have to be
realistic: a good process doesn't get created overnight just because
somebody wants it. There is no agreement among the principals about what
a good process would be, so I am not hopeful that a single set of rules
will be adopted by everyone. My reaction to this will be to focus on
agile processes, and then try to make them look like whatever processes
the higher-ups starting asking for.</p>
<p>I'm going to delegate a lot more responsibility to others this time
around. Last time I was a manager, I tried to do all the hard work
myself, and to help everyone else with the rest of the work. That really
burned me out. So this time, I'll assign myself a reasonable amount of
work, and assign everyone else a reasonable amount of work, and I won't
try to do anyone else's job.</p>
<p>I plan to focus my own work on design and implementation of
"infrastructure" types of stuff. I hope this keeps me in the center of
things. Of course the danger is that I'll make myself a critical
resource, which is bad for a manager to be, so I'll be sure to involve
others in what I'm working on. I'd like to do pair programming, but I'm
not sure whether this company's culture will accept it.</p>
<p>Finally, the most important thing I learned from last time is that this
project is not my life. It's just another computer system that will make
money for some people I don't know. It's not going to change the world.
Nothing important is lost if I don't get it done. It's not worth
sacrificing myself to complete it.</p>Managing Again2004-05-07T03:07:00-04:002004-05-07T03:07:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-05-07:/managing-again.html<p>Why do they keep doing this to me? I was just minding my own business,
writing buggy software more slowly than expected. But they've made me a
manager again. I'm the "lead engineer" on a "highly visible" project.
It's a "great opportunity", generating "a lot of excitement" in upper
management …</p><p>Why do they keep doing this to me? I was just minding my own business,
writing buggy software more slowly than expected. But they've made me a
manager again. I'm the "lead engineer" on a "highly visible" project.
It's a "great opportunity", generating "a lot of excitement" in upper
management levels. It "could lead to other things".</p>
<p>Time to start looking for another job, I guess.</p>Too Busy2004-05-04T03:05:00-04:002004-05-04T03:05:00-04:00Kristopher Johnsontag:undefinedvalue.com,2004-05-04:/too-busy.html<p>I hate to neglect my blog, but I've been too busy the past couple of
months. According to my timesheets, I've been working over 250
hours/month. This needs to change.</p>
<p>Being deluged with too much work has got me thinking about my desired
amount of work. I know that …</p><p>I hate to neglect my blog, but I've been too busy the past couple of
months. According to my timesheets, I've been working over 250
hours/month. This needs to change.</p>
<p>Being deluged with too much work has got me thinking about my desired
amount of work. I know that for a lot of people, the desired amount of
work is "none". But that's not me. I like to have things to do, some
goals, and some deadlines. I like to feel that I'm doing something that
others find useful. This is probably driven by some juvenile need for
validation and acceptance, but whatever it is, I need it.</p>
<p>I hate having too much work as much as anyone does. Whenever I have to
work 80 hours in a week, I start fantasizing about writing that perfect
resignation letter, or about screaming at the boss. After the indulgence
in fantasy wears out, I update my resume and look at job postings.</p>
<p>But even that is a fantasy. From experience, I know that the other jobs
are just as bad as the one I have.</p>
<p>I do notice that the project managers and salespeople aren't working 80
hours a week. That gives me some ideas for desirable career paths.</p>I Suck2004-03-31T06:06:00-05:002004-03-31T06:06:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-03-31:/i-suck.html<p>Well, my project at work is nearing its deadline, which means we are all
working 70-80 hours a week without any days off.</p>
<p>This is dumb. Dumb, dumb, dumb. I'm stupid for doing it, and the people
I work with are just as stupid. But we're doing it anyway. Why …</p><p>Well, my project at work is nearing its deadline, which means we are all
working 70-80 hours a week without any days off.</p>
<p>This is dumb. Dumb, dumb, dumb. I'm stupid for doing it, and the people
I work with are just as stupid. But we're doing it anyway. Why?</p>
<p>Of course, the only reason I'm doing it is because everyone else is
doing it. I know I am not more productive this way. I know my
assignments won't get done any faster. I know I'm burning myself out
without good reason. I know it's the incompetent managers who should be
suffering rather than me. But here I am, tired, pissed-off, and
fantasizing about the perfect resignation letter.</p>
<p>I often think that I've matured as a developer (and as a person). I
think that I've learned my lessons and will not repeat past mistakes.
And then I make the same mistakes over again. What's worse: making a
mistake I've never made before, or making the same mistake again and
again? What the hell is wrong with me?</p>
<p>I'm weak. I don't have the courage to say "No", or to ask "Remember what
complete shit we created the last time we worked ourselves to
exhaustion?" I've spent the past six months trying to fix all the stuff
we threw together at the last minute from the last project, and now I
know we're setting ourselves up for another six months of bug fixing on
this project.</p>
<p>I'm on autopilot. I'm just doing what's expected of me. I look at the
list of fifty things that need to get done in the next week, and instead
of saying "This is impossible," I just go on to the next item.</p>
<p>Maybe next time I'll do the right thing. But this time, I'm sticking
with what is familiar. I'm an idiot.</p>Adaptable Developers2004-03-10T03:53:00-05:002004-03-10T03:53:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-03-10:/adaptable-developers.html<p>The MSDN feed indicates that there are several new walkthroughs
available today:</p>
<ul>
<li>Build a Data-Driven Website Using Visual Basic .NET and Visual Studio .NET 2003</li>
<li>Build a Data-Driven Website Using Visual C# .NET and Visual Studio .NET 2003</li>
<li>Build a Data-Driven Website Using Visual Basic .NET and ASP.NET Web …</li></ul><p>The MSDN feed indicates that there are several new walkthroughs
available today:</p>
<ul>
<li>Build a Data-Driven Website Using Visual Basic .NET and Visual Studio .NET 2003</li>
<li>Build a Data-Driven Website Using Visual C# .NET and Visual Studio .NET 2003</li>
<li>Build a Data-Driven Website Using Visual Basic .NET and ASP.NET Web Matrix</li>
<li>Build a Data-Driven Website Using Visual C# .NET and ASP.NET Web Matrix</li>
<li>Build a Data-Driven Website Using Visual C# .NET and the .NET Framework SDK</li>
</ul>
<p>I haven't looked at these articles, but I assume they contain the same
information, except some are in C# and some are in VB.NET, and some use
Visual Studio .NET and the others use different tools. At first I
wondered why there is not a "Build a Data-Driven Website Using Visual
Basic .NET and the .NET Framework SDK" (the one missing combination),
but then I realized that VB .NET developers are assumed to be incapable
of using command-line tools.</p>
<p>At first I was pissed off at the MSDN folks for once again promulgating
the belief that VB .NET programmers can't read C# (and vice versa), or
that a Web Matrix programmer won't be able to figure out which menu
items to use to create an application, having only been led step-by-step
through the Visual Studio process. But of course, the MSDN folks have to
do this because <em>there really are</em> a lot of developers who are unable or
unwilling to make sense out of code that is not in the one language they
know, or who don't know their tools well enough to use them after being
shown a demonstration with a similar tool.</p>
<p>I have some sympathy when a C programmer has trouble reading Haskell, or
a Fortran programmer has trouble reading Prolog. But C# and VB.NET are
essentially the same language, with some minor syntactical differences.
If you know one of those languages, you should have no problem reading
the other unless you are either incredibly stupid or incredibly
closed-minded. If you can't figure out that a "project" in one IDE is
like a "solution" in another IDE, then please find another career so I
won't risk running into you someday.</p>
<p>If you are learning .NET, you should be able to read C#, VB.NET, and
JScript.NET with equal facility. C programmers and C++ programmers
should have no trouble reading one anothers' code, nor should Lisp and
Scheme programmers, or Smalltalk and Ruby programmers. If you are
unwilling to learn even a little about things at the boundaries of your
existing knowledge, you have no business calling yourself a professional
or being proud of your skills, no matter what field you are in.</p>
<p>I look forward to the day when MSDN could publish an article called
"Build a Data-Driven Website Using .NET", without the need to focus on
specific languages or specific tools. I know that day will never come,
so I'll have to settle for finding colleagues who would be capable of
understanding such an article.</p>Helicopter vs. Lamp: No Winner2004-03-06T22:44:00-05:002004-03-06T22:44:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-03-06:/helicopter-vs-lamp-no-winner.html<p>I've reached the point where I can "hover out" a battery pack. That is,
I can hover the helicopter without crashing until the battery runs out.
This is much harder than it might sound. Hovering a micro-helicopter is
much like riding a unicycle, with an extra dimension. Keeping the thing …</p><p>I've reached the point where I can "hover out" a battery pack. That is,
I can hover the helicopter without crashing until the battery runs out.
This is much harder than it might sound. Hovering a micro-helicopter is
much like riding a unicycle, with an extra dimension. Keeping the thing
steady for a few minutes takes a lot of concentration, and is mentally
exhausting. It's taken several weeks of practice, but I can do it.</p>
<p>So I decided to try something more interesting: take off from the dining
room floor and land on the living room coffee table. I've got a small
apartment, so this is a flight of only twelve or fifteen feet, but it
was my first attempt to travel from a point A to a different point B.
This also tests my altitude control, as I need to pass over some
furniture to get to the coffee table. The coffee table is pretty big, so
landing shouldn't be too difficult.</p>
<p>I took off smoothly. The helicopter kept drifting to the left. I should
have landed and adjusted the trim, but decided to just keep going. I
made it about halfway to the destination, but the leftward drift took
the helicopter into a tabletop lamp. The crash was pretty spectacular,
as parts flew in all directions.</p>
<p>The lamp shade looked bad, but was easy to fix. It is covered with
frilly fabric, and I was able to adjust it so that the frills looked
fine.</p>
<p>The chopper needs a little work. I broke the tail rotor into two pieces,
but I already have a replacement part. The landing gear broke as well,
but that gear has been breaking continuously. It's a common problem with
the Ikarus Piccolo landing gear: after it breaks once, it will break on
any hard landing.</p>
<p>So I need to balance a new tail rotor and wait for the glue on the
landing gear to dry. I'll be flying again later tonight. I may move the
lamp before I try.</p>Installing QNX 42004-03-03T05:38:00-05:002004-03-03T05:38:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-03-03:/installing-qnx-4.html<p>I installed <a href="http://www.qnx.com">QNX</a> 4 on a machine today, for some
upcoming embedded systems work. This is not the first time I've
installed it, but of course I didn't take any notes the last time I did
it, so it was almost a new experience.</p>
<p>My first hurdle was figuring out …</p><p>I installed <a href="http://www.qnx.com">QNX</a> 4 on a machine today, for some
upcoming embedded systems work. This is not the first time I've
installed it, but of course I didn't take any notes the last time I did
it, so it was almost a new experience.</p>
<p>My first hurdle was figuring out how to run the installer. The
installation program runs on Windows, but the machine where I wanted to
install only had Linux.</p>
<p>"No problem," I thought, "it's a bootable CD, so I can just boot from
that". My next hurdle was the discovery that while the machine had a
CD-ROM drive inside it, there were no power or data cables connected to
it. And there was a good reason for that: the drive didn't have an IDE
interface, but something with a whole lot more pins than any ribbon
cable I could find. So I scrounged up another CD-ROM drive and plugged
it in. Then it took several minutes of rebooting and cursing before I
had to conclude that the ancient BIOS on that 300 MHz Pentium II machine
wouldn't let one boot from the CD.</p>
<p>I was starting to think I'd have to install Windows on that machine just
so I could run the installer for QNX. This would have been a problem,
because this was a CD-ROM drive, and we only have DVDs for installing
Windows. But poking through the contents of the CD, I found a .BAT file
that would create a bootable floppy for installing a demo version of
QNX. I didn't want a demo version, but a few more minutes of poking
around (and guessing) led me to discover how to create a bootable floppy
for installing the full QNX.</p>
<p>So, finally I booted the floppy and went through the QNX installation. I
actually had to do this twice, because the first time I couldn't figure
out how to delete all the Linux partitions to make the entire hard drive
a QNX partition. But I eventually had a bootable QNX box.</p>
<p>The last problem was getting a working network interface. The machine
(which I inherited from a recently departed colleague) had both built-in
Ethernet on the motherboard and a PCI Ethernet card. I tried configuring
both interfaces for DHCP and plugging both into my switch, but neither
card was able to retrieve an IP address. I won't bore anyone with the
details (assuming they are not thoroughly bored already), but I
eventually figured out that there was no driver for the card, and
removing it let the built-in Ethernet port start working. I had to give
it a static IP address at first, but eventually got it doing DHCP by
ignoring the nice network-configuration GUI and instead using
"dhcp.client -h mymachine -i en0 &".</p>
<p>So it wasn't impossible, but this illustrates one of the reasons Windows
is going to remain popular. I've heard people with horror stories about
Windows installs, but every one I've done has been basically (a) boot
with the CD, (b) follow the onscreen instructions, (c) OK, everything
works. Sure, QNX is not a "consumer" operating system, and a lot of my
problems were related to having ancient hardware, but still, I think it
should have been easier.</p>My Evil Web Site2004-02-28T20:26:00-05:002004-02-28T20:26:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-02-28:/my-evil-web-site.html<p>Using the <a href="http://homokaasu.org/gematriculator/">Gematriculator</a>, I am
shocked to discover that my web site <a href="http://kristopherjohnson.net/">http://kristopherjohnson.net/</a> is
60% evil.</p>
<p>Luckily, my blog is currently 69% good, and my
<a href="http://kristopherjohnson.net/kjresume.html">resumé</a> is only 34% evil,
so I may have hope for salvation.</p>
<hr>
<p><a href="http://homokaasu.org/gematriculator/?referer"><img alt="This site is certified 33% EVIL by the
Gematriculator" src="http://homokaasu.org/pics/g/e33.jpg"></a></p>NYC Crosswalk Buttons Don't Do Anything2004-02-28T20:25:00-05:002004-02-28T20:25:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-02-28:/nyc-crosswalk-buttons-dont-do-anything.html<p>Years ago, I worked on the New York City Vehicular Traffic Control
System (VTCS), the system that controls several thousand computerized
traffic signal throughout the five boroughs. Slashdot had an article
today about a NY Times
<a href="http://www.nytimes.com/2004/02/27/nyregion/27BUTT.html?ex=1078462800&en=39bb50fe4af084ea&ei=5062&partner=GOOGLE">article</a>
that says few of the crosswalk buttons actually work.</p>
<p>It's not the first …</p><p>Years ago, I worked on the New York City Vehicular Traffic Control
System (VTCS), the system that controls several thousand computerized
traffic signal throughout the five boroughs. Slashdot had an article
today about a NY Times
<a href="http://www.nytimes.com/2004/02/27/nyregion/27BUTT.html?ex=1078462800&en=39bb50fe4af084ea&ei=5062&partner=GOOGLE">article</a>
that says few of the crosswalk buttons actually work.</p>
<p>It's not the first time I've read a Times article related in some way to
that system. We made the paper a few times during system deployment,
when traffic got snarled throughout the city. What proud feelings we
had, knowing what an effect our work had on the lives of millions of
people.</p>
<p>If I was malicious and uncaring, I could tell everyone about a lot of
other aspects of the traffic system that may not work as some people
would expect, or about some of the politics surrounding design and
deployment (hint: the traffic system exists primarily as a source of
unionized jobs; helping people move around the City is just a
side-effect). But that would eliminate work for Times reporters, so I'll
just leave them guessing.</p>Branching and Merging2004-02-28T16:45:00-05:002004-02-28T16:45:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-02-28:/branching-and-merging.html<p>A few months ago, we started developing a new product that was a major
extension of an existing product. While we may have been able to keep
one codeline that supported both the old product and the new product,
many factors led us to decide to branch the codeline. I …</p><p>A few months ago, we started developing a new product that was a major
extension of an existing product. While we may have been able to keep
one codeline that supported both the old product and the new product,
many factors led us to decide to branch the codeline. I have continued
to maintain the old codeline for existing deployments, while I and a
group of others have worked on the new codeline.</p>
<p>In the new codeline, we have tried to correct many of our past sins. The
original product was developed under a tight deadline with vague
requirements and insufficient testing, and the codeline reflects that.
The new codeline has some significant architectural changes in addition
to having the new features.</p>
<p>I have been trying to keep things in sync between old and new. When we
find and fix a bug in one codeline, or when a feature change is made
that is desirable for both codelines, I merge that change into the
other. This was easy at first, but as the underlying architectures
diverge, it has become more difficult.</p>
<p>One problem is that we are using SourceSafe, and I can't find any good
features for assisting in analyzing the differences between branches to
intelligently decide what needs to be merged and what does not. At the
level of individual files, branches can be merged, but I can't find an
easy way to get a report of all changed for all files in both codelines
and to merge individual sets of changes. (Maybe SS has better features,
but I can't find them.) So we have had an informal manual process, which
relies on other people telling me about things that have to be merged,
and on my having time to do the merge.</p>
<p>This situation sucks, so what I decided to do about it was to write some
Python scripts to help me. The first script I've written just compares
the files in two directories, and writes diff files to a third
directory. My hope was that this would give me a list of a dozen or so
non-matching files, and I could spend the weekend getting everything
back in shape. I'd then just run the script every few days to find new
changes.</p>
<p>Unfortunately, when I ran the script, I found that the number of
non-matching files was 135. Each codeline also has a few dozen files
that are unique (that is, not corresponding to a file in the other
project). Clearly, this is going to take more than a weekend.</p>
<p>I'm not sure exactly what I should have done to prevent things from
getting so bad. Obviously, having one person (myself) be the only person
working on synchronization between the codelines was a problem. Not
having automated unit tests is another factor, because without such
tests, it is dangerous to have changes merged back and forth without a
lot of analysis. A better version-control tool would have helped,
particularly one that supports merging an identifiable set of related
changes to multiple files rather than forcing an all-or-nothing merge of
all changes on a file-by-file basis.</p>
<p>Ultimately the core problem is that our original codeline had to be
thrown together hastily, leading to code that is difficult to adapt to
our new requirements while still meeting the old requirements. So I can
blame management for not giving us sufficient time. Unfortunately,
management isn't going to fix my problem for me, so I'll be working this
weekend.</p>
<p>(For another story about branching/merging issues, I recommend the story
of <a href="http://weblogs.asp.net/Rick_Schaut/archive/2004/02/26/80193.aspx">Mac Word 6.0</a>.)</p>What Can One Person Do?2004-02-28T16:43:00-05:002004-02-28T16:43:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-02-28:/what-can-one-person-do.html<p>I'm all in favor of teamwork. I like the sharing of ideas, the
leveraging of individuals' skills, and the camraderie that comes from
working with other people. However, I notice that a lot of my work
habits are focused on insulating myself from others.</p>
<p>It's not that I avoid other …</p><p>I'm all in favor of teamwork. I like the sharing of ideas, the
leveraging of individuals' skills, and the camraderie that comes from
working with other people. However, I notice that a lot of my work
habits are focused on insulating myself from others.</p>
<p>It's not that I avoid other people, or that I try to keep them away from
me. Rather, a lot of my habits are focused on eliminating any dependence
upon others. I keep everything I can on my own machine, in case the
network goes down. I keep my own backups of important files. I know how
to set up the server software on my own machine in case the real
development server goes down or gets funky. When something breaks, I
tend to fix it myself and then notify others later, rather than
co-ordinating with them first.</p>
<p>While this self-reliance and initiative keeps me out of a lot of jams, I
wonder if it interferes with teamwork. Would we work more closely
together if we were more dependent on one another? Are we each doing a
lot of redundant work to keep ourselves self-reliant?</p>
<p>Being dependent upon others is a little scary, as is having others
depend upon you. You never know how they might unintentionally mess you
up, and there may even be a few who will do with it intentional malice.
But I have learned that I can trust others. If I ever think I can't
trust my co-workers, it will be time to find another job.</p>
<p>Even with that trust in others, I am going to continue my self-reliant
habits. Groups don't really accomplish things; individuals do, and I
want to be an effective individual.</p>Considering an MBA Program2004-02-24T15:14:00-05:002004-02-24T15:14:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-02-24:/considering-an-mba-program.html<p>Lately, I've been thinking a lot about going for an MBA. A few years
ago, I would never have considered such a thing, but now it seems like a
solution to several "forces" in my life:</p>
<ul>
<li><strong>I'd like to go back to school.</strong> I think I'd like being in an …</li></ul><p>Lately, I've been thinking a lot about going for an MBA. A few years
ago, I would never have considered such a thing, but now it seems like a
solution to several "forces" in my life:</p>
<ul>
<li><strong>I'd like to go back to school.</strong> I think I'd like being in an environment where I am learning things and interacting with other students. I think I wasted my time in undergraduate studies, being so shy that I never talked to other students or to my professors, and would like to try a different tack this time around.</li>
<li><strong>I have an entrepreneurial bent.</strong> Whenever I read books or articles that describe what makes a good entrepreneur, I see myself. I am tired of working for other people. I am independent. I want to choose what I work on. Of course, I also lack some of the good entrepreneurial skills, which is why I'd like to learn more about business.</li>
<li><strong>My career has reached a dead end.</strong> All anyone wants to pay me to do is to write C++ GUI applications. That was interesting the first few dozen times I did it, but now there is no joy in it. And when I look around the IT industry, I don't see anything that I want to get into. I need a change, and I don't know exactly what I want to do, so learning more about business seems like something that could help me find a new direction. Even if I don't figure out what I want, having an MBA and a technical background will probably keep me hireable.</li>
<li><strong>People are trying to make me into a manager anyway.</strong> I keep getting pushed into management jobs, and into situations where I am doing business planning, marketing, etc. My primary objection to this is that I have no idea what I'm doing when thrust into those roles. Some formal education in the subject would ease my concerns.</li>
</ul>
<p>The University of Georgia has an evening MBA program at a location close
to me. So I figure it can't hurt to sign up for a semester's worth of
classes and see whether this is for me. If not, it's just a couple grand
down the drain.</p>My R/C Helicopter2004-02-07T17:39:00-05:002004-02-07T17:39:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-02-07:/my-rc-helicopter.html<p>I've had my <a href="http://www.pgoelz.com/piccolo1.html">Piccolo R/C model helicopter</a> for a couple of weeks
now. I've had a total flying time of about 60 seconds. The average
flight lasts two or three seconds, and many of them end in a "crash",
which I define as "a landing that makes me wait …</p><p>I've had my <a href="http://www.pgoelz.com/piccolo1.html">Piccolo R/C model helicopter</a> for a couple of weeks
now. I've had a total flying time of about 60 seconds. The average
flight lasts two or three seconds, and many of them end in a "crash",
which I define as "a landing that makes me wait for glue to dry before I
can fly again". But they say any landing you can walk away from is a
good one, and I've walked away from them all.</p>
<p>I've spent about $50 for replacement parts over these two weeks (I've
broken the swashplate, the tail rotor, the main rotor and one of the
push rods). I've also preemptively purchased $50 worth of additional
parts that I expect to need in the coming weeks. I was grounded for four
days waiting for parts to arrive, so I'm planning ahead.</p>
<p>It's been a lot of fun. I haven't built any models since I was a
teenager, so putting this thing together was almost a new experience.
The instructions were clear enough to follow, but also cryptic enough
that I had to figure out many things for myself. I had to develop my own
assembly techniques for manipulating small parts; I never thought I'd be
able to get the 1.5-mm-long bolts screwed into the right places with my
normal-sized hands and tools, but I did it.</p>
<p>The one thing I haven't quite figured out is the Piccoboard, a little
printed-circuit board that handles the mixing of main rotor and tail
rotor throttles. It has a couple of potentiometers to be adjusted, and
I'm not sure I'm doing it right. It seems that any adjustment either has
no effect or has a disasterous effect. More experimentation is needed.</p>
<p>Most of my flying has been inside my apartment, which is really too
small for a beginning helicopter pilot (hence the frequent crashes). The
chopper hasn't done much damage to my walls or furniture. There is a
nice open area in the parking lot where I have flown a few times, but it
has been pretty windy lately.</p>
<p>I don't quite have the hang of stable hovering yet, but I'm pretty sure
I'll be able to do it pretty soon. Using the computer simulator has
helped me get accustomed to the controls, but controlling a real
helicopter in front of me is a lot different from controlling an
onscreen copter. I never have to worry about flying the helicopter into
my own body with the simulator, whereas that is a constant concern with
the real one. But the real one is actually a little easier, because I
can use binocular vision and my ears to get a better sense of what the
heli is doing and where it is heading.</p>
<p>Having a little helicopter is really cool. I've found that I get a lot
less frustrated at work these days, because I'm always thinking about
that next flight.</p>Stick with the Defaults2004-01-31T05:42:00-05:002004-01-31T05:42:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-31:/stick-with-the-defaults.html<p>"It crashes in the call to bpGetStatus()."</p>
<p>"Do you have structure-packing set to 1?"</p>
<p>"Uh, no. Let me try that..."</p>
<p><em>Five minutes later:</em></p>
<p>"OK, now it crashes the output routine."</p>
<p>"Are you using the standard library?"</p>
<p>"Yes. Why?"</p>
<p>"You need to use the Gee-Cool-Whizzy port of the STL. I'll e-mail …</p><p>"It crashes in the call to bpGetStatus()."</p>
<p>"Do you have structure-packing set to 1?"</p>
<p>"Uh, no. Let me try that..."</p>
<p><em>Five minutes later:</em></p>
<p>"OK, now it crashes the output routine."</p>
<p>"Are you using the standard library?"</p>
<p>"Yes. Why?"</p>
<p>"You need to use the Gee-Cool-Whizzy port of the STL. I'll e-mail you
the URL."</p>
<p>"OK, I'll try that..."</p>
<p>Don't do this to people. The default settings for many compilers and
IDEs are perfectly reasonable. If you are going to use some bizarre
settings, write up a document or something to explain what other have to
do (and explain <em>why</em> while you're at it). The rest of us will
appreciate it.</p>
<p>But even better, don't use bizarre settings. I've never understood why
some people go to such lengths to find what they consider to be the
perfect set of compiler flags for their particular application. If
proper operation of the program depends upon some strange set of
compiler options, then it is almost certain that the programmer is doing
something (a) non-standard, (b) tricky, (c) incompatible with other
people's code, and (d) that is going to break when moved to another
platform or another compiler. If proper operation doesn't depend on a
strange set of compiler options, then why use them at all?</p>
<p>There are exceptions to this rule. For example, Visual C++'s default
settings are to statically link to a single-threaded version of the C++
runtime. For real work, almost everyone uses dynamic linking to the
multithreaded DLL. But this is not really an exception: if <em>everyone</em>
uses the same non-default setting, then you can consider it to be a
standard.</p>
<p>Whenever you are doing something that nobody else does, and which makes
life more difficult for everyone else, you really need to ask yourself
whether you are doing the right thing. Usually, the answer will be <em>No</em>.
Sometimes following the herd is best for everyone.</p>C++ Testing Frameworks2004-01-25T14:14:00-05:002004-01-25T14:14:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-25:/c-testing-frameworks.html<p>I've always rolled my own unit-testing frameworks for C++. I don't like
CppUnit, because it is too Java-like and not C++-like. It has always
been easier to throw together what I need than to try to figure out
someone else's framework. I can throw together the basics in ten …</p><p>I've always rolled my own unit-testing frameworks for C++. I don't like
CppUnit, because it is too Java-like and not C++-like. It has always
been easier to throw together what I need than to try to figure out
someone else's framework. I can throw together the basics in ten
minutes, and then evolve the test rig as necessary.</p>
<p>So I'm not really sure why I decided to re-examine off-the-shelf
frameworks for my current testing needs. I guess it comes down to a
desire to learn something new, and to make sure nobody else is doing
something smarter than I am.</p>
<p>After looking around, I settled on the <a href="http://www.boost.org/libs/test/doc/index.htm">Boost Test Library</a>. What I like most
about this library is that it can be used by simply #include-ing a
header file; there is no need to build a library and link to it. The
Boost Test Library worked out-of-the-box. The only special thing I did
was to use a <a href="http://www.codeproject.com/debug/debugout.asp">debug output stream</a> so that the
output shows up in the Visual C++ output window. The output format makes
it possible to double-click a line in the output window and VC++ will
open the source file and go to the source line. There is no need for a
TestRunner-style GUI.</p>
<p>I also ran across Michael Feathers's <a href="http://c2.com/cgi/wiki?CppUnitLite">CppUnitLite</a>. He created this as a
reaction to the bloat of CppUnit. CppUnitLite is intended to be just a
simple example of a testing framework, which should be modified and
extended as necessary by its users. While I like the idea of using a
simple barebones framework and modifying it as needed, CppUnitLite still
seems too complex to me.</p>
<p>I think that C++ unit testing is one of those things where an
off-the-shelf framework never seems right. I've come to believe that
small off-the-shelf frameworks are a bad idea. The smaller the
framework, the less it does for you and the easier it would be to roll
your own that does the job better. Only really big frameworks (MFC,
etc.) provide enough value to justify spending the time to learn them.
C++ unit testing is just too easy to justify learning a framework.</p>
<p>But I'll keep using the Boost Test Library for now. I hope I'll learn
something in the process. Using an off-the-shelf library might help me
to "test-infect" my co-workers.</p>Reinventing Wheels2004-01-23T14:16:00-05:002004-01-23T14:16:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-23:/reinventing-wheels.html<p>I have a sudden need for a store-and-forward mechanism for a C++
application I'm working on. I'm sure countless others have done this,
and there is probably an off-the-shelf solution somewhere I could use,
but I have decided to implement one from scratch.</p>
<p>I did spend some time on Google …</p><p>I have a sudden need for a store-and-forward mechanism for a C++
application I'm working on. I'm sure countless others have done this,
and there is probably an off-the-shelf solution somewhere I could use,
but I have decided to implement one from scratch.</p>
<p>I did spend some time on Google trying to find something that would suit
my needs, but none of the hits gave me a good feeling that I would
actually save any time or effort by using an off-the-shelf product. I
have to have this feature implemented in a couple of days, or the world
is going to end (or so customer believes), so I don't have a lot of time
to figure out someone else's product, and I definitely don't have time
to wait for a bug fix. So I've satisfied myself that I am not
reinventing the wheel without good reason.</p>
<p>The buy-vs.-build question is one that comes up often, and I am never
satisfied with my decision. I was once strongly in favor of "buy", but
after being stung by many low-quality products over the years, I now
tend to believe I can usually do better by myself.</p>
<p>The best thing about doing it myself is that the solution will be
specifically designed for my use. I don't have to learn how to manage
all the various configuration parameters that a generic off-the-shelf
"solution" would have. I don't have to worry about whether it will be
compatible with my compiler, my OS, and the other libraries I am using.
I don't have to figure out whether it is thread-safe, or how it manages
memory, or how to properly initialize and terminate it.</p>
<p>And of course, doing it myself is a lot more fun.</p>Pay Me What I'm Worth2004-01-18T18:54:00-05:002004-01-18T18:54:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-18:/pay-me-what-im-worth.html<p>Recently receiving a nice raise got me thinking about the relationship
between work and financial compensation.</p>
<p>Some people claim that high salaries encourage hard work. Maybe it does
for some people, but it doesn't work that way for me. My work ethic has
little to do with how much I …</p><p>Recently receiving a nice raise got me thinking about the relationship
between work and financial compensation.</p>
<p>Some people claim that high salaries encourage hard work. Maybe it does
for some people, but it doesn't work that way for me. My work ethic has
little to do with how much I am being paid, or how high my taxes are.
I've always felt that if one has accepted a job, one does it as well as
possible. I expect to be paid well because I do a good job; I don't do a
good job because I want a raise. If I'm not being paid enough, I may
quit, but I won't slack off.</p>
<p>I know people who obsess over exactly what percentage their raise was,
and how that compares to others' raises.</p>
<p>During the 90's, I was getting raises of 20%, 25%, or more per year for
a while. Those raises were so ridiculously high that I will probably
never be able see any raise as being indicative of my abilities; they
are more a reflection of how well my employer is doing, and how much
clout my manager has.</p>
<p>I have never chased after maximizing my income, but have been fortunate
to receive nice salaries. Making lots of money has provided a counter to
<a href="http://fecolumnists.expressindia.com/full_column.php?content_id=32033">Imposter Syndrome</a>.
My work must be providing some sort of value, although I've never
figured out exactly what it is. So for me, salary is symbolic of
approval and respect.</p>
<p>As long as I'm being paid "enough", I don't worry too much about salary.
I just want to feel that I am being paid what I am worth, and right now,
I think I'm being paid a lot more than I'm worth. (But please don't tell
my boss.)</p>Software and Hardware2004-01-14T03:04:00-05:002004-01-14T03:04:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-14:/software-and-hardware.html<p>Charles Miller had an <a href="http://fishbowl.pastiche.org/2004/01/14/blood_sacrifice">amusing blog entry</a> about
the hazards of computers. I am currently working with development of
embedded systems software, so I spend a lot of my day plugging and
unplugging PCI cards, comm ports, etc. My hands are covered with little
scratches, cuts, and punctures.</p>
<p>Computer programming …</p><p>Charles Miller had an <a href="http://fishbowl.pastiche.org/2004/01/14/blood_sacrifice">amusing blog entry</a> about
the hazards of computers. I am currently working with development of
embedded systems software, so I spend a lot of my day plugging and
unplugging PCI cards, comm ports, etc. My hands are covered with little
scratches, cuts, and punctures.</p>
<p>Computer programming is supposed to be a cushy intellectual job, but
it's amazing how much physical labor and torment programmers go through.
RSI and sore backs are common. Electric shocks are common as well. We
have to crawl around on floors to figure out which cable is connected to
what. We've all over-strained ourselves carrying around big CRTs and
servers. Many of us have had equipment racks fall on top of us.
Incidents like these stress the importance of the first syllable of
<em>hardware</em>.</p>
<p>I've heard that there are some programmers who never have to deal with
hardware. The computer is off in some other room somewhere, and it is
someone else's job to keep it running. Somebody else installs the device
drivers, and configures the networks, and connects the keyboards and the
monitors. I've always felt that these programmers are somewhat like the
ivory-tower academics who never have to deal with the reality of
computers. Without playing with the hardware, how can one really
appreciate what the software does?</p>Helicopters and Simulations2004-01-11T19:55:00-05:002004-01-11T19:55:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-11:/helicopters-and-simulations.html<p>I've always been fascinated by helicopters. The way they float in the
air, and the throbbing "chop-chop-chop" provide a Zen-like experience
for me, where I can stop thinking and just <em>experience</em> the helicopter
overhead.</p>
<p>Actually learning to fly a helicopter would be nice, but I'm not willing
to devote the …</p><p>I've always been fascinated by helicopters. The way they float in the
air, and the throbbing "chop-chop-chop" provide a Zen-like experience
for me, where I can stop thinking and just <em>experience</em> the helicopter
overhead.</p>
<p>Actually learning to fly a helicopter would be nice, but I'm not willing
to devote the time and money required to do so. But I like models. When
I was a kid, I spent many hours/days with the Mattel
<a href="http://www.whirlybirdcentral.com/downloads/7604.pdf">VertiBird</a> toy
helicopter. I looked into RC helicopters a few years ago, but at the
time they were very expensive and lacking in capabilities.</p>
<p>A couple of months ago, my interest was re-kindled when I ran across
some small electric RC helicopters designed to fly indoors. This is
perfect for me; I am an apartment dweller, and I don't have wide open
spaces available for larger RC aircraft. The thought of flying a small
chopper from room to room intrigued me. I put in on my Christmas list,
but I guess my friends and family didn't want to spend several hundred
dollars on my gift (cheap buggers). So I guess I'm going to have to buy
it myself.</p>
<p>While reading various online pages about the mini-helicopters, I noticed
many recommendations to "use the simulator" when learning to fly. At
first I was amused to find out that there are flight simulators for
model aircraft, but it does make sense. A crash of a model can require
expensive repairs (both to the craft and to whatever it hit), so a safe
simulation is important. So I've bought the
<a href="http://www.rc-soar.com/tech/piccofly.htm">simulator</a>. I hope to have
several hours of simulated flight time under my belt before flying the
real model helicopter.</p>
<p>So, my interest in real helicopters led to an interest in model
helicopters, and that in turn led to an interest in simulated model
helicopters. I'm definitely moving between meta-levels here. My only
regret is that neither the model nor the simulation is going to provide
a satisfyingly thunderous "chop-chop-chop". I guess I'll need a
simulated digitized synthesized sound effect for that.</p>Reading Beginners' Books2004-01-10T00:54:00-05:002004-01-10T00:54:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-10:/reading-beginners-books.html<p>I am reading the Pragmatic Programmers' new "Starter Pack" books. Most
of the content is nothing new to me, but I always enjoy reading good
books about the basics.</p>
<p>I like seeing things put simply. The real world is rarely simple, and we
forget how following a few easy-to-understand principles …</p><p>I am reading the Pragmatic Programmers' new "Starter Pack" books. Most
of the content is nothing new to me, but I always enjoy reading good
books about the basics.</p>
<p>I like seeing things put simply. The real world is rarely simple, and we
forget how following a few easy-to-understand principles can help
de-complexify things. Reading beginners' books reminds me that while
software development can be chaotic, it can be kept under control.</p>
<p>Reading well-written books for beginners gives me pointers on how to
better explain the concepts to beginners (or to non-beginners who shoud
know better). Sometimes I even learn something new myself.</p>
<p>Reading beginners' books reminds me of my youth. I remember where and
when I first learned these things. I try to remember what I did before I
learned the lessons. I remember how I learned the lessons - generally,
the hard way. I remember my teachers, mentors, and fellow students. I
remember the time when writing a linked-list was the most challenging
thing I had to do.</p>
<p>The danger of reading a book for beginners is that it sometimes gives me
a false sense of mastery. "I already know everything in this book, so I
am no longer a beginner," I think, or "See, it really is just that
easy." But those false feelings don't last for long, so I don't think
that indulging in them once in a while causes any harm.</p>
<p>Of course, the best thing about a well-written beginners' book is that
it is a well-written book. Such books are so rare that they should be
celebrated whenever they are found, no matter what the subject matter or
level of expertise required to understand them.</p>Measuring "Simplest"2004-01-09T18:16:00-05:002004-01-09T18:16:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-09:/measuring-simplest.html<p><a href="http://www.metaprog.com/blogs/index.php?p=63&more=1&c=1&tb=1&pb=1">Joseph Pelrine</a>
notes that "as Alistair Cockburn rightly states, 'simplest' has no
metric. It is not a quantifiable amount." While it is true that one
cannot put a number on it, there is an obvious test: you have the
simplest solution if nobody can think of anything simpler.</p>
<p>Of course …</p><p><a href="http://www.metaprog.com/blogs/index.php?p=63&more=1&c=1&tb=1&pb=1">Joseph Pelrine</a>
notes that "as Alistair Cockburn rightly states, 'simplest' has no
metric. It is not a quantifiable amount." While it is true that one
cannot put a number on it, there is an obvious test: you have the
simplest solution if nobody can think of anything simpler.</p>
<p>Of course, different people have different ideas of what simple is. For
example, C++ templates provide simple solutions to many C++ problems,
but many C++ programmers will consider any use of templates to be
"complicated". (And some would say that C++ provides no simple solutions
for any problem!) Similarly, some prefer break up a class into many
small classes to make things simpler, whereas others think that a small
number of big classes is simpler. Who's right?</p>
<p>Ward Cunningham wrote somewhere that his definition of <em>simple</em> is
whatever is easiest to reason about. I would add that it is also
whatever is easiest to communicate to others. If you are trying to
explain your "simple" solution to others, and they don't get it, that is
a good sign that you haven't really found a simple solution. The reason
that XP advocates simplicity is that simple code is easier for somebody
else to understand and to modify in the future.</p>
<p>So, when judging the simplicity of a solution, it is important to
remember that you are writing code for an audience. You have to make
some assumptions about that audience. Do they understand the language
features that you are using? Are they going to understand the idioms?
Does the code you are writing fit in with the rest of the project's code
in a way that will make sense to them?</p>
<p>Always remember that the value of simplicity is that it makes future
change easier. The simplest solution is the one that will be easiest to
replace.</p>Why Bother with .NET?2004-01-07T14:39:00-05:002004-01-07T14:39:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-07:/why-bother-with-net.html<p>Someone asked me "What's the big deal about .NET? Besides the fact that
the Microsoft world is steering that way and so knowing it is probably a
good career move. Is there a specific problem that it solves that I
couldn't solve as easily with, say, Ruby or Perl?"</p>
<p>This …</p><p>Someone asked me "What's the big deal about .NET? Besides the fact that
the Microsoft world is steering that way and so knowing it is probably a
good career move. Is there a specific problem that it solves that I
couldn't solve as easily with, say, Ruby or Perl?"</p>
<p>This is a question I asked myself a lot while <a href="http://kristopherjohnson.blogspot.com/2004_01_01_kristopherjohnson_archive.html#107336832099080752">studying for the MCSD
exams</a>.
The conclusion I came to was that the primary value of .NET is that it
provides a better way to write Windows programs than C++ or Visual
Basic.</p>
<p>Over the years, I've learned that the best thing to do when developing
for Windows is to do it Microsoft's way. Third-party libraries and
development tools always end up "breaking" at some point because
Microsoft changes an API or doesn't provide sufficient information to
other vendors. When using non-Microsoft tools, it always seems like I
spend more time working around incompatibilities than getting my job
done. So I try to stick with Visual C++, MFC, COM, ODBC, etc., and
nobody gets hurt too badly.</p>
<p>So, now Microsoft provides a Java-like development platform, and is
going to be integrating it more tightly with the OS as Longhorn comes
together. What I've read in the Microsofties' blogs leads me to think
that things are generally going in the right direction. This is good in
that C++ is no longer the best way to write Windows software. Knowing
.NET lets me write better Windows software.</p>
<p>But aside from its ties to Windows, is .NET "better" than alternatives?
No. It has some very nice features, but I'd rather be using Python (or
Ruby, or Scheme, or Squeak, or ...).</p>
<p>It's not that those other things are "better" than .NET. A lot of people
knock .NET because it is not innovative, and that it is just a
collection of features that have been better implemented elsewhere. I
actually find the non-innovativeness of .NET to be comforting. The fact
that Microsoft is "stealing" a lot of good ideas from other places and
isn't trying anything too radical suggests to me that .NET is going to
be a pretty usable framework. I don't want innovation; I want something
that works.</p>
<p>The big drawback to .NET is that it will always be a Microsoft-specific
technology. Despite the efforts of open-source developers, I don't think
there will ever be an industrial-strength non-Microsoft implementation.
I'm not going to be able to run my .NET code on Linux, or Solaris, or
QNX Neutrino, or Palm OS, or another of the other operating systems I
develop software for.</p>
<p>Is it worthwhile to learn about .NET? If you develop Windows
applications, then I would definitely recommend it. If you don't develop
Windows apps, then you may want to study .NET to see how the approached
issues differently from other platforms' designers. But I can't find
anything in .NET that makes me say "Wow! This is exactly what I've been
waiting for!"</p>Confessional2004-01-07T03:23:00-05:002004-01-07T03:23:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-07:/confessional.html<p>I committed several developer sins today. (Actually, I only committed a
couple today; the others were committed in the past but came to light
today.)</p>
<p>First, I assumed that the bug was in the other guy's code. I was sure
there could be nothing wrong in my simple straightforward
object-oriented …</p><p>I committed several developer sins today. (Actually, I only committed a
couple today; the others were committed in the past but came to light
today.)</p>
<p>First, I assumed that the bug was in the other guy's code. I was sure
there could be nothing wrong in my simple straightforward
object-oriented code, so it must have been in his quagmire of
multi-threaded straight ANSI C. So I spent most of my time trying to
read his code instead of examining my own more carefully. I cursed him
for not providing documentation or unit tests.</p>
<p>Second, I put off as long as possible the arduous task of reading
through the voluminous logging information his code generated. After
all, if he is a bad programmer it stands to reason that his log will
hold only useless information. When I finally did read through the log,
it became clear that the code I had copied-and-pasted from another
program was doing bad things.</p>
<p>Yes, that was the original sin: I copied-and-pasted code from another
program without reviewing it sufficiently. I know it's wrong. I scream
at people when I see them do this. But it was the easiest thing to do at
the time.</p>
<p>And when i discovered the copied-and-pasted bug, my first instinct was
to curse the name of the writer of that code I had copied. But of
course, he does not deserve my ire. The real culprit is my boss, who
suggested that copying and pasting was the fastest way to get the
feature implemented! No, that isn't right either. I committed the act;
it is my responsibility.</p>
<p>I keep thinking I am getting better at this stuff. I think I have better
habits than others. I only break my own rules when I think it really
won't hurt anything.</p>
<p>I am ashamed. I ask forgiveness.</p>On Being a Microsoft Certified Solution Developer2004-01-06T06:40:00-05:002004-01-06T06:40:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-06:/on-being-a-microsoft-certified-solution-developer.html<p>I earned the Microsoft Certified Solution Developer for .NET
certification last year. My resumé is mired in C++ and CORBA, and I
wanted something that would indicate to potential employers that I could
do something else. I missed out on the Java bandwagon, and couldn't
compete with the junior Java …</p><p>I earned the Microsoft Certified Solution Developer for .NET
certification last year. My resumé is mired in C++ and CORBA, and I
wanted something that would indicate to potential employers that I could
do something else. I missed out on the Java bandwagon, and couldn't
compete with the junior Java developers with 3+ years experience who
would work for $25/hour. So I thought being an MCSD might get me in
early on .NET development.</p>
<p>The tests weren't the cakewalk that I had been led to believe by the
detractors of Microsoft's certification program. I agree that the tests
don't provide a good indication of whether a candidate would be a good
developer, but they do indicate that the candidate has an encyclopedic
knowledge of the .NET API and the features of Visual Studio .NET.</p>
<p>The architecture exam, usually considered to be the toughest of the MCSD
exams, was the easiest one for me. It was the one exam where careful
reading and common sense would get you through, rather than a lot of
rote memorization.</p>
<p>Getting the MCSD didn't help me much in getting a job. I suspect it is
actually considered a black mark by those who believe anyone taking
Microsoft's tests must be a clueless, mindless drone.</p>
<p>I think that studying for the exams did help me to learn some things
about .NET that I would not have learned otherwise. The exam guides gave
me a plan for learning .NET, rather than just stumbling around randomly.
So that is what is of most value to me: the learning, not the piece of
paper.</p>Assertiveness2004-01-03T18:58:00-05:002004-01-03T18:58:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-03:/assertiveness.html<p>The most significant personal change I've gone through in the past few
years is an increase in my assertiveness. I am a very different person
from who I used to be, due to this change.</p>
<p>I am a stutterer. That, coupled with natural introversion, caused me to
grow up avoiding …</p><p>The most significant personal change I've gone through in the past few
years is an increase in my assertiveness. I am a very different person
from who I used to be, due to this change.</p>
<p>I am a stutterer. That, coupled with natural introversion, caused me to
grow up avoiding any situation where I had to talk to anyone. Many
people fear speaking because they think they will sound stupid, but a
stutterer <em>knows</em> he will sound stupid no matter what he is saying. All
through childhood and college, I avoided any role or situation that
required me to speak up.</p>
<p>I was able to keep my mouth mostly shut for several years when I started
working. I only had to talk to my boss and a few co-workers. So I was
able to get things done, but I shied away from any roles or situations
where I would have to speak my mind to people outside that circle. I
also avoided looking for better jobs, because I dreaded the interview
process.</p>
<p>Then, I got promoted. I was put in charge of development of an important
project. I faced a crisis: my job now required me to speak to clients
and higher level managers, and to tell other people what to do, but I
didn't think I could do that. I considered all sorts of ways to avoid
those responsibilities, but none made sense. So, I just decided I would
just do it, because it was my job.</p>
<p>And a funny thing happened: it learned it was surprisingly easy. I asked
people to do things, and they did it. I explained technical issues to
clients, and they understood and believed what I was saying. I talked to
those scary upper-level managers, and they turned out to be pretty cool
guys who trusted my judgment. All my fears had been unfounded.</p>
<p>It makes me a little sad to think about all the missed opportunities I
had before learning this simple lesson at age 35. I wish I had gone out
and played with other kids instead of sitting in my room reading books.
I wish I had taken advantage of everything that was available in
college, instead of just sitting in the back row, keeping my mouth shut,
and passing the tests. I wish I had talked to more girls when I was in
my teens and twenties. I know there's no going back, so I just have to
console myself by realizing that I won't have to miss future
opportunites. I think I've finally grown up.</p>
<p>After my epiphany, I now have little sympathy for programmers who
complain that their managers don't let them do what they want. Managers
aren't stupid; if you think you have a good idea, then tell them about
it and fight for it. If you don't like your job, then go get a better
one.</p>
<p>Having become assertive myself, I have a greater respect for other
assertive people. I once thought people like that were just arrogant
jerks, but now I understand that they are just fighting for their
beliefs and ideas the same way that I am. I would much rather argue with
another intelligent assertive person than to try to talk to someone who
just agrees with everything rather than presenting their own opinions.</p>
<p>I hate to sound like a motivational speaker, but this is the one lesson
I've learned that I want to share with the world. If there is something
you want to say, then say it. If there is something you want to do, then
do it. Nobody else is going to say and do these things for you. The
course of your life is up to you.</p>The Making of ...2004-01-03T05:22:00-05:002004-01-03T05:22:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-03:/the-making-of.html<p>When I was a kid, I really liked the "Making of Star Wars" and similar
documentaries that gave behind-the-scenes looks at the production of
movies. These days, the "Making of ..." pieces are really just ads for
the movies, usually being released before the movie itself.</p>
<p>But we have DVD commentary …</p><p>When I was a kid, I really liked the "Making of Star Wars" and similar
documentaries that gave behind-the-scenes looks at the production of
movies. These days, the "Making of ..." pieces are really just ads for
the movies, usually being released before the movie itself.</p>
<p>But we have DVD commentary tracks. That's my favorite part of owning a
DVD player: being able to hear commentary from the directors, producers,
writers, and actors in movies. A lot of what they say reminds me of
software development. My favorite parts are when they discuss what they
would have done if they had more time and money, and make their
complaints about stuff that wound up in the movie but they really don't
like.</p>
<p>(By the way, if you liked the movie <em>Fight Club</em>, then you've got to
listen to the commentary tracks on the DVD. Best commentary I've heard.)</p>
<p>I also enjoy "Inside the Actor's Studio" on the Bravo channel. Hearing
actors talk about their craft, how they learned it, and how they
continue to study it inspires me to continue improving my knowledge of
my craft.</p>
<p>Commentary from software developers about their projects would not be as
entertaining, but I wish we had such a thing. "The Making of Windows",
as told by its developers, would certainly have some interesting bits.</p>Giving The Lord of the Rings Another Chance2004-01-02T05:10:00-05:002004-01-02T05:10:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-02:/giving-the-lord-of-the-rings-another-chance.html<p>I didn't like <em>The Lord of the Rings</em> when I read it as a teenager. I
loved <em>The Hobbit</em>, but <em>Rings</em> just bored me. It had too much singing
and story-telling, and was just too long. I lost interest before
finishing the first volume, so the other two volumes were …</p><p>I didn't like <em>The Lord of the Rings</em> when I read it as a teenager. I
loved <em>The Hobbit</em>, but <em>Rings</em> just bored me. It had too much singing
and story-telling, and was just too long. I lost interest before
finishing the first volume, so the other two volumes were a long slog. I
had to read it because of peer pressure (I got tired of hearing "What,
you haven't read the <em>Rings</em> trilogy?"), but it was a lot like reading
books my teachers assigned.</p>
<p>After seeing the three <em>Rings</em> films, I am ready to give the books
another chance. The films were entertaining, and have given me enough
knowledge of the story and characters that I can read the books without
losing interest. I hope to enjoy all the stuff that didn't make it into
the movies, and to be able to say something intelligent when I get
trapped in one of those stupid "The movie wasn't like the book"
conversations.</p>
<p>Who knows? If this works out, maybe I'll give that horrible <em>Dune</em>
series another chance as well.</p>Self-Reflection2004-01-01T04:54:00-05:002004-01-01T04:54:00-05:00Kristopher Johnsontag:undefinedvalue.com,2004-01-01:/self-reflection.html<p>I spend a lot of time in front of reflective surfaces (computer monitors
and my TV), and so I occasionally catch a glimpse of myself when I don't
mean to look at myself. Before I realize who I am looking at, my first
impression is often that the guy looks …</p><p>I spend a lot of time in front of reflective surfaces (computer monitors
and my TV), and so I occasionally catch a glimpse of myself when I don't
mean to look at myself. Before I realize who I am looking at, my first
impression is often that the guy looks pretty dull-witted, tired, and
unhappy. I hope that's not how others see me.</p>
<p>There have been occasions when I am reading the <a href="http://c2.com/cgi/wiki">C2 wiki</a>, and suddenly realize I am reading
something that I wrote myself long ago. Thankfully, the impressions I
get from reading my own writing are more positive than those I get from
looking at my face. Sometimes I cringe a little, and many of my opinions
have changed dramatically, but that guy usually seems to be pretty smart
and reasonable.</p>
<p>Who am I? What do other people think of me? Of those two questions, the
first is more important, but I'm not too proud to admit that I care a
lot about the second also. Self-knowledge and self-confidence are great,
but if other people think I'm a ninny, maybe they're right. I don't know
how to get an honest, objective assessment of myself, but the occasional
unknowing glances in the mirror may be the truest view I can get of
myself.</p>
<p>Ultimately, that's what this blog is all about: trying to get a look at
myself and what I am doing, with a detached view. The writing itself
clarifies my thoughts for me, and when I re-read these entries in the
future, I hope to gain additional insights about who I was when I wrote,
and how I am changing.</p>My Failed Contracting Career2003-12-31T03:59:00-05:002003-12-31T03:59:00-05:00Kristopher Johnsontag:undefinedvalue.com,2003-12-31:/my-failed-contracting-career.html<p>After my last permanent position, I took a few months off from work, and
then tried to start a career as an independent consultant/contractor.
After many years of hearing about the freedom enjoyed by consultants,
and the exhorbitant fees they charged, I wanted to get my piece of the …</p><p>After my last permanent position, I took a few months off from work, and
then tried to start a career as an independent consultant/contractor.
After many years of hearing about the freedom enjoyed by consultants,
and the exhorbitant fees they charged, I wanted to get my piece of the
action. People encouraged me to give it a try, because I had many of the
traits associated with independents: desire for variety, lots of
knowledge, no trouble voicing my opinions, and no dependents who would
go hungry if I couldn't find work.</p>
<p>My primary motivation was the desire to be independent. I wanted that
whole be-you-own-boss thing. I wanted to be able to work less than 40
hours a week when I felt like it. I wanted to be able to choose which
assignments to take. I wanted to be able to decide what new computer to
buy and which development tools to use.</p>
<p>Unfortunately, this was mid 2002, and as all you people more clued into
reality than me know, that was a bad time to try to start a contracting
career. Established contractors were having trouble finding work. Those
who were finding work had to charge fractions of what they used to
charge.</p>
<p>The biggest reason I failed as a contractor was that I didn't have the
business sense for it. I didn't know anything about marketing. I didn't
know what my market niche would be. I didn't have a lot of contacts in
the industry. I hoped I could just post my resumé on my web site, and
jobs would start rolling in.</p>
<p>I did get a few calls, but most of them were for things that I wasn't
really prepared to do. For example, my resume lists Oracle and OpenVMS.
I was contacted by someone who needed me to provide training for
employees who would be administering Oracle on an OpenVMS system. Ten
years ago, when Oracle on OpenVMS was what I was doing, I may have been
able to do it, but it had been so long that I didn't think I could do a
reasonable job, so I had to pass. There were several other similar
opportunities that I passed on as well.</p>
<p>In hindsight, I wonder if I should have taken a few of those jobs. I
wouldn't have known what I was doing, but it would have been work and it
could have led to something I knew better. I have never been comfortable
claiming to be able to do things that I don't really know I can do.
Other consultants don't have that problem.</p>
<p>I did manage to land a contract with a former employer. They did pay me
well, but unfortunately only had a few hours of work per month for me to
do.</p>
<p>Eventually, I gave up on the idea of being completely independent, and
signed up with one of those organizations that hires people out as
contractors. I got a good gig with them, but for no more money than I
would have received as a permanent employee, and without security or
benefits.</p>
<p>So, I gave up on contracting, and got a permanent job.</p>
<p>Will I ever go independent or start my own business again? Maybe. I
think I do have a strong entrepreneurial streak, and a disdain for
working at the direction of others. But I'm not going to try it again
until I have a better idea about what products or services I will
provide, and who will pay for them.</p>New is Good?2003-12-31T03:59:00-05:002003-12-31T03:59:00-05:00Kristopher Johnsontag:undefinedvalue.com,2003-12-31:/new-is-good.html<p>Python is the coolest language ever.</p>
<p>I've never written a real Python program. But after reading the language
spec and a few tutorials, and writing a few simple programs, I am
fascinated with it.</p>
<p>Like many other programmers, I love to play with new things. Learning is
fun. New challenges …</p><p>Python is the coolest language ever.</p>
<p>I've never written a real Python program. But after reading the language
spec and a few tutorials, and writing a few simple programs, I am
fascinated with it.</p>
<p>Like many other programmers, I love to play with new things. Learning is
fun. New challenges are fun. Unfortunately, we often confuse enthusiasm
with evaluation. If we <em>want</em> to use something, it must be superior to
all that junk we don't want to use anymore.</p>
<p>C++ is a horrible language. (Only Perl is uglier.) But when I am writing
important code, I use C++ instead of the "new" languages. I hate it, but
I know it well. It's limitations infuriate me, but I never get
surprised. Other developers deride me for using MFC and the Visual C++
wizards, but I am more productive and less error-prone with those tools
I've used for years than I would be with the "better" tools that I don't
know.</p>
<p>Am I missing out by using old bad tools instead of new cool tools?
Maybe, I don't know.</p>
<p>The distinction between "new" and "old" is not necessarily
chronological. For the past few years, I have been writing Palm OS
programs using <a href="http://www.quartus.net/products/forth/">Forth</a>. Forth is
certainly not a new language, having been around for decades, but it was
new to me. I loved the way I could create programs with a relatively
small amount of code. It was interesting to do all my programming and
debugging on the handheld device, without benefit of a desktop-based
cross-compiler and debugger. I remember the experience fondly, but I
can't kid myself into thinking I was more productive with Forth than I
would have been with a C compiler.</p>
<p>I once felt differently. In college and in my early professional years,
I was constantly learning about new programming languages and other
development tools. I'm sure that the study of all those things made me a
better programmer. However, I also believe that concentrating on a
couple of languages for many years has made me a better programmer than
I would be if I flitted between languages constantly.</p>
<p>Ultimately, all development tools and technologies suck, and the new
ones don't make things that much better. The hard part of software
development is figuring out exactly what the program needs to do. The
level of effort required to do that is independent of the programming
language used. Being able to pick the right tool for the job is
important, but usually the right tool is the one I know how to use most
proficiently.</p>
<p>Python is the coolest language ever. I'll continue to believe that until
I actualy have to use it.</p>Welcome to My Blog2003-12-31T03:59:00-05:002003-12-31T03:59:00-05:00Kristopher Johnsontag:undefinedvalue.com,2003-12-31:/welcome-to-my-blog.html<p>My name is Kris Johnson. For eleven years, I have been a computer
programmer. For the first few years, I enjoyed it. Now, I've grown tired
of it, but I don't have any other marketable skills and I don't want to
move into management. So, I do what I can …</p><p>My name is Kris Johnson. For eleven years, I have been a computer
programmer. For the first few years, I enjoyed it. Now, I've grown tired
of it, but I don't have any other marketable skills and I don't want to
move into management. So, I do what I can to improve my programming
skills in the hope that I can work less hard. I also like to exchange
ideas with others about how to make the programmer's life easier.</p>
<p>Writing is a great outlet for me. It helps me focus my thinking, and
gives me a way to vent my frustrations. I also like to think that others
benefit from reading what I write (although I've seen no evidence of
that). For a while, I interacted with the community at the <a href="http://c2.com/cgi/wiki">C2
wiki</a> (a.k.a., "Ward's Wiki"). I learned quite a
bit there. But that community has changed, and I don't think my writings
will fit there anymore, so from now on I'll write here.</p>
<p>To avoid trouble, I'm going to refrain from writing about my current
job. If I do write about my job, it will be disguised so much that
nobody will know. However, most if not all of what I write will be about
what I've learned about software development, and about what I still
hope to learn.</p>
<p>I may also occasionally post an "off-topic" item such as a movie or book
review, or just some odd thought I had while eating breakfast. But don't
worry; I don't have that many thoughts.</p>