Archive for the 'NSNotificationCenter' Category

2016/06/18 指定フォルダ以下にあるMacDownとPages書類をソートしてPDFに書き出す

指定フォルダ以下にあるMacDownで記述したMarkdown書類と、Pages書類をすべての階層からピックアップしてファイル名でソートして、すべてデスクトップにPDFで書き出すAppleScriptです。

コンパイル(構文確認)および実行に際しては、Shane StanleyのScript Library「Bridge Plus」をインストールしておく必要があります。また、GUI Scriptingを利用しているため「システム環境設定」>「セキュリティとプライバシー」>「プライバシー」>「アクセシビリティ」でスクリプトエディタ(アプレットとして実行する場合にはアプレットそのもの)を登録して許可しておく必要があります。

「技術書典」に出す電子ブックのフォーマットがギリギリまで決まらず、しかも縦長のスクロールさせるタイプのものにできないかとあがいていたのですが、結局iPadあたりで読むことを考慮するとiPadのリーダーの仕様にしたがう必要があります。

……あれ?(^ー^; 結局、ページめくりは発生するし、一般的な本と同じような体裁になってしまいますよ → フォーマットがPDFになりました。

Markdown書類とPages書類が混在しているフォルダ構造のトップ階層のフォルダを指定すると、Markdown書類とPages書類をピックアップし、ファイル名でそれらをソートし、順次PDFに書き出すAppleScriptを書いてみました(必要は発明のマザー!)。

book1.png

ただ、MacDownには「書類をPDFに書き出す」という機能がAppleScript側に公開されていません(T_T)。

macdown_dict.png

ないものを「ないない」と嘆いても仕方がないので、さっさとGUI Scriptingで強制的にメニュー操作することにしました。

macdown_gui.png

で、どこに? どこに保存させるのでしょう??

大丈夫! そんなときには、保存ダイアログで幾つかのフォルダに強制的に移動させるキーボードショートカットが存在しており、Command-Dは「カレントディレクトリをデスクトップに移動させる」=「保存先をデスクトップにする」働きをします。

このため、保存先を操作しづらい(不可能とはいいませんけれども)GUI Scriptingにおいて保存先を指定することが、デスクトップフォルダについては可能になっています。

Pagesの方はひじょうに素直に(GUI Scriptingなんて使わずに)PDF書き出しが可能です。

そんなわけで、時間に追い詰められながらもなんとか大量のデータ処理を行っているのでありました。書き出した大量のPDFもAppleScriptでさくっと連結できるので、非常にいい感じです。あとは、本が完成すれば、、、、

AppleScript名:指定フォルダ以下にあるMDとPagesをソートしてPDFに書き出す
– Created 2016-06-17 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”
–use spotLight : script “spotlightLib”

property searchRes : {}

load framework

set origPath to POSIX path of (choose folder with prompt “Markdown/Pages ファイルの入っているフォルダを選択”)

set aResList to (spotlightSearch(origPath, “kMDItemKind == ’Markdown’ || kMDItemKind == ’Pages 一般書類’”) of me) as list –Caution! this parameter is *localized*

–フルパスとファイル名のペアの2D Listを作成
set newList to {}
repeat with i in aResList
  set j to contents of i
  
set aStr to (current application’s NSString’s stringWithString:j)
  
set aFileName to aStr’s lastPathComponent()
  
set the end of newList to {aStr, aFileName}
end repeat

–番号順にソート
set sortIndexes to {1} –Key Item id: begin from 0, Sort by filename
set sortOrders to {true}
set sortTypes to {“compare:”}
set resList to (current application’s SMSForder’s subarraysIn:(newList) sortedByIndexes:sortIndexes ascending:sortOrders sortTypes:sortTypes |error|:(missing value)) as list
if resList = {} then return –No Result

–ソートした順番にMarkdownファイル/Pages書類ファイルをオープンしてデスクトップにPDF生成してクローズ
repeat with i in newList
  copy i to {fullPath, aFileName}
  
  
set apFile to (POSIX file (fullPath as string))
  
set anAlias to apFile as alias
  
set aFileName to aFileName as string
  
  
if aFileName ends with “.md” then
    exportFromMacDown(anAlias) of me –Markdown
  else if aFileName ends with “.pages” then
    exportFromPages(anAlias) of me –Pages
  end if
end repeat

–指定のPagesファイル(alias)をデスクトップ上にPDFで書き出し
on exportFromPages(anAlias)
  tell application “Finder”
    set aName to name of anAlias
  end tell
  
  
set dtPath to (path to desktop) as string
  
set outPath to dtPath & aName & “.pdf”
  
  
tell application “Pages”
    close every document without saving
    
open anAlias
    
export document 1 to file outPath as PDF with properties {image quality:Best}
    
close every document without saving
  end tell
end exportFromPages

–指定のMacDownファイル(alias)をデスクトップ上にPDFで書き出し
on exportFromMacDown(anAlias)
  tell application “MacDown”
    open {anAlias}
  end tell
  
  
tell current application
    delay 1 –ここの時間待ちが少ないと画像抜けが発生?
  end tell
  
macDownForceSave() of me
  
  
tell application “MacDown”
    close every document without saving
  end tell
end exportFromMacDown

–注意!! ここでGUI Scriptingを使用。バージョンが変わったときにメニュー階層などの変更があったら書き換え
on macDownForceSave()
  activate application “MacDown”
  
tell application “System Events”
    tell process “MacDown”
      – File > Export > PDF
      
click menu item 2 of menu 1 of menu item 14 of menu 1 of menu bar item 3 of menu bar 1
      
      
–Go to Desktop Folder
      
keystroke “d” using {command down}
      
      
–Save Button on Sheet
      
click button 1 of sheet 1 of window 1
    end tell
  end tell
end macDownForceSave

–Spotlight Libの内容を引っ張り出してきた
on spotlightSearch(origPOSIXpath, aCondition)
  
  
set searchRes to {} –initialize
  
  
initiateSearchForFullPath(aCondition, origPOSIXpath) –Predicate & Scope Directory
  
  
–Waiting for the result
  
repeat while searchRes = {}
    current application’s NSThread’s sleepForTimeInterval:(“0.001″ as real) –delay 0.001
  end repeat
  
  
set anObj to searchRes’s firstObject() –Pick up the first one for test
  
if anObj = missing value then return {} –No Result
  
  
–set anAttrList to anObj’s attributes() –”mdls” attributes
  
–>  (NSArray) {”kMDItemContentTypeTree”, “kMDItemContentType”, “kMDItemPhysicalSize”, …}
  
  
set resArray to {}
  
repeat with anItem in my searchRes
    set j to contents of anItem
    
set aPath to (j’s valueForAttribute:“kMDItemPath”) as string
    
set the end of resArray to aPath
  end repeat
  
  
return resArray
end spotlightSearch

on initiateSearchForFullPath(aQueryStrings, origPath)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
aSearch’s setPredicate:aPredicate
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{origPath}
  
aSearch’s setSearchScopes:aScope
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemFSName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
aSearch’s startQuery()
  
end initiateSearchForFullPath

on queryDidUpdate:sender
  –  
end queryDidUpdate:

on initalGatherComplete:sender
  set anObject to sender’s object
  
anObject’s stopQuery()
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
set my searchRes to anObject’s results()
end initalGatherComplete:

★Click Here to Open This Script 

2016/06/17 ASOCでmdfindするじっけん v4(フルパスを返す)

Spotlightの機能をCocoa経由で呼び出してmdfindコマンドのクエリーと検索対象フォルダをPOSIX pathで与えると、条件に合致するファイルのPOSIX pathをリストに入れて返してくるAppleScriptです。

以前に掲載したバージョンを実際に使おうとしたら、激しく間違っていることを発見。なんで気がつかなかったんでしょう。いえ、なんでこんな忙しいときに気づいてしまうんでしょう。

AppleScript名:ASOCでmdfindするじっけん v4(フルパスを返す)
– Created 2016-06-17 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

property searchRes : {}

set origPath to POSIX path of (choose folder)
set aList to spotlightSearch(origPath, “kMDItemContentType == ’net.daringfireball.markdown’”) of me

on spotlightSearch(origPath, aMDfindParam)
  
  
set my searchRes to {} –initialize
  
initiateSearchForFullPath(aMDfindParam, origPath) –Predicate & Scope Directory
  
  
–Waiting for the result
  
repeat while my searchRes = {}
    delay 0.01
  end repeat
  
  
set anObj to my searchRes’s firstObject() –Pick up the first one for test
  
if anObj = missing value then return {} –No Result
  
  
set resArray to {}
  
repeat with anItem in my searchRes
    set j to contents of anItem
    
set aPath to (j’s valueForAttribute:“kMDItemPath”) as string
    
set the end of resArray to aPath
  end repeat
  
  
return resArray
  
end spotlightSearch

on initiateSearchForFullPath(aQueryStrings, origPath)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
aSearch’s setPredicate:aPredicate
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{origPath}
  
aSearch’s setSearchScopes:aScope
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemFSName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
aSearch’s startQuery()
  
end initiateSearchForFullPath

on queryDidUpdate:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0×7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = }
end queryDidUpdate:

on initalGatherComplete:sender
  set anObject to sender’s object
  
anObject’s stopQuery()
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
  
set my searchRes to anObject’s results()
end initalGatherComplete:

★Click Here to Open This Script 

2015/09/25 spotlightでタグを指定して検索

Cocoaの機能を用いてSpotlight検索で指定フォルダ以下の指定タグのついているファイルを検索するAppleScriptです。

Shane StanleyがAppleScript Users MLに投稿したもので、Spotlightの検索待ち処理でプロパティをループで監視するやり方が採用されていますが、delayの秒数に0.01と指定して済ますあたりが書きこなれています。

実際0.1秒以下の数値指定は無効ではあるものの、delayを使わないとうまく動きません。動いているし、この書き方でエラーを回避できているので、これでいいんでしょう。

複数のPOSIX pathおよび定数で指定されるフォルダをリストにして与え、一括検索を行っているあたりもみどころです。

AppleScript名:spotlightでタグを指定して検索
– Created 2015-09-25 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

property searchIsDone : false

set aRes to my searchInListOfPaths:{POSIX path of (path to home folder)} forTags:{“レッド”}
–>  {{kMDItemPath:”/Users/me/Xxxxxxxxx/XxxxxXxxxxx/XX出撃回数集計(xxxxx)/XXXX_9xxxx_x9(XXXX_XXXX統合版) _9999.9.9XX変更対応.scptd”, kMDItemUserTags:{”レッド”}}, {kMDItemPath:”/Users/me/Xxxxxxxxx/XxxxxXxxxxx/XxxXxxx上のリプレイのダウンロード(alive)/指定IDの戦場の絆のムービーをOffLiberty経由でローカルにダウンロード v3.scpt”, kMDItemUserTags:{”レッド”}}, {kMDItemPath:”/Users/me/Xxxxxxxxx/XxxxxXxxxxx/クリップボードの内容をRTFとPDFとHTMLで書き出す v2.scptd”, kMDItemUserTags:{”レッド”}},…….}

(* scope can be a list of POSIX paths and/or the following enums:
NSMetadataQueryUserHomeScope
NSMetadataQueryLocalComputerScope
NSMetadataQueryNetworkScope
NSMetadataQueryUbiquitousDocumentsScope
NSMetadataQueryUbiquitousDataScope
NSMetadataQueryAccessibleUbiquitousExternalDocumentsScope
NSMetadataQueryIndexedLocalComputerScope — 10.10 or later
NSMetadataQueryIndexedNetworkScope

Eg:
searchInListOfPaths:{current application’s NSMetadataQueryLocalComputerScope} forTags:tagList
*)

–スコープリストの範囲のフォルダから、指定タグを含むファイルをすべてリストアップ
on searchInListOfPaths:scopeList forTags:tagList – pass empty list for any tags
  
  
– Build the search predicate
  
set tagListCount to count of tagList
  
if tagListCount = 0 then – any tags
    set pred to current application’s NSPredicate’s predicateWithFormat:(“kMDItemUserTags LIKE ’*’”)
  else if tagListCount = 1 then
    set pred to current application’s NSPredicate’s predicateWithFormat:“kMDItemUserTags CONTAINS[c] %@” argumentArray:tagList
  else
    set predList to {}
    
repeat with aTag in tagList
      set end of predList to (current application’s NSPredicate’s predicateWithFormat:“kMDItemUserTags CONTAINS[c] %@” argumentArray:{aTag})
    end repeat
    
set pred to current application’s NSCompoundPredicate’s andPredicateWithSubpredicates:predList
  end if
  
  
– make the query
  
set theQuery to current application’s NSMetadataQuery’s alloc()’s init()
  
theQuery’s setPredicate:pred
  
  
– set its scope
  
theQuery’s setSearchScopes:scopeList
  
  
– tell the notification center to tell us when it’s done
  
set notifCenter to current application’s NSNotificationCenter’s defaultCenter()
  
notifCenter’s addObserver:me selector:“searchDone:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:theQuery
  
  
– start searching
  
set my searchIsDone to false
  
theQuery’s startQuery()
  
  
– loop until it’s done
  
repeat
    delay 0.01
    
if searchIsDone then exit repeat
  end repeat
  
  
– get results
  
theQuery’s stopQuery()
  
set theCount to theQuery’s resultCount()
  
set resultsArray to current application’s NSMutableArray’s array() – to hold results
  
repeat with i from 1 to theCount
    (resultsArray’s addObject:((theQuery’s resultAtIndex:(i - 1))’s valuesForAttributes:{current application’s NSMetadataItemPathKey, “kMDItemUserTags”}))
  end repeat
  
  
return resultsArray as list – will be list of records
  
end searchInListOfPaths:forTags:

on searchDone:notification
  set my searchIsDone to true
end searchDone:

★Click Here to Open This Script 

2015/09/15 指定URLをロードして1枚ものの大きなPDFにレンダリング

Cocoaの機能を用いて、指定URLのページを読み込み、1枚ものの大きなPDFにレンダリングするAppleScriptです。

デスクトップ上に「outPdf_2.pdf」の名前で書き出します。実行には、Script Editor上でControl-Command-Rによるフォアグラウンド実行する必要があります。

まだまだ、完成にはほど遠い感じがしますが、とりあえずPDF出力できたので。

render2.png
▲本AppleScriptでレンダリングしたPDF

render1.png
▲同じページをSafariで表示させたところ

ページの上と下が「ものすごく残念な状態」になっています。一応、User AgentをSafariに合わせてみたのですが、あまり状況はよくなっていないようです。

まだまだ、本Blogを表示させると、右側のメニュー部分しか表示されなかったり、PDFの縦の長さをページ内容に合わせて自動調整できていなかったり、ページ上のグラフィックがロードされていない状態になることがあったり、、、と、前途多難な印象を受けます。

ただ、これまでこの手のオフラインレンダラーは有用な割にAppleScriptへの対応度が低く、「ならAppleScriptで自力でやっちゃえばいいじゃん」というアプローチは間違っていない(たぶん)はず。また、オフラインレンダラー自体のアップデートが割とおざなりで、OSのアップデートについてこられないことがままあります(便利なのに)。

AppleScript名:ASOCで指定URLをロードして1枚ものの大きなPDFにレンダリング
– Created 2015-09-15 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “WebKit”
use framework “Quartz”
use framework “AppKit”

property loadDone : false
property theWebView : missing value
property userAgentName : “Version/9.0 Safari/601.1.56″

set aOutPath to “~/Desktop/outPdf_2.pdf”
set aURL to “http://www.apple.com/jp/shop/browse/home/specialdeals/mac”
–set aURL to “http://piyocast.com/as”–このBlogをレンダリングすると悲惨な状態に、、、
set targX to 1024
set targY to 2000 –動的に変更できないか?

–Check If this script runs in foreground
if not (current application’s NSThread’s isMainThread()) as boolean then
  display alert “This script must be run from the main thread (Command-Control-R in Script Editor).” buttons {“Cancel”} as critical
  
error number -128
end if

set aPath to current application’s NSString’s stringWithString:aOutPath
set bPath to aPath’s stringByExpandingTildeInPath()

–URL Validation check
set aRes to validateURL(aURL)
if aRes = false then return
set aTitle to getURLandRender(aURL)
–>  ”AS Hole(AppleScriptの穴) By Piyomaru Software”
set aTargetView to (my theWebView)’s mainFrame()’s frameView()’s documentView()

set aScreen to current application’s NSScreen’s mainScreen()
set {scrX, scrY} to aScreen’s frame()’s |size|() as list

(*
set aWin to current application’s NSWindow’s alloc()’s init()
aWin’s setContentSize:{scrX, scrY}
aWin’s setContentView:(my theWebView)
aWin’s |center|()
*)

set aPrintInfo to current application’s NSPrintInfo’s sharedPrintInfo()
set newPrintOp to current application’s NSPrintOperation’s PDFOperationWithView:(aTargetView) insideRect:(current application’s NSMakeRect(0, 0, targX, targY)) toPath:bPath printInfo:aPrintInfo

set runPrint to newPrintOp’s runOperation()
return (runPrint as boolean)

–Download the URL page to WebView and get page title
on getURLandRender(aURL)
  
  
set my loadDone to false
  
set my theWebView to missing value
  
openURL(aURL)
  
  
set waitLoop to 1000 * 60 –60 seconds
  
  
set hitF to false
  
repeat waitLoop times
    if my loadDone = true then
      set hitF to true
      
exit repeat
    end if
    
current application’s NSThread’s sleepForTimeInterval:(“0.001″ as real) –delay 0.001
  end repeat
  
if hitF = false then return
  
  
set jsText to “document.title”
  
set x to ((my theWebView)’s stringByEvaluatingJavaScriptFromString:jsText) as text
  
  
return x
end getURLandRender

–Down Load URL contents to WebView
on openURL(aURL)
  set noter1 to current application’s NSNotificationCenter’s defaultCenter()
  
set (my theWebView) to current application’s WebView’s alloc()’s init()
  (
my theWebView)’s setApplicationNameForUserAgent:userAgentName
  
noter1’s addObserver:me selector:“webLoaded:” |name|:(current application’s WebViewProgressFinishedNotification) object:(my theWebView)
  (
my theWebView)’s setMainFrameURL:aURL
end openURL

–Web View’s Event Handler:load finished
on webLoaded:aNotification
  set my loadDone to true
end webLoaded:

–URL Validation Check
on validateURL(anURL as text)
  set regEx1 to current application’s NSString’s stringWithString:“((https|http)://)((\\w|-)+)(([.]|[/])((\\w|-)+))+”
  
set predicate1 to current application’s NSPredicate’s predicateWithFormat_(“SELF MATCHES %@”, regEx1)
  
set aPredRes1 to (predicate1’s evaluateWithObject:anURL) as boolean
  
return aPredRes1
end validateURL

★Click Here to Open This Script 

2015/09/07 指定URLをロードしてtitleを取得

Cocoaの機能を用いて、指定URLのページのtitleを取得するAppleScriptです。

Script Editor上でControl-Command-Rの操作でforeground実行する必要があります。

動的にWebViewを作成し、そこに指定URLを読み込み。JavaScript経由でタイトルを取得するという、ひねりも工夫もないものです。ログインなどが必要なSNS内などのページではなく、普通にただオープンすれば見られるタイプのページを前提としています。

この手の処理を行う場合には、Safariをコントロールするのが定番でしたが、GUIベースのアプリケーションであるSafariの全機能を必要としない場合には、動的にWebViewを生成して情報取得するほうが便利なケースもあります。

AppleScript名:指定URLをロードしてtitleを取得
– Created 2015-09-07 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “WebKit”

property loadDone : false
property theWebView : missing value

set aURL to “https://www.youtube.com/watch?v=WuziqYptTyE”
set aTitle to getPageTitle(aURL)
–>  ”戦場の絆ポータブル【HD】鉱山都市 オンライン対戦 2015.09.04 - YouTube”

on getPageTitle(aURL)
  –Check If this script runs in foreground
  
if not (current application’s NSThread’s isMainThread()) as boolean then
    display alert “This script must be run from the main thread (Command-Control-R in Script Editor).” buttons {“Cancel”} as critical
    
error number -128
  end if
  
  
set my loadDone to false
  
set my theWebView to missing value
  
openURL(aURL)
  
  
set waitLoop to 1000 * 60 –60 seconds
  
  
set hitF to false
  
repeat waitLoop times
    if my loadDone = true then
      set hitF to true
      
exit repeat
    end if
    
current application’s NSThread’s sleepForTimeInterval:(“0.001″ as real) –delay 0.001
  end repeat
  
if hitF = false then return
  
  
set jsText to “document.title”
  
set x to ((my theWebView)’s stringByEvaluatingJavaScriptFromString:jsText) as text
  
set my theWebView to missing value
  
  
return x
end getPageTitle

–WebViewにURLを読み込む
on openURL(aURL)
  set noter1 to current application’s NSNotificationCenter’s defaultCenter()
  
set my theWebView to current application’s WebView’s alloc()’s init()
  
noter1’s addObserver:me selector:“webLoaded:” |name|:(current application’s WebViewProgressFinishedNotification) object:(my theWebView)
  
my (theWebView’s setMainFrameURL:aURL)
end openURL

–Web Viewのローディング完了時に実行
on webLoaded:aNotification
  set my loadDone to true
end webLoaded:

★Click Here to Open This Script 

2015/09/05 ASOCでspotlight検索するじっけん v2

Cocoaの機能を用いてspotlight検索を行うAppleScriptです。Script Editor上でControl-Command-RでForeground実行する必要があります。

前バージョンでは、ファイル名しか取得できていませんでしたが、検索でヒットしたアイテムのフルパスを返すようにできたので、ようやくこれで実用レベルに達したと思います。

(1)検索対象フォルダを指定
(2)検索条件を設定

すると、実際にspotlight検索を行って結果を返します。

検索結果待ちループ側でdelayを極小にできるよう、試行錯誤してみましたが、

(1)delayを削除するとうまく動かない
(2)delay 0.01のとおりに本当に0.01秒待っているかはわからない
(3)手元の環境ではうまく動いているが、環境に応じたチューニングが必要?(2台で確認)

という印象を持っています。

ASOCで0.001という数値をプログラム内に記述するのにえっらい苦労しました。そのまま書くと指数表現されてしまい、Pure AppleScriptの世界ではエラーにならないんですが、Cocoaのオブジェクトに渡されるときにうまくブリッジされないのか、エラーに。NSSNumberでfloatValueを設定しようとしてもうまく行かないわで、結局、文字列で書いて強制的にcastするという強引な方法で解決。もっといい方法があるとよいのですが、相当苦労しました。

いろいろ条件を変えて検索の実験をしたところ、与えた検索条件でヒットがない場合には早々に{}を返してくるため、無限ループで処理が終わらないということはないようです。

これでもう、spotlight検索のためにshell commandのmdfindを呼び出す必要はなくなりました。

AppleScript Users MLの過去ログや、ShaneのEveryday AppleScriptObjC添付のサンプルコード、ASObjC Explorer 4添付のサンプルコードなども探し回ってspotlight検索のサンプルを探してみましたが、検索結果のフルパスを返すものはなかったので(ついでに言うとObjective-Cのプログラムを検索エンジンで探しても、けっこう見つからない)、これを書いておいた意義はあるものと思います。

AppleScript名:ASOCでmdfindするじっけん v2(フルパスを返す)
– Created 2015-09-03 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

property searchRes : {}

set searchRes to {} –initialize

set origPath to POSIX path of (choose folder)
initiateSearchForFullPath(“kMDItemDisplayName == ’ICON0.PNG’”, origPath) –Predicate & Scope Directory

–Waiting for the result
repeat while searchRes = {}
  current application’s NSThread’s sleepForTimeInterval:(“0.001″ as real) –delay 0.001
end repeat

set anObj to searchRes’s firstObject() –Pick up the first one for test
if anObj = missing value then return {} –No Result

–set anAttrList to anObj’s attributes() –”mdls” attributes
–>  (NSArray) {”kMDItemContentTypeTree”, “kMDItemContentType”, “kMDItemPhysicalSize”, …}

set resArray to current application’s NSMutableArray’s alloc()’s init()
repeat with anItem in searchRes
  (resArray’s addObject:(anObj’s valueForAttribute:“kMDItemPath”))
  
–>  (NSString) “/Users/me/Documents/機動戦士ガンダム戦場の絆 Folder/ULJS00181USERID_0000/ICON0.PNG”
end repeat

resArray
–>  (NSArray) {”/Users/me/Documents/機動戦士ガンダム戦場の絆 Folder/ULJS00181USERID_0000/ICON0.PNG”,……}

on initiateSearchForFullPath(aQueryStrings, origPath)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
aSearch’s setPredicate:aPredicate
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{origPath}
  
aSearch’s setSearchScopes:aScope
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemFSName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
aSearch’s startQuery()
  
end initiateSearchForFullPath

on queryDidUpdate:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0×7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = }
end queryDidUpdate:

on initalGatherComplete:sender
  set anObject to sender’s object
  
anObject’s stopQuery()
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
  
set my searchRes to anObject’s results()
end initalGatherComplete:

★Click Here to Open This Script 

2015/08/31 ASOCでspotlight検索するじっけん

Cocoaの機能を用いてspotlightによる検索を行うAppleScriptの実験的な実装です。

オリジナルは、Appleのオンラインドキュメント「File Metadata Search Programming Guide」にあった、「Listing 2-1 Static Spotlight search implementation」です。これをAppleScriptObjCに(少しアレンジを加えつつ)翻訳したものです。

Script Editor上でCommand-Control-RによるForeground実行する必要があります。プログラムリスト中におけるログ内容はASObjC Explorer 4で表示させたもので、同様の内容を確認したい場合にはASObjC Explorer 4が必要です。

海外のユーザーから「どうやって表示してんの、それ?(Cocoa Objectのログ)」と、問い合わせがあって「通常のScript Editorでは表示できないよ」と回答。意外と知られていないようで、、、(もったいないので、ここで宣伝しておくことにしました)。

ASObjC Explorer 4のログ表示は、AppleScriptのプログラムの間にログ表示用のコードを挟んでいるとのことなので、必ずしも実際のオブジェクトとイコールの内容が表示されているわけではありませんが、実際の内容をつかみやすいものになっています。この機能を持つAppleScript系のエディタは、目下のところASObjC Explorer 4だけです。

Objective-CのプログラムをさらっとAppleScriptObjCのプログラムに書き換えできるのは、ASObjC ExplorerのCocoaオブジェクトログ機能があればこそです。ログを出しつつ属性値を確認できるため、おおいに役立っています。逆に、Cocoaオブジェクトログ機能がないと、全く歯が立ちません(AppleがShaneのプログラムを買い取って、標準のScript Editorに機能をマージすればいいのに、、、)。

AppleScript名:ASOCでspotlight検索するじっけん
– Created 2015-08-31 by Takaaki Naganoya
– 2015 Piyomaru Software

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

property searchRes : {}

set searchRes to {} –initialize
initiateSearch(“kMDItemContentTypeTree == ’public.image’”)

–Waiting for the result
repeat while searchRes = {} –Inifinity loop, danger!!
  delay 0.1
end repeat

set anObj to searchRes’s firstObject() –Pick up the first one for test
set anAttrList to anObj’s attributes() –”mdls” attributes
–>  (NSArray) {”kMDItemContentTypeTree”, “kMDItemContentType”, “kMDItemPhysicalSize”, …}

set aFileName to anObj’s valueForKey:“kMDItemDisplayName”
–>  (NSString) ” 1 - 95.7.16.jpg” — example

on initiateSearch(aQueryStrings)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
–>  (NSMetadataQuery)
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
–> (NSComparisonPredicate) kMDItemContentTypeTree == “public.image”
  
aSearch’s setPredicate:aPredicate
  
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{current application’s NSMetadataQueryUserHomeScope, current application’s NSMetadataQueryUbiquitousDocumentsScope}
  
–> (NSArray) {{”kMDQueryScopeHome”, “NSMetadataQueryUbiquitousDocumentsScope”}
  
aSearch’s setSearchScopes:aScope
  
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemDisplayName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
  
aSearch’s startQuery()
  
end initiateSearch

on queryDidUpdate:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0×7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = }
end queryDidUpdate:

on initalGatherComplete:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0×7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = }
  
  
set anObject to sender’s object
  
anObject’s stopQuery()
  
  
set aRes to anObject’s resultCount()
  
–>  173352 –different in each environment. Just a test
  
  
set my searchRes to anObject’s results()
  
–>  (NSArray) {(NSMetadataItem) , (NSMetadataItem) , ……
  
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
  
set anObject to missing value –really needed?
  
end initalGatherComplete:

★Click Here to Open This Script 

2015/08/20 ASOCでスレッド処理?

Cocoaの機能を用いて、NSThreadによる並列処理のテストを行うAppleScriptです。

画像ファイルにCIFilterによるフィルタ処理を行ったのちにリサイズし、仕上げに再度CIFilterによるリサイズを行う・・・そういう処理を書いて、リサイズ前に画像にフィルタをかけるのにはそれなりに時間がかかったため、複数の画像ファイルを同時に並列で処理できるといいんじゃないか、というのがテストを行った趣旨です。

テストに用いたのがsyslogにコメントを残すという処理なので、このテスト用の処理自体があまりふさわしくないのかもしれないですが、期待していたよりも重い感じです。

とりあえず、100個(ぐらい)の画像のリサイズをシーケンシャルに順次処理する場合と、並列で(実行環境に存在するCPUのコア数を鑑みつつ並列数を決定)処理する場合とで、実際に速度を計測してみるとよいと思われます。

AppleScript名:ASOCでスレッド処理?
– Created 2015-08-20 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aThread to current application’s NSThread’s alloc()’s initWithTarget:me selector:“_threadLoop:” object:(current application’s NSString’s stringWithString:“Apple”)

set bThread to current application’s NSThread’s alloc()’s initWithTarget:me selector:“_threadLoop:” object:(current application’s NSString’s stringWithString:“Orange”)

set aRes to current application’s NSThread’s isMultiThreaded()
–>  true

set bRes to current application’s NSThread’s currentThread()
–>  (NSThread) {number = 1, name = main}

set a1Res to current application’s NSThread’s currentThread()’s threadDictionary()
–>  (NSDictionary) {NSAppleEventManagerHandlingStack:{}, NSDocumentsContinuingFileAccess:(NSSet) {}, OSADefaultComponentInstanceKey:(OSAComponentInstance) , OSADefaultLanguageKey:(OSALanguage) , OSAAvailableLanguagesKey:{(OSALanguage) , (OSALanguage) , (OSALanguage) }}

set cRes to current application’s NSThread’s callStackReturnAddresses()
–>  (NSArray) {140735481653180, 140735481652754, 140735559055105, 140735559057361, 4473123264, 4473263701, 4473142396, 4473141941, 4472935365, 4472916653, 140735705914913, 140735481264735, 4400422881, 4400227287, 140735611717816, 140735611717253, 140735611714364, 140735611713043, 140735453174803, 140735453224127, 140735482160121, 140735481878159, 140735481875416, 140735475504495, 140735475503850, 140735475503403, 140735666104491, 140735666101848, 140735666060019, 140735665521220, 140735553762761, 1}

set dRes to current application’s NSThread’s callStackSymbols()
–>  (NSArray) {”0 CoreFoundation 0×00007fff886427bc __invoking___ + 140″, ” …… (omit)

aThread’s setName:“Apple”
bThread’s setName:“Orange”

aThread’s |threadPriority|()
–> 0.5 –(0.0〜1.0)

set c1Res to aThread’s |name|()
–>  (NSString) “Apple”
set c2Res to bThread’s |name|()
–>  (NSString) “Orange”

aThread’s threadDictionary()
–>  (NSDictionary) {}

aThread’s stackSize()
–>  524288 –this depends on each machine’s memory configuration?

aThread’s isExecuting()
–>  false

aThread’s isFinished()
–>  false

aThread’s isCancelled()
–>  false

set noter1 to current application’s NSNotificationCenter’s defaultCenter()
noter1’s addObserver:me selector:“_threadWillExit:” |name|:(current application’s NSThreadWillExitNotification) object:aThread
noter1’s addObserver:me selector:“_threadWillExit:” |name|:(current application’s NSThreadWillExitNotification) object:bThread

aThread’s start()
bThread’s start()

–各Threadが実行するハンドラ
on _threadLoop:aInfo
  repeat 3 times
    set aText to aInfo as text
    
do shell script “logger -s “ & quoted form of aText & ” &”
    
set aNum to random number from 1 to 3
    
delay aNum
  end repeat
end _threadLoop:

–Threadが終了する際に呼ばれるハンドラ
on _threadWillExit:aNotification
  
  
set tmpRes to aNotification’s object’s |name|()
  
say ((tmpRes as text) & ” finished.”) using “Alex”
  
end _threadWillExit:

★Click Here to Open This Script 

2013/04/17 WebViewでローディング完了通知

WebViewで任意のURLのWebを表示し、Webコンテンツのローディングが完了したら通知するAppleScriptObjCのサンプルです。

web11.png

web2.png

自前のWebブラウザを作って、ローディング完了通知をもらうのは簡単ですが……Safari内部のローディング完了通知なんかを取れないものかと考えてしまうところ。

→ Xcodeプロジェクトのダウンロード

AppleScriptObjCファイル名:wbTestAppDelegate.applescript

– wbTestAppDelegate.applescript
– wbTest

– Created by 長野谷 隆昌 on 13/04/17.
– Copyright 2012 Piyomaru Software. All rights reserved.


script wbTestAppDelegate
  property parent : class “NSObject”
  
  property webview : missing value
  
property internetlocation : missing value
  
  
  on applicationWillFinishLaunching_(aNotification)
    
    –Web Viewのローディング完了通知をNotification Centerに登録
    
set noter1 to current application’s NSNotificationCenter’s defaultCenter()
    
noter1’s addObserver_selector_name_object_(me, “webLoaded:”, current application’s WebViewProgressFinishedNotification, webview)
    
  end applicationWillFinishLaunching_
  
  
  on applicationShouldTerminate_(sender)
    return current application’s NSTerminateNow
  end applicationShouldTerminate_
  
  
  on clicked_(aSender)
    
    set aurlobj to internetlocation’s stringValue()
    
set aurlobj to aurlobj as string
    
    –URLのプロトコルヘッダーが欠けている場合には補完
    
if (aurlobj does not start with “http://”) and (aurlobj does not start with “https://”) then
      set aurlobj to “http://” & aurlobj
    end if
    
    –指定URLをオープンする
    
webview’s setMainFrameURL_(aurlobj)
    
  end clicked_
  
  
  –Web Viewのローディング完了時に実行
  
on webLoaded_(aNotification)
    display dialog “Loaded”
  end webLoaded_
  
end script

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2013/04/16 AppleScriptObjCでPDFViewを使ってPDFをプレビュー(4)

AppleScriptObjCでPDFをプレビューするテストプログラムをその後進展させたものです。

ようやく、現在表示中のPDFの選択中のページのインデックス番号(0からはじまる通し番号)を取得できるようになりました。

→ Xcodeプロジェクトのダウンロード

PDF Kit Programming Guideの「PDF Kit Tasks」を参照し、そのとおりにAppleScriptObjCで書いても動かず、延々とデバッグを行い…………………………「document」を「|document|」と修正したところ、何の問題もなく動作しました。

このあたりは、Xcode上でチェックしたり対策してくれないと、なかなか気付きません。

AppleScriptObjC Explorer2を外部からAppleScriptでコントロールして、自動でチェックするようにしたほうがよいかもしれません(XcodeのAppleScript用語辞書の実装はあいかわらずタコなままなので、Xcodeをこづき回しても得るものが少ない。また、AppleScriptの構文色分けを反映しなくなったのは納得できません)。

本サンプルは、選択したPDFをPhotoshopでレンダリングして画像にするための「プレビュー」を目的としており、対象ファイル、対象ページ、レンダリング解像度などを取得してPhotoshopでレンダリングするフローの中で使用する部品として想定しています。実際にはこの部品は(間に合わなかったので)使用せず、もっと簡易な仕様の部品を使いましたが……次回はこの部品の改良版を投入したいところです。

pdf1.png

pdf2.png

AppleScriptObjCファイル名:AppDelegate.applescript

– AppDelegate.applescript
– pdftest2

– Created by Takaaki Naganoya on 2013/04/16.
– Copyright (c) 2012年 Takaaki Naganoya. All rights reserved.


script AppDelegate
  property parent : class “NSObject”
  
property nsurl : class “NSURL”
  
property PDFDocument : class “PDFDocument”
  
  
  property pdfPreviewWin : missing value
  
property pdfView : missing value
  
property pdfThumb : missing value
  
  property pdfPageText : missing value
  
  
  
  on applicationWillFinishLaunching_(aNotification)
    
    pdfView’s setAllowsDragging_(false)
    
pdfView’s setDelegate_(me)
    
    
    pdfThumb’s setMaximumNumberOfColumns_(2)
    
pdfThumb’s setAllowsMultipleSelection_(false)
    
    –書類が変更になった(オープンされた場合)Notificationの処理先ハンドラを登録
    
set noter1 to current application’s NSNotificationCenter’s defaultCenter()
    
noter1’s addObserver_selector_name_object_(me, “pdfChange:”, current application’s PDFViewDocumentChangedNotification, pdfView)
    
    –ページが変更になったNotificationの処理先ハンドラを登録
    
noter1’s addObserver_selector_name_object_(me, “pdfPageChange:”, current application’s PDFViewPageChangedNotification, pdfView)
    
    
    
    –ファイルを選択
    
set aFile to choose file of type “com.adobe.pdf”
    
set aFileString to aFile as Unicode text
    
dispPDFWithHFSpath_(aFileString)
    
    
  end applicationWillFinishLaunching_
  
  
  
  –プレビューウィンドウをセンタリング(見た目以外にとくに意味なし)
  
on applicationDidFinishLaunching_(aNotification)
    
    pdfPreviewWin’s |center|()
    
  end applicationDidFinishLaunching_
  
  
  
  –HFSパスの文字列を渡すとPDFViewに表示する
  
on dispPDFWithHFSpath_(aFileString)
    
    set aFileString to aFileString as Unicode text
    
    tell application “Finder”
      set aFileURL to (URL of file aFileString)
    end tell
    
    set aFileURL to aFileURL as Unicode text
    
set theURL to nsurl’s URLWithString_(aFileURL)
    
    set aPDFdoc to PDFDocument’s alloc()’s initWithURL_(theURL) –allocわすれてた、、、
    
pdfView’s setDocument_(aPDFdoc)
    
  end dispPDFWithHFSpath_
  
  
  on applicationShouldTerminate_(sender)
    return current application’s NSTerminateNow
  end applicationShouldTerminate_
  
  
  –表示ページが変更になった場合にnotificationでこちらを呼び出している
  
on pdfPageChange_(notification)
    
    dispPDFPageNumber()
    
  end pdfPageChange_
  
  
  –ファイルの新規オープン、および表示ページが変更になった場合にnotificationでこちらを呼び出している
  
on pdfChange_(notification)
    
    dispPDFPageNumber()
    
  end pdfChange_
  
  
  –PDFページ数を表示する
  
on dispPDFPageNumber()
    
    set curDoc to pdfView’s |document| –ここが問題の箇所!!!
    
set curPage to pdfView’s currentPage
    
set curPageNumber to curDoc’s indexForPage_(curPage)
    
    pdfPageText’s setStringValue_((curPageNumber + 1) as string)
    
  end dispPDFPageNumber
  
  
end script

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2012/12/27 AppleScriptObjCでPDFViewを使ってPDFをプレビュー

PDFのファイルを選択して、Photoshopで画像フォーマット変換を行うため、対象ページ数と解像度を指定しなければなりませんでした。そこで、PDF内の各ページのプレビューを行うウィンドウを表示すべく、AppleScriptObjCでPDFプレビュー用の試作品を作ってみました。

pdfview.png

とりあえず、指定したPDFのファイルをプレビューできています。本来なら、これで十分なはずですが……PDFビューで表示しているPDF内のリンクなどをDisableにできていないので、表示中の内容をクリックすると別のページに飛べてしまったりします。

あとは、現在表示中のページ数を取得できるとよいのですが……まだ構造を調べながらいろいろつっついている状態なので、まだできていません。さらに、パスワードが設定されていた場合への対処なども手を打っておく必要があるでしょう。

→ Xcodeプロジェクトのダウンロード

AppleScriptObjCファイル名:AppDelegate.applescript

– AppDelegate.applescript
– pdftest2

– Created by Takaaki Naganoya on 2012/10/23.
– Copyright (c) 2012年 Takaaki Naganoya. All rights reserved.


script AppDelegate
  property parent : class “NSObject”
  
property nsurl : class “NSURL”
  
property PDFDocument : class “PDFDocument”
  
  
  property pdfPreviewWin : missing value
  
property pdfView : missing value
  
property pdfThumb : missing value
  
  property pdfPageText : missing value
  
  
  
  on applicationWillFinishLaunching_(aNotification)
    
    pdfView’s setAllowsDragging_(true)
    
pdfView’s setDelegate_(me)
    
    
    pdfThumb’s setMaximumNumberOfColumns_(2)
    
    
    –書類が変更になった(オープンされた場合)
    
set noter1 to current application’s NSNotificationCenter’s defaultCenter()
    
noter1’s addObserver_selector_name_object_(me, “pdfChange:”, current application’s PDFViewDocumentChangedNotification, missing value)
    
    –ページが変更になった
    
set noter2 to current application’s NSNotificationCenter’s defaultCenter()
    
noter2’s addObserver_selector_name_object_(me, “pdfChange:”, current application’s PDFViewPageChangedNotification, missing value)
    
    
    –ファイルを選択
    
set aFile to choose file of type “com.adobe.pdf”
    
set aFileString to aFile as Unicode text
    
dispPDFWithHFSpath_(aFileString)
    
    
  end applicationWillFinishLaunching_
  
  
  
  –プレビューウィンドウをセンタリング(見た目以外にとくに意味なし)
  
on applicationDidFinishLaunching_(aNotification)
    
    pdfPreviewWin’s |center|()
    
  end applicationDidFinishLaunching_
  
  
  
  –HFSパスの文字列を渡すとPDFViewに表示する
  
on dispPDFWithHFSpath_(aFileString)
    
    set aFileString to aFileString as Unicode text
    
    tell application “Finder”
      set aFileURL to (URL of file aFileString)
    end tell
    
    set aFileURL to aFileURL as Unicode text
    
set theURL to nsurl’s URLWithString_(aFileURL)
    
    set aPDFdoc to PDFDocument’s alloc()’s initWithURL_(theURL) –allocわすれてた、、、
    
pdfView’s setDocument_(aPDFdoc)
    
  end dispPDFWithHFSpath_
  
  
  on applicationShouldTerminate_(sender)
    return current application’s NSTerminateNow
  end applicationShouldTerminate_
  
  
  –ファイルの新規オープン、および表示ページが変更になった場合にnotificationでこちらを呼び出している
  
–ここで、選択中のページのノンブルを取得したい(まだできていない)
  
on pdfChange_(sender)
    set curPage to pdfView’s currentPage
    
–set curPageNum to curPage’s indexForPage
    
    log curPage
  end pdfChange_
  
end script

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2012/06/13 おかえり(シンプル版)

読者の方(edama2さん)からの投稿です。以前に私がAppleScript Studioで作成して公開していた(る?)、スリープ解除検知&ユーザー通知アプリケーション「おかえり」の簡略版をMac OS X 10.7から作れるようになったAppleScriptエディタベースのAppleScriptObjCで作られました。

yyyeyoyycyaye-2012-06-13-02922.jpeg

yyyeyoyycyaye-2012-06-10-235739.jpeg

投稿していただいてから公開するまでに時間がかかってしまいましたが……実は、AppleScriptエディタベースのASOCのHTML書き出しツールを用意していなかったためで……ようやく、書き出し用のツール(AppleScriptで記述)を準備しました。

——
<edama2さんより>
今日のネタは「おかえり(シンプル版)」です。なかなか更新されない「おかえり」にいらだちを覚え、ついに自分で作り……というのは冗談で、「Cocoa-applescript aplet」の習作として作ってみました。

スリープの復帰の通知を受け取り、半透明のウインドウの表示と音を鳴らすだけです。誕生日とか正月だけの特別なウィンドウはありません。

ウインドウの色と音を変更できるようにしたいところですが、Xcodeを使わずコードだけでウィンドウを作るのは結構面倒だったのとシンプル版ということで省略しました。オリジナルの「おかえり」から「Twentieth Anniversary Macintosh.aiff」だけ使用させていただきました。

最初、同じスプリクト内にカスタムビューのコード(MyView.scpt)を書いていたのですが、初期化の際にエラーが出て難儀しました。どうやら別ファイルにしないと上手く初期化してくれないようです。

作成は10.7.4、テストは10.7.4と10.6.8でしました。ASOC Scriptの新規作成は10.7.4でないとできませんが、編集は書き方によっては10.6.8でもできました。

→ アプレット(編集可能)のダウンロード
</edama2さんより>

スクリプト名:おかえり(シンプル版)
property _sound_name : “Twentieth Anniversary Macintosh”

on run
  #通知の登録
  
tell current application’s NSWorkspace’s sharedWorkspace()
    tell notificationCenter()
      addObserver_selector_name_object_(me, “okaeri:”, “NSWorkspaceScreensDidWakeNotification”, missing value)
    end tell
  end tell
end run

on quit
  current application’s NSNotificationCenter’s defaultCenter()’s removeObserver_(me)
  
continue quit
end quit

#ウィンドウを表示→待機→閉じる
on okaeri_(sender)
  set pool to current application’s NSAutoreleasePool’s new() –>意味があるかよくわからないけどとりあえず。
  
  
copy makeWin() to okaeriWindow
  
  
set filePath to current application’s NSBundle’s mainBundle()’s pathForResource_ofType_(_sound_name, “aiff”)
  
current application’s NSSound’s alloc()’s initWithContentsOfFile_byReference_(filePath, true)’s play()
  
  
delay 5
  
  
closeWin_(okaeriWindow)
  
  
pool’s drain() –release()
end okaeri_

#ウィンドウを作成
on makeWin()
  set aScreen to current application’s NSScreen’s mainScreen()
  
set aFrame to {{0, 0}, {800, 250}}
  
set aBacking to current application’s NSBorderlessWindowMask
  
set aDefer to current application’s NSBackingStoreBuffered
  
  
tell current application’s NSWindow’s alloc()
    tell initWithContentRect_styleMask_backing_defer_screen_(aFrame, aBacking, aDefer, false, aScreen)’s autorelease()
      
      
#カスタムビュー
      
tell current application’s MyView’s alloc()
        tell initWithFrame_(aFrame)
          setNeedsDisplay_(true)
          
set aView to it
        end tell
      end tell
      
      
set {origin:{x:x, y:y}, |size|:{width:w, height:h}} to contentView()’s |bounds|()
      
set titleHeight to 26
      
set y2 to h - titleHeight - (titleHeight / 6)
      
      
#タイトルバー
      
set messageText to “スリープ復帰を検出しました(” & (current date) & “)” as text
      
tell current application’s NSTextView’s alloc()
        tell initWithFrame_({{0, y2}, {w, titleHeight}})
          insertText_(messageText)
          
setAlignment_(current application’s NSCenterTextAlignment)
          
setDrawsBackground_(false)
          
setEditable_(false)
          
setFont_(current application’s NSFont’s titleBarFontOfSize_(13))
          
setSelectable_(false)
          
setTextColor_(current application’s NSColor’s whiteColor())
          
aView’s addSubview_(it)
        end tell
      end tell
      
      
set y3 to h - titleHeight
      
set y3 to h - y3 - (y3 / 6)
      
      
#メッセージビュー
      
set myName to current application’s NSProcessInfo’s processInfo()’s environment()’s valueForKey_(”LOGNAME”) as text
      
set messageText to myName & “さん” & return & “おかえりなさい”
      
tell current application’s NSTextView’s alloc()
        tell initWithFrame_({{0, y3}, {w, y2}})
          insertText_(messageText)
          
setAlignment_(current application’s NSCenterTextAlignment)
          
setDrawsBackground_(false)
          
setEditable_(false)
          
setFont_(current application’s NSFont’s fontWithName_size_(”HiraMinPro-W3″, 72))
          
setSelectable_(false)
          
setTextColor_(current application’s NSColor’s whiteColor())
          
aView’s addSubview_(it)
        end tell
      end tell
      
      
#ウィンドウの設定
      
setBackgroundColor_(current application’s NSColor’s clearColor())
      
setContentView_(aView)
      
setDelegate_(me)
      
setDisplaysWhenScreenProfileChanges_(true)
      
setHasShadow_(true)
      
setIgnoresMouseEvents_(not false)
      
setLevel_((current application’s NSScreenSaverWindowLevel) + 10000)
      
setOpaque_(false)
      
setReleasedWhenClosed_(true)
      
|center|()
      
makeKeyAndOrderFront_(me)
      
      
return it
    end tell
  end tell
end makeWin

#ウィンドウを閉じる
on closeWin_(aWindow)
  tell aWindow
    repeat with n from 10 to 1 by -1
      setAlphaValue_(n / 10)
      
delay 0.02
    end repeat
    
|close|()
  end tell
end closeWin_

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

スクリプト名:MyView
script MyView
  property parent : class “NSView”
  
  
on drawRect_(rect)
    
    
–log my |bounds|()
    
set {origin:{x:x, y:y}, |size|:{width:w, height:h}} to my |bounds|()
    
set titleHeight to 26
    
    
set thePath to current application’s NSBezierPath’s bezierPath()
    
    
#タイトルバー
    
set aFrame to {{0, h - titleHeight}, {w, titleHeight}}
    
set aPath to current application’s NSBezierPath’s bezierPathWithRect_(aFrame)
    
set strartColor to current application’s NSColor’s blackColor()’s colorWithAlphaComponent_(0.4)
    
set endColor to current application’s NSColor’s blackColor()’s colorWithAlphaComponent_(0.8)
    
tell current application’s NSGradient’s alloc()
      tell initWithStartingColor_endingColor_(strartColor, endColor)
        drawInBezierPath_angle_(aPath, 270)
      end tell
    end tell
    
thePath’s appendBezierPath_(aPath)
    
    
#メッセージ
    
set aFrame to {{0, 0}, {w, h - titleHeight}}
    
set aPath to current application’s NSBezierPath’s bezierPathWithRect_(aFrame)
    
set strartColor to current application’s NSColor’s blueColor()’s colorWithAlphaComponent_(0.4)
    
set endColor to current application’s NSColor’s blueColor()’s colorWithAlphaComponent_(0.8)
    
tell current application’s NSGradient’s alloc()
      tell initWithStartingColor_endingColor_(strartColor, endColor)
        drawInBezierPath_angle_(aPath, 270)
      end tell
    end tell
    
thePath’s appendBezierPath_(aPath)
    
  end drawRect_
  
end script

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

スクリプト名:CocoaAppletAppDelegate.scpt

– CocoaAppletAppDelegate.applescript
– Cocoa-AppleScript Applet

– Copyright 2011 {Your Company}. All rights reserved.

– This application delegate emulates the OSA script applet by loading “main.scpt” from the
– “Scripts” folder in the application resources and invoking the traditional run/open/reopen/quit
– handlers in response to Cocoa application delegate methods being called.

– This is provided in source form so that you may customize or replace it if your needs go
– beyond the basic applet handlers.

– Some of these methods must guard against re-entrancy, because invoking the main.scpt
– handler may end up invoking the event handler inherited from the current application,
– which calls the application delegate’s method again.

script CocoaAppletAppDelegate
  property parent : class “NSObject”
  
property mainScript : missing value – the applet’s main.scpt
  
property didOpenFiles : false – true = the application opened documents during startup
  
property isOpeningFiles : false – re-entrancy guard: true = in the process of opening files
  
property isReopening : false – re-entrancy guard: true = in the process of re-opening
  
property isQuitting : false – re-entrancy guard: true = in the process of quitting
  
  
on applicationWillFinishLaunching_(aNotification)
    – Insert code here to initialize your application before any files are opened
    
    
– Emulate an OSA Applet: Load the main script from the Scripts resource folder.
    
try
      set my mainScript to load script (path to resource “main.scpt” in directory “Scripts”)
    on error errMsg number errNum
      – Perhaps this should silently fail if it can’t load the script; that way, a Cocoa applet
      
– can just have Cocoa classes and no main.scpt.
      
display alert “Could not load main.scpt” message errMsg & ” (” & errNum & “)” as critical
    end try
  end applicationWillFinishLaunching_
  
  
on applicationDidFinishLaunching_(aNotification)
    – Insert code here to do startup actions after your application has initialized
    
    
if mainScript is missing value then return
    
    
– Emulate an OSA Applet: Invoke the “run” handler.
    
    
– If we have already opened files during startup, don’t invoke the run handler.
    
if didOpenFiles then return
    
    
try
      tell mainScript to run
    on error errMsg number errNum
      if errNum is not -128 then
        display alert “An error occurred while running” message errMsg & ” (” & errNum & “)” as critical
      end if
    end try
    
    
– TODO: Read the applet’s “stay open” flag and quit if it’s false or unspecified.
    
– For now, all Cocoa Applets stay open and require the run handler to explicitly quit,
    
– which is arguably more correct for a Cocoa application, anyway.
    
(* if not shouldStayOpen then
      quit
    end if *)
  end applicationDidFinishLaunching_
  
  
on applicationShouldHandleReopen_hasVisibleWindows_(sender, flag)
    – Insert code here to perform actions in response to a “reopen” event
    
    
if mainScript is missing value then return true
    
    
– Guard against re-entrancy.
    
if not isReopening then
      set isReopening to true
      
      
– Emulate an OSA Applet: Invoke the “reopen” handler. If there isn’t one, let the application object
      
– handle reopen (this is different from an OSA applet, which would do nothing if there is no handler;
      
– this way, the application will perform the usual “create untitled document” behavior by default).
      
try
        tell mainScript to reopen
        
set isReopening to false
        
        
return false
      on error errMsg number errNum
        if errNum is not -128 then
          display alert “An error occurred while reopening” message errMsg & ” (” & errNum & “)” as critical
        end if
      end try
      
      
set isReopening to false
    end if
    
    
return true
  end applicationShouldHandleReopen_hasVisibleWindows_
  
  
on application_openFiles_(sender, filenames)
    – Insert code here to perform actions in response to an “open documents” event
    
    
– Remember that we opened files, to avoid invoking the “run” handler later.
    
set didOpenFiles to true
    
    
– Guard against re-entrancy.
    
if not isOpeningFiles and mainScript is not missing value then
      set isOpeningFiles to true
      
      
try
        – Convert all the filenames from NSStrings to script strings
        
set theFilenameStrings to {}
        
repeat with eachFile in filenames
          set theFilenameStrings to theFilenameStrings & (eachFile as text)
        end repeat
        
        
tell mainScript to open theFilenameStrings
        
set isOpeningFiles to false
        
        
tell sender to replyToOpenOrPrint_(current application’s NSApplicationDelegateReplySuccess)
      on error errMsg number errNum
        if errNum = -128 then
          tell sender to replyToOpenOrPrint_(current application’s NSApplicationDelegateReplyCancel)
        else
          display alert “An error occurred while opening file(s)” message errMsg & ” (” & errNum & “)” as critical
          
tell sender to replyToOpenOrPrint_(current application’s NSApplicationDelegateReplyFailure)
        end if
      end try
      
      
set isOpeningFiles to false
    else
      tell sender to replyToOpenOrPrint_(current application’s NSApplicationDelegateReplyFailure)
    end if
  end application_openFiles_
  
  
on applicationShouldTerminate_(sender)
    – Insert code here to do any housekeeping before your application quits
    
    
– Guard against re-entrancy.
    
if not isQuitting and mainScript is not missing value then
      set isQuitting to true
      
      
– Emulate an OSA Applet: Invoke the “quit” handler; if the handler returns, it has fully
      
– handled the quit message and we should not quit, otherwise, it calls “continue quit”,
      
– which returns error -10000.
      
try
        tell mainScript to quit
        
set isQuitting to false
        
        
return current application’s NSTerminateCancel
      on error errMsg number errNum
        – -128 means there is no quit handler
        
– -10000 means the handler did “continue quit”
        
if errNum is not -128 and errNum is not -10000 then
          display alert “An error occurred while quitting” message errMsg & ” (” & errNum & “)” as critical
        end if
      end try
      
      
set isQuitting to false
    end if
    
    
return current application’s NSTerminateNow
  end applicationShouldTerminate_
  
end script

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

オリジナルの「おかえり」のコアコードは10行ぐらいで、このサンプルよりもはるかに単純な原理で動いています。
いまから10年近く前にすでに動いていたもので、オリジナルコードはAppleScriptだけで記述。公開版は透明ウィンドウを付けるため(だけ)にAppleScript Studioで開発しました。

「おかえり」のアップデートが止まっていたのは、自分が欲しい機能をてんこ盛りに盛り込んで……画面をInterface Builder上でデザインした時点で「こんなに大量のGUI部品のコードを書くのは大変」なことに気づき……そのまま放置状態に陥ってしまったためでしょう(Piyocastをはじめ、個人的な優先順位の高い自作の未公開アプリがいくつもあるので)。

アプリが自分自身のコード量で自滅した、といえなくもありません。

当時は、Mac AppStoreも存在していませんでしたし、フリーで配布するにはバージョンアップに手がかかりすぎる自作ソフト群にうんざりしていた、ということもあります。

いまだったら、AppleScriptObjCならGUI部品のサポートコードを書くのもそれほど手間ではないので、バージョンアップして公開してみてもよいかもしれません。