2012-02-29

MacのX11で日本語入力可能にする

Inkscape、Gimpだけじゃなく、Wineもある程度使えるかもしれないので、Mac OS XのX11で日本語入力を可能にすることに挑戦します。

ネットでやり方を調べたんですが、ちょっと難しいそうでした。そこで調べたことをまとめてみます。
なお、変換ができるようになると、Shift + Space で日本語モードの切替になります。かなキーを押して変換するわけじゃないんですね。
いろいろ試したんですが、Inkscapeで日本語入力を行うことは未だにできていません。Gimpでは日本語入力できます。Wineで導入したPupSQLiteでも大丈夫でした。xtermでもできますが、入力中は文字化けしています。


変換クライアントと変換サーバー


そもそもLinuxでの日本語入力を可能にするためには、インプットメソッド(変換クライアント)と変換エンジン(変換サーバー) の二つが必要ということです。変換クライアントがユーザーと対話を行う部分で、変換エンジンは変換する漢字の候補リストを探すみたいな役割分担だと思います。この二つの組み合わせには制限があるみたいです。変換クライアントをkinput2にすると、変換エンジンにはcannaを使わないといけないようですが、cannaというのはちょっと古いみたいです。
変換クライアントがMacUIMならば、変換エンジンとしてAnthy、PRIME、SKK、Cannaなどを選べるようなので、こっちを使うのが良さそうです。ただしxtermだと文字化けします。


.bashrc と .bach_profile


.bashrc と .bach_profileは、両方ともLinuxの設定ファイルで、ユーザのホームディレクトリにあります(隠しファイルなので、デフォルトのFinderの設定では見えないです。ターミナルで ls -a とやれば表示されます)。.bach_profileは、ログイン時だけに実行されるファイルです。一方、.bashrcは別のシェルが起動したときにも実行されます。ログイン時にも .bach_profileから読み込ませるという形で実行するのが一般的なようです。


launchctl


launchctlは、Macでデーモンを管理するプログラムらしいです。
たとえば、sudo port load canna とやれば、cannaをデーモンとして起動・登録することができます。解除する場合は、sudo port unload canna です。


---
書いている途中で力尽きました

AppStoreとAndroid Marketへのリンク

新しく作成するアプリの告知ために、以前につくったスマホ用Webページに、Android Marketへのリンクを追加する予定です。

しかし、最近のAndroid Marketは、新登場のアプリに冷たい気がします。以前は、新しいアプリを一覧表示するページがあって、そのページビュー数がなかなかのものだったと思うんですが、いまはAndroid Marketのつくりが変わって新しいアプリの紹介ページのページビューが減っているんじゃないかと思われるうえに、新しくリリースされたもののうち人気のあるものしか一覧に載らないみたいです。たぶん、人気があって品質の高そうなアプリをユーザに紹介するという考え方なんでしょうが、勝者がすべてを独占することになります。ここに食い込むのはなかなか厳しそうです。アプリの広告を打ってもコンバージョンがあまり見込めないので、リスクをしょって大量に広告を打つことでランキングに食い込むことを目指すという方針でないとたぶん採算がとれません。以前にリリースしたアプリで告知を行えば、余計なお金はかからないのでやってみます。こういうことはある意味、アプリに機能を付け足すことより大事かもしれないです。

WebページはjQuery Mobileでつくっていますが、サムネイルつきのリストを作成するには簡単なマークアップを行うだけです。これだけなら楽勝です。
しかし、リンク先がAndroid MarketだったりAppStoreだったりすると、ちょっと話がややこしくなります。ユーザーエージェントを見てリンク先を変えないと、ページを読み込めずにエラーになってしまうからです。WbViewやUIWebViewを組み込んだアプリでも、自前で処理を追加しないとページを読み込めずにエラーになります。どのブラウザからのアクセスをどこにリンクさせるか、という方針を決めないとわけが分からなくなりそうです。

まず、Android Marketには、Web版と、アプリ版(?)の二つがあります。
Web版はどんなブラウザからも表示できますが、アプリ版はAndroid端末でないと開けません。Android端末でWeb版アクセスするとアプリ版にリダイレクトされるという機能が以前はあったような気がするのですが、いまはないようです。Android端末からでも、デフォルトブラウザであればアプリ版へのリンクを開けますが、それ以外のブラウザやアプリからは開けない可能性もあります。

ということで、次のように場合分けして考えます。
  1. iPhone/iPad/iPodからのアクセスならばリンクを表示しない
  2. Androidマーケットへのリンクを開けそうな場合は、アプリ版のマーケットへのリンクを表示
  3. それ以外はWeb版へのリンクを表示
それほど複雑じゃなかったですね。

ちなみに、WbViewでAndroid Marketへのリンクを開くためには、WebViewClientの、shouldOverrideUrlLoading(WebView, String) をオーバーライドします。URL文字列が "market://" で始まるようであれば、次のようにしてIntentを投げます。FLAG_ACTIVITY_NEW_TASKなどのフラグは不要だと思います。
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlStr));
activity.startActivity(intent);
return true;

UIWebViewでAppStoreのリンクを開く場合も考え方は同じです。UIWebViewDelegateの、webView:shouldStartLoadWithRequest:navigationType: で、AppStoreへのリンクであるかを判定して
[[UIApplication sharedApplication] openURL:url];
とやります。AppStoreへのリンクであれば、@"http://itunes.apple.com/"か、@"http://phobos.apple.com" でURL文字列が始まるはずです。

Wineで日本語表示

Wineで、PupSQLiteというSQLiteのクライアントソフトが使えるか試そうと思い立ちました。スマホのアプリや、小規模のWebサービスだとSQLiteが結構便利なんですが、Mac/Linuxのクライアントソフトだと、DBテーブルの再定義ができません(列を追加したり名前を変える程度ならできますが、それが限界です)。SQLite自体に、テーブルを再定義する構文があまり備わっていないので当然といえば当然なのですが、PupSQLiteだと、派手にテーブルを変更することができるんです。たぶん、テーブルを新しくつくりなおして、それに既存のデータを挿入するという力技で処理しているんだと思います。

PupSQLiteには、.NET Framework 4.0ベースのものと2.0ベースのものがありますが、wineだと2.0ベースの方が安定して動きそうな気がして、こちらをダウンロードしました。winetricksで、.NET Framework 2.0をインストールした後に、PupSQLiteを起動したらいちおう立ち上がってくれました。wineだと、起動時にエラーにならないだけでも嬉しくなります。
しかし、メニューがみんな文字化けして□になっていてとても使えたものではありません。wineの日本語環境を整えないといけないみたいです。

ネットで調べたらレジストリを変更する方法や、~/.wine/user.regに設定を追加する方法などが見つかったのですが、なんだかめんどうで難しそうでした。しかし、文字化けの原因は、MSゴシックやMS明朝などがwineにインストールされていないことだろうと見当がつきました。だったら、Windowsからこれらのフォントをコピーしてwineにも置いてやればいいはずです。ライセンス的に許されているのかどうか知りませんが、¥windows¥Fontsに置かれているフォントファイルをコピペして再起動したら、メニューを無事に表示できました。

試しにデータベースを読み込ませてみたらちゃんと表示できました。韓国語のフォントが入っていないためか、韓国語の部分が文字化けしていましたけど。
複雑なことはやっていませんが、タブを開いたり閉じたりはできました。もしかしたら実用レベルなんじゃないかと、期待がもてるくらいです。

こうなってくると、日本語で入力する環境も整えたくなってきます。MacでUnix環境が整うと、実は結構うれしいことが多いかもしれないです。

2012-02-28

UIWebViewのエラーハンドリングを見直す

iPhoneアプリで、UIWebViewのwebView:didFailLoadWithError: を、いままで次のように書いていました。
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    if (error.code != -999) {
        // ユーザによる意図的な中止でない場合はエラーダイアログ表示
  NSString *errMsg = [error.userInfo objectForKey:@"NSLocalizedDescription"];
        UIAlertView *dlg = [[[UIAlertView alloc] initWithTitle:@"Error" message:errMsg delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil] autorelease];
        [dlg show];
 }
}

しかし、これはあまり良くないですね。ユーザーが中止ボタンを押した場合以外にも正常な読み込み中断はあるのに、それらに対してもErrorダイアログが表示されてしまいます。
正常な処理なのにwebView:didFailLoadWithError:が呼ばれる例としては、音声データなどをプラグインで処理した場合(エラーコード 204)や、AppStoreへのリンクをopenURL: した場合(エラーコード 102)があります。思いつかないだけで他にもあるかもしれません。
エラーコード 204と102の場合はダイアログを表示する必要がなくて、それ以外の場合も本当にエラーなのか怪しいので、エラーメッセージを取得できなかったときにだけダイアログのタイトルに「エラー」と表示するという感じにしたいと思います。

MySQLとPostgreSQLのライセンス

MySQLとPostgreSQLでは、ライセンスが違います。
MySQLは、GPLライセンスと商用ライセンスの二つがあって、どちらにするか選べます。
PostgreSQLは、BSDライセンスです。

つまり、MySQLはオープンソースのプロジェクトであれば無料で使えますが、そうでなければ商用ライセンスを買わなければなりません。一方PostgreSQLは、どんなプロジェクトでも無料で使えます。

サーバー上で動かすプログラムをオープンソースにするのは、セキュリティ上の懸念もあって考えものです。DBが利用できればどれでもいい場合には、MySQLではなく、PostgreSQLを選んでおいた方が、ライセンス的にめんどうなことがなくて良さそうです。
また、DjangoでDBを使う場合は、特にPostgreSQLが推奨されているようです。

Android Market用のプロモーション画像を作成しました

Androidマーケットにアプリを登録する際、オプションで宣伝用の画像をアプロードできます。1024x500ピクセルのものと、180x120ピクセルのものの二つです。余計な手間をかけさせやがってと思いながらもいちおう作成しました。

2012-02-27

Ubuntuでnode.jsを試してみる、あとCloud9でも

Ubuntu 11.10にnode.jsをインストールしてみました。apt-getでいけます。パッケージマネージャのnpmもapt-getでいけます。
$ sudo apt-get install nodejs-dev
$ sudo apt-get install npm
サンプルコードとして、次のようなコードを書いて、hellonode.jsという名前で保存します。
var sys = require("sys");
var http = require("http");

var server = http.createServer(
  function(req, res)
  {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.write("Hello World!");
    res.end();
  }
);
server.listen(8080);
sys.log("Server is running at http:127.0.0.1:8080");
で、terminalで、
$ node hellonode.js
と入力したらとちゃんと動いているのをブラウザで確認できました。まあ、ちょっと遊んでみただけです。

調子に乗ってexpressもインストールしてみたけど、node.jsで遊ぶんならCloud9が手軽でいいかもしれません。ブラウザ上で動くIDEですが、意外と快適です。
ただ、正しくコマンドを打ったはずなのに原因不明のエラーになったり、あるはずのモジュールが見つからなかったりしました。しばらく待ってページを開き直して、また同じことを試してみたら上手くいきましたが。 

なお、Macでも、portを使ってnode.jsとnpmをインストールできるみたいです。

AndroidのWebViewで音声を再生する

Webページに音声を再生する仕組みが組み込まれている場合があるのですが、AndroidのWebViewは、そのままではそれを再生してくれません。何かプログラムを書いてやらなければいけないようです。

参考にすべきは、Androidの公式ドキュメントのこの記事でしょう。

音声を再生する場合、利用する主要なクラスはMediaPlayerとAudioManagerです。これらのクラスの機能を利用するために、マニフェストに記述しなければならないことは特になさそうですが、インターネット経由で音声ファイルにアクセスする場合や、音声の再生中にデバイスがスリープしてしまうのを防ぎたい場合は、それぞれINTERNETとWAKE_LOCKパーミッションを許可する必要があります。 ネット上の音声ファイルを再生するには、次のようにしてUriを指定します。

try {
    String url = "http://........"; // your URL here
    MediaPlayer mediaPlayer = new MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setDataSource(url);
    mediaPlayer.prepare(); // might take long! (for buffering, etc)
    mediaPlayer.start();
} catch(IllegalArgumentException ex1) {
    // handle error
} catch (IOException ex2) {
    // handle error
}
prepare()メソッドは時間がかかる場合があるので、UIスレッドで呼んでユーザを待たせてはいけません。あまり待たせるとアプリケーションがハングしたとシステムに判断されてしまうこともあるようです。「never call it from your application's UI thread」と太字でドキュメントに書かれています。 しかし、必ずしも開発者が別スレッドを用意しなくていけないわけではなくて、代わりにprepareAsync()メソッドを呼ぶという手もあります。この場合は、MediaPlayerの準備が整った後で実行するべき処理をsetOnPreparedListener()で登録しておく必要があります。 MediaPlayerは状態管理を行っていて、準備が整わないのにいきなり音声を再生することはできません。次のような状態遷移図を頭に入れておけとのことです。

MediaPlayerはリソースを消費するので、使い終わったらちゃんと解放してやらなければなりません。ActivityにMediaPlayerを保持している場合は、onStop()が呼ばれたら解放しないとダメです。解放するときは、
this.mediaPlayer.release();
this.mediaPlayer = null;
という具合にreleaseした後でnullを代入します。


状態遷移図を見ると、一度setDataSource()を実行してInitializedされたMediaPlayerに対して、もう一度setDataSource()を実行することはできないようです。同じMediaPlayerは、同じデータを繰り返し再生できますが、別のデータを再生する場合は、新たにインスタンスをつくらないといけないということでしょう。
MediaPlayerをActivityではなくService利用する場合は、毎回解放する必要はないみたいですが、とりあえず今回はServiceを利用しないので関係のない話です。

後は、ちょっとプログラムをいじってちゃんと音声を再生できるか試してみます。
つぎのようなコードを書いてみました。わずか数秒の音声データの再生が目的なので、最後まで再生して終了というだけのシンプルなロジックです。これを、WebViewClientのonLoadResource(WebView view, String url) の中で実行しています。URLをみて音声データだと判定できた場合にのみ実行する処理です。レスポンスのMimeTypeをチェックできたら、もっといいんですが、それっぽいイベントが見つかりませんでした。
try {
    Log.d(TAG, "Initialize MediaPlayer");
    MediaPlayer mediaPlayer = new MediaPlayer(); //idle
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setLooping(false);
    mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mp) { //prepared
            Log.d(TAG, "MediaPlayer prepared");
            mp.start();
        }
    });
    mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mp) { // playback completed
            Log.d(TAG, "MediaPlayer completed");
            mp.stop(); //stopped
            mp.release();
        }
    });
    mediaPlayer.setDataSource(url); // initialized
    mediaPlayer.prepareAsync(); //preparing
} 
catch (Exception ex) {
    Log.w(TAG, ex.toString(), ex);
}

軽くテストしてみたところ、いろいろと問題が発覚しました。
まず、Android1.6のエミュレータだと音声がループしてしまいます。ログを見たところonCompletionが呼ばれていないです。代わりに、「E/PlayerDriver(31): Invalid percentage value ####」というログがずっと出力され続けています。####の部分は数値で、この数値がだんだん増加していきます。
Android 2.1では、再生ができないです。「W/MediaPlayer(223): info/warning (1, 44) 」というログが出力されています。OnInfoListenerでは、MEDIA_INFO_UNKNOWN == 1が通知されます。
Android2.3だとループなく再生できますが、音声の最後が少しだけ途切れてしまいます。
上のコードでは、OnErrorListenerがセットされていませんが、onErrorListenerが定義されていなければonCompletionが呼ばれるということなので、これは問題ないはずです。
さらに調査していたら、ちゃんと再生できるデータとそうでないデータがあるということが分かりました。インターネット上のデータに依存する問題ということであきらめざる得ない部分も多そうです。リピートに関してはバックボタンで再生を中止できるようにしてお茶をにごし、再生できないデータについては潔くあきらめる、というあたりで手を打ちます。音声の最後が途切れるという問題は、ちょっとスリープしてからstop()を実行することで対処できるといえばできますが、こんなことでいいのかどうか考えものです。onCompletion()はUIスレッドらしく、その中でスリープするとその間ユーザの操作をアプリが受け付けなくなってしまうので、やるとしたら別スレッドを用意しなくてはいけません。

あと、複数のMediaPlayerを同時に起動できてしまうという問題にも対処しておいた方がよさそうですね。イベントハンドラの中でtry/catchすることも必要そうです。


Androidで同じタブがクリックされたイベントを検知する

Androidではイベントハンドラを1個しか登録できないので、onClickListenerをセットして同じタブがクリックされたイベントをとろうとすると、タブを切り替える処理が上書きされてしまうという問題があります。この問題を回避するには、setOnTouchListenerを利用すれば良いようです。 こんな具合です。
TabWidget tabWidget = tabHost.getTabWidget();
for (int i = 0; i < countOfTabs; i++) {
    View tab = tabWidget.getChildAt(i);
    tab.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            boolean consumedFlg = false;
            if (MotionEvent.ACTION_UP == event.getAction()) {
                View currTabView = tabHost.getCurrentTabView();
                if (v.equals(currTabView)) {
                    //なにかやる。v.getContext() でActivityを取得したり、tabAdapter.findFragmentAtPosition(i) でFragmentを取得したりできる
                    consumedFlg = true;
                }
            }
            return consumedFlg;
        }
    });
}

国旗アイコンづくり

昨日は、iOSアプリ用の小さなボタン画像を完成させる予定だったんですが、なんだかあまり集中できず、完成にこぎ着けることができませんでした。今日はその続きをやるべきとこかもしれないんですが、よく考えたら他にやらなきゃいけないことがたくさんありました。先日リリースしたAndroidアプリのアップグレード版をリリースして、さらに新しいアプリ用のアイコンを作成します。iOSアプリの完成までにはまだ少し時間がかかりそうなので、こっちを片付ける方が先だという判断です。

新しくつくっているのは翻訳アプリなので、日本とアメリカの国旗を二つ並べて、交互に矢印をひくというようなベタなデザインのアイコンをつくります。なぜイギリスではなくてアメリカなのかというと、アメリカ人の方が数が多いからです。

これまでは友達のデザイナさんにアイコン作りを依頼することが多かったのですが、彼が何やら忙しいらしいので自分でつくります。シリーズ化する予定のアプリなので、全部依頼していたらお金もそれなりにかかりそうですし。フリーのアイコンを利用するという手もありそうですが、イメージ通りのものはなかなか見つからなさそうなので自分でつくります。
こんなときに日本の国旗はシンプルで素晴らしいと思いますね!異国の旗はゴチャゴチャしていて困ります。

アメリカ国旗はWikipediaによると「白線と赤線の組み合わせの13本の横縞(赤7本と白6本、最初と最後は必ず赤)、四角に区切った左上部(カントン)は青地に50の白い星が配置されている。赤は勇気、白は真実、青は正義を表す。また縞模様は独立当時の13の入植地を表し、星は現在の州を表している。デザインは27回更新された。」とのことです。星は9段あって、6個並びの行と5個ならびの行が交互に来ているようです。正義の青は赤線4本分ですね。
どこまで忠実に再現するかは分かりません。アイコンなので多少単純化しても誰も文句は言わないでしょう。しかし、宣伝用に引き延ばした画像も用意するので、ある程度は現物に忠実なものを用意ておいた方が良さそうです。画像がつぶれてしまうようだったら、またつくりなおさないとダメですけど。

国旗の横縦の比率は3対2くらいでOKでしょう。国旗の背後には影を軽くつけます。表面になにか処理が必要かどうかは、シンプルなのを一つ作ってみてから判断します。矢印は立体的にしたいなあ(どうやればいいのかよく分かっていないけど)。Androidのアイコンなので、とりあえず背景は透明です。iPhoneアプリはアイコンの背景を透明にできないので、iPhone版を出すときは背景色をまた考えないといけません。

そんな感じでとりあえず作業に着手してみます。


こんなのができました。

このチュートリアルを読んで参考にしようと思ったのですが、結局ここで学んだテクニックは使いませんでした。Echo Icons Tips - Creating Echo Icons with On the Table Perspective 

MAC OSXでX11のアプリを切り替える

貧乏でPhotoshopやらIllustratorが買えないので、画像をつくるときは、InkscapeやGimpなどのフリーソフトを利用しています。
これらはもともとLinux環境用につくられたものなので、OS Xで使うときはX11の上で動作することになります。しかし、X11で起動したアプリは、は他のアプリと操作の仕方が違うのでなかなか使いにくいです。

X11で快適に作業を行うには、次のような設定とショートカットキーを組み合わせて使ったらいいのではないかと思います。

X11の環境設定ダイアログのウィンドウタブで[選択されていないウィンドウを直接クリック]にチェックを入れます。これをやらないとGimpが使いにくいです。

あと全画面でもX11を使う場合は、入力タブで[X11の代替キーを有効にする]にチェックを入れます。こうして置けば、X11内部でのウィンドウの切替を Cmd + F1 でできるようになります。各ウィンドウに番号が割り振られるようで、Cmd + 1 やら Cmd + 2 で、切り替えるウィンドウを指定することもできます。(なお、InkscapeやGimpなどのアプリ内でウィンドウを切り替えるには、Ctrl + Tabが使われることが多いようです。)
全画面から戻るときは、Cmd + Alt + A でいけます。タッチパッドをスワイプしても戻れますけど。全画面の場合は、F10で、アプリのメニューバーがフォーカスされます。

* 全画面モードにしなくても、Cmd + F10 でメニューバーを選択することができました。.xmodmapで左commandをcontrolに割り当てている場合は、右commandキーを利用する必要があります。

* X11でいちばん厄介なのは、commandキーやaltキーが効かなくなることかもしれません。 これについては、さきほどちょっと書きました(MAC OS XのX11でALTキーを有効にする


MAC OS XのX11でALTキーを有効にする(Inkscape用)


InkscapeをMacで使う場合は、altキーを有効にして置かないと不便でどうしようもないです。
ホームに .xmodemap というファイルを作成し、それに以下のような記述して保存することで、X11でaltキーが有効になります。設定を読み込ませるにはX11の再起動が必要です。(MacBook Proには右altキーはないので、keycode 69 の設定は不要かもしれません。 .xmodemap の設定の意味がよく分かっていないので変な記述が含まれているかもしれません。)

keycode 66 = Alt_L
keycode 69 = Alt_R

ちなみに、つぎの記述で左commandキーをcontrolキーとして使えるようにしておけば、Macの他のアプリと同じようなキーバインディングが使えるようになって、誤操作が減るかと思います。

keycode 63 = Control_L
add control = Control_L

ひとつ忘れていました。altキーを有効にするには、X11の環境設定ダイアログの入力タブで、「3 マウスボタンをエミュレート」と「システムのキーボードレイアウトに従う」のチェックを外して、「X11の代替キーを有効にする」にチェックを入れておく必要もあったかと思います。

X11で日本語入力を可能にする方法を紹介しているブログも発見しましたが、これはだいぶ手間がかかりそうなので、たぶんやりません。むかし挑戦して懲りました。日本語入力を可能にしたところで、いろいろと制約があってそれほど使いやすくはならなかったし。

AndroidのIntent-Filterについてまとめてみる

自分でつくった複数のアプリ間の連携を行うために、AndroidのIntentについてまとめてみる。自分用のメモなので他の人には読みづらいかも。

Intent Filterは、Actionテスト、Categoryテスト、Dataテストの3種類がある。Intentが投げられた場合、この3つのテストを通過したActivityだけが起動の対象となる。

1.Actionテスト

manifest側のintent-filterには複数指定できるが、Intentオブジェクトには一つしか指定できない。
Intentオブジェクトで指定されたactionが、intent-filterに含まれていれば受け取れる。
(intent-filterにactionが指定されていないと、Intentを絶対に受け取れない。)


2.Categoryテスト

intent-filterにもIntentオブジェクトにも複数指定できる。
Intentオブジェクトに指定されたすべてのCategoryが、intent-filterに指定されていれば受け取れる。(intent-filter側にもっとたくさんのCategoryがふくまれていてもよい。)
IntentオブジェクトにCategoryを指定しないこともできるが、Activityが明示的に指定されない暗黙的Intentの場合は、intent-filterにandroid.intent.category.DEFAULTが含まれていなければ、テストをパスしない。


3.Dataテスト

intent-filterは、mimeType、shema、host、port、path、authority(hostとportをまとめてもの)といった要素を持つ。IntentオブジェクトにはUriやDataTypeを指定する。
テストは複雑なので詳細に調べていない。


ということで、Dataテストについては、ちゃんと調べていない。しかし、DataテストはデータをUriで渡す場合に、そのデータをちゃんと処理できるかどうかでフィルタリングするというのが意図なので、Uriでデータを渡さない場合は考慮しなくてよさそう。

起動されるアプリをフィルタリングしたい場合は、Categoryテストを用いるのが正しいだろう。
たとえば、ACTION_VIEWを渡しただけでは、実にさまざまなアプリが起動の候補になってしまう。これを絞り込むには、Categoryを追加してやれば良い。

CentOS5にpython2.7をインストールする

たしかこんな感じでできたはず。

$ sudo yum install zlib-devel

$ wget http://www.python.org/ftp/python/2.7.2/Python-2.7.2.tgz
$ tar zxvf Python-2.7.2.tgz
$ cd Python-2.7.2
$ ./configure --with-threads --enable-shared
$ sed #zlib/zlib Modules/Setup
$ make
$ sudo make install
$ echo /usr/local/lib > /etc/ld.so.conf.d/python2.7.conf

2012-02-26

UIWebViewにオリジナルの小さな操作ボタンをつける

UIWebViewに戻るボタンや更新ボタンをつける予定です。こういう場合、UIToolBarをUIWebViewの下だか上だかに配置してそこにボタンを並べるのが一般的だと思うのですが、あえてそうはしません。というのは、UIToolBarはスペースをとり過ぎるからです。今つくっているアプリにはタブがあるので、そこにさらにツールバーがのっかると、広告を表示するスペースがなくなってしまい収益化が困難になってしまうという大問題が発生します。(広告なんて邪魔なだけと思う人もいるかもしれませんが、開発者にとっては重要です。収益があがらなかったら開発を続けることができません!)

Androidアプリの場合は、電話機のメニューボタンが押されたときに「戻る」とか「更新」とかメニューを表示してやればいいので普段は画面のスペースを占拠せずに機能を実装することができるのですが、iPhoneアプリではこれができないので、画面のどこかにボタンを表示してやらなければなりません。なるべく小さく表示したいと思います。Mercuryというブラウザアプリで全画面機能をつかっているときに、ボタンがUIWebView内部の下の方に、ちっちゃなボタンが並ぶのですが、だいたいそんなイメージです。

実現する方法としては、二つほど考えられます。
  1. 背景の透明なUIToolbarをつくって、そこにボタンを並べる
  2. UIWebViewの上に直接ボタンを並べる
背景な透明なUIToolbarを利用する場合の懸念点は、透明ではあってもツールバーが存在する領域がユーザーを惑わすことにならないかということです。たとえば、ボタンとボタンの間に十分にスペースがあるように見えるために、ユーザーがそこでリンクをクリックしようとするかもしれません。特に、デバイスを横向きにしたときにはそうです。
それに対して、UIWebViewの上に直接ボタンを並べる方法には、懸念すべき点があまりありません。スクリーンの向きを変えたときのアニメーションが変になるかもしれないということぐらいでしょうか。実装するのはこちらの方が時間がかかりそうですが、この方法で行きたいと思います。

まず、ボタンの画像を粗くつくって、その後それをつかって機能を実装。それからボタン画像の完成度を高めるという手順で、今日中に完了させたいです。

アイコンはいつもInkscapeというソフトで作成しています。
必要なのは、戻るボタン、進むボタン、更新ボタン、中止ボタン、メニュー表示ボタン、以上の5つです。
画像大きさはだいたい20px(ratina用は40px)くらいで良さそうです。半透明の灰色のボタン枠をつくって、それを白で切り抜くということにします。選択状態のときの画像などは、デフォルトの挙動に任せます。
アイコンはいつも無料のInkscapeでつくるのですが、PNGで出力する画像全体を透明にする方法がよく分からないので、この部分はGIMPで処理した方が良いかもしれません。

*透過画像をGIMPでつくるのではなく、UIButtonにalphaをしてしてやれば済む話かもしれません。UIBarButtonItemにはalphaを指定できませんが、UIButtonなら可能です。(ちなみにUIBarButtonItemの親クラスは、UIButtonではなくてUIBarItemです。)

背景の透明なUIToolbarをつくる

背景が透明なUIToolbarをつくるには、alphaを設定してもダメです。これだと、UIToolbarの上にのってるボタンまで透明になってしまいます。

では、
toolbar.backgroundColor = [UIColor clearColor];
toolbar.opaque = NO;
toolbar.translucent = YES;
という具合にやればいいかというと、これもダメです。普通に背景が表示されてしまいます。

stackoverflowで解決策が紹介されていました。UIToolbarを継承して新しいクラスをつくればいいようです。次のような具合にやります。

TransparentToolbar.h
@interface TransparentToolbar : UIToolbar
@end

TransparentToolbar.m
@implementation TransparentToolbar

// Override draw rect to avoid
// background coloring
- (void)drawRect:(CGRect)rect {
    // do nothing in here
}

// Set properties to make background
// translucent.
- (void) applyTranslucentBackground
{
    self.backgroundColor = [UIColor clearColor];
    self.opaque = NO;
    self.translucent = YES;
}

// Override init.
- (id) init
{
    self = [super init];
    [self applyTranslucentBackground];
    return self;
}

// Override initWithFrame.
- (id) initWithFrame:(CGRect) frame
{
    self = [super initWithFrame:frame];
    [self applyTranslucentBackground];
    return self;
}

@end

2012-02-25

UIScrollViewに余白を設定する

今日は、UIScrollViewをいじっています。これまでに自分でViewに追加した経験がないので、はっきり言って使い方がいまいち分かっていません。簡単なサンプルプログラムをつくりながら理解を深めていく計画です。

UIScrollViewを使って今回実現したいことは、スクロールして表示するコンテンツの外に余白をつくることです。このとき重要なプロパティが二つあります。ひとつはcontentSize、もう一つはcontentInsetです。
contentSizeは、UIScrollviewに表示したいコンテンツの大きさです。たとえば、500x500ピクセルの写真を表示する場合は、contentSizeを500x500に設定することになります。そして、contentInsetというのが、contentSizeの上や下にスクロール可能な領域を広めるために使うプロパティになります。
なので、UIScrollViewの外側に余白をつくるには、contentInsetを設定してやればそれでミッション完了です。
次のようにして設定します。

      scrollView.contentInset=UIEdgeInsetsMake(50.0, 30.0, 44.0, 0);

これで、上に50.0、左に30.0、下に44.0、右に0の余白ができます。

しかし、このままだとスクロールビューの中での現在の表示位置を示すインジケータが余白部分も加味して表示されてしまいます。これが好ましくない場合は、scrollIndicatorInsetsを使って調整します。たとえば、こんな具合です。

      scrollView.scrollIndicatorInsets=UIEdgeInsetsMake(50.0, 30.0, 44.0, 0);

こうしてやれば、余白部分を考慮せずに、コンテンツ内での位置をインジケータで示すことができます。多くの場合には、contentInsetとscrollIndicatorInsetsに同じ値を設定することで望ましい挙動が得られるのではないかと思われます。

2012-02-24

Ubuntu11.10のデスクトップをUnityからLinux Mintと同じcinnamonに変更

このページを参考にcinnamonを導入した。cinnamonは、最近Ubuntuに代わって人気のあるLinux Mintのデスクトップ環境らしい。
sudo add-apt-repository ppa:merlwiz79/cinnamon-ppa
sudo apt-get update
sudo apt-get install cinnamon cinnamon-session cinnamon-settings
で終わり。いったんログアウトしてからログインしなおせば、cinnamonを選択できる。 UbuntuのUnityというのは使いにくかったが、cinnamonは違和感なく使える。

JavaScriptでページのトップに滑らかにスクロールさせる

JavaScriptでページのトップにまでアニメーション付きで滑らかにスクロースするコードを書いてみました。特徴は、jQueryなどのライブラリを必要とせず、ほんの数行のプログラムだという点です。ちゃんとテストしていないから、動かないブラウザもあるかもしれません。
(function()  {
    var getScrollTop = function() {
        return document.body.scrollTop || document.documentElement.scrollTop;
    };
    var getScrollLeft = function() {
        return document.body.scrollLeft || document.documentElement.scrollLeft;
    };
    var startTime = new Date() - 0;
    var scrollInterval = setInterval(function() {
        var currTime = new Date() - startTime;
        if (currTime > 500) {
            clearInterval(scrollInterval);
            scrollTo(0, 0); 
        }
        else {
            var x = getScrollLeft() * 3 / 4;
            var y = getScrollTop() * 3 / 4;
            scrollTo(x, y);
        }
    }, 30);
})();
なんでこんなコードを書いたかというと、AndroidのWebViewに滑らかにスクロールさせるメソッドが見つからなかったからです。scrollTo(int x, int y)だと一瞬でページのトップに来てしまいます。しかし、上のコードをJavaにそのまま移植すると、スレッドの関係で問題が生じる可能性がある気がします。そこらへんはまだ検証していません。

UIActionSheetのボタンをNSArrayで指定

iPhoneアプリの開発で、UIActionSheetを表示するとき項目を配列で指定したいことはよくあると思います。たとえば、iOS 5で追加されたTwitter APIを利用できるかによって、表示項目の数を動的に変更したい場合などです。
しかし不便なことに、initWithTitleのotherButtonTitlesには、配列を渡すことができません。
この問題に対処するためには、一度ボタンなしでUIActionSheetを初期化した後で、項目をaddButtonWithTitleで追加していくことになります。
ただしこのとき注意が必要なのは、初期化のときに、キャンセルボタンをつくってはいけないということです。他のボタンを追加した後でキャンセルボタンを指定しないと、iOS 4.xのバグでボタンのインデックスがメチャクチャになってしまい、actionSheet:clickedButtonAtIndex: の処理が正常に行えなくなります。(iOS 5では正常に動作するようです。)

だいたい次のようなコードになるかと思います。

// ボタンなしで一度初期化する
UIActionSheet* sheet =[ [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"ActionSheetTitle", nil) delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil] autorelease];
// ボタンを追加
NSArray* buttonTitleArray = [MyButtons array];
for (NSString* buttonTitle in buttonTitleArray) {
[sheet addButtonWithTitle:buttonTitle];
}
// 古いiOSには、キャンセルボタンも後から追加しないインデクスが狂うバグがある
sheet.cancelButtonIndex = [sheet addButtonWithTitle:NSLocalizedString(@"Cancel", nil)];

SyntaxHighliterをBloggerに導入

プログラムコードを表示するために、SyntaxHighliterを導入しています。SyntaxHighliterをブロガーに簡単にインストールできるように、下記のスクリプトを作成して埋め込んでいます。これをHTML/JavaScriptガジェットにコピペすれば導入完了です。(SyntaxHighliterの使い方の詳細については作者Webページを参照してください。)
<script src="http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shCore.js" type="text/javascript"></script>
<script src="http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shAutoloader.js" type="text/javascript"></script>
<script type="text/javascript">
    (function() {
        var loadCssFile = function(href) {
            var link = document.createElement("link");
            link.rel = "stylesheet";
            link.href = href;
            link.type = "text/css"
            document.getElementsByTagName('head')[0].appendChild(link);
        };
        loadCssFile("http://alexgorbatchev.com/pub/sh/3.0.83/styles/shThemeEclipse.css");
        loadCssFile("http://alexgorbatchev.com/pub/sh/3.0.83/styles/shCore.css");
        SyntaxHighlighter.autoloader(
            'applescript            http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushAppleScript.js',
            'actionscript3 as3      http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushAS3.js',
            'bash shell             http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushBash.js',
            'coldfusion cf          http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushColdFusion.js',
            'cpp c                  http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushCpp.js',
            'c# c-sharp csharp cs   http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushCSharp.js',
            'css                    http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushCss.js',
            'delphi pascal          http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushDelphi.js',
            'diff patch pas         http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushDiff.js',
            'erl erlang             http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushErlang.js',
            'groovy                 http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushGroovy.js',
            'java                   http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushJava.js',
            'jfx javafx             http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushJavaFX.js',
            'js jscript javascript  http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushJScript.js',
            'perl pl                http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushPerl.js',
            'php                    http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushPhp.js',
            'text plain             http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushPlain.js',
            'py python              http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushPython.js',
            'ruby rails ror rb      http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushRuby.js',
            'sass scss              http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushSass.js',
            'scala                  http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushScala.js',
            'sql                    http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushSql.js',
            'vb vbnet               http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushVb.js',
            'xml xhtml xslt html    http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushXml.js'
        );
        SyntaxHighlighter.config.stripBrs = true;
        SyntaxHighlighter.config.bloggerMode = true;
        SyntaxHighlighter.defaults.toolbar = false;
        SyntaxHighlighter.all();
    })();
</script>