I’m trying to use this statement
display alert theAlert message theMessage as warning default button "OK" attached to theWindow
but compilation stops with the error “Expected end of line, etc. but found identifier.”
It happens when I use Xcode notation of “display alert” (“attached to”, “alternate button” and so on).
Otherwise in terms of “StandardAdditions” compilation passes well.
Xcode version 3.2.1
Try putting “tell current application” around it.
No luck.
Neither “tell me” works.
That’s not really Xcode notation; it’s AppleScript Studio syntax. Is this a Studio app or ASObjC app?
It’s an ASObjC application
In that case you can’t use ASStudio syntax. You need to use either standard AS or the Cocoa methods.
I got this syntax to work:
script AlertsAppDelegate
property parent : class “NSObject”
property NSAlert : class “NSAlert”
property mainWindow : missing value – This is attached to the window in IB
on alertDidEnd_rc_ci_(alert, returnCode, contextInfo)
log contextInfo
end alertDidEnd_rc_ci_
on applicationWillFinishLaunching_(aNotification)
set messageText to "This is an alert message"
set infoText to "Click OK to close"
set myAlert to NSAlert's alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(messageText, "OK", "Cancel", "Done", infoText)
myAlert's setAlertStyle_(0)
set returnCode to myAlert's runModal() --use this if you want a free floating window. Statement below will give you an attached sheet.
--myAlert's beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(mainWindow, me, "alertDidEnd_rc_ci_", missing value)
log returnCode --1 for default button ("OK" in this case), 0 for alternate button ("Cancel"), and -1 for other button ("Done")
end applicationWillFinishLaunching_
on applicationShouldTerminate_(sender)
myAlert's release()
return current application's NSTerminateNow
end applicationShouldTerminate_
end script
This gives a free floating alert window and returns values dependent on which button the user clicked on. I tried to get an attached sheet to work, and got most of the way there. If you comment out the line starting "set returnCode to myAlert’s… and uncomment the line below it, you will get an attached alert sheet. However, when you click on any of the buttons you get the following error message:
2010-04-06 16:52:16.033 Alerts[4419:a0f] *** -[AlertsAppDelegate alertDidEnd_rc_ci_]: {} doesn’t match the parameters {alert, returnCode, contextInfo} for alertDidEnd_rc_ci_. (error -1721)
Any thoughts on why I get this error message?
Where you call beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_, the didEndSelector parameter must use colons rather than underscores: “alertDidEnd:rc:ci:”
Shane,
I tried that, since that was the syntax that worked for NSTimer. But if I do that, and my method is:
on alertDidEnd_rc_ci_(sender, returnCode, contextualInfo)
log sender
end alertDidEnd_rc_ci_
I get an EXC_BAD_Acess errror message.
Interestingly, if I make the call just “alertDidEnd:” and make my method:
on alertDidEnd_(sender)
log sender
end alertDidEnd_
It works even though the selector doesn’t have the required 3 parameters, and the log reads <NSAlert: 0x200955e40>
But, of course, now you don’t get the info on what button was used to dismiss the alert.
The problem is that contextualInfo is a void *, and ASObjC doesn’t cope with them. The workaround is to use an ObjectiveC protocol to avoid the error. I’m pretty sure you’ll find a discussion of it in the archives here.
I’m not well versed enough in Objective C to know what that means. If you can point me to a particular discussion, I’d appreciate it, because I’ve searched and I can’t find anything relevant.
It means you get an error
Search Apple’s ASObjC mailing list’s archives – it was discussed there.
Shane,
I finally wrote my first objective C program to get around the problem with the contextInfo. I made a new C class called AttachedAlert and moved the creation and showing of the alert sheet to that class. Now I can log the return code when I dismiss the alert. This is what I have so far:
// AttachedAlert.h
// Alerts
#import <Cocoa/Cocoa.h>
@interface AttachedAlert : NSObject {
}
+(void)bringupSheetOnWindow: (NSWindow*)window WithMessage: (NSString*)messageText andInfoText:(NSString*) infoText;
- (void)alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)info;
@end
// AttachedAlert.m
// Alerts
#import “AttachedAlert.h”
@implementation AttachedAlert
-
(void)bringupSheetOnWindow: (NSWindow*)window WithMessage: (NSString*)messageText andInfoText:(NSString*) infoText
{
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
[alert setAlertStyle:NSWarningAlertStyle];
[alert addButtonWithTitle:@“First”];
[alert addButtonWithTitle:@“Second”];
[alert addButtonWithTitle:@“Third”];
[alert setMessageText:[NSString stringWithFormat:@“%@”, messageText]];
[alert setInformativeText:[NSString stringWithFormat:@“%@”, infoText]];
[alert beginSheetModalForWindow:window modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:nil];
}
-
(void)alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)info
{
NSLog(@“%d”,returnCode);
}
@end
And I call it from the applescript with:
attachedAlert’s bringupSheetOnWindow_WithMessage_andInfoText_(myWindow, “message”, “info”)
The problem is, I can’t figure out how to get the value of returnCode back to the applescript.
You’ll need alertDidEnd: to call a handler in your AS, passing the value; it’s just like calling a Cocoa method.
You can also solve the problem this way (excuse the brevity; I’m on the road):
- Make a new Objective-C protocol (just an .h file) containing this:
#import <Cocoa/Cocoa.h>
@protocol ASSheetProtocol
@optional
- (void)sheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(id)contextInfo;
@end
- Make a new Objective-C subclass of NSObject that conforms to the above protocol:
#import <Cocoa/Cocoa.h>
#import "ASSheetProtocol.h"
@interface ASSuperclass : NSObject <ASSheetProtocol> {
}
@end
The implementation file does nothing.
Then you can do the dialogs in AS.
Shane,
" You’ll need alertDidEnd: to call a handler in your AS, passing the value; it’s just like calling a Cocoa method."
This is what I’ve been trying to do for the past two days, and I just can’t figure out how to do it. I know how to call Cocoa methods from an ASOC script, but I don’t know how to call an AS handler or a Cocoa method from within an ObjectiveC method like alertDidEnd:. I just can’t figure out the right syntax, and I can’t find any examples of doing that in my books.
If I didn’t know that I’d done this before, I’d have given up. But…
OK, let’s assume you have a handler in AlertsAppDelegate like this:
on userPushed_(n)
set n to n as integer
...
This looks to Cocoa like:
+ (id) userPushed:(id)n;
- (id) userPushed:(id)n;
You then have to instantiate your Objective-C class AttachedAlert in IB, and add an @class statement after the #import statement to let it know about your AS class. And in its @interface declaration you’ll need an IBOutlet that you connect to AlertsAppDelegate in IB. So it should look like this:
// AttachedAlert.h
// Alerts
#import <Cocoa/Cocoa.h>
@class AlertsAppDelegate
@interface AttachedAlert : NSObject {
IBOutlet AlertsAppDelegate *myASScript;
}
Now in your alertDidEnd: method you can call the handler, with one twist; presumably because userPushed:'s parameter is effectively declared as type id, you have to pass an object rather than an int. So:
[myASScript userPushed:[NSNumber numberWithInt:returnCode]];
You’ll get a compiler warning: “No ‘-userPushed:’ method found”, which you can ignore. If it annoys you, you can make a protocol that declares the types, perhaps as something other than id, and make your Objective-C class conform to it. That way you might be able to pass ints.
To be honest, though, I think I prefer the protocol method I posted earlier…
Shane,
Thanks for the reply. I’ve tried the method from your last post, and in addition to the warning that you mentioned, I got another warning: “instance variable ‘myASScript’ accessed in class method” The sheet opens and the 2 log statements I have in the alertDidEnd: method work but the alert doesn’t close.
So, I think I’m confused about class methods vs instance methods. I get unrecognized selector messages from my AS call of the ObjC method if I don’t make that method a class method (the alertDidEnd: method also needs to be a class method to work). I don’t understand why the methods can’t be instance methods.
OK, I think you need to make your existing methods instance methods, then connect the instance in IB to a property in your script that you can address them by that, rather than as class methods. The problem is that you can’t refer to instance variables in a class method.
Shane,
Thanks once again. I’ll try to see if I can make that work
I tried your other method with the protocol, and that worked great! At first, I wasn’t sure what you meant by making the AS a subclass of the new Objective-C class (I’m thinking now that just means to change the AS’s parent property to “ASSuperclass”), so I didn’t do that, but it worked any way. I don’t really understand why it should have worked without doing that (it still worked after I did it). In fact I don’t really understand what this whole procedure does – I read the docs on Objective-C protocols and I think that I understand it in general. I thought maybe it had something to do with the fact that you changed the data type of the contextInfo to id, but I tried changing it back to void* (and NSInteger also) and it still worked. So, why you need to do this in this case, but not with NSTimer which has a similar method with a selector imbedded in it doesn’t make any sense to me.
I’m far from an expert on Objective-C, but as I understand it, there can only be one method signature for a given method, and presumably protocol-defined signatures are loaded first and hence rule.
I too noticed that it worked without the subclassing, but in the absence of more than a shakey understanding I decided to play safe. It does seem to require at least one class to conform to the protocol.
I had presumed the fact that any workaround was needed was either a bug or shortcoming of the AS-Cocoa bridge in handling void * objects, just like you can’t pass pointers to pointers, or pointers to C classes like int. But the fact that you found it worked using void * in the protocol has me wondering.
Perhaps without the protocol the AS method signature is being loaded, calling for the default type of id, and that trying to pass a non-object where a type of id is wanted causes the problem (as happens when an Objective-C class tries to call an AS method passing a non-object like an int instead of an NSNumber, as in the other thread here recently).
I wish there were someone we could ask who knows the answer…