2008/03/28
Rails と AIR で、付箋紙アプリ

Rails の最新は 2.0.2 ですし、AIR は正式版の 1.0 が公開されました。
情報はある程度追ってはいるものの、やはり実際に試してみないとなかなか身につきません。
というわけで、以前から気になっていた、[Think IT] 第1回:付箋紙アプリケーションを作ろう!を参考に、Ruby on RailsとAIRによるデスクトップ付箋紙アプリケーションを作ってみました。
Adobe AIR のインストールはこちらから。
サンプル付箋紙アプリをお試しいただく場合は、こちらから。
ちなみに、このアプリはユーザ管理はしておりません。ので、大変ソーシャルな付箋アプリです(汗
基本的な動作は、[Think IT] 第1回:付箋紙アプリケーションを作ろう!と、Ruby on RailsとAdobe AIRでデスクトップアプリを作る - Pokeal.COMをベースに、なんとなくタスクトレイアイコンも使ってみました。ついでに、常に前面表示も可能です。
Railsに関しては、実質、次の2行のみです。これでRestfulなバックエンドアプリのできあがりです・・。
$ ruby script/generate scaffold sticky body:text x:float y:float width:integer height:integer
$ rake db:migrate
なお、Railsアプリは、先日紹介した、Herokuで構築しています。
AIRでは、透過するTextFieldを作るのにちょっと苦労しました。
ポイントは、blendModeをLAYERにしたSpriteでした。
dynamic textfield alpha problem - kirupaForum
I am not sure if this is quite what you are looking for but here is a solution I found for adjusting alpha on a TextField object.
// create an empty sprite
var rect:Sprite = new Sprite();
rect.blendMode = BlendMode.LAYER;
addChild(rect);
var txtField:TextField = new TextField();
txtField.txtColor = 0x000000;
txtField.alpha = .5;
txtField.appendText("SOME TEXT");
rect.addChild(txtField);
the key is to set the blendMode property on the sprite to LAYER.
以下、参考までにソースコードです。
Menu.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="300" height="200" creationComplete="onCreationComplete();" closing="closing(event);">
<mx:Script>
<![CDATA[
import mx.core.BitmapAsset;
[Embed(source="icons/broken-16x16.png")]
private var icon16:Class;
private var stickies:Array = [];
private function create():void {
setStatus(">> Create new sticky");
var sticky:Sticky = new Sticky();
sticky.setAlwaysInFront(isAlwaysInFront.selected);
sticky.save();
sticky.show();
stickies.push(sticky);
}
private function onCreationComplete():void {
setStatus(">> Initializing...");
// is supports system tray icon
if (NativeApplication.supportsSystemTrayIcon) {
var images:Array = [];
images.push((new icon16() as BitmapAsset).bitmapData);
nativeApplication.icon.bitmaps = images;
var systemTrayIcon:SystemTrayIcon = (nativeApplication.icon as SystemTrayIcon);
systemTrayIcon.tooltip = "Stickynotes";
var nativeMenu:NativeMenu = new NativeMenu();
var menuItemNew:NativeMenuItem = new NativeMenuItem("New Stickynote");
menuItemNew.addEventListener(Event.SELECT, function(e:Event):void {
create();
});
var menuItemReload:NativeMenuItem = new NativeMenuItem("Reload Stickynotes");
menuItemReload.addEventListener(Event.SELECT, function(e:Event):void {
load();
});
var menuItemRestore:NativeMenuItem = new NativeMenuItem("Restore window");
menuItemRestore.addEventListener(Event.SELECT, function(e:Event):void {
restore();
});
var menuItemExit:NativeMenuItem = new NativeMenuItem("Exit");
menuItemExit.addEventListener(Event.SELECT, function(e:Event):void {
exit();
});
nativeMenu.addItem(menuItemNew);
nativeMenu.addItem(menuItemReload);
nativeMenu.addItem(menuItemRestore);
nativeMenu.addItem(menuItemExit);
systemTrayIcon.menu = nativeMenu;
}
load();
}
private function load():void {
setStatus(">> Loading from http://stickynotes.heroku.com ...");
closing(null);
var request:URLRequest = new URLRequest("http://stickynotes.heroku.com/stickies.xml");
request.method = 'GET';
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, function(e:Event):void {
messages.text += e.target.data + "\n";
var xml:XML = new XML(e.target.data);
for each (var element:Object in xml.sticky) {
var sticky:Sticky = new Sticky();
sticky.id = element.id;
sticky.editor.text = element.body;
sticky.window.x = element.x * Capabilities.screenResolutionX;
sticky.window.y = element.y * Capabilities.screenResolutionY;
sticky.window.width = element.width;
sticky.window.height = element.height;
sticky.setAlwaysInFront(isAlwaysInFront.selected);
sticky.updateStatus();
sticky.show();
stickies.push(sticky);
}
clearStatus();
});
loader.load(request);
}
private function setStatus(s:String):void {
this.status = s;
}
private function clearStatus():void {
this.status = "";
}
private function closing(e:Event):void {
for each (var sticky:Sticky in stickies) {
if (sticky) {
sticky.window.close();
}
}
stickies = [];
}
private function saveAll():void {
setStatus(">> Save stickies");
for each(var sticky:Sticky in stickies) {
sticky.save();
}
}
private function onChangeHandle():void {
for each (var sticky:Sticky in stickies) {
sticky.setAlwaysInFront(isAlwaysInFront.selected);
}
}
]]>
</mx:Script>
<mx:Button left="10" top="10" label="New" id="new_btn" click="create();" />
<mx:Button left="60" top="10" label="Reload" id="load_btn" click="load();" />
<mx:Button right="10" top="10" label="Save" id="save_btn" click="saveAll();" />
<mx:CheckBox left="10" top="40" label="always in front" id="isAlwaysInFront" change="onChangeHandle();" />
<mx:TextArea right="10" top="70" left="10" bottom="10" id="messages" />
</mx:WindowedApplication>Sticky.as
package {
import flash.text.*;
import flash.display.*;
import flash.events.*;
import flash.system.*;
import flash.net.*;
import mx.controls.*;
public class Sticky {
public var window:NativeWindow; // sticky window
public var editor:TextField; // sticky edit area
public var id:Number; // primary key
private var button:SimpleButton = new SimpleButton(); // cloase button
private var resizeHandle:SimpleButton = new SimpleButton(); // resize handle
private var x:Number; // sticky x
private var y:Number; // sticky y
private var height:Number; // sticky height
private var width:Number; // sticky width
private var body:String; // sticky body
private var sprite:Sprite = new Sprite();
private static var RESIZE_HANDLE_SIZE:int = 20;
/* create sticky window */
public function Sticky():void {
var initOptions:NativeWindowInitOptions = new NativeWindowInitOptions();
initOptions.systemChrome = NativeWindowSystemChrome.NONE;
initOptions.transparent = true;
initOptions.type = NativeWindowType.LIGHTWEIGHT;
window = new NativeWindow(initOptions);
window.alwaysInFront = false;
window.stage.align = StageAlign.TOP_LEFT;
window.stage.scaleMode = StageScaleMode.NO_SCALE;
// layer for transparent editor
sprite.blendMode = BlendMode.LAYER;
window.stage.addChild(sprite);
// for edit area
editor = new TextField();
editor.x = editor.y = 0;
editor.selectable = true;
editor.border = false;
editor.type = TextFieldType.INPUT;
editor.multiline = true;
editor.background = true;
editor.wordWrap = true;
editor.backgroundColor = 0xE6E082;
editor.alpha = 1;
sprite.addChild(editor);
// window position of center
window.x = 0.5 * Capabilities.screenResolutionX - 300 * 0.5
window.y = 0.5 * Capabilities.screenResolutionY - 100 * 0.5
// size for window and edit area
window.width = editor.width = 300;
window.height = editor.height = 100;
// resize for window and edit area
window.stage.addEventListener(Event.RESIZE, function(e:Event):void {
resized();
});
// move and resize
window.stage.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):void {
var x:Number = e.stageX;
var y:Number = e.stageY;
if (y > window.height - RESIZE_HANDLE_SIZE && x > window.width - RESIZE_HANDLE_SIZE) {
//window.startResize(NativeWindowResize.BOTTOM_RIGHT);
} else {
window.startMove();
}
});
window.stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void {
if (isChanged()) {
save();
}
});
editor.addEventListener(FocusEvent.FOCUS_OUT, function(e:FocusEvent):void {
if (isChanged()) {
save();
}
});
} // end of function Sticky
// show window
public function show():void {
// create close button
button.x = window.width - 15;
button.y = 5;
button.upState = createBox(0xE6E082, 10, 0.3);
button.overState = createBox(0xE6E082, 10, 1);
button.downState = createBox(0xCCCCCC, 10, 1);
button.hitTestState = button.upState;
button.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void {
var request:URLRequest = new URLRequest("http://stickynotes.heroku.com/stickies/" + id + ".xml");
request.method = 'DELETE';
var loader:URLLoader = new URLLoader();
loader.load(request);
window.close();
});
window.stage.addChild(button);
// create resize handle
resizeHandle.x = window.width - 15;
resizeHandle.y = window.height - 15;
resizeHandle.upState = createResizeHandle(0xE6E082, 10, 0.3);
resizeHandle.overState = createResizeHandle(0xE6E082, 10, 1);
resizeHandle.downState = createResizeHandle(0xCCCCCC, 10, 1);
resizeHandle.hitTestState = button.upState;
resizeHandle.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):void {
window.startResize(NativeWindowResize.BOTTOM_RIGHT);
});
window.stage.addChild(resizeHandle);
window.visible = true;
}
// call after window resized
public function resized():void {
editor.width = window.width;
editor.height = window.height;
button.x = window.width - 15;
button.y = 5;
resizeHandle.x = window.width - 15;
resizeHandle.y = window.height - 15;
}
// save sticky
public function save():void {
// create URL of sticky
var request:URLRequest
if (!id) {
request = new URLRequest("http://stickynotes.heroku.com/stickies.xml");
request.method = 'POST';
} else {
request = new URLRequest("http://stickynotes.heroku.com/stickies/" + id + ".xml");
request.method = 'PUT';
}
// create paramater for edit
var variables:URLVariables = new URLVariables();
variables['sticky[x]'] = window.x / Capabilities.screenResolutionX;
variables['sticky[y]'] = window.y / Capabilities.screenResolutionY;
variables['sticky[width]'] = window.width;
variables['sticky[height]'] = window.height;
variables['sticky[body]'] = editor.text;
request.data = variables;
// send
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, function(e:Event):void {
var xml:XML = new XML(e.target.data);
if (!id) {
id = xml.id;
}
updateStatus();
});
loader.load(request);
}
public function setAlwaysInFront(value:Boolean):void {
window.alwaysInFront = value;
if (value) {
editor.alpha = 0.85;
} else {
editor.alpha = 1;
}
}
public function updateStatus():void {
// sticky status
x = window.x;
y = window.y;
width = window.width;
height = window.height;
body = editor.text;
}
/* private methods ***********/
// close button
private function createBox(color:uint, radius:Number, vis:Number):Shape {
var xShape:Shape = new Shape();
xShape.graphics.lineStyle(1, 0x000000);
xShape.graphics.beginFill(color);
xShape.graphics.drawRect(0, 0, radius, radius);
xShape.graphics.moveTo(0, radius);
xShape.graphics.lineTo(radius, 0);
xShape.graphics.moveTo(radius, radius);
xShape.graphics.lineTo(0, 0);
xShape.graphics.endFill();
xShape.alpha = vis;
return xShape;
}
private function createResizeHandle(color:uint, radius:Number, vis:Number):Shape {
var xShape:Shape = new Shape();
xShape.graphics.lineStyle(1, 0x000000);
xShape.graphics.beginFill(color);
xShape.graphics.moveTo(0, radius);
xShape.graphics.lineTo(radius, 0);
xShape.graphics.lineTo(radius, radius);
xShape.graphics.lineTo(0, radius);
xShape.graphics.endFill();
xShape.alpha = vis;
return xShape;
}
// is paramater changed
private function isChanged():Boolean {
return (x != window.x || y != window.y ||
width != window.width || height != window.height ||
body != editor.text);
}
} // end of class Sticky
} // end of package
2008/03/11
CentOS 4.6 で、ZABBIX 1.4.4
社内のサーバ監視に、ZABBIX(日本語サイト)を使用しているのですが、ZABBIX 1.4 では、「WEB monitoring」というWebサイトを簡単に監視できる機能が搭載されたようなので、この機能を使いたいがため、1.1.3 からアップデートしてみました。
New in ZABBIX 1.4 FEATURES(日本語サイト)
- Installation Wizard
Installation Wizard automatically checks pre-requisites, database connectivity and generates configuration file for WEB front end.
- Support of new database engines
Support of SQLite has been implemented. It allows use of ZABBIX in embedded environments.
- WEB interface improvements
Speed and usability of WEB interface has been improved very much.
- New notification methods
Native support of Jabber messaging has been introduced.
- Distributed monitoring
ZABBIX distributed monitoring is made for complex environments consisting of different locations.
ZABBIX supports monitoring of unlimited number of nodes. Centralized configuration allows easy configuration of all nodes from a single location.
- Auto-discovery
ZABBIX distributed monitoring module allows easy deployment of ZABBIX systems. The discovery support IP ranges, service checks, agent and SNMP checks for efficient auto-discovery.
- Many-to-many template linkage
More flexible host-template linkage saves time and make configuration of hosts more flexible and straight forward.
- Database watchdog
ZABBIX server will automatically warn group of users if database is down and continues normal operations when database is back.
- WEB monitoring
WEB monitoring module allows flexible and easy monitoringof availability and performanceof WEB sites and WEB based applications. It supports passing of GET and POST variables.
- XML data import/export
New XML data import and export functionality is an excellent way of sharing templates, hosts configuration and items/triggers related information.
- Support of Windows Vista
ZABBIX Windows agent supports Windows Vista, both 32 and 64 bit versions.
- More flexible actions
Multiple operations (notifications, script execution) per action are supported. Choice of action calculation algorithm was introduced.
- Server-side external checks
Server-side external checks can be used to introduce custom checks executed on ZABBIX server side.
- New user permission schema
Old user permission schema is no longer support. It was replaced by new more efficient, yet simple, schema working on level of user groups and host groups.
- Support of hysteresis
ZABBIX support use of different trigger expressions for going to ON and OFF states.
- Support of slide show
Several screens can be grouped into a slide show for better presentation.
- ZABBIX server can spread load across several servers
Groups of server side processes (discoverer, poller, HTTP poller, trapper, etc) can be located on different physical servers for better performance and availability.
- Other improvements
See Release Notes for a complete list of improvements.
CentOS 4.6 の環境で、いくつか躓いたところがあったので、メモです。
CURLのバージョン
Web Monitoring機能を有効にするには、--with-libcurlオプションを付けてインストールします。
$ ./configure --enable-server --with-mysql --with-net-snmp --with-libcurl
すると、次のようなエラーが出て止まってしまいます。
checking for libcurl >= version 7.13.1... no
configure: error: Not found Curl library
CentOS 4.x では、curlのバージョンは7.12なのでダメなようです。
いろいろ調べてみると、結局、curlを自身でバージョンアップするしかないようです。
以下のようにして解決です。
centosinstall
$ cd ~/src/
$ wget ftp://ftp.planetmirror.com/pub/curl/libcurl4-devel-7.16.2-1.i386.rpm
$ wget ftp://ftp.planetmirror.com/pub/curl/libcurl4-7.16.2-1.i386.rpm
$ sudo yum install openssl096b
$ sudo yum remove curl-devel
$ sudo rpm -i libcurl4*
PHP4
ZABBIXのフロントエンドはPHPなのですが、やっとインストールが成功して管理画面にアクセスすると以下のエラーが発生。
PHP Parse error: parse error, unexpected T_STATIC, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or '}' in /u02/zabbix/web/include/copt.lib.php on line 112
調べてみると、PHP5用のコードになっているのが原因のようです。
以下のようにして解決です。
Blank web install - ZABBIX Forums
includes/copt.inc.php を修正し、'static function' を 'function' にすべて置き換えます。
$ vi includes/copt.inc.php
:%s/static function/function/g
これで、無事にアップデートすることができました。
2008/03/09
RubyでGoogle Data APIs
Googleが提供するさまざまなサービスには、Google Data APIsを利用して、データの取得や更新を行うことができます。
現在、以下のサービスへのAPIが提供されています。
- Google Apps APIs
- Google Base data API
- Blogger data API
- Google Calendar data API
- Google Code Search data API
- Google Notebook data API
- Google Spreadsheets data API
- Picasa Web Albums data API
- Google Documents List data API
- YouTube data API
JavaScriptからSpreadsheetsを操作できないかと調べてみたのですが、JavaScript client libraryは、現在、Google Calendar と Bloggerのみのようです。
そこで、Rubyのライブラリを探してみると、gdata-rubyというライブラリがありました。
ただ、このライブラリは現在開発がストップしているらしく、SpreadsheetとBloggerのみに対応しているようです。
というわけで、せっかくなのでRubyでGoogle Spreadsheets data APIを試してみました。
ruby-gdataのインストール
gem install GData書き込みテスト
#!/usr/bin/env ruby
require 'gdata/spreadsheet'
gdata_user = 'xxx@gmail.com'
gdata_pass = 'xxx'
gs_key = 'xxx'
gs = GData::Spreadsheet.new(gs_key)
gs.authenticate(gdata_user, gdata_pass)
gs.add_to_cell 'sin(0.2)'
残念ながら、使用感はいまいちでした。他によいライブラリはないのでしょうか・・・?
