My "Dumbphone" Solved the Mute Switch

Screenshots of old phone

Those are three "screenshots" from my old LG phone. The interface for switching volume is essentially identical to the iPhone's, minus the hardware mute switch. In almost every other case, I would tell interface designers to take as little inspiration from this phone as possible: it's genuinely bad. However, it does have one thing the iPhone lacks: a dedicated "alarms only" volume setting. My old phone only has a pair of volume buttons, without a dedicated mute switch, but here's how I think it could work on the iPhone:

  1. The mute switch means make no sounds. The music app is probably the only exception, as the user is explicitly requesting that the iPhone play music at that time, and as a second use for the mute switch is to avoid alert sounds interrupting your music.
  2. Add an "alarms only" volume level at the bottom of the volume scale. It does what you'd expect.

The alarms only mode is great for a few reasons. Firstly, since everything is more explicit, you won't end up embarrassed in front of an entire concert hall. Secondly, it's great for actually sleeping. No late night Words with Friends players will interrupt your sleep, but your alarm will still go off.

"Now, wait a minute!" you might say. "If you're not dedicated enough of a Scrabble player to wake up for a game, it works like that now! Just flip the mute switch, and your alarm will still sound!"

Yes, that's true. But I had no idea. I figured that the mute switch would, well, mute the phone. I had never considered that the mute switch might have exceptions. I might be dumb, and I haven't read my iPhone's manual. It might be because I used to have a phone with a dedicated "alarms" setting. But I'm not the only one who didn't understand the mute switch, and I'm sure there are more of us out there.

One More Thing...

The iPhone's mute icon

That's what appears when you flip the mute switch on an iPhone. It's a bell, crossed out. A bell is a common symbol meaning "alarm". When raising and lowering the volume level, the iPhone uses a speaker icon, but when you flip the mute switch, it shows a crossed out alarm bell, while not silencing alarms.


Improving CGContext with Blocks

In both Cocoa and the lower-level C APIs on Mac OS X, there are a lot of paired "begin" and "end" messages and functions. For example, if you want to use a transparency layer, you'll probably do something like this:

CGContextBeginTransparencyLayer(ctx, NULL);

// draw some things...

CGContextEndTransparencyLayer(ctx);

While that's fairly readable, if you want to do some more complex drawing, a more "nested" structure will quickly emerge. Throw in some state saving and restoring, and it can quickly become a mess. Here, I'm drawing an icon with a shadow, a gradient fill, and a little highlight on the top, sort of like the sidebar in Twitter for Mac:

CGContextSaveGState(ctx);
// set the shadow

CGContextBeginTransparencyLayer(ctx, NULL);

CGContextSaveGState(ctx);
CGContextClipToMask(ctx, maskBounds, mask);
// fill the context with the highlight color
CGContextRestoreGState(ctx);

CGContextSaveGState(ctx);
maskBounds.origin.y -= 1.0;
CGContextClipToMask(ctx, maskBounds, mask);
// draw the primary gradient
maskBounds.origin.y += 1.0;
CGContextRestoreGState(ctx);

CGContextEndTransparencyLayer(ctx);
CGContextRestoreGState(ctx);

Well, that's a bit of a mess. If you've used Photoshop, you know how to visualize a final composited image as layers. Core Graphics is more powerful1, because it doesn't restrict you to a single stack, but instead allows a tree2.

The obvious solution is to simply treat the begin/end function pairs as control structures, and indent the inner drawing code. While this gets the job done, Xcode3 won't like it, and every time you dare type a semicolon it will "fix" the indentation for you. If you cut and paste the code, it'll do the same thing, except this time you'll get all of your lines helpfully reindented (this is fun with multiline escaped macros).

I wanted a better solution. So I wrote these two simple functions:

typedef void(^CGStateBlock)();

void CGContextState(CGContextRef ctx, CGStateBlock actions)
{
    CGContextSaveGState(ctx);
    actions();
    CGContextRestoreGState(ctx);
}

void CGContextTransparencyLayer(CGContextRef ctx, CGStateBlock actions)
{
    CGContextBeginTransparencyLayer(ctx, NULL);
    actions();
    CGContextEndTransparencyLayer(ctx);
}

It's incredibly simple. Wrap a block with the begin/end functions. The result is that the previous block of code is turned into this:

CGContextState(ctx, ^{
    // set the shadow

    CGContextTransparencyLayer(ctx, ^{
        CGContextState(ctx, ^{
            CGContextClipToMask(ctx, maskBounds, mask);
            // fill the context with the highlight color
        });

        CGContextState(ctx, ^{
            maskBounds.origin.y -= 1.0;
            CGContextClipToMask(ctx, maskBounds, mask);
            // draw the primary gradient
            maskBounds.origin.y += 1.0;
        });
    });
});

The structure is instantly clear. I can see exactly how each "layer" of the final rendering comes together. If I added the actual drawing code, the difference would be even clearer as the ratio of state saves/restores to drawing code changed. The beginning and ending of each nested state is obvious, where before it had to be determined either by reading from the top of by the use of extra newlines.

  1. Obviously, but I'm talking about the "user interface" of CG versus that of Photoshop.
  2. Hacking it with smart objects is a UX disaster and doesn't count. OK, maybe it does, since's it a lot better than nothing. Seriously though, someone needs to write "Photoshop for programmers". Not a book, an app. Basically Quartz Composer melded with Photoshop. I'd like to do it myself, and I know exactly how I'd like it to work, but writing a graphics editor is a tad bit of work.
  3. vim and emacs won't like it by default either, although I suppose it's probably not too hard to fix in either. The lack of good completion keeps me away from vim for Cocoa code.

Photoshop across Mac OS X partitions

I installed Lion on a fairly small partition, because I wanted to keep Snow Leopard around for testing applications and I didn't want to deal with moving all of my music and other files. It went great until I wanted to run Photoshop:

Photoshop doesn't work

One of the nice things about Mac OS X is that applications just work, no matter where they are. No matter where an application is in the directory structure, if you double click it, it will run just the same as it would in /Applications.

Adobe, of course, follows this rule about as well as they follow all other rules involving "fitting in on Mac OS X", which is to say that they completely ignore it.

However, I wasn't going to take no for an answer. I have 3GB of free space remaining on my Lion partition, enough for about half of a Creative Suite app. Since Adobe's error dialog didn't provide any information besides "it's broken", I was pretty much on my own. After some messing around with chroot (a complete failure), I tried something more obvious.

$ sudo ln -s /Volumes/Macintosh\ HD/Library/Application\ Support/Adobe
             /Library/Application\ Support/Adobe

Progress at last!

Well, that's a different dialog, and one that sounds like progress. I like to live on the edge, so I did not update.

It works (apparently)

Oh. Well, never mind. At least their error message tells me what files are missing (it doesn't).

Except, as far as I can tell, it does work. Photoshop didn't quit after that error dialog, and I can open documents, save documents, and move pixels around, which is what Photoshop is for as far as I can tell. A success.


Getting mouse events from a transparent NSWindow

When a Core Animation layer is present in the root view of an NSWindow, the window loses its shadow. However, if you want to animate the entire contents of the window, this is the only choice. The problem isn't particularly difficult to solve: outset the window bounds from the actual content and add a shadow to the CALayer.

However, this raises another problem: your window will now start to gobble up mouse events that appear to be "outside" it. Initially, I tried to solve this by using +NSWindow windowNumberAtPoint:belowWindowWithWindowNumber: to retrieve the window number of the window below the clicked window, CGWindowListCopyWindowInfo to get a list of windows below the window, a loop to find the correct one, and CGEventPostToPSN to post a modified (specifically, the location and window number) event to the process owning that window.

It worked. Sort of. Occasionally.

Clearly, there had to be a better suggestion. And there was. There were a few key problems.

  1. Sending events across processes was the part that "sort of" worked. Within my application, all worked as expected. However, when I clicked on a background app, the window would be raised, but it did not gain key status.
  2. Unfortunately, there's no NSMutableEvent, so I had to make new NSEvent instances rather than editing the old one. Also unfortunately, while "mouse down" events are really easy to make (there's a class message on NSEvent), scrolling events are... difficult. I'm sure there's a way to do it, but it involved a lot more work than it ought to.

Luckily, there was a much better option. There is a rectangular portion of my window (the non-shadow part) that should accept mouse events. The events outside of this area should go to the window below it. Windows are rectangular. Just add another window.

So, I made my primary window ignore mouse events, and added a child window on top of it, temporarily with a 50% transparent red fill so that I could ensure the frame was being set correctly. The window's -sendEvent implementation just checks if the event is a mouse event, then translates the event's -locationInWindow (which is trivial) to match the old window. Pass -sendEvent with a modified event to the other window, and we're done. It works perfectly.

Until you remove the color. Transparent NSWindow instances don't get mouse events.

The prevailing suggestion on the web to solve this issue is actually make it a little opaque. You might be able to get away with that, I guess. But it's not clear what opacity it will begin to work at. 0.1 worked. 0.01 didn't.

As it turns out, the best solution is the class that caused the issue in the first place: CALayer.

@implementation RDEventOverlayView

-(void)viewDidMoveToWindow
{
    [self setWantsLayer:YES];

    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    [[self layer] setOpacity:0.0];
    [CATransaction commit];
}

-(void)drawRect:(NSRect)dirtyRect
{
    [[NSColor blackColor] set];
    [NSBezierPath fillRect:dirtyRect];
}

@end

Just give the window's content view a CALayer with full transparency. NSWindow has no idea, and gets the events as it normally would, while the display on screen remains perfect.


Shake to Undo (for Mac OS X)

Shake to Undo

For those of us that have been using Mac OS X for a while, reverting a mistake with ⌘Z is natural. However, the popularity of Apple's iOS devices have brought a large new group of users to the Mac. In the most recent version of Mac OS X, 10.7 "Lion", Apple has addressed many of the concerns of these users with a few features. Launchpad fixes the unintuitiveness of an automatically alphabetized grid of applications (provided by Stacks) by allowing the user to arrange applications in any order. Mission Control removes the confusing grid that previously plagued Spaces. Where a user with four spaces could previously end up on any of the other three with a single keystroke, he or she is now presented with a much less confusing choice between one or two.

However, Apple missed an obvious location for improvement. Although Lion provides many touch gestures that can be activated with the trackpad, it completely ignores the potential ability to use the laptop itself as a gesture device. When both commands and text input are mapped to the keyboard, as is currently the case, the keyboard loses the design focus of being a single-purpose device. When the user has to take additional time to switch to a different input method in order to perform commands, his or her mind will be entirely focused on the task at hand by the time the gesture is performed.

Shake to Undo

Therefore, I have fixed a small portion of this mistake, by implementing the "Shake to Undo" gesture from iOS. Once the application is running, simply shake your laptop to activate undo. To prevent accidental activation of the undo gesture, there is an optional confirmation overlay, similar in style to the undo overlay on iOS.

This is all made possible by SMSLib, an open source library for accessing the sudden motion sensor in MacBooks.

Download Shake to Undo (for Mac OS X)

If you feel that the current implementation is lacking or is otherwise not feature complete, please fork the project on Github.