I'm trying to add some scripting functionality to a Cocoa app that I've written. I've created an sdef (Scripting Definition File) for my project. So far I have been successful in accessing object children (elements) with AppleScript but I cannot for the life of me figure out how to call methods (commands).
Here is my sdef file.
<suite name="mySuite" code="mSUI" description="My Application Suite">
<class name="application" code="capp" description="Top level scripting object.">
<cocoa class="NSApplication"/>
<!-- I can access these elements fine -->
<element description="Test children." type="child" access="r">
<cocoa key="myChildren"/>
</element>
<!-- Cannot seem to call this method :( -->
<responds-to command="testmethod">
<cocoa method="exposedMethod:"/>
</responds-to>
</class>
<class name="child" code="cHIL" description="A Child." plural="children">
<cocoa class="Child"/>
<property name="name" code="pnam" description="The child name." type="text" access="r">
<cocoa key="name"/>
</property>
</class>
<command name="testmethod" code="tEST" description="Execute the test method" />
</suite>
Here are my controller class implementations (this is the delegate of my application)
MyController.h
#import <Cocoa/Cocoa.h>
#interface MyController : NSObject {
NSMutableArray* myChildren;
}
// Some Methods
#end
MyController+Scripting.m
#import "MyController+Scripting.h"
#implementation MyController (Scripting)
// This works when I'm accessing the myChildren
- (BOOL)application:(NSApplication*)sender delegateHandlesKey:(NSString*)key {
NSLog(#"Key = %#", key);
return ([key isEqualToString:#"myChildren"]);
}
// This does NOT work...
- (void)exposedMethod:(NSScriptCommand*)command {
NSLog(#"Invoked Test Script Method %#", [command description]);
}
#end
Lastly, the AppleScript I am trying is:
tell application "MyApplication"
testmethod
end tell
which responds with "AppleScript Error - The variable testmethod is not defined."
Any ideas what I'm doing wrong here? I feel like I'm missing something simple but my Googling doesn't seem to be turning up anything helpful.
Things look mostly right, but the code for the <command/> should be a two-part code (eight characters) and not four.
I just posted a detailed solution on how to add a command with argument here: https://stackoverflow.com/a/10773994/388412
In short: I think you should subclass NSScriptCommand and override -(void)performDefaultImplementation rather than exposing a method in your controller. At least that's what I distilled from the docs:
"This command is a subclass of NSScriptCommand, containing one method, performDefaultImplementation. That method overrides the version in NSScriptCommand"
… and it works fine for me, see my linked answer for details.
(I've actually never added scriptability to a Cocoa app, so take my stab in the dark with a grain of salt.)
The first thing I would guess is that exposedMethod: takes a parameter, but I don't see where one might be specified in your sdef or AppleScript. In fact, it looks like AppleScript is treating testmethod as a variable, not a command. (Maybe it should be something like "testmethod with ..." instead?) You may need to define a <parameter> or <direct-parameter> element for the command in the sdef.
Also, wouldn't you need an object to call the method on? Since your controller is the application delegate, I'm not sure of the intricacies there, but might AppleScript try to invoke testMethod: on NSApplication instead of your delegate?
I'm guessing you've looked at Apple's sample code and other resources, but if not, here are a few links that may help:
Introduction to Cocoa Scripting Guide
TechNote 2106: Scripting Interface Guidelines
http://developer.apple.com/samplecode/SimpleScriptingObjects/
http://developer.apple.com/samplecode/SimpleScriptingPlugin/
http://www.shadowlab.org/softwares/SdefEditor/sdef-format.html
Good luck!
Related
Can someone point me to an example of this working. I just want to set a property value via AppleScript. I have gone through all of the scriptable examples, which are setup differently.
<?xml version="1.0" encoding="UTF-8"?>
<dictionary title="">
<suite name="Circle View Scripting" code="bccS" description="Commands and classes for Circle View Scripting">
<class name="application" code="capp" description="" >
<cocoa class="NSApplication"/>
<property name="circletext" code="crtx" type="text" description="The text that gets spun into a circle">
<cocoa key="circleText"/>
</property>
<property name="myint" code="crmy" type="integer" description="The text that gets spun into a circle">
<cocoa key="myInt"/>
</property>
</class>
</suite>
the header file:
// header
#interface MyDelegate : NSObject <NSApplicationDelegate>
{
WebScriptObject *scriptObject;
WebView *webView;
NSWindow *window;
NSInteger myInt;
}
// implementation
- (BOOL)application:(NSApplication*)sender delegateHandlesKey:(NSString*)key
{
return key isEqualToString:#"myInt"] || [key isEqualToString:#"circleText"];;
}
-(NSInteger)myInt
{
NSInteger myInteger = 42;
return myInteger;
}
-(void)setMyInt:(NSInteger*)newVal
{
// do nothing right now
NSLog(#"SETTER CALLED");
}
// Applescript attempt to set property "myInt"
tell application "BrowserConfigClient"
set myint to 7
properties
end tell
Ultimately, the delegateHandlesKey method is called, I am able to return a value for the property, but the setter is never called. Thanks in advance...
Your method statement has an error...
-(void)setMyInt:(NSInteger*)newVal
There should not be a "*" as NSInteger is not a "pointer" variable. I see in the comments of your question that Ken Thomases has already told you this so make sure to fix it.
So if this is not your problem then look at your sdef file. I can see you did not close the dictionary tag. You need this as the last line of that file.
</dictionary>
I also have this as the second line in my sdef files...
<!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">
I'm using nhibernate2.1 as part of spring.net 1.3. I have the following declaration as part of my mapping. My understanding is that this object should not load unless the getter is called. I have a break point set on the setter and also dump all nhibernate SQL statements to the logger. In part of my testing, I've actually created a brand new child object and a brand new property on my original object (hence the "2" on the names) so I'm positive that property is not being accessed anywhere. Despite this, as soon as my parent object loads, I can verify that this property is loaded. So...what am I missing here?
<set name="UserCustomer2" lazy="true">
<key column="[FK_USERS]" />
<one-to-many class="UserCustomer2" />
</set>
#A: here is my property:
private ICollection<UserCustomer2> _UserCustomer2 = new HashSet<UserCustomer2>();
public virtual ICollection<UserCustomer2> UserCustomer2
{
get { return _UserCustomer2; }
set { this._UserCustomer2 = value; }
}
and here is how I request the parent object:
IQuery query = dao.GetQuery("FROM UserImpl u WHERE u.UserName = :username AND u.Password = :password");
query.SetParameter("username", username);
query.SetParameter("password", password);
IList users = query.List();
#dotjoe, you led me down the right path. I tested out quite a few scenarios and based on breakpoints and the logged SQL statements I've determined that setting breakpoints does NOT trigger the lazy load. However, when you hover over the property to inspect it while in debug mode, it DOES in fact hydrate the object. I guess that sort of makes sense but I was assuming when I inspected the object, I would just see a proxy object instead of the fully hydrated object. I'm surprised that a debug mode action would trigger the lazy load -- I assumed that would only be triggered from actual application code. The other thing I'm curious about is if the setter is ALWAYS called and just passed a proxy object or if it was only called BECAUSE I had put a breakpoint in the setter. I would assume the former but so far my assumptions have been wrong. If anyone can provide some insights, I'm very curious how this actually works behind the scenes.
Following the technique described here, I was able to populate a domain object that uses custom collections for its children. The relevant property mapping looks like this:
<component name="Contacts" class="My.CustomList`1[[Domain.Object, DomainAssembly]], MyAssembly">
<set name="InnerList">
<key column="PARENT_ID" />
<one-to-many class="Contact" />
</set>
</component>
My custom collection class exposes the InnerList property as an ICollection like so:
protected System.Collections.ICollection InnerList
{
get
{
return this;
}
set
{
Clear();
foreach (DomainObject o in value)
{
Add(o);
}
}
}
This worked like a charm to load data from the database and not have to abandon my rather useful custom collection class.
Then I moved on to try implement saving, and following the advice given in this thread, decided to wrap every call to NHibernate in a transaction.
Now when I commit following my load, NHibernate throws an InvalidCastException: "Unable to cast object of type 'My.CustomList`1[Domain.Object, DomainAssembly]' to type 'Iesi.Collections.ISet'."
Is there a way to make this work the way I expect it to?
EDIT:
Following the lead provided by Raphael, I tried switching to ICollection<T> which gives me a different InvalidCastException when I commit the transaction: Unable to cast object of type 'My.CustomList`1[Domain.Object]' to type 'NHibernate.Collection.IPersistentCollection'.
Change the property to be of type
IList<T>
I'm having a hard time trying to get my stored procedure works with NHibernate. The data returned from the SP does not correspond to any database table.
This is my mapping file:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainModel" namespace="DomainModel.Entities">
<sql-query name="DoSomething">
<return class="SomeClass">
<return-property name="ID" column="ID"/>
</return>
exec [dbo].[sp_doSomething]
</sql-query>
</hibernate-mapping>
Here is my domain class:
namespace DomainModel.Entities
{
public class SomeClass
{
public SomeClass()
{
}
public virtual Guid ID
{
get;
set;
}
}
}
When I run the code, it fails with
Exception Details: NHibernate.HibernateException: Errors in named queries: {DoSomething}
at line 80
Line 78: config.Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "NHibernate.config"));
Line 79:
Line 80: g_sessionFactory = config.BuildSessionFactory();
When I debug into NHibernate code, it seems that SomeClass is not added to the persister dictionary because there isn't a class mapping (only sql-query) defined in hbm.xml. And later on in CheckNamedQueries function, it is not able to find the persistor for SomeClass.
I've checked all the obvious things (e.g. make hbm as an embedded resource) and my code isn't too much different from other samples I found on the web, but somehow I just can't get it working. Any idea how I can resolve this issue?
Well, where is your class mapping for SomeClass?
You still need to map it. Read http://nhibernate.info/doc/nh/en/index.html#querysql-load.
Look at using a class mapping with a subselect block. I found this in the Java documentation but maybe it will work for .Net too.
http://docs.jboss.org/hibernate/core/3.3/reference/en/html/mapping.html (scroll down to section 5.1.3)
I'm trying to use groovy ws to call a webservice. One of the properties of the generated class is it's self a class with an enum type. Although the debug messages show that the com.test.FinalActionType is created at runtime when the WSDL is read I can't create an instance of it using code like
proxy.create("com.test.FinalActionType")
When I try and assign a string to my class uin place of an instance of FinalActionType groovy is not able to do the conversion. How can I get an instance of this class to use in a webservice call? I've pasted the important part of the WSDL below.
<xsd:simpleType name="FinalActionType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="stop"/>
<xsd:enumeration value="quit"/>
<xsd:enumeration value="continue"/>
<xsd:whiteSpace value="collapse"/>
</xsd:restriction>
</xsd:simpleType>
I don't think it can easily be done yet, not using the available WSClient API.
There was a suggestion to add a "createEnum" method to the WSClient class (see test case patch and issue GMOD-82). Judging by Guillaume Alleon's comments under issue GMOD-4, there should be a way to create an enum instance when WSClient 0.5.1 is released.
UPDATE:
As of GroovyWS 0.5.2 (at least, I haven't tried 0.5.1), enums can be used with GroovyWS as follows:
...
wsProxy = new WSClient(wsdlUrl, this.class.classLoader)
wsProxy.initialize()
def anObject = wsProxy.create("some.package.AServiceInterface")
def anEnum = wsProxy.create("some.package.AnEnum")
anObject.anEnumProperty = anEnum.AN_ENUM_VALUE
...