Cocoa Break Logo Top Soft Develop  脱力空間 Logo
Apple Web Badge
made by mi

概要 翻訳 ソース リンク がらくた

概要 Examples ADC Samples 3rd Parties etc CBOriginals

Voices

以下は、Mac OS X 10.4.11 上の Xcode 2.5 で説明しています。このウィジェットは、BlankWidgetで解説したことは繰り返しませんので、そちらを参照してください。また、コマンド行ツールの同期使用については、Uptime(引数なし)や Which(引数あり)を見てください。

目次:
1 ウィジェット
2 ファイル構成
3 Voices.html
4 Voices.css
5 Which.js
6 非同期バージョン
7 まとめ

1 ウィジェット

Voices には 1-Synchronous(同期)と 2-Asynchronus(非同期)という 2 つのフォルダがあります。まず同期バージョンについて説明し、それから、非同期バージョンで違う部分を説明します。Voices.wdgt は、以下のようなウィジェットです。

「Available Voices(使用可能な声)」をクリックするとメニューが出現し、そこから声を選べます。そして下の「Speak(話す)」というボタンを押すと、「Fred」なら「Hi, I'm Fred」という風に声を出します。上の長方形の部分にはテキストが入力やペーストできるようになっていて、そこにテキストを入れてから「Speak」ボタンを押すと、そのテキストを読み上げます。

2 ファイル構成

このサンプルのファイル構成は以下のようになります。

Default.png は、濃い青色の背景部分だけの画像です。Background.png も同じです。Button.png と Button_down.png はどちらも薄い灰色で同じに見えますが、白い背景にコピーしてみれば、Button.png のほうが少しだけ明るいことがわかります。これは透明度をもっているので、暗い背景に合成すると、逆に Button_down.png のほうが明るくなります。Menu.png はメニュー部分の灰色画像です。TextArea.png はテキスト入力部分の灰色の背景画像です。

Info.plist は、BlankWidget とほぼ同じキーですが、widget オブジェクトの system メソッドでコマンド行ツールを使うため、AllowSystem キーが TRUE に設定されていることに注意してください。主要 HTML ファイルは、Voices.html に指定されています。

3 Voices.html

それでは、主要 HTML ファイルを見てみます(空行は除去して詰めています)。

Which.html
<html> <head> <!-- スタイルシートは別のファイル内に格納されたほうがいい。これはウィジェットに対するデザインを収容。 ウィジェット内の各言語プロジェクトディクレクトリは独自のHelloWelt.cssファイルを持つことに注意 これは言語にもとづいて、ウィジェットの各ローカル化が異なるデザインを持つから Dashboard はユーザーの言語環境設定により自動的に正しい CSS ファイルをウィジェットに提供する --> <style type="text/css"> @import "Voices.css"; </style> <!-- JavaScript ファイルはローカル化修正と動作を含むウィジェットに必要な動作を収容 --> <script type='text/javascript' src='Voices.js' charset='utf-8'/> </head> <body onload="setup();"> <!-- setup() は本体テキストのローカル化を試みる --> <img src="Images/Background.png"> <!-- ウィジェットの背景 --> <img class="textArea" src="Images/TextArea.png"> <!-- ウィジェットの背景 --> <img class="voiceMenu" src="Images/Menu.png"> <!-- ウィジェットの背景 --> <div id="voiceMenuText">Available Voices</div> <div id="theButton" onmousedown='speak();' onmouseup='buttonUpOut();' onmouseout='buttonUpOut();'> <img id="speakButton" src="Images/Button.png"> <div id="speakButtonText">Speak</div> </div> <textarea id="textInput" scrollbars='false'></textarea> <select id='voicePopup' onchange='voiceChanged(this);'> <option value="Agnes">Agnes</option> <option value="Albert">Albert</option> <option value="Bad News">Bad News</option> <option value="Bahh">Bahh</option> <option value="Bells">Bells</option> <option value="Boing">Boing</option> <option value="Bruce">Bruce</option> <option value="Bubbles">Bubbles</option> <option value="Cellos">Cellos</option> <option value="Deranged">Deranged</option> <option value="Fred" selected>Fred</option> <option value="Good News">Good News</option> <option value="Hysterical">Hysterical</option> <option value="Junior">Junior</option> <option value="Kathy">Kathy</option> <option value="Pipe Organ">Pipe Organ</option> <option value="Princess">Princess</option> <option value="Ralph">Ralph</option> <option value="Trinoids">Trinoids</option> <option value="Vicki">Vicki</option> <option value="Victoria">Victoria</option> <option value="Whisper">Whisper</option> <option value="Zarvox">Zarvox</option> </select> </body>

コメントが変な所がありますが無視します。これは HelloWelt サンプルをコピーして、それを修正して作られているようです。長いですが、ほとんどがメニュー項目なので、構造は簡単です。

まず body 要素に onload ハンドラとして関数 setup() が指定されています。これは読み込みが完了した時に呼ばれることになります。

つぎがウィジェットの背景画像で、その後に、テキストエリアの背景画像とメニュー部分の背景画像が置かれています。その後に、メニューに最初表示されるテキストが置かれ、Button という id 属性をもつ div 要素内に、通常のボタン画像である Button.png と、タイトル文字列が置かれています。このボタン画像はクリックされた時に JavaScript で修正されます。div 要素全体にハンドラが設定されていることに注意してください。

つぎにテキストエリアが置かれます。スクロールなしに指定されています。その後にメニュー要素である select が置かれています。これにもハンドラが設定されています。その後、メニュー項目の値が指定されています。

ウィジェットでは、通常 CSS ファイル内で要素の位置指定を行うため、ここでの項目の並びは気にしなくていいでしょう。どういう項目があるのか、どういうハンドラが設定されているかなどを注意すればいいだけです。

4 Voices.css

スタイルシートを見てみましょう。

Voices.css
... .voiceMenu { position: absolute; left: 28px; top: 169px; } ... #voicePopup { position:absolute; top: 169px; left: 28px; width: 163px; height: 30px; opacity: 0.0; z-index: 20; }

通常設定されているような文字属性や位置などのプロパティなどは省略しています。これ以外は各項目の位置を設定したり、コントロールとして使う項目には -apple-dashboard-region プロパティで領域指定しているだけです。

voicePopup という id 属性を持っていた select 要素に対して、opacity: 0.0 という設定があることに注意してください。これは不透明度で、現在の内容にどれぐらいかぶせて表示されるかをということで、0.0 は完全な透明で、1.0 が完全な不透明です。ここでは全く見えない状態になっています。また、z-index は重なりの順番です。通常の状態を 0 として大きいほど上に重ねられます。ここでは 20 に指定されています。これで最初は目に見えない状態で上に重ねられていることがわかります。これを見ると、opacity を変更することで、上にメニューが示されることがわかるでしょう。

voiceMenu クラスへの指定と、voicePopup ID への指定を比べて見れば、同じ場所にこのメニューが重ねられていることがわかります。これにより、これをクリックした時の動作は標準のブラウザの select 項目の動作と同じになり、メニュー操作を独自に実行しているわけではありません。ただし、通常の select とは違い、見えないため選択された項目を表示させる手段がありません。そのため、この select 項目に、onchange ハンドラが設定されていたわけです。ここで注意すべきは、あくまで opacity を設定し、displayvisiblity を使っていないのは、存在してユーザーのクリックに対応するものの見えないという状態にしたかったからです。

5 Voices.js

このファイルでは、変数といくつかの関数が実装されています。まず、変数と body の読み込みが完了した時に呼ばれる setup() を見てみます。

Voices.js > 変数と setup
var currentlyBeingSpoken = null; // setup() は、ウィジェットの body が読み込まれたとき呼ばれる // ここで、ウィジェットをユーザーに紹介する function setup() { if(window.widget) { // つねに Dashboard 内で実行されていることを確認するためにチェックする currentlyBeingSpoken = widget.system ("/usr/bin/osascript -e 'say \"Welcome to Voices!\" using \"Fred\"'" , null); } }

window .widget は、ウィジェットオブジェクトで Dashboard 内で自動作成されるものです。これがないということは、通常のブラウザなどで呼ばれたことになります。これをチェックすることで、ウィジェットとして動作しているかどうかを調べられます。

そしてメソッド system を使ってコマンド行ツールを実行しています。最後の引数が null なので同期実行されます。

osascript はコマンド行ツールで、osascript <file> または osascript -e <code> の形で使われます。<file> の場合は、指定したファイルの AppleScript を、-e の場合は、<code> に書かれた AppleScript を実行します。ここでは、Fred という音声を使って「Wellcome to Voices!」を話させています。

つぎにメニュー項目の選択が変更された場合に呼ばれる関数を見てみます。

Voices.js > voiceChanged
// voiceChanged() は Voices ポップアップメニューが変更されたとき呼ばれる // これは捏造メニューテキストを変更し、ユーザーに新しい声を伝える function voiceChanged(elem) { var chosenVoice = elem.options[elem.selectedIndex].value; document.getElementById("voiceMenuText").innerText = chosenVoice; currentlyBeingSpoken = widget.system ("/usr/bin/osascript -e 'say \"Hi, I`m " + chosenVoice + ".\" using \"" + chosenVoice + "\"'" , null); }

まず chosenVoice に、この要素のオプションの配列の選択項目要素の値を入れています。そして、voiceMenuText という id 属性をもつ要素の内部テキストをいま取得したものに設定しています。それから、今選んだ声を使って、自己紹介させています。

つぎは、「Speak」ボタンをクリックした時に呼ばれる関数です。

Voices.js > speak
// speak() は入力テキストを取得し osascript で使うのに不適切な部分を除去し // それから現在選択されている声を使ってユーザーに対してそのテキストを話す function speak(event) { document.getElementById("speakButton").src = "Images/Button_down.png"; var textToSpeak = document.getElementById('textInput').value; var regExForSingleQuote = new RegExp ('\'', 'g') ; var textToSpeak = textToSpeak.replace(regExForSingleQuote, '`') ; var regExForDoubleQuote = new RegExp ('"', 'g') ; var textToSpeak = textToSpeak.replace(regExForDoubleQuote, '') ; var voiceMenu = document.getElementById('voicePopup'); var chosenVoice = voiceMenu.options[voiceMenu.selectedIndex].value; if(window.widget) { currentlyBeingSpoken = widget.system ("/usr/bin/osascript -e 'say \"" + textToSpeak + "\" using \"" + chosenVoice + "\"'" , null); } }

まずボタン画像を押された時のものに変更しています。

次に、textToSpeak にテキスト入力領域の値を代入しています。つぎに正規表現オブジェクを作成して、それを使って置換を行っています。この形を使わなくても、replace の最初の引数として直接そこに正規表現を書きこんでも問題ありません。ウェブ上での JavaScript は直接入力のもののほうが多いでしょう。

そして、選択された声を取得します。

最後にウィジェットオブジェクトがあるなら、コマンド行ツールを実行しています。

つぎは、ボタンが離されたり外に出た時の関数を見てみます。

Voices.js > buttonUpOut
// buttonUpOut()はボタン画像をクリックされていないものに切替 function buttonUpOut() { document.getElementById("speakButton").src = "Images/Button.png"; }

単にボタン画像を戻しているだけです。

6 非同期バージョン

同期と非同期バージョンで唯一違うファイルは Voices.js です。他はすべて同じものです。

Voices.js > 変数と setup
var currentlyBeingSpoken = null; // setup() は、ウィジェットの body が読み込まれたとき呼ばれる // ここで、ウィジェットをユーザーに紹介する function setup() { if(window.widget) { // つねに Dashboard 内で実行されていることを確認するためにチェックする currentlyBeingSpoken = widget.system ("/usr/bin/osascript -e 'say \"Welcome to Voices!\" using \"Fred\"'" , doneSpeaking); // 引数変更 } }

上と比べるとわかるように、system メソッドの最後の引数が doneSpeaking となっています。非同期のコマンド行ツール使用では、コマンドが終了するのを待たないので、終了したときに通知してもらえるようにする必要があります。この最後の引数で、コマンドが終了したときに呼ばれる関数を指定できます。

非同期の呼び出しでは、同期の場合と違ったプロパティやメソッドが使えます。リファレンスに掲載されている表を見てみます。

オプション目的説明
command.outputStringプロパティコマンドによって stdout(標準出力)に書き出された現在の文字列です。
command.errorStringプロパティコマンドによって stderr(標準エラー出力)に書き出された現在の文字列です。
command.statusプロパティコマンドによって定義される、コマンドの終了状態です。
command.onreadoutputイベントハンドラコマンドが stdout に書き出しを行うたびに呼び出される関数です。ハンドラは単一の引数を受け取らなくてはなりません。呼び出されたとき、その引数は、stdout に出力された現在の文字列を含んでいます。
command.onreaderrorイベントハンドラコマンドが stderr に書き出しを行うたびに呼び出される関数です。ハンドラは単一の引数を受け取らなくてはなりません。呼び出されたとき、その引数は、stderr に出力された現在の文字列を含んでいます。
command.cancel()メソッドコマンドの実行を取り消します。
command.write(string)メソッドstdin(標準入力)に文字列を書き出します。
command.close()メソッドstdin を閉じます(EOF)。

つぎは声の変更です。

Voices.js > voiceChanged
// voiceChanged() は Voices ポップアップメニューが変更されたとき呼ばれる // これは捏造メニューテキストを変更し、ユーザーに新しい声を伝える function voiceChanged(elem) { var chosenVoice = elem.options[elem.selectedIndex].value; document.getElementById("voiceMenuText").innerText = chosenVoice; if(window.widget) { if( currentlyBeingSpoken != null) { // 以下4行追加 currentlyBeingSpoken.cancel(); done(); } currentlyBeingSpoken = widget.system ("/usr/bin/osascript -e 'say \"Hi, I`m " + chosenVoice + ".\" using \"" + chosenVoice + "\"'" , doneSpeaking); // 引数変更 } }

この関数でも、doneSpeaking が指定されています。それ以外に、真ん中にコード行が追加されていることに注意してください。currentlyBeingSpokennull でなければ、cancel() メソッドを呼び出してコマンドの実行を中止します。そして done() を呼び出します。これがコマンド行ツール実行前に呼ばれていることに注意してください。現在実行中でまだ終了していない場合、先にそれをキャンセルしてから次のコマンド行ツール実行を行います。

Voices.js > speak
// speak() は入力テキストを取得し osascript で使うのに不適切な部分を除去し // それから現在選択されている声を使ってユーザーに対してそのテキストを話す function speak(event) { document.getElementById("speakButton").src = "Images/Button_down.png"; var textToSpeak = document.getElementById('textInput').value; var regExForSingleQuote = new RegExp ('\'', 'g') ; var textToSpeak = textToSpeak.replace(regExForSingleQuote, '`') ; var regExForDoubleQuote = new RegExp ('"', 'g') ; var textToSpeak = textToSpeak.replace(regExForDoubleQuote, '') ; var voiceMenu = document.getElementById('voicePopup'); var chosenVoice = voiceMenu.options[voiceMenu.selectedIndex].value; var speakButtonText = document.getElementById("speakButtonText"); // 以下2行追加 speakButtonText.innerText = "Stop"; var theButton = document.getElementById("theButton"); // 以下3行追加 theButton.onmousedown = null; theButton.onmousedown = cancel; if(window.widget) { if( currentlyBeingSpoken != null) { // 以下3行追加 currentlyBeingSpoken.cancel(); } currentlyBeingSpoken = widget.system ("/usr/bin/osascript -e 'say \"" + textToSpeak + "\" using \"" + chosenVoice + "\"'" , done); // 引数変更 } }

ボタンのテキストを「Stop(停止)」に変更するコードが追加されています。さらにボタンのマウスクリックを null にした後で、cancel に設定しています。そして、currentlyBeingSpokennull でなければ、まず cancel() を呼び出して、実行中のコマンドをキャンセルしています。

buttonUpOut() は以前と全く同じです。

このスクリプトファイルは、上で見たコマンド完了後のハンドラやいくつかの関数と、読み込まれた時に実行される関数外コードを含んでいます。まず setupvoiceChanged で指定されていた doneSpeaking を見てみます。

Voices.js > doneSpeaking
// doneSpeaking() は2の紹介に対する終了ハンドラで、done() の最後で呼ばれる // これは現在話されているコマンドのグローバルな追跡を消去する function doneSpeaking() { currentlyBeingSpoken = null; }

これはグローバル変数を null に設定しているだけです。このサンプルではコマンド完了後の値は使われません。使われるなら、done やここでその値を取り出すなどの作業を行うわけです。つぎに speak で指定されていた done を見てみます。

Voices.js > doneSpeaking
// done() は widegt.system に対する終了ハンドラとして使われ、 // ユーザーが発話をキャンセルした時にも呼ばれる。これはインターフェイスの後始末を行う。 function done() { var speakButtonText = document.getElementById("speakButtonText"); speakButtonText.innerText = "Speak"; var theButton = document.getElementById("theButton"); theButton.onmousedown = null; theButton.onmousedown = speak; doneSpeaking(); }

まず「Stop」に変更されていたテキストを「Speak」に戻します。つぎにボタンのクリック時のハンドラが cancel になっていたので元に戻します。それから上の doneSpeaking を呼び出しグローバル変数をクリアします。

つぎに、実行中にさらに実行されたときや「Stop」ボタンから呼ばれる cancel を見てみます。

Voices.js > doneSpeaking
// cancel()はウィジットの発話を中止させたいユーザーによって呼ばれる function cancel(event) { if(window.widget) { currentlyBeingSpoken.cancel(); } document.getElementById("speakButton").src = "Images/Button_down.png"; done(); }

まず cancel() メソッドでコマンドの実行を中止しています。その後、ボタンの画像を変更しているだけです。

さて、このスクリプトファイルでは remove() という関数と、関数ブロック内にないコードが含まれています。この 2 つは関連しているので同時に見てます。

Voices.js > removed
// removed() はウィジェットが Dashboard から除去された時に呼ばれる。 // これは実行中かもしれない発話を中止する。 function removed() { if( currentlyBeingSpoken != null) { currentlyBeingSpoken.cancel(); } } // ここで関心のあるウィジェットイベントを登録する if(window.widget) { widget.onremove = removed; }

removed は単にコマンドが実行されていれば中止するだけです。後の方は、関数内にないコード文で、このスクリプトファイルが読み込まれた時に実行されます。ウィジェット全体に対する登録などは、ここで行います。ここでは、removed が除去された時のイベントハンドラとして登録されています。

7 まとめ

このサンプルでは、コマンド行ツールの非同期の利用の例を示しています。ただし、ここでの例では、コマンドの結果が使われていない事に注意してください。コマンドからの出力文字列などは、Uptime サンプルのように、コマンドのプロパティを得ることで行えます。ここで行うとすれば、currentlyBeingSpoken にプロパティ問い合わせを行うことになるでしょう。このように system メソッドを代入することで、コマンドのプロパティやメソッドを自由に使えます。


管理人:神吉 秀典 E-mail:puer@ba.wakwak.com