darling-cocotron/AppKit/NSPopUpView.m
2020-05-12 17:04:16 -04:00

775 lines
28 KiB
Objective-C

/* Copyright (c) 2006-2007 Christopher J. W. Lloyd <cjwl@objc.net>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. */
#import <AppKit/NSGraphicsStyle.h>
#import <AppKit/NSMenuItem.h>
#import <AppKit/NSPopUpView.h>
#import <AppKit/NSStringDrawer.h>
enum { KEYBOARD_INACTIVE, KEYBOARD_ACTIVE, KEYBOARD_OK, KEYBOARD_CANCEL };
// TODO : NSPopUpView should at least inherit from NSMenuView - we have plenty
// of common code here
#define ITEM_MARGIN 2
@implementation NSPopUpView
// If the user clicks and holds on a popup menu and then releases
// the app should just dismiss the popup and reselect the current value.
// If the user clicks and immediately releases the popup menu should remain
// on screen. This threshold is the dividing line between those two behaviours.
static const NSTimeInterval kMenuInitialClickThreshold = .3f;
#define MIN_TITLE_KEY_GAP 8
#define WINDOW_BORDER_THICKNESS 3
#define IMAGE_TITLE_GAP 8
// Note: moved these above init to avoid compiler warnings
- (NSDictionary *) itemAttributes {
return [NSDictionary
dictionaryWithObjectsAndKeys: _font, NSFontAttributeName, nil];
}
- (NSArray *) visibleItemArray {
NSArray *items = [[self menu] itemArray];
NSMutableArray *visibleArray = [[[NSMutableArray alloc] init] autorelease];
for (NSMenuItem *item in items) {
if (![item isHidden]) {
[visibleArray addObject: item];
}
}
return visibleArray;
}
- (NSSize) sizeForMenuItemImage: (NSMenuItem *) item {
NSSize result = NSZeroSize;
if ([item image] != nil)
result = [[item image] size];
return result;
}
- (NSSize) sizeForMenuItems: (NSArray *) items {
NSSize result = NSZeroSize;
CGFloat maxTitleWidth = 0.0f;
BOOL anItemHasAnImage = NO;
CGFloat maxKeyWidth = 0.0f;
CGFloat totalHeight =
WINDOW_BORDER_THICKNESS; // border. Magic constants that may not be
// right for Win7 vs XP
NSSize gutterSize = [[self graphicsStyle] menuItemGutterSize];
NSSize rightArrowSize = [[self graphicsStyle] menuItemBranchArrowSize];
unsigned i, count = [items count];
NSRect rects[count];
BOOL useCustomFont = _font != nil;
if (useCustomFont) {
// If we have the default font, then really use the default menu one
// instead of forcing it
if ([_font isEqual: [NSFont fontWithName: @"Arial" size: 9.]]) {
useCustomFont = NO;
}
}
for (i = 0; i < count; i++) {
NSMenuItem *item = [items objectAtIndex: i];
CGFloat height = 0.;
CGFloat titleAndIconWidth = self.bounds.size.width;
if ([item isSeparatorItem]) {
height = [[self graphicsStyle] menuItemSeparatorSize].height;
} else {
NSSize size = NSZeroSize;
size = [self sizeForMenuItemImage: item];
height = MAX(height, size.height);
if ((titleAndIconWidth = size.width) > 0.0f)
anItemHasAnImage = YES;
if ([item attributedTitle]) {
size = [[self graphicsStyle]
menuItemAttributedTextSize: [item attributedTitle]];
} else {
if (useCustomFont) {
NSDictionary *attributes = [self itemAttributes];
NSAttributedString *attributedTitle =
[[[NSAttributedString alloc]
initWithString: [item title]
attributes: attributes] autorelease];
size = [[self graphicsStyle]
menuItemAttributedTextSize: attributedTitle];
} else {
size = [[self graphicsStyle]
menuItemTextSize: [item title]];
}
}
titleAndIconWidth += size.width;
maxTitleWidth = MAX(maxTitleWidth, titleAndIconWidth);
height = MAX(height, size.height);
if ([[item keyEquivalent] length] != 0) {
size = [[self graphicsStyle]
menuItemTextSize: [item _keyEquivalentDescription]];
maxKeyWidth = MAX(maxKeyWidth, size.width);
height = MAX(height, size.height);
}
height = MAX(height, gutterSize.height);
height = MAX(height, rightArrowSize.height);
}
rects[i] = NSMakeRect(0, totalHeight, titleAndIconWidth, height);
totalHeight += height;
}
result.height = totalHeight;
result.width = gutterSize.width;
result.width += [[self graphicsStyle] menuItemGutterGap];
if (anItemHasAnImage)
result.width += IMAGE_TITLE_GAP;
result.width += maxTitleWidth;
if (maxKeyWidth > 0.0f) {
result.width += MIN_TITLE_KEY_GAP;
result.width += maxKeyWidth;
}
result.width += rightArrowSize.width;
// Add the left+right and bottom borders
result.width += WINDOW_BORDER_THICKNESS * 2;
result.height += WINDOW_BORDER_THICKNESS;
if (_cachedItemRects == nil && [items isEqual: [self visibleItemArray]]) {
// Build our cached item rects
_cachedItemRects = [[NSMutableArray arrayWithCapacity: count] retain];
for (int i = 0; i < count; ++i) {
// Normalize the items widths to the menu widths
rects[i].size.width = result.width;
[_cachedItemRects addObject: [NSValue valueWithRect: rects[i]]];
}
}
return result;
}
- initWithFrame: (NSRect) frame {
[super initWithFrame: frame];
_cellSize = frame.size;
_font = [[NSFont messageFontOfSize: 12] retain];
_selectedIndex = -1;
_pullsDown = NO;
NSSize sz = [@"ABCxyzgjX" sizeWithAttributes: [self itemAttributes]];
_cellSize.height = sz.height + ITEM_MARGIN + ITEM_MARGIN;
return self;
}
- (void) dealloc {
[_cachedItemRects release];
[_font release];
[super dealloc];
}
- (BOOL) isFlipped {
return YES;
}
- (void) setFont: (NSFont *) font {
[_font autorelease];
_font = [font retain];
}
- (BOOL) pullsDown {
return _pullsDown;
}
- (void) setPullsDown: (BOOL) pullsDown {
_pullsDown = pullsDown;
}
- (void) selectItemAtIndex: (NSInteger) index {
_selectedIndex = index;
_initialSelectedIndex = index;
}
- (NSSize) sizeForContents {
return [self sizeForMenuItems: [self visibleItemArray]];
}
- (void) _buildCachedItemRects {
// Force a build of the cached item rects
[self sizeForMenuItems: [self visibleItemArray]];
}
// For attributed strings - precalcing the the item rects makes performance much
// faster.
- (NSArray *) _cachedItemRects {
if (_cachedItemRects == nil) {
[self _buildCachedItemRects];
}
return _cachedItemRects;
}
- (NSRect) rectForItemAtIndex: (NSInteger) index {
NSRect result = [[[self _cachedItemRects] objectAtIndex: index] rectValue];
// Be sure we cover the full view width
result.size.width = self.bounds.size.width;
return result;
}
- (NSRect) rectForSelectedItem {
if (_pullsDown) {
return NSZeroRect;
} else if (_selectedIndex == -1) {
return [self rectForItemAtIndex: 0];
} else {
return [self rectForItemAtIndex: _selectedIndex];
}
}
static NSRect boundsToTitleAreaRect(NSRect rect) {
return NSInsetRect(rect, WINDOW_BORDER_THICKNESS, WINDOW_BORDER_THICKNESS);
}
- (void) drawRect: (NSRect) rect {
NSRect bounds = [self bounds];
NSRect itemArea = boundsToTitleAreaRect(bounds);
NSArray *items = [self visibleItemArray];
unsigned i, count = [items count];
NSPoint origin = itemArea.origin;
BOOL useCustomFont = _font != nil;
if (useCustomFont) {
// If we have the default font, then really use the default menu one
// instead of forcing it
if ([_font isEqual: [NSFont fontWithName: @"Arial" size: 9.]]) {
useCustomFont = NO;
}
}
[[self graphicsStyle] drawMenuWindowBackgroundInRect: self.bounds];
for (i = 0; i < count; i++) {
NSMenuItem *item = [items objectAtIndex: i];
NSRect itemRect = [self rectForItemAtIndex: i];
if (NSIntersectsRect(rect, itemRect) == NO) {
continue;
}
itemArea = itemRect;
origin = itemRect.origin;
if ([item isSeparatorItem]) {
NSRect separatorRect = NSMakeRect(
origin.x, origin.y, NSWidth(itemArea),
[[self graphicsStyle] menuItemSeparatorSize].height);
[[self graphicsStyle] drawMenuSeparatorInRect: separatorRect];
origin.y += NSHeight(separatorRect);
} else {
#define CENTER_PART_RECT_VERTICALLY(partSize) { \
NSSize __partSize = (partSize); \
__partSize.width = ceilf(__partSize.width); \
partRect.origin.y = origin.y + (itemHeight - __partSize.height) / 2; \
partRect.size.height = __partSize.height; \
partRect.size.width = __partSize.width; \
}
NSImage *image = [item image];
BOOL selected = (i == _selectedIndex) ? YES : NO;
CGFloat itemHeight = itemRect.size.height;
NSRect partRect;
NSSize partSize;
BOOL showsEnabled = ([item isEnabled] || [item hasSubmenu]);
partRect = NSMakeRect(origin.x, origin.y, itemArea.size.width,
itemHeight);
if (selected)
[[self graphicsStyle] drawMenuSelectionInRect: partRect
enabled: showsEnabled];
// Draw the gutter and checkmark (if any)
CENTER_PART_RECT_VERTICALLY(
[[self graphicsStyle] menuItemGutterSize]);
if ([item state] || _initialSelectedIndex == i) {
[[self graphicsStyle] drawMenuGutterInRect: partRect];
[[self graphicsStyle] drawMenuCheckmarkInRect: partRect
enabled: showsEnabled
selected: selected];
}
partRect.origin.x += [[self graphicsStyle] menuItemGutterGap];
// Draw the image
if (image) {
NSRect imageRect;
partRect.origin.x += partRect.size.width;
CENTER_PART_RECT_VERTICALLY([image size]);
CGContextRef ctx =
[[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, partRect.origin.x,
partRect.origin.y);
if ([self isFlipped]) {
CGContextTranslateCTM(ctx, 0, partRect.size.height);
CGContextScaleCTM(ctx, 1, -1);
}
NSRect drawingRect = partRect;
drawingRect.origin = NSZeroPoint;
[[self graphicsStyle] drawButtonImage: image
inRect: drawingRect
enabled: showsEnabled
mixed: NO];
CGContextRestoreGState(ctx);
partRect.origin.x += IMAGE_TITLE_GAP;
}
// Draw the title
partRect.origin.x += partRect.size.width;
NSAttributedString *atitle = [item attributedTitle];
if (atitle != nil && [atitle length] > 0) {
CENTER_PART_RECT_VERTICALLY([[self graphicsStyle]
menuItemAttributedTextSize: atitle]);
#if WINDOWS
// On Windows, when using the AGG graphics context, enabling
// font smoothing switches to using AGG for text drawing,
// instead of the native Win32 API.
// In case we're using a lot of different fonts, like for a
// styled for menu, that's leading to much faster drawing thanks
// to fonts caching done by this context. However, drawing is
// more smoothed that when traditional Win32 text rendering,
// leading to a different look than standard Win32 menus - so
// we'll do that only if we're just drawing "normal" text,
// without any special attributes It would probably be better to
// add font caching to the regular Win32 drawing context
CGContextSetShouldSmoothFonts(
(CGContextRef)[[NSGraphicsContext
currentContext] graphicsPort],
YES);
#endif
[[self graphicsStyle] drawAttributedMenuItemText: atitle
inRect: partRect
enabled: showsEnabled
selected: selected];
} else {
#if WINDOWS
// On Windows, when using the AGG graphics context, enabling
// font smoothing switches to using AGG for text drawing,
// instead of the native Win32 API.
// In case we're using a lot of different fonts, like for a
// styled for menu, that's leading to much faster drawing thanks
// to fonts caching done by this context. However, drawing is
// more smoothed that when traditional Win32 text rendering,
// leading to a different look than standard Win32 menus - so
// we'll do that only if we're just drawing "normal" text,
// without any special attributes It would probably be better to
// add font caching to the regular Win32 drawing context
CGContextSetShouldSmoothFonts(
(CGContextRef)[[NSGraphicsContext
currentContext] graphicsPort],
NO);
#endif
NSString *title = [item title];
if (useCustomFont) {
NSDictionary *attributes = [self itemAttributes];
NSAttributedString *attributedTitle =
[[[NSAttributedString alloc]
initWithString: title
attributes: attributes] autorelease];
CENTER_PART_RECT_VERTICALLY([[self graphicsStyle]
menuItemAttributedTextSize: attributedTitle]);
[[self graphicsStyle]
drawAttributedMenuItemText: attributedTitle
inRect: partRect
enabled: showsEnabled
selected: selected];
} else {
CENTER_PART_RECT_VERTICALLY(
[[self graphicsStyle] menuItemTextSize: title]);
[[self graphicsStyle] drawMenuItemText: title
inRect: partRect
enabled: showsEnabled
selected: selected];
}
}
// Draw the key equivalent
if ([[item keyEquivalent] length] != 0) {
NSString *keyString = [item _keyEquivalentDescription];
NSSize branchArrowSize =
[[self graphicsStyle] menuItemBranchArrowSize];
NSSize keyEquivalentSize =
[[self graphicsStyle] menuItemTextSize: keyString];
partRect.origin.x = origin.x + NSWidth(itemArea) -
branchArrowSize.width -
keyEquivalentSize.width;
CENTER_PART_RECT_VERTICALLY(keyEquivalentSize);
[[self graphicsStyle] drawMenuItemText: keyString
inRect: partRect
enabled: showsEnabled
selected: selected];
}
// Draw the submenu arrow
if ([item hasSubmenu]) {
NSSize branchArrowSize =
[[self graphicsStyle] menuItemBranchArrowSize];
partRect.origin.x =
origin.x + NSWidth(itemArea) - branchArrowSize.width;
partRect.size.width = branchArrowSize.width;
CENTER_PART_RECT_VERTICALLY(branchArrowSize);
[[self graphicsStyle] drawMenuBranchArrowInRect: partRect
enabled: showsEnabled
selected: selected];
}
origin.y += itemHeight;
}
}
}
/*
* Returns NSNotFound if the point is outside of the menu
* Returns -1 if the point is inside the menu but on a disabled or separator
* item Returns a usable index if the point is on a selectable item
*/
- (NSInteger) itemIndexForPoint: (NSPoint) point {
NSInteger result;
NSArray *items = [self visibleItemArray];
int i, count = [items count];
point.y -= 2;
if (point.y < 0) {
return NSNotFound;
}
for (i = 0; i < count; i++) {
if ([[items objectAtIndex: i] isHidden])
continue;
NSRect itemRect = [self rectForItemAtIndex: i];
if (NSPointInRect(point, itemRect)) {
if (([[items objectAtIndex: i] isEnabled] == NO) ||
([[items objectAtIndex: i] isSeparatorItem])) {
return -1;
}
return i;
}
}
return NSNotFound;
}
- (void) rightMouseDown: (NSEvent *) event {
// do nothing
}
- (void) updateSelectedIndex: (NSInteger) index {
if (_selectedIndex != index) {
NSInteger previous = _selectedIndex;
_selectedIndex = index;
if (previous != _selectedIndex) {
if (previous != -1) {
NSRect itemRect = [self rectForItemAtIndex: previous];
[self setNeedsDisplayInRect: itemRect];
}
if (_selectedIndex != -1) {
NSRect itemRect = [self rectForItemAtIndex: _selectedIndex];
[self setNeedsDisplayInRect: itemRect];
}
}
}
}
/*
* This method may return NSNotFound when the view positioned outside the
* initial tracking area due to preferredEdge settings and the user clicks the
* mouse. The NSPopUpButtonCell code deals with it. It might make sense for this
* to return the previous value.
*/
- (NSInteger) runTrackingWithEvent: (NSEvent *) event {
enum {
STATE_FIRSTMOUSEDOWN,
STATE_MOUSEDOWN,
STATE_MOUSEUP,
STATE_EXIT
} state = STATE_FIRSTMOUSEDOWN;
NSPoint firstLocation, point = [event locationInWindow];
NSInteger initialSelectedIndex = _selectedIndex;
firstLocation = point;
// Make sure we get mouse moved events, too, so we can respond apporpiately
// to click-click actions as well as of click-and-drag
BOOL oldAcceptsMouseMovedEvents = [[self window] acceptsMouseMovedEvents];
[[self window] setAcceptsMouseMovedEvents: YES];
// point comes in on controls window
point = [[event window] convertBaseToScreen: point];
point = [[self window] convertScreenToBase: point];
point = [self convertPoint: point fromView: nil];
// Make sure we know if the user clicks away from the app in the middle of
// this
BOOL cancelled = NO;
NSRect screenVisible =
NSInsetRect([[[self window] screen] visibleFrame], 0, 4);
[NSEvent startPeriodicEventsAfterDelay: .0 withPeriod: .02];
BOOL mouseMoved = NO;
NSTimeInterval firstTimestamp = 0.;
do {
NSInteger index = [self itemIndexForPoint: point];
/*
If the popup is activated programmatically with performClick: index may
be NSNotFound because the mouse starts out outside the view. We don't
change _selectedIndex in this case.
*/
if (index != NSNotFound && _keyboardUIState == KEYBOARD_INACTIVE) {
[self updateSelectedIndex: index];
}
event = [[self window]
nextEventMatchingMask: NSPeriodicMask | NSLeftMouseUpMask |
NSMouseMovedMask |
NSLeftMouseDraggedMask | NSKeyDownMask |
NSAppKitDefinedMask];
if (firstTimestamp == 0.) {
// Note: we don't do that using the first event, because in case the
// menu takes a long of time to display, the next event we get will
// be actually after our "long click" threshold
firstTimestamp = [event timestamp];
}
if ([event type] == NSKeyDown) {
[self interpretKeyEvents: [NSArray arrayWithObject: event]];
switch (_keyboardUIState) {
case KEYBOARD_INACTIVE:
_keyboardUIState = KEYBOARD_ACTIVE;
continue;
case KEYBOARD_ACTIVE:
break;
case KEYBOARD_CANCEL:
_selectedIndex = initialSelectedIndex;
case KEYBOARD_OK:
state = STATE_EXIT;
break;
}
} else
_keyboardUIState = KEYBOARD_INACTIVE;
if ([event type] == NSAppKitDefined) {
if ([event subtype] == NSApplicationDeactivated) {
cancelled = YES;
}
}
point = [NSEvent mouseLocation];
if (mouseMoved == NO) {
mouseMoved = ABS(point.x - firstLocation.x) > 2. ||
ABS(point.y - firstLocation.y) > 2.;
}
if (NSPointInRect(point, [[self window] frame])) {
if (!NSPointInRect(point, screenVisible)) {
// The point is inside the menu, and on the top or bottom border
// of the screen - let's autoscroll
NSPoint origin = [[self window] frame].origin;
BOOL change = NO;
if (point.y < NSMinY(screenVisible)) {
origin.y += _cellSize.height;
change = YES;
}
if (point.y > NSMaxY(screenVisible)) {
origin.y -= _cellSize.height;
change = YES;
}
if (change)
[[self window] setFrameOrigin: origin];
}
} else {
[self updateSelectedIndex: -1];
}
// Convert the global point to view coordinates
point = [[self window] convertScreenToBase: point];
point = [self convertPoint: point fromView: nil];
if ([event type] == NSPeriodic) {
// Periodic events are just there for autoscroll so we're done with
// this one
continue;
}
switch (state) {
case STATE_FIRSTMOUSEDOWN:
if ([event type] == NSLeftMouseUp) {
if (mouseMoved || [event timestamp] - firstTimestamp >
kMenuInitialClickThreshold) {
// Long click - accept the selection
state = STATE_EXIT;
} else {
// Short click - let's keep the menu sticky
state = STATE_MOUSEUP;
}
} else {
if (mouseMoved) {
state = STATE_MOUSEDOWN;
}
}
break;
default:
if ([event type] == NSLeftMouseUp) {
// If the user clicked outside of the window - then they want
// to dismiss it without changing anything
NSPoint winPoint = [event locationInWindow];
winPoint = [[event window] convertBaseToScreen: winPoint];
if (NSPointInRect(winPoint, [[self window] frame]) == NO) {
[self updateSelectedIndex: -1];
}
state = STATE_EXIT;
}
break;
}
} while (cancelled == NO && state != STATE_EXIT);
[NSEvent stopPeriodicEvents];
[[self window] setAcceptsMouseMovedEvents: oldAcceptsMouseMovedEvents];
_keyboardUIState = KEYBOARD_INACTIVE;
return (_selectedIndex == -1) ? NSNotFound : _selectedIndex;
}
- (void) keyDown: (NSEvent *) event {
[self interpretKeyEvents: [NSArray arrayWithObject: event]];
}
- (void) moveUp: (id) sender {
NSInteger previous = _selectedIndex;
// Find the previous visible item
NSArray *items = [self visibleItemArray];
if (_selectedIndex == -1)
_selectedIndex = [items count];
do {
_selectedIndex--;
} while (_selectedIndex >= 0 &&
([[items objectAtIndex: _selectedIndex] isHidden] ||
[[items objectAtIndex: _selectedIndex] isSeparatorItem]));
if (_selectedIndex < 0)
_selectedIndex = previous;
[self setNeedsDisplay: YES];
}
- (void) moveDown: (id) sender {
NSInteger previous = _selectedIndex;
// Find the next visible item
NSArray *items = [self visibleItemArray];
NSInteger searchIndex = _selectedIndex;
do {
searchIndex++;
} while (searchIndex < [items count] &&
([[items objectAtIndex: searchIndex] isHidden] ||
[[items objectAtIndex: searchIndex] isSeparatorItem]));
if (searchIndex >= [items count]) {
_selectedIndex = previous;
} else {
_selectedIndex = searchIndex;
}
[self setNeedsDisplay: YES];
}
- (void) cancel: (id) sender {
_keyboardUIState = KEYBOARD_CANCEL;
}
- (void) insertNewline: (id) sender {
_keyboardUIState = KEYBOARD_OK;
}
- (void) insertText: (id) aString {
// We're intercepting insertText: so we can do menu navigation by letter
unichar ch = [aString characterAtIndex: 0];
NSString *letterString =
[[NSString stringWithCharacters: &ch length: 1] uppercaseString];
NSInteger oldIndex = _selectedIndex;
NSArray *items = [self visibleItemArray];
NSInteger newIndex = _selectedIndex;
// Set to the next item in the array or the start if we're at the end or
// there's no selection
if (oldIndex == NSNotFound || oldIndex == [items count] - 1) {
newIndex = 0;
} else {
newIndex = oldIndex + 1;
}
// Find the next visible item that has a title with an uppercase letter
// matching what the user entered (who knows what this means in Japan...)
BOOL found = NO;
while (!found && newIndex != oldIndex) {
// Make sure we stop eventually
if (oldIndex == NSNotFound || oldIndex == -1) {
oldIndex = 0;
}
// Try and find a new item to select
NSMenuItem *item = [items objectAtIndex: newIndex];
if ([item isEnabled] == YES && [item isSeparatorItem] == NO) {
NSRange range = [[item title] rangeOfString: letterString];
if (range.location != NSNotFound) {
_selectedIndex = newIndex;
found = YES;
}
}
if (!found) {
if (newIndex == [items count] - 1) {
newIndex = 0;
} else {
newIndex++;
}
}
}
if (newIndex != oldIndex) {
[self setNeedsDisplay: YES];
}
}
@end