Introduction…
As we develop Applescript-based Applications using Xcode (Applescript Studio), we often find ourselves needing ways to add features and capabilities to our projects that are not available in Applescript alone. There is a simple, yet powerful way of supplementing the limited abilities of Applescript-based Applications called “Objective-C subclassing”, which I will discuss in this article.
What is a Subclass?
Every object you create in your Cocoa application descends from the ‘NSObject’ foundation class. The NSObject class identifies properties and methods which apply to all objects. The NSObject class is divided into smaller groups of objects, called subclasses. Objects in these subclasses not only conform to the protocol of NSObject, they are also defined more precisely by the methods that govern their subclass. Every object class inherits from the superclasses above it in the object hierarchy, and also declares the methods which make it a unique class.
Why might I need or want to subclass?
It is common for Applescript developers to look for alternate methods of achieving goals and performing tasks. We often rely on complex shell scripts, unreliable brute-force parsing, or klunky gui scripts to accomplish simple tasks. In plain applescript we do not have the luxury of accessing Objective-C classes directly. But in ASStudio, we have a comprehensive set of powerful tools available to us in our ability to incorporate Objective-C calls directly into our applescripts. Many of our problems in ASStudio can be easily solved by creating an Objective-C subclass that can both handle our task, and also avoid messy workarounds and hacks.
There are three primary reasons that your application may have to use subclasses…
-
ASStudio does not support all of the capabilities available to developers using other languages like Objective-C or java. Unfortunately, the dictionaries that govern which objects and methods you have access to in ASStudio are not complete… leaving gaps in feature support that range from trivial to severe. To gain access to these features, you must write custom subclasses that add the unsupported code you need.
-
You may want to override the methods in existing classes. Some objects have default methods and settings that may not be suitable for your needs. By creating a custom subclass, you can set your interface elements to inherit qualities according to your criteria, not just from the object’s default class implementation.
-
It is often beneficial to create your own generic subclasses, to act as a convenient location to keep any methods you write that execute custom Objective-C code. In many cases, Objective-C has methods of performing certain actions or managing data in ways that Applescript is not capable of. Objective-C has a versatile set of existing methods that you can use to make your application work more efficiently and effectively.
In the discussion below, I will show examples of each of these reasons and discuss briefly why Objective-C was the necessary or preferred means of achieving the goal of each task.
How to create a new subclass…
Creating a subclass in Xcode/Interface Builder is fairly straightforward. There are actually a couple of ways to create subclasses, but I have found this method to be the most stable. There are two types of subclassing that might interest developers writing Applescript-based applications.
The first type involves subclassing existing classes in order to add or modify object behaviors. For example, if you wanted to create popup buttons with no arrow, you would subclass NSPopUpButton. If you wanted to create windows that were transparent and had no title bar, you would want to subclass NSWindow.
The second type of subclass involves creating a generic subclass in which to place methods you will not use to define a new class of object. Instead, you will use these methods to perform tasks, such as to manipulate or gather data or manipulate existing objects. In this case, you would typically just subclass NSObject. For example, your generic subclass might hold methods that: check for screen dimensions, create a custom main menu item, add separator items to a menu, or manipulate image data.
Follow these steps to create a new subclass:
-
In Interface Builder (IB) select the “Classes” tab in your main nib window. Navigate to the class you wish to subclass and select it.
-
Choose “Classes > Subclass NSSomeClass” from the IB main menu, where ‘NSSomeClass’ is the name of the class you intend to subclass. A new subclass will be created for you.
-
Name your subclass. Typically, you should name your subclass to reflect the class it is derived from, along with a unique prefix consistent with your project name or other identifier. Since the example code I post below is all derived from my Chronicle plugin, you’ll notice that all of my subclasses begin with “CHR”, and end with the original class name…i.e. “CHRWindow” or “CHRObject”.
-
Make sure that your new class is selected, then select “Classes > Create files for NSSomeClass”. A dialog will open, prompting you to confirm the creation of your class files. It is recommended that you create both the interface (.h) and implementation (.m) files for your subclass, although you can get away with only creating the .m file. After confirming, the files will be created in your Xcode project, populated with generic code to begin writing your class methods into.
-
If you are subclassing to add to or modify the default methods of an existing class, you can now begin setting your new subclass as the class for any applicable objects. For example, you are creating a subclass of NSPopupButton… named CHRPopUpButton… that overrides the default methods of some of your popup buttons. Select a popup button that will inherit from the new subclass. In the IB inspector window, go to the “Custom Class” pane. You should be able to select either the original NSPopUpButton class, or your new CHRPopUpButton subclass. Select the new subclass. The popup button will now inherit any methods you place inside your subclass file. Repeat this procedure for all applicable objects.
-
Save your changes in IB. Return to Xcode and locate your new class files in the “Groups&Files” list.
How to write an Objective-C subclass file…
Continuing with examples taken from my Chronicle plugin, I’ll show you how to subclass specific object classes, and also how to create some custom methods in a generic subclass.
- Our application calls for a subclass of NSPopUpButton that draws custom popup buttons with no arrow. In this example, we are simply telling every popup button that inherits from this class to draw no arrow instead of a normal arrow when it awakens from nib. In the default implementation of the “awake from nib” method of the NSPopUpButton superclass, popup buttons are initialized with basic values taken from our settings in IB. By overriding the “awake from nib” method, we provide our own procedure for what to execute when awakening from nib. If we fail to override a method, it then takes the default implementation from the superclass…
[code]// CHRPopUpButton.h
#import <Cocoa/Cocoa.h>
@interface CHRPopUpButton : NSPopUpButton {} // *1
- (void)awakeFromNib;
@end[/code]
[code]// CHRPopUpButton.m
#import “CHRPopUpButton.h”
@implementation CHRPopUpButton
- (void)awakeFromNib {
NSPopUpButton* popupButton = self;
[[popupButton cell] setArrowPosition:NSPopUpNoArrow];
}
@end[/code]
- Our second example creates a customized NSWindow subclass. We need to override the initialization of every window in this class, to make sure that it is drawn transparent and with no title bar. We look to our Apple NSWindow class doc to determine which methods we need to override to perform our own initialization, and we find that the “Designated initializer” is the “initWithContentRect:styleMask:backing:defer:” method seen below. When any window of the CHRWindow class get’s initialized, it will use our version of the initialzation method instead of the generic one provided by NSWindow.
[code]// CHRWindow.h
#import <Cocoa/Cocoa.h>
#import <AppKit/AppKit.h>
@interface CHRWindow : NSWindow {}
- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag;
@end[/code]
[code]// CHRWindow.m
#import “CHRWindow.h”
@implementation CHRWindow
-
(id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag {
NSWindow* result = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
[result setBackgroundColor: [NSColor clearColor]];
[result setAlphaValue:1.0];
[result setOpaque:NO];
[result setHasShadow: YES];return result;
}
@end[/code]
*** Note: The above code for creating a transparent window also needs more methods to function properly. If you’re interested in implementing this, do a search over at MacScripter’s Forums for “initWithContentRect” or “NSBorderlessWindowMask” to get more complete implementations.
- The third subclass we will create is a generic subclass that will contain the miscellaneous methods we will use in our application. These methods do not modify an existing class, but instead act upon existing objects. For this class, we simply make a subclass of the root NSObject class and place any generic methods we create into it. My actual CHRObject subclass contains many more methods, which perform functions such as creating and deleting my custom menu, expanding and collapsing outline views, and arranging window levels.
The two example methods which I have used below, along with the corresponding applescript code, can give you an idea of how to structure your custom methods and how to use them in your code. A more thorough knowledge of Objective-C is necessary to begin writing and implementing your own methods in this way.
[code]// CHRObject.h
#import <Cocoa/Cocoa.h>
@interface CHRObject : NSObject {}
+(void)createSeparatorItem:(NSMenu *)theMenu;
+(void)addIconToPopupButton:(NSPopUpButton *)thePopupButton usingImageAtPath:(NSString *)theImagePath;
@end[/code]
[code]// CHRObject.m
#import “CHRObject.h”
@implementation CHRObject
+(void)createSeparatorItem:(NSMenu *)theMenu {
[theMenu addItem:[NSMenuItem separatorItem]];
}
+(void)addIconToPopupButton:(NSPopUpButton *)thePopupButton usingImageAtPath:(NSString )theImagePath {
NSMenuItem theMenuItem = [thePopupButton itemAtIndex:0];
NSImage* theImage;
theImage = [[NSImage alloc] initWithContentsOfFile:theImagePath];
[theMenuItem setImage: theImage];
[theImage release]; // Always release any memory you own when you don't need it anymore - Added 4.10.07
}
@end[/code]
The first example is the “createSeparatorItem:” method. It takes one argument from your applescript code… a reference to a menu… and inserts a menu item at the bottom of it’s current menu items. Since ASStudio has no applescript command which dynamically adds separator items to either main menu’s or popup button menus, I needed a way to add them in Objective-C. In my applescript, I use the code below to add separator items to my main menu as I construct it programmatically. Note that “ChronicleMenu” is a reference to my main menu item that is set elsewhere in the script…
call method "createSeparatorItem:" of class "CHRObject" with parameters {ChronicleMenu}
The second example is the “addIconToPopupButton:usingImageAtPath:” method. It takes two arguments when you call the method from applescript… a reference to the popup button you wish to add an image to, and the path to the image that will be displayed in the popup button. Note that “Img_Actions_Path” is a path to an image in my plugin’s bundle that is set elsewhere in the script…
on awake from nib theObject
if name of theObject is "Files_Action" then
call method "addIconToPopupButton:usingImageAtPath:" of class "CHRObject" with parameters {theObject, Img_Actions_Path}
end if
end awake from nib
Conclusion…
If you’ve only worked with Applescript and Applescript Studio, you may find the task of dealing with Objective-C and subclassing a bit confusing at first. As I write this tutorial, I think back to the few other articles and posts I’ve read that try to explain these same concepts, and I am certain that this one will probably be just as confusing. The only thing that will help you to better understand this powerful feature of ASStudio is to study all of the examples you can find carefully, to practice through trial-and-error, and to ask for help when you need it. The simple examples above most certainly will not tell you everything you need to know about subclassing in ASStudio, but I hope that they are comprehensive enough to explain why and how you might create your own subclasses.
Good luck,
j