守破離でいこう! -Let's go with SyuHaRi!-

2008/03/28

Rails と AIR で、付箋紙アプリ

Stickynotes

Rails の最新は 2.0.2 ですし、AIR は正式版の 1.0 が公開されました。
情報はある程度追ってはいるものの、やはり実際に試してみないとなかなか身につきません。

というわけで、以前から気になっていた、[Think IT] 第1回:付箋紙アプリケーションを作ろう!を参考に、Ruby on RailsとAIRによるデスクトップ付箋紙アプリケーションを作ってみました。

Adobe AIR のインストールはこちらから。

サンプル付箋紙アプリをお試しいただく場合は、こちらから。
ちなみに、このアプリはユーザ管理はしておりません。ので、大変ソーシャルな付箋アプリです(汗

Download Stickynotes 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

ラベル: ,

automatically translated by Google Translate Hack!

naoki 21:51 | 0 comments |
HaloScan: |

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

これで、無事にアップデートすることができました。

ラベル: ,

automatically translated by Google Translate Hack!

naoki 22:02 | 0 comments |
HaloScan: |

2008/03/09

RubyでGoogle Data APIs

Googleが提供するさまざまなサービスには、Google Data APIsを利用して、データの取得や更新を行うことができます。

現在、以下のサービスへの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)'

残念ながら、使用感はいまいちでした。他によいライブラリはないのでしょうか・・・?

ラベル: , ,

automatically translated by Google Translate Hack!

naoki 21:18 | 0 comments |
HaloScan: |