210°

It appears to be H in HSV space for Apple’s standard color.

I thought 210° was just the magic number for iPhone software: the gray-blue color that appears throughout the UI is variations of H=210°. But it also appears that the Apple Mail application uses the same light blue-gray color for the side panel, and for other side panels elsewhere.

As an aside, the blue throughout this blog is at 205°.

As a second aside, for those who don’t know what I’m talking about, here’s the algorithm to convert from HSV to RGB, along with the formulas to go the other way around: Wikipedia: HSL and HSV

Of course it’s important to remember to honor the user’s color selection when building custom controls: NSColor’s +selectedTextBackgroundColor returns the hilite color, and +selectedControlColor returns the color for the face of a selected control, such as the color of a menu item highlight.

Touch screen dead zone.

While chasing down a usability bug, I discovered something interesting about the hardware for the Google developer phone, which I also suspect plagues the release G1 and G2 phones. The problem is an issue with the touch screen technology used by HTC.

The bottom line is this: there is a border to the left and right of the screen (and, I suspect, the top and bottom) where the finger’s location is not reported. On a piece of test software, when dragging my finger left to right, I found that when my finger was within 20 pixels of the left border, the position was reported as 0–and on the right border, as 319.0. This, despite being able to see my finger’s location reported with one- and two- pixel increments elsewhere on the screen.

I’ve also found that finger-down events within that 20 pixel border are not reported, unless accompanied with a drag outside of that 20 pixel border. Thus, if you design a UI where the user is expected to tap within 20 pixels of the border, the tap event will not be detected by the current hardware. A tap and drag, however, will be detected, which is why when you tap in the notification area at the top of the Android screen it doesn’t necessarily work–but a tap and drag will reliably open the notification area.

Just something to keep in mind when designing for the Android: tap areas near the left or right of the screen that may be 40×40 pixels in size will have the unfortunate problem that half of the potential touch area will be dead to tap events.

WebView on Android

I ran into a weird crashing bug on our application: when we closed an Activity containing a WebView display, on occasion we’d get a SIGSEGV crash. This reliably happened about one in a half-dozen times.

The fix appears to be to call WebView’s destroy() method on the view inside the Activity during the Activity’s onDestroy:

    protected void onDestroy()
    {
        fWebView.destroy();
        super.onDestroy();
    }

I’m not sure exactly why this is the case. But it appears to resolve my issues.

Java System Property Reference for Mac OS X

I don’t know why I keep misplacing this document, so I’m noting it here for future reference: Java System Property Reference for Mac OS X. This contains the various magic runtime system properties in Mac OS X.

The Java Development Guide for Mac OS X is also useful for how to do certain standard Mac-like things in the Java environment.

What I found was that by testing the System.getProperty(“os.name”).toLowerCase() return string to see if it contains “mac os”, then sprinkle various minor changes to make the UI align with the recommendations in the Java Development Guide above, you can build an application that looks Mac-like on a Mac platform with little effort.

List View Context Menus

This was driving me nuts all weekend, because I became misguided.

On Google Android, I want to create a custom list view, and I want to handle context menus. Of course the right way to do this is:

(1) In your activity that contains your list, call setOnCreateContextMenu with a class that will be handling the context menu callbacks. (The activity already implements the interface, so you can just pass it your activity.)

        fListView.setOnCreateContextMenuListener(this);

(2) Implement the onCreateContextMenu and onContextItemSelect items as usual.

Where I ran into problems was with another suggestion from another web site that suggested in my custom list adapter that I call the view’s registerForContextMenu routine. The problem with that is that you don’t get the list view’s yellow highlight and animated fade effect.

Three Ways To Load A Nib. Only Two Work.

Someone I’m working with told me that you can load a NIB on the iPhone in the following way:

TestViewController *c = [[TestViewController alloc] initWithNibName:@"TestNib" bundle:[NSBundle mainBundle]];
[self presentModalViewController:c animated:YES];

However, this doesn’t appear to work. When chasing down the code, by the time TestViewController -(void)viewDidLoad gets called, self has changed; however, deep inside the lazy load of presentModalViewController the old pointer seems to be used because I get an exception indicating the view reference of the underlying UIViewController (that TestViewController descends from) was uninitialized.

There are two other ways to load the Nib.

First is using -(NSArray *)loadNibNamed:owner:options:

NSArray *a = [[NSBundle mainBundle] loadNibNamed:@"TestNib" owner:self options:nil];
TestViewController *c = [a objectAtIndex:0];
[self presentModalViewController:c animated:YES];

This seems to work, though the top level objects have been offset by one since v2.0 of the iPhone OS. (That is, this code will only work on v2.1 or later; if you build for v2.0 you must add one to the objectAtIndex index.)

The second way is the recommended way given in Apple’s documentation here:

// In my top level class
@interface TestRetainViewController : UIViewController 
{
    TestViewController *fController;
}
@property (nonatomic, retain) IBOutlet TestViewController *fController;
[[NSBundle mainBundle] loadNibNamed:@"TestNib" owner:self options:nil];
[self presentModalViewController:fController animated:YES];

This assumes there is a reference in the TestNib.xib file which points from the file owner object to the NIB.

One hitch about the second method, using an array: the array that is returned has a reference count of 1, but also has been set to autorelease. Thus you should either retain the array or the items in the array you want. The hitch about the third method, of course, is that because you’ve declared a property with retain semantics, you must also release the object on dealloc:

- (void)dealloc
{
    [fController release];
    [super dealloc];
}

You also can test to see if the NIB was already initialized in the call to loadNibNamed, however, in theory during NIB loading the reassignment to fController should release whatever was already loaded there before.

What tripped me up about the last project I worked on was that I was in such a rush to get very complicated NIBs built for an over-engineered UI, I created circular references and (more importantly) I wasn’t releasing any of the objects loaded via the NIB loading process. Turned out object connections on the iPhone are established using -setValue:forKey:, which does an automatic retain, and that means you need to release the reference in the -dealloc method.

What’s worse, if you don’t explicitly create the @property (or equivalent setXXX call according to the key/value rules, I believe simple assignment with retain is called–which means if you do multiple calls to loadNibNamed: above on TestNib but without the @property declaration, you’ll leak as each call simply overwrites the pointer.

Valid format values for declare-styleable/attr tags

Meh. Wasted an hour trying to sort this out.

The answer happened to be buried in some Android code. The fact that Android is open source is a cool thing–but I shouldn’t have to dig through that source kit just to figure out the answer to something that could be better documented.

When declaring a <declare-styleable> <attr> tag, you supply a name for the new attribute for your custom class, and you provide a format. Well, I couldn’t find the valid values for the format attribute in the documentation. But I did find them here.

And those values?

  • reference
  • string
  • color
  • dimension
  • boolean
  • integer
  • float
  • fraction
  • enum
  • flag

It also appears from the source code that this field is optional, and I presume if it is left blank, either one of two things will happen: this will default as a resource reference, or the format is only used for type checking, and this can be any value. I dunno; I haven’t tried it.

On the off-chance someone knows the answer, could they leave it in the comments?

Centering the contents of a component in a JScrollPane

When creating a drawing program it’s nice to be able to center the contents of your view if the content area of the JScrollPane is larger than the contents of your document. By default your contents are slammed to the upper-left; however, by using the Scrollable interface you can prevent the viewport containing your content view from shrinking smaller than the content area of the scroll pane, thus allowing you to do things like center your content.

Here is some code which I used to do this. I’m posting the code here so I can remember this in the future.

First, the class itself:

public class ContentView extends JComponent implements Scrollable
{
    private int width = 500;    // document width, height. (replace with whatever code you use to find these values.
    private int height = 400;
    private Rectangle offset;   // caching code; see discussion below.

I’m declaring a width and height inside my class for demo purposes., but you can obtain the visible width and height anyway you wish.

Now the centering code:

    /********************************************************************************/
    /*                                                                              */
    /*  Viewport Centering                                                          */
    /*                                                                              */
    /********************************************************************************/
    
    /**
     * Internal routine which computes the smallest rectangle size
     * encompassing the document area and the scroll viewport.
     */
    private Dimension getViewportSize()
    {
        Dimension size = getParent().getSize(); // get my viewport size

        int xsize = size.width;
        if (xsize < width) xsize = width;
        int ysize = size.height;
        if (ysize < height) ysize = height;
        
        return new Dimension(xsize,ysize);
    }
    
    /**
     * getMinimumSize returns the smallest acceptable size of this view. This
     * is then used resize
     */
    public Dimension getMinimumSize()
    {
        return new Dimension(300,200);
    }
    
    /**
     * The getPreferredSize routine returns my preferred size; this returns the
     * viewport size
     */
    public Dimension getPreferredSize()
    {
        return getViewportSize();
    }

    /**
     * The Scrollable.getPreferredScrollableViewportSize routine returns the suggested
     * size of the viewport. This will return the viewport size I want, which is
     * the larger of the view area size and the existing viewport size
     */
    public Dimension getPreferredScrollableViewportSize()
    {
        return getViewportSize();
    }


    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction)
    {
        int x;
        if (orientation == SwingConstants.VERTICAL) {
            x = visibleRect.width - 12;
        } else {
            x = visibleRect.height - 12;
        }
        if (x < 12) x = 12;
        return x;
    }

    public boolean getScrollableTracksViewportHeight()
    {
        return false;
    }

    public boolean getScrollableTracksViewportWidth()
    {
        return false;
    }

    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)
    {
        return 12;
    }

The core of the code above is the getViewportSize() routine, which determines a size which contains the size of my content and the viewport size. This is then returned for the getPreferredSize and getPreferredScrollableViewportSize() routines–both are used by the JScrollPane to resize my view within the scroll view area.

The routine getMinimumSize() is used to return the minimum acceptable size. To make a long story short, in my custom LayoutManager, I call the JScrollPane’s getMinimumSize() routine to get the minimum area allowed for resizing my window; this makes sure the contents area of my scroll bar doesn’t shrink below the minimum size returned.

The other interface methods for the Scrollable are filled in with acceptable defaults for a drafting program: page scrolling scrolls almost the entire width or height of the visible area, and clicking the arrows steps 12 pixels.

Now to hang all this together you need to then draw the contents in the right place; this means you need a rectangle representing the content area of your document.

    /********************************************************************************/
    /*                                                                              */
    /*  Drawing Support                                                             */
    /*                                                                              */
    /********************************************************************************/

    /**
     * invalidate is called when the size or boundaries of this thing has been
     * invalidated. This resets my offset rectangle because I will need to recalculate
     * it.
     */
    public void invalidate()
    {
        super.invalidate();
        offset = null;
    }
    
    /**
     * getOffset calculates (if needed) and returns a rectangle which defines the
     * location of my drawing in my view. If the visible area of my view is larger
     * than my drawing, this will return a rectangle offset appropriately
     * @return
     */
    public Rectangle getOffset()
    {
        if (offset == null) {
            Dimension parentSize = getParent().getSize();
            int xoff = (parentSize.width - width) / 2;
            if (xoff < 0) xoff = 0;
            int yoff = (parentSize.height - height) / 2;
            if (yoff < 0) yoff = 0;
            
            offset = new Rectangle(xoff,yoff,width,height);
        }
        return offset;
    }
    
    protected void paintComponent(Graphics g)
    {
        Rectangle r = getOffset();
        Dimension size = getSize();
        
        // erase to background
        g.setColor(Color.lightGray);
        g.fillRect(0, 0, size.width, size.height);
        
        // erase my document area
        g.setColor(Color.WHITE);
        g.fillRect(r.x, r.y, r.width, r.height);
    }

The offset field above is used to cache the current offset and location of my contents in my drawing area; I cache this value so I’m not constantly recomputing this on a mouse down/move/up cycle. The calculation itself is handled in getOffset(). The invalidate() method is called by the OS whenever the size of my view is changed; this allows me the chance to dump the previously cached value to force a redraw.

And the paintComponent() routine will repaint the contents; here I draw the area as a white area within the content area.

As a footnote, if you write code to recalculate the location of the scroll bar and use that code to calculate the minimum acceptable size of your view, your LayoutManager should use getMinimumSize rather than getPreferredSize when determining the size for scrolling. (I’m lazy; my LayoutManagers use the same value for preferredLayoutSize and minimumLayoutSize.) Otherwise, you will find the window constantly resizing itself larger and larger as the custom getPreferredSize() above interacts poorly with the resize code.

Look; this is a hack. I make no promises this works correctly; standard disclaimers apply.

MacOS X 10.5, Apache and my Sites folder.

They’re not working. When I point the web browser to localhost/~me, I get a 403 error: forbidden.

Chasing down the configuration files, it turns out:

(1) The httpd.conf file controlling Apache is located in /etc/apache2.
(2) It uses mod_userdir in order to map the Sites folder in my home directory to the web browser.
(3) The default access is set in the httpd.conf file to deny all–meaning by default I cannot browse files.
(4) To configure the mod_userdir, httpd.conf includes httpd-userdir.conf from the directory /private/etc/apache2/extra
(5) This file then includes all of the conf files from /private/etc/apache2/users rather than set a deny none on the directory pattern /Users/*/Sites.
(6) The users directory only contains a configuration file for the Guest account.

So it appears the thing to do in order to enable web browsing of my Sites directory is to do the following:

cd /private/etc/apache2/users
sudo cp Guest.conf me.conf
sudo ed me.conf
ed> s/Guest/me
ed> w
ed> q

This will create a configuration file for user ‘me’, and set the directory settings the same as for Guest. The ed commands are shown with the ed> prompt.

Once this is done, restart web sharing (I did it in the MacOS X System Preferences ‘sharing’ panel), and you should now be able to browse your Sites folder.

The iPhone Simulator

An accidental discovery I thought I’d write down so I don’t forget.

On the iPhone simulator, option left-click simulates two-finger clicking: option-click and drag simulates two fingers sliding in or out for zooming in or zooming out.

Likewise, option+shift left-click simulates two-finger dragging: option+shift and drag simulates two fingers dragging with equal spacing across the screen.

Useful for testing zooming in and zooming out, and useful if two-finger drag means something different than one-finger drag.