views:

45

answers:

1

Trying to change the selected text background color for an NSTextField (we have a dark UI, and selected text background is almost the same as the text itself), but only NSTextView seems to allow us to change this.

So we're trying to fake an NSTextField using an NSTextView, but can't get text scrolling to work the same.

The closest we get is with this code:

NSTextView *tf = [ [ NSTextView alloc ] initWithFrame: NSMakeRect( 30.0, 20.0, 80.0, 22.0 ) ];

// Dark UI
[tf setTextColor:[NSColor whiteColor]];
[tf setBackgroundColor:[NSColor darkGrayColor]];

// Fixed size
[tf setVerticallyResizable:FALSE];
[tf setHorizontallyResizable:FALSE];

[tf setAlignment:NSRightTextAlignment]; // Make it right-aligned (yup, we need this too)

[[tf textContainer] setContainerSize:NSMakeSize(2000, 20)]; // Try to Avoid line wrapping with this ugly hack
[tf setFieldEditor:TRUE]; // Make Return key accept the textfield

// Set text properties
NSMutableDictionary *dict = [[[tf selectedTextAttributes] mutableCopy ] autorelease];
[dict setObject:[NSColor orangeColor] forKey:NSBackgroundColorAttributeName];
[tf setSelectedTextAttributes:dict];

This works almost alright, except that if the text is longer than the text field, you can't scroll to it in any way.

Any idea as how to accomplish this?

Thanks in advance

Edit: Solution suggested below by Joshua Nozzi

Thanks to Joshua, this is a great solution to what I was looking for:

@interface ColoredTextField : NSTextField
- (BOOL)becomeFirstResponder;
@end

@implementation ColoredTextField
- (BOOL)becomeFirstResponder
{
    if (![super becomeFirstResponder])
        return NO;

    NSDictionary * attributes = [NSDictionary dictionaryWithObjectsAndKeys : 
                     [NSColor orangeColor], NSBackgroundColorAttributeName, nil];

    NSTextView * fieldEditor = (NSTextView *)[[self window] fieldEditor:YES forObject:self];
    [fieldEditor setSelectedTextAttributes:attributes];
    return YES;
}
@end

Instead of faking it with an NSTextView, it's just an NSTextField that changes the selected text color when it becomes first responder.

Edit: The above code falls back to the default selection color once you press Enter in the textfield. Here's a way to avoid that.

@interface ColoredTextField : NSTextField
- (BOOL)becomeFirstResponder;
- (void)textDidEndEditing:(NSNotification *)notification;

- (void)setSelectedColor;
@end

@implementation ColoredTextField
- (BOOL)becomeFirstResponder
{
    if (![super becomeFirstResponder])
        return NO;
    [self setSelectedColor];
    return YES;
}

- (void)textDidEndEditing:(NSNotification *)notification
{
    [super textDidEndEditing:notification];
    [self setSelectedColor];
}

- (void) setSelectedColor
{
    NSDictionary * attributes = [NSDictionary dictionaryWithObjectsAndKeys : 
                                [NSColor orangeColor], NSBackgroundColorAttributeName, nil];

    NSTextView * fieldEditor = (NSTextView *)[[self window] fieldEditor:YES forObject:self];
    [fieldEditor setSelectedTextAttributes:attributes];
}
@end
+1  A: 

Why not just set the properties of the text field's field editor directly when the field becomes first responder?

In a typical text field, selection is only visible when the field is first responder, so if you ask for the text field's field editor, then set its selected text attributes, you'd get the same affect, wouldn't you?

NSDictionary * attributes = [NSDictionary dictionaryWithObjectsAndKeys:
    [NSColor orangeColor], NSBackgroundColorAttributeName, nil];
NSTextView * fieldEditor = (NSTextView *)[[self window] fieldEditor:YES 
    forObject:textField];
[fieldEditor setSelectedTextAttributes:attributes];

Note: As discussed in the comments below, the documentation is correct in saying the field editor is an instance of NSTextView, but the -[NSWindow fieldEditor:forObject:] method claims to return an NSText (NSTextView's immediate superclass). If you plan to send the field editor NSTextView-only methods, you'll need to cast it as an NSTextView to quiet the compiler's warnings. The casting won't break anything in your code, should the method prototype be corrected in the future, so it can safely be left in place.

Joshua Nozzi
Take a look at this answer to another question. It shows how to manipulate the field editor for an NSTokenField (which is a subclass of NSTextField, so it works exactly the same way). http://stackoverflow.com/questions/2995205/prevent-selecting-all-tokens-in-nstokenfield
Joshua Nozzi
The text field's field editor is an NSText object, and that class doesn't have the setSelectedTextAttributes method either. Unless I'm missing something, it doesn't seem like it can be done this way.
hasvn
@hasvn: This is confusing... the prototype for `-[NSWindow fieldEditor:forObject:]` says it returns `NSText*`, but the documentation says that the field editor is an NSTextView object.
JWWalker
The documentation is correct; the prototype is not. Easily verified in the debugger and typecasting shuts up the compiler (and won't break if/when the prototype is fixed). File bugs: http://bugreporter.apple.com
Joshua Nozzi
Great, combining the 2 pieces of info you just gave me, it works perfectly. I'll add the final code to the original post.
hasvn
Whenever I've done this, I've explicitly checked to make sure that the object `isKindOfClass:[NSTextView class]` before casting just in case Apple decides to "fix" the implementation instead of the interface in future.
Alex
A good idea. :-)
Joshua Nozzi