Expressでログを出力する
おぼえ書き.
http://d.hatena.ne.jp/higed/20130907/1378558020
でWebサーバを作った時にExpressを使ったが,
デバッグログの出し方が分からなかったので困った,
var express = require("express"),app = express(); app.use(express.logger("dev"));
loggerミドルウェアで[dev]フォーマット使えばレスポンスタイムが表示されるんですね.
便利.
canvas+WebSocket(node.js)で画像にポインタを表示する[Firefox限定]
canvas上でマウスクリックしたポインタと背景画像を画像合成して,
WebSocketで送信するサンプルアプリを作成しました.
とりあえずFirefox限定です.
Chromeだと合成した画像が表示されません.
StackOverFlowでimg.onload = function() {}でイケると書いてありましたが,
動きませんでした.
- 参考にしたサイト
- https://github.com/einaros/ws
- http://www.osaka3t.com/socket/realtime.html
- http://nigohiroki.hatenablog.com/entry/2013/01/04/025502
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
- http://blog.agektmr.com/2013/09/canvas-png-blob.html
- http://d.hatena.ne.jp/miruto824/20110527/1306472982
動作
- クライアント画面のcanvas上でマウスクリックをするとjpegの背景画像と,ポインタを表示する
- マウスクリックをトリガに背景画像とポインタを合成して,WebSocketサーバに送信する
- WebSocketサーバは受信した画像を全クライアントへブロードキャストする
- 全クライアントからポインタ情報を送信可能
構成
- WebSocketサーバ:node.js + ws
- HTTPサーバ:node.js + express
WebSocketサーバ(localhost:8080) <-> Clients(Firefox) <-> HTTPサーバ(localhost:8080)
実行手順
- コードを以下の通り配置する
/hoge SampleWebSocketServer.js SimpleWebSocketServer.js /hoge/public SampleWebSocketCanvasClient.html backgtround.jpg
- 2つのコンソールで以下を実行する
node SimpleWebSocket.js node SimpleWebServer.js
Code
SimpleWebServer.js(HTTPサーバ)
var express = require("express"), app = express(); app.configure(function() { app.use(express.static(__dirname + "/public")); }); app.use(express.logger("dev")); app.listen(8000);
SampleWebSocketServer.js(WebSocketサーバ)
var WebSocketServer = require("ws").Server; var clients = []; function initialize() { wss = new WebSocketServer({port:8080}); wss.on("connection", function(ws) { clients.push(ws); ws.on("message", function(data, flags) { var type; // Check binary if(flags.binary) { console.log("binary"); type = "binary"; broadcast(data, flags.binary); } else { console.log("text"); type = "text"; broadcast(data. flags.binary); } }); // Broadcast function broadcast(data, flag) { clients.forEach(function (client, i) { client.send(data, {binary:flag, mask:false}); }); } ws.on("close", function() { console.log("disconnected"); clients = clients.filter(function (client) { return (client === ws) ? false : true; }); }); }); } initialize();
SampleWebSocketCanvasClient.html(クライアント)
<!DOCTYPE HTML> <html lang="ja-JP"> <head> <meta charset="UTF-8"> <script type="text/javascript"> // constant var canvasFrameEndX = 300; var canvasFrameEndY = 300; // canvas var canvas; var context; var saveImage; // global variables var img; var host = "ws://localhost:8080"; var ws = new WebSocket(host); function initialize() { // DOM img = document.getElementById("img"); canvas = document.getElementById("canvas"); // Canvas context context = canvas.getContext("2d"); // 画像の読み込み canvas.addEventListener('mousedown', function(e) { clear(); var x; var y; if(e.hasOwnProperty("offsetX")) { x = e.offsetX; y = e.offsetX; } else { // for Firefox x = e.layerX; y = e.layerY; } draw(x, y); }, false); // Canvas context.strokeStyle = '#FF0000'; context.lineWidth = 5; context.strokeStyle =""; // WebSocket ws.onopen = function() { console.log("WebSocket connect"); ws.onmessage = function(e) { if(e.data instanceof Blob) { console.log("blob"); img.src = (URL || WebKit).createObjectURL(e.data); } else if(typeof e.data === "string") { console.log("string"); console.log(e.data); } }; }; } function clear() { context.clearRect(0, 0, canvasFrameEndX, canvasFrameEndY); } function draw(x, y) { var background = new Image(); background.src = "background.jpg"; background.onload = function() { // img.onloadに入れても動かない }; context.drawImage(background, 0, 0); context.strokeRect(x, y, 50, 50); /*** canvas に絵を書くコード ***/ var type = 'image/png'; // canvas から DataURL で画像を出力 var dataurl = canvas.toDataURL(type); // DataURL のデータ部分を抜き出し、Base64からバイナリに変換 var bin = atob(dataurl.split(',')[1]); // 空の Uint8Array ビューを作る var buffer = new Uint8Array(bin.length); // Uint8Array ビューに 1 バイトずつ値を埋める for (var i = 0; i < bin.length; i++) { buffer[i] = bin.charCodeAt(i); } // Uint8Array ビューのバッファーを抜き出し、それを元に Blob を作る var blob = new Blob([buffer.buffer], {type: type}); //console.log(blob); ws.send(blob); } window.addEventListener("load", initialize, false); </script> <title>canvas sample</title> <style type="text/css"> #canvasdiv { width: 300px; border-style: solid; border-color: green; } #canvas { width: 300px; border-style: solid; border-color: red; } #recieveimage { width: 300px; border-style: solid; border-color: blue; } </style> </head> <body> <div id="canvasdiv"> <canvas id="canvas" width="300px" height="300px"></canvas> </div> overlay <div id="recieveimage"> <img src="data:image/jpeg,base64" id="img" width="300px"/> </div> </body> </html>
言語仕様の参考サイト
HTML5勉強の前に,JavaScriptの勉強中.
サイ本で勉強するつもりでしたが,
時間がかかりそうなため,
サンプルを作りながら必要に応じてサイ本を見る勉強方法に変更しました・.
JavaScriptはWebブラウザ毎にAPIが異なったりするためか,
まとまって解説するページがありませんでした.
探した中でMDN(Mozilla Developer Network)が分かりやすかったです.
https://developer.mozilla.org/ja/docs/Web/JavaScript
サンプルをさっさと書こう.
ListViewに追加,削除するサンプル
ListViewの勉強で作ったサンプル.
参考にしたページは以下.
http://techbooster.org/android/ui/9039/
http://www.mitoroid.com/category/android/android_listview2.php
動作は以下の通り.
- 動作環境はAndroid 3.0以降(FragmentManagerを使っているので)
- テキストボックス入力してボタンで追加
- ListView内のアイテムを長押しで削除
意外と追加,削除といった動的な処理のサンプルは少ないようです.
探すとありますね.
DB操作も一緒になったサンプルとか.
http://handin.sakura.ne.jp/archives/368
package com.example.samplelistview; import android.os.Bundle; import android.app.Activity; import android.app.AlertDialog.Builder; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.FragmentManager; import android.content.DialogInterface; import android.util.Log; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.Toast; /* * ListViewのアイテムを追加,削除するクラス * テキストボックスでアイテムを入力する. * ListViewアイテムの長押しで当該アイテムを削除する. */ public class MainActivity extends Activity { private static final String DEBUG = "DEBUG"; private ArrayAdapter<String> adapter = null; private Button _button = null; private ListView _listView = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // LayoutファイルのListViewのリソースID _listView = (ListView) findViewById(R.id.list_view); // Androidフレームワーク標準のレイアウト adapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1); // ListViewの初期表示 adapter.add("Japan"); adapter.add("Tokyo"); _listView.setAdapter(adapter); // ListViewアイテムを選択した場合の動作 _listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // 選択したListViewアイテムを表示する ListView list = (ListView) parent; String selectedItem = (String) list.getItemAtPosition(position); Toast.makeText(getApplicationContext(), selectedItem, Toast.LENGTH_LONG).show(); Log.d(DEBUG, selectedItem); } }); // ListViewアイテムの長押しでListViewアイテムを削除する // リスナーはAdapaterView.onItemLongClickListener()を利用する // 利用しないとListViewのアイテムを取得できない _listView.setOnItemLongClickListener (new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { ListView list = (ListView) parent; String selectedItem = (String) list .getItemAtPosition(position); Log.d(DEBUG, "Long click : " + selectedItem); showDialogFragment(selectedItem); return false; } }); // EditTextのエントリをListViewアイテムに追加する _button = (Button) findViewById(R.id.add_list_button); _button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { EditText editText = (EditText) findViewById(R.id.add_edit_text); String entry = editText.getText().toString(); if (entry.equals("")) { Log.d(DEBUG, "Entry is empty"); } else { addListData(entry); } // ボタン押下後のエントリ文字列を削除する editText.setText(""); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } // ListViewアイテムにエントリを追加するメソッド private void addListData(String entry) { adapter.add(entry); } // FragmentManagerでDialogを管理するクラス private void showDialogFragment(String selectedItem) { FragmentManager manager = getFragmentManager(); DeleteDialog dialog = new DeleteDialog(); dialog.setSelectedItem(selectedItem); dialog.show(manager, "dialog"); } /* * 削除ダイアログを生成する内部クラス * 内部クラスは外部クラスのインスタンスを直接参照できないため, * Activity#getActivity()で外部クラスのインスタンスを取得している. */ public static class DeleteDialog extends DialogFragment { private static final String DEBUG = "DEBUG"; /* 選択したListViewアイテム */ private String selectedItem = null; // 削除ダイアログの作成. @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Log.d(DEBUG, "onCreateDialog()"); Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Delete entry."); builder.setMessage("Are you really?"); // positiveを選択した場合の処理. // リスナーはDialogINterface#onClickListener() // を使うことに注意. builder.setPositiveButton("Yes I'm serious.", new DialogInterface.OnClickListener() { // 外部クラスのインスタンスを直接参照することができないため, // Activity#getActivity()でActivityのインスタンスを取得する @Override public void onClick(DialogInterface dialog, int which) { MainActivity activity = (MainActivity) getActivity(); activity.removeItem(selectedItem); } }); AlertDialog dialog = builder.create(); return dialog; } // 選択したアイテムをセットする. // HACK:削除ダイアログ自身に選択したアイテムを渡せないため, // ダイアログをユーザが呼び出した際に,Activityで選択した項目を保持しておく. public void setSelectedItem(String selectedItem) { Log.d(DEBUG, "setSelectedItem() - item : " + selectedItem); this.selectedItem = selectedItem; } } // 選択したアイテムを削除する. protected void removeItem(String selectedItem) { Log.d(DEBUG, "doPositiveClick() - item : " + selectedItem); adapter.remove(selectedItem); } }
Nexus7のUSBドライバインストール
Google Nexus7にUSBドライバをインストール.
そのまま挿せば使えるかと思っていたが,別途インストール手順が必要だった.
2.3.3のサンプルを入れてみたけど,上手く動かないのね...
AlertDialogを使ってみる.
物凄い間が空いた再開です.
AndroidでAlertDialogを使って,Googleのトップページを表示します.
タブ幅を3にすると,枠の端で折り返されてしまうことが多いのだろうか.
プログラム用に上手く表示できるようにしたい.
SyntaxHighLighterが上手く動かない...
- activity_mock_alert_dialog.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MockAlertDialogActivity" > <Button android:id="@+id/dialog_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="Dialog" /> </RelativeLayout>
- MockAlertDialogActivity.java
package com.example.mockalertdialog; import android.net.Uri; import android.os.Bundle; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.Toast; /** * http://google.co.jpを表示するアクティビティ ボタンを押すとダイアログ表示->OKとすると,暗黙的Intentでブラウザに遷移する. * */ public class MockAlertDialogActivity extends Activity { private Button dialogButton = null; private AlertDialog.Builder alertBuilder = null; private String URLString = null; private Intent intent = null; // State @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mock_alert_dialog); } @Override protected void onResume() { super.onResume(); // URL URLString = "http://google.co.jp"; // AlertDialogの生成 alertBuilder = new AlertDialog.Builder(MockAlertDialogActivity.this); dialogButton = (Button) findViewById(R.id.dialog_button); // ボタンのリスナーを無名クラスで生成 dialogButton.setOnClickListener(new OnClickListener() { // クリック時のメソッド @Override public void onClick(View v) { // [Title]はタイトル部, [Message]は本文に該当する alertBuilder.setTitle("Warning!"); alertBuilder.setMessage(URLString); // Positive, Neutral, NegativeをAlertDialogでは選択可で,使用方法は自由. // Positiveの選択をした場合 alertBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 暗黙的インテントの生成.URLの文字列をパースする. // Intentを0以上とするとActivity#onActivityResult()に戻って,アプリケーション/Activity間で結果を受け取る事ができる. intent = new Intent(Intent.ACTION_VIEW, Uri .parse(URLString)); startActivityForResult(intent, -1); } }); // Neutralの選択をした場合 alertBuilder.setNeutralButton("Neutral", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(getApplicationContext(), "Neutral", Toast.LENGTH_SHORT).show(); } }); // Negativeの選択をした場合 alertBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); // AlertDialogの表示 alertBuilder.show(); } }); } @Override protected void onPause() { super.onPause(); } // Menu @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_mock_alert_dialog, menu); return true; } }
SVNでnon-lFのエラー
以前に記載したnon-LFのエラーですが,対処したと思った下記エラーが,
Cannot accept non-LF line endings in 'svn:log' property
コミット時に再度エラーが出ました.
ReadyNAS上のリポジトリにコミットできない - higedの日記
grayhole: subversion Cannot accept non-LF アホかい
ここらへんのリンクから行けますが,
1. It is possible unknowingly commit a value for svn:ignore which mixes eol-style, using a version of Subclipse on Windows. 2. The presence of this property value effectively breaks svnsnync 1.6.0 because it insists on eol-style purity.
文字コードのチェックで止まるみたいですね.
今回はソースの文字コードも変更したのですが,
コミット時のログも同様に文字コードのチェック対象となるようです.
そこで,暫定対処としてはログをコミット後に追記します.
手順は下記の通り.
- コミット時にログを空白のままコミット
- 作業ディレクトリを選択して[右クリック]->[TortoiseSVN]->[ログを表示(L)]
- 最新のリビジョンを選択して[右クリック]->[ログメッセージを編集]
- 「ログメッセージを編集」画面で,ログを投入して,[OK]を押下
- 「ログメッセージ」画面で[OK]を押下
文字コードのチェック機能を入れる必要は特にないでしょ...