What I want to do is create a contextual menu for each text field created in IB as a token field (of the sort used by Mail.app for dragging addresses). However, there is no “token field” AppleScript element, so I’m assuming these have to been established some how using the AppKit’s NSTokenField.h or NSTokenFieldCell.h files. I am pretty much a strictly AppleScript guy in AppleScript Studio, so I only have a vague familiarity with methods and I’m guessing I’ll need to use some “call method” handlers to do this. Can anyone post a quick tutorial or point me in the right direction?
Hi Doug,
I don’t believe NSTokenField menues can be used with ‘pure’ AppleScript (not yet - maybe in the next AS update?). TokenFields don’t have Menues by default - You have to implement two delegate methods to create them:
- (BOOL)tokenField:(NSTokenField *)tokenField hasMenuForRepresentedObject:(id)representedObject
return ‘YES’ to make the tokens show a menu
and
- (NSMenu *)tokenField:(NSTokenField *)tokenField menuForRepresentedObject:(id)representedObject
return the Menu you want to be shown
I think it is possible but it will need some lines of Objective-C to make this accessible from AppleScript. As far as I can see it should:
- include the two delegate methods mentioned above
- hold one or more NSMenu objects to be used with the TokenFields
- provide methods to set/delete/modify these Menu(s) from AppleScript (using call method …)
Hope that helps … sorry, currently I am short of time so I couldn’t try it - maybe I can do within the next days …
D.
Dominik:
Thanks for the reply. Yes, I had presumed something like that had to be done. I am just not sure how to create a token field object in AS with those delegate methods. Not sure of that whole process, actually. (Although there is an excellent post here that describes how to create a menu separator object, so I’m guessing it’s done something along those lines.)
Any assistance you can provide would be much appreciated. Thanks again.
Hi Doug,
yesterday TV was boring so … here’s a first step showing how to …
- programmatically create a NSTokenField
- set it’s content
- build a menu for it’s tokens
- enable/set autocompletition
what I haven’t found out yet, is a solution for connecting the menu items to other scripts, sorry … i will keep on thinking about this problem …
msg me if you have problems running the example - I can send you the whole project if you need it.
D.
=======================================================================
[i]1. in interface builder:
make a new subclass of NSObject:
- you find NSObject in the nib window, 2nd tab (Classes) scrolling to the left
- then select NSObject
- ctrl-click for the context menu - choose the first menu item “Subclass NSObject”
- name it “myTokenHandler”
create the files for myTokenHandler:
- ctrl-click myTokenHandler - choose menu item “Create files for myTokenHandler”
instantiate myTokenHandler:
- ctrl-Click myTokenHandler - choose “Instantiate myTokenWindow” of the context menu
now you’re back in the first tab and the window is showing the (blue) instance of myTokenHandler
- ctrl-click-and-drag a connection from the main window to the instance of myTokenHandler
the Inspector window (NSWindow inspector) is now showing “connections”
-
doubleClick “delegate”
-
follow the same two steps for “File’s Owner”
save the nib
2. in Xcode:
you’ll find the two new files “myTokenHandler.m” and “myTokenHandler.h” in your project in the folder “Other Sources”
copy and paste the text contents for these files (see below)
you are done and can use the methods in your applescript (test script below)
3.
name the window “main” and
create a button and connect it to the on clicked handler … now you should be able to try it
[/i]
myTokenHandler.h
#import <Cocoa/Cocoa.h>
@interface myTokenHandler : NSObject {
NSMenu *tokenMenu;
NSMutableArray *tokenCompletitionArray;
unsigned comparisonOption;
IBOutlet NSTokenField *tofi;
}
-(NSMenu *)menu;
-(NSTokenField *)makeTokenFieldWithFrame:(NSArray *)theFrameValues inView:(id)theView;
-(BOOL)insertSeparatorAtIndex:(int)index;
-(BOOL)insertItemAtIndex:(int)index action:(id)selector title:(NSString *)title keyEquivalent:key;
-(void)setCompletition:(NSArray *)theCompletitionArray;
-(void)addCompletition:(NSString *)newCompletitionString;
-(void)setCompletitionCompareType:(NSString *)compareType;
@end
myTokenHandler.m
#import "myTokenHandler.h"
@implementation myTokenHandler
#pragma mark ========= initialisation / dealloc ===============
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
tokenMenu = [[NSMenu alloc] init];
tokenCompletitionArray = [[NSMutableArray alloc] init];
comparisonOption = NSLiteralSearch;
}
- (void) dealloc {
[tokenCompletitionArray release];
[tokenMenu release];
[super dealloc];
}
#pragma mark ========= delegate methods: ==================
- (BOOL)tokenField:(NSTokenField *)tokenField hasMenuForRepresentedObject:(id)representedObject{
return YES;
}
- (NSMenu *)tokenField:(NSTokenField *)tokenField menuForRepresentedObject:(id)representedObject{
return [[tokenMenu retain] autorelease];
}
- (NSArray *)tokenField:(NSTokenField *)tokenField completionsForSubstring:(NSString *)substring indexOfToken:(int)tokenIndex indexOfSelectedItem:(int *)selectedIndex{
NSMutableArray *completitionResults = [[NSMutableArray alloc] init];
NSEnumerator *enumerator = [tokenCompletitionArray objectEnumerator];
NSString *nObject;
while (nObject = [enumerator nextObject]) {
if ([[nObject commonPrefixWithString:substring options:comparisonOption] length] == [substring length]) {
[completitionResults addObject:nObject];
}
}
[completitionResults retain];
[completitionResults release];
return [NSArray arrayWithArray:completitionResults];
}
#pragma mark ========= AppleScript access methods: ===========
-(NSTokenField *)makeTokenFieldWithFrame:(NSArray *)theFrameValues inView:(id)theView{
NSRect theFrame = NSMakeRect( [[theFrameValues objectAtIndex:0] floatValue],
[[theFrameValues objectAtIndex:1] floatValue],
[[theFrameValues objectAtIndex:2] floatValue],
[[theFrameValues objectAtIndex:3] floatValue]);
NSTokenField *tf = [[NSTokenField alloc] initWithFrame];
[theView addSubview:tf];
[tf retain];
[tf release];
return tf;
}
-(NSMenu *)menu{
return tokenMenu;
}
-(BOOL)insertItemAtIndex:(int)index action:(id)selector title:(NSString *)title keyEquivalent:key{
BOOL retVal = NO;
if (--index <= [tokenMenu numberOfItems]) {
NSMenuItem *newMenuItem = [[NSMenuItem alloc] initWithTitle:title action:(SEL)selector keyEquivalent:key];
[tokenMenu insertItem:newMenuItem atIndex:index];
retVal = YES;
}
return retVal;
}
-(BOOL)insertSeparatorAtIndex:(int)index{
BOOL retVal = NO;
if (--index <= [tokenMenu numberOfItems]) {
[tokenMenu insertItem:[NSMenuItem separatorItem] atIndex:index];
retVal = YES;
}
return retVal;
}
-(void)setCompletition:(NSArray *)theCompletitionArray{
[tokenCompletitionArray setArray:[NSArray arrayWithArray:theCompletitionArray]];
}
-(void)addCompletition:(NSString *)newCompletitionString{
if (! [tokenCompletitionArray containsObject:newCompletitionString]) {
[tokenCompletitionArray addObject:newCompletitionString];
}
}
-(void)setCompletitionCompareType:(NSString *)compareType{
if ([compareType isEqual:@"considering case"]) {
comparisonOption = NSLiteralSearch;
} else { // any other input
comparisonOption = NSCaseInsensitiveSearch;
}
}
@end
examplescript.applescript
on clicked theObject
tell window "main"
-- to make it work you have to connect myTokenHandler as delegate of the window "main" in Interface Builder
-- now we can ask the window for it's delgate:
set tokenmenuhandler to call method "delegate" of it
-- create a new NSTokenField using myTokenHandler's method "makeTokenFieldWithFrame:inView:"
-- two parameters are needed:
-- 1. a list of the field frame coordinates {x,y,width,height}
-- 2. the view we want to be the superview of the new NSTokenField:
set newTokenField to call method "makeTokenFieldWithFrame:inView:" of tokenmenuhandler ¬
with parameters {{16, 100, 400, 21}, content view of it}
-- now we connect the delegate to make the delegate methods (menu) work:
call method "setDelegate:" of newTokenField with parameter tokenmenuhandler
-- wow ;-) - first time we can use AppleScript ... the content of an NSTokenField is of type list (= NSArray)
set content of newTokenField to (words of "programmatically created token field")
-- and don't forget ...
update
end tell
-- now we build the menu - I have implemented two methods:
-- "insertItemAtIndex:action:title:keyEquivalent:" creates a new menu item
--
if not (call method "insertItemAtIndex:action:title:keyEquivalent:" of tokenmenuhandler ¬
with parameters {1, null, "menu item 1", "1"}) as boolean then
log "inserting a new item failed"
end if
-- "insertSeparatorAtIndex:" creates a separator ..
if not (call method "insertSeparatorAtIndex:" of tokenmenuhandler with parameter 2) as boolean then
log "inserting a separator failed"
end if
if not (call method "insertItemAtIndex:action:title:keyEquivalent:" of tokenmenuhandler ¬
with parameters {3, null, "menu item 2", ""}) as boolean then
log "inserting a new item failed"
end if
set tokenmenu to (call method "menu" of tokenmenuhandler)
delay 1
display dialog "The Menu has " & (count menu items of tokenmenu) & " menu items" giving up after 3
-- and three methods for autocompletion:
call method "setCompletition:" of tokenmenuhandler with parameter {"Douglas", "Dominik", "Dennis", "Dave", "Peter", "Paul", "Pjotr"}
set newCompletition to (text returned of (display dialog "Please enter your name:" default answer "John Doe"))
call method "addCompletition:" of tokenmenuhandler with parameter newCompletition
call method "setCompletitionCompareType:" of tokenmenuhandler with parameter "considering case" -- any other value turns considering case off
display dialog "Now you can try autocompletition - type \"D\" or \"P\" ..." giving up after 3
end clicked
Thanks for the code. I’ll have a go at it.
Doug,
I thought you were no longer interested … so I didn’t notice your response, sorry. As mentioned above, the example I posted was more or less useless since the main part - connecting the token menu back to AppleScript was missing. Meanwhile I have completized a fully working demo showing not only how to create tokenfields and token menus from AppleScript but also calling an Applescript handler from these menues. But it’s a little bit too large now to post here and I think it’s better to give you the whole demo project instead of only single files, so …
I offer to send it via mail (send me a mail i can answer to if you are interested)
or …
@ moderators: if you think this might be interesting for public then plz. tell me - is there a possibility to upload the project somewhere here on the site?
D.