WebP、WebM、VP9などのデータフォーマットに対応したSafari v14がmacOS 10.14.x、10.15,x向けに配信されました。macOS標準のソフトウェアアップデート経由でアップデートできます。

AppleScript用語辞書(sdef)を前バージョンの13.1.3と比較してみたところ、とくに差異はみあたりません。

相棒のTakamori氏から指摘されてはじめて気づいた事実、Piyomaru Softwareにはロゴマークが存在していませんでした!
ながらく、マスコットキャラクターの「Piyo Wolf」をロゴっぽく使ってきましたが、アイコン的に利用するには形状が複雑すぎです。
そこで、夜分に音声チャットであーだこーだ意見を交換しつつ、それらしきものを作ってみました。
自分たちでは割と気に入っています。とくに、TwitterやFacebook、YouTubeなど各種Webサービスでは個人の識別用アイコンを丸く切り抜いて表示するようになっており、その切り抜き表示時の相性がいいと思います。
印刷用のグレースケール版も存在していますが、カラー版をそのままグレースケール化しても似たような濃淡になるようにカラーを調整しています(そこが一番のこだわりどころ)。

「P」を図案化したロゴマークでは、Parking表示やPanic Inc.のロゴマークがあり、これら既存の強いロゴマークと差別化することをテーマに組み立ててみました。

▲ボツ案1 納得感がなく、これじゃない

▲ボツ案2 Google風。誰でも一度は試してみる路線

▲ボツ案3 頭が悪くない感じを狙ってみた。なごまない(脳みそへの認知負荷が高い)のでNG
例によってAppleScriptで開発したアプリケーション「White Pages」のMac App Storeでの販売を開始しました。macOS 10.13以降に対応しています。
![]()
PDFの空白ページを削除・出力するアプリケーションです。空白ページの検出のため、全ページを画像にレンダリングして、空白判定。空白判定には速度と精度で定評のある空白画像検出ルーチンを使用しています。
決して、ページごとに文字の有無を判断するだけのソフト(Fredrik Method)ではありません。それだと、グラフィックだけのページを「空白」と判定してしまいますので。
アイコンは、macOS 11.0, Big Surに合わせた形状。手の色を赤くしているのは、何らかの特定の人種を想起させるような塗り色を避けたためです。



例によってAppleScriptで開発したアプリケーション「Table Dripper」のMac App Storeでの販売を開始しました。macOS 10.14以降に対応しています(動作原理的にmacOS 10.13対応は無理だった、、、)。
![]()
Safari/Google Chrome/Microsoft Edgeで表示中のWebサイトのTableデータをCSVに変換してダウンロードしてNumbers.appでオープンするアプリケーションです。

Mac App Storeの審査に10日かかって焦りました(機能のコンパクトさに反比例して審査期間が長い。たぶん、開発期間よりも審査期間の方が長い)。
すでに、他のChromiumベースのWebブラウザ「Brave」と「Vivaldi」への対応が済んでおり、次のアップデートでこれらにも対応します。AppleScript用語辞書(sdef)を持っているWebブラウザであっても、do javascript的なコマンドが実装されていないと、本アプリケーションでは対応できません(FireFox、Opera)。まして、AppleScriptにまったく対応していないWebブラウザでは、対応のしようがありません(Sleipnirなど)。
4つのアプリケーションをコントロールするアプリケーションをMac App Storeに申請したのははじめてです。
そろそろ、Script EditorとScript DebuggerをコントロールするアプリケーションをMac App Storeに申請することに。これも、審査が荒れる(期間が長くなる)ことが見込まれます。
Keynoteの最前面の書類で、現在表示中のスライドと次のスライドの間で、矩形座標が重なっている「画像」と「グループアイテム」(複数のオブジェクトをグループ化したアイテム)を検出して、現在のスライドの開始位置に場所をそろえるAppleScriptです。
# 最初掲載したもの(v1)は、動作しているものの処理内容に誤りがあったので修正しました(v2)

連続したスライド上に画面図を配置して、その画面図の位置をそろえるためのものです。何回か書いたような気がします。見た目より書くのが大変ではないので、ついつい書いてしまう処理です。
単にそれぞれの対象候補のオブジェクトの矩形開始座標と幅&高さをリスト化して、それをNSRectに変換して重ね合わせが発生していないかをCocoaの機能で判定しているだけです。この、一番めんどくさい部分をCocoaに丸投げしているので、おおよそ知性というものを感じさせないレベルのScriptです。
画像やグループアイテム 以外の表オブジェクト同士の重なりあいの検出を行うものに書き換えるのは簡単ですが、複数のオブジェクトの重なり合いが発生している場合には対処できません。
| AppleScript名:現在のスライドと次のスライドでオーバーラップしている画像とグループを検出して位置をそろえる v2.scpt |
| — – Created by: Takaaki Naganoya – Created on: 2020/09/10 — – Copyright © 2020 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" use scripting additions use framework "Foundation" tell application "Keynote" set dCount to count every document if dCount = 0 then return –ドキュメントがオープンしていないと処理終了 tell front document set allNum to count every slide if allNum < 2 then return –slideの枚数が少なかった場合に処理終了 set curSlide to current slide if allNum = curSlide then return –current slideが末尾だった場合処理終了 set sNum to slide number of curSlide set nextSlide to slide (sNum + 1) –最初のページ(スライド)からオブジェクトを収集 tell current slide set curObj1 to every image set curObj2 to every group set curObjList to curObj1 & curObj2 if curObjList = {} then return end tell –次のページ(スライド)からオブジェクトを収集 tell nextSlide set nextObj1 to every image set nextObj2 to every group set nextObjList to nextObj1 & nextObj2 if nextObjList = {} then return end tell set nexObjRes to calcOverlappedObj(curObjList, nextObjList) of me repeat with i in nexObjRes copy i to {origID, targID} set aOrigObj to contents of item origID of curObjList set aTargObj to contents of item origID of nextObjList set origPos to position of aOrigObj set position of aTargObj to origPos end repeat set current slide to slide sNum end tell end tell –与えられた2つのリストに入っているKeynoteオブジェクトの矩形座標が重なっているものをIDペアで出力する –同時に複数のオブジェクトが重なっていないことが前提 on calcOverlappedObj(objList1, objList2) tell application "Keynote" –最初のページのオブジェクトからオブジェクトIDとNSRectからなるリストを作成 set objRecList1 to {} set aCount to 1 repeat with i in objList1 if contents of i is not equal to {} then set {x1, y1} to position of i set aRect to {origin:{x:x1, y:y1}, |size|:{|width|:(width of i), |height|:(height of i)}} set the end of objRecList1 to {objID:aCount, rect:aRect} end if set aCount to aCount + 1 end repeat –次のページのオブジェクトからオブジェクトIDとNSRectからなるリストを作成 set objRecList2 to {} set aCount to 1 repeat with i in objList2 if contents of i is not equal to {} then set {x1, y1} to position of i set aRect to {origin:{x:x1, y:y1}, |size|:{|width|:(width of i), |height|:(height of i)}} set the end of objRecList2 to {objID:aCount, rect:aRect} end if set aCount to aCount + 1 end repeat –最初のページのオブジェクトと次のページのオブジェクトで矩形エリアが重なっているオブジェクトを抽出してそれぞれのIDをペアにしたリストを作成 set matchList to {} repeat with i in objRecList1 set origRect to rect of i set origID to objID of i repeat with ii in objRecList2 set targRect to rect of ii set targID to objID of ii set tRes to detectRectanglesCollision(origRect, targRect) of me if tRes = true then set the end of matchList to {origID, targID} end if end repeat end repeat return matchList end tell end calcOverlappedObj –NSRect同士の衝突判定 on detectRectanglesCollision(aRect, bRect) set a1Res to (current application’s NSIntersectionRect(aRect, bRect)) as {record, list} set tmpClass to class of a1Res if tmpClass = record then –macOS 10.10, 10.11, 10.12 return not (a1Res = {origin:{x:0.0, y:0.0}, |size|:{width:0.0, height:0.0}}) else if tmpClass = list then –macOS 10.13 or later return not (a1Res = {{0.0, 0.0}, {0.0, 0.0}}) end if end detectRectanglesCollision |
指定座標がどのディスプレイ上に表示されているかをIDで返すAppleScriptです。
試しに、Finderの最前面のウィンドウの始点座標を取得して、どのディスプレイ上に表示されているかを地道にループで計算しています。
0はメニューを配置しているメインディスプレイで、その後のIDについてはNSScreen’s screens()の出現順に割り振られています。
MacBook Pro 10,1にディスプレイ3枚接続して(通常状態)、さらにUSB経由でiPadをDuet Displayで外部ディスプレイ化。4枚のディスプレイで動作確認しています。
本来、どのディスプレイ上にあるかをきっちり判定できるはずなのですが、最後のIDのものだけうまく判定できていないので例外処理しています。
このあたりの動作内容が怪しかったので、ながらく放置状態になっていましたが、実際にディスプレイを4枚つないで動作確認してみたら大丈夫そうだったので掲載することに。
| AppleScript名:指定Finderウィンドウがどのディスプレイ上に表示されているかをIDで返す(0はじまり).scptd |
| — Created 2015-11-01 by Takaaki Naganoya — 2015 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" use framework "AppKit" tell application "Finder" if (count every window) = 0 then return tell front window set {xPos, yPos} to position log {xPos, yPos} end tell end tell set dispID to getPointInWhichScreen(xPos, yPos) of me –指定座標がどのディスプレイ上に表示されているかをIDで返す(0はじまり。0はメインディスプレイ) on getPointInWhichScreen(xPos, yPos) set dList to getScreensResol() of me set aPoint to current application’s NSMakePoint(xPos, yPos) set dCount to 0 repeat with i in dList set dRes to current application’s NSPointInRect(aPoint, i) as boolean if dRes = true then return dCount end if set dCount to dCount + 1 end repeat return dCount – 1 –ちょっと怪しいが、動作している様子 end getPointInWhichScreen on getScreensResol() set dispList to (current application’s NSScreen’s screens()) as list set dList to {} repeat with i in dispList set framePref to i’s visibleFrame() set {xPos, yPos} to first item of framePref set theInfo to (i’s deviceDescription()’s NSDeviceSize) as record set a1Rect to {origin:{x:xPos, y:yPos}, |size|:theInfo} set the end of dList to a1Rect end repeat return dList end getScreensResol |
指定のPDFをチェックし、オープンするのにパスワードが設定されているとか、コピーが禁止されていないかといったパーミッションを調査するAppleScriptです。
| API | Lebel in this script | Desc |
| (no api) | openPermission | document allows opening |
| allowsCopying | copyPermission | document allows copying of content to the Pasteboard |
| allowsPrinting | printPermission | document allows printing |
| allowsCommenting | commentPermission | document allows to create or modify document annotations, including form field entries |
| allowsContentAccessibility | contentAccessPermission | document allows to extract content from the document |
| allowsDocumentAssembly | assemblyPermission | document allows manage a document by inserting, deleting, and rotating pages |
| allowsDocumentChanges | docchangePermission | document allows modify the document contents except for document attributes |
| allowsFormFieldEntry | formPermission | document allows modify form field entries even if you can’t edit document annotations |
PDFのオープン自体にパスワードロックがかかっている状態を検出するAPIがとくになかったので、いろいろ試行錯誤してOpenをロックしてある状態を検出してみました。
検出は、パスワード未指定でUnlockを試みるというもので、実際にパスワードを設定したPDFを相手に試行錯誤して求めてみました。
このUnlock操作に対してfalseが返ってくることでPDFオープンに対してパスワードが設定されているものと判断しています。
| AppleScript名:PDFにパスワードが設定されている場合には、そのパーミッション情報を取得する |
| — Created 2015-11-02 13:48:32 +0900 by Takaaki Naganoya — 2015 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" use framework "QuartzCore" set aPath to (choose file of type {"com.adobe.pdf"} with prompt "Select PDF to check") set aRes to my retProtectedPDFPermissions(aPath) –> missing value (パスワードは設定されていない) –> {openPermission:false, copyPermission:true, printPermission:true, commentPermission:true, contentAccessPermission:true, assemblyPermission:true, docchangePermission:true, formPermission:true}–オープンするためにはパスワードが必要(openPermission) –指定のPDFにパスワードが設定されているかどうかをチェック on retProtectedPDFPermissions(aPath) set aURL to current application’s |NSURL|’s fileURLWithPath:(POSIX path of aPath) set aPDFdoc to current application’s PDFDocument’s alloc()’s initWithURL:aURL set anEncF to aPDFdoc’s isEncrypted() if anEncF = false then return missing value set passLocked to aPDFdoc’s unlockWithPassword:"" set cpPerm to aPDFdoc’s allowsCopying() as boolean set prPerm to aPDFdoc’s allowsPrinting() as boolean set cmPerm to aPDFdoc’s allowsCommenting() as boolean set caPerm to aPDFdoc’s allowsContentAccessibility() as boolean set daPerm to aPDFdoc’s allowsDocumentAssembly() as boolean set dcPerm to aPDFdoc’s allowsDocumentChanges() as boolean set ffPerm to aPDFdoc’s allowsFormFieldEntry() as boolean return {openPermission:passLocked, copyPermission:cpPerm, printPermission:prPerm, commentPermission:cmPerm, contentAccessPermission:caPerm, assemblyPermission:daPerm, docchangePermission:dcPerm, formPermission:ffPerm} end retProtectedPDFPermissions |
Mac App Storeに新アプリケーション「Rename by contents」を提出しました。Now On Saleです。
論文PDFのリネーム用ツールであり、「PDFの1ページ目の一番大きな文字の文をファイル名として採用する」というものです。macOS 10.13以降用で、Intel 64バイナリでビルドしてあります。全部AppleScriptで書いてあるので、ARM 64e向けにビルドし直すのもとくに問題ありません。
このアプリケーションほか、小物アプリケーションをMac App Storeに提出する方向で作業をすすめていますが、今回の審査は一番手間取りました。割と日常的に使っている小物アプリケーションは、小物アプリケーションらしくいろいろUIの味付けを変えているのですが、そういうアレンジしている部分を「HIG(Human Interface Guideline)に反する」という理由でリジェクトされました。
たとえば、こうした小物アプリケーションの場合に、メインウィンドウを閉じられたくない(処理がめんどくさいから)という事情があり、メインウィンドウのクローズボタンを無効にしていることがよくあります。
ウィンドウさえクローズされなければ問題は発生しないので、クローズ機能自体にフタをしてしまえばよいという話です。ただ、HIGの原則からいえば「クローズボタンがないのはダメ」なので、Mac App Store版のアプリケーションではこの「クローズボタンの無効化」は禁じ手ということになります。
クローズボタンを有効化して、メインウィンドウが閉じられたらアプリケーションを自動終了させる処理をコピペで書き足して、最終的にリジェクトを回避できました。
ちなみに、これとほぼ同時に作りはじめた別のアプリケーション「Foreign Love」(指定アプリケーションを他の言語環境で起動)は、現行のSandboxの仕様上、Sandbox環境では機能を発揮できないことが判明してオクラ入りしました。
ほかにもこまごまとしたリジェクト理由でAppleのレビュー担当(名前は知らない。毎回違うはず)とやりとりしていましたが、正直なところ小さいアプリケーションをMac App Storeに通すほうが大変な気がしました。ただ、この手の申請作業に慣れてきたような気がします。
あと、XcodeのOSACompile ビルドオプション、「Save as Execute-only」でデフォルトだとビルド時に「ソースを開示したままビルド」になっているので、AppleScriptのコードを全表示させたビルドをさせようとするのに何回も煮え湯を飲まされました。とても怖いdeath。
emada2さんからの投稿です。Collection Viewの利用については、Kamenokoの開発時に導入検討したことがありますが、Objective-CのサンプルコードをAppleScriptに翻訳しきれなくて中途半端に放置していました。
まとまった形でCollection Viewのサンプルコードが出てきたのは、おそらくこれが初めてだと思います。ありがとうございます。
Collection Viewを箱庭ダイアログに乗せることができました。 でも残念なのは、itemPrototypeは10.14でDeprecatedになっているので将来的に長く使えないでしょう その他のやり方を探してappleのサンプルコードをみたら、swift 3で書かれていているので10.14でビルドできませんでした。 なのでまだまだCollection Viewを使いこなすのは難しそうです😢
@property (nullable, strong) NSCollectionViewItem *itemPrototype API_DEPRECATED("Use -registerNib:forItemWithIdentifier: or -registerClass:forItemWithIdentifier: instead.", macos(10.5,10.14));
| AppleScript名:アラートダイアログ上にCollection Viewを表示 |
| use AppleScript version "2.4" — Yosemite (10.10) or later use framework "AppKit" use framework "AppleScriptObjC" use framework "CoreFoundation" use framework "Foundation" use scripting additions on run my testRun() end run on testRun() set aMainMes to "アプリケーションの選択" set aSubMes to "適切なものを以下からえらんでください" #ダイアログ内のビューサイズを指定 set aHeight to 384 set aWidth to 512 + 12 –> スクロールバーの分 #アプリケーションフォルダ内のアプリケーションを取得 set aFolder to path to applications folder set aPath to aFolder’s POSIX path set aURL to current application’s NSURL’s fileURLWithPath:aPath # 指定フォルダの直下のファイルを取得 set filePaths to current application’s NSFileManager’s defaultManager’s ¬ contentsOfDirectoryAtURL:aURL ¬ includingPropertiesForKeys:{current application’s NSURLNameKey} ¬ options:(current application’s NSDirectoryEnumerationSkipsHiddenFiles) ¬ |error|:(missing value) set filePaths to filePaths as list set thisFileType to "com.apple.application-bundle" set dataSourceList to {} repeat with anItem in filePaths set aPath to anItem’s contents’s POSIX path set aURL to (current application’s NSURL’s fileURLWithPath:aPath) set {aResult, aUTI, aError} to (aURL’s getResourceValue:(reference) ¬ forKey:(current application’s NSURLTypeIdentifierKey) ¬ |error|:(reference)) if (aUTI as text) is thisFileType then set iconImage to (current application’s NSWorkspace’s sharedWorkspace’s iconForFile:aPath) set dict to {fileURL:aURL, icon:iconImage} set dataSourceList’s end to dict end if end repeat set execButtonTitle to "OK😄" set dateObj to my chooseItemByCollectionView:aMainMes subMes:aSubMes myWidth:aWidth myHeight:aHeight myDataSource:dataSourceList okTitle:execButtonTitle end testRun on chooseItemByCollectionView:(aMainMes as text) subMes:(aSubMes as text) myWidth:(aWidth as integer) myHeight:(aHeight as integer) myDataSource:(dataSourceList as list) okTitle:(execButtonTitle as text) # Script Bundle内のResourcesフォルダを求める set resourcePath to POSIX path of (path to me) & "Contents/Resources/" set theBundle to current application’s NSBundle’s bundleWithPath:resourcePath theBundle’s loadAppleScriptObjectiveCScripts() # subClasses.scptを呼び出す set DialogCore to current application’s AppDelegate’s new() set fRes to DialogCore’s chooseItemByCollectionView:aMainMes subMes:aSubMes myWidth:aWidth myHeight:aHeight myDataSource:dataSourceList okTitle:execButtonTitle if fRes is missing value then error number -128 return fRes as list end chooseItemByCollectionView:subMes:myWidth:myHeight:myDataSource:okTitle: |
| AppleScript名:subClasses |
| script AppDelegate property parent : class "NSObject" — IBOutlets property theWindow : missing value on applicationShouldTerminate:sender return current application’s NSTerminateNow end applicationShouldTerminate: on applicationShouldTerminateAfterLastWindowClosed:sender return true end applicationShouldTerminateAfterLastWindowClosed: —————— on applicationWillFinishLaunching:aNotification log my testRun() end applicationWillFinishLaunching: on testRun() set aMainMes to "アプリケーションの選択" set aSubMes to "適切なものを以下からえらんでください" #ダイアログ内のビューサイズを指定 set aHeight to 384 set aWidth to 512 + 12 –> スクロールバーの分 #アプリケーションフォルダ内のアプリケーションを取得 set aFolder to path to applications folder set aPath to aFolder’s POSIX path set aURL to current application’s NSURL’s fileURLWithPath:aPath # 指定フォルダの直下のファイルを取得 set filePaths to current application’s NSFileManager’s defaultManager’s ¬ contentsOfDirectoryAtURL:aURL ¬ includingPropertiesForKeys:{current application’s NSURLNameKey} ¬ options:(current application’s NSDirectoryEnumerationSkipsHiddenFiles) ¬ |error|:(missing value) set filePaths to filePaths as list set thisFileType to "com.apple.application-bundle" set dataSourceList to {} repeat with anItem in filePaths set aPath to anItem’s contents’s POSIX path set aURL to (current application’s NSURL’s fileURLWithPath:aPath) set {aResult, aUTI, aError} to (aURL’s getResourceValue:(reference) ¬ forKey:(current application’s NSURLTypeIdentifierKey) ¬ |error|:(reference)) if (aUTI as text) is thisFileType then set iconImage to (current application’s NSWorkspace’s sharedWorkspace’s iconForFile:aPath) set dict to {fileURL:aURL, icon:iconImage} set dataSourceList’s end to dict end if end repeat set execButtonTitle to "OK😄" set dateObj to my chooseItemByCollectionView:aMainMes subMes:aSubMes myWidth:aWidth myHeight:aHeight myDataSource:dataSourceList okTitle:execButtonTitle end testRun # アラートダイアログでCollectionViewを表示 property _retrieve_data : missing value on chooseItemByCollectionView:(aMainMes as text) subMes:(aSubMes as text) myWidth:(aWidth as integer) myHeight:(aHeight as integer) myDataSource:(dataSourceList as list) okTitle:(execButtonTitle as text) set paramObj to {myMessage:aMainMes} set paramObj to paramObj & {mySubMessage:aSubMes} set paramObj to paramObj & {myDataSource:dataSourceList} set paramObj to paramObj & {myWidth:aWidth} set paramObj to paramObj & {myHeight:aHeight} set paramObj to paramObj & {myOKTitile:execButtonTitle} my performSelectorOnMainThread:"raizeAlert:" withObject:paramObj waitUntilDone:true if (my _retrieve_data) is missing value then error number -128 return (my _retrieve_data) end chooseItemByCollectionView:subMes:myWidth:myHeight:myDataSource:okTitle: ## retrieve date on raizeAlert:paramObj set mesText to paramObj’s myMessage as text set infoText to paramObj’s mySubMessage as text set dataSourceList to paramObj’s myDataSource as list set viewWidth to paramObj’s myWidth as integer set viewHeight to paramObj’s myHeight as integer set okButton to paramObj’s myOKTitile as text # set up view set {thisView, collectionView} to my makeContentView(dataSourceList, viewWidth, viewHeight) ### set up alert tell current application’s NSAlert’s new() addButtonWithTitle_(okButton) addButtonWithTitle_("Cancel") setAccessoryView_(thisView) –setIcon_(aImage) setInformativeText_(infoText) setMessageText_(mesText) tell |window|() setInitialFirstResponder_(thisView) end tell #### show alert in modal loop if runModal() is (current application’s NSAlertSecondButtonReturn) then return end tell ### retrieve data set aIndexSet to collectionView’s selectionIndexes() set theArray to current application’s NSArrayController’s alloc()’s initWithContent:(collectionView’s content()) set chooseItems to ((theArray)’s arrangedObjects()’s objectsAtIndexes:aIndexSet) as list # reset set _retrieve_data to {} repeat with anItem in chooseItems set (my _retrieve_data)’s end to anItem’s contents end repeat return my _retrieve_data end raizeAlert: # set up view on makeContentView(dataSourceList, viewWidth, viewHeight) set thisRect to current application’s NSMakeRect(0, 0, viewWidth, viewHeight) tell current application’s NSCollectionView’s alloc tell initWithFrame_(thisRect) setItemPrototype_(current application’s YKZCollectionViewItem’s new()) setAllowsEmptySelection_(true) setAllowsMultipleSelection_(true) setContent_(dataSourceList) setSelectable_(true) set collectionView to it end tell end tell # Viewを作成 tell current application’s NSScrollView’s alloc() tell initWithFrame_(thisRect) setBorderType_(current application’s NSBezelBorder) setDocumentView_(collectionView) setHasHorizontalScroller_(true) setHasVerticalScroller_(true) set baseView to it end tell end tell return {baseView, collectionView} end makeContentView end script #MARK: – script YKZView property parent : class "NSView" property imageView : missing value property selected : false property viewItemHeight : 128 property viewItemWidth : 128 on initWithFrame:rect set myRect to current application’s NSMakeRect(0, 0, viewItemWidth, viewItemHeight) continue initWithFrame:myRect set aHeight to viewItemHeight – 10 set aWidth to viewItemWidth – 10 set newRect to current application’s NSMakeRect(5, 5, aWidth, aHeight) tell current application’s NSImageView’s alloc() tell initWithFrame_(newRect) setImageScaling_(current application’s NSImageScaleProportionallyUpOrDown) setImageAlignment_(current application’s NSImageAlignCenter) set my imageView to it end tell end tell my addSubview:(my imageView) return me end initWithFrame: on drawRect:rect if my selected then current application’s NSColor’s redColor()’s |set|() else current application’s NSColor’s controlBackgroundColor()’s |set|() end if current application’s NSRectFill(rect) continue drawRect:rect end drawRect: end script #MARK: – script YKZCollectionViewItem property parent : class "NSCollectionViewItem" on loadView() my setView:(current application’s YKZView’s alloc()’s initWithFrame:(current application’s NSZeroRect)) end loadView on setRepresentedObject:representedObject –continue setRepresentedObject:representedObject if representedObject is missing value then return (my view())’s imageView’s setImage:(representedObject’s icon) end setRepresentedObject: on setSelected:isSelected set (my view())’s selected to isSelected (my view())’s setNeedsDisplay:true continue setSelected:isSelected end setSelected: end script |
指定のアプリケーションファイルが32bitバイナリかどうかを判定するAppleScriptの改訂版です。
本Scriptシリーズは対象のアプリケーションが起動している状態で対象CPUアーキテクチャを判定するものではなく、起動していない状態で判定するものです。
PPC 32 / PPC 64 / Intel 32 / Intel 64 / ARM 64の各アーキテクチャの判定を行います。FAT Binaryの場合には複数のアーキテクチャが返ってきます。
▲本Scriptの応用例
| AppleScript名:指定アプリケーションのバイナリアーキテクチャ判定 v3.scptd |
| — – Created by: Takaaki Naganoya – Created on: 2020/08/18 — – Copyright © 2020 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use scripting additions set anApp to choose file of type {"com.apple.application-bundle"} default location (path to applications folder) –set anApp to choose file default location (path to applications folder) set app32 to chk32Binary(anApp) of me –> {"PPC 32", "Intel x32"} on chk32Binary(anAlias) set aPOSIX to POSIX path of anAlias set aURL to current application’s |NSURL|’s fileURLWithPath:(aPOSIX) set aBundle to current application’s NSBundle’s bundleWithURL:aURL set F1 to (aBundle = missing value) as boolean if F1 is not equal to false then return calcIllegular(aPOSIX) of me set aDict to aBundle’s executableArchitectures() set F2 to (aDict = missing value) as boolean if F2 is equal to true then return calcIllegular(aPOSIX) of me set aList to (aDict’s valueForKeyPath:"stringValue") as list set arList to {} repeat with i in aList set j to contents of i if j = "7" then –NSBundleExecutableArchitectureI386 set the end of arList to "Intel 32" else if j = "18" then –NSBundleExecutableArchitecturePPC set the end of arList to "PPC 32" else if j = "16777223" then –NSBundleExecutableArchitectureX86_64 set the end of arList to "Intel 64" else if j = "16777234" then –NSBundleExecutableArchitecturePPC64 set the end of arList to "PPC 64" else if j = "16777228" then –NSBundleExecutableArchitectureARM64 set the end of arList to "ARM 64" end if end repeat return arList end chk32Binary on calcIllegular(aPOSIX) –Old Style Bundle for PPC set sRes to do shell script "file " & quoted form of aPOSIX if sRes contains "Mach-O executable ppc" then return {"PPC 32"} –NSBundleExecutableArchitecturePPC return missing value end calcIllegular |
指定のアプリケーションファイルが32bitバイナリかどうかを判定するAppleScriptです。
実行ファイルのパスを求めて、fileコマンドで情報を取得します。本Scriptでは32bitバイナリであるかどうかの判定のみ行なっていますが、CPUアーキテクチャに着目してPPC/Intel 32/Intel 64/ARMの判定を行えるように作り込めます。
また、アプリケーションでありながらもバンドル形式になっていない実行ファイル(古い時代のファイルだったり、サードパーティの開発環境だったり)も存在しているため、NSBundleを求めたときにmissing valueが返ってくることもあります。
1ファイルだけ処理できてもあまり意味がないので、指定フォルダ以下のすべてのアプリケーションファイルをリストアップして、ループで処理するようなことをやることになるでしょう。
--PPC 32bit /Applications/okaeri_up/v2.0b/Okaeri.app/Contents/MacOS/Okaeri: Mach-O executable ppc --PPC/Intel 32bit FAT Binary /Applications/Adobe Illustrator CS3/Adobe Illustrator.app//Contents/MacOS/Adobe Illustrator: Mach-O universal binary with 2 architectures: [i386:Mach-O executable i386] [ppc] /Applications/Adobe Illustrator CS3/Adobe Illustrator.app//Contents/MacOS/Adobe Illustrator (for architecture i386): Mach-O executable i386 /Applications/Adobe Illustrator CS3/Adobe Illustrator.app//Contents/MacOS/Adobe Illustrator (for architecture ppc): Mach-O executable ppc --Intel 32 /Applications/Adobe Illustrator CS5/Adobe Illustrator.app//Contents/MacOS/Adobe Illustrator: Mach-O executable i386 --Intel 32/64bit FAT Binary /Applications/System Preferences.app//Contents/MacOS/System Preferences: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [i386:Mach-O executable i386] /Applications/System Preferences.app//Contents/MacOS/System Preferences (for architecture x86_64): Mach-O 64-bit executable x86_64 /Applications/System Preferences.app//Contents/MacOS/System Preferences (for architecture i386): Mach-O executable i386 --Intel 64 /Applications/Adobe Illustrator 2020/Adobe Illustrator.app//Contents/MacOS/Adobe Illustrator: Mach-O 64-bit executable x86_64 --Intel/PPC 64bit FAT Binary /Applications/Geekbench 2.1/Geekbench (64-bit).app/Contents/MacOS/Geekbench: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [ppc64] /Applications/Geekbench 2.1/Geekbench (64-bit).app/Contents/MacOS/Geekbench (for architecture x86_64): Mach-O 64-bit executable x86_64 /Applications/Geekbench 2.1/Geekbench (64-bit).app/Contents/MacOS/Geekbench (for architecture ppc64): Mach-O executable ppc64 --Intel 64/ARM 64 FAT Binary /System/Applications/Utilities/Script Editor.app/Contents/MacOS/Script Editor: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64e:Mach-O 64-bit executable arm64e] /System/Applications/Utilities/Script Editor.app/Contents/MacOS/Script Editor (for architecture x86_64): Mach-O 64-bit executable x86_64 /System/Applications/Utilities/Script Editor.app/Contents/MacOS/Script Editor (for architecture arm64e): Mach-O 64-bit executable arm64e
| AppleScript名:指定アプリケーションのバイナリアーキテクチャ判定.scptd |
| — – Created by: Takaaki Naganoya – Created on: 2020/08/18 — – Copyright © 2020 Piyomaru Software, All Rights Reserved — use AppleScript version “2.4” — Yosemite (10.10) or later use framework “Foundation” use scripting additions
|
| ★Click Here to Open This Script |
Finder上で選択中のアプリケーションのplistファイルを探してFinder上で選択表示するAppleScriptです。
| AppleScript名:display selected application’s plist on Finder.scpt |
| — – Created by: Takaaki Naganoya – Created on: 2020/08/17 — – Copyright © 2020 Piyomaru Software, All Rights Reserved — use AppleScript version "2.5" –macOS 10.11 or later use scripting additions use framework "Foundation" use framework "AppKit" property |NSURL| : a reference to current application’s |NSURL| property NSArray : a reference to current application’s NSArray property NSPredicate : a reference to current application’s NSPredicate property NSURLTypeIdentifierKey : a reference to current application’s NSURLTypeIdentifierKey tell application "Finder" set aliasList to selection as alias list end tell –Filter selection application bundle only set aRes to filterAliasListByUTI(aliasList, "com.apple.application-bundle") of me set aLen to length of aRes if aLen = 0 then return –Pick up the first element set anApp to contents of first item of aRes set aRes to getBundleIDFromPath(POSIX path of anApp) of me set pListName to aRes & ".plist" set prefPath to POSIX path of (path to preferences) & pListName set aExt to current application’s NSFileManager’s defaultManager()’s fileExistsAtPath:prefPath if aExt = true then revealAFileByFinder(prefPath) of me end if on filterAliasListByUTI(aliasList as list, acceptUTI as string) set fList to {} repeat with i in aliasList set aUTI to getUTIfromPath(i) of me if aUTI is not equal to missing value then set uRes to filterUTIList({aUTI}, acceptUTI) of me if uRes is not equal to {} then set the end of fList to contents of i end if end if end repeat return fList end filterAliasListByUTI –Application path –> Bundle ID on getBundleIDFromPath(aPOSIXpath as string) set aURL to current application’s |NSURL|’s fileURLWithPath:aPOSIXpath set aWorkspace to current application’s NSWorkspace’s sharedWorkspace() set appURL to aWorkspace’s URLForApplicationToOpenURL:aURL set aBundle to current application’s NSBundle’s bundleWithURL:appURL set anID to aBundle’s bundleIdentifier() return anID as string end getBundleIDFromPath on revealAFileByFinder(aPOSIXpath as string) set pathStr to current application’s NSString’s stringWithString:aPOSIXpath set parentPath to pathStr’s stringByDeletingLastPathComponent() set aRes to current application’s NSWorkspace’s sharedWorkspace()’s selectFile:pathStr inFileViewerRootedAtPath:parentPath end revealAFileByFinder –Alias –> UTI on getUTIfromPath(anAlias) set aPOSIXpath to POSIX path of anAlias set aURL to current application’s |NSURL|’s fileURLWithPath:aPOSIXpath if aURL = missing value then return missing value set aRes to aURL’s resourceValuesForKeys:{current application’s NSURLTypeIdentifierKey} |error|:(missing value) if aRes = missing value then return missing value return (aRes’s NSURLTypeIdentifierKey) as string end getUTIfromPath on filterUTIList(aUTIList, aUTIstr) set anArray to NSArray’s arrayWithArray:aUTIList set aPred to NSPredicate’s predicateWithFormat_("SELF UTI-CONFORMS-TO %@", aUTIstr) set bRes to (anArray’s filteredArrayUsingPredicate:aPred) as list return bRes end filterUTIList |
edama2さんからの投稿です。Markdown書類をプレビューするAppleScriptです。
本Scriptには、画期的な技術が用いられています。普通、AppleScriptObjCでは、WkWebViewによるネットワークアクセスを行うと、ネットワークアクセスを行う子プロセスと、WkWebViewで表示を行う子プロセスが生じて、処理が終了しても残ってしまいます。
スクリプトエディタ上で実行した場合には、スクリプトエディタを終了すればこうした子プロセスはメモリ上からパージされますが、終了するまで子プロセスは残されたままです。
「まー、AppleScriptObjCだから仕方ないよねー」
などと去年の年末に秋葉原でカレーを食べながらバカ話をしていた記憶がありますが、edama2さんがついにこれを回避する記法を編み出してしまったわけです。
回避方法については、やり方を説明されると「なるほど」という単純明快なものです(それを思いつくのがすごい)。osascript経由でAppleScriptを再実行することで、ランタイム環境を明示的にosascriptに指定してプログラムを実行。osascriptの終了とともにメモリ上から子プロセスがパージされるということのようです。
ランタイム環境を動的に生成し、実行後にランタイム環境がメモリ上から消去されるようになっていれば、子プロセス(ゴミ)が残っていてもまとめて消える………AppleScriptのランタイム環境はosascriptのほかCocoa系もあるので、そちらでも試してみるとよいでしょう。
ほかにも、WkWebViewによる表示を行う専用の支援アプリケーションを作っておき、AppleScript用語辞書経由で表示を行わせるという解決策も考えられます(ためしていないですが)。
| AppleScript名:アラートダイアログでMarkdownをプレビュー v3a |
| use AppleScript use framework “AppKit” use framework “Foundation” use framework “WebKit” use scripting additions on run args if (args’s class) is script then set myPath to (path to me)’s POSIX path set resultText to do shell script “osascript “ & myPath’s quoted form else my main() end if end run
## 後始末 theView’s stopLoading() set js to “window.open(’about:blank’,’_self’).close();” theView’s evaluateJavaScript:js completionHandler:(missing value) set theView to missing value end displayMarkdownPreview: |
| ★Click Here to Open This Script |
配列に入れた言語コードに対して、指定のLocaleの言語による表現で、言語名称を求めるAppleScriptです。
| AppleScript名:指定の言語コードを、指定のLocaleの表現で、言語名称に.scpt |
| — – Created by: Takaaki Naganoya – Created on: 2020/08/14 — – Copyright © 2020 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" use scripting additions use framework "Foundation" set langList to {"en", "ru", "ja"} set aList to getLocalizedLangNameList(langList, "ja_JP") of me –> {"英語", "ロシア語", "日本語"} set aList to getLocalizedLangNameList(langList, "en_US") of me –> {"English", "Russian", "Japanese"} set aList to getLocalizedLangNameList(langList, "zh-Hans") of me —> {"英文", "俄文", "日文"} on getLocalizedLangNameList(langList, aLocaleID) set aLocale to (current application’s NSLocale’s localeWithLocaleIdentifier:aLocaleID) set langLocalizedList to {} repeat with i in langList set aLang to getLangNameWithLocale(i, aLocale) of me set the end of langLocalizedList to aLang end repeat return langLocalizedList end getLocalizedLangNameList on getLangNameWithLocale(langCode, aLocale) set aLangName to (aLocale’s displayNameForKey:(current application’s NSLocaleIdentifier) value:langCode) as string return aLangName end getLangNameWithLocale |
配列変数に入れた言語コードを、デフォルトのLocaleの言語による表現で、言語名称を求めるAppleScriptです。
| AppleScript名:指定の言語コードを、デフォルトのLocaleの表現で、言語名称に.scpt |
| — – Created by: Takaaki Naganoya – Created on: 2020/08/14 — – Copyright © 2020 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" use scripting additions use framework "Foundation" set langList to {"en", "ru", "ja"} set aList to getLocalizedLangNameList(langList) of me –> {"英語", "ロシア語", "日本語"} on getLocalizedLangNameList(langList) set aLocale to (current application’s NSLocale’s currentLocale()) set langLocalizedList to {} repeat with i in langList set aLang to getLangNameWithLocale(i, aLocale) of me set the end of langLocalizedList to aLang end repeat return langLocalizedList end getLocalizedLangNameList on getLangNameWithLocale(langCode, aLocale) set aLangName to (aLocale’s displayNameForKey:(current application’s NSLocaleIdentifier) value:langCode) as string return aLangName end getLangNameWithLocale |
Safariで指定URLのページローディングを検出するAppleScriptです。
Safari 13で従来どおり、do javascriptコマンド経由で、
if (do JavaScript "document.readyState" in document 1) is "complete" then
などと処理させてみたら、URLが変更される前に”complete”が返ってきました。どうも、この処理が非同期実行されるのか、表示が反映される前に内部的にはページ遷移が完了しているようです。
現行のSafariにおいては、上記の処理ではページローディング完了検出が行えないことが判明。割と利用する機会が多く、重要な処理であるため、書き換えてみました。
いろいろ実験してみると、documentに新規URLを設定しても、すぐにはURLが変更されないようです。その割にAppleScriptの実行は完了したものとして次の行へと進んでしまいます。普通、こんな挙動を行うアプリケーションはないのですが(OCRで見たことがあったかも?)、ただ、逆にここで処理待ちすると問題が発生するケースもありそうです。
非同期モードと同期モードの両方が存在していて、明示的に選択できるとよいのですが、現状ではそうなっていません。
仕方がないので、旧URLから新URLへの切り替えをループで見張るという処理を書いてみたら、いい感じにローディング検出できました。
| AppleScript名:SafariでURLローディング検出.scpt |
| — – Created by: Takaaki Naganoya – Created on: 2020/08/09 — – Copyright © 2020 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use scripting additions set aURL to "http://piyocast.com/as/" openURLWithSafari(aURL) set aText to getBodyTextFromSafariFrontWindow() of me on openURLWithSafari(aURL as string) if aURL = "" then error "URL is blank" try tell application "Safari" set d to count every window if d = 0 then make new window tell front document set URL to aURL end tell else tell front document set oldURL to URL set URL to aURL end tell end if detectPageLoaded(10, oldURL, aURL) of me end tell on error return false end try end openURLWithSafari on detectPageLoaded(timeout_value, oldURL, newURL) try repeat with i from 1 to (timeout_value * 5) tell application "Safari" tell front document set curURL to URL end tell end tell if curURL = newURL then return delay 0.2 end repeat on error return false end try return false end detectPageLoaded on getBodyTextFromSafariFrontWindow() –フレームを使っていないことが前提条件 tell application "Safari" return text of front document end tell end getBodyTextFromSafariFrontWindow |
githubのREST APIを呼び出して、Markdownファイルの内容をHTMLにレンダリングするAppleScriptです。

▲他のプログラムに処理部分を組み込んで表示させたところ(本ScriptはただHTMLレンダリングするだけで表示しません)
MacDownなどのアプリケーションでMarkdownファイルをオープンする方法とコードの行数はどっこいどっこいですが、MacDownを必要としないので、どこの環境でも(インターネット接続していれば)実行できます。
志の低さが目を覆わんばかりの出来で、実に少ない行数でレンダリングできるものの、Markdownをサーバーに送ってレンダリングしてもらうので、処理速度は遅いです。あるいは、githubのREST API処理サーバーにつねに負荷がかかっているとか。
| AppleScript名:githubの機能でMarkdownをhtmlに.scpt |
| — – Created by: Takaaki Naganoya – Created on: 2020/08/09 — – Copyright © 2020 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use scripting additions set mdFile to choose file of type {"net.daringfireball.markdown"} with prompt "Choose Markdown" set mdStr to (read mdFile as «class utf8») as text do shell script "curl -X POST https://api.github.com/markdown/raw -H ’Content-Type: text/plain’ -d " & quoted form of mdStr set aString to result return aString |
edama2さんからの投稿です。起動中のアプリケーション一覧をダイアログ表示するAppleScriptシリーズで、アプリケーションのアイコン画像やパス情報を表示し、単数/複数のアプリケーション選択ができます。
–> Download Script Bundle archive
タイトルは「アラートダイアログ上のTable Viewで起動中のアプリケーションを選択」 起動中のアプリケーションをiOSライクなレイアウトで表示します Table Viewにカスタムセルビューを作って表現し、アプリの起動と終了を検知してリアルタイムで更新します 行数は12行に固定し、項目の複数選択がオプションで選べます
AppleScriptObjCフレームワークを呼び出すことにより、Xcod上で記述するAppleScriptアプリケーションのように、各種クラスのサブクラスを定義して利用しているScriptです。さまざまなScripterの技術が積み上がっている感じがして壮観ですが、ほとんどはedama2さんが長い時間をかけて積み上げてきたものの集大成といったものです。
本Scriptは途中で試行錯誤がいろいろあって、返り値がファイルパスだったりアプリケーションオブジェクトになったりと紆余曲折あって、現在の「アプリケーションオブジェクト」になりました。
ただ、実行中のアプリケーション自身を選択すると「current application」になってしまう点に注意が必要です。
| AppleScript名:アラートダイアログ上のTable Viewで起動中のアプリケーションを選択 v3c |
| use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use framework "AppleScriptObjC" use scripting additions on run my testRun() end run on testRun() set mes to "選択モードを指定してください" set bOK to "複数選択" set bOther to "単一選択" set bCancel to "キャンセル" set bList to {bCancel, bOther, bOK} set userAction to display dialog mes buttons bList default button bOK cancel button bCancel with icon note if userAction’s button returned is bOther then set isMultiple to false else if userAction’s button returned is bOK then set isMultiple to true end if set aMainMes to "Select Runnning Application" set aSubMes to "Sub Message" set execButtonTitle to "OK😁" set dateObj to my chooseRunningApplication:aMainMes subMes:aSubMes withMultipleSelections:(isMultiple) okTitle:execButtonTitle end testRun on chooseRunningApplication:(aMainMes as text) subMes:(aSubMes as text) withMultipleSelections:(isMultiple as boolean) okTitle:(execButtonTitle as text) # Script Bundle内のResourcesフォルダを求める set resourcePath to POSIX path of (path to me) & "Contents/Resources/" set theBundle to current application’s NSBundle’s bundleWithPath:resourcePath theBundle’s loadAppleScriptObjectiveCScripts() # subClasses.scptを呼び出す set DialogCore to current application’s AppDelegate’s new() set fRes to DialogCore’s chooseRunningApplication:aMainMes subMes:aSubMes withMultipleSelections:isMultiple okTitle:execButtonTitle if fRes is missing value then error number -128 return fRes as list end chooseRunningApplication:subMes:withMultipleSelections:okTitle: |
| AppleScript名:subClasses.scpt |
| #MARK: AppDelegate script AppDelegate property parent : class "NSObject" — IBOutlets property theWindow : missing value on applicationShouldTerminate:sender return current application’s NSTerminateNow end applicationShouldTerminate: on applicationShouldTerminateAfterLastWindowClosed:sender return true end applicationShouldTerminateAfterLastWindowClosed: on applicationWillFinishLaunching:aNotification log my testRun() end applicationWillFinishLaunching: —————— #MARK: テスト on testRun() –log "testRun" set mes to "選択モードを指定してください" set bOK to "複数選択" set bOther to "単一選択" set bCancel to "キャンセル" set bList to {bCancel, bOther, bOK} set userAction to display dialog mes buttons bList default button bOK cancel button bCancel with icon note if userAction’s button returned is bOther then set isMultiple to false else if userAction’s button returned is bOK then set isMultiple to true end if set aMainMes to "Select Runnning Application" set aSubMes to "Sub Message" set execButtonTitle to "OK😁" set dateObj to my chooseRunningApplication:aMainMes subMes:aSubMes withMultipleSelections:(isMultiple) okTitle:execButtonTitle return dateObj end testRun #MARK: アラートダイアログでtableviewを表示 property _retrieve_data : missing value on chooseRunningApplication:(aMainMes as text) subMes:(aSubMes as text) withMultipleSelections:(isMultiple as boolean) okTitle:(execButtonTitle as text) # reset set my _retrieve_data to missing value # if aMainMes = "" then error "Dialog main message is missing" # set dataSourceList to {} # OKボタンの文字 if execButtonTitle = "" then set execButtonTitle to "Execute" set thisFileType to "com.apple.application-bundle" # set paramObj to {myMessage:aMainMes} set paramObj to paramObj & {mySubMessage:aSubMes} set paramObj to paramObj & {myDataSource:dataSourceList} set paramObj to paramObj & {myType:thisFileType} set paramObj to paramObj & {mySelectType:isMultiple} set paramObj to paramObj & {myOKTitile:execButtonTitle} my performSelectorOnMainThread:"raizeAlert:" withObject:paramObj waitUntilDone:true if (my _retrieve_data) is missing value then error number -128 set retrieveData to my _retrieve_data set my _retrieve_data to missing value return retrieveData end chooseRunningApplication:subMes:withMultipleSelections:okTitle: #MARK: Raize Alert on raizeAlert:paramObj set mesText to paramObj’s myMessage as text set infoText to paramObj’s mySubMessage as text set dataSourceList to paramObj’s myDataSource as list set myUTI to paramObj’s myType as text set isMultiple to paramObj’s mySelectType as boolean set okButton to paramObj’s myOKTitile as text ### set up view #### data sourceにデータを追加 set theController to current application’s YKZArrayController’s alloc()’s initWithContent:dataSourceList theController’s setMultipleSelections:isMultiple theController’s setupView() ### アイコンの指定 –set aImage to current application’s NSWorkspace’s sharedWorkspace()’s iconForFileType:myUTI set setSize to 128 set fontObj to current application’s NSFont’s systemFontOfSize:setSize set aValue to {fontObj} set aKey to {current application’s NSFontAttributeName} set attributes to current application’s NSDictionary’s dictionaryWithObjects:aValue forKeys:aKey set strEmoji to current application’s NSString’s stringWithString:"😎" set newSizeObj to current application’s NSMakeSize(setSize, setSize) tell current application’s NSImage’s alloc tell initWithSize_(newSizeObj) lockFocus() strEmoji’s drawInRect:(current application’s NSMakeRect(0, 0, setSize, setSize)) withAttributes:attributes unlockFocus() set aImage to it end tell end tell ### set up alert tell current application’s NSAlert’s new() addButtonWithTitle_(okButton) addButtonWithTitle_("Cancel") setAccessoryView_(theController’s _the_view) setIcon_(aImage) setInformativeText_(infoText) setMessageText_(mesText) tell |window|() setInitialFirstResponder_(theController’s _the_view) end tell #### show alert in modal loop if runModal() is (current application’s NSAlertSecondButtonReturn) then return end tell ### retrieve data set (my _retrieve_data) to theController’s retrieveSelectItems() return my _retrieve_data end raizeAlert: end script #MARK: – NSValueTransformer script YKZURLToIcon property parent : class "NSValueTransformer" property allowsReverseTransformation : false –>逆変換 property transformedValueClass : a reference to current application’s NSImage –>クラス #変換処理 on transformedValue:fileURL if fileURL is missing value then return set appPath to fileURL’s |path|() set iconImage to current application’s NSWorkspace’s sharedWorkspace’s iconForFile:appPath return iconImage end transformedValue: end script script YKZURLToDisplayedName property parent : class "NSValueTransformer" property allowsReverseTransformation : false –>逆変換 property transformedValueClass : a reference to current application’s NSString –>クラス #変換処理 on transformedValue:fileURL if fileURL is missing value then return set appPath to fileURL’s |path|() set displayedName to current application’s NSFileManager’s defaultManager’s displayNameAtPath:appPath return displayedName end transformedValue: end script script YKZURLToPath property parent : class "NSValueTransformer" property allowsReverseTransformation : false –>逆変換 property transformedValueClass : a reference to current application’s NSString –>クラス #変換処理 on transformedValue:fileURL if fileURL is missing value then return set appPath to fileURL’s |path|() return appPath end transformedValue: end script script YKZURLToVersion property parent : class "NSValueTransformer" property allowsReverseTransformation : false –>逆変換 property transformedValueClass : a reference to current application’s NSString –>クラス #変換処理 on transformedValue:fileURL if fileURL is missing value then return set appPath to fileURL’s |path|() tell (current application’s NSBundle’s bundleWithURL:fileURL) tell its infoDictionary() set aVar to objectForKey_("CFBundleShortVersionString") end tell end tell return aVar end transformedValue: end script #MARK: – Array Controller script YKZArrayController property parent : class "NSArrayController" #MARK: IBOutlets property _the_view : missing value property _table_view : missing value #MARK: Property property multipleSelections : false property rowCount : 12 #MARK: 初期化 on initWithContent:aContent continue initWithContent:aContent #通知の登録 tell current application’s NSWorkspace’s sharedWorkspace()’s notificationCenter() addObserver_selector_name_object_(me, "changed:", "NSWorkspaceDidLaunchApplicationNotification", missing value) addObserver_selector_name_object_(me, "changed:", "NSWorkspaceDidTerminateApplicationNotification", missing value) end tell return me end initWithContent: #MARK: 戻り値 on retrieveSelectItems() set retrieveData to {} set aIndexSet to my _table_view’s selectedRowIndexes() set chooseItems to ((my _table_view’s dataSource())’s arrangedObjects()’s objectsAtIndexes:aIndexSet) as list repeat with anItem in chooseItems set aPath to anItem’s fileURL set retrieveData’s end to application (aPath as text) end repeat return retrieveData as list end retrieveSelectItems #MARK: viewの作成 on setupView() #Transformerの登録 set tNames to {} set tNames’s end to "YKZURLToIcon" set tNames’s end to "YKZURLToDisplayedName" set tNames’s end to "YKZURLToPath" set tNames’s end to "YKZURLToVersion" repeat with aTransformer in tNames set theTransformer to current application’s class aTransformer’s new() (current application’s NSValueTransformer’s setValueTransformer:theTransformer forName:aTransformer) end repeat log multipleSelections ## NSTableView tell current application’s NSTableView’s alloc() tell initWithFrame_(current application’s CGRectZero) –registerForDraggedTypes_({current application’s NSFilenamesPboardType, (my _data_type)}) setAllowsEmptySelection_(false) setAllowsMultipleSelection_(multipleSelections) setDataSource_(me) setDelegate_(me) setDoubleAction_("doubleAction:") –setDraggingSourceOperationMask_forLocal_(current application’s NSDragOperationCopy, false) setGridStyleMask_(current application’s NSTableViewSolidVerticalGridLineMask) setSelectionHighlightStyle_(current application’s NSTableViewSelectionHighlightStyleRegular) setTarget_(me) setUsesAlternatingRowBackgroundColors_(true) setHeaderView_(missing value) setRowHeight_(50) set thisRowHeight to rowHeight() as integer set my _table_view to it end tell end tell ## NSTableColumn ### Columnの並び順を指定する set keyrec to {column1:"Name"} set keyDict to (current application’s NSDictionary’s dictionaryWithDictionary:keyrec) set viewWidth to 400 –表示幅を変更 set columnsCount to keyDict’s |count|() repeat with colNum from 1 to columnsCount set keyName to "column" & colNum as text set aTitle to (keyDict’s objectForKey:keyName) tell (current application’s NSTableColumn’s alloc()’s initWithIdentifier:(colNum as text)) tell headerCell() setStringValue_(aTitle) set thisHeaderHeight to cellSize()’s height end tell ### バインディングのオプション — ソートを無効にする set anObj to (current application’s NSNumber’s numberWithBool:false) set aKey to current application’s NSCreatesSortDescriptorBindingOption set bOptions to (current application’s NSDictionary’s dictionaryWithObject:anObj forKey:aKey) ### 表示内容をNSArrayControllerにバインディング bind_toObject_withKeyPath_options_(current application’s NSValueBinding, (my _table_view’s dataSource()), "arrangedObjects", bOptions) set aTableColumn to it end tell ### Columnの横幅を調整 tell (my _table_view) addTableColumn_(aTableColumn) end tell tell aTableColumn setWidth_(viewWidth) end tell end repeat ## NSScrollView ### Viewの高さを計算 set viewHeight to thisRowHeight * rowCount + thisHeaderHeight #ダイアログ内のビューサイズを指定 set aScreen to current application’s NSScreen’s mainScreen() set screenFrame to aScreen’s frame() set aHeight to current application’s NSHeight(screenFrame) set aWidth to current application’s NSWidth(screenFrame) set maxHeight to aHeight * 0.845 set maxWidth to aWidth * 0.94 –log viewHeight –log maxHeight –set viewHeight to viewHeight + aSpace + sfHeight if viewHeight > maxHeight then set viewHeight to maxHeight if viewWidth > maxWidth then set viewWidth to maxWidth if viewWidth < 360 then set viewWidth to 360 set vSize to current application’s NSMakeRect(0, 0, viewWidth, viewHeight) ### Viewを作成 tell current application’s NSScrollView’s alloc() tell initWithFrame_(vSize) setBorderType_(current application’s NSBezelBorder) setDocumentView_(my _table_view) setHasHorizontalScroller_(true) setHasVerticalScroller_(true) set my _the_view to it end tell end tell my changed:me # 1行目を選択 my setSelectionIndex:0 return my _the_view end setupView #MARK: アプリケーションが切り替わった時 — tableViewを作成してから実行する on changed:sender log "changed" tell (my _table_view)’s dataSource() removeObjects_(arrangedObjects()) end tell #起動中のアプリケーション set aList to current application’s NSWorkspace’s sharedWorkspace()’s runningApplications() set sort to (current application’s NSSortDescriptor’s alloc()’s initWithKey:"localizedName" ascending:true selector:"caseInsensitiveCompare:") set sortedList to aList’s sortedArrayUsingDescriptors:(sort as list) repeat with anItem in (sortedList as list) #普通のアプリだけ set aFlag to anItem’s activationPolicy() is current application’s NSApplicationActivationPolicyRegular if aFlag then set theURL to anItem’s bundleURL() ((my _table_view)’s dataSource()’s addObject:{fileURL:theURL}) end if end repeat end changed: #MARK: Data Source Overrides on numberOfRowsInTableView:aTableView return (aTableView’s dataSource())’s content()’s |count|() end numberOfRowsInTableView: on tableView:aTableView viewForTableColumn:aColumn row:aRow set aCellView to aTableView’s makeViewWithIdentifier:"YKZTableCellView" owner:me if aCellView is missing value then set frameRect to current application’s NSMakeRect(0, 0, aColumn’s width, aTableView’s rowHeight()) set aCellView to current application’s YKZTableCellView’s alloc’s initWithFrame:frameRect set anObj to "YKZURLToIcon" set aKey to current application’s NSValueTransformerNameBindingOption set bOptions to (current application’s NSDictionary’s dictionaryWithObject:anObj forKey:aKey) (aCellView’s imageView())’s bind:(current application’s NSValueBinding) toObject:aCellView withKeyPath:"objectValue.fileURL" options:bOptions set anObj to "YKZURLToDisplayedName" set aKey to current application’s NSValueTransformerNameBindingOption set bOptions to (current application’s NSDictionary’s dictionaryWithObject:anObj forKey:aKey) (aCellView’s textField())’s bind:(current application’s NSValueBinding) toObject:aCellView withKeyPath:"objectValue.fileURL" options:bOptions set anObj to "YKZURLToPath" set aKey to current application’s NSValueTransformerNameBindingOption set bOptions to (current application’s NSDictionary’s dictionaryWithObject:anObj forKey:aKey) (aCellView’s pathTextField)’s bind:(current application’s NSValueBinding) toObject:aCellView withKeyPath:"objectValue.fileURL" options:bOptions set anObj to "YKZURLToVersion" set aKey to current application’s NSValueTransformerNameBindingOption set bOptions to (current application’s NSDictionary’s dictionaryWithObject:anObj forKey:aKey) (aCellView’s varTextField)’s bind:(current application’s NSValueBinding) toObject:aCellView withKeyPath:"objectValue.fileURL" options:bOptions return aCellView end if return missing value end tableView:viewForTableColumn:row: # テーブル内のセルが編集できるか on tableView:aTableView shouldEditTableColumn:aColumn row:aRow return false end tableView:shouldEditTableColumn:row: # on validateProposedFirstResponder:aResponder forEvent:aEvent log "validateProposedFirstResponder" return true set aBoolean to aResponder’s isKindOfClass:(current application’s NSTableCellView’s |class|()) if aBoolean as boolean then return true end if return true end validateProposedFirstResponder:forEvent: # テーブル内をダブルクリックしたらOKボタンを押す on doubleAction:sender log "doubleAction" ## ヘッダをクリックした時は何もしない if (sender’s clickedRow()) is -1 then return set theEvent to current application’s NSEvent’s ¬ keyEventWithType:(current application’s NSEventTypeKeyDown) ¬ location:(current application’s NSZeroPoint) ¬ modifierFlags:0 ¬ timestamp:0.0 ¬ windowNumber:(sender’s |window|()’s windowNumber()) ¬ context:(current application’s NSGraphicsContext’s currentContext()) ¬ |characters|:return ¬ charactersIgnoringModifiers:(missing value) ¬ isARepeat:false ¬ keyCode:0 current application’s NSApp’s postEvent:theEvent atStart:(not false) end doubleAction: end script #MARK: – NSTableCellView script YKZTableCellView property parent : class "NSTableCellView" property acceptsFirstResponder : true property pathTextField : missing value property varTextField : missing value on initWithFrame:rect continue initWithFrame:rect setAutoresizingMask_(current application’s NSViewWidthSizable) –set myWidth to current application’s NSWidth(rect) –set myHeight to current application’s NSHeight(rect) # アイコン tell current application’s NSImageView’s alloc tell initWithFrame_(current application’s NSMakeRect(12, 1, 48, 48)) setImageScaling_(current application’s NSImageScaleProportionallyUpOrDown) setImageAlignment_(current application’s NSImageAlignCenter) my setImageView:it my addSubview:it end tell end tell # 表示名 tell current application’s NSTextField’s alloc tell initWithFrame_(current application’s NSMakeRect(66, 26, 280, 17)) setBordered_(false) setDrawsBackground_(false) setEditable_(false) setLineBreakMode_(current application’s NSLineBreakByTruncatingMiddle) setFont_(current application’s NSFont’s boldSystemFontOfSize:13) my setTextField:it my addSubview:it end tell end tell # パス tell current application’s NSTextField’s alloc tell initWithFrame_(current application’s NSMakeRect(66, 6, 316, 17)) setBordered_(false) setDrawsBackground_(false) setEditable_(false) setLineBreakMode_(current application’s NSLineBreakByTruncatingMiddle) setFont_(current application’s NSFont’s systemFontOfSize:11) set my pathTextField to it my addSubview:it end tell end tell # バージョン tell current application’s NSTextField’s alloc tell initWithFrame_(current application’s NSMakeRect(300, 24, 80, 17)) setBordered_(false) setDrawsBackground_(false) setEditable_(false) setFont_(current application’s NSFont’s systemFontOfSize:11) setLineBreakMode_(current application’s NSLineBreakByTruncatingMiddle) setAlignment_(current application’s NSRightTextAlignment) set my varTextField to it my addSubview:it end tell end tell return me end initWithFrame: end script |
edama2さんから投稿していただいた、Markdown書類をダイアログ上でプレビューするScriptの第二弾です。
WKWebViewではなくNSAttributedStringを使ってMarkdownを表示しています。 NSAttributedStringでhtmlが表示できるとわかってWKWebViewの代わりになるかな?と思ったんですが、javascriptは使えないので自前で実行し、NSAttributedStringに<hr>に相当するものがないのとWKWebViewとは描画結果が違うので、プレビューとして使うのはイマイチです。作っておいてなんですが…😢
本Scriptの見どころは2点。「Scriptバンドル内にJavaScriptのライブラリを突っ込んで呼び出しているところ」と、「Markdownを(HTML経由で)NSAttributedStringに変換してプレビューしている」ところです。
ひかえめな説明文とは裏腹に、ずいぶんと攻めているというか、野心的な実装です。
JavaScriptのライブラリをローカルに置く案についてはいろいろedama2さんと意見交換をしていたんですが、割とさらっと書いているところにショックを受けます。これだけでも、相当に試行錯誤が必要な内容なんですが、このレベルに追いつくためには相当の苦労が、、、
| AppleScript名:アラートダイアログでMarkdownをプレビュー v2 |
| use AppleScript use framework "Foundation" use scripting additions on run my main() end run on main() set mes to "Markdownファイルを選択してください。" set chooseItems to choose file of type {"net.daringfireball.markdown"} with prompt mes # set aScreen to current application’s NSScreen’s mainScreen() set screenFrame to aScreen’s frame() set aHeight to current application’s NSHeight(screenFrame) set aWidth to current application’s NSWidth(screenFrame) set aHeight to aHeight * 0.845 set aWidth to aWidth * 0.94 / 2 set paramObj to {} set paramObj to paramObj & {myMessage:"Markdown preview"} set paramObj to paramObj & {mySubMessage:"file : " & chooseItems’s POSIX path} set paramObj to paramObj & {mdFile:chooseItems} set paramObj to paramObj & {viewWidth:aWidth} set paramObj to paramObj & {viewHeight:aHeight} my performSelectorOnMainThread:"displayMarkdownPreview:" withObject:(paramObj) waitUntilDone:true end main # Markdownをダイアログで表示 on displayMarkdownPreview:paramObj set mesText to myMessage of paramObj as text set infoText to mySubMessage of paramObj as text set mdFile to (mdFile of paramObj) as alias set viewWidth to (viewWidth of paramObj) as integer set viewHeight to (viewHeight of paramObj) as integer ## MDを読み込む set mdStr to (read mdFile as «class utf8») as text ## javascriptを読み込む set mePath to path to me set resPath to (mePath & "Contents:Resources:marked.min.js") as text set jsStr to (read (resPath as alias) as «class utf8») as text ## javascriptでMarkdownをhtmlに変換 tell current application’s JSContext’s new() evaluateScript_(jsStr) tell objectForKeyedSubscript_("marked") tell callWithArguments_({mdStr}) set resultStr to toString() end tell end tell end tell ## HTMLを読み込む set mePath to path to me set resPath to (mePath & "Contents:Resources:index.html") as text set htmlStr to (read (resPath as alias) as «class utf8») as text ## html内の文字を置き換え set aString to current application’s NSString’s stringWithFormat_(htmlStr, resultStr) as text log result ## NSAttributedString set aString to current application’s NSString’s stringWithString:aString set stringData to aString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding) set aValue to {current application’s NSHTMLTextDocumentType, current application’s NSUTF8StringEncoding} set aKey to {current application’s NSDocumentTypeDocumentAttribute, current application’s NSCharacterEncodingDocumentAttribute} set aOption to current application’s NSDictionary’s dictionaryWithObjects:aValue forKeys:aKey tell current application’s NSAttributedString’s alloc() tell initWithHTML_options_documentAttributes_(stringData, aOption, missing value) set attributedString to it end tell end tell log result # Create a TextView with Scroll View set frameSize to current application’s NSMakeRect(0, 0, viewWidth, viewHeight) tell current application’s NSTextView’s alloc() tell initWithFrame_(frameSize) setEditable_(false) tell textStorage() appendAttributedString_(attributedString) end tell set aView to it end tell end tell tell current application’s NSScrollView’s alloc() tell initWithFrame_(frameSize) setDocumentView_(aView) set theView to it end tell end tell ## アイコンの指定 set aImage to current application’s NSWorkspace’s sharedWorkspace()’s iconForFileType:"net.daringfireball.markdown" ## set up alert tell current application’s NSAlert’s new() addButtonWithTitle_("Close") setAccessoryView_(theView) setIcon_(aImage) setInformativeText_(infoText) setMessageText_(mesText) tell |window|() setInitialFirstResponder_(theView) end tell ### show alert in modal loop if runModal() is (current application’s NSAlertSecondButtonReturn) then return end tell end displayMarkdownPreview: |
そして、本Scriptをよくよく見回してみると、NSAttributedStringになっているのでPDFに変換するのがとても簡単でした。WkWebView上に表示したWebコンテンツをPDF化しようと試みているものの、うまくできていません。Blocks構文を必要とする機能であるため(なんでこんなものまでBlocks(非同期実行)必要なんだろ?)、AppleScriptからの呼び出しができないためです。Xcode上で他の言語をまじえて組めば不可能ではない気もしますが……
CSSがWeb表示用のものなので、PDF出力時には割とゆったり目で密度の低いレイアウトになってしまうものの、このCSS指定を別のものにできればけっこうMarkdown→PDFの変換を行う部品として使えるレベルになってくるのではないでしょうか。それはそれで、Scriptバンドル内に入れたCSSを指定するとか、Web上のどこかに印刷用のCSSをホスティングしておくなどの方法が必要になることでしょう。
起動中のアプリケーション一覧から、1つを選択するダイアログを提供するライブラリです。
「そういうのがない」という話があって「そういえばなかったね、大した内容じゃないけど」と、作ってみたものです。これで一番左に各アプリケーションのアイコン画像が表示できたら完璧なんですが、そこまではやりませんでした。
CPU Architectureを表示に含んでいるのが、ARMへの移行を目前に控えた時期っぽい感じでしょうか。本来は各アプリケーションへのパスをPOSIX pathで表示していたのですが、途中からなぜか表示されなくなって(不思議)、その機能は割愛しています。
–> Download chooseAppLib(To ~/Library/Script Libraries)
| AppleScript名:sample.scpt |
| — – Created by: Takaaki Naganoya – Created on: 2020/08/07 — – Copyright © 2020 Piyomaru Software, All Rights Reserved – http://piyocast.com/as/asinyaye use apSel : script "choose appLib" set aRes to choose running application main message "Select a Running App" sub message "Sub Message" size {900, 200} –> {"Safari", "Safari", "13.1.2", "x86_64"} |