views:

122

answers:

2

I'm working with an NSTextView and one of the requirements I have is that a tab character, '\t', shall have the same width as four spaces.

So the text-content would look like this:

AAAA
    AAAA - 1 tab
    AAAA - 4 spaces

And this is how I accomplish this:

// done when NSTextView first loaded and when
// delegate's textDidBeginEditing gets called: (perhaps overkill, but is a work in progress).
- (void)updateMyTextViewTextAttributes
{
    NSMutableParagraphStyle* paragraphStyle = [[myTextView defaultParagraphStyle] mutableCopy];
    if (paragraphStyle == nil) {
        paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    }
    float charWidth = [[myFont screenFontWithRenderingMode:NSFontDefaultRenderingMode] advancementForGlyph:(NSGlyph) ' '].width;
    [paragraphStyle setDefaultTabInterval:(charWidth * 4)];
    [paragraphStyle setTabStops:[NSArray array]];
    [myTextView setDefaultParagraphStyle:paragraphStyle];

    NSMutableDictionary* typingAttributes = [[myTextView typingAttributes] mutableCopy];
    [typingAttributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
    [typingAttributes setObject:scriptFont forKey:NSFontAttributeName];
    [myTextView setTypingAttributes:typingAttributes];
}

This allows the appropriate layout to be shown with the initial text as well as keep the typing attributes the same.

The problem is that the end-user can change the font. And when that happens, the sample text becomes misaligned. Much like the below:

[smaller font]
AAAA
     AAAA - 1 tab
    AAAA - 4 spaces


[larger font]
AAAA
   AAAA - 1 tab
    AAAA - 4 spaces

I've tried calling myTextView's setNeedsDisplay:YES as I read that it ends up calling NSTextView's setNeedsDisplayInRect:avoidAdditionalLayout with a NO for the avoidAdditionalLayout parameter. This didn't change anything.

I've tried calling my updateMyTextViewTextAttributes call when myTextView has the new myFont set. That doesn't change a thing.

I've also tried telling the layoutManager of myTextView to ensureLayoutFOrTextContainer for the textContainer of myTextView. No change.

At this point, I'm not sure what to try next. Any suggestions?

+1  A: 

Have you tried to compute space advancement with advancementForGlyph: directly on the font instead of the screen font ?

float charWidth = [myFont advancementForGlyph:(NSGlyph) ' '].width;

Screen font are not meant to be used directly outside the window server:

Screen fonts are for direct use with the window server only. Never use them with Application Kit objects, such as in setFont: methods. Internally, the Application Kit automatically uses the corresponding screen font for a font object as long as the view is not rotated or scaled.

Laurent Etiemble
I found the section of code: "[[myFont screenFontWithRenderingMode:NSFontDefaultRenderingMode] advancementForGlyph:(NSGlyph) ' '].width;"online in some code described to handle programmatically specified tab-widths. If I change it to just: "[myFont advancementForGlyph:(NSGlyph)' '].width;", the lines no longer match up. What you quoted from the documentation is there, but it doesn't have the same desired functionality...
Lyndsey Ferguson
A: 

Douglas Davidson of Apple provided me with the clues to get to the answer via the [email protected] email list.

I've resolved the problem by updating the updateMyTextViewTextAttributes function like so:

- (void)updateMyTextViewTextAttributes
{
   NSMutableParagraphStyle* paragraphStyle = [[myTextView defaultParagraphStyle] mutableCopy];

   if (paragraphStyle == nil) {
       paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
   }

   float charWidth = [[myFont screenFontWithRenderingMode:NSFontDefaultRenderingMode] advancementForGlyph:(NSGlyph) ' '].width;
   [paragraphStyle setDefaultTabInterval:(charWidth * 4)];
   [paragraphStyle setTabStops:[NSArray array]];

   [myTextView setDefaultParagraphStyle:paragraphStyle];

   NSMutableDictionary* typingAttributes = [[myTextView typingAttributes] mutableCopy];
   [typingAttributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
   [typingAttributes setObject:scriptFont forKey:NSFontAttributeName];
   [myTextView setTypingAttributes:typingAttributes];

   /** ADDED CODE BELOW **/
   NSRange rangeOfChange = NSMakeRange(0, [[myTextView string] length]);
   [myTextView shouldChangeTextInRange:rangeOfChange replacementString:nil];
   [[myTextView textStorage] setAttributes:typingAttributes range:rangeOfChange];
   [myTextView didChangeText];

   [paragraphStyle release];
   [typingAttributes release];
}
Lyndsey Ferguson