Androidのキーボードのモードオプション

AndroidのsoftInput(画面上に出るキーボード)は、いくつかのモードがある。
例えば右下のEnterボタンに当たるキーが、「次へ」、「実行」、や検索を意味する虫眼鏡になっている、など。
この指定については意外と情報が少なかったので覚書をしておこうと思う。
softInputを呼び出す元のEditTextのLayoutで、android:imeOptions=”actionSearch” などのようにしてモードを指定できる。

<EditText android:id="@+id/edit_test" android:imeOptions="actionSearch"/>

指定できる出来るオプションには、

  • actionSearch
  • actionGo
  • actionNext

等がある。

androidで複数のMapViewを使う場合の注意点

androidで複数のActivityで別々のMapViewを持っている場合に、戻るボタンで戻ってきた時に、前の画面でのMapViewのレイアウトが引き継がれてしまう事があって嵌ったので覚書。

元々adroidでは1つのアプリの中で使用できるMapViewは1つだけらしく、別のActivityでもMapViewのインスタンスは使いまわされているみたい。(詳細未確認)

解決策としては、戻ったActivityのonResume 内で、MapView#requestLayout() を呼んでやると直る。
こんな感じ。

public void onResume(){
  super.onResume();
  MapView mapView = (MapView) findViewById(R.id.MapView);
  mapView.requestLayout();
}

普通によくありそうな事なのに、なかなか情報が見つからなかった、、、。

androidエミュレータからlocalhostへの接続

androidの開発でlocalhostへの接続ではまったので覚書を。
androidから外部APIなどへの接続って言うのは良くあると思いますが、開発時にローカルに開発用サーバーを準備してそこへ接続って言うのは良くやると思います。
ここで、Webアプリの開発をやっている人は(僕もそうですが)うっかり、接続先のホスト名をlocalhost 又は 127.0.0.1 としてしまいそうですが、これだと接続でしません。
connection refused とか言われてしまいます。
AVDから見たlocalhostはAVD自身になるそうです。
その代わりにローカルPCには、10.0.2.2 というIPが割り当てられているようです。
なので、これをURLとして指定してあげると接続できます。
考えてみれば当然のことのようですが、やりががちな間違いです。

cakePHPのimageBehaviorについて

こちらの
http://blog.syuhari.jp/archives/1905
記事でimageBehaviorというのを知り、早速使っていました。
findしたときにも、各画像のパス結果レコードに挿入してくれて便利なのですが、どうやらリレーションテーブルのiに関しては処理してくれないようです。
なので、上記ソースに少し手を入れて、belongsToのテーブルに関しても持ってきてくれるように追記してみました。
即席なのでおかしなところがあったらご指摘いただけるとありがたいです。


<?php

class ImageBehavior extends ModelBehavior {

	var $settings = null;

	function setup(&$model, $config = array()) {
		$this->imageSetup($model, $config);
	}

	function imageSetup(&$model, $config = array()) {
		$settings = Set::merge(array(
		'baseDir'=> '',
		), $config);

		if (!isset($settings['fields'])) $settings['fields']=array();
		$fields=array();
		foreach($settings['fields'] as $key=>$value) {
			$field = ife(is_numeric($key), $value, $key);
			$conf = ife(is_numeric($key), array(), ife(is_array($value),$value,array()));
			$conf=Set::merge(
			array (
				'thumbnail' => array('prefix'=>'thumb',
					         'create'=>false,
					         'width'=>'100',
 					         'height'=>'100',
 					         'aspect'=>true,
							 'allow_enlarge'=>true,
		                    ),
				'resize'=>null, // array('width'=>'100','heigth'=>'100'),
				'versions' => array(
				),
			), $conf);
			foreach ($conf['versions'] as $id=>$version) {
				$conf['versions'][$id]=Set::merge(array(
 					         'aspect'=>true,
							 'allow_enlarge'=>false,
		                    ),$version);
			}
			if (is_array($conf['resize'])) {
				if (!isset($conf['resize']['aspect'])) $conf['resize']['aspect']=true;
				if (!isset($conf['resize']['allow_enlarge'])) $conf['resize']['allow_enlarge']=false;
			}
			$fields[$field]=$conf;
		}
		$settings['fields']=$fields;

		$this->settings[$model->name] = $settings;
	}

	/**
	 * Before save method. Called before all saves
	 *
	 * Overriden to transparently manage setting the item position to the end of the list
	 *
	 * @param AppModel $model
	 * @return boolean True to continue, false to abort the save
	 */
	function beforeSave(&$model) {
		extract($this->settings[$model->name]);
		if (empty($model->data[$model->name][$model->primaryKey])) {
		}

		//Ajax でプレアップロードされている場合はそちらを使用する。 modified by M.Araki
		if(isset($model->data[$model->name][TMP_IMG_FILE_NAME])){
			$field = 'image';
			$tempData[$field] = array(
				'tmp_name' => TMP_IMG_DIR_SYSTEM.DS.$model->data[$model->name][TMP_IMG_FILE_NAME],
				'name' => $model->data[$model->name][TMP_IMG_FILE_NAME],
				'type' => $model->data[$model->name][TMP_IMG_FILE_TYPE],
				'error' => '0'
			);
			$model->data[$model->name][$field] = $model->data[$model->name][TMP_IMG_FILE_TYPE];
		}else{
			$tempData = array();
			foreach ($fields as $key=>$value) {
				$field = ife(is_numeric($key), $value, $key);
				if (isset($model->data[$model->name][$field])) {
					if ($this->__isUploadFile($model->data[$model->name][$field])) {
						$tempData[$field] = $model->data[$model->name][$field];
						$model->data[$model->name][$field]=$this->__getContent($model->data[$model->name][$field]);
					} else {
						unset($model->data[$model->name][$field]);
					}
				}
			}
		}

		$this->runtime[$model->name]['beforeSave'] = $tempData;
		return true;
	}

	function afterSave(&$model) {
		extract($this->settings[$model->name]);
		if (empty($model->data[$model->name][$model->primaryKey])) {
		}

		$tempData = $this->runtime[$model->name]['beforeSave'];
		unset($this->runtime[$model->name]['beforeSave']);
		foreach($tempData as $field=>$value) {
			$this->__saveFile($model, $field, $value);
			@unlink($value['tmp_name']);
		}

		return true;
	}


	function afterFind(&$model, &$results, $primary) {
		extract($this->settings[$model->name]);
		$relatedModels = $model->belongsTo;

		if ( is_array( $results ) ) {
			$i=0;
			if (isset($results[0])) {
				while ( isset( $results[$i][$model->name] ) && is_array( $results[$i][$model->name] ) )  {
					foreach ($fields as $field => $fieldParams) {
						if (isset($results[$i][$model->name][$field]) && ($results[$i][$model->name][$field]!='')) {
							$value=$results[$i][$model->name][$field];
							$results[$i][$model->name][$field]=$this->__getParams($model, $field, $value, $fieldParams, $results[$i][$model->name]);
						}
					}
					foreach($relatedModels as $relatedModelName => $param){
						$relatedModel = $model->{$relatedModelName};
						if(!isset($this->settings[$relatedModelName])) continue;
						foreach ($this->settings[$relatedModelName]['fields'] as $field => $fieldParams) {
							if (isset($results[$i][$relatedModelName][$field]) && ($results[$i][$relatedModelName][$field]!='')) {
								$value=$results[$i][$relatedModelName][$field];
								$results[$i][$relatedModelName][$field]=$this->__getParams($relatedModel, $field, $value,$fieldParams, $results[$i][$relatedModelName]);
							}
						}
					}
	                $i++;
				}
			} else {
					foreach ($fields as $field => $fieldParams) {
						if (isset($results[$model->name][$field]) && ($results[$model->name][$field]!='')) {
							$value=$results[$model->name][$field];
							$results[$model->name][$field]=$this->__getParams($model, $field, $value, $fieldParams, $results[$model->name]);
						}
					}
					foreach($relatedModels as $relatedModelName => $param){
						$relatedModel = $model->{$relatedModelName};
						foreach ($this->settings[$relatedModelName]['fields'] as $field => $fieldParams) {
							if (isset($results[$relatedModelName][$field]) && ($results[$relatedModelName][$field]!='')) {
								$value=$results[$relatedModelName][$field];
								$results[$relatedModelName][$field]=$this->__getParams($relatedModel, $field, $value,$fieldParams, $results[$relatedModelName]);
							}
						}
					}

/*				foreach ($fields as $field => $fieldParams) {
					if (isset($results[$model->name][$field]) && ($results[$i][$model->name][$field]!='')) {
						$value=$results[$i][$model->name][$field];
						$results[$model->name][$field]=$this->__getParams($model, $field, $value, $fieldParams, $results[$model->name]);
					}
				}*/
			}
		}
		return $results;
		//return true;
	}

	function __getParams(&$model, $field, $value, $fieldParams, $record) {
		extract($this->settings[$model->name]);
		$result=array();
		if ($value!='') {
			$folderName = $this->__getFolder($model, $record);
			$ext=$this->decodeContent($value);
			$fileName=$field .'.'. $ext;
			$result['path']=$folderName.$fileName;

			$thumb=$fields[$field]['thumbnail'];
			if ($thumb['create']) {
				$result['thumb']=$folderName.$this->__getPrefix($thumb).'_'.$fileName;
			}
			foreach($fields[$field]['versions'] as $version) {
				$result[$this->__getPrefix($version)]=$folderName.$this->__getPrefix($version).'_'.$fileName;
			}
		}
		return $result;
	}

	/**
	 * Before delete method. Called before all deletes
	 *
	 * Will delete the current item from list and update position of all items after one
	 *
	 * @param AppModel $model
	 * @return boolean True to continue, false to abort the delete
	 */
	function beforeDelete(&$model) {
		$this->runtime[$model->name]['ignoreUserAbort'] = ignore_user_abort();
		@ignore_user_abort(true);
		return true;
	}

	function afterDelete(&$model) {
		extract($this->settings[$model->name]);

		foreach ($fields as $field=>$fieldParams) {
			$folderPath=$this->__getFullFolder($model, $field);
			uses ('folder');
			$folder = &new Folder($path = $folderPath, $create = false);
			if ($folder!==false) {
				@$folder->delete($folder->pwd());
			}
		}

		@ignore_user_abort((bool) $this->runtime[$model->name]['ignoreUserAbort']);
		unset($this->runtime[$model->name]['ignoreUserAbort']);
		return true;
	}

	function __isUploadFile($file) {
		if (!isset($file['tmp_name'])) return false;
		return (file_exists($file['tmp_name']) && $file['error']==0);
	}

	function __getContent($file) {
		return $file['type'];
	}
	function decodeContent($content) {
		$contentsMaping=array(
	      "image/gif" => "gif",
	      "image/jpeg" => "jpg",
	      "image/pjpeg" => "jpg",
	      "image/x-png" => "png",
	      "image/jpg" => "jpg",
	      "image/png" => "png",
	      "application/x-shockwave-flash" => "swf",
	      "application/pdf" => "pdf",
	      "application/pgp-signature" => "sig",
	      "application/futuresplash" => "spl",
	      "application/msword" => "doc",
	      "application/postscript" => "ps",
	      "application/x-bittorrent" => "torrent",
	      "application/x-dvi" => "dvi",
	      "application/x-gzip" => "gz",
	      "application/x-ns-proxy-autoconfig" => "pac",
	      "application/x-shockwave-flash" => "swf",
	      "application/x-tgz" => "tar.gz",
	      "application/x-tar" => "tar",
	      "application/zip" => "zip",
	      "audio/mpeg" => "mp3",
	      "audio/x-mpegurl" => "m3u",
	      "audio/x-ms-wma" => "wma",
	      "audio/x-ms-wax" => "wax",
	      "audio/x-wav" => "wav",
	      "image/x-xbitmap" => "xbm",
	      "image/x-xpixmap" => "xpm",
	      "image/x-xwindowdump" => "xwd",
	      "text/css" => "css",
	      "text/html" => "html",
	      "text/javascript" => "js",
	      "text/plain" => "txt",
	      "text/xml" => "xml",
	      "video/mpeg" => "mpeg",
	      "video/quicktime" => "mov",
	      "video/x-msvideo" => "avi",
	      "video/x-ms-asf" => "asf",
	      "video/x-ms-wmv" => "wmv"
		);
		if (isset($contentsMaping[$content]))
			return $contentsMaping[$content];
		else return $content;
	}


	function __saveAs($fileData, $fileName=null, $folder) {

		if (is_writable($folder)) {
			if (is_uploaded_file($_FILES[$fileData]['tmp_name']))
			{
				if (empty($fileName)) $fileName = $_FILES[$fileData]['name'];
				copy($_FILES[$fileData]['tmp_name'], $folder.$fileName);
				return true;
			}
			else
			{
				return false;
			}
		}
		else
		{
			return false;
		}
	}

	function __getFolder(&$model, $record) {
		extract($this->settings[$model->name]);
		return  Inflector::camelize($model->name) .'/'. $record[$model->primaryKey] . '/';
	}
	function __getFullFolder(&$model, $field) {
		extract($this->settings[$model->name]);
		return  WWW_ROOT . IMAGES_URL. Inflector::camelize($model->name) .DS. $model->id .DS;
	}

	function __saveFile(&$model, $field, $fileData) {
		extract($this->settings[$model->name]);
		$folderName = $this->__getFullFolder($model, $field);
		$ext=$this->decodeContent($this->__getContent($fileData));
		$fileName=$field .'.'. $ext;

		uses ('folder');
		uses ('file');
		$folder = &new Folder($path = $folderName, $create = true, $mode = 0777);

		$files=$folder->find($fileName);

		$file= &new File($folder->pwd().DS.$fileName);

		$fileExists=($file!==false);
		if ($fileExists) {
			@$file->delete();
		}

		if (isset($fields[$field]['resize']['width']) && isset($fields[$field]['resize']['height'])) {
			$file=$folder->pwd().DS.'tmp_'.$fileName;
			copy($fileData['tmp_name'], $file);
			$this->__resize($folder->pwd(),'tmp_'.$fileName,$fileName,$field, $fields[$field]['resize']);
			@unlink($file);
		} else {
			$file=$folder->pwd().DS.$fileName;
			copy($fileData['tmp_name'], $file);
		}



		if ($fields[$field]['thumbnail']['create']) {
			$fieldParams=$fields[$field]['thumbnail'];
			$newFile=$this->__getPrefix($fieldParams).'_'.basename($fileName);
			$this->__resize($folder->pwd(),$fileName,$newFile, $field, $fieldParams);
		}
		foreach($fields[$field]['versions'] as $version) {
			$fieldParams=$fields[$field]['thumbnail'];
			$newFile=$this->__getPrefix($version).'_'.basename($fileName);
			$this->__resize($folder->pwd(),$fileName,$newFile,$field, $version);

		}

	}


	function __getPrefix($fieldParams) {
		if (isset($fieldParams['prefix'])) {
			return $fieldParams['prefix'];
		} else {
			return $fieldParams['width'].'x'.$fieldParams['height'];
		}
	}

	/**
	 * Automatically resizes an image and returns formatted IMG tag
	 *
	 * @param string $path Path to the image file, relative to the webroot/img/ directory.
	 * @param integer $width Image of returned image
	 * @param integer $height Height of returned image
	 * @param boolean $aspect Maintain aspect ratio (default: true)
	 * @param array    $htmlAttributes Array of HTML attributes.
	 * @param boolean $return Wheter this method should return a value or output it. This overrides AUTO_OUTPUT.
	 * @return mixed    Either string or echos the value, depends on AUTO_OUTPUT and $return.
	 * @access public
	 */
    function __resize($folder, $originalName, $newName, $field, $fieldParams) {

        $types = array(1 => "gif", "jpeg", "png", "swf", "psd", "wbmp"); // used to determine image type
        $fullpath = $folder;

        $url = $folder.DS.$originalName;

        if (!($size = getimagesize($url)))
            return; // image doesn't exist

		$width=$fieldParams['width'];
		$height=$fieldParams['height'];
        if ($fieldParams['allow_enlarge']===false) { // don't enlarge image
			if (($width>$size[0])||($height>$size[1])) {
				$width=$size[0];
				$height=$size[1];
			}
		} else {
	        if ($fieldParams['aspect']) { // adjust to aspect.
	            if (($size[1]/$height) > ($size[0]/$width))
	                $width = ceil(($size[0]/$size[1]) * $height);
	            else
	                $height = ceil($width / ($size[0]/$size[1]));
	        }
        }

        $cachefile = $fullpath.DS.$newName;  // location on server

        if (file_exists($cachefile)) {
            $csize = getimagesize($cachefile);
            $cached = ($csize[0] == $width && $csize[1] == $height); // image is cached
            if (@filemtime($cachefile) < @filemtime($url)) // check if up to date
                $cached = false;
        } else {
            $cached = false;
        }

        if (!$cached) {
            $resize = ($size&#91;0&#93; > $width || $size[1] > $height) || ($size[0] < $width || $size&#91;1&#93; < $height || ($fieldParams&#91;'allow_enlarge'&#93;===false));
        } else {
            $resize = false;
        }

        if ($resize) {
            $image = call_user_func('imagecreatefrom'.$types&#91;$size&#91;2&#93;&#93;, $url);
            if (function_exists("imagecreatetruecolor") && ($temp = imagecreatetruecolor ($width, $height))) {
                imagecopyresampled ($temp, $image, 0, 0, 0, 0, $width, $height, $size&#91;0&#93;, $size&#91;1&#93;);
              } else {
                $temp = imagecreate ($width, $height);
                imagecopyresized ($temp, $image, 0, 0, 0, 0, $width, $height, $size&#91;0&#93;, $size&#91;1&#93;);
            }
            call_user_func("image".$types&#91;$size&#91;2&#93;&#93;, $temp, $cachefile);
            imagedestroy ($image);
            imagedestroy ($temp);
        }

    }


}
?>


PHPのjson_decodeの挙動がおかしい件

PHPのjson_decode()の挙動がおかしくてはまったので覚書を。
通常、jsonのフォーマットではフィールド名(プロパティ名)部分はクオートする必要はない、というかクオートしないのが正しい形と思っていたが、PHPのjson_decode($json_str)に、クオートされていないフィールド名を持つjsonを渡すと、戻り値はundefinedとなる。
これって仕様としてどうなんでしょうか、、、
ちなみに、 json_last_error()というメソッドがあり、直近のエラーを拾うことができるようなので、おかしいかな?というときは使ってみると何かわかるかも。

設定値や定数はどこに書くか

アプリで外出しにしておきたい設定や、初期設定したい定数などどこに書いておくのがきれいかな?、とよく考えていたのですが、(以前は、config.phpやbootstrap.php, app_controller.php などに書いていたのですが、)、cakephpのグローバルメソッドで

config('file1', 'file2',......);

というのがありました。
このメソッド何をしているかというと、単純に引数で指定した文字列をファイルパスに変換して(/config/file1.php のようになる)、そのパスにファイルがあればinclude_onceしてくれます。また、ファイルが存在しない場合はスルーします。
なので、初期設定したい定数や、場合よって読み込みたい設定などを書いておき、bootstrap.phpでconfig(‘file’);とやってやるといい感じです。

CakePHPでコンソールアプリを作る(その2)

CakePHPでコンソールアプリを作るに引き続き、基本的なメソッドを紹介します。

  • $this->out($str)
  • 標準出力に$strを出力します。

  • $this->in($title, $choice, $default)
  • $choice(配列)で指定した選択リストを表示します。ちょうどbake等の選択肢のようなイメージになります。
    bakeで言うと$this->in(‘Look okay?’, array(‘y’,’n’,’q’), ‘y’); のような感じです。

  • $this->hr($int)
  • “-”を使って区切り線を出力します。$intで数値をしてすると、区切り線の下に指定数分の空白行が入ります。

  • $this->error($title, $msg)
  • $title, $msg を出力してexitします。

  • $this->createFile($path, $content)
  • $path で指定したパスにファイルを作成します

CakePHPでコンソールアプリを作る

PHPでコマンドランの簡単な処理を作ることになったので、せっかくなのでCakePHPのShellを使ってみました。
ブログなどでの取り上げも少ないようなのでちょっと覚書的に書いておこうと思います。
使い方は非常に簡単で、/app/vendors/shells/ に、test.php のような名前でファイルを作ります。
このファイルの中身は、Shellクラスをオーバーライドしたものとします。
例えばこんな感じです。

<?php
class TestShell extends Shell{
	private $_str = array(
			'construct' => 'コンストラクト',
			'initialize' => '初期化処理',
			'startup' => '開始処理',
			'main' => '基本処理',
			'choise' => '選択してください',
			'suspend' => '中止されました',
			'openFile' => 'ファイルを開いて表示します。パスを入力してください。:'
		);

	function initialize(){
		mb_convert_variables('SJIS', 'UTF-8', $this->_str);
		$this->out($this->_str['initialize']);
	}
	function startup(){
		$this->out($this->_str['startup']);
	}
	function main(){
		if($this->in($this->_str['choise'], array('y','q'), 'y') == 'q'){
			$this->error('line:'.__LINE__.' on '.__FILE__, $this->_str['suspend']);
		}else{
			$this->hr(1);
			$this->out($this->_str['main']);
			$this->hr(1);
			$input = $this->in($this->_str['openFile']);
			echo file_get_contents($input);
		}
	}
}
?>

コマンドラインからのアクセスには、

cake シェルのクラス名 メソッド名 引数1 引数2,,,,

のようになります。メソッド名を省略すると、mainメソッドが呼ばれます。
一番最初の cake は /cake/console/ 配下にあるコマンドなので、あらかじめ /cake/console を環境変数に入れておいた方が使いやすいでしょう。
その上で、APPのディレクトリに移動して上記のようにコマンドをたたきます。
また、別の方法として

cake -app APPへのパス シェルのクラス名 メソッド名 引数1 引数2,,,,

のように明示的に指定すると、どこからでもアクセスできます。

Shellクラスにはコンポーネントと同様に、startup, initialize 等のコールバックメソッドが用意されていますので、初期化処理や定型処理を書いておくのもいいかもしれません。
また、コントローラと同様に、$uses メンバ変数で使用するモデルを指定できるので、コントローラで行う処理と同じ感覚でモデルのデータにアクセスできます。

Windowsのコマンドラインから使用する場合、文字コードがSJISになるので、今回は上記のように、メンバ変数に使用するメッセージ文言を定義しましたが、CakePHPのLocale機能を使って、

$this->in(__get('openFile'));

のようにするとより綺麗かも知れません。

可視領域をシュミレートするGBroserSize

Above the Fold といいう言葉があるようですが、訪問者のアクセズブラウザ上の可視領域(統計的な情報にもとづく)をサイトデザインに重ね合わせてくれるGoogleBrwoserSizeといういサービスがあるようです。
いつもWebの仕事をしていて、LPOとかユーザビリティなど話題にしていますが、非常に初歩的な指摘を受けた気になります。
そう、そもそも自分が普段Webを利用するうえで、各ページに対して、どのくらいの注意を払って見ているでしょう?
検索結果のディスクリプション+遷移先のファーストビュー、というのがほとんどではないでしょうか。
このサービスはGoogleの訪問者の情報を元に、ユーザーの可視領域を視覚化しているらしいですが、対象がどのサイトであれ、ユーザーの動向というのは大きく変わらないと思います。
ちょっと気にしてみると何かの気づきになるかもと思いました。

debugKitToolbarが激しく便利

CakePHPのデバッグ用プラグインのdebugKitToolbarが激しく便利です。
今まではFireCakeやFirePHPを使っていて、それなりに重宝していたのですが、先日のカンファレンスで小耳に挟み、早速使ってみました。
まずは、http://www.ohloh.net/p/cakephp-debugkit/ からダウンロードして、解凍してできたdebug_kitを /app/plugins/におきます。で終了。
後はapp_controller.php等で読み込んであげればOK。

var $components = array('DebugKit.Toolbar');

こんな感じ。
ただ、デフォルトでは debugが1以上だと無条件に動作するので、モバイルのテストなどする時は気をつけたほうがいいかも。
バイト数制限を簡単に超えてしまいテストすらできません。

getting health insurance in new york buy clomid online UK major health insurance companies buy levitra uk online what are wells fargo hours buy finasteride affordable health insurance for children accutane no prescription medical center of trinity viagra online uten resept midwestern university wellness center dapoxetine top individual health insurance companies viagra ireland