Ethna2.5以降でTemplateのディレクトリ・パスを変えたい場合

2009.03.15    Ethna, PHP, フレームワーク, 中井      hiko   

ethna2.3.6までは、デフォルトのTemplateディレクトリ(template/ja)から変更したい時に{App_Id}_ControllerでgetTemplatedirをオーバーライドするのが定石でした。ethnaコマンドもgetTemplatedirを見てくれるのでethna add-templateなども問題ありませんでした。

ただ、Ethna-2.5以降では、国際化対応のためLocaleが追加され、以下のジェネレータプラグインのコードのコメント通り(39,40行目)、Localeが入っていないと勝手に補完してくれるようです。このため、ethna add-templateコマンドでtemplateを生成する際にLocaleをディレクトリパスに強制的に入れられてしまうため、国際化対応したくない場合(笑)はここをスキップする必要があります。

今回は綺麗に拡張するのが面倒だったためコメントアウトしましたが、今後は何らかの切り替えができるといいかもしれませんね。

Ethna/class/Plugin/Generator/Ethna_Plugin_Generator_Template.php

13 /**
14  *  スケルトン生成クラス
15  *
16  *  @author     Masaki Fujimoto <fujimoto@php.net>
17  *  @access     public
18  *  @package    Ethna
19  */
20 class Ethna_Plugin_Generator_Template extends Ethna_Plugin_Generator
21 {
22     /**
23      *  テンプレートのスケルトンを生成する
24      *
25      *  @access public
26      *  @param  string  $forward_name   テンプレート名
27      *  @param  string  $skelton        スケルトンファイル名
28      *  @param  string  $locale         ロケール名
29      *  @param  string  $encoding       エンコーディング
30      *  @return true|Ethna_Error        true:成功 Ethna_Error:失敗
31      */
32     function &generate($forward_name, $skelton = null, $locale, $encoding)
33     {
34         //  ロケールが指定された場合は、それを優先する
35         if (!empty($locale)) {
36             $this->ctl->setLocale($locale);
37         }
38
39         //  ロケール名がディレクトリに含まれていない場合は、
40         //  ディレクトリがないためなのでそれを補正
41         $tpl_dir = $this->ctl->getTemplatedir();
42         if (!empty($locale) && strpos($tpl_dir, $locale) === false) {
43             $tpl_dir = $this->ctl->getDirectory(’template’);
44             $tpl_dir .= “/$locale”;
45         }
46         if ($tpl_dir{strlen($tpl_dir)-1} != ‘/’) {
47             $tpl_dir .= ‘/’;
48         }

C#でListBoxの項目が無い時の背景描画処理を変更する

2009.03.11    C#, Windows, 馬場   タグ: , , —    baba   

C#でListBoxの描画処理を変更したい場合、当然ながらOwnerDrawを使います。
DrawMode = DrawMode.OwnerDrawFixed;

これで
protected override void OnDrawItem(DrawItemPaintArgs e) {}
を定義すれば、項目は自由に描画できます。

背景描画も変更したい場合、ウィンドウプロシージャをオーバーライドして、

protected override void WndProc(ref Message m)
{
  if (m.Msg == 0x14) //0x14:WM_ERASEBKGND
  {
    PaintBackground();//任意の描画処理
    return;
  }
  base.WndProc(ref m);
}

とすれば良いです。

ただしこれだと、項目が0個の時に正しく背景が描画されません。どんな処理を書いても、必ずBackColorの単色塗りつぶしになってしまいます。

メッセージを追っていくと、どうやら項目が0個の時にはWM_ERASEBKGNDが呼ばれないみたいです。
ListBoxは内部的に複数のコントロールから出来ているようで、項目が0個でも、ウィンドウを移動しているとたまにWM_ERASEBKGNDが呼ばれたり、よく分からない挙動をします。

再描画時に必ずWM_PAINTは呼ばれるので、このときに背景描画処理をしてしまえば良さそうです。
しかし、単にWM_PAINT時に描画しても、その後に上から単色塗りつぶしが行われて、期待した結果は得られません。
WM_PAINT時に背景描画を行ってからreturn;してしまうと、描画は上手くいくのですが、無限に再描画が発生してCPUを食い尽くしてしまいます。これは、無効領域が有効化されないためです。

要するに、WinAPIで直接描画するのと同じように、BeginPaintをやって、EndPaintで無効領域を有効化してしまえば良いわけです。

[StructLayout(LayoutKind.Sequential)]
unsafe public struct PAINTSTRUCT
{
  public IntPtr hdc;
  public bool fErase;
  public RECT rcPaint;
  public bool fRestore;
  public bool fIncUpdate;
  public fixed byte rgbReserved[32];
}

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
  public int left;
  public int top;
  public int right;
  public int bottom;
}

[DllImport("user32.dll")]
public static extern IntPtr BeginPaint(IntPtr hWnd, out PAINTSTRUCT ps);

[DllImport("user32.dll")]
public static extern IntPtr EndPaint(IntPtr hWnd, ref PAINTSTRUCT ps);

protected override void WndProc(ref Message m)
{
  if (m.Msg == 0xf && Items.Count == 0) //0xf:WM_PAINT
  {
    PAINTSTRUCT ps;
    BeginPaint(Handle, out ps);
    
    PaintBackground(); //任意の描画処理
    
    EndPaint(Handle, ref ps);
    return;
  }
  base.WndProc(ref m);
}

注意点としては、PAINTSTRUCT構造体が固定長の配列を持つため、unsafeブロックで囲み、fixedキーワードで配列を静的確保しています(この記述は古いC#ではだめみたいです)。当然、ビルドオプションで「unsafeコードを許可する」にチェックを入れておいてください。

この手法を使う副作用として、VistualStudioのフォームデザイナが使えなくなります。unsafeコードを使うと、例外を吐いてだめになるみたいです。
しかたないので、これらのコードを全部#ifで囲み、条件付きコンパイルで普段は無効にしておき、リリース時に有効にすることで回避できます。これらのコードを無効にしても、背景が単色になるだけで、開発には支障がないはずです。

ここまでやるとC#で書く意味があるのか疑問になりますが、どうしてもリストボックスの背景を変更したいときの参考になれば幸いです。

XAMPPのApacheが起動しない

2009.03.10    Windows, ネットワーク, 馬場   タグ: , —    baba   

XAMPPでApacheが起動しなくてちょっと困りました。
XAMPP Control PanelでApacheの「起動」を押しても、瞬時に終了してしまいます。

コマンドプロンプトで
C:\xampp\apache\bin>apache.exe
を実行したら、httpd.conf (C:\xampp\apache\conf\httpd.conf) が間違ってるというエラーが出てきました。(ServerRootとDocumentRootを間違えた)

いや、書き間違える僕が悪いんですが、終了する前にエラーを出して欲しかった。
コマンドプロンプトから実行したときだけ、ちゃんとエラーを見せてくれます。

PHPプログラマのためのRubyチュートリアル #1 環境構築

2009.03.06    Ruby   タグ: —    tomotaka   

こんにちは、伊藤です。

PHPプログラマのためのRubyチュートリアルというネタで、書いていこうと思います。
記事の目的は社内のRubyに詳しくないが、PHPはバリバリな人たちをRubyに引き込むことです。

インストール

主にWindows, Mac OS X, Linuxの3つの環境について。
とりあえず安定版の1.8.6を入れる想定で書いています。

  • Windows
    お手軽なのはRuby One-Click Installer for Windowsです。
    rubygemsや、主要な便利ライブラリなどもまとめて入れてくれる便利パッケージです。
    ただ、1.8.7がリリースされてしばらく経つのに追従していないため、今後メンテナンスされるかどうか心配です。
    公式のmswin32のパッケージを使ってインストールする場合、
    ダウンロードしてきて、C:\ruby\などに配置して環境変数を通すだけです。簡単ですね。
  • Mac OS X
    実はMac OS Xには標準でrubyがインストールされています。ターミナルを開いてruby -vなどと打ってみるとバージョンがわかります。
  • Linux
    現在Linuxで大きなシェアを占めていると思われるFedora CoreやCentOSなどでは

    $ sudo yum install ruby
    

    として、パッケージマネージャyumを使って簡単にインストールすることができます。
    Debian GNU Linuxや、Ubuntu Linuxなどdpkg(apt-get, aptitude)を使うディストリビューションでも、
    同様に以下のようにしてインストールできます。

    $ sudo aptitude install ruby
    または
    $ sudo apt-get install ruby
    

各環境のコマンドラインでruby -vとすると以下のようにインストールされたrubyのバージョンを確認できます。

tomotaka@mist:~$ ruby -v
ruby 1.8.6 (2007-06-07 patchlevel 36) [x86_64-linux]

コンパイルされたアーキテクチャなんかもわかりますね。

FreeBSDなんかの環境は、僕はあまり使ったことがないのでよくわかりませんが、
portsなどを使って簡単に入れられたりするのでしょうか?
ソースからインストールするのも簡単だと思います。

rubyインタプリタにプログラムを実行させるには単純にruby [ファイル名]とすればOKです。
HelloWorldプログラムを作って動かしてみた例↓

tomotaka@mist:~$ cat test.rb
puts "Hello, world!"

tomotaka@mist:~$ ruby test.rb
Hello, world!

shellのシェバング行と実行権限を使えばファイル自体をコマンドとして叩くスタイルで起動することもできます。

tomotaka@mist:~$ which ruby
/usr/bin/ruby
tomotaka@mist:~$ cat test2.rb
#!/usr/bin/ruby
puts "Hello, world!!"

tomotaka@mist:~$ chmod +x ./test2.rb
tomotaka@mist:~$ ./test2.rb
Hello, world!!

irb(Interactive Ruby)

irbは対話的なrubyコードの実行環境です。
1行ずつ、命令を実行していくことができます。
rubyではclassやmodule, メソッド(関数)の定義も実行文なので、クラス定義やモジュール定義の細かい挙動のチェックなどもお手軽に使えます。
linuxのパッケージマネージャではrubyパッケージとruby-irbなどとして分離されている可能性があるので、
ぜひirbのパッケージを探してインストールしてあげてください。

irbの実行イメージ↓

tomotaka@mist:~$ irb
irb(main):001:0> a = 10
=> 10
irb(main):002:0> b = 20
=> 20
irb(main):003:0> a * 3 + b * 5
=> 130
irb(main):004:0> def echo3(str)
irb(main):005:1>   3.times do
irb(main):006:2*     puts str
irb(main):007:2>   end
irb(main):008:1> end
=> nil
irb(main):009:0> echo3("learning ruby!")
learning ruby!
learning ruby!
learning ruby!
=> 3

irbについて詳しくはirbのマニュアルを参照してみてください。

次回は基礎的な文法(if, while, メソッド定義, クラス定義)を説明したいと思います。

EthnaでXML-RPCの引数を連想配列にする

2009.03.04    PHP, Web, ネットワーク, 馬場   タグ: , —    baba   

Ethnaは標準でXML-RPCに対応しているので、
# ethna add-action-xmlrpc hoge
とやるだけで app/action-xmlrpc にActionのテンプレートが作れますし、クライアントからHogeメソッドをコールすると、自動的にHogeアクションが実行されます。
(エントリポイントはindex.phpではなくxmlrpc.phpになります)

しかし、デフォルトでは引数がActionFormのform定義と順番に1対1で対応するため、引数が多くなると使いづらくなります。

ここは、PHPらしく引数名をキーにした連想配列にしたいところ。
(プロジェクト名)_Controller に以下のメソッドを定義(オーバーライド)することで、XMLRPCの第一引数を連想配列として扱い、対応する名前のformに値がセットされるようになります。

function trigger_XMLRPC($method, $param)
{
    // アクション定義の取得
    $action_obj =& $this->_getAction($method);
    if (is_null($action_obj)) {
        return Ethna::raiseError("undefined xmlrpc method [%s]", E_APP_UNDEFINED_ACTION, $method);
    }

    // オブジェクト生成
    $backend =& $this->getBackend();

    $form_name = $this->getActionFormName($method);
    $this->action_form =& new $form_name($this);
    $def = $this->action_form->getDef();
    
    /***** ここから変更 *****/
    if (isset($param[0]) && is_array($param[0]))
    {
        foreach ($param[0] as $key => $value)
        {
            if (isset($def[$key]))
            {
                $this->action_form->set($key, $value);
            }
        }
    }
    /***** ここまで変更 *****/

    // バックエンド処理実行
    $backend->setActionForm($this->action_form);

    $session =& $this->getSession();
    $session->restore();
    $r = $backend->perform($method);

    return $r;
}

これで、クライアント側では引数の順番を気にせずに済みます。

ノートン先生

家族のPCにNorton Internet Security 2009体験版を入れてみたところ、Shurikenでのメール送受信が出来なくなりました。
POP3S/SMTPSは使えるのですが、POP/SMTPが使えない状態です。インストール直後は平気だったのに、ウィルススキャンをしたらだめになったとか。

110番と25番許可にしたり、ファイアウォールの一番上に全部許可のルール入れたり、アプリケーション制御で信頼するアプリに登録したり、ファイアウォールオフにしたり、ノートン先生丸ごとオフにしたり、色々やっても解決せず、

再起動したら直りました。謎。

やっぱり僕はKasperskyが好きです。高いけど。

IE7でposition:relativeの要素が重なるバグ

2009.03.03    HTML, javascript, 馬場   タグ: , , , , —    baba   

IE7で、JavaScriptを使って動的に要素を表示・非表示した場合、要素が重なってしまうことがあります。

実験ページ

IE7だと問題が再現し、IE8やFirefoxは再現しません。また、冒頭のDOCTYPE宣言を外してQuarksモードにしても再現しません。
IE7でのみ、Showボタンを押すと、文字が重なってしまうはずです。

不思議なことに、21行目のbackground指定を外すと、再現しません。
また、21行目の<div>開きと22行目の<div>開きの間に「スペース・TAB・改行以外の何か(コメントでも可)」を入れると、再現しません。
22行目の[height:2em] は、hasLayoutがtrueになれば何でもOKです。

まとめると、
・ブロック要素Aがある。
・Aの最初の子要素はブロック要素Bである。
・AはCSSで背景がnone以外に指定されていて、[hasLayout=false]である。
・Bは[position:relative] であり、なおかつ[hasLayout=true]である。

これらの条件を満たす場合、「Aの前にある要素の高さが変わり、AのY座標が変更になっても、BのY座標は再計算されない」というIE7独自のバグと言えそうです。

DebugToolbar等で位置の再計算を行うと、正しい位置に移動します。

おそらく計算順序にバグがあるのだと思います。position:relativeを使うときは気をつけましょう。

Ruby on Railsでコントローラでヘルパーメソッドを呼ぶ

# app/controllers/application.rb
ApplicationController < ActionController::Base
  class HelperImpl
    include ::Singleton
    include ::ApplicationHelper
  end

  protected

  def helper
    return HelperImpl.instance
  end
end

これでコントローラからApplicationHelperに定義されてるメソッドをhelperメソッド経由で呼べる。

COPYRIGHT [C] 2009 BEYOND PERSPECTIVE SOLUTIONS LTD. ALL RIGHTS RESERVED.