Sunday, March 7, 2010

Inline editing - NSTextView and the mysterious 5px margin

The PC version of cello has a dialog box to edit labels. You can, using a contextual menu, add a label or edit a label. Deleting a label is a matter of editing the label and removing it's text.

A more OSX way of handling the editing of a label is to be able to edit inline. The idea is that if you double click on a label then you get a text cursor in the label - and you edit the name inline.

To implement this I field the double click and then create an NSTextView. The label is an irregular shape.

I wanted it to grow and shrink as the you type. I wanted the irregular shape to change with as you type. The key bits are to set the NSTextView up as stretchy setHorizontallyResizable:YES and then to stop it from drawing a background setDrawsBackground:NO. When the edited label is drawn it gets it's size from the text field and does not draw text. The result is that the NSTextView is responsible for drawing the text in this unique situation. The final twist is to set the delegate of NSTextView to the timeline view. The textDidChange method just gets the label to redraw - so it now grows and shrinks as you type.

Getting something like this to work is a bit of a fiddle but mainly boils down to getting the right clutch of settings. Mine were these:

[_editLabelView setHorizontallyResizable:YES];

[_editLabelView setMinSize:NSMakeSize(400.0f, kLabelViewHeight)];

[_editLabelView setFieldEditor:YES];

[_editLabelView setTextContainerInset:NSMakeSize(0, 0)];

NSTextContainer *container = [_editLabelView textContainer];

[container setLineFragmentPadding:0];

[_editLabelView setDelegate:self];

[_editLabelView setString:label];

[_editLabelView setRichText:NO];

[_editLabelView setSelectedRange:NSMakeRange(0, [label length])];

[_editLabelView setDrawsBackground:NO];


My biggest problem was that I always had a 5px right shift of the start of the text from where I expected it to be. This margin or inset is the lineFragmentPadding on the NSTextContainer. It took a while to track this one down. The magic fix was

_editLabelView = [[NSTextView alloc] initWithFrame:bounds];

NSTextContainer *container = [_editLabelView textContainer];

[container setLineFragmentPadding:0];



No comments: