本Scriptは、Edama2さんから投稿していただいたものです。Cocoa AppleScript AppletでCustom Classを宣言して作られた、丸いウィンドウ(透明ウィンドウの上に丸いグラフィックを描画)を表示して、タイマー割り込みで時計を表示するAppleScriptです。
–> Download Editable and Executable Applet
AppleScriptのランタイム環境はいくつかあり、それぞれに「できること」と「できないこと」、「手軽さ」などが異なります。
(1)スクリプトエディタ上で記述、実行する環境
一番セキュリティ上の制約が緩く、できることの多い環境です。
(2)Script Debugger上で記述、実行する環境
Cocoaのイベントやオブジェクトのログ表示などができる環境です。
(3)Applet環境
AppleScriptの実行ファイルです。
(4)Script Menu環境
macOS標準装備の、メニューからAppleScriptを実行できる環境です。
さらに、Cocoa Scriptingの機能に着目してみると、見え方が変わります。
本Scriptを記述している「Cocoa AppleScript Applet」環境(上の図の赤い部分)は、スクリプトエディタ上で記述する通常のAppleScriptと、Xcode上で記述するCocoaアプリケーションの中間的な性格を持つものです。スクリプトエディタ上で直接は動かせず、アプレット形式で動作させることになりますが、スクリプトエディタ上で動かすよりも、よりCocoaの機能が利用できます。
Cocoa AppleScript Appletでは、アプリケーション(Applet)起動や終了の最中で発生するイベントを利用できますし、本ScriptのようにCocoaのCustom Classを宣言できます。これは、普通のスクリプトエディタ上で記述する(本Blogの大部分のScriptのような)Scriptではできない真似です。
→ Shane Stanleyからツッコミが入って、手の込んだ作業を行うとできるとかで(テンプレートそのままでは無理)、後で実際に試してみます
タイトルは「丸いウィンドウと時計の表示」 NSWindowのカスタムクラスを使ったタイトルバーなしのドラッグで移動できる丸いウィンドウとオマケに時計を表示したものです。 初心者受けしそうなやつです。 問題はそこではなく、 XcodeやCocoa applescript appletから実行するASOCだとカスタムクラスが作れるけど、 ノーマルのapplescriptから実行するASOCではカスタムクラスが作れないということです。 表現がややこしいですが...。 ノーマルのapplescriptからカスタムクラスを作ると、ただのスクリプトオブジェクトにしかなりません。 誰かうまい解決方法を知っている人がいたら教えてください。
ちょうど、こういう資料をまとめていたので補足説明に役立ってしまいました。スクリプトエディタ上で記述する通常のAppleScriptでもCustom Cocoa Classが宣言できると便利そうですが、どんなもんでしょうか?
Custom Classは便利なので使いたくなる一方、AppleScriptのインタプリタ上で実行するため、Objective-Cなどで書くのと同じような感覚で使うと「遅くて使えない」という話になると思いますが、このEdama2さんのサンプルぐらいの使いかたであれば、ちょうどいいんじゃないかというところです。
歴史的にみると、Cocoa-AppleScript Appletは、Xcode上で記述するCocoa-Applicationを簡略化してスクリプトエディタ上でCocoa Scriptingを手軽に使えるように手直しした「途中」の環境といえます。
Cocoa-AppleScript Appletは、GUIが手軽に作れるわけでもなく、スクリプトエディタ上で直接実行やログ表示ができるわけでもなありません。マスターしたところで最終到達点がCocoaアプリケーションほど高くなく、編集や習熟もしづらいことから「中途半端」「使えない」という評価になっていました(自分も使っていませんでした)。
その後、Cocoa-AppleScript Appletの機能要素をさらにダウンサイジングして、スクリプトエディタ上で手軽に記述・実行ができるように進化したのが現在・広くつかわれているCocoa Scripting環境です。
ただ、使いやすくなって広く使われるようになったはいいものの、「Xcodeを使うまでもないが、もうちょっとCocoaの機能が利用できないか?」という意見も出るようになり、Cocoa-AppleScript Appletを再評価してもいいんじゃないかと考えるようになってきてはいます。
ちなみに、本Cocoa-AppleScript AppletでCustom Classを宣言しているのと同じような書き方で、通常のCocoa Scriptingの環境で動かすような変更を加えたScriptもEdama2さんが試していますが、それは「動かない」ということで結論が出ています。
AppleScript名:CocoaAppletAppDelegate.scpt |
script CocoaAppletAppDelegate property parent : class "NSObject" property _clock_text_view : missing value –> 時計用の文字列 property _clock_timer : missing value –> 時計用のNSTimer on applicationWillFinishLaunching:aNotification my raizeWindow() end applicationWillFinishLaunching: on applicationShouldTerminate:sender my _clock_timer’s invalidate() return current application’s NSTerminateNow end applicationShouldTerminate: # ウィンドウを作成 on raizeWindow() # 時計用の文字を作成 tell current application’s NSTextView’s alloc() tell initWithFrame_(current application’s NSMakeRect(35, 120, 300, 40)) setRichText_(true) useAllLigatures_(true) setTextColor_(current application’s NSColor’s whiteColor()) setFont_(current application’s NSFont’s fontWithName:"Arial-Black" |size|:48) setBackgroundColor_(current application’s NSColor’s colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.0) setAlphaValue_(1.0) setEditable_(false) –setString_("00:00:00") set my _clock_text_view to it end tell end tell # 時計を更新するNSTimerを作成 set my _clock_timer to current application’s NSTimer’s scheduledTimerWithTimeInterval:1 target:me selector:"idleHandler:" userInfo:(missing value) repeats:true # 丸いViewを作成 set aFrame to current application’s NSMakeRect(0, 0, 300, 300) tell current application’s RoundView’s alloc() tell initWithFrame_(aFrame) setNeedsDisplay_(true) setSubviews_({my _clock_text_view}) set customView to it end tell end tell #スクリーンのサイズを調べる set aScreen to current application’s NSScreen’s mainScreen() # Window set aBacking to current application’s NSWindowStyleMaskBorderless –set aBacking to current application’s NSBorderlessWindowMask set aDefer to current application’s NSBackingStoreBuffered tell current application’s CustomWindow’s alloc() tell initWithContentRect_styleMask_backing_defer_screen_(aFrame, aBacking, aDefer, false, aScreen) –setTitle_(uniqueName) –>タイトル setBackgroundColor_(current application’s NSColor’s clearColor()) –>背景色 setContentView_(customView) –>内容ビューのセット setDelegate_(me) –>デリゲート setDisplaysWhenScreenProfileChanges_(true) –>スクリーンプロファイルが変更されたときウインドウの内容をアップデートするか setHasShadow_(true) –>ウインドウに影があるか setIgnoresMouseEvents_(false) –>マウスイベントを無視するか –setLevel_((current application’s NSScreenSaverWindowLevel) + 1) –>ウインドウの前後関係の位置 setOpaque_(false) –>ウインドウを不透明にするか setReleasedWhenClosed_(true) –>閉じたときにメモリを解放するか # |center|() makeKeyAndOrderFront_(me) –>キーウインドウにして前面に持ってくる –setFrame_display_(aFrame, true) –>表示 end tell end tell end raizeWindow #タイマー割り込み on idleHandler:aSender set mesStr to time string of (current date) (my _clock_text_view)’s setString:mesStr end idleHandler: end script script CustomWindow property parent : class "NSWindow" property canBecomeKeyWindow : true property _initial_location : missing value on mouseDown:theEvent set my _initial_location to theEvent’s locationInWindow() end mouseDown: on mouseDragged:theEvent –set res to display dialog "mouseDragged" buttons {"OK"} default button "OK" try set screenVisibleFrame to current application’s NSScreen’s mainScreen()’s visibleFrame() set screenVisibleFrame to my myHandler(screenVisibleFrame) set windowFrame to my frame() set windowFrame to my myHandler(windowFrame) set newOrigin to windowFrame’s origin set currentLocation to theEvent’s locationInWindow() set newOrigin’s x to (newOrigin’s x) + ((currentLocation’s x) – (_initial_location’s x)) set newOrigin’s y to (newOrigin’s y) + ((currentLocation’s y) – (_initial_location’s y)) set tmpY to ((newOrigin’s y) + (windowFrame’s |size|’s height)) set screenY to (screenVisibleFrame’s origin’s y) + (screenVisibleFrame’s |size|’s height) if tmpY > screenY then set newOrigin’s y to (screenVisibleFrame’s origin’s y) + ((screenVisibleFrame’s |size|’s height) – (windowFrame’s |size|’s height)) end if my setFrameOrigin:newOrigin on error error_message number error_number set error_text to "Error: " & error_number & ". " & error_message display dialog error_text buttons {"OK"} default button 1 return error_text end try end mouseDragged: on myHandler(aFrame) if class of aFrame is list then set {{theX, theY}, {theWidth, theHeight}} to aFrame set aFrame to {origin:{x:theX, y:theY}, |size|:{width:theWidth, height:theHeight}} –set aFrame to current application’s NSMakeRect(theX, theY, theWidth, theHeight) end if return aFrame end myHandler end script script RoundView property parent : class "NSView" on drawRect:rect set aFrame to my frame() set myColor to current application’s NSColor’s redColor() tell current application’s NSBezierPath tell bezierPathWithOvalInRect_(aFrame) myColor’s |set|() fill() end tell end tell end drawRect: end script |