5 // Created by Evan Schoenberg on 12/20/05.
8 #import "AIMenuItemView.h"
10 #define CHECK_UNICODE 0x2713 /* Unicode CHECK MARK*/
12 #define MENU_ITEM_HEIGHT 17
13 #define MENU_ITEM_SPACING 2
15 @interface AIMenuItemView (PRIVATE)
19 @implementation AIMenuItemView
20 - (void)_initMenuItemView
22 currentHoveredIndex = -1;
27 if ([[self superclass] instancesRespondToSelector:@selector(awakeFromNib)]) {
31 if (delegate && [delegate respondsToSelector:@selector(menuForMenuItemView:)]) {
32 [self setMenu:[delegate menuForMenuItemView:self]];
35 [self _initMenuItemView];
38 - (id)initWithFrame:(NSRect)inFrame
40 if ((self = [super initWithFrame:inFrame])) {
41 [self _initMenuItemView];
50 [trackingTags release];
51 [menuItemAttributes release];
52 [disabledMenuItemAttributes release];
53 [hoveredMenuItemAttributes release];
62 * @brief Set the menu we display
64 - (void)setMenu:(NSMenu *)inMenu
66 // NSLog(@"Set menu: %@",inMenu);
69 menu = [inMenu retain];
72 [self setNeedsDisplay:YES];
74 if ([delegate respondsToSelector:@selector(menuItemViewDidChangeMenu:)]) {
75 [delegate menuItemViewDidChangeMenu:self];
77 // NSLog(@"set menu reset");
78 [self resetCursorRects];
87 * @brief Set our delegate
89 - (void)setDelegate:(id)inDelegate
91 delegate = inDelegate;
94 #pragma mark Index and Point/Rect Correlation
97 * @brief Return the index at a point
99 * @param inPoint The point in our local coordinates
101 - (int)indexAtPoint:(NSPoint)inPoint
103 float heightFromTop = [self frame].size.height - inPoint.y;
106 while ((heightFromTop - (((MENU_ITEM_HEIGHT + MENU_ITEM_SPACING) * index) + MENU_ITEM_HEIGHT)) > 0) {
114 * @brief Return the rect (in local coordinates) for a menu item by index
116 - (NSRect)rectForIndex:(int)index
118 NSRect myFrame = [self frame];
120 (myFrame.size.height - (((MENU_ITEM_HEIGHT + MENU_ITEM_SPACING) * index) + MENU_ITEM_HEIGHT)),
127 - (void)drawRect:(NSRect)inRect
129 int i, numberOfMenuItems;
130 BOOL willDisplayACheckbox = NO;
132 numberOfMenuItems = [menu numberOfItems];
134 //Determine if one or more menu items is in a non-off (that it, on or mixed) state.
135 for (i = 0; i < numberOfMenuItems; i++) {
136 NSMenuItem *menuItem = [menu itemAtIndex:i];
137 if ([menuItem state] != NSOffState) {
138 willDisplayACheckbox = YES;
143 //Now do the actual drawing
144 for (i = 0; i < numberOfMenuItems; i++) {
145 NSRect menuItemRect = [self rectForIndex:i];
147 if (NSIntersectsRect(menuItemRect,inRect)) {
148 NSMenuItem *menuItem = [menu itemAtIndex:i];
150 if ([menuItem isSeparatorItem]) {
151 //Draw the separatorItem line centered in the menu item rect
152 menuItemRect.origin.y = (menuItemRect.origin.y + (menuItemRect.size.height / 2) - 1);
153 menuItemRect.size.height = 1;
155 [[NSColor grayColor] set];
156 NSRectFill(menuItemRect);
159 NSAttributedString *title;
160 BOOL currentlyHovered = ((currentHoveredIndex == i) && [menuItem isEnabled]);
162 if (currentlyHovered) {
163 //Draw a selectedMenuItemColor box if we are hovered...
164 [[NSColor selectedMenuItemColor] set];
165 NSRectFill(menuItemRect);
168 //Move in so the highlight drawn above has proper borders around a checkmark
169 menuItemRect.origin.x += 1;
170 menuItemRect.size.width -= 1;
172 //Indent the menu item if appropriate
173 float indentation = [menuItem indentationLevel] * 5.0;
174 menuItemRect.origin.x += indentation;
175 menuItemRect.size.width -= indentation;
177 if ([menuItem state] == NSOnState) {
178 NSImage *onStateImage;
181 if (currentlyHovered) {
182 //If we're currently hovered, we need to turn our checkmark white...
183 NSImage *originalImage = [menuItem onStateImage];
184 size = [originalImage size];
186 onStateImage = [[[NSImage alloc] initWithSize:[originalImage size]] autorelease];
188 [onStateImage lockFocus];
190 //Fill the new image with white
191 [[NSColor whiteColor] set];
192 NSRectFill(NSMakeRect(0, 0, size.width, size.height));
194 //But only keep the white where originalImage (the checkmark) exists
195 [originalImage drawInRect:NSMakeRect(0, 0, size.width, size.height)
196 fromRect:NSMakeRect(0, 0, size.width, size.height)
197 operation:NSCompositeDestinationAtop
199 [onStateImage unlockFocus];
202 onStateImage = [menuItem onStateImage];
203 size = [onStateImage size];
206 [onStateImage drawAtPoint:NSMakePoint(menuItemRect.origin.x,
207 menuItemRect.origin.y + ((menuItemRect.size.height - size.height) / 2))
208 fromRect:NSMakeRect(0, 0, size.width, size.height)
209 operation:NSCompositeSourceOver
210 fraction:(currentlyHovered ? 1.0 : 0.85)];
213 NSDictionary *currentTextAttributes;
215 if (currentlyHovered) {
216 //We're displaying the hovered menu item
217 if (!hoveredMenuItemAttributes) {
218 hoveredMenuItemAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
219 [NSFont menuFontOfSize:13], NSFontAttributeName,
220 [NSColor selectedMenuItemTextColor], NSForegroundColorAttributeName,
224 currentTextAttributes = hoveredMenuItemAttributes;
226 } else if (![menuItem isEnabled]) {
227 //We're displaying a disabled menu item
228 if (!disabledMenuItemAttributes) {
229 disabledMenuItemAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
230 [NSFont menuFontOfSize:13], NSFontAttributeName,
231 [NSColor disabledControlTextColor], NSForegroundColorAttributeName,
235 currentTextAttributes = disabledMenuItemAttributes;
238 //We're displaying a non-hovered menu item
239 if (!menuItemAttributes) {
240 menuItemAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
241 [NSFont menuFontOfSize:13], NSFontAttributeName,
245 currentTextAttributes = menuItemAttributes;
248 title = [[NSAttributedString alloc] initWithString:[menuItem title]
249 attributes:currentTextAttributes];
251 menuItemRect.origin.x += 2;
252 menuItemRect.size.width -= 2;
254 if (willDisplayACheckbox) {
255 //Shift right, and shorten our width, for indentation like a real menu, leaving space for the checkmark if it's needed
256 #define CHECKMARK_WIDTH 9
257 menuItemRect.origin.x += CHECKMARK_WIDTH;
258 menuItemRect.size.width -= CHECKMARK_WIDTH;
261 [title drawInRect:menuItemRect];
271 NSRect frame = [self frame];
273 float change = (([[self menu] numberOfItems] * (MENU_ITEM_HEIGHT + MENU_ITEM_SPACING)) - MENU_ITEM_SPACING) - frame.size.height;
274 frame.size.height += change;
275 frame.origin.y -= change;
277 [self setFrame:frame];
280 #pragma mark Hovering tracking
283 * @brief The mouse is hovering over a point
285 * @param inPoint The point in our local coordinates, or NULL if we are no longer hovering
287 - (void)setHoveringAtPoint:(NSPoint)inPoint
289 if (!NSEqualPoints(inPoint, NSZeroPoint)) {
290 currentHoveredIndex = [self indexAtPoint:inPoint];
292 currentHoveredIndex = -1;
295 [self setNeedsDisplay:YES];
299 //Cursor entered one of our tracking rects
300 - (void)mouseEntered:(NSEvent *)theEvent
302 [self setHoveringAtPoint:[self convertPoint:[theEvent locationInWindow] fromView:nil]];
304 [super mouseEntered:theEvent];
308 //Cursor left one of our tracking rects
309 - (void)mouseExited:(NSEvent *)theEvent
311 [self setHoveringAtPoint:NSZeroPoint];
313 [super mouseExited:theEvent];
316 - (void)mouseDown:(NSEvent *)theEvent
318 if (currentHoveredIndex != -1) {
319 NSMenuItem *menuItem = [[self menu] itemAtIndex:currentHoveredIndex];
320 [[menuItem target] performSelector:[menuItem action] withObject:menuItem];
323 [super mouseDown:theEvent];
327 #pragma mark Tracking rects
330 * @brief Remove all our tracking rects
332 - (void)removeTrackingRects
334 NSEnumerator *enumerator;
335 NSNumber *trackingTag;
337 enumerator = [trackingTags objectEnumerator];
338 while ((trackingTag = [enumerator nextObject])) {
339 [self removeTrackingRect:[trackingTag intValue]];
342 [trackingTags release]; trackingTags = nil;
345 //Reset our cursor tracking
346 - (void)resetCursorRects
348 //Stop any existing tracking
350 [self removeTrackingRects];
353 //Add tracking rects if our superview and window are ready
354 if ([self superview] && [self window]) {
355 int i, numberOfMenuItems;
357 trackingTags = [[NSMutableSet alloc] init];
359 numberOfMenuItems = [menu numberOfItems];
360 for (i = 0; i < numberOfMenuItems; i++) {
361 NSTrackingRectTag trackingTag;
362 NSRect trackRect = [self rectForIndex:i];
363 NSPoint localPoint = [self convertPoint:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
365 BOOL mouseInside = NSPointInRect(localPoint, trackRect);
367 trackingTag = [self addTrackingRect:trackRect owner:self userData:nil assumeInside:mouseInside];
368 [trackingTags addObject:[NSNumber numberWithInt:trackingTag]];
369 NSLog(@"Added tracking rect %i for %@ (%i)",trackingTag,NSStringFromRect(trackRect), mouseInside);
370 if (mouseInside) [self mouseEntered:nil];