In addition to drawing, the NSView class can also process events (because it’s a subclass of the NSResponder abstract superclass). To receive mouse-down or mouse-up events, all your custom NSView needs to do is override one of the following event-handling methods (there are others) that are declared in the NSResponder class:
- (void)mouseDown:
(NSEvent *)theEvent
- (void)rightMouseDown:
(NSEvent *)theEvent
- (void)mouseUp:
(NSEvent *)theEvent
- (void)rightMouseUp:
(NSEvent *)theEvent
- (void)mouseDragged:
(NSEvent *)theEvent
- (void)rightMouseDragged:
(NSEvent *)theEvent
To receive mouse-entered or mouse-exited events, your custom NSView needs to override one of the following event methods and set up a tracking rectangle — something we will describe later, in Chapter 18.
- (void)mouseEntered:
(NSEvent *)theEvent
- (void)mouseExited:
(NSEvent *)theEvent
Additionally, if your custom NSView is made the first responder of its containing window, it will receive the following keyboard and mouse events:
- (void)keyDown:
(NSEvent *)theEvent
- (void)keyUp:
(NSEvent *)theEvent
- (void)mouseMoved:
(NSEvent *)theEvent
mouseMoved: events must be turned on explicitly. We will discuss this topic in Chapter 18.
In the remainder of this section, we’ll show you how to receive and interpret mouse-related events and how to detect whether or not a point is within a polygon.
Overriding an event method can be as simple as adding a single method to your PolygonView class definition. Determining whether a mouseclick is inside or outside a polygon, a process called hit detection , is a bit more complicated. Fortunately, the NSBezierPath object that we use to draw the polygon will also take care of hit detection. We just need to keep the object intact, rather than letting it be autoreleased.
The following modifications will change the PolygonView class so that it has a list of colors for drawing the polygon. Each time you click the polygon, it will be redisplayed in a different color. If you click outside the polygon, an alert panel will be displayed instead.
Back in PB, insert the three lines shown here in bold into
PolygonView.h
:
#import <Cocoa/Cocoa.h> @interface PolygonView : NSView { int sides; NSBezierPath *shape; NSMutableArray *colors; int colorNum; } - (IBAction)takeNumSidesFrom:(id)sender; - (void)setNumSides:(int)val; - (void)setSize:(float)size; - (IBAction)takeFloatSize:(id)sender; @end
The shape
instance variable will replace the
local variable with the same name in thedrawRect:
method in our previous PolygonView
example, so that we can perform hit detection. The
colors
array will be used to keep track of the
list of colors, while colorNum
will track the
current color. The latter two instance variables need to be set up.
Insert the six lines shown here in bold into the initWithFrame:
method in
PolygonView.m
:
- initWithFrame:(NSRect)rect { [super initWithFrame:rect]; [self setBounds:NSMakeRect(-1,-1,2,2)]; [self setNumSides:3]; colors = [[NSMutableArray alloc] init]; [colors addObject:[NSColor blackColor]]; [colors addObject:[NSColor blueColor]]; [colors addObject:[NSColor redColor]]; [colors addObject:[NSColor greenColor]]; [colors addObject:[NSColor whiteColor]]; return self; }
Replace the drawRect:
method in
PolygonView.m
with the new version that follows:
-(void)drawRect:(NSRect)rect { float theta; if (shape) { // New [shape release]; shape = nil; } shape = [ [NSBezierPath bezierPath] retain]; // New [shape moveToPoint:NSMakePoint(sin(0.0),cos(0.0))]; // M_PI is a predefined value of PI. // M_PI*2.0 is number of radians in a circle. // The for( ) statement below sweeps through each // pie-section of the polygon for each side. for (theta=0.0; theta <= 2*M_PI; theta += (M_PI*2.0)/sides) { [shape lineToPoint:NSMakePoint(sin(theta),cos(theta)) ]; } [ [colors objectAtIndex:colorNum] set]; // New [shape fill]; }
These code changes accomplish two things. By retaining the
shape
variable (as an instance variable rather
than as a local variable), we assure that it will not be freed and
will be available to perform hit detection. The second change causes
the polygon to be drawn in the currently selected color, rather than
always in black.
Finally, we need to implement the mouseDown: event:
Insert the following mouseDown:
method into PolygonView.m
:
- (void)mouseDown:(NSEvent *)theEvent { NSPoint loc = [self convertPoint: [theEvent locationInWindow] fromView:nil]; if ([shape containsPoint:loc]) { colorNum = (colorNum+1) % [colors count]; [self setNeedsDisplay:YES]; } else { NSRunAlertPanel([self description], @"You missed the shape!",nil,nil,nil); } }
This mouseDown: method is passed a
pointer to the theEvent
object and sends the
object the locationInWindow message
to find out the point (loc
) where the mouseDown: event took place. The returned
NSPoint is then converted from NSWindow coordinates to the NSView
coordinates using the convertPoint:fromView: method.
We next invoke the NSBezierPath method called containsPoint:, which returns YES if the
passed point (loc
) is inside the path and NO of it
is not. If the point is within the path, we increment
colorNum
to the next color (mod [colors
count]), where [colors
count] returns the number of colors in the
colors
array. If the event is not inside the path,
we use the NSRunAlertPanel( )
function to display
an alert panel.
The NSRunAlertPanel( )
function takes five
mandatory arguments: the title for the alert panel, the text, and the
text of up to three buttons. If the second argument is a format
string, you can provide additional arguments after the fifth
argument. In this example, the title of the alert panel is the
Objective-C description string for the PolygonView itself, provided
using the NSObject-inherited description method.
Build and run ViewDemo with PolygonView. Save all pertinent files when prompted.
Click the mouse inside the polygon, and it will change color. Click the mouse outside the polygon (but in the PolygonView), and you will see an alert panel, as shown in Figure 15-10.
18.224.73.102