Archive for 9月, 2009

2009/09/28 AppleScriptObjCのドキュメント公開が開始される

Appleのwww.macosxautomation.comサイト上にて、AppleScriptObjCのドキュメント公開が開始されました。

「公開が開始された」というのは、はなはだおかしな日本語ですが……従来のようにリファレンスのドキュメントが公開されたり、サンプルコードが一括でローカルに置かれたり……というスタイルではないようです。

Sal Soghoianが9/25にAppleScript Studio MLに投稿した内容も、ドキュメントといいつつムービーの掲載に関する話であり、サンプルコードが一気に出てくるとか、リファレンスのドキュメントが見られるようになったという話ではありません。

ただ、このガイダンスのムービーはよくできているので、時間のある時に何度も繰り返してみるようにしています。

従来のAppleScript Studioでは、各種イベントハンドラをNibファイル側でオンにする必要がありましたが、AppleScriptObjCではプログラム中に書いておくだけで大丈夫なのだろうか……などなど、リファレンスがないと心細いかぎりですが、とりあえずないよりはマシといったところでしょうか。

2009/09/26 Mail.appのメールボックスオブジェクトを渡すと、テキストのフルパスに変換 v1

Mail.appのmailboxオブジェクトを与えると、そのフォルダ階層のパスを「/」で区切ったテキストにして返すサブルーチンです。

mailfol.jpg

Mail.appのmailboxオブジェクトのプロパティには、名称や上位階層のmailboxオブジェクトの情報は入っているものの、現在の階層を示すフルパスの情報が入っていません。mailboxオブジェクトを生成する際に、フォルダ階層のテキストで指定することになるので、mailboxオブジェクトを相手にするのではなく、その元となるテキスト情報を相手にすれば「楽」に処理はできます。ただ、それだとあまり柔軟性に富んだ処理が行えません。

そこで、mailboxオブジェクトを渡すと、フルパスをテキスト化するAppleScriptを書いてみました。

そもそも、こんなルーチンが欲しいと思ったのは……Mac OS X 10.6が登場して、Appleの各MLフォルダ内に「10.6, Snow Leopard」(カンマで区切って名前を付けておくと、それらのどれかでヒットしたメールをフォルダに振り分けるAppleScriptを運用中)といったフォルダを一括で作成する必要が出てきたためで……ほぼすべてのUS Appleのデベロッパー系MLを購読しているため、それをすべて手作業で行うのは骨が折れます。また、すでに手でフォルダを作ってあるところに対しては作成しない、という処理も行わなくてはなりません。

スクリプト名:Mail.appのメールボックスオブジェクトを渡すと、テキストのフルパスに変換 v1
(*

Mail.appのmailboxオブジェクトはフルパスのプロパティを持っていないため、オブジェクトで渡されると、それがどのパスに存在
しているものなのか、処理するのが非常に面倒。

そこで、mailboxオブジェクトを渡すと、再帰でパスを求めるルーチンを作ってみた。
処理前にグローバル変数aFullPathをクリアしておく必要がある
ルート階層に達したときにエラートラップで判定を行うが、ここはもう少しスマートな書き方に変えたほうがいい

*)

global aFullPath

set aFullPath to “”
tell application “Mail”
  set aMB to properties of mailbox “ML/Apple US/ASに関係のあるもの/SyncService” –mailboxオブジェクトを作ってみた
end tell

extraxctTextFullPathOfMBObject(aMB) of me
aFullPath
–> “ML/Apple US/ASに関係のあるもの/SyncService”

–Mail.appのメールボックスオブジェクトを渡すと、テキストのフルパスに変換
on extraxctTextFullPathOfMBObject(aPath)
  tell application “Mail”
    try
      set parentPath to container of aPath
    on error
      return
    end try
    
    
set meName to name of aPath
    
if aFullPath = “” then –1回目のみスラッシュを入れないで処理
      set aFullPath to meName
    else
      –通常処理はこちら
      
set aFullPath to meName & “/” & aFullPath
    end if
    
    
extraxctTextFullPathOfMBObject(parentPath) of me –再帰呼び出し
  end tell
end extraxctTextFullPathOfMBObject

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

2009/09/19 Sin Cos演算ルーチン

三角関数のサイン、コサインを演算するサブルーチンです。海外でひろってきました。

先日、知人から「AppleScriptで三角関数を求めるための命令はないのか?」と聞かれたのですが、標準ではAppleScriptの処理系に三角関数を求める命令は入っていません。

サードパーティのOSAX(Scripting Additionとも呼ばれる)を見つけてきて、プログラミングを行うMacの環境および実行するMacの環境に同じOSAXをインストールしておく必要があります(バンドル形式のアプリケーションにして、バンドル内にOSAXを入れておくという手もあります)。

実際、仏Satimage Softwareが配布している「Satimage」OSAXにはSin/Cosをはじめとする数値関数の命令が入っています。たいていの場合にはこれを利用するのがよいでしょう。

ただし、どうしても実行環境にOSAXをインストールしたくないとか、どこで実行しても大丈夫なようにしておきたいという場合には、このような三角関数演算ルーチンを作ったり拾ってきたりすることになることでしょう。

または、たいてい近似値で済む場合が多いですし、パラメータに整数しか渡さないといった条件が整っている場合には、360度分のSinテーブル、Cosテーブルを作成しておいて、そこから求めるという手もあります。

以前に、Aboutメニューでいろんな文字が画面上を楕円運動するようなAppleScript Studioのアプリケーションを作ってみましたが、その時にはこのSin/Cosテーブルを用意して実装した次第です。

スクリプト名:Sin Cos演算ルーチン
set a to sine_of(47) of me
–> 0.731353701619

set b to cosine_of(71) of me
–> 0.325568154457

on sine_of(x)
  repeat until x 0 and x < 360
    if x 360 then
      set x to x - 360
    end if
    
if x < 0 then
      set x to x + 360
    end if
  end repeat
  
  
set x to x * (2 * pi) / 360 –convert from degrees to radians
  
  
set answer to 0
  
set numerator to x
  
set denominator to 1
  
set factor to -(x ^ 2)
  
  
repeat with i from 3 to 40 by 2
    set answer to answer + numerator / denominator
    
set numerator to numerator * factor
    
set denominator to denominator * i * (i - 1)
  end repeat
  
  
return answer
end sine_of

on cosine_of(x)
  return sine_of(x + 90)
end cosine_of

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

2009/09/19 Diary++Xで最近の日記本文をもとに今日の日記を新規作成する v3

Diary++X 1.0.3用にこのAppleScriptを書いて使ってみたものの、実際に使ってみるとだんだん問題点が見えてきました。

たとえば、9月19日の日記(たいていは仕事の内容やアイデアなどをまとめたもの)を書こうとして、このAppleScriptを実行。Script Menuに入れておいてメニューから実行することになるわけですが、その時にDiary++Xのカレンダービューで9月19日を選択した状態だと、なぜか「D 2009-9-19 1」ではなく、「D 2009-9-19 2」というエントリができてしまいます。

作ろうとしたエントリのIDが勝手にアプリケーション側の都合で変わってしまうのであれば、以前に指摘したように、

set newArticle to (make new article)
tell newArticle
 –何かの命令
end

のように、作成時にエントリへの参照を返すべきです。参照は返さないわ、指定したIDでエントリを作れないわでは話になりません。

ただ、文句を言っているばかりでは話にならないので、アプリケーションの挙動(バグの度合い)を見ながら、解決方法を模索することになります(Adobeのアプリケーションのバグの多さには辟易するばかり。本当にあの会社は出荷前にチェックをしているのか疑問です)。

この現象は、ユーザーがGUIからカレンダービュー上の9月19日のセルを選択した瞬間に、Diary++Xがこっそり「D 2009-0-19 1」というエントリを(ないしょで)確保。この日付に対してユーザーからのキーボード入力が行われた場合に備えておくようにしているのではないか? このため、エントリのIDにズレが生じるのではないか、と推測しました。

そこで、「D 2009-0-19 1」というエントリを作成する前に、この「9月19日」以外のどこか別の日付のエントリを選択した状態を作り出しておき、その後で「D 2009-0-19 1」を作成してやれば問題はないのではないか? と考え、その通りにAppleScriptを修正。

結果は、想像どおり。このバージョン3で、無事に直近の日記をもとに本日の日記エントリを作成するという構想を満たすものとなりました。

かように、AppleScriptでアプリケーションをコントロールするということは、そのアプリケーションの機能や方向性を理解し、挙動のクセを把握したうえで、実際にプログラムを書いて実行してはきちんと動くかどうか確認を行うという……ひじょうに地味で地道な作業の繰り返しが要求されます。

ただ、それをひたすら積み上げていくと、トンでもないことができるようになってみたりするものです。

スクリプト名:Diary++Xで最近の日記本文をもとに今日の日記を新規作成する v3
今日のアーティクルが存在しているかどうかチェック
set todaysDate to do shell script “date +%Y-%m-%d”
set anArticleID to (”D ” & todaysDate & ” 1″)
set aRes to retExsists(anArticleID) of me
if aRes = true then
  すでに今日の日記が存在したら処理終了
  
display dialogすでに本日の日記が存在するので処理を中断しますbuttons {”OK”} default button 1 with icon 1
  
return
end if

今日の日記が存在しない場合(想定しているパターン)
set decCount to 1
set curDate to current date

過去にさかのぼって日記の記事を検索(ただし100日前まで)
repeat
  set curDateTmp to curDate - decCount * days
  
  
set aYear to year of curDateTmp
  
set aMonth to month of curDateTmp as number
  
set aDate to day of curDateTmp
  
  
set aYearStr to retZeroPaddingText(aYear, 4) of me
  
set aMonthStr to retZeroPaddingText(aMonth, 2) of me
  
set aDateStr to retZeroPaddingText(aDate, 2) of me
  
  
set todaysDateTmp to aYearStr & “-” & aMonthStr & “-” & aDateStr
  
set anArticleIDTmp to (”D ” & todaysDateTmp & ” 1″)
  
set aRes to retExsists(anArticleIDTmp) of me
  
  
if aRes = true then exit repeat
  
  
set decCount to decCount + 1
  
if decCount = 100 then
    display dialog “100日さかのぼりましたが、過去の日記は存在しませんでした。buttons {”OK”} default button 1 with icon 1
    
return
  end if
end repeat

発見した直近の日記の本文をコピーして本日の日記を作成する
tell application “Diary++X”
  tell document 1
    
    
set current article to article id anArticleIDTmp ★ ここを付け足した ★
    
本日の日付がカレンダー上で選択されている状態で本日の日記をAS側から作成しようとすると、
    
–1番の記事がリザーブされてしまうためか、2番の記事が作成されてしまう(アプリ側のバグ)
    
これを回避するため、一時的に選択日付を今日以外の場所に移動させる。記事が存在さえして
    
いれば別に直近の記事でなくてもよい
    
    
    
直近のアーティクル本文をコピー    
    
tell article id anArticleIDTmp
      set oldContents to text contents
    end tell
    
    
今日の日記アーティクルを作成し、本文に一番近い過去のアーティクル本文を突っ込む
    
make new article
    
tell article id anArticleID
      set text contents to oldContents
    end tell
    
    
作成した日記を表示状態に
    
set current article to article id anArticleID
  end tell
end tell

指定IDのアーティクルが存在するかどうか調べて返す
on retExsists(anArticleID)
  tell application “Diary++X”
    tell document 1
      set dExists to (exists of article id anArticleID)
    end tell
  end tell
  
return dExists
end retExsists

数値にゼロパディングしたテキストを返す
on retZeroPaddingText(aNum, aLen)
  set tText to (”0000000000″ & aNum as text)
  
set tCount to length of tText
  
set resText to text (tCount - aLen + 1) thru tCount of tText
  
return resText
end retZeroPaddingText

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

2009/09/17 9/19にホスティングサービス側の都合で5分間ダウン

9月19日(土) AM3:00〜AM7:30の間に、サーバーの再起動にともない5分間程度のサービス停止が発生する予定です。

ぴよまるソフトウェア

2009/09/16 Diary++Xで最近の日記本文をもとに今日の日記を新規作成する v2

Diary++ Xで最近の日記本文をもとに今日の日記を新規作成するAppleScriptのバグ修正版です。

日付をさかのぼる際に、current dateを保持する変数を更新してしまっていたので、その点を直しています。

スクリプト名:Diary++Xで機能の内容を今日の日記にコピーする v2
–今日のアーティクルが存在しているかどうかチェック
set todaysDate to do shell script "date +%Y-%m-%d"
set anArticleID to ("D " & todaysDate & " 1")
set aRes to retExsists(anArticleID) of me
if aRes = true then
  –すでに今日の日記が存在したら処理終了
  
display dialog "すでに本日の日記が存在するので処理を中断します" buttons {"OK"} default button 1 with icon 1
  
return
end if

–今日の日記が存在しない場合(想定しているパターン)
set decCount to 1
set curDate to current date

–過去にさかのぼって日記の記事を検索(ただし100日前まで)
repeat
  set curDateTmp to curDate - decCount * days
  
  
set aYear to year of curDateTmp
  
set aMonth to month of curDateTmp as number
  
set aDate to day of curDateTmp
  
  
set aYearStr to retZeroPaddingText(aYear, 4) of me
  
set aMonthStr to retZeroPaddingText(aMonth, 2) of me
  
set aDateStr to retZeroPaddingText(aDate, 2) of me
  
  
set todaysDateTmp to aYearStr & "-" & aMonthStr & "-" & aDateStr
  
set anArticleIDTmp to ("D " & todaysDateTmp & " 1")
  
set aRes to retExsists(anArticleIDTmp) of me
  
  
if aRes = true then exit repeat
  
  
set decCount to decCount + 1
  
if decCount = 100 then
    display dialog "100日さかのぼりましたが、過去の日記は存在しませんでした。" buttons {"OK"} default button 1 with icon 1
    
return
  end if
end repeat

–過去の日記の存在確認
tell application "Diary++X"
  tell document 1
    –ヒットした、一番近い過去のアーティクル本文をコピー
    
tell article id anArticleIDTmp
      set oldContents to text contents
    end tell
    
    
–今日の日記アーティクルを作成し、本文に一番近い過去のアーティクル本文を突っ込む
    
make new article
    
tell article id anArticleID
      set text contents to oldContents
    end tell
    
    
–作成した日記を表示状態に
    
set current article to article id anArticleID
  end tell
end tell

–指定IDのアーティクルが存在するかどうか調べて返す
on retExsists(anArticleID)
  tell application "Diary++X"
    tell document 1
      set dExists to (exists of article id anArticleID)
    end tell
  end tell
  
return dExists
end retExsists

–数値にゼロパディングしたテキストを返す
on retZeroPaddingText(aNum, aLen)
  set tText to ("0000000000" & aNum as text)
  
set tCount to length of tText
  
set resText to text (tCount - aLen + 1) thru tCount of tText
  
return resText
end retZeroPaddingText

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

2009/09/16 Diary++Xで最近の日記本文をもとに今日の日記を新規作成する

Diary++ X バージョン1.0.3以降で、当日の日記アーティクルが存在しないことを確認し、存在しない場合には日付をさかのぼって一番最近書いた日記の内容をコピーして当日の日記記事にペーストします。

Diary++X上で日記なり業務記録をつけている場合に、毎日同じようなフォーマットの記述を行うようにしています。仕事の内容や個人的な調査内容、家庭で何をしたかなど……前日の内容をコピーして、当日の日記アーティクルとして自動的に作成してくれたら楽です。

そこで、本AppleScriptを作成してみました(所用時間15分ぐらい)。Script Menuに入れて使用することを前提にしています。

こうして、実戦レベルのAppleScriptを書いてみると、「make new article」を実行した結果として返ってきた値にtellしたいのにできないとか、Diary++Xのスクリプティング上の問題点が見えてきます。

set newArticle to (make new article)
tell newArticle
 –何かの命令
end

と、だいたいこんな書き方ができるのが一般的なアプリケーションの挙動ですが、これをDiary++Xで行うとエラーになります。このあたりの挙動がいまひとつ、といったところでしょうか。

スクリプト名:Diary++Xで機能の内容を今日の日記にコピーする
–今日のアーティクルが存在しているかどうかチェック
set todaysDate to do shell script "date +%Y-%m-%d"
set anArticleID to ("D " & todaysDate & " 1")
set aRes to retExsists(anArticleID) of me
if aRes = true then
  –すでに今日の日記が存在したら処理終了
  
display dialog "すでに本日の日記が存在するので処理を中断します" buttons {"OK"} default button 1 with icon 1
  
return
end if

–今日の日記が存在しない場合(想定しているパターン)
set decCount to 1
set curDate to current date

–過去にさかのぼって日記の記事を検索(ただし100日前まで)
repeat
  set curDate to curDate - decCount * days
  
  
set aYear to year of curDate
  
set aMonth to month of curDate as number
  
set aDate to day of curDate
  
  
set aYearStr to retZeroPaddingText(aYear, 4) of me
  
set aMonthStr to retZeroPaddingText(aMonth, 2) of me
  
set aDateStr to retZeroPaddingText(aDate, 2) of me
  
  
set todaysDateTmp to aYearStr & "-" & aMonthStr & "-" & aDateStr
  
set anArticleIDTmp to ("D " & todaysDateTmp & " 1")
  
set aRes to retExsists(anArticleIDTmp) of me
  
  
if aRes = true then exit repeat
  
  
set decCount to decCount + 1
  
if decCount = 100 then
    display dialog "100日さかのぼりましたが、過去の日記は存在しませんでした。" buttons {"OK"} default button 1 with icon 1
    
return
  end if
end repeat

–過去の日記の存在確認
tell application "Diary++X"
  tell document 1
    –ヒットした、一番近い過去のアーティクル本文をコピー
    
tell article id anArticleIDTmp
      set oldContents to text contents
    end tell
    
    
–今日の日記アーティクルを作成し、本文に一番近い過去のアーティクル本文を突っ込む
    
make new article
    
tell article id anArticleID
      set text contents to oldContents
    end tell
    
    
–作成した日記を表示状態に
    
set current article to article id anArticleID
  end tell
end tell

–指定IDのアーティクルが存在するかどうか調べて返す
on retExsists(anArticleID)
  tell application "Diary++X"
    tell document 1
      set dExists to (exists of article id anArticleID)
    end tell
  end tell
  
return dExists
end retExsists

–数値にゼロパディングしたテキストを返す
on retZeroPaddingText(aNum, aLen)
  set tText to ("0000000000" & aNum as text)
  
set tCount to length of tText
  
set resText to text (tCount - aLen + 1) thru tCount of tText
  
return resText
end retZeroPaddingText

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

2009/09/16 QuickTime PlayerのXと7を区別して命令を発行する

Mac OS X 10.6標準装備のQuickTime Player Xと、オプションインストールのQuickTime Player 7を明確に区別してコントロールする場合の記法です。

Bundle IDでアプリケーションを指定する記法はMac OS X 10.5以降で拡張された構文であり、これによってQuickTime Player Xと7を区別して命令することが可能です。

なお、この記法はMac OS X 10.4などの古いOS上では使用できません。

スクリプト名:QuickTime PlayerのXと7を区別して命令を発行する
–Mac OS X 10.5以降で採用になったBundle IDによるアプリケーション指定を用いた、
–Mac OS X 10.6上でのQuickTime Player X/7の使い分け

–QuickTime Player Xへの命令
tell application id "com.apple.QuickTimePlayerX"
  activate
end tell

–QuickTime Player 7への命令
tell application id "com.apple.QuickTimePlayer"
  activate
end tell

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

2009/09/14 10.6でtext item delimitersがリストで指定可能に

Mac OS X 10.6でtext item delimitersが、きちんとリストで指定できるようになりました。正確にいえば、リストの1項目目しか認識されていなかったものが、ちゃんと2項目目も認識されるようになった次第です。

Mac OS X 10.3.9上でも、10.4.11上でも、10.5.8上でも {”12″, “4567890″} という間違った結果が返ってきていたり、AppleScript Studio上ではうまく動いていたり……ある意味、Appleのチェックのザルさ加減が分る話ですが……Appleは、みんなで文句を言わないと(間違っていたとしても)動かない会社なので、どんどん文句を言うべきです。

スクリプト名:10.6でtext item delimitersがリストで指定可能に
set aText to “1234567890″
set aCurDelim to AppleScript’s text item delimiters
set AppleScript’s text item delimiters to {“3″, “9″}
set aRes to text items of aText
set AppleScript’s text item delimiters to aCurDelim
aRes
–> {”12″, “45678″, “0″}

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

2009/09/14 Diary++X 1.0.3で指定カテゴリ内のアーティクルを取得する

Diary++X バージョン1.0.3が登場。AppleScript関連の命令が大幅に強化されました。

以前に指摘した点がほとんど修正されているのは「お見事」としかいいようがありません。脱帽です。

本AppleScriptでは、指定カテゴリ内のアーティクル(記事)の一覧を取得します。結果はリストで返ってきます。

スクリプト名:Diary++X 103で指定カテゴリ内のアーティクルを取得する
tell application "Diary++X"
  tell document 1
    tell category "履歴"
      set aList to every article
      
–> {article 1 of document "Diary++X" of application "Diary++X", article 2 of document "Diary++X" of application "Diary++X", (省略) }
    end tell
  end tell
end tell

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

2009/09/14 リスト中の要素の存在確認(10.6以降)

Mac OS X 10.6以降で、リスト中の要素の存在確認で、このような書き方が通るようになりました。本来、この書き方で通ってほしいところであり、より自然な記法が認められたということでしょう。

スクリプト名:リスト中の要素の存在確認(10.6以降)
–Mac OS X 10.6以降で通るようになった記述
set aList to {"abc", "def", "ghi"}
set a to "abc"

if a is in aList then
  display dialog "a is in aList"
end if

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

2009/09/03 AppleScriptObjCはじめての一歩

ML上で漏れ伝わってきている仕様や、Macscripter.netにCraig Williamsが掲載しているチュートリアルを参考に、かんたんなAppleScriptObjCのプログラムを書いてみました。

とりあえず、ウィンドウ上にボタンを2つ置いて、それぞれのボタンのクリックを区別する……という、よくあるものです。

AppleScriptObjCのプロジェクトをXcode上で作成すると、すでに終了イベントのイベントハンドラとウィンドウクローズ時のイベントハンドラが書かれているので、

on applicationWillFinishLaunching_(aNotification)
– Insert code here to initialize your application before any files are opened
end applicationWillFinishLaunching_

on applicationShouldTerminate_(sender)
– Insert code here to do any housekeeping before your application quits
return my NSTerminateNow
end applicationShouldTerminate_

このスクリプトのparentとしてNSObjectを指定し、ボタン2つ分のプロパティを作成してどちらもmissing valueを指定。

てきとーにボタンクリック時のイベントを受信するハンドラを作成して、なんとなくdisplay dialogで押されたことをメッセージ表示。

ここまではすんなりできたのですが、「どうやって押されたボタンを区別するか?」という問題があります。従来のAppleScript Studioでは、Interface BuilderでGUI部品にAppleScriptから参照できるNameをつけておいて、

 set aName to name of theObject

などと名前を参照して各ボタンを識別していたのですが、AppleScriptObjCではその仕組みが使えません。

とりあえず、Interface builder上でGUI部品にTagを付けられるので、これを取得してみたところ……無事、複数のボタンクリックを識別してTagを表示するようにできました。

asoc1.jpg

asoc2.jpg

このレベルなら、さほど難しくないのですが、Cocoa-Bindingを多用するプログラミングスタイルになるわけで、「つなぎ間違い」をどうやって防ぐとか、どこがどうつながっているかをどーーやって確認したらよいのだろう? などといった点が気になります。

さらに、従来のAppleScript Studioでは、プログラムを分割しにくかったのとプログラムの使い回しがしにくかったので……それらがこのAppleScriptObjCのプログラミングスタイルで解決できるのかどうか、気になります。

→ Xcodeプロジェクト asoc2.zip