本文由 简悦 SimpRead转码, 原文地址 preserve.mactech.com
相关信息: 苹果事件管理器
并非所有内容都能在普通 AppleScript 中处理 - 以下是添加到 AppleScript 的方法
作者:Donald Olson,苹果电脑公司
注:文章附带的源代码文件位于 MacTech CD-ROM 或源代码磁盘中。
I. 简史
最初,大祭司们制作了 AppleScript。它有条件、流程控制和漂亮的打印。它有带属性和方法的脚本对象。它能与应用程序对话,并集成其功能。它可以跨网络对话。它是一种 OODL。大祭司们宣称它很棒。
但 AppleScript 的用户很快就发现了设计中的问题。"他们问:"问/答对话框和低级哔哔声在哪里?"他们问:"标准文件和 PPC 浏览器对话框在哪里?
大祭司们宣布:"你们要求的这些东西不属于 AppleScript 这样的纯语言。你们必须从应用程序中寻求解脱!"
在 AppleScript 的国度里,人们哀声叹气,咬牙切齿。"人们抗议说:"我们必须拥有简单的'哔'声和友好的'问/答'对话框,这样我们就可以在不使用特殊应用程序的情况下互相交谈了。"我们必须有这么多小程序来为我们服务,这对我们的硬盘是一种亵渎!"
于是,祭司们商议起来。"他们说:"AppleScript 的人想为我们的语言添加新的功能。"我们不允许 AppleScript 这门语言的纯洁性受到用户界面元素和噪音的玷污。因此,我们将提供一种机制,允许人们为 AppleScript 添加语法、对话框和数据转换,而无需将其作为我们语言的永久组成部分。我们将为他们提供一套基本的扩展程序,以满足他们的需求"。
于是,大祭司们向 AppleScript 的使用者们揭开了 "脚本扩展 "的神秘面纱。
人们欢欣鼓舞 "我们有了我们的哔哔声,我们的问/答,这很好!脚本附加功能按需加载,整个系统都可以使用,而且还不会填满我们的系统堆!"
"但是,哦,大祭司,"一位 AppleScript 人员说,"这套最基本的脚本附加功能并不能满足我的全部需求。我怎样才能自己编写这样的脚本附加程序,从而实现 AppleScript 承诺的全面集成呢?"
"哦,AppleScript 人,我将从 VirtualTables 的山峰上带来脚本附加程序的编码说明和戒律。明智地使用这些信息,你的脚本附加程序就会繁荣昌盛。"
"好好听我说,AppleScript 的子民们。脚本附加功能是我们新的 OSA 脚本语言 AppleScript 的一部分。脚本附加功能提供了一种扩展 AppleScript 语言和功能的机制。它们类似于 HyperCard 的 XCMD 和 Frontier 的 UCMD"。
MacTech 编辑:"Hellooow,Donald!"
文章作者 "嗯,尼尔,你说呢?"
MacTech 编辑:"这些神话故事很有趣,但我们能继续写文章吗?"
作者: "文章?哦!对不起!好了,开始吧
二. 部件
脚本附加功能是以 Apple 事件处理程序或强制程序的形式实现的,AppleScript 可按需加载。要编写脚本附加功能,你需要对苹果事件管理器(AEM)有基本的了解。
AEM 是一组例程,提供了一种在本地计算机或 AppleTalk 网络上的应用程序之间收发消息(称为 AppleEvents)的机制。每条消息都包含一对标识符,用于告知接收消息的应用程序将采取的行动。这些标识符被称为报文的类和 id。由于类和 id 指定了要采取的行动,因此通常把类和 id 称为消息的动词。
数据也可能与消息的动词相关联。这些数据被放置在称为描述符的结构中,并作为参数添加到 Apple 事件中。AEM 提供了一套用于添加或提取消息参数的例程。由于与一条信息相关的参数可能不止一个,因此每个参数都有一个称为关键字的唯一标识符。由于动词通常有一个要执行的动作焦点,因此有一个称为直接参数(或直接对象)的标准参数,用于指定动词要执行的对象。
苹果事件消息的一个例子是打开文档消息,当你双击一个或多个与应用程序相关的文档时,Finder 就会发送该消息。打开文档事件的类是 kCoreEventClass,id 是 kAEOpenDocuments。它有一个参数,即 keyDirectObject(又称直接参数),其中包含一个 AppleEvent 列表,该列表包含应用程序要打开的文档的别名。
当应用程序在其事件队列中收到 Apple 事件(高级事件)时,它会调用 AEM 例程 AEProcessAppleEvent。这会导致 AEM 尝试将 AppleEvent 分派给应用程序在 AEM 应用程序分派表中安装的 Apple 事件处理程序。AEM 会根据事件的类和 ID 进行分派。在我们上面的例子中,应用程序需要安装一个类为 kCoreEventClass、id 为 kAEOpenDocuments 的处理程序,才能成功处理打开文档 Apple 事件。
你会问,这与编写脚本附加程序有什么关系?
AEM 不仅为每个安装了处理程序的应用程序提供了 AppleEvents 的调度表,还为系统处理程序维护了一个调度表。系统处理程序与特定于应用程序的处理程序不同,安装了系统处理程序的计算机上的所有应用程序都可以使用系统处理程序。这就是 AppleScript 安装脚本插件的地方。如果 AEM 在应用程序调度表中找不到匹配的处理程序,或者处理程序返回两个特殊错误之一(errAEEventNotHandled 或 errAENoSuchObject),AEM 就会尝试在系统调度表中找到匹配的处理程序,从而调用 OSAX。
胁迫的工作方式与此类似。在强制中,不使用 class 和 id。取而代之的是 "from "和 "to "数据类型。AEM 还为胁迫维护应用程序和系统调度表。
现在,我们已经对苹果事件有了一个非常简短的概述,下面让我们深入了解脚本添加文件的各个部分。
脚本附加文件
包含组成脚本附加文件的各种资源的文件是以 "osax "类型和 "ascr "创建的。这样,Finder 就能将脚本附加文件图标(存储在 AppleScript 中)与每个 OSAX 文件关联起来。
"osax"资源
每个脚本附加文件至少有一个 "osax "类型的代码资源,其中包含脚本附加文件的事件或强制处理程序。osax "资源的名称告诉 AppleScript 脚本附加文件的类型、事件处理程序的类和 ID,或强制 OSAX 的 from 和 to 类型。命名方案是这样的: osax "资源名称的前四个字符告诉 AppleScript OSAX 的类型。事件处理程序为 "AEVT",强制处理程序为 "CSDS "或 "CSPT"。AEVT "代表 "苹果事件","CSDS "代表 "从描述符强制","CSPT "代表 "从指针强制"。
例如,名为 "AEVTaevtodoc "的 "osax "资源将安装一个 "打开文档 "处理程序。名为 "CSDSscptTEXT "的 "osax "资源将安装从 "scpt"(AppleScript 编译脚本的数据类型)到 "TEXT"(文本类型)的强制。
其他资源
事件和强制 OSAX 文件中都可以包含其他资源。一个显示对话框的事件处理程序可能包含 "DLOG "和 "DITL "资源。强制处理程序可能有一个包含强制使用的转换信息的资源。在向 OSAX 代码分派之前,AppleScript 中的脚本添加机制会将 OSAX 的资源文件作为当前资源文件。因此,OSAX 可以访问以下资源链中的任何资源: OSAX -> [目标应用程序] -> 系统。如果脚本中没有指定目标(即没有使用 "告诉应用程序 "块),目标应用程序就是正在使用的脚本编辑器。
事件处理程序需要一种强制处理程序不需要的资源。"aete" 或 Apple 事件术语扩展资源。这是提供事件语言语法的资源。
"aete"资源
AppleScript 使用 "aete "资源来确定 OSAX 命令的语法。您可以在 aete 中描述 Apple 事件及其所有参数,并为每个参数提供相应的语法等价。事实上,我在设计 OSAX 命令时,喜欢从 AppleScript 的动词语法入手,编写与语法相匹配的 aete,然后再实际编写代码。
III. 编写脚本添加
先设计语法
让我们设计一个在模式对话框中播放 QuickTime 电影的 OSAX。让我们看看语法如何:play movie <the path to the movie> [at <point or rectangle>]?听起来差不多。这样,用户就可以指定要播放的电影,还可以选择窗口在屏幕上的位置。(我们特意不在本实现中添加对控制器的支持。这将留给读者一个练习)。
下面是上述语法的事件:
-
play movie:(播放电影)- 这是一个动词,因此让我们为此指定类和 ID。
-
class:"OLIE" - 这恰好是我的昵称,您应该使用您在 DTS 注册的应用程序签名或其他可以区分您的 OSAX 的 4 位字符代码。请记住,小写字母是为系统(读作苹果电脑)保留的。
-
id:"QTIM" - 尽量使用描述性的文字。
-
<the path to the movie>- 苹果事件的直接参数被定义为没有相关语言标签。这是因为动词所作用的对象意味着下一个要作用的对象将被描述。由于我们的动词 "播放电影 "要求我们描述要播放的电影,因此我们将使用直接参数来表示要播放的电影的路径。 -
keyword: '----' - 直接参数。
-
type: typeAlias - 对于路径,我们将使用 typeAlias。有关别名与其他寻址形式的讨论,请参阅下面的 "提示、技巧和疑难解答 "部分。
-
[at <point or rectangle>]- 我们的第一个 "命名 "参数。
关键字:'LOCA' - 这可以是一个 2 或 4 个整数的列表,也可以是一个包含点或矩形位置标签的记录;例如:play movie alias “Cool Disk:Cooler Folder:Hot Movie” at {qttop: 15, qtleft:30}。这对用户来说很好,因为他们不需要记住点或矩形坐标的排序。请注意,我们并没有简单地使用 "top "和 "left "作为记录标签,而是使用了前缀 "qt"。这是因为与 AppleScript 本身包含的文本套件定义的术语相冲突。左 "和 "右 "已被定义为文本块的格式选项。有关其他可能的语法冲突,请参阅下文第五节。
- type: typeAEList/typeMyRectangle - 由于数据可以是列表或记录,我们需要用每种可能的数据类型来定义此参数。typeMyRectangle 是一种自定义类型,我们将其定义为一个类。有关使用类定义自定义数据类型的更多信息,请参阅下文第五部分。
我们也可以为 "at "参数使用通配符类型。但我们选择不这样做,因为这会给 OSAX 的语法增加歧义,因为这意味着可以为该参数传递任何数据类型。
第二步构建您的 aete
现在看看 "SAPlayMovie.r "列表,看看上述设计在 "aete "中的运行情况。请注意我们是如何填写 aete 的其他字段的。尤其要注意靠近底部的类定义。这里是我们为 "at "参数声明记录的地方。我们将矩形记录类型定义为 "rect "类,其属性包括上坐标、左坐标、下坐标和右坐标。
另外请注意,我们声明了两次 "at "参数:一次是类型列表,另一次是矩形记录的类定义类型。这样,AppleScript 就知道了 "at "参数允许使用的两种类型。
"aete "资源的正式定义见文件 "AEUserTermTypes.r"。
/* 1 */
/*
SAPlayMovie.r written by Donald O. Olson
A simple QuickTime Scripting Addition written
to illustrate writing Scripting Additions.
Copyright ®1993 Donald O. Olson
All rights reserved.
*/
#include ”Types.r"
#include "SysTypes.r"
#include "AEUserTermTypes.r"
#define typeMyRectangle 'RECT'
resource 'vers' (1) {
0x1,
0x0,
final,
0x0,
verUS,
"1.0",
"1.0, Copyright ® 1993 Donald Olson"
". All rights reserved."
};
resource 'vers' (2) {
0x1,
0x0,
final,
0x0,
verUS,
"1.0",
"(by Donald Olson)"
};
/* This string gets displayed if the user double clicks on us. */
resource 'STR ' (-16397) {
"This document can not be opened or printed."
" It extends the functionality of AppleScript™"
"and should be placed in the Scripting Additions"
"folder found in the Extensions folder of your"
" System Folder."
};
resource 'aete' (0, "play movie") {
/*
The major and minor version fields refer to the
'aete' definition.
*/
0x1, /* major version in BCD */
0x0, /* minor version in BCD */
english, /* language code */
roman, /* script code */
{ /* array Suites: 1 elements */
/* [1] */
"The Olson OSAX Suite.", /* suite name */
/* Suite Description */
"A collection of fine Scripting Additions"
" for work and play.", /* suite description */
'OLIE',/* suite ID */
1,/* suite level */
1,/* suite version */
{ /* array Events: 1 elements */
/* [1] */
"play movie", /* event name */
/* event description */
"Play a QuickTime movie in a modal dialog.",
'OLIE',/* Our Class */
'QTIM',/* Our ID */
noReply, /* No Reply */
/* Reply comment. */
"No reply is returned by this event.",
replyOptional, /* Reply not required. */
singleItem,/* Reply is a single item. */
notEnumerated, /* Reply is not enumerated */
reserved, reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved, reserved,
reserved, reserved, reserved,
'alis',/* Direct param is alias. */
/* Comment for direct parameter. */
"Pass in path to the QuickTime movie to play.",
directParamRequired,
singleItem,
notEnumerated,
doesntChangeState,
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
{ /* array OtherParams: 2 elements */
/* [1] */
"at", /* Optional parameter */
'LOCA',/* Its keyword. */
'list',/* Its type. */
/* Comment for optional parameter. */
"Point to use to position movie or rectangle"
" to play move in. Must be in order 'left,"
" top, right, bottom.",
optional,/* Optional parameter */
listOfItems, /* Must be a list. */
notEnumerated, /* is not enumerated */
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
reserved,
/* [2] */
/*
We define our optional parameter
twice so that we can allow two
different data types.
*/
"at", /* Optional parameter */
'LOCA',/* Its keyword. */
typeMyRectangle, /* Its type. */
/* Comment for optional parameter. */
"Point to use to position movie or rectangle"
" to play move in. Must be in order 'left,"
" top, right, bottom.",
optional,/* Optional parameter */
listOfItems, /* Must be a list. */
notEnumerated, /* is not enumerated */
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
reserved
}
},
{ /* array Classes: 1 elements */
/* [1] */
/*
This is how we define our custom
record. We define a class 'rectangle'
with the custom keyword typeMyRectangle.
We then define properties of this class
for each of our records labels.
*/
"rectangle", /* Name of our class. */
typeMyRectangle,/* Type of our class. */
/* Comment for optional parameter. */
"This is a custom class definition used to "
"define the record we use to position our "
"movie's window.",
{/* array Properties: 4 elements */
/* [1] */
"qttop", /* Name of property. */
'TOP ',/* Keyword */
typeMyRectangle,/* Its type. */
"Top of rectangle.",/* Comment field. */
reserved,
singleItem,
notEnumerated,
readOnly,
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
/* [2] */
"qtright", /* Name of property. */
'RGHT',/* Keyword */
typeMyRectangle,/* Its type. */
/* Comment field. */
"Right side of rectangle.",
reserved,
singleItem,
notEnumerated,
readOnly,
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
/* [3] */
"qtleft",/* Name of property. */
'LEFT',/* Keyword */
typeMyRectangle,/* Its type. */
"Left side of rectangle.",/* Comment field. */
reserved,
singleItem,
notEnumerated,
readOnly,
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
/* [4] */
"qtbottom",/* Name of property. */
'BOTM',/* Keyword */
typeMyRectangle,/* Its type. */
"Bottom of rectangle.", /* Comment field. */
reserved,
singleItem,
notEnumerated,
readOnly,
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved,
reserved, reserved, reserved, reserved
},
{ /* array Elements: 0 elements */
}
},
{ /* array ComparisonOps: 0 elements */
},
{ /* array Enumerations: 0 elements */
}
}
};
第三步编写代码
编写 OSAX 非常简单:遵循编写独立代码资源的所有规则即可。
我们的示例将使用 "C "语言编写,但脚本添加可以使用任何可以将您的工作编译成代码资源的语言编写。
/* 2 */
/////////////////////////////////////////////////////////////
//
// SAPlayMovie.c written by Donald O. Olson
// A simple QuickTime Scripting Addition written
// to illustrate writing Scripting Additions.
//
// Copyright ®1993 Donald O. Olson
// All rights reserved.
//
/////////////////////////////////////////////////////////////
// Our includes
#include <Movies.h>
#include <Memory.h>
#include <Fonts.h>
#include <OSEvents.h>
#include <limits.h>
#include <Menus.h>
#include <Processes.h>
#include <String.h>
#include <Resources.h>
#include <Packages.h>
#include <AppleEvents.h>
#include <Errors.h>
#include <GestaltEqu.h>
#include <Files.h>
// Our optional parameters keyword
#define keyLocation'LOCA'
// Our rectangle records keywords
#define keyRight 'RGHT'
#define keyLeft 'LEFT'
#define keyTop 'TOP '
#define keyBottom'BOTM'
#define kLeft 1 // Our list of points are in the
#define kTop2 // order shown
#define kRight 3
#define kBottom 4
// Used to position movie on screen
#define kDefaultOffset 100
#define kBogusNumber UINT_MAX
/*
Our prototypes, could be in a .h file but are included here
for ease of use.
*/
OSErr PlayTheMovie(FSSpec myFSSpec, AEDesc theLocationDesc);
OSErr SetMovieRect(AEDesc theLocationDesc, Movie theMovie, Rect *ourRect);
/////////////////////////////////////////////////////////////
//
// main()
// The entry to our Scripting Addition.
// Remember to declare it pascal!!
//
/////////////////////////////////////////////////////////////
pascal OSErr main(AppleEvent *theEvent,
AppleEvent *theReply,
long theRefCon)
{
OSErr theErr = noErr;
FSSpec theFSSpec;
DescType typeCode;
long theGestaltReturn = 0;
Size actualSize;
AEDesc theLocationDesc;
/* Is QuickTime present? */
theErr = Gestalt(gestaltQuickTime, &theGestaltReturn);
if(theErr) return theErr; // If not, bail.
/*
Grab the movie file's path from the direct parameter.
We declared the direct parameter to be an alias in our
aete. Since the AEM will coerce an alias to a FSSpec for
us, and that's what the OpenMovieFile call wants, we'll
ask for it as an FSSpec.
*/
theErr = AEGetParamPtr(theEvent, keyDirectObject,
typeFSS, &typeCode, (Ptr)&theFSSpec,
sizeof(FSSpec), &actualSize);
if(theErr) return theErr;
/*
Now get the location parameter, if it's present.
We don't check errors for this call since the AEM
will return the descriptor with the descriptorType
field set to typeNull if there is an error. We check
for NULL in the PlayTheMovie function.
*/
theErr = AEGetParamDesc(theEvent, keyLocation,
typeWildCard, &theLocationDesc);
/* Start up the movie tools */
if(EnterMovies()) return theErr; // Bail on error.
theErr = PlayTheMovie(theFSSpec, theLocationDesc);
ExitMovies(); // Close our connection to the movie tools
return theErr; // And return our error.
}
/////////////////////////////////////////////////////////////
//
// PlayTheMovie() Opens and plays Movie File
// This code is based on the SimplePlayer sample
// that comes with the QuickTime Developers Disk.
//
/////////////////////////////////////////////////////////////
OSErr PlayTheMovie(FSSpec myFSSpec, AEDesc theLocationDesc)
{
Movie theMovie;
Rect dispBounds;
WindowPtrmovieWindow = NULL;
OSErr theErr = noErr;
short resRefNum;
long duration = 60, finalTick;
/* Open the movie file */
if(OpenMovieFile(&(myFSSpec), &resRefNum, 0)) {
SysBeep(1);// Signal our error
return theErr; // And bail
}
if(NewMovieFromFile(&theMovie, resRefNum,
NULL, NULL,0, NULL)) {
SysBeep(1);// Signal our error
return theErr; // And bail
}
/* Get the bounds for the movie. */
GetMovieBox(theMovie, &dispBounds);
/*
If the user passed in a location or size for the window,
let's grab it now.
*/
if(theLocationDesc.descriptorHandle != NULL) {
// Use the values sent to us by the user.
theErr = SetMovieRect(theLocationDesc, theMovie,
&dispBounds);
if(theErr) return theErr;
} else {
OffsetRect(&dispBounds, -dispBounds.left, -dispBounds.top);
SetMovieBox(theMovie, &dispBounds);
// Make sure window not under menu bar
OffsetRect(&dispBounds, kDefaultOffset, kDefaultOffset);
}
/*
Any time you are going to put up a dialog be sure to
call AEInteractWithUser. The AppleEvent Manager will
take care of posting notification and/or layer switching
as needed.
*/
theErr = AEInteractWithUser(kAEDefaultTimeout, NULL, NULL);
if(theErr) return theErr;
/* Set up our window */
movieWindow = NewCWindow(0L, &dispBounds,
(StringPtr)myFSSpec.name,
true, 0, (WindowPtr)-1L, false, 0L);
if(movieWindow == NULL) {
// Whoops, no window so BAIL;
SysBeep(1);
return memFullErr;
}
ShowWindow(movieWindow); // Make the window visible
SetPort(movieWindow); // Set the part
// Set up the movie world
SetMovieGWorld(theMovie,NULL,NULL);
/* Now we're ready to play the movie */
GoToBeginningOfMovie(theMovie);// Rewind movie
PrerollMovie(theMovie,0,0);// Get the movie ready to play
SetMovieActive(theMovie,true); // Activate movie
StartMovie(theMovie); // Start playing
/*
Play the movie to the end unless the mouse button has
been pressed
*/
while (!IsMovieDone(theMovie) && !Button())
MoviesTask(theMovie,0);
FlushEvents(everyEvent, 0);// Clean up spurious events
Delay(duration, &finalTick); // Pause on the final frame
/* Clean up and go home */
DisposeMovie(theMovie); // Get rid of the movie
CloseMovieFile(resRefNum); // Close movie file
DisposeWindow(movieWindow);// And dispose our window
return theErr;
}
/////////////////////////////////////////////////////////////
//
// SetMovieRect
// Set the position and bounding rectangle of
// our movie window.
//
/////////////////////////////////////////////////////////////
OSErr SetMovieRect(AEDesc theLocationDesc, Movie theMovie, Rect *ourRect)
{
long numberOfListItems = 0;
OSErr theErr = noErr;
AEKeyword theAEKeyword;
DescType typeCode;
Size actualSize;
long pointLeft, pointTop,
pointRight = kBogusNumber, // We use pointRight
pointBottom; // to see if we've gotten a point
// or a rectangle
/* Did we get passed a list? */
if(theLocationDesc.descriptorType == typeAEList) {
/* Get the data handle size to determine if point or rect */
theErr = AECountItems(&theLocationDesc, &numberOfListItems);
if(theErr) return theErr; // Bail on error!
/* Must be two or four items in list */
if(numberOfListItems != 2 && numberOfListItems != 4)
return paramErr;
/* If it's a point, just move the window. */
if(numberOfListItems == 2) {
theErr = AEGetNthPtr(&theLocationDesc, kLeft,
typeLongInteger, &theAEKeyword, &typeCode,
(Ptr)&pointLeft, sizeof(pointLeft), &actualSize);
if(theErr) return theErr; // Just in case
theErr = AEGetNthPtr(&theLocationDesc, kTop,
typeLongInteger, &theAEKeyword, &typeCode,
(Ptr)&pointTop, sizeof(pointTop), &actualSize);
if(theErr) return theErr; // Just in case
} else if(numberOfListItems == 4) { // It's a rectangle
theErr = AEGetNthPtr(&theLocationDesc, kLeft,
typeLongInteger, &theAEKeyword, &typeCode,
(Ptr)&pointLeft, sizeof(pointLeft), &actualSize);
if(theErr) return theErr; // Just in case
theErr = AEGetNthPtr(&theLocationDesc, kTop,
typeLongInteger, &theAEKeyword, &typeCode,
(Ptr)&pointTop,sizeof(pointTop), &actualSize);
if(theErr) return theErr; // Just in case
theErr = AEGetNthPtr(&theLocationDesc, kRight,
typeLongInteger, &theAEKeyword, &typeCode,
(Ptr)&pointRight, sizeof(pointRight), &actualSize);
if(theErr) return theErr; // Just in case
theErr = AEGetNthPtr(&theLocationDesc, kBottom,
typeLongInteger,&theAEKeyword, &typeCode,
(Ptr)&pointBottom, sizeof(pointBottom), &actualSize);
if(theErr) return theErr; // Just in case
}
/* Is it a record? */
} else if(theLocationDesc.descriptorType == typeAERecord) {
/* Get the points out by key names */
theErr = AEGetKeyPtr(&theLocationDesc, keyLeft,
typeLongInteger, &typeCode, (Ptr)&pointLeft,
sizeof(pointLeft), &actualSize);
if(theErr) return theErr; // Must have these two
theErr = AEGetKeyPtr(&theLocationDesc, keyTop,
typeLongInteger, &typeCode, (Ptr)&pointTop,
sizeof(pointTop), &actualSize);
if(theErr) return theErr; // Must have these two
theErr = AEGetKeyPtr(&theLocationDesc, keyRight,
typeLongInteger, &typeCode, (Ptr)&pointRight,
sizeof(pointRight), &actualSize);
// Ignore this error
theErr = AEGetKeyPtr(&theLocationDesc, keyBottom,
typeLongInteger, &typeCode, (Ptr)&pointBottom,
sizeof(pointBottom), &actualSize);
// Ignore this error too, but clear our variable
theErr = noErr;
}
if(pointRight == kBogusNumber) // We got a new origin...
SetRect(ourRect, pointLeft, pointTop,
(ourRect->right - ourRect->left) + pointLeft,
(ourRect->bottom - ourRect->top) + pointTop);
else // We got a new rectangle...
SetRect(ourRect, pointLeft, pointTop,
pointRight, pointBottom);
/* Set topleft to 0,0 */
OffsetRect(ourRect, -ourRect->left, -ourRect->top);
/* Set the movie box to the new rect. */
SetMovieBox(theMovie, ourRect);
OffsetRect(ourRect, pointLeft, pointTop);
return theErr;
}
第四步编译
要在 MPW 中编译,请使用以下编译命令:
/* 3 */
C -b "SAPlayMovie.c" -d SystemSevenOrLater
Rez -a -o "play movie" -t osax -c ascr 'SAPlayMovie.r'
Link -p -w -t osax -c ascr -rt osax=6991 -m MAIN -sg "AEVTOLIEQTIM" -ra
"AEVTOLIEQTIM"=resSysHeap,resPurgeable
"SAPlayMovie.c.o"
"{CLibraries}"StdCLib.o
"{Libraries}"Runtime.o
"{Libraries}"Interface.o
-o "play movie"
要在 Symantec THINK C 中编译,请在项目中包含 SAPlayMovie.c、SAPlayMovie.r 和 MacTraps。如图所示设置项目类型。确保资源属性 Purgeable 和 System Heap 设置为 true。
第五步编写一些测试脚本
play movie (choose file) at {100, 100}
play movie (choose file) at {qttop: 100, qtleft: 100, qtright:250, qtbottom, 250}
IV. 编写强制添加脚本
编写强制 OSAX 比编写事件 OSAX 更简单。设计工作仅仅是注意到需要将一种数据类型强制为另一种数据类型。你需要做的一个决定是,胁迫应该从指针胁迫还是从描述符胁迫。一般来说,应始终使用从指针胁迫,因为这意味着 AppleScript 无需构建 AEDesc 以传递到胁迫中。
编写代码
我们将在这里编写一个胁迫示例,将一个脚本对象胁迫为文本。我们将利用开放脚本架构(Open Scripting Architecture)中的一些调用,将脚本对象去编译为具有代表性的文本形式。下面是一个使用胁迫描述符的示例。
/* 4 */
/////////////////////////////////////////////////////////////
//
// CoercescptToText.c written by Donald O. Olson
// A simple Coercion Scripting Addition written
// to illustrate writing Scripting Additions.
//
// Copyright ®1993 Donald O. Olson
// All rights reserved.
//
/////////////////////////////////////////////////////////////
#include <Memory.h>
#include <Fonts.h>
#include <OSEvents.h>
#include <Menus.h>
#include <Processes.h>
#include <String.h>
#include <Resources.h>
#include <Packages.h>
#include <AppleEvents.h>
#include <Errors.h>
#include <GestaltEqu.h>
#include <Files.h>
#include <OSA.h>
#define typeStyledText 'STXT'
/////////////////////////////////////////////////////////////
//
// main()
// This is the interface for a coerce from descriptor
// coercion. Remember to declare it 'pascal'!!!
//
/////////////////////////////////////////////////////////////
pascal OSErr main( AEDesc *scriptDesc, DescType toType, long refcon, AEDesc *resultDesc)
{
OSErr theErr = noErr;
OSAError theOSAErr;
ComponentInstance gASComponent = 0;
OSAID resultingID = 0;
long modeFlags = 0;
/*
Open an instantiation of the
Generic Scripting Component
*/
gASComponent = OpenDefaultComponent(kOSAComponentType,
kOSAGenericScriptingComponentSubtype);
/* Checking errors here! */
if(((long)gASComponent == (long)badComponentInstance) ||
((long)gASComponent == (long)badComponentSelector)) {
theErr = invalidComponentID;
goto CLEANUP; // Yea! A valid use for a 'goto'!!
}
/* Load script in scriptDesc into a scriptID */
theOSAErr = OSALoad(gASComponent, scriptDesc,
modeFlags, &resultingID);
if(theOSAErr != noErr) {
theErr = theOSAErr;
goto CLEANUP;
}
/*
Now get the source. Since AppleScript can coerce any
of the various text forms to a text object (which is
what we claim to be returning, let's just return the
styled text and let AppleScript do the secondary
coercion for us.
*/
theOSAErr = OSAGetSource(gASComponent, resultingID,
typeStyledText, resultDesc);
if(theOSAErr != noErr) theErr = theOSAErr;
CLEANUP:;
if(resultingID != 0) OSADispose(gASComponent, resultingID);
if(gASComponent != 0) CloseComponent(gASComponent);
return theErr;
}
编译
要在 MPW 中编译,请使用以下编译命令:
/* 5*/
C -b "CoercescptToText.c" -d SystemSevenOrLater
Link -p -w -t osax -c ascr -rt osax=9999 -m MAIN -sg "CSDSscptctxt" -ra
"CSDSscptctxt"=resSysHeap,resPurgeable
"CoercescptToText.c.o"
"{CLibraries}"StdCLib.o
"{Libraries}"Runtime.o
"{Libraries}"Interface.o
-o "ScriptToTextCoercion"
要在 Symantec THINK C 中编译,请在项目中包含 CoercescptToText.c 和 MacTraps。如图所示设置项目类型。确保资源属性 Purgeable 和 System Heap 设置为 true。
编写一些测试脚本
下面是用于测试胁迫的示例脚本。
script Fred
property foo : 3
property bar : 3
on fumble()
beep 2
return "Whoops!"
end fumble
end script
Fred作为文本
执行此脚本将返回以下强制结果:
"property foo : 3
property bar : 3
on fumble()
beep 2
return \"Whoops!\"
end fumble"
这是正确的,因为这是与脚本对象相关联的脚本。脚本声明不作为脚本对象的一部分存储。
V. 提示、技巧和故障
通配符类和多个动词
苹果事件管理器允许在事件处理程序的类或 ID 中使用通配符,并允许在强制类型中使用 "from "和 "to "类型。当 AEM 无法找到直接匹配的事件或强制时,它会查看是否能找到部分匹配的表项,该表项的任一键或两个键都使用了通配符。例如:如果我在 AEM 调度表中为类 "OLIE "和id ****设置了一个条目,而我们收到了一个类 "OLIE "和id "TEXT "的事件,只要在调度表中没有匹配到id "TEXT "的地方,就会调用通配符条目。
我们可以在 OSAX 中使用这一功能,让一个 "osax "代码资源处理多个不同但相关的事件或强制。
对于事件,方法是使用 ****通配符类型定义类或 id。例如,我想编写一个共享大量相同代码的 CD 播放器 OSAX。我会这样定义 OSAX 的名称 AEVTCDPL****。在 OSAX 的 "aete "中,我会使用类 "CDPL "定义所需的动词,每个动词都有唯一的 id。在处理程序的 "osax "代码中,我将使用关键字 keyEventIDAttr 调用 AEGetAttributePtr 来提取 id。这将返回事件的 id。只需将 ID 套入相应的代码即可。
我们也可以用胁迫做同样的事情。要声明一个从通配符到文本的强制(将任何内容强制为文本),我们可以将 "osax "代码资源命名为 CSPT****TEXT。这里最大的不同是,from 类型是作为参数传递给强制处理程序的,因此我们可以直接使用它。
全局变量和 refCon
如果需要在 OSAX 中使用全局数据,或者需要在某个地方保留某个数据的句柄,可以使用事件或强制处理程序的 refCon 字段。AppleScript 安装处理程序时,会将 refCon 初始化为 NULL。这样,在首次调用时就可以测试是否为 NULL,以确定是否已初始化全局或数据。
要设置 refCon 字段,需要使用两个步骤。首先,对事件处理程序调用 AEGetEventHandler,对强制程序调用 AEGetCoercionHandler。在这两种情况下,都要将 isSystem 参数设置为 true。其次,使用 AEInstallEventHandler 或 AEInstallCoercionHandler。请确保使用从 AEGet 调用中返回的 procPtr,因为脚本添加机制使用一种特殊的加载方案来加载处理程序或强制机制,并使其资源在当前资源链中可用。
可移动模式和其他窗口
虽然可以编写具有可移动窗口的 OSAX,但并不推荐这样做。制作可移动模式对话框的常规方法需要在主机应用程序的事件循环中加入一个钩子,以确保更新事件得到适当处理。由于没有向应用程序传递更新事件的简单方法,因此只能在 OSAX 中使用模式窗口。
与动词、属性、枚举等的冲突
AppleScript 在任何时候都可能使用三种不同的术语缓存。AppleScript 有一个内部术语缓存,可在系统范围内使用。该术语缓存包含 Required Suite、Core Suite 和 AppleScript Suite。AppleScript 还为 "告诉 "程序块中的每个运行应用程序保留术语缓存。最后一个术语缓存是 OSAX 缓存。
有一个 "术语继承 "链会导致 OSAX 出现术语冲突。其路径如下:目标应用程序(如有)、AppleScript,最后是 OSAX。事实上,OSAX 在最后是产生冲突的最大原因。
术语冲突通常会在编译时出现。编译时冲突的一个例子是,当你使用偏移量 OSAX 的脚本作为可脚本文本编辑器的目标时。偏移量也被定义为许多文本类(如段落、单词和字符)的属性。下面的脚本演示了这个问题:
tell app "scriptable text editor"
offset of "1" in "123"
end tell
返回错误信息 Can’t get ‘1’ in ‘123’. Access not allowed.。
另一个例子是我们上面编写的 "播放电影 "OSAX。由于 "left "和 "right "已被定义为文本类的属性(向左对齐、向右对齐),编译 OSAX 时将会失败,因为会选中 "left "或 "right "的第一个声明后的第一个冒号,并显示错误信息 "预期为','或'}',但发现':'",供我们欣赏。我们以最丑陋的方式解决了这个问题,在四个参数中分别预置了 "qt"。
对于 OSAX 的作者来说,这是一个很难解决的问题。适用于 Macintosh 程序广泛使用的桌面隐喻的描述性动词数量有限。如果没有 AppleScript 的额外支持来解决这类冲突,OSAX 开发人员所能做的最好的事情就是尽可能多地使用可编写脚本的应用程序来测试他们的工作。
一个文件中包含多个 "osax "代码资源
您可以在一个脚本添加文件中放置多个 OSAX。将 OSAX 的 "家族 "放在一起可能比较合理。文件命令脚本附加文件就是一个例子。该文件包含四种不同的命令,它们都以某种方式对文件系统进行操作。
如果脚本添加文件中包含多个 OSAX,只需将其中一个命令替换为更新或功能更强的版本,就必须替换整个旧文件,或进行手术移除旧命令并用新命令取而代之。而且,由于同一文件中的 OSAX 集合的术语必须包含所有语法,更新其中一个集合的语法需要使用集合的 "aete"。这超出了大多数用户的技能范围,而且肯定不太友好。
错误、错误字符串和 AEM
苹果事件管理器会将你从事件处理程序中返回的错误添加为回复苹果事件的属性。这就是 keyErrorNumber。如果希望 AppleScript 在返回错误时显示描述性字符串,可将错误字符串添加到回复中,作为 keyErrorString 的属性。AppleScript 已为工具箱返回的大多数常见错误提供了错误字符串,因此不必为脚本添加程序返回的大多数错误添加错误字符串。
记录标签的类/属性定义
如果需要从命令或强制 OSAX 向 AppleScript 返回自定义记录类型,请创建一个自定义类,并为记录所需的标签定义属性。只需将创建的类 id 用作命令 OSAX 的返回数据类型即可。在 OSAX 中,将记录捆绑为 AERecord,每个参数都以自定义类中定义的属性为键。请参阅文件 "SAPlayMovie.r",了解命令 OSAX 中的示例。
AEInteractWithUser
无论何时从 OSAX 显示对话框或窗口,都应确保在窗口显示之前立即调用 AEInteractWithUser。如果 AEInteractWithUser 调用失败,请不要显示窗口。请返回 AEInteractWithUser 返回的错误信息。
'osiz'资源 - AppleScript 1.1 的新功能
从 AppleScript 1.1 开始,OSAX 可包含一个附加资源,为 AppleScript 提供有关 OSAX 文件中 OSAX 的更多信息。"osiz "包含两个供 OSAX 编写者使用的标志。
第一个标志指定 OSAX 机制是否打开即将调用的 OSAX 文件的resource fork。如果 OSAX 没有自有资源,则将此标志设为 dontOpenResourceFile。换句话说,如果 OSAX 不依赖于使用与 osax 代码资源位于同一文件中的资源,请指定 dontOpenResourceFile。另一方面,如果 OSAX 确实使用对话框或声音等自有资源,则应将此标记设置为 openResourceFile。如果 OSAX 文件中没有包含 "osiz "资源,默认设置为 openResourceFile。
第二个标志指定 OSAX 机制是否派发来自远程的事件。如果希望本地和远程机器都能访问 OSAX,请将此标志设置为 acceptRemoteEvents。如果 OSAX 有潜在危险,或可能需要长时间占用大量 CPU 循环,则应将该标记设为 dontAcceptRemoteEvents。如果 OSAX 文件中没有包含 "osiz "资源,则默认设置为 acceptRemoteEvents。
"osiz "资源定义如下:
/*6*/
type 'osiz' {
booleanopenResourceFile,
dontOpenResourceFile;
boolean acceptRemoteEvents,
dontAcceptRemoteEvents;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
booleanreserved;
};
VI. 事件处理程序和两种强制形式的原型
在'C'中
/*7*/
pascal OSErr MyEventHandler(AppleEvent *theEvent,
AppleEvent *theReply, long theRefCon)
pascal OSErr MyCoerceFromPtr(DescType fromType,
Ptr dataPtr, Size dataSize, DescType toType,
long refcon, AEDesc *resultDesc)
pascal OSErr MyCoerceFromDesc (AEDesc *fromDesc,
DescType toType, long refcon, AEDesc *resultDesc)
在 Pascal 中
/*8*/
FUNCTION MyEventHandler(theEvent, theReply: AppleEvent;
theRefCon: LONGINT):OSErr;
FUNCTION MyCoerceFromPtr(fromType: DescType;
dataPtr: Ptr; dataSize: Size;
toType: DescType; refcon: LONGINT;
VAR resultDesc: AEDesc):OSErr;
FUNCTION MyCoerceFromDesc(fromDesc: AEDesc;
toType: DescType; refcon: LONGINT;
VAR resultDesc: AEDesc):OSErr;