Responding to Events in an NSView

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

Tip

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.

Getting a Mouse-Down Event

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.

  1. 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.

  1. 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;
    }
  2. 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:

  1. 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.

  1. Build and run ViewDemo with PolygonView. Save all pertinent files when prompted.

  2. 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.

Clicking outside the polygon causes an NSAlertPanel to be displayed

Figure 15-10. Clicking outside the polygon causes an NSAlertPanel to be displayed

  1. Quit ViewDemo.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.199.227