



In this crazy, brave, new world of collaboration, Mr. Peabody might suddenly decide to use your application as a server when he's running his amazing new script. Let's have a look at what happens in an application written with MacApp 3 and the OSL Scripting Components source code framework.
pascal void TScriptServerApplication::IScriptServerApplication()
{
this->IOSLServerApplication(
kScriptServerMainFileType, kScriptServerAppSignature);
}
This means that if you want to build your own server (or recorder) application, then you should add a server side to your application; for the developer's convenience, the "normal" cases are already implemented in the OSL Scripting Components package. The following method in TOSLClientServerApplication, the abstract superclass from which TOSLServerApplication and TOSLRecorderApplication inherits, illustrates the main things that an installation of a server side should do.
pascal void TOSLClientServerApplication::DoAddServerSide()
{
this->DoMakeServerSideNodes();
this->DoMakeOSLResolverManager();
this->DoAddServerSideBehaviors();
}
pascal void TOSLServerApplication::DoAddServerSideBehaviors()
{
inherited::DoAddServerSideBehaviors();
this->DoAddReqSuiteHandler();
this->DoAddCoreSuiteHandler();
this->DoAddMiscSuiteHandler();
this->DoAddSCSuiteHandlerForServerSide();
}
By overriding the DoAddServerSideBehaviors method in your own TYourServerApplication class, you could add any number of suite-dispatching behaviors. You might, for example, want to dispatch any of the other standard suites like the Text suite and Database suite or dispatch the events of your own suite.
Note, however, that the OSL Scripting Components package already has defined everything you need if you are going to work with the Required, Core, Miscellaneous and OSLSC suites, both the resource files and the actual dispatching code inside the application.
However, if you are implementing your own suite and your suite's name is, for example, MySuite, then you have to create a MySuite.r resource file, containing constants for the suite ID and the suite's event IDs (see figure 2). This figure shows the three different resource files you'll need to specify and dispatch your own Apple event suite. Note that the constants in the MySuite.r file also will be used if you are creating a client-type application that issues command from your suite.. In the case of the OSL Scripting Components suite, that resource file is named SCSuite.r, and it contains all the constants used by this suite. Remember, this file is not necessarily used only to compile your server application, but could be used to compile your client application as well, if you want that client to issue Apple events from the MySuite suite.
An example of the contents of a Suite Definition resource file: the OSL Scripting Components suite's SCSuite.r:
//*********************************************************** // OSL Scripting Components suite //*********************************************************** #define kOSLScriptingComponentsSuiteEventClass 'SCec' #define kSCInformServerThatClientQuits 'SCis' #define kSCInformClientThatServerQuits 'SCic'
In the OSL Scripting Components package, all command constants that are a result of an incoming Apple event are placed in special file for the suite the event belongs to. For example, the command constants that are corresponding to incoming events from the Core suite are placed in the HandleCoreSuiteCommandID.r file.
#define cHandleAEClone 10100 #define cHandleAEClose 10101 #define cHandleAECountElements 10102 (more)Note that all command constants for incoming Apple events begin with "cHandle" to point out that they are on an application's server side.
An example of such a resource from the CoreSuiteAEDispatch.r file is shown below, where the constants for the incoming Apple events of the Core suite are mapped to corresponding command constants.
// **********************************************************
// APPLE EVENT DISPATCHING - CORE SUITE
// **********************************************************
resource 'aedt' (kAECoreSuiteDispatchTable) {{
kAECoreSuite, kAEClone, cHandleAEClone;
kAECoreSuite, kAEClose, cHandleAEClose;
kAECoreSuite, kAECountElements, cHandleAECountElements;
(more)
}};
In order to bring some system into this, I decided to work with events on a suite-level basis-that is, a suite and its events is a building block, which I would like to add or remove from the application. To accomplish this, I used the concept of behaviors that I can install into the application. Each behavior contains the handling of all the commands in one suite.
For example, the handling of the Core suite events are done in the behavior called TCoreSuiteHandler, located in the UHandleCoreSuiteCmds unit (see example code below).
pascal void TCoreSuiteHandler::DoAppleCommand(
CommandNumber aCmdNumber,
const AppleEvent& message,
const AppleEvent& reply) // override
{
switch (aCmdNumber)
{
case cHandleAEClone:
this->DoHandleAECloneCommand(aCmdNumber,message,reply);
break;
case cHandleAEClose:
this->DoHandleAECloseCommand(aCmdNumber,message,reply);
break;
case cHandleAECountElements:
this->DoHandleAECountElementsCommand(aCmdNumber,message,
reply);
break;
(more)
default:
inherited::DoAppleCommand(aCommandNumber,
message, reply);
break;
}
}
The DoHandleAECloneCommand, DoHandleAECloseCommand and DoHandleAECountElementsCommand methods in the code example above create, initialize and post a new instance of THandleAECloneCommand, THandleAECloseCommand and THandleAECountElementsCommand, respectively. These server-type command classes (they all inherit from TServerCommand, even though not direct descendants) are also physically located in the UHandleCoreSuiteCmds unit, together with the behavior (see Figure 3).
class TOSLServerCommand : public TServerCommand
{
public :
// Creating and freeing:
virtual pascal void InitializeFromAppleEvent(
CommandNumber itsCommandNumber,
TCommandHandler* itsContext,
Boolean canUndo,
Boolean causesChange,
TObject* objectToNotify,
const AppleEvent& itsMessage,
const AppleEvent& itsReply);
virtual pascal void IOSLServerCommand(
CommandNumber itsCommandNumber,
TCommandHandler* itsContext,
Boolean canUndo,
Boolean causesChange,
TObject* objectToNotify);
// Action:
virtual pascal void FreeTheMessage(); // override
virtual pascal void Completed(); // override
virtual pascal void RegisterClient();
virtual pascal void UnregisterClient();
};
This class is responsible for the main structure of what's going to happen in any Apple event-type command.
The DoReadParameters method calls the GotRequiredParameters and the ReadParameters methods. GotRequiredParameters checks to see if all parameters that are required are OK, while the ReadParameters method is unimplemented (see subclasses).
The DoProcessing method identifies the need for additional processing, but is unimplemented in this class.
This class also identifies the concept of a result, fResultDesc, which can be written to the reply Apple event with the DoWriteReply method. The DoWriteReply method is unimplemented in this class.
class THandleAppleEventCommand : public TOSLServerCommand
{
public:
// Creating and freeing:
virtual pascal void Initialize(); // override
virtual pascal void Free(); // override
virtual pascal void DoIt(); // override
// Accessors and mutators:
virtual pascal CDesc GetResultDesc();
virtual pascal void SetResultDesc(const CDesc& theResultDesc);
// Action:
virtual pascal void DoReadParameters();
virtual pascal void ReadParameters();
virtual pascal void GotRequiredParameters();
virtual pascal void DoProcessing();
virtual pascal void ReportError(OSErr error, long); // override
virtual pascal void DoWriteReply();
private:
CDesc fResultDesc;
};
The reading of the incoming Apple event is done in the ReadDirectObject method. This method also finds out if it's a "real" object specifier (typeObjectSpecifier) or a null specifier (typeNull). If it's a null object specifier, then there's no need for additional resolving; the null value specifies that we found the object tree's root-the application.
If it needs to be resolved, the resolving is initiated by the Toolbox's AEResolve function, which is called from the ResolveObject method. This will initiate the resolving process, which is controlled by the TOSLResolverManager. When the resolving is ready, the DoAfterResolve method is called, which in turn calls the DoTheResolverAction. The DoTheResolverAction is responsible for applying the Apple event verb to the newly resolved resolver object.
class THandleOSLObjectCommand : public THandleAppleEventCommand
{
public:
// Creating and freeing:
virtual pascal void Initialize(); // override
virtual pascal void Free(); // override
// Accessors and mutators:
virtual pascal Boolean GetCallResolveObject();
virtual pascal DescType GetDirectObjectType();
virtual pascal CDesc GetObjectSpecifier();
virtual pascal TOSLObjectResolver* GetOSLObjectResolver();
virtual pascal void SetCallResolveObject(
Boolean callResolveObject);
virtual pascal void SetDirectObjectType(D
escType theDirectObjectType);
virtual pascal void SetObjectSpecifier(
const CDesc& theObjectSpecifier);
virtual pascal void SetOSLObjectResolver(
TOSLObjectResolver* theOSLObjectResolver);
virtual pascal void SetShouldCallResolveObject(
DescType theDescType);
// Action:
virtual pascal void DoProcessing(); // override
virtual pascal void DoBeforeResolve();
virtual pascal void DoResolve();
virtual pascal void DoAfterResolve();
virtual pascal void ResetBeforeResolve();
virtual pascal void ResolveObject();
virtual pascal void ResolveProperty();
virtual pascal void CoerceResult();
virtual pascal OSErr DoTheResolverAction(
TOSLObjectResolver* theOSLObjectResolver);
// Read/Write:
virtual pascal void ReadParameters();
virtual pascal void ReadDirectObject();
virtual pascal void DoWriteReply(); // override
private:
Boolean fCallResolveObject; // should we actually do resolve?
DescType fDirectObjectType; // either a null aedesc, AEList
// or a obj specifier
CDesc fObjectSpecifier;
TOSLObjectResolver* fOSLObjectResolver;
};
The THandleAEGetDataCommand's main responsibility is to override the ReadParameters method to read an additional (and optional) parameter of the Get Data event: its keyAERequestedType parameter.
Another thing it does is to provide an override of the DoTheResolverAction method, which gives the TOSLObjectResolver object (a result of the resolving process) a DoGetData message.
The last thing it does is to coerce the result to the type requested in the keyAERequestedType parameter. This is done in the override of the CoerceResult method.
class THandleAEGetDataCommand : public THandleOSLObjectCommand
{
public:
// Create / free
virtual pascal void Initialize(); // override
// Access:
virtual pascal DescType GetRequestedType();
virtual pascal void SetRequestedType(
DescType theRequestedType);
// Reading:
virtual pascal void ReadParameters(); // override
virtual pascal DescType ReadRequestedType();
// Resolving
virtual pascal OSErr DoTheResolverAction(
TOSLObjectResolver* theOSLObjectResolver); // override
virtual pascal void CoerceResult(); // override
private:
DescType fRequestedType;
};
The responsibility of the TOSLResolverManager is to manage the process of resolving. There should be only one TOSLResolverManager in an application, and the one provided by the framework is the one you should use-no subclassing is necessary. A typical resolving process goes like this:
First, AEResolve begins its internal resolving mechanism and calls the TOSLResolverManager:CallResolve static function, previously installed by the TOSLResolverManager::InstallAccessors method.
Second, CallResolve calls the TOSLResolverManager:ResolveIt function, which tries to find out which TOSLObjectResolver object the specifier contains. When this first round of resolving is ready, it sets the TOSLResolverManager's field fCurrentOSLObjectResolver to that newly found TOSLObjectResolver object.
Repeat the second step until all contained object resolvers are resolved.
Last, the server-type command retrieves the TOSLObjectResolver object in the TOSLResolverManager's field fCurrentOSLObjectResolver.
A resolver object communicates with its "real" object to get additional information-for example, a TOSLApplicationResolverObject communicates with its TOSLApplication object. What's more, the "real" object is responsible for actually creating the resolver object. This way, you can override the "real" object's class definition to create another resolver object-maybe a simple override of the original one with a couple of new properties and elements?
Then you have to override the DoGetData method of TOSLWindowResolver. You need to do this because you want the additional pColor property to be dispatched.
pascal OSErr TYourWindowResolver::DoGetData(CDesc& theData)
{
DescType propertyID = this->GetPropertyID();
switch (propertyID)
{
case pColor:
return this->DoGetData_ColorProperty(theData);
default:
return inherited::DoGetData(theData);
}
}
The next thing you need to do is to add a new method in TYourWindowResolver that retrieves the data of that property. Getting the color property from your window object probably should be called DoGetData_ColorProperty. The example below shows you that in that method, you must ask the "real" window for its color property and then ask the "real" window object for its name.
pascal OSErr TYourWindowResolver::DoGetData_ColorProperty
(CDesc& theData)
{
this->FailThisObjectReference(); //is my "real" object valid?
long aColor = fOSLWindow->OSL_GetData_Color();
//color is long in this example
theData.Create(aColor); // create a long data descriptor
return noErr;
}
pascal long TYourWindow::OSL_GetData_Color()
{
long theColor = this->GetColor();
return theColor; // yes, color is a long in this example !
}
pascal TOSLObjectResolver* TYourApplication::NewOSLWindowResolver(
TOSLWindow* theOSLWindow,
TOSLObjectResolver* theOSLObjectResolver)
{
if (theOSLWindow) {
TYourWindowResolver* aYourWindowResolver =
new TYourWindowResolver;
aYourWindowResolver>IYourWindowResolver(
theOSLWindow,theOSLObjectResolver);
return aYourWindowResolver;
}
return NULL;
}



