2

This answer describes how to set the font, and thus the font color, of an NSMenuItem.

In order to alert the user to a problem with the selected item in a popup menu, I set the color to red. Works great, except when the item is highlighted, the background becomes blue, and my red-on-blue is hard to read and looks lousy. The font of regular menu items changes from black to white. I would like my modified menu item to change its font color when highlighted like that.

This is a dynamic menu. I set the font/color when items are created, in -menuNeedsUpdate. Of course, -[NSMenuItem isHighlighted] returns NO there because the item has just been created.

I also tried adding an observer on NSMenuDidBeginTrackingNotification and NSMenuDidBeginTrackingNotification, but that doesn't help either because these two notifications are always received in pairs, three to six pair each time I click the menu, and then after tracking has ended comes another -menuNeedsUpdate: which re-creates everything from scratch again. I'm not sure what it means when a menu is "tracking", but apparently it's not what I want.

I thought I'd ask if anyone has ever come up with a good answer for this, before I go off and do something really kludgey like these guys did for a similar NSMenuItem quandary.

Community
  • 1
  • 1
Jerry Krinock
  • 4,860
  • 33
  • 39

1 Answers1

4

You can implement the menu's delegate to be notified when an item is highlighted.

#pragma mark - NSMenuDelegate

- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item {
    [menu.highlightedItem nik_restoreTextColor];
    [item nik_overrideTextColor:[NSColor selectedMenuItemTextColor]];
}

It should be pretty straightforward to remove and re-add the color of a single item. But here's the generic solution I'm using to remember and later restore the color:

@implementation NSMutableAttributedString(NIKExchangeAttribute)

- (void)nik_renameAttribute:(NSString *)originalAttribute to:(NSString *)newAttribute {
    NSRange fullRange = NSMakeRange(0, self.length);
    [self removeAttribute:newAttribute range:fullRange];
    [self enumerateAttribute:originalAttribute
                     inRange:fullRange
                     options:0
                  usingBlock:^(id value, NSRange range, BOOL *stop) {
        [self addAttribute:newAttribute value:value range:range];
    }];
    [self removeAttribute:originalAttribute range:fullRange];
}

@end

static NSString *const ORIGINAL_COLOR_KEY = @"nik_originalColor";

@implementation NSMenuItem(NIKOverrideColor)

- (void)nik_overrideTextColor:(NSColor *)textColor {
    NSMutableAttributedString *title = [self.attributedTitle mutableCopy];
    [title nik_renameAttribute:NSForegroundColorAttributeName to:ORIGINAL_COLOR_KEY];
    [title addAttribute:NSForegroundColorAttributeName
                  value:textColor
                  range:NSMakeRange(0, title.length)];
    self.attributedTitle = title;
}

- (void)nik_restoreTextColor {
    NSMutableAttributedString *title = [self.attributedTitle mutableCopy];
    [title nik_renameAttribute:ORIGINAL_COLOR_KEY to:NSForegroundColorAttributeName];
    self.attributedTitle = title;
}

@end
nschum
  • 15,322
  • 5
  • 58
  • 56