Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Controller.m
// Controller.m |
// TextLinks |
// Copyright (c) 2003 Apple Computer, Inc. All rights reserved. |
// See legal notice at end of file. |
// This application demonstrates how to programmatically create links in text, |
// and optionally handle clicks in those links. See README.rtf for more information. |
// MSM |
// version 0.2 July 2003 -- fixes logic for mouse tracking |
// version 0.1 February 2003 |
#import "Controller.h" |
// Tags in createLinkType NSMatrix |
typedef enum |
{ linkToURLFromSelectionTag = 0, // create a link which shows the URL |
linkToOtherURLTag = 1, // create a link which doesn't show the URL |
linkToAlertTag = 2 // create a link to something other than a URL |
} linkTag; |
typedef enum |
{ linkShowLinkInfo = 0, // show the NSLink part of the attributes |
linkShowAttributes = 1 // show all attributes |
} showTag; |
@implementation Controller |
#pragma mark PRIVATE INSTANCE METHODS |
// Return any text selected in the main text-view, cleaned up a little bit. |
- (NSString *) selectedText |
{ |
NSString *result; |
result = [[[textView textStorage] string] substringWithRange: [textView selectedRange]]; |
// Remove trailing newlines and other junk, which will mess up the radio button we fill in. |
// (Really should remove newlines in the middle, too.) |
return [result stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]; |
} |
- (NSObject *) urlFromString: (NSString *) aString; // RETURN: nil means user's been alerted to the error |
{ |
NSURL *url; |
url = [NSURL URLWithString: aString]; |
if (url == nil) |
NSRunAlertPanel (@"Create Link", |
@"Ò%@Ó isnÕt a valid URL", |
nil, nil, nil, |
aString); // argument for %@ in the string above |
return url; |
} |
// Create and return the object which we'll link to. This can be an NSURL for a traditional link, |
// or anything else. |
// This method gets invoked only from -createLink:, and is broken out to simplify that method. |
- (NSObject *) linkObject; // RETURN: nil means user's been alerted to the error |
{ |
NSString *alertText; |
switch ((linkTag) [createLinkTypeMatrix selectedTag]) |
{ |
// If the user selects Òhttp://www.apple.comÓ, create a link to that address. |
case linkToURLFromSelectionTag: |
return [self urlFromString: [self selectedText]]; |
// No matter what the user selected, create a link to the address in the field. |
case linkToOtherURLTag: |
if ([createLinkURLField stringValue] == 0) |
{ |
[createLinkURLField selectText: nil]; // make it easy for them to correct |
NSRunAlertPanel (@"Create Link", @"Please enter a URL.", nil, nil, nil); |
return nil; |
} |
return [self urlFromString: [createLinkURLField stringValue]]; |
// No matter what the user selected, create a link to an NSString instance. |
case linkToAlertTag: |
alertText = [createLinkAlertField stringValue]; |
if ([alertText length] == nil) |
{ |
[createLinkAlertField selectText: nil]; // make it easy for them to correct |
NSRunAlertPanel (@"Create Link", @"Please enter the text to alert.", nil, nil, nil); |
return nil; |
} |
return alertText; |
break; |
default: // don't expect this |
return nil; |
} |
} |
// Find the attributes of the text the mouse is pointing to in the NSTextView and display them. |
- (void) showAttributesUnderMouse |
{ |
NSPoint mouseLoc, mouseLocInContentView; |
NSView *contentView; |
int glyphIndex, charIndex; |
NSDictionary *attributes; |
// Limit the work we do if the app isnÕt active |
// (Would be even better to stop the timer entirely...) |
if (! [[NSApplication sharedApplication] isActive]) |
{ |
[feedbackField setStringValue: @"(the application is not active)"]; |
[feedbackField setTextColor: [NSColor darkGrayColor]]; |
return; |
} |
// Get mouse location in window coordinates |
mouseLoc = [[textView window] mouseLocationOutsideOfEventStream]; |
// Convert to view coordinates |
mouseLoc = [textView convertPoint: mouseLoc fromView: nil]; |
// Note that using [[textView textContainer] containsPoint: mouseLoc] won't work, |
// because the container for a flexible-height text-view is ÒbottomlessÓ -- it |
// will consider the mouse to be inside the text even when the mouse is below it. |
// And... using [textView mouse: mouseLoc inRect: [textView bounds]] won't work, |
// because the text view itself can grow below the bottom of the scroll-view. So use |
// the scroll-view's content view. |
contentView = [[textView enclosingScrollView] contentView]; |
mouseLocInContentView = [contentView convertPoint: mouseLoc fromView: textView]; |
if ([contentView mouse: mouseLocInContentView inRect: [contentView bounds]]) |
{ |
// Map the mouse location to the glyph index. |
glyphIndex = [[textView layoutManager] glyphIndexForPoint: mouseLoc inTextContainer: [textView textContainer]]; |
// Glyphs aren't always the same as characters in a string, so now find the character index in the string. |
charIndex = [[textView layoutManager] characterIndexForGlyphAtIndex: glyphIndex]; |
} |
else |
{ |
// Flag that we're not in view |
charIndex = -1; |
} |
// It's tempting to cache ÒcharIndexÓ here, then compare to the cache to avoid |
// updating the field. But if the user (for example) bolds the font with the |
// keyboard, the attributes could change without the character index changing... |
if (charIndex != -1) |
{ |
// They're pointing at some text; get its attributes and show them. |
attributes = [[textView textStorage] attributesAtIndex: charIndex effectiveRange: NULL]; |
if ([feedbackTypeMatrix selectedTag] == linkShowLinkInfo) |
{ |
NSObject *link; |
link = [attributes objectForKey: @"NSLink"]; |
if (link == nil) |
{ |
[feedbackField setStringValue: @"(the mouse is not over a link)"]; |
} |
else |
{ |
[feedbackField setStringValue: |
[NSString stringWithFormat: @"link class = %@\nlink value = %@", |
[link class], link]]; |
} |
} |
else |
[feedbackField setStringValue: [attributes description]]; |
[feedbackField setTextColor: [NSColor blackColor]]; |
} |
else |
{ |
// Hint that they should move the mouse into the text-view to see something in this field |
[feedbackField setStringValue: @"(point at some text to see its link)"]; |
[feedbackField setTextColor: [NSColor redColor]]; |
} |
} |
#pragma mark PUBLIC INSTANCE METHODS -- NSNibAwaking PROTOCOL |
- (void) awakeFromNib |
{ |
// On startup, begin redisplaying the attributes every 1/10th of a second. |
// (It'd be smarter to do this only when the mouse enters, moves, or exits, |
// but this is just an example app.) |
[[NSTimer scheduledTimerWithTimeInterval: 0.1 |
target: self |
selector: @selector(showAttributesUnderMouse) |
userInfo: nil |
repeats: YES] retain]; |
// Fake a selection change so the matrix reflects the starting situation. |
[self textViewDidChangeSelection: nil]; |
} |
#pragma mark PUBLIC INSTANCE METHODS -- NSTextField DELEGATE METHODS |
// If they type into a field, select the relevant radio button for them. |
- (void) controlTextDidChange: (NSNotification *) notification |
{ |
if ([notification object] == createLinkURLField) |
[createLinkTypeMatrix selectCellWithTag: linkToOtherURLTag]; |
if ([notification object] == createLinkAlertField) |
[createLinkTypeMatrix selectCellWithTag: linkToAlertTag]; |
} |
#pragma mark PUBLIC INSTANCE METHODS -- NSTextView DELEGATE METHODS |
// Handle a click in a link. |
- (BOOL) textView: (NSTextView *) textView |
clickedOnLink: (id) link |
atIndex: (unsigned) charIndex |
{ |
if ([link isKindOfClass: [NSString class]]) |
{ |
NSRunAlertPanel (@"Whee!", |
[NSString stringWithFormat: @"You clicked a link containing the text Ò%@Ó", link], |
nil, nil, nil); |
return YES; |
} |
// We don't need to check for a link belonging to the NSURL class; |
// NSTextView automatically does that. |
// If we don't recognize the link object, say so. |
return NO; |
} |
// When the selection changes in the big text-view, |
// update the title of the relevant radio button. |
- (void) textViewDidChangeSelection: (NSNotification *) aNotification |
{ |
NSString *selected, *title; |
BOOL enable; |
NSCell *matrixCell; |
// Assume 'aNotification' is for our NSTextView -- it's the only one in the app |
selected = [self selectedText]; |
// Truncate a really long selection, to avoid messing up the matrix display. |
if ([selected length] > 30) |
selected = [[selected substringToIndex: 30] stringByAppendingString: @"É"]; |
if ([selected length] == 0) |
{ |
title = @"Éto (selected text)"; |
enable = NO; |
} |
else |
{ |
title = [NSString stringWithFormat: @"Éto \"%@\"", selected]; |
enable = YES; |
} |
matrixCell = [createLinkTypeMatrix cellWithTag: linkToURLFromSelectionTag]; |
[matrixCell setTitle: title]; |
[matrixCell setEnabled: enable]; |
// If we've just disabled the selected cell, we should really push the selection to another cell... |
} |
// Optionally return the cursor for a given type of link. |
// This isn't really an NSTextView delegate method, but our NSTextView subclass sends it to the delegate. |
- (NSCursor *) cursorForLink: (NSObject *) linkObject |
atIndex: (unsigned) charIndex |
ofTextView: (NSTextView *) aTextView |
{ |
// Return something distinctive for alert strings. |
if ([linkObject isKindOfClass: [NSString class]]) |
return [NSCursor arrowCursor]; |
// Ignore everything else; the text-view subclass will default. |
return nil; |
} |
#pragma mark PUBLIC INSTANCE METHODS -- INTERFACE BUILDER ACTIONS |
// Turn the selection in the text-view into a link, optionally blue-and-underlined. |
- (IBAction) createLink: (id) sender |
{ |
NSRange selection; |
NSObject *linkObject; |
NSMutableDictionary *linkAttributes; |
selection = [textView selectedRange]; |
if (selection.length == 0) |
{ |
NSRunAlertPanel (@"Create Link", @"Please select some text before creating a link.", nil, nil, nil); |
return; |
} |
// Get the object which will go into the link. If it comes back nil, the user's been alerted. |
linkObject = [self linkObject]; |
if (linkObject == nil) |
return; |
// Start to build attributes |
// NSLinkAttributeName => the object |
linkAttributes = [NSMutableDictionary dictionaryWithObject: linkObject |
forKey: NSLinkAttributeName]; |
// If the user wants it to look like a traditional link, add these attributes |
// NSForegroundColorAttributeName => blue |
// NSUnderlineStyleAttributeName => YES |
if ([blueUnderlineCheckbox state] == NSOnState) |
{ |
[linkAttributes setObject: [NSColor blueColor] forKey: NSForegroundColorAttributeName]; |
[linkAttributes setObject: [NSNumber numberWithBool: YES] forKey: NSUnderlineStyleAttributeName]; |
} |
// Add the attributes. This adds the link (and perhaps color and underline) attributes to the selected range. |
// (Don't "set" the attributes, which would lose unrelated attributes like the ruler.) |
// Note that we don't need to follow this with -edited:range:changeInLength:; |
// that's only for folks who subclass NSTextStorage. |
[[textView textStorage] addAttributes: linkAttributes range: selection]; |
// Tickle the AppKit into calling our -resetCursorRects implementation. |
// One would expect [[textView window] invalidateCursorRectsForView: textView] |
// to work, but it doesn't for us, so just go the direct route... |
[[textView window] resetCursorRects]; |
} |
// When they click a radio button, select the relevant field (if any) for them. |
// (Would be even better to select only if the field doesn't already have a selection.) |
- (IBAction) linkTypeSelected: (id) sender |
{ |
switch ((linkTag) [createLinkTypeMatrix selectedTag]) |
{ |
case linkToURLFromSelectionTag: |
break; |
case linkToOtherURLTag: |
[createLinkURLField selectText: nil]; |
break; |
case linkToAlertTag: |
[createLinkAlertField selectText: nil]; |
break; |
default: // don't expect this |
break; |
} |
} |
@end |
/* |
IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in |
consideration of your agreement to the following terms, and your use, installation, |
modification or redistribution of this Apple software constitutes acceptance of these |
terms. If you do not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject to these |
terms, Apple grants you a personal, non-exclusive license, under Apple's copyrights in |
this original Apple software (the "Apple Software"), to use, reproduce, modify and |
redistribute the Apple Software, with or without modifications, in source and/or binary |
forms; provided that if you redistribute the Apple Software in its entirety and without |
modifications, you must retain this notice and the following text and disclaimers in all |
such redistributions of the Apple Software. Neither the name, trademarks, service marks |
or logos of Apple Computer, Inc. may be used to endorse or promote products derived from |
the Apple Software without specific prior written permission from Apple. Except as expressly |
stated in this notice, no other rights or licenses, express or implied, are granted by Apple |
herein, including but not limited to any patent rights that may be infringed by your |
derivative works or by other works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, |
EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, |
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS |
USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL |
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, |
REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND |
WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR |
OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
*/ |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-07-29